From 365d3912d58bffe1ffbd109474c4d89b35dbd0af Mon Sep 17 00:00:00 2001 From: brasmusson Date: Thu, 1 Aug 2013 17:03:48 +0200 Subject: [PATCH 1/4] Return exit code 0 when no features are found Return exit code 0 when no features are found, or if no found features match the tags filter used. Print the same message, that is used in the currently thrown CucumberException when this occurs, as information before the summary printout. --- .../java/cucumber/runtime/RuntimeGlue.java | 2 +- .../java/cucumber/runtime/RuntimeOptions.java | 2 +- .../runtime/model/CucumberFeature.java | 10 ++-- .../java/cucumber/runtime/RuntimeTest.java | 26 +++++++++ .../runtime/model/CucumberFeatureTest.java | 54 ++++++++++--------- .../cucumber/runtime/junit/CucumberTest.java | 5 +- .../junit/ExecutionUnitRunnerTest.java | 6 ++- 7 files changed, 69 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/cucumber/runtime/RuntimeGlue.java b/core/src/main/java/cucumber/runtime/RuntimeGlue.java index 4a7735b39b..7985640876 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeGlue.java +++ b/core/src/main/java/cucumber/runtime/RuntimeGlue.java @@ -103,7 +103,7 @@ private List stepDefinitionMatches(String uri, Step step) { @Override public void writeStepdefsJson(List featurePaths, URL dotCucumber) throws IOException { if (dotCucumber != null) { - List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS); + List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS, null); List metaStepdefs = new StepdefGenerator().generate(stepDefinitionsByPattern.values(), features); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String json = gson.toJson(metaStepdefs); diff --git a/core/src/main/java/cucumber/runtime/RuntimeOptions.java b/core/src/main/java/cucumber/runtime/RuntimeOptions.java index e4eb13a642..9e7cebe9be 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeOptions.java +++ b/core/src/main/java/cucumber/runtime/RuntimeOptions.java @@ -121,7 +121,7 @@ private void printUsage() { } public List cucumberFeatures(ResourceLoader resourceLoader) { - return load(resourceLoader, featurePaths, filters); + return load(resourceLoader, featurePaths, filters, System.out); } public Formatter formatter(ClassLoader classLoader) { diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java index eac2dd5175..7435780e7e 100644 --- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java +++ b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java @@ -1,6 +1,5 @@ package cucumber.runtime.model; -import cucumber.runtime.CucumberException; import cucumber.runtime.FeatureBuilder; import cucumber.runtime.Runtime; import cucumber.runtime.io.Resource; @@ -15,6 +14,7 @@ import gherkin.formatter.model.ScenarioOutline; import gherkin.formatter.model.Step; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -29,7 +29,7 @@ public class CucumberFeature { private I18n i18n; private CucumberScenarioOutline currentScenarioOutline; - public static List load(ResourceLoader resourceLoader, List featurePaths, final List filters) { + public static List load(ResourceLoader resourceLoader, List featurePaths, final List filters, PrintStream optionalOut) { final List cucumberFeatures = new ArrayList(); final FeatureBuilder builder = new FeatureBuilder(cucumberFeatures); boolean resourceFound = false; @@ -40,11 +40,11 @@ public static List load(ResourceLoader resourceLoader, List", step, reporter, i18n); } + private ResourceLoader createResourceLoaderThatFindsNoFeatures() { + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources(anyString(), eq(".feature"))).thenReturn(Collections.emptyList()); + return resourceLoader; + } + private Runtime createStrictRuntime() { return createRuntime("-g", "anything", "--strict"); } @@ -355,9 +373,17 @@ private Runtime createNonStrictRuntime() { return createRuntime("-g", "anything"); } + private Runtime createStrictRuntime(ResourceLoader resourceLoader) { + return createRuntime(resourceLoader, Thread.currentThread().getContextClassLoader(), "-g", "anything", "--strict"); + } + private Runtime createRuntime(String... runtimeArgs) { ResourceLoader resourceLoader = mock(ResourceLoader.class); ClassLoader classLoader = mock(ClassLoader.class); + return createRuntime(resourceLoader, classLoader, runtimeArgs); + } + + private Runtime createRuntime(ResourceLoader resourceLoader, ClassLoader classLoader, String... runtimeArgs) { RuntimeOptions runtimeOptions = new RuntimeOptions(new Properties(), runtimeArgs); Backend backend = mock(Backend.class); Collection backends = Arrays.asList(backend); diff --git a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java b/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java index 5247b28d0f..093fbe6bba 100644 --- a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java +++ b/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java @@ -1,48 +1,52 @@ package cucumber.runtime.model; -import cucumber.runtime.CucumberException; import cucumber.runtime.io.Resource; import cucumber.runtime.io.ResourceLoader; import org.junit.Test; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.util.Collections; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class CucumberFeatureTest { @Test - public void fails_if_no_features_are_found() { - try { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - when(resourceLoader.resources("does/not/exist", ".feature")).thenReturn(Collections.emptyList()); - CucumberFeature.load(resourceLoader, asList("does/not/exist"), emptyList()); - fail("Should have failed"); - } catch (CucumberException e) { - assertEquals("No features found at [does/not/exist]", e.getMessage()); - } + public void succeds_if_no_features_are_found() { + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources("does/not/exist", ".feature")).thenReturn(Collections.emptyList()); + + CucumberFeature.load(resourceLoader, asList("does/not/exist"), emptyList(), null); + } + + @Test + public void logs_message_if_no_features_are_found() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + when(resourceLoader.resources("does/not/exist", ".feature")).thenReturn(Collections.emptyList()); + + CucumberFeature.load(resourceLoader, asList("does/not/exist"), emptyList(), new PrintStream(baos)); + + assertEquals(String.format("No features found at [does/not/exist]%n"), baos.toString()); } @Test - public void fails_if_features_are_found_but_filters_are_too_strict() throws IOException { - try { - ResourceLoader resourceLoader = mock(ResourceLoader.class); - - Resource resource = mock(Resource.class); - when(resource.getPath()).thenReturn("foo.feature"); - when(resource.getInputStream()).thenReturn(new ByteArrayInputStream("Feature: foo".getBytes("UTF-8"))); - - when(resourceLoader.resources("features", ".feature")).thenReturn(asList(resource)); - CucumberFeature.load(resourceLoader, asList("features"), asList((Object) "@nowhere")); - fail("Should have failed"); - } catch (CucumberException e) { - assertEquals("None of the features at [features] matched the filters: [@nowhere]", e.getMessage()); - } + public void logs_message_if_features_are_found_but_filters_are_too_strict() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + Resource resource = mock(Resource.class); + when(resource.getPath()).thenReturn("foo.feature"); + when(resource.getInputStream()).thenReturn(new ByteArrayInputStream("Feature: foo".getBytes("UTF-8"))); + when(resourceLoader.resources("features", ".feature")).thenReturn(asList(resource)); + + CucumberFeature.load(resourceLoader, asList("features"), asList((Object) "@nowhere"), new PrintStream(baos)); + + assertEquals(String.format("None of the features at [features] matched the filters: [@nowhere]%n"), baos.toString()); } } diff --git a/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java b/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java index c062f92a79..6cf6c8c6d8 100644 --- a/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java +++ b/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java @@ -46,9 +46,10 @@ public void finds_features_based_on_explicit_root_package() throws IOException, assertEquals("Feature: FA", cucumber.getChildren().get(0).getName()); } - @Test(expected = CucumberException.class) + @Test public void finds_no_features_when_explicit_package_has_nothnig() throws IOException, InitializationError { - new Cucumber(ExplicitFeaturePathWithNoFeatures.class); + Cucumber cucumber = new Cucumber(ExplicitFeaturePathWithNoFeatures.class); + assertEquals(0, cucumber.getChildren().size()); } @RunWith(Cucumber.class) diff --git a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java b/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java index 345117e80d..027e16e059 100644 --- a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java +++ b/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java @@ -20,7 +20,8 @@ public void shouldAssignUnequalDescriptionsToDifferentOccurrencesOfSameStepInASc List features = CucumberFeature.load( new ClasspathResourceLoader(this.getClass().getClassLoader()), asList("cucumber/runtime/junit/fb.feature"), - Collections.emptyList() + Collections.emptyList(), + null ); ExecutionUnitRunner runner = new ExecutionUnitRunner( @@ -48,7 +49,8 @@ public void shouldIncludeScenarioNameAsClassNameInStepDescriptions() throws Exce List features = CucumberFeature.load( new ClasspathResourceLoader(this.getClass().getClassLoader()), asList("cucumber/runtime/junit/feature_with_same_steps_in_different_scenarios.feature"), - Collections.emptyList() + Collections.emptyList(), + null ); ExecutionUnitRunner runner = new ExecutionUnitRunner( From 0fcca0a3b50ed928ac6aee13d52535d111a15a3f Mon Sep 17 00:00:00 2001 From: brasmusson Date: Thu, 1 Aug 2013 19:46:54 +0200 Subject: [PATCH 2/4] Add a dummy test case in JUnit report if no features are found Jenkins will mark a job as failed even though the exit code was 0, if the JUnit report file contains no testcase elements. Therefore add a dummy testcase element to the JUnit report, when no features are found, or no found features match the tags filter used. --- .../runtime/formatter/JUnitFormatter.java | 13 +++++++++++++ .../runtime/formatter/JUnitFormatterTest.java | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java b/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java index aaef83f6ba..54c1f56f95 100644 --- a/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java +++ b/core/src/main/java/cucumber/runtime/formatter/JUnitFormatter.java @@ -97,6 +97,9 @@ public void done() { try { //set up a transformer rootElement.setAttribute("failures", String.valueOf(rootElement.getElementsByTagName("failure").getLength())); + if (rootElement.getElementsByTagName("testcase").getLength() == 0) { + addDummyTestCase(); // to avoid failed Jenkins jobs + } TransformerFactory transfac = TransformerFactory.newInstance(); Transformer trans = transfac.newTransformer(); trans.setOutputProperty(OutputKeys.INDENT, "yes"); @@ -108,6 +111,16 @@ public void done() { } } + private void addDummyTestCase() { + Element dummy = doc.createElement("testcase"); + dummy.setAttribute("classname", "dummy"); + dummy.setAttribute("name", "dummy"); + rootElement.appendChild(dummy); + Element skipped = doc.createElement("skipped"); + skipped.setAttribute("message", "No features found"); + dummy.appendChild(skipped); + } + @Override public void result(Result result) { testCase.results.add(result); diff --git a/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java b/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java index 0e9adf9a27..2780baf504 100644 --- a/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java +++ b/core/src/test/java/cucumber/runtime/formatter/JUnitFormatterTest.java @@ -367,6 +367,24 @@ public void should_handle_one_step_at_the_time_execution() throws Exception { assertXmlEqual(expected, replaceTimeWithZeroTime(actual)); } + @Test + public void should_add_dummy_testcase_if_no_features_are_found_to_aviod_failed_jenkins_jobs() throws Exception { + final File report = File.createTempFile("cucumber-jvm-junit", ".xml"); + final JUnitFormatter junitFormatter = createJUnitFormatter(report); + + junitFormatter.done(); + junitFormatter.close(); + + String actual = new Scanner(new FileInputStream(report), "UTF-8").useDelimiter("\\A").next(); + String expected = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n"; + assertXmlEqual(expected, replaceTimeWithZeroTime(actual)); + } + private File runFeaturesWithJunitFormatter(final List featurePaths) throws IOException { return runFeaturesWithJunitFormatter(featurePaths, false); } From bc7d976a21beba2b2e357e329db12ea56cdb466c Mon Sep 17 00:00:00 2001 From: brasmusson Date: Fri, 2 Aug 2013 20:05:03 +0200 Subject: [PATCH 3/4] Aviod passing null, improve message when missing feature path Overload CucumberFeature.load to avoid passing null and the need for null check. Improve the log message when no feature path is given at the command line. --- .../java/cucumber/runtime/RuntimeGlue.java | 2 +- .../runtime/model/CucumberFeature.java | 26 +++++++++++-------- .../runtime/model/CucumberFeatureTest.java | 13 +++++++++- .../junit/ExecutionUnitRunnerTest.java | 6 ++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/cucumber/runtime/RuntimeGlue.java b/core/src/main/java/cucumber/runtime/RuntimeGlue.java index 7985640876..4a7735b39b 100644 --- a/core/src/main/java/cucumber/runtime/RuntimeGlue.java +++ b/core/src/main/java/cucumber/runtime/RuntimeGlue.java @@ -103,7 +103,7 @@ private List stepDefinitionMatches(String uri, Step step) { @Override public void writeStepdefsJson(List featurePaths, URL dotCucumber) throws IOException { if (dotCucumber != null) { - List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS, null); + List features = load(new FileResourceLoader(), featurePaths, NO_FILTERS); List metaStepdefs = new StepdefGenerator().generate(stepDefinitionsByPattern.values(), features); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String json = gson.toJson(metaStepdefs); diff --git a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java index 7435780e7e..193c78a9da 100644 --- a/core/src/main/java/cucumber/runtime/model/CucumberFeature.java +++ b/core/src/main/java/cucumber/runtime/model/CucumberFeature.java @@ -29,29 +29,33 @@ public class CucumberFeature { private I18n i18n; private CucumberScenarioOutline currentScenarioOutline; - public static List load(ResourceLoader resourceLoader, List featurePaths, final List filters, PrintStream optionalOut) { + public static List load(ResourceLoader resourceLoader, List featurePaths, final List filters, PrintStream out) { + final List cucumberFeatures = load(resourceLoader, featurePaths, filters); + if (cucumberFeatures.isEmpty()) { + if (featurePaths.isEmpty()) { + out.println(String.format("Got no path to feature directory or feature file")); + } else if (filters.isEmpty()) { + out.println(String.format("No features found at %s", featurePaths)); + } else { + out.println(String.format("None of the features at %s matched the filters: %s", featurePaths, filters)); + } + } + return cucumberFeatures; + } + + public static List load(ResourceLoader resourceLoader, List featurePaths, final List filters) { final List cucumberFeatures = new ArrayList(); final FeatureBuilder builder = new FeatureBuilder(cucumberFeatures); - boolean resourceFound = false; for (String featurePath : featurePaths) { Iterable resources = resourceLoader.resources(featurePath, ".feature"); for (Resource resource : resources) { - resourceFound = true; builder.parse(resource, filters); } } - if (cucumberFeatures.isEmpty() && optionalOut != null) { - if (resourceFound) { - optionalOut.println(String.format("None of the features at %s matched the filters: %s", featurePaths, filters)); - } else { - optionalOut.println(String.format("No features found at %s", featurePaths)); - } - } Collections.sort(cucumberFeatures, new CucumberFeatureUriComparator()); return cucumberFeatures; } - public CucumberFeature(Feature feature, String uri) { this.feature = feature; this.uri = uri; diff --git a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java b/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java index 093fbe6bba..af3785fb12 100644 --- a/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java +++ b/core/src/test/java/cucumber/runtime/model/CucumberFeatureTest.java @@ -22,7 +22,7 @@ public void succeds_if_no_features_are_found() { ResourceLoader resourceLoader = mock(ResourceLoader.class); when(resourceLoader.resources("does/not/exist", ".feature")).thenReturn(Collections.emptyList()); - CucumberFeature.load(resourceLoader, asList("does/not/exist"), emptyList(), null); + CucumberFeature.load(resourceLoader, asList("does/not/exist"), emptyList(), new PrintStream(new ByteArrayOutputStream())); } @Test @@ -49,4 +49,15 @@ public void logs_message_if_features_are_found_but_filters_are_too_strict() thro assertEquals(String.format("None of the features at [features] matched the filters: [@nowhere]%n"), baos.toString()); } + + @Test + public void logs_message_if_no_feature_paths_are_given() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ResourceLoader resourceLoader = mock(ResourceLoader.class); + + CucumberFeature.load(resourceLoader, Collections.emptyList(), emptyList(), new PrintStream(baos)); + + assertEquals(String.format("Got no path to feature directory or feature file%n"), baos.toString()); + } + } diff --git a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java b/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java index 027e16e059..345117e80d 100644 --- a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java +++ b/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java @@ -20,8 +20,7 @@ public void shouldAssignUnequalDescriptionsToDifferentOccurrencesOfSameStepInASc List features = CucumberFeature.load( new ClasspathResourceLoader(this.getClass().getClassLoader()), asList("cucumber/runtime/junit/fb.feature"), - Collections.emptyList(), - null + Collections.emptyList() ); ExecutionUnitRunner runner = new ExecutionUnitRunner( @@ -49,8 +48,7 @@ public void shouldIncludeScenarioNameAsClassNameInStepDescriptions() throws Exce List features = CucumberFeature.load( new ClasspathResourceLoader(this.getClass().getClassLoader()), asList("cucumber/runtime/junit/feature_with_same_steps_in_different_scenarios.feature"), - Collections.emptyList(), - null + Collections.emptyList() ); ExecutionUnitRunner runner = new ExecutionUnitRunner( From 96a5c15d4a70476431e9bd62ebcad0f3e83b9047 Mon Sep 17 00:00:00 2001 From: Dmytro Chyzhykov Date: Sat, 3 Aug 2013 21:56:37 +0300 Subject: [PATCH 4/4] Created examples/java-no-features to demonstrate Cucumber JVM behaviour when there are no features found at all. --- examples/java-no-features/.gitignore | 7 ++++ examples/java-no-features/README.md | 11 ++++++ examples/java-no-features/pom.xml | 38 +++++++++++++++++++ .../java/no/features/RunCukesTest.java | 8 ++++ 4 files changed, 64 insertions(+) create mode 100644 examples/java-no-features/.gitignore create mode 100644 examples/java-no-features/README.md create mode 100644 examples/java-no-features/pom.xml create mode 100644 examples/java-no-features/src/test/java/cucumber/examples/java/no/features/RunCukesTest.java diff --git a/examples/java-no-features/.gitignore b/examples/java-no-features/.gitignore new file mode 100644 index 0000000000..5d469f3159 --- /dev/null +++ b/examples/java-no-features/.gitignore @@ -0,0 +1,7 @@ +/.settings +/.classpath +/.project +.idea +*.iml +.DS_Store + diff --git a/examples/java-no-features/README.md b/examples/java-no-features/README.md new file mode 100644 index 0000000000..2e2cbd56c4 --- /dev/null +++ b/examples/java-no-features/README.md @@ -0,0 +1,11 @@ +# java-no-features + +If wonder what would Cucumber JVM do when there are no feature files found at all. +Just run these cukes by + + mvn clean test + +## Read more + + - [Jenkins fails if there are no testcases](http://jenkins-ci.361315.n4.nabble.com/Jenkins-fails-if-there-are-no-testcases-td3899185.html) Jenkins CI thread + - [[JVM] Exception thrown when no tags are found](https://groups.google.com/forum/#!topic/cukes/wZo0FTQJrVA) Google group discussion diff --git a/examples/java-no-features/pom.xml b/examples/java-no-features/pom.xml new file mode 100644 index 0000000000..a65b272f8e --- /dev/null +++ b/examples/java-no-features/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + + + info.cukes + cucumber-jvm + ../../pom.xml + 1.1.4-SNAPSHOT + + + java-no-features + jar + Examples: No Cucumber features + + + + info.cukes + cucumber-jvm-deps + test + + + info.cukes + cucumber-java + test + + + info.cukes + cucumber-junit + test + + + junit + junit + test + + + diff --git a/examples/java-no-features/src/test/java/cucumber/examples/java/no/features/RunCukesTest.java b/examples/java-no-features/src/test/java/cucumber/examples/java/no/features/RunCukesTest.java new file mode 100644 index 0000000000..1c6b5b5154 --- /dev/null +++ b/examples/java-no-features/src/test/java/cucumber/examples/java/no/features/RunCukesTest.java @@ -0,0 +1,8 @@ +package cucumber.examples.java.no.features; + +import cucumber.api.junit.Cucumber; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +public class RunCukesTest { +}