Skip to content

Commit f600027

Browse files
committed
Handle failures and add 'progress' formatter (close #6, #16, #17)
- Failing steps are now properly handled and following steps in scenario are skipped - Second core.feature scenario is passing - Progress formatter is now available and used by default - We still miss stack trace output on failure
1 parent 4090a2f commit f600027

File tree

12 files changed

+1063
-127
lines changed

12 files changed

+1063
-127
lines changed

cucumber.js

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
11
#!/usr/bin/env node
2-
var fs = require('fs');
3-
var Cucumber = require('./lib/cucumber');
4-
var supportCodePath = process.ARGV[3] ? process.cwd() + '/' + process.ARGV[3] : './features/step_definitions/cucumber_steps';
5-
var supportCode = require(supportCodePath);
6-
var cucumber = Cucumber(fs.readFileSync(process.ARGV[2]), supportCode);
7-
var progressFormatter = Cucumber.Listener.ProgressFormatter;
8-
cucumber.attachListener(progressFormatter());
9-
cucumber.start(function() {});
2+
var fs = require('fs');
3+
var Cucumber = require('./lib/cucumber');
4+
var supportCodePath = process.ARGV[3] ? process.cwd() + '/' + process.ARGV[3] : './features/step_definitions/cucumber_steps';
5+
var supportCode = require(supportCodePath);
6+
var cucumber = Cucumber(fs.readFileSync(process.ARGV[2]), supportCode);
7+
var formatter = Cucumber.Listener.ProgressFormatter();
8+
cucumber.attachListener(formatter);
9+
cucumber.start(function(succeeded) {
10+
var code = succeeded ? 0 : 1;
11+
var exitFunction = function() {
12+
process.exit(code);
13+
};
14+
15+
// --- exit after waiting for all pending output ---
16+
var waitingIO = false;
17+
process.stdout.on('drain', function() {
18+
if (waitingIO) {
19+
// the kernel buffer is now empty
20+
exitFunction();
21+
}
22+
});
23+
if (process.stdout.write("")) {
24+
// no buffer left, exit now:
25+
exitFunction();
26+
} else {
27+
// write() returned false, kernel buffer is not empty yet...
28+
waitingIO = true;
29+
}
30+
});

features/progress_formatter.feature

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
Feature: progress formatter
2+
In order to get quick feedback when doing BDD
3+
As a developer
4+
I want to use a "progress" formatter
5+
6+
Scenario: one scenario, one step, passing
7+
Given a step definition matching /a passing step/
8+
When I run the following feature with the "progress" formatter:
9+
"""
10+
Feature:
11+
Scenario:
12+
Given a passing step
13+
"""
14+
Then the listener should output the following:
15+
"""
16+
.
17+
18+
1 scenario (1 passed)
19+
1 step (1 passed)
20+
"""
21+
22+
Scenario: one scenario, two steps, passing
23+
Given a step definition matching /a passing step/
24+
When I run the following feature with the "progress" formatter:
25+
"""
26+
Feature:
27+
Scenario:
28+
Given a passing step
29+
And a passing step
30+
"""
31+
Then the listener should output the following:
32+
"""
33+
..
34+
35+
1 scenario (1 passed)
36+
2 steps (2 passed)
37+
"""
38+
39+
Scenario: two scenarios, five steps, passing
40+
Given a step definition matching /a passing step/
41+
When I run the following feature with the "progress" formatter:
42+
"""
43+
Feature:
44+
Scenario:
45+
Given a passing step
46+
And a passing step
47+
Scenario:
48+
Given a passing step
49+
And a passing step
50+
When a passing step
51+
"""
52+
Then the listener should output the following:
53+
"""
54+
..
55+
56+
2 scenarios (2 passed)
57+
5 steps (5 passed)
58+
"""
59+
60+
Scenario: one scenario, one step, failing
61+
Given a step definition failing with message "boom" matching /a failing step/
62+
When I run the following feature with the "progress" formatter:
63+
"""
64+
Feature:
65+
Scenario:
66+
Given a failing step
67+
"""
68+
Then the listener should output the following:
69+
"""
70+
F
71+
72+
1 scenario (1 failed)
73+
1 step (1 failed)
74+
"""
75+
76+
Scenario: one scenario, two steps, second failing
77+
Given a step definition matching /a passing step/
78+
And a step definition failing with message "boom" matching /a failing step/
79+
When I run the following feature with the "progress" formatter:
80+
"""
81+
Feature:
82+
Scenario:
83+
Given a passing step
84+
When a failing step
85+
"""
86+
Then the listener should output the following:
87+
"""
88+
.F
89+
90+
1 scenario (1 failed)
91+
2 steps (1 failed, 1 passed)
92+
"""
93+
94+
Scenario: one two-step passing scenario, one two-step scenario with latest step failing
95+
Given a step definition matching /a passing step/
96+
And a step definition failing with message "boom" matching /a failing step/
97+
When I run the following feature with the "progress" formatter:
98+
"""
99+
Feature:
100+
Scenario:
101+
Given a passing step
102+
When a passing step
103+
Scenario:
104+
Given a passing step
105+
When a failing step
106+
"""
107+
Then the listener should output the following:
108+
"""
109+
...F
110+
111+
2 scenarios (1 failed, 1 passed)
112+
4 steps (1 failed, 3 passed)
113+
"""
114+
115+
Scenario: one failing scenario with a skipped step
116+
Given a step definition matching /a passing step/
117+
And a step definition matching /a skipped step/
118+
And a step definition failing with message "boom" matching /a failing step/
119+
When I run the following feature with the "progress" formatter:
120+
"""
121+
Feature:
122+
Scenario:
123+
Given a passing step
124+
When a failing step
125+
Then a skipped step
126+
"""
127+
Then the listener should output the following:
128+
"""
129+
.F-
130+
131+
1 scenario (1 failed)
132+
3 steps (1 failed, 1 skipped, 1 passed)
133+
"""

features/step_definitions/cucumber_js_mappings.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,12 @@ def write_failing_mapping(step_name)
160160
# end
161161

162162
def assert_passing_scenario
163-
assert_partial_output("1 scenario(s) (1 passed)", all_output)
163+
assert_partial_output("1 scenario (1 passed)", all_output)
164164
assert_success true
165165
end
166166

167167
def assert_failing_scenario
168-
assert_partial_output("1 scenario(s) (1 failed)", all_output)
168+
assert_partial_output("1 scenario (1 failed)", all_output)
169169
assert_success false
170170
end
171171

features/step_definitions/cucumber_steps.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,24 @@ var stepDefinitions = function() {
2121
// The created step definition body should:
2222
// 1. Pass all the time.
2323
Given(/^a(?: "(Given|When|Then)")? step definition matching \/(.*)\/$/, function(keyword, name, callback) {
24-
var content = function(callback) { callback() };
24+
var content = function(callback) { callback(); };
25+
_addStepDefinition(keyword, name, content);
26+
callback();
27+
});
28+
29+
// Creates a Given, When or Then step definition that does nothing but fails all the time.
30+
//
31+
// Matching groups:
32+
// 1. /Given|When|Then/ Step definition keyword (optional)
33+
// 2. /.*/ Step definition name
34+
// 3. /.*/ Exception message
35+
//
36+
// Created step definition matching groups: none.
37+
//
38+
// The created step definition body should:
39+
// 1. Fail all the time.
40+
Given(/^a(?: "(Given|When|Then)")? step definition failing with message "(.*)" matching \/(.*)\/$/, function(keyword, errorMessage, name, callback) {
41+
var content = function(callback) { throw(errorMessage); };
2542
_addStepDefinition(keyword, name, content);
2643
callback();
2744
});

lib/cucumber/ast/tree_walker.js

+47-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
var TreeWalker = function(features, supportCodeLibrary, listeners) {
22
var listeners;
3+
var allFeaturesSucceded = true;
4+
var skippingSteps = false;
35

46
var self = {
57
walk: function walk(callback) {
6-
self.visitFeatures(features, callback);
8+
self.visitFeatures(features, function() {
9+
var featuresResult = self.didAllFeaturesSucceed();
10+
callback(featuresResult);
11+
});
712
},
813

914
visitFeatures: function visitFeatures(features, callback) {
@@ -26,6 +31,7 @@ var TreeWalker = function(features, supportCodeLibrary, listeners) {
2631
},
2732

2833
visitScenario: function visitScenario(scenario, callback) {
34+
self.witnessNewScenario();
2935
var payload = { scenario: scenario };
3036
var event = TreeWalker.Event(TreeWalker.SCENARIO_EVENT_NAME, payload);
3137
self.broadcastEventAroundUserFunction(
@@ -36,16 +42,15 @@ var TreeWalker = function(features, supportCodeLibrary, listeners) {
3642
},
3743

3844
visitStep: function visitStep(step, callback) {
39-
var payload = { step: step };
40-
var event = TreeWalker.Event(TreeWalker.STEP_EVENT_NAME, payload);
41-
self.broadcastEventAroundUserFunction(
42-
event,
43-
function(callback) { step.acceptVisitor(self, callback); },
44-
callback
45-
);
45+
if (self.isSkippingSteps())
46+
self.skipStep(step, callback);
47+
else
48+
self.executeStep(step, callback);
4649
},
4750

4851
visitStepResult: function visitStepResult(stepResult, callback) {
52+
if (!stepResult.isSuccessful())
53+
self.witnessFailedStep();
4954
var payload = { stepResult: stepResult };
5055
var event = TreeWalker.Event(TreeWalker.STEP_RESULT_EVENT_NAME, payload);
5156
self.broadcastEvent(event, callback);
@@ -86,6 +91,39 @@ var TreeWalker = function(features, supportCodeLibrary, listeners) {
8691

8792
lookupStepDefinitionByName: function lookupStepDefinitionByName(stepName) {
8893
return supportCodeLibrary.lookupStepDefinitionByName(stepName);
94+
},
95+
96+
didAllFeaturesSucceed: function didAllFeaturesSucceed() {
97+
return allFeaturesSucceded;
98+
},
99+
100+
witnessFailedStep: function witnessFailedStep() {
101+
allFeaturesSucceded = false;
102+
skippingSteps = true;
103+
},
104+
105+
witnessNewScenario: function witnessNewScenario() {
106+
skippingSteps = false;
107+
},
108+
109+
isSkippingSteps: function isSkippingSteps() {
110+
return skippingSteps;
111+
},
112+
113+
executeStep: function executeStep(step, callback) {
114+
var payload = { step: step };
115+
var event = TreeWalker.Event(TreeWalker.STEP_EVENT_NAME, payload);
116+
self.broadcastEventAroundUserFunction(
117+
event,
118+
function(callback) { step.acceptVisitor(self, callback); },
119+
callback
120+
);
121+
},
122+
123+
skipStep: function skipStep(step, callback) {
124+
var payload = { step: step };
125+
var event = TreeWalker.Event(TreeWalker.SKIPPED_STEP_EVENT_NAME, payload);
126+
self.broadcastEvent(event, callback);
89127
}
90128
};
91129
return self;
@@ -94,6 +132,7 @@ TreeWalker.FEATURES_EVENT_NAME = 'Features';
94132
TreeWalker.FEATURE_EVENT_NAME = 'Feature';
95133
TreeWalker.SCENARIO_EVENT_NAME = 'Scenario';
96134
TreeWalker.STEP_EVENT_NAME = 'Step';
135+
TreeWalker.SKIPPED_STEP_EVENT_NAME = 'SkippedStep';
97136
TreeWalker.STEP_RESULT_EVENT_NAME = 'StepResult';
98137
TreeWalker.BEFORE_EVENT_NAME_PREFIX = 'Before';
99138
TreeWalker.AFTER_EVENT_NAME_PREFIX = 'After';

lib/cucumber/debug/simple_ast_listener.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ var SimpleAstListener = function(options) {
5656
log(currentStep.getDocString().getString(), 3);
5757
log('"""', 3);
5858
};
59+
if (!stepResult.isSuccessful()) {
60+
log('--- FAILED ---', 3);
61+
}
5962
callback();
6063
},
6164

@@ -93,4 +96,4 @@ var SimpleAstListener = function(options) {
9396
return indented;
9497
};
9598
};
96-
module.exports = SimpleAstListener;
99+
module.exports = SimpleAstListener;

0 commit comments

Comments
 (0)