Skip to content

Commit 80773f0

Browse files
committed
Merge cucumber#589. Update History.md
2 parents 2ac08a8 + c591426 commit 80773f0

File tree

12 files changed

+401
-326
lines changed

12 files changed

+401
-326
lines changed

History.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [1-1-6-SNAPSHOT (Git master)](https://github.com/cucumber/cucumber-jvm/compare/v1.1.5...master)
22

3+
* [Core] Make the RerunFormatter handle failures in background and scenario outline examples correctly ([#589](https://github.com/cucumber/cucumber-jvm/pull/589) Björn Rasmusson)
34
* [Core] Fix stop watch thread safety ([#606](https://github.com/cucumber/cucumber-jvm/pull/606) Dave Bassan)
45
* [Android] Fix Cucumber reports for cucumber-android ([#605](https://github.com/cucumber/cucumber-jvm/pull/605) Frieder Bluemle)
56
* [Spring] Fix for tests annotated with @ContextHierarchy ([#590](https://github.com/cucumber/cucumber-jvm/pull/590) Martin Lau)

core/src/main/java/cucumber/runtime/formatter/RerunFormatter.java

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,12 @@
2424
* Failed means: (failed, undefined, pending) test result
2525
*/
2626
class RerunFormatter implements Formatter, Reporter {
27-
2827
private final NiceAppendable out;
29-
3028
private String featureLocation;
31-
32-
private Step step;
33-
29+
private Scenario scenario;
30+
private boolean isTestFailed = false;
3431
private Map<String, LinkedHashSet<Integer>> featureAndFailedLinesMapping = new HashMap<String, LinkedHashSet<Integer>>();
3532

36-
3733
public RerunFormatter(Appendable out) {
3834
this.out = new NiceAppendable(out);
3935
}
@@ -53,6 +49,7 @@ public void background(Background background) {
5349

5450
@Override
5551
public void scenario(Scenario scenario) {
52+
this.scenario = scenario;
5653
}
5754

5855
@Override
@@ -65,7 +62,6 @@ public void examples(Examples examples) {
6562

6663
@Override
6764
public void step(Step step) {
68-
this.step = step;
6965
}
7066

7167
@Override
@@ -78,10 +74,10 @@ public void syntaxError(String state, String event, List<String> legalEvents, St
7874

7975
@Override
8076
public void done() {
81-
reportFailedSteps();
77+
reportFailedScenarios();
8278
}
8379

84-
private void reportFailedSteps() {
80+
private void reportFailedScenarios() {
8581
Set<Map.Entry<String, LinkedHashSet<Integer>>> entries = featureAndFailedLinesMapping.entrySet();
8682
boolean firstFeature = true;
8783
for (Map.Entry<String, LinkedHashSet<Integer>> entry : entries) {
@@ -105,23 +101,27 @@ public void close() {
105101

106102
@Override
107103
public void startOfScenarioLifeCycle(Scenario scenario) {
108-
// NoOp
104+
isTestFailed = false;
109105
}
110106

111107
@Override
112108
public void endOfScenarioLifeCycle(Scenario scenario) {
113-
// NoOp
109+
if (isTestFailed) {
110+
recordTestFailed();
111+
}
114112
}
115113

116114
@Override
117115
public void before(Match match, Result result) {
118-
116+
if (isTestFailed(result)) {
117+
isTestFailed = true;
118+
}
119119
}
120120

121121
@Override
122122
public void result(Result result) {
123123
if (isTestFailed(result)) {
124-
recordTestFailed();
124+
isTestFailed = true;
125125
}
126126
}
127127

@@ -131,17 +131,20 @@ private boolean isTestFailed(Result result) {
131131
}
132132

133133
private void recordTestFailed() {
134-
LinkedHashSet<Integer> failedSteps = this.featureAndFailedLinesMapping.get(featureLocation);
135-
if (failedSteps == null) {
136-
failedSteps = new LinkedHashSet<Integer>();
137-
this.featureAndFailedLinesMapping.put(featureLocation, failedSteps);
134+
LinkedHashSet<Integer> failedScenarios = this.featureAndFailedLinesMapping.get(featureLocation);
135+
if (failedScenarios == null) {
136+
failedScenarios = new LinkedHashSet<Integer>();
137+
this.featureAndFailedLinesMapping.put(featureLocation, failedScenarios);
138138
}
139139

140-
failedSteps.add(step.getLine());
140+
failedScenarios.add(scenario.getLine());
141141
}
142142

143143
@Override
144144
public void after(Match match, Result result) {
145+
if (isTestFailed(result)) {
146+
isTestFailed = true;
147+
}
145148
}
146149

147150
@Override

core/src/test/java/cucumber/runtime/TestHelper.java

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
package cucumber.runtime;
22

3+
import cucumber.runtime.io.ClasspathResourceLoader;
4+
import gherkin.formatter.Formatter;
5+
import gherkin.formatter.Reporter;
6+
import org.mockito.invocation.InvocationOnMock;
7+
import org.mockito.stubbing.Answer;
8+
import cucumber.runtime.formatter.StepMatcher;
9+
import gherkin.formatter.model.Step;
10+
import gherkin.formatter.model.Tag;
11+
import cucumber.api.PendingException;
12+
import gherkin.I18n;
13+
import junit.framework.AssertionFailedError;
314
import cucumber.runtime.io.Resource;
415
import cucumber.runtime.model.CucumberFeature;
516
import org.junit.Ignore;
617

718
import java.io.ByteArrayInputStream;
19+
import java.io.FileNotFoundException;
820
import java.io.IOException;
921
import java.io.InputStream;
22+
import java.io.PrintWriter;
1023
import java.io.UnsupportedEncodingException;
24+
import java.util.AbstractMap.SimpleEntry;
1125
import java.util.ArrayList;
26+
import java.util.Arrays;
27+
import java.util.Collections;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.Set;
32+
33+
import static java.util.Arrays.asList;
34+
import static org.mockito.Mockito.doAnswer;
35+
import static org.mockito.Matchers.argThat;
36+
import static org.mockito.Matchers.anyCollectionOf;
37+
import static org.junit.Assert.fail;
38+
import static org.mockito.Matchers.any;
39+
import static org.mockito.Matchers.anyString;
40+
import static org.mockito.Mockito.doThrow;
41+
import static org.mockito.Mockito.when;
42+
import static org.mockito.Mockito.mock;
1243

1344
@Ignore
1445
public class TestHelper {
@@ -37,4 +68,138 @@ public String getClassName() {
3768
}, new ArrayList<Object>());
3869
return cucumberFeatures.get(0);
3970
}
71+
72+
public static void runFeatureWithFormatter(final CucumberFeature feature, final Map<String, String> stepsToResult, final List<SimpleEntry<String, String>> hooks,
73+
final long stepHookDuration, final Formatter formatter, final Reporter reporter) throws Throwable, FileNotFoundException {
74+
runFeaturesWithFormatter(Arrays.asList(feature), stepsToResult, Collections.<String,String>emptyMap(), hooks, stepHookDuration, formatter, reporter);
75+
}
76+
77+
public static void runFeaturesWithFormatter(final List<CucumberFeature> features, final Map<String, String> stepsToResult,
78+
final List<SimpleEntry<String, String>> hooks, final long stepHookDuration, final Formatter formatter, final Reporter reporter) throws Throwable {
79+
runFeaturesWithFormatter(features, stepsToResult, Collections.<String,String>emptyMap(), hooks, stepHookDuration, formatter, reporter);
80+
}
81+
82+
public static void runFeatureWithFormatter(final CucumberFeature feature, final Map<String, String> stepsToLocation,
83+
final Formatter formatter, final Reporter reporter) throws Throwable {
84+
runFeaturesWithFormatter(Arrays.asList(feature), Collections.<String,String>emptyMap(), stepsToLocation,
85+
Collections.<SimpleEntry<String, String>>emptyList(), 0L, formatter, reporter);
86+
}
87+
88+
private static void runFeaturesWithFormatter(final List<CucumberFeature> features, final Map<String, String> stepsToResult, final Map<String, String> stepsToLocation,
89+
final List<SimpleEntry<String, String>> hooks, final long stepHookDuration, final Formatter formatter, final Reporter reporter) throws Throwable {
90+
final RuntimeOptions runtimeOptions = new RuntimeOptions("");
91+
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
92+
final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
93+
final RuntimeGlue glue = createMockedRuntimeGlueThatMatchesTheSteps(stepsToResult, stepsToLocation, hooks);
94+
final Runtime runtime = new Runtime(resourceLoader, classLoader, asList(mock(Backend.class)), runtimeOptions, new StopWatch.Stub(stepHookDuration), glue);
95+
96+
for (CucumberFeature feature : features) {
97+
feature.run(formatter, reporter, runtime);
98+
}
99+
formatter.done();
100+
formatter.close();
101+
}
102+
103+
private static RuntimeGlue createMockedRuntimeGlueThatMatchesTheSteps(Map<String, String> stepsToResult, Map<String, String> stepsToLocation,
104+
final List<SimpleEntry<String, String>> hooks) throws Throwable {
105+
RuntimeGlue glue = mock(RuntimeGlue.class);
106+
TestHelper.mockSteps(glue, stepsToResult, stepsToLocation);
107+
TestHelper.mockHooks(glue, hooks);
108+
return glue;
109+
}
110+
111+
private static void mockSteps(RuntimeGlue glue, Map<String, String> stepsToResult, Map<String, String> stepsToLocation) throws Throwable {
112+
for (String stepName : mergeStepSets(stepsToResult, stepsToLocation)) {
113+
String stepResult = getResultWithDefaultPassed(stepsToResult, stepName);
114+
if (!"undefined".equals(stepResult)) {
115+
StepDefinitionMatch matchStep = mock(StepDefinitionMatch.class);
116+
when(glue.stepDefinitionMatch(anyString(), TestHelper.stepWithName(stepName), (I18n) any())).thenReturn(matchStep);
117+
mockStepResult(stepResult, stepName, matchStep);
118+
mockStepLocation(getLocationWithDefaultEmptyString(stepsToLocation, stepName), stepName, matchStep);
119+
}
120+
}
121+
}
122+
123+
private static void mockStepResult(String stepResult, String stepName, StepDefinitionMatch matchStep) throws Throwable {
124+
if ("pending".equals(stepResult)) {
125+
doThrow(new PendingException()).when(matchStep).runStep((I18n) any());
126+
} else if ("failed".equals(stepResult)) {
127+
AssertionFailedError error = TestHelper.mockAssertionFailedError();
128+
doThrow(error).when(matchStep).runStep((I18n) any());
129+
} else if (!"passed".equals(stepResult) &&
130+
!"skipped".equals(stepResult)) {
131+
fail("Cannot mock step to the result: " + stepResult);
132+
}
133+
}
134+
135+
private static void mockStepLocation(String stepLocation, String stepName, StepDefinitionMatch matchStep) {
136+
when(matchStep.getLocation()).thenReturn(stepLocation);
137+
}
138+
139+
private static void mockHooks(RuntimeGlue glue, final List<SimpleEntry<String, String>> hooks) throws Throwable {
140+
List<HookDefinition> beforeHooks = new ArrayList<HookDefinition>();
141+
List<HookDefinition> afterHooks = new ArrayList<HookDefinition>();
142+
for (SimpleEntry<String, String> hookEntry : hooks) {
143+
TestHelper.mockHook(hookEntry, beforeHooks, afterHooks);
144+
}
145+
if (beforeHooks.size() != 0) {
146+
when(glue.getBeforeHooks()).thenReturn(beforeHooks);
147+
}
148+
if (afterHooks.size() != 0) {
149+
when(glue.getAfterHooks()).thenReturn(afterHooks);
150+
}
151+
}
152+
153+
private static void mockHook(SimpleEntry<String, String> hookEntry, List<HookDefinition> beforeHooks,
154+
List<HookDefinition> afterHooks) throws Throwable {
155+
HookDefinition hook = mock(HookDefinition.class);
156+
when(hook.matches(anyCollectionOf(Tag.class))).thenReturn(true);
157+
if (hookEntry.getValue().equals("failed")) {
158+
AssertionFailedError error = TestHelper.mockAssertionFailedError();
159+
doThrow(error).when(hook).execute((cucumber.api.Scenario) any());
160+
}
161+
if ("before".equals(hookEntry.getKey())) {
162+
beforeHooks.add(hook);
163+
} else if ("after".equals(hookEntry.getKey())) {
164+
afterHooks.add(hook);
165+
} else {
166+
fail("Only before and after hooks are allowed, hook type found was: " + hookEntry.getKey());
167+
}
168+
}
169+
170+
private static Step stepWithName(String name) {
171+
return argThat(new StepMatcher(name));
172+
}
173+
174+
private static AssertionFailedError mockAssertionFailedError() {
175+
AssertionFailedError error = mock(AssertionFailedError.class);
176+
Answer<Object> printStackTraceHandler = new Answer<Object>() {
177+
@Override
178+
public Object answer(InvocationOnMock invocation) throws Throwable {
179+
PrintWriter writer = (PrintWriter) invocation.getArguments()[0];
180+
writer.print("the stack trace");
181+
return null;
182+
}
183+
};
184+
doAnswer(printStackTraceHandler).when(error).printStackTrace((PrintWriter) any());
185+
return error;
186+
}
187+
188+
public static SimpleEntry<String, String> hookEntry(String type, String result) {
189+
return new SimpleEntry<String, String>(type, result);
190+
}
191+
192+
private static Set<String> mergeStepSets(Map<String, String> stepsToResult, Map<String, String> stepsToLocation) {
193+
Set<String> steps = new HashSet<String>(stepsToResult.keySet());
194+
steps.addAll(stepsToLocation.keySet());
195+
return steps;
196+
}
197+
198+
private static String getResultWithDefaultPassed(Map<String, String> stepsToResult, String step) {
199+
return stepsToResult.containsKey(step) ? stepsToResult.get(step) : "passed";
200+
}
201+
202+
private static String getLocationWithDefaultEmptyString(Map<String, String> stepsToLocation, String step) {
203+
return stepsToLocation.containsKey(step) ? stepsToLocation.get(step) : "";
204+
}
40205
}
Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,20 @@
11
package cucumber.runtime.formatter;
22

3-
import cucumber.runtime.Backend;
4-
import cucumber.runtime.Runtime;
5-
import cucumber.runtime.RuntimeGlue;
6-
import cucumber.runtime.RuntimeOptions;
7-
import cucumber.runtime.StepDefinitionMatch;
8-
import cucumber.runtime.io.ClasspathResourceLoader;
3+
import cucumber.runtime.TestHelper;
94
import cucumber.runtime.model.CucumberFeature;
10-
import gherkin.I18n;
11-
import gherkin.formatter.model.Step;
125
import org.junit.Test;
136

14-
import java.io.IOException;
157
import java.util.HashMap;
168
import java.util.Map;
179

1810
import static cucumber.runtime.TestHelper.feature;
19-
import static java.util.Arrays.asList;
2011
import static org.hamcrest.CoreMatchers.containsString;
2112
import static org.junit.Assert.assertThat;
22-
import static org.mockito.Matchers.any;
23-
import static org.mockito.Matchers.anyString;
24-
import static org.mockito.Matchers.argThat;
25-
import static org.mockito.Mockito.mock;
26-
import static org.mockito.Mockito.when;
2713

2814
public class CucumberPrettyFormatterTest {
2915

3016
@Test
31-
public void should_align_the_indentation_of_location_strings() throws IOException {
17+
public void should_align_the_indentation_of_location_strings() throws Throwable {
3218
CucumberFeature feature = feature("path/test.feature",
3319
"Feature: feature name\n" +
3420
" Scenario: scenario name\n" +
@@ -49,32 +35,12 @@ public void should_align_the_indentation_of_location_strings() throws IOExceptio
4935
" Then third step # path/step_definitions.java:11\n"));
5036
}
5137

52-
private String runFeatureWithPrettyFormatter(final CucumberFeature feature, final Map<String, String> stepsToLocation) throws IOException {
53-
final RuntimeOptions runtimeOptions = new RuntimeOptions("");
54-
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
55-
final ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader(classLoader);
56-
final RuntimeGlue glue = createMockedRuntimeGlueThatMatchesTheSteps(stepsToLocation);
57-
final Runtime runtime = new Runtime(resourceLoader, classLoader, asList(mock(Backend.class)), runtimeOptions, glue);
38+
private String runFeatureWithPrettyFormatter(final CucumberFeature feature, final Map<String, String> stepsToLocation) throws Throwable {
5839
final StringBuilder out = new StringBuilder();
5940
final CucumberPrettyFormatter prettyFormatter = new CucumberPrettyFormatter(out);
6041
prettyFormatter.setMonochrome(true);
61-
62-
feature.run(prettyFormatter, prettyFormatter, runtime);
63-
42+
TestHelper.runFeatureWithFormatter(feature, stepsToLocation, prettyFormatter, prettyFormatter);
6443
return out.toString();
6544
}
6645

67-
private RuntimeGlue createMockedRuntimeGlueThatMatchesTheSteps(Map<String, String> stepsToLocation) {
68-
RuntimeGlue glue = mock(RuntimeGlue.class);
69-
for (String stepName : stepsToLocation.keySet()) {
70-
StepDefinitionMatch matchStep = mock(StepDefinitionMatch.class);
71-
when(matchStep.getLocation()).thenReturn(stepsToLocation.get(stepName));
72-
when(glue.stepDefinitionMatch(anyString(), stepWithName(stepName), (I18n) any())).thenReturn(matchStep);
73-
}
74-
return glue;
75-
}
76-
77-
private Step stepWithName(String name) {
78-
return argThat(new StepMatcher(name));
79-
}
8046
}

0 commit comments

Comments
 (0)