http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java new file mode 100644 index 0000000..c1a075f --- /dev/null +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java @@ -0,0 +1,128 @@ +package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.surefire.booter.Command; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder; +import static org.apache.maven.surefire.booter.Command.NOOP; +import static org.apache.maven.surefire.booter.Command.SHUTDOWN; +import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Testing cached and immediate commands in {@link TestLessInputStream}. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public class TestLessInputStreamBuilderTest +{ + @Rule + public final ExpectedException e = ExpectedException.none(); + + @Test + public void cachableCommandsShouldBeIterableWithStillOpenIterator() + { + TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); + TestLessInputStream is = builder.build(); + Iterator<Command> iterator = builder.getIterableCachable().iterator(); + + assertFalse( iterator.hasNext() ); + + builder.getCachableCommands().skipSinceNextTest(); + assertTrue( iterator.hasNext() ); + assertThat( iterator.next(), is( SKIP_SINCE_NEXT_TEST ) ); + + assertFalse( iterator.hasNext() ); + + builder.getCachableCommands().noop(); + assertTrue( iterator.hasNext() ); + assertThat( iterator.next(), is( NOOP ) ); + + builder.removeStream( is ); + } + + @Test + public void immediateCommands() + throws IOException + { + TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); + TestLessInputStream is = builder.build(); + assertThat( is.availablePermits(), is( 0 ) ); + is.noop(); + assertThat( is.availablePermits(), is( 1 ) ); + is.beforeNextCommand(); + assertThat( is.availablePermits(), is( 0 ) ); + assertThat( is.nextCommand(), is( NOOP ) ); + assertThat( is.availablePermits(), is( 0 ) ); + e.expect( NoSuchElementException.class ); + is.nextCommand(); + } + + @Test + public void combinedCommands() + throws IOException + { + TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); + TestLessInputStream is = builder.build(); + assertThat( is.availablePermits(), is( 0 ) ); + builder.getCachableCommands().skipSinceNextTest(); + is.noop(); + assertThat( is.availablePermits(), is( 2 ) ); + is.beforeNextCommand(); + assertThat( is.availablePermits(), is( 1 ) ); + assertThat( is.nextCommand(), is( NOOP ) ); + assertThat( is.availablePermits(), is( 1 ) ); + builder.getCachableCommands().skipSinceNextTest(); + assertThat( is.availablePermits(), is( 1 ) ); + builder.getImmediateCommands().shutdown(); + assertThat( is.availablePermits(), is( 2 ) ); + is.beforeNextCommand(); + assertThat( is.nextCommand(), is( SHUTDOWN ) ); + assertThat( is.availablePermits(), is( 1 ) ); + is.beforeNextCommand(); + assertThat( is.nextCommand(), is( SKIP_SINCE_NEXT_TEST ) ); + assertThat( is.availablePermits(), is( 0 ) ); + builder.getImmediateCommands().noop(); + assertThat( is.availablePermits(), is( 1 ) ); + builder.getCachableCommands().shutdown(); + builder.getCachableCommands().shutdown(); + assertThat( is.availablePermits(), is( 2 ) ); + is.beforeNextCommand(); + assertThat( is.nextCommand(), is( NOOP ) ); + assertThat( is.availablePermits(), is( 1 ) ); + is.beforeNextCommand(); + assertThat( is.nextCommand(), is( SHUTDOWN ) ); + assertThat( is.availablePermits(), is( 0 ) ); + e.expect( NoSuchElementException.class ); + is.nextCommand(); + } +}
http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java index feca48b..6fc171e 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java @@ -19,8 +19,6 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; * under the License. */ -import org.apache.maven.surefire.booter.Command; -import org.apache.maven.surefire.booter.MasterProcessCommand; import org.junit.Test; import java.io.IOException; @@ -45,7 +43,7 @@ public class TestProvidingInputStreamTest public void closedStreamShouldReturnEndOfStream() throws IOException { - Queue<Command> commands = new ArrayDeque<Command>(); + Queue<String> commands = new ArrayDeque<String>(); TestProvidingInputStream is = new TestProvidingInputStream( commands ); is.close(); assertThat( is.read(), is( -1 ) ); @@ -55,7 +53,7 @@ public class TestProvidingInputStreamTest public void emptyStreamShouldWaitUntilClosed() throws Exception { - Queue<Command> commands = new ArrayDeque<Command>(); + Queue<String> commands = new ArrayDeque<String>(); final TestProvidingInputStream is = new TestProvidingInputStream( commands ); final Thread streamThread = Thread.currentThread(); FutureTask<Thread.State> futureTask = new FutureTask<Thread.State>( new Callable<Thread.State>() @@ -79,9 +77,9 @@ public class TestProvidingInputStreamTest public void finishedTestsetShouldNotBlock() throws IOException { - Queue<Command> commands = new ArrayDeque<Command>(); - commands.add( new Command( MasterProcessCommand.TEST_SET_FINISHED ) ); + Queue<String> commands = new ArrayDeque<String>(); final TestProvidingInputStream is = new TestProvidingInputStream( commands ); + is.testSetFinished(); new Thread( new Runnable() { public void run() @@ -104,8 +102,8 @@ public class TestProvidingInputStreamTest public void shouldReadTest() throws IOException { - Queue<Command> commands = new ArrayDeque<Command>(); - commands.add( new Command( MasterProcessCommand.RUN_CLASS, "Test" ) ); + Queue<String> commands = new ArrayDeque<String>(); + commands.add( "Test" ); final TestProvidingInputStream is = new TestProvidingInputStream( commands ); new Thread( new Runnable() { @@ -136,6 +134,7 @@ public class TestProvidingInputStreamTest } catch ( InterruptedException e ) { + // do nothing } } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java index 995e2a1..80cf5ff 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java @@ -26,6 +26,7 @@ import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderC import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerStartupConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ForkConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ForkingRunListenerTest; +import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest; import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest; import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest; @@ -65,6 +66,7 @@ import org.junit.runners.Suite; BooterDeserializerStartupConfigurationTest.class, BooterDeserializerProviderConfigurationTest.class, TestProvidingInputStreamTest.class, + TestLessInputStreamBuilderTest.class } ) @RunWith( Suite.class ) public class JUnit4SuiteTest http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java index 4c06f6b..2e925cf 100644 --- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java +++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java @@ -104,7 +104,6 @@ public class SurefirePlugin @Parameter( property = "surefire.useFile", defaultValue = "true" ) private boolean useFile; - /** * Set this to "true" to cause a failure if the none of the tests specified in -Dtest=... are run. Defaults to * "true". @@ -279,6 +278,17 @@ public class SurefirePlugin @Parameter( property = "surefire.excludesFile" ) private File excludesFile; + /** + * Set to "true" to skip remaining tests after first test failure appeared. + * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br/> + * Enable with system property -Dsurefire.skipAfterFailureCount=1 or any number greater than zero. + * Defaults to "0". + * + * @since 2.19 + */ + @Parameter( property = "surefire.skipAfterFailureCount", defaultValue = "0" ) + private int skipAfterFailureCount; + protected int getRerunFailingTestsCount() { return rerunFailingTestsCount; @@ -448,6 +458,11 @@ public class SurefirePlugin this.failIfNoSpecifiedTests = failIfNoSpecifiedTests; } + public int getSkipAfterFailureCount() + { + return skipAfterFailureCount; + } + public boolean isPrintSummary() { return printSummary; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-plugin/src/site/apt/examples/skip-after-failure.apt.vm ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/site/apt/examples/skip-after-failure.apt.vm b/maven-surefire-plugin/src/site/apt/examples/skip-after-failure.apt.vm new file mode 100644 index 0000000..9e7af97 --- /dev/null +++ b/maven-surefire-plugin/src/site/apt/examples/skip-after-failure.apt.vm @@ -0,0 +1,58 @@ + ------ + Skipping Tests After Failure + ------ + Tibor Digana + ------ + July 2015 + ------ + + ~~ Licensed to the Apache Software Foundation (ASF) under one + ~~ or more contributor license agreements. See the NOTICE file + ~~ distributed with this work for additional information + ~~ regarding copyright ownership. The ASF licenses this file + ~~ to you under the Apache License, Version 2.0 (the + ~~ "License"); you may not use this file except in compliance + ~~ with the License. You may obtain a copy of the License at + ~~ + ~~ http://www.apache.org/licenses/LICENSE-2.0 + ~~ + ~~ Unless required by applicable law or agreed to in writing, + ~~ software distributed under the License is distributed on an + ~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~~ KIND, either express or implied. See the License for the + ~~ specific language governing permissions and limitations + ~~ under the License. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + +Skipping Tests After First Failure + + To skip remaining tests after first failure or error has happened + set configuration parameter <<<skipAfterFailureCount>>> to <<<1>>>. + + Prerequisite: use ${project.artifactId} 2.19 or higher, JUnit 4.0 + or higher, or TestNG 5.10 or higher. + + Limitations: Although this feature works in forking modes as well, the + functionality cannot be fully guaranteed (real first failure) in concurrent + mode due to race conditions. + + If version of TestNG is lover than 5.10 while the parameter + <<<skipAfterFailureCount>>> is set, the plugin fails with error: + <<<[ERROR] org.apache.maven.surefire.util.SurefireReflectionException: + java.lang.NoClassDefFoundError: org/testng/IInvokedMethodListener>>> + + +Skipping after the Nth failure or error + + To skip remaining tests after the Nth failure or error has happened + set configuration parameter <<<skipAfterFailureCount>>> to N, where + N is number greater than zero. + + +Notices + + TestNG reports skipped methods however JUnit reports skipped classes. + Preferably use JUnit 4.12 or higher version which fixed thread safety issues. + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-plugin/src/site/site.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/site/site.xml b/maven-surefire-plugin/src/site/site.xml index 1320caf..833c0a9 100644 --- a/maven-surefire-plugin/src/site/site.xml +++ b/maven-surefire-plugin/src/site/site.xml @@ -42,6 +42,7 @@ <item name="Using JUnit" href="examples/junit.html"/> <item name="Using POJO Tests" href="examples/pojo-test.html"/> <item name="Skipping Tests" href="examples/skipping-test.html"/> + <item name="Skip After Failure" href="examples/skip-after-failure.html"/> <item name="Inclusions and Exclusions of Tests" href="examples/inclusion-exclusion.html"/> <item name="Running a Single Test" href="examples/single-test.html"/> <item name="Re-run Failing Tests" href="examples/rerun-failing-tests.html"/> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java index 980f6d9..0433a01 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java @@ -45,7 +45,8 @@ import java.util.Map; */ public class BaseProviderFactory implements DirectoryScannerParametersAware, ReporterConfigurationAware, SurefireClassLoadersAware, TestRequestAware, - ProviderPropertiesAware, ProviderParameters, TestArtifactInfoAware, RunOrderParametersAware, MainCliOptionsAware + ProviderPropertiesAware, ProviderParameters, TestArtifactInfoAware, RunOrderParametersAware, MainCliOptionsAware, + FailFastAware { private static final int ROOT_CHANNEL = 0; @@ -69,6 +70,8 @@ public class BaseProviderFactory private TestArtifactInfo testArtifactInfo; + private int skipAfterFailureCount; + public BaseProviderFactory( ReporterFactory reporterFactory, boolean insideFork ) { this.reporterFactory = reporterFactory; @@ -188,4 +191,19 @@ public class BaseProviderFactory { this.mainCliOptions = mainCliOptions == null ? Collections.<CommandLineOption>emptyList() : mainCliOptions; } + + public int getSkipAfterFailureCount() + { + return skipAfterFailureCount; + } + + public void setSkipAfterFailureCount( int skipAfterFailureCount ) + { + this.skipAfterFailureCount = skipAfterFailureCount; + } + + public boolean isInsideFork() + { + return insideFork; + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java index 992c78c..25bb2d1 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java @@ -19,17 +19,27 @@ package org.apache.maven.surefire.booter; * under the License. */ +import static org.apache.maven.surefire.util.internal.StringUtils.requireNonNull; + /** * Encapsulates data and command sent from master to forked process. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 */ public final class Command { + public static final Command TEST_SET_FINISHED = new Command( MasterProcessCommand.TEST_SET_FINISHED ); + public static final Command SKIP_SINCE_NEXT_TEST = new Command( MasterProcessCommand.SKIP_SINCE_NEXT_TEST ); + public static final Command SHUTDOWN = new Command( MasterProcessCommand.SHUTDOWN ); + public static final Command NOOP = new Command( MasterProcessCommand.NOOP ); + private final MasterProcessCommand command; private final String data; public Command( MasterProcessCommand command, String data ) { - this.command = command; + this.command = requireNonNull( command ); this.data = data; } @@ -47,4 +57,30 @@ public final class Command { return data; } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + Command arg = (Command) o; + + return command == arg.command && ( data == null ? arg.data == null : data.equals( arg.data ) ); + } + + @Override + public int hashCode() + { + int result = command.hashCode(); + result = 31 * result + ( data != null ? data.hashCode() : 0 ); + return result; + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java new file mode 100644 index 0000000..06e47ba --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/FailFastAware.java @@ -0,0 +1,31 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * See the plugin configuration parameter <em>skipAfterFailureCount</em>. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +interface FailFastAware +{ + void setSkipAfterFailureCount( int skipAfterFailureCount ); +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java index 78f29e2..8108609 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingReporterFactory.java @@ -48,7 +48,7 @@ public class ForkingReporterFactory this.originalSystemOut = originalSystemOut; } - public synchronized RunListener createReporter() + public RunListener createReporter() { return new ForkingRunListener( originalSystemOut, testSetChannelId++, isTrimstackTrace ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java index 203651e..e56b94b 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ForkingRunListener.java @@ -29,6 +29,7 @@ import org.apache.maven.surefire.report.ConsoleOutputReceiver; import org.apache.maven.surefire.report.ReportEntry; import org.apache.maven.surefire.report.RunListener; import org.apache.maven.surefire.report.SafeThrowable; +import org.apache.maven.surefire.report.SimpleReportEntry; import org.apache.maven.surefire.report.StackTraceWriter; import org.apache.maven.surefire.util.internal.StringUtils; @@ -80,6 +81,8 @@ public class ForkingRunListener public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N'; + public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S'; + public static final byte BOOTERCODE_ERROR = (byte) 'X'; public static final byte BOOTERCODE_BYE = (byte) 'Z'; @@ -144,15 +147,18 @@ public class ForkingRunListener encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) ); } + public void testExecutionSkippedByUser() + { + encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) ); + } + void sendProps() { Properties systemProperties = System.getProperties(); if ( systemProperties != null ) { - Enumeration<?> propertyKeys = systemProperties.propertyNames(); - - while ( propertyKeys.hasMoreElements() ) + for ( Enumeration<?> propertyKeys = systemProperties.propertyNames(); propertyKeys.hasMoreElements(); ) { String key = (String) propertyKeys.nextElement(); String value = systemProperties.getProperty( key ); @@ -202,7 +208,10 @@ public class ForkingRunListener private void encodeAndWriteToTarget( String string ) { byte[] encodeBytes = encodeStringForForkCommunication( string ); - target.write( encodeBytes, 0, encodeBytes.length ); + synchronized ( target ) // See notes about synchronization/thread safety in class javadoc + { + target.write( encodeBytes, 0, encodeBytes.length ); + } } private String toPropertyString( String key, String value ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java index 0a553f7..3512b5a 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java @@ -40,7 +40,7 @@ public enum MasterProcessCommand { RUN_CLASS( 0, String.class ), TEST_SET_FINISHED( 1, Void.class ), - STOP_ON_NEXT_TEST( 2, Void.class ), + SKIP_SINCE_NEXT_TEST( 2, Void.class ), SHUTDOWN( 3, Void.class ), /** To tell a forked process that the master process is still alive. Repeated after 30 seconds. */ NOOP( 4, Void.class ); @@ -65,6 +65,11 @@ public enum MasterProcessCommand return dataType; } + public boolean hasDataType() + { + return dataType != Void.class; + } + @SuppressWarnings( "checkstyle:magicnumber" ) public byte[] encode( String data ) { @@ -119,7 +124,8 @@ public enum MasterProcessCommand if ( command.getDataType() == Void.class ) { // must read entire sequence to get to the next command; cannot be above the loop - throw new IOException( "Command " + command + " read Void data with length " + dataLength ); + throw new IOException( format( "Command %s unexpectedly read Void data with length %d.", + command, dataLength ) ); } if ( total != dataLength ) @@ -129,7 +135,7 @@ public enum MasterProcessCommand throw new EOFException( "stream closed" ); } - throw new EOFException( format( "%s read %d out of %d bytes", + throw new IOException( format( "%s read %d out of %d bytes", MasterProcessCommand.class, total, dataLength ) ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java new file mode 100644 index 0000000..89a4f89 --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java @@ -0,0 +1,28 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * listener interface + */ +public interface MasterProcessListener +{ + void update( Command command ); +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java new file mode 100644 index 0000000..8c36bd2 --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java @@ -0,0 +1,421 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicReference; + +import static java.lang.Thread.State.NEW; +import static java.lang.Thread.State.RUNNABLE; +import static java.lang.Thread.State.TERMINATED; +import static java.util.concurrent.locks.LockSupport.park; +import static java.util.concurrent.locks.LockSupport.unpark; +import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; +import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED; +import static org.apache.maven.surefire.booter.MasterProcessCommand.decode; +import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication; +import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank; +import static org.apache.maven.surefire.util.internal.StringUtils.isBlank; +import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread; +import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST; + +/** + * Reader of commands coming from plugin(master) process. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public final class MasterProcessReader +{ + private static final MasterProcessReader READER = new MasterProcessReader(); + + private final Queue<TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener>> listeners + = new ConcurrentLinkedQueue<TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener>>(); + + private final Thread commandThread = newDaemonThread( new CommandRunnable(), "surefire-forkedjvm-command-thread" ); + + private final AtomicReference<Thread.State> state = new AtomicReference<Thread.State>( NEW ); + + private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); + + private final Node head = new Node(); + + private volatile Node tail = head; + + private static class Node + { + final AtomicReference<Node> successor = new AtomicReference<Node>(); + volatile String item; + } + + public static MasterProcessReader getReader() + { + final MasterProcessReader reader = READER; + if ( reader.state.compareAndSet( NEW, RUNNABLE ) ) + { + reader.commandThread.start(); + } + return reader; + } + + /** + * @param listener listener called with <em>Any</em> {@link MasterProcessCommand command type} + */ + public void addListener( MasterProcessListener listener ) + { + listeners.add( new TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener>( null, listener ) ); + } + + public void addListener( MasterProcessCommand cmd, MasterProcessListener listener ) + { + listeners.add( new TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener>( cmd, listener ) ); + } + + public void removeListener( MasterProcessListener listener ) + { + for ( Iterator<TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener>> it = listeners.iterator(); + it.hasNext(); ) + { + TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener> listenerWrapper = it.next(); + if ( listener == listenerWrapper.getP2() ) + { + it.remove(); + } + } + } + + Iterable<String> getIterableClasses( PrintStream originalOutStream ) + { + return new ClassesIterable( head, originalOutStream ); + } + + public void stop() + { + if ( state.compareAndSet( NEW, TERMINATED ) || state.compareAndSet( RUNNABLE, TERMINATED ) ) + { + makeQueueFull(); + listeners.clear(); + commandThread.interrupt(); + } + } + + private static boolean isLastNode( Node current ) + { + return current.successor.get() == current; + } + + private boolean isQueueFull() + { + return isLastNode( tail ); + } + + private boolean addToQueue( String item ) + { + if ( tail.item == null ) + { + tail.item = item; + Node newNode = new Node(); + tail.successor.set( newNode ); + tail = newNode; + return true; + } + else + { + return false; + } + } + + /** + * After this method returns the queue is closed, new item cannot be added and method + * {@link #isQueueFull()} returns true. + */ + @SuppressWarnings( { "all", "checkstyle:needbraces", "checkstyle:emptystatement" } ) + public void makeQueueFull() + { + // order between (#compareAndSet, and #get) matters in multithreading + for ( Node tail = this.tail; + !tail.successor.compareAndSet( null, tail ) && tail.successor.get() != tail; + tail = tail.successor.get() ); + } + + private void insertForQueue( Command cmd ) + { + MasterProcessCommand expectedCommandType = cmd.getCommandType(); + switch ( expectedCommandType ) + { + case RUN_CLASS: + addToQueue( cmd.getData() ); + break; + case TEST_SET_FINISHED: + makeQueueFull(); + break; + default: + // checkstyle noop + break; + } + } + + private void insertForListeners( Command cmd ) + { + MasterProcessCommand expectedCommandType = cmd.getCommandType(); + for ( TwoPropertiesWrapper<MasterProcessCommand, MasterProcessListener> listenerWrapper + : MasterProcessReader.this.listeners ) + { + MasterProcessCommand commandType = listenerWrapper.getP1(); + MasterProcessListener listener = listenerWrapper.getP2(); + if ( commandType == null || commandType == expectedCommandType ) + { + listener.update( cmd ); + } + } + } + + private void insert( Command cmd ) + { + insertForQueue( cmd ); + insertForListeners( cmd ); + } + + private final class ClassesIterable + implements Iterable<String> + { + private final Node head; + private final PrintStream originalOutStream; + + ClassesIterable( Node head, PrintStream originalOutStream ) + { + this.head = head; + this.originalOutStream = originalOutStream; + } + + public Iterator<String> iterator() + { + return new ClassesIterator( head, originalOutStream ); + } + } + + private final class ClassesIterator + implements Iterator<String> + { + private final PrintStream originalOutStream; + + private Node current; + + private String clazz; + + private ClassesIterator( Node current, PrintStream originalOutStream ) + { + this.current = current; + this.originalOutStream = originalOutStream; + } + + public boolean hasNext() + { + popUnread(); + return isNotBlank( clazz ); + } + + public String next() + { + popUnread(); + try + { + if ( isBlank( clazz ) ) + { + throw new NoSuchElementException(); + } + else + { + return clazz; + } + } + finally + { + clazz = null; + } + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + + private void popUnread() + { + if ( state.get() == TERMINATED ) + { + clazz = null; + return; + } + + if ( isBlank( clazz ) ) + { + do + { + requestNextTest(); + if ( isLastNode( current ) ) + { + clazz = null; + } + else if ( current.item == null ) + { + do + { + await(); + /** + * {@link java.util.concurrent.locks.LockSupport#park()} + * may spuriously (that is, for no reason) return, therefore the loop here. + */ + } while ( current.item == null && !isLastNode( current ) ); + clazz = current.item; + current = current.successor.get(); + } + else + { + clazz = current.item; + current = current.successor.get(); + } + } + while ( tryNullWhiteClass() ); + } + + if ( state.get() == TERMINATED ) + { + clazz = null; + } + } + + private boolean tryNullWhiteClass() + { + if ( clazz != null && isBlank( clazz ) ) + { + clazz = null; + return true; + } + else + { + return false; + } + } + + private void requestNextTest() + { + byte[] encoded = encodeStringForForkCommunication( ( (char) BOOTERCODE_NEXT_TEST ) + ",0,want more!\n" ); + originalOutStream.write( encoded, 0, encoded.length ); + } + } + + private Command read( DataInputStream stdIn ) + throws IOException + { + Command command = decode( stdIn ); + if ( command != null ) + { + insertForQueue( command ); + } + return command; + } + + private void await() + { + final Thread currentThread = Thread.currentThread(); + try + { + waiters.add( currentThread ); + park(); + } + finally + { + waiters.remove( currentThread ); + } + } + + private void wakeupWaiters() + { + for ( Thread waiter : waiters ) + { + unpark( waiter ); + } + } + + private final class CommandRunnable + implements Runnable + { + public void run() + { + DataInputStream stdIn = new DataInputStream( System.in ); + boolean isTestSetFinished = false; + try + { + while ( MasterProcessReader.this.state.get() == RUNNABLE ) + { + Command command = read( stdIn ); + if ( command == null ) + { + System.err.println( "[SUREFIRE] std/in stream corrupted: first sequence not recognized" ); + break; + } + else + { + if ( command.getCommandType() == TEST_SET_FINISHED ) + { + isTestSetFinished = true; + wakeupWaiters(); + } + else if ( command.getCommandType() == RUN_CLASS ) + { + wakeupWaiters(); + } + insertForListeners( command ); + } + } + } + catch ( EOFException e ) + { + MasterProcessReader.this.state.set( TERMINATED ); + } + catch ( IOException e ) + { + MasterProcessReader.this.state.set( TERMINATED ); + if ( !( e.getCause() instanceof InterruptedException ) ) + { + System.err.println( "[SUREFIRE] std/in stream corrupted" ); + e.printStackTrace(); + } + } + finally + { + // ensure fail-safe iterator as well as safe to finish in for-each loop using ClassesIterator + if ( !isTestSetFinished ) + { + insert( new Command( TEST_SET_FINISHED ) ); + } + wakeupWaiters(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java index b271171..cf07331 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java @@ -294,6 +294,11 @@ public class SurefireReflector } } + public void setSkipAfterFailureCount( Object o, int skipAfterFailureCount ) + { + ReflectionUtils.invokeSetter( o, "setSkipAfterFailureCount", int.class, skipAfterFailureCount ); + } + public void setDirectoryScannerParameters( Object o, DirectoryScannerParameters dirScannerParams ) { final Object param = createDirectoryScannerParameters( dirScannerParams ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/booter/TwoPropertiesWrapper.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/TwoPropertiesWrapper.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TwoPropertiesWrapper.java new file mode 100644 index 0000000..5c6e690 --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/TwoPropertiesWrapper.java @@ -0,0 +1,50 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Internal generic wrapper. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + * @param <P1> first property + * @param <P2> second property + */ +final class TwoPropertiesWrapper<P1, P2> +{ + private final P1 p1; + private final P2 p2; + + TwoPropertiesWrapper( P1 p1, P2 p2 ) + { + this.p1 = p1; + this.p2 = p2; + } + + P1 getP1() + { + return p1; + } + + P2 getP2() + { + return p2; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java index cb6da9a..708b436 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java @@ -132,4 +132,15 @@ public interface ProviderParameters TestArtifactInfo getTestArtifactInfo(); List<CommandLineOption> getMainCliOptions(); + + /** + * Defaults to 0. Configured with parameter <em>skipAfterFailureCount</em> in POM. + */ + int getSkipAfterFailureCount(); + + /** + * @return {@code true} if test provider appears in forked jvm; Otherwise {@code false} means + * in-plugin provider. + */ + boolean isInsideFork(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java index e46ebcf..b964430 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/RunListener.java @@ -66,7 +66,6 @@ public interface RunListener */ void testAssumptionFailure( ReportEntry report ); - /** * Event fired when a test ended with an error (non anticipated problem) * @@ -87,4 +86,11 @@ public interface RunListener * @param report The report entry to log for */ void testSkipped( ReportEntry report ); + + /** + * Event fired skipping an execution of remaining test-set in other fork(s); or does nothing if no forks. + * The method is called by {@link org.apache.maven.surefire.providerapi.SurefireProvider}.<p> + * (The event is fired after the Nth test failed to signal skipping the rest of test-set.) + */ + void testExecutionSkippedByUser(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java index 2229815..9455b04 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/report/SimpleReportEntry.java @@ -35,6 +35,11 @@ public class SimpleReportEntry private final String message; + public SimpleReportEntry() + { + this( null, null ); + } + public SimpleReportEntry( String source, String name ) { this( source, name, null, null ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java index 00e7bbc..717ab4e 100644 --- a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java +++ b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java @@ -29,7 +29,6 @@ import java.io.IOException; import static org.apache.maven.surefire.booter.MasterProcessCommand.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; import static org.junit.Assert.assertNotNull; /** @@ -98,7 +97,7 @@ public class MasterProcessCommandTest decoded = command.toDataTypeAsString( encoded ); assertNull( decoded ); break; - case STOP_ON_NEXT_TEST: + case SKIP_SINCE_NEXT_TEST: assertEquals( Void.class, command.getDataType() ); encoded = command.fromDataType( dummyData ); assertThat( encoded.length, is( 0 ) ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-api/src/test/java/org/apache/maven/surefire/report/MockReporter.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/report/MockReporter.java b/surefire-api/src/test/java/org/apache/maven/surefire/report/MockReporter.java index ad26f85..91ff446 100644 --- a/surefire-api/src/test/java/org/apache/maven/surefire/report/MockReporter.java +++ b/surefire-api/src/test/java/org/apache/maven/surefire/report/MockReporter.java @@ -114,6 +114,15 @@ public class MockReporter testIgnored.incrementAndGet(); } + public void testExecutionSkippedByUser() + { + } + + public void testSkippedByUser( ReportEntry report ) + { + testSkipped( report ); + } + public String getFirstEvent() { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java index be5c4c3..749f0e9 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java @@ -53,4 +53,5 @@ public final class BooterConstants public static final String FORKTESTSET_PREFER_TESTS_FROM_IN_STREAM = "preferTestsFromInStream"; public static final String RERUN_FAILING_TESTS_COUNT = "rerunFailingTestsCount"; public static final String MAIN_CLI_OPTIONS = "mainCliOptions"; + public static final String FAIL_FAST_COUNT = "failFastCount"; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java index 5b89ed0..b8f2d0b 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java @@ -86,7 +86,7 @@ public class BooterDeserializer DirectoryScannerParameters dirScannerParams = new DirectoryScannerParameters( testClassesDirectory, includes, excludes, specificTests, - properties.getBooleanObjectProperty( FAILIFNOTESTS ), runOrder ); + properties.getBooleanProperty( FAILIFNOTESTS ), runOrder ); RunOrderParameters runOrderParameters = new RunOrderParameters( runOrder, runStatisticsFile ); @@ -96,14 +96,16 @@ public class BooterDeserializer rerunFailingTestsCount ); ReporterConfiguration reporterConfiguration = - new ReporterConfiguration( reportsDirectory, properties.getBooleanObjectProperty( ISTRIMSTACKTRACE ) ); + new ReporterConfiguration( reportsDirectory, properties.getBooleanProperty( ISTRIMSTACKTRACE ) ); Collection<String> cli = properties.getStringList( MAIN_CLI_OPTIONS ); + int failFastCount = properties.getIntProperty( FAIL_FAST_COUNT ); + return new ProviderConfiguration( dirScannerParams, runOrderParameters, properties.getBooleanProperty( FAILIFNOTESTS ), reporterConfiguration, testNg, testSuiteDefinition, properties.getProperties(), typeEncodedTestForFork, - preferTestsFromInStream, fromStrings( cli ) ); + preferTestsFromInStream, fromStrings( cli ), failFastCount ); } public StartupConfiguration getProviderConfiguration() http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java index 518c523..7567cd5 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java @@ -193,7 +193,7 @@ public final class ForkedBooter private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory, ProviderConfiguration providerConfiguration, boolean insideFork, - StartupConfiguration startupConfiguration1, + StartupConfiguration startupConfig, boolean restoreStreams ) throws TestSetFailedException, InvocationTargetException { @@ -202,11 +202,10 @@ public final class ForkedBooter // Note that System.out/System.err are also read in the "ReporterConfiguration" instatiation // in createProvider below. These are the same values as here. - final SurefireProvider provider = - createProviderInCurrentClassloader( startupConfiguration1, insideFork, providerConfiguration, factory ); try { - return provider.invoke( testSet ); + return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfiguration, factory ) + .invoke( testSet ); } finally { @@ -233,6 +232,7 @@ public final class ForkedBooter bpf.setRunOrderParameters( providerConfiguration.getRunOrderParameters() ); bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() ); bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() ); + bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() ); return (SurefireProvider) ReflectionUtils.instantiateOneArg( classLoader, startupConfiguration1.getActualClassName(), ProviderParameters.class, bpf ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java deleted file mode 100644 index 89a4f89..0000000 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.apache.maven.surefire.booter; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * listener interface - */ -public interface MasterProcessListener -{ - void update( Command command ); -} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java deleted file mode 100644 index 2dde2e1..0000000 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java +++ /dev/null @@ -1,257 +0,0 @@ -package org.apache.maven.surefire.booter; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import org.apache.maven.surefire.util.internal.DaemonThreadFactory; - -import java.io.DataInputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicReference; - -import static java.lang.Thread.State.NEW; -import static java.lang.Thread.State.RUNNABLE; -import static java.lang.Thread.State.TERMINATED; -import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED; -import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; -import static org.apache.maven.surefire.booter.MasterProcessCommand.decode; -import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication; -import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank; -import static org.apache.maven.surefire.util.internal.StringUtils.isBlank; -import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST; - -/** - * Testing singleton {@code MasterProcessReader}. - * - * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> - * @since 2.19 - */ -public final class MasterProcessReader -{ - private static final MasterProcessReader READER = new MasterProcessReader(); - - private final BlockingQueue<Command> classes = new LinkedBlockingQueue<Command>(); - - private final Queue<MasterProcessListener> listeners = new ConcurrentLinkedQueue<MasterProcessListener>(); - - private final Thread commandThread = - DaemonThreadFactory.newDaemonThread( new CommandRunnable(), "surefire-forkedjvm-command-thread" ); - - private final AtomicReference<Thread.State> state = new AtomicReference<Thread.State>( NEW ); - - public static MasterProcessReader getReader() - { - if ( READER.state.compareAndSet( NEW, RUNNABLE ) ) - { - READER.commandThread.start(); - } - return READER; - } - - private MasterProcessReader() - { - commandThread.setDaemon( true ); - } - - Iterable<String> getIterableClasses( PrintStream originalOutStream ) - { - return new ClassesIterable( originalOutStream ); - } - - void stop( boolean interruptCurrentThread ) - { - if ( state.compareAndSet( NEW, TERMINATED ) || state.compareAndSet( RUNNABLE, TERMINATED ) ) - { - classes.drainTo( new ArrayList<Command>() ); - listeners.clear(); - commandThread.interrupt(); - if ( interruptCurrentThread ) - { - Thread.currentThread().interrupt(); - } - } - } - - private final class ClassesIterable - implements Iterable<String> - { - private final ClassesIterator it; - - public ClassesIterable( PrintStream originalOutStream ) - { - it = new ClassesIterator( originalOutStream ); - } - - public Iterator<String> iterator() - { - return it; - } - } - - private final class ClassesIterator - implements Iterator<String> - { - private final PrintStream originalOutStream; - - private String clazz; - - private ClassesIterator( PrintStream originalOutStream ) - { - this.originalOutStream = originalOutStream; - } - - public boolean hasNext() - { - popUnread(); - return isNotBlank( clazz ); - } - - public String next() - { - popUnread(); - try - { - if ( isBlank( clazz ) ) - { - throw new NoSuchElementException(); - } - else - { - return clazz; - } - } - finally - { - clazz = null; - } - } - - public void remove() - { - throw new UnsupportedOperationException(); - } - - private void popUnread() - { - if ( state.get() == TERMINATED ) - { - clazz = null; - return; - } - - if ( isBlank( clazz ) ) - { - requestNextTest(); - try - { - do - { - Command command = MasterProcessReader.this.classes.take(); - clazz = command.getCommandType() == TEST_SET_FINISHED ? null : command.getData(); - } - while ( tryNullWhiteClass() ); - } - catch ( InterruptedException e ) - { - Thread.currentThread().interrupt(); - clazz = null; - } - } - - if ( state.get() == TERMINATED ) - { - clazz = null; - } - } - - private boolean tryNullWhiteClass() - { - if ( clazz != null && isBlank( clazz ) ) - { - clazz = null; - return true; - } - else - { - return false; - } - } - - private void requestNextTest() - { - byte[] encoded = encodeStringForForkCommunication( ( (char) BOOTERCODE_NEXT_TEST ) + ",0,want more!\n" ); - originalOutStream.write( encoded, 0, encoded.length ); - } - } - - private final class CommandRunnable - implements Runnable - { - public void run() - { - DataInputStream stdIn = new DataInputStream( System.in ); - try - { - while ( MasterProcessReader.this.state.get() == RUNNABLE ) - { - Command command = decode( stdIn ); - if ( command != null ) - { - // command is null if stream is corrupted, i.e. the first sequence could not be recognized - insert( command ); - } - } - } - catch ( IOException e ) - { - MasterProcessReader.this.state.set( TERMINATED ); - } - finally - { - // ensure fail-safe iterator as well as safe to finish in for-each loop using ClassesIterator - insert( new Command( TEST_SET_FINISHED ) ); - } - } - - @SuppressWarnings( "unchecked" ) - private void insert( Command cmd ) - { - MasterProcessCommand commandType = cmd.getCommandType(); - if ( commandType == RUN_CLASS || commandType == TEST_SET_FINISHED ) - { - MasterProcessReader.this.classes.add( cmd ); - } - else - { - for ( MasterProcessListener listener : MasterProcessReader.this.listeners ) - { - listener.update( cmd ); - } - } - } - } -} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java index 61f204d..69c7f2c 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java @@ -66,13 +66,15 @@ public class ProviderConfiguration private final List<CommandLineOption> mainCliOptions; + private int skipAfterFailureCount; + @SuppressWarnings( "checkstyle:parameternumber" ) public ProviderConfiguration( DirectoryScannerParameters directoryScannerParameters, RunOrderParameters runOrderParameters, boolean failIfNoTests, ReporterConfiguration reporterConfiguration, TestArtifactInfo testArtifact, TestRequest testSuiteDefinition, Map<String, String> providerProperties, TypeEncodedValue typeEncodedTestSet, boolean readTestsFromInStream, - List<CommandLineOption> mainCliOptions ) + List<CommandLineOption> mainCliOptions, int skipAfterFailureCount ) { this.runOrderParameters = runOrderParameters; this.providerProperties = providerProperties; @@ -84,6 +86,7 @@ public class ProviderConfiguration this.forkTestSet = typeEncodedTestSet; this.readTestsFromInStream = readTestsFromInStream; this.mainCliOptions = mainCliOptions; + this.skipAfterFailureCount = skipAfterFailureCount; } public ReporterConfiguration getReporterConfiguration() @@ -152,4 +155,14 @@ public class ProviderConfiguration { return mainCliOptions; } + + public void setSkipAfterFailureCount( int skipAfterFailureCount ) + { + this.skipAfterFailureCount = skipAfterFailureCount; + } + + public int getSkipAfterFailureCount() + { + return skipAfterFailureCount; + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java index c5d93ab..5ad587f 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java @@ -46,8 +46,7 @@ public class ProviderFactory private final Object reporterManagerFactory; - private static final Class[] INVOKE_PARAMETERS = new Class[]{ Object.class }; - + private static final Class[] INVOKE_PARAMETERS = { Object.class }; public ProviderFactory( StartupConfiguration startupConfiguration, ProviderConfiguration providerConfiguration, ClassLoader testsClassLoader, Object reporterManagerFactory ) @@ -61,7 +60,7 @@ public class ProviderFactory public static RunResult invokeProvider( Object testSet, ClassLoader testsClassLoader, Object factory, ProviderConfiguration providerConfiguration, boolean insideFork, - StartupConfiguration startupConfiguration1, boolean restoreStreams ) + StartupConfiguration startupConfig, boolean restoreStreams ) throws TestSetFailedException, InvocationTargetException { final PrintStream orgSystemOut = System.out; @@ -69,12 +68,11 @@ public class ProviderFactory // Note that System.out/System.err are also read in the "ReporterConfiguration" instantiation // in createProvider below. These are the same values as here. - ProviderFactory providerFactory = - new ProviderFactory( startupConfiguration1, providerConfiguration, testsClassLoader, factory ); - final SurefireProvider provider = providerFactory.createProvider( insideFork ); try { - return provider.invoke( testSet ); + return new ProviderFactory( startupConfig, providerConfiguration, testsClassLoader, factory ) + .createProvider( insideFork ) + .invoke( testSet ); } finally { @@ -101,6 +99,7 @@ public class ProviderFactory surefireReflector.setRunOrderParameters( o, providerConfiguration.getRunOrderParameters() ); surefireReflector.setIfDirScannerAware( o, providerConfiguration.getDirScannerParams() ); surefireReflector.setMainCliOptions( o, providerConfiguration.getMainCliOptions() ); + surefireReflector.setSkipAfterFailureCount( o, providerConfiguration.getSkipAfterFailureCount() ); Object provider = surefireReflector.instantiateProvider( startupConfiguration.getActualClassName(), o ); currentThread.setContextClassLoader( systemClassLoader ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java index 9440e0d..ae299c1 100644 --- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java @@ -30,13 +30,19 @@ import java.io.InputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Random; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME; import static org.junit.Assert.assertFalse; @@ -56,6 +62,7 @@ public class MasterProcessReaderTest { private final BlockingQueue<Byte> blockingStream = new LinkedBlockingQueue<Byte>(); private InputStream realInputStream; + private MasterProcessReader reader; @Before public void init() @@ -63,25 +70,26 @@ public class MasterProcessReaderTest { Thread.interrupted(); realInputStream = System.in; - addThisTestToPipeline( MasterProcessReaderTest.class.getName() ); + addThisTestToPipeline( getClass().getName() ); System.setIn( new SystemInputStream() ); + reader = MasterProcessReader.getReader(); } @After public void deinit() { + reader.stop(); System.setIn( realInputStream ); } @Test public void readJustOneClass() throws Exception { - MasterProcessReader reader = MasterProcessReader.getReader(); Iterator<String> it = reader.getIterableClasses( new PrintStream( new ByteArrayOutputStream() ) ) .iterator(); assertTrue( it.hasNext() ); assertThat( it.next(), is( getClass().getName() ) ); - reader.stop( true ); + reader.stop(); assertFalse( it.hasNext() ); try { @@ -98,7 +106,6 @@ public class MasterProcessReaderTest public void stopBeforeReadInThread() throws Throwable { - final MasterProcessReader reader = MasterProcessReader.getReader(); Runnable runnable = new Runnable() { public void run() @@ -110,7 +117,7 @@ public class MasterProcessReaderTest }; FutureTask<Object> futureTask = new FutureTask<Object>( runnable, null ); Thread t = new Thread( futureTask ); - reader.stop( false ); + reader.stop(); t.start(); try { @@ -126,7 +133,6 @@ public class MasterProcessReaderTest public void readTwoClassesInThread() throws Throwable { - final MasterProcessReader reader = MasterProcessReader.getReader(); final CountDownLatch counter = new CountDownLatch( 1 ); Runnable runnable = new Runnable() { @@ -152,9 +158,45 @@ public class MasterProcessReaderTest { throw e.getCause(); } - finally + } + + @Test + public void readTwoClassesInTwoThreads() + throws Throwable + { + final Iterable<String> lazyTestSet = + reader.getIterableClasses( new PrintStream( new ByteArrayOutputStream() ) ); + + class Provider implements Runnable + { + public void run() + { + Iterator<String> it = lazyTestSet.iterator(); + assertThat( it.next(), is( MasterProcessReaderTest.class.getName() ) ); + assertThat( it.next(), is( PropertiesWrapperTest.class.getName() ) ); + assertFalse( it.hasNext() ); + } + } + + ExecutorService es = Executors.newFixedThreadPool( 2 ); + Future<?> result1 = es.submit( new Provider() ); + Random rnd = new Random(); + TimeUnit.MILLISECONDS.sleep( rnd.nextInt( 50 ) ); + Future<?> result2 = es.submit( new Provider() ); + TimeUnit.MILLISECONDS.sleep( rnd.nextInt( 50 ) ); + addThisTestToPipeline( PropertiesWrapperTest.class.getName() ); + TimeUnit.MILLISECONDS.sleep( rnd.nextInt( 50 ) ); + addTestSetFinishedToPipeline(); + for ( Future<?> result : Arrays.asList( result1, result2 ) ) { - reader.stop( false ); + try + { + result.get(); + } + catch ( ExecutionException e ) + { + throw e.getCause(); + } } } @@ -190,4 +232,13 @@ public class MasterProcessReaderTest blockingStream.add( buffer.get() ); } } + + private void addTestSetFinishedToPipeline() + throws UnsupportedEncodingException + { + for ( byte b : MasterProcessCommand.TEST_SET_FINISHED.encode() ) + { + blockingStream.add( b ); + } + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java ---------------------------------------------------------------------- diff --git a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java index 35dc888..e43a71e 100644 --- a/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java +++ b/surefire-providers/common-junit4/src/main/java/org/apache/maven/surefire/common/junit4/JUnit4Reflector.java @@ -22,38 +22,36 @@ package org.apache.maven.surefire.common.junit4; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import org.apache.maven.surefire.util.ReflectionUtils; import org.apache.maven.surefire.util.SurefireReflectionException; import org.junit.Ignore; import org.junit.runner.Description; import org.junit.runner.Request; +import static org.apache.maven.surefire.util.ReflectionUtils.tryGetMethod; +import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray; + /** * JUnit4 reflection helper * */ public final class JUnit4Reflector { - private static final Class[] PARAMS = new Class[]{ Class.class }; + private static final Class[] PARAMS = { Class.class }; + + private static final Class[] IGNORE_PARAMS = { Ignore.class }; - private static final Class[] IGNORE_PARAMS = new Class[]{ Ignore.class }; + private static final Class[] PARAMS_WITH_ANNOTATIONS = { String.class, Annotation[].class }; private JUnit4Reflector() { throw new IllegalStateException( "not instantiable constructor" ); } - public static Ignore getAnnotatedIgnore( Description description ) + public static Ignore getAnnotatedIgnore( Description d ) { - Method getAnnotation = ReflectionUtils.tryGetMethod( description.getClass(), "getAnnotation", PARAMS ); - - if ( getAnnotation == null ) - { - return null; - } - - return (Ignore) ReflectionUtils.invokeMethodWithArray( description, getAnnotation, IGNORE_PARAMS ); + Method getAnnotation = tryGetMethod( d.getClass(), "getAnnotation", PARAMS ); + return getAnnotation == null ? null : (Ignore) invokeMethodWithArray( d, getAnnotation, IGNORE_PARAMS ); } public static String getAnnotatedIgnoreValue( Description description ) @@ -92,20 +90,79 @@ public final class JUnit4Reflector } catch ( NoSuchMethodError e ) { - try - { - return (Description) Description.class.getDeclaredMethod( "createSuiteDescription", - String.class, Annotation[].class ) - .invoke( null, description, new Annotation[0] ); - } - catch ( InvocationTargetException e1 ) + Method method = tryGetMethod( Description.class, "createSuiteDescription", PARAMS_WITH_ANNOTATIONS ); + Object[] parameters = { description, new Annotation[0] }; + // may throw exception probably with JUnit 5.x + return (Description) invokeMethodWithArray( null, method, parameters ); + } + } + + public static Description createDescription( String description, Annotation... annotations ) + { + Method method = tryGetMethod( Description.class, "createSuiteDescription", PARAMS_WITH_ANNOTATIONS ); + return method == null + ? Description.createSuiteDescription( description ) + : (Description) invokeMethodWithArray( null, method, new Object[] { description, annotations } ); + } + + public static Ignore createIgnored( String value ) + { + return new IgnoredWithUserError( value ); + } + + private static class IgnoredWithUserError + implements Annotation, Ignore + { + private final String value; + + public IgnoredWithUserError( String value ) + { + this.value = value; + } + + public IgnoredWithUserError() + { + this( "" ); + } + + public String value() + { + return value; + } + + public Class<? extends Annotation> annotationType() + { + return Ignore.class; + } + + @Override + public int hashCode() + { + return value == null ? 0 : value.hashCode(); + } + + @Override + public boolean equals( Object obj ) + { + return obj instanceof Annotation && obj instanceof Ignore && equalValue( ( Ignore ) obj ); + } + + @Override + public String toString() + { + return String.format( "%s(%s)", Ignore.class, value ); + } + + private boolean equalValue( Ignore ignore ) + { + if ( ignore == null ) { - throw new SurefireReflectionException( e1.getCause() ); + return false; } - catch ( Exception e1 ) + else { - // probably JUnit 5.x - throw new SurefireReflectionException( e1 ); + String val = ignore.value(); + return val == null ? value == null : val.equals( value ); } } }