Skip to content

Commit aa82f4f

Browse files
simonlampenjbpros
authored andcommitted
Target scenario by line number on CLI (close #168)
This adds a command line option for running single scenarios in feature files with the pattern some.feature:linenum.
1 parent da5c977 commit aa82f4f

File tree

11 files changed

+91
-10
lines changed

11 files changed

+91
-10
lines changed

bin/cucumber.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
#!/usr/bin/env node
22
var Cucumber = require('../lib/cucumber');
33
var cli = Cucumber.Cli(process.argv);
4-
cli.run(function(succeeded) {
4+
cli.run(function (succeeded) {
55
var code = succeeded ? 0 : 1;
66

7-
process.on('exit', function() {
7+
process.on('exit', function () {
88
process.exit(code);
99
});
10+
11+
var timeoutId = setTimeout(function () {
12+
console.error('Cucumber process timed out after waiting 60 seconds for the node.js event loop to empty. There may be a resource leak. Have all resources like database connections and network connections been closed properly?');
13+
process.exit(code);
14+
}, 60 * 1000);
15+
16+
if (timeoutId.unref) {
17+
timeoutId.unref();
18+
}
19+
else {
20+
clearTimeout(timeoutId);
21+
}
1022
});

features/cli.feature

+28
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,34 @@ Feature: Command line interface
2727
2828
"""
2929

30+
Scenario: run a single scenario within feature
31+
Given a file named "features/a.feature" with:
32+
"""
33+
Feature: some feature
34+
Scenario: first scenario
35+
When a step is passing
36+
37+
Scenario: second scenario
38+
When a step does not exist
39+
"""
40+
And a file named "features/step_definitions/cucumber_steps.js" with:
41+
"""
42+
var cucumberSteps = function() {
43+
this.When(/^a step is passing$/, function(callback) { callback(); });
44+
};
45+
module.exports = cucumberSteps;
46+
"""
47+
When I run `cucumber.js features/a.feature:2`
48+
Then it should pass with:
49+
"""
50+
.
51+
52+
1 scenario (1 passed)
53+
1 step (1 passed)
54+
55+
"""
56+
57+
3058
Scenario: run a single feature without step definitions
3159
Given a file named "features/a.feature" with:
3260
"""

features/step_definitions/cli_steps.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ var cliSteps = function cliSteps() {
6262
});
6363
});
6464

65-
this.Then(/^it passes with:$/, function(expectedOutput, callback) {
65+
this.Then(/^it should pass with:$/, function(expectedOutput, callback) {
6666
var actualOutput = lastRun['stdout'];
6767
var actualError = lastRun['error'];
6868
var actualStderr = lastRun['stderr'];
6969

70-
if (actualOutput.indexOf(expectedOutput) == -1)
70+
if (actualOutput == actualError)
7171
throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.\n" +
7272
"Error:\n'" + actualError + "'.\n" +
7373
"stderr:\n'" + actualStderr +"'.");

lib/cucumber/ast/filter.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ var Filter = function(rules) {
1313
};
1414
Filter.AnyOfTagsRule = require('./filter/any_of_tags_rule');
1515
Filter.ElementMatchingTagSpec = require('./filter/element_matching_tag_spec');
16+
Filter.ScenarioAtLineRule = require('./filter/only_run_scenario_at_line_rule');
1617
module.exports = Filter;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var ScenarioAtLineRule = function(tags) {
2+
var Cucumber = require('../../../cucumber');
3+
4+
var self = {
5+
isSatisfiedByElement: function isSatisfiedByElement(element) {
6+
if (element.getUri && element.getLine){
7+
var matches = Cucumber.Cli.ArgumentParser.FEATURE_FILENAME_AND_LINENUM_REGEXP.exec(element.getUri());
8+
var specifiedLineNum = matches && matches[2];
9+
if (specifiedLineNum) {
10+
return parseInt(specifiedLineNum) === element.getLine();
11+
}
12+
return true;
13+
}
14+
return true;
15+
}
16+
};
17+
return self;
18+
};
19+
module.exports = ScenarioAtLineRule;

lib/cucumber/cli/argument_parser.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ var ArgumentParser = function(argv) {
114114
};
115115
ArgumentParser.NUMBER_OF_LEADING_ARGS_TO_SLICE = 2;
116116
ArgumentParser.DEFAULT_FEATURES_DIRECTORY = "features";
117-
ArgumentParser.FEATURE_FILENAME_REGEXP = /[\/\\][^\/\\]+\.feature$/i;
117+
ArgumentParser.FEATURE_FILENAME_REGEXP = /[\/\\][^\/\\]+\.feature(:\d+)*$/i;
118+
ArgumentParser.FEATURE_FILENAME_AND_LINENUM_REGEXP = /(.*)\:(\d*)$|.*/; //fullmatch, filewithoutlinenum, linenum
118119
ArgumentParser.LONG_OPTION_PREFIX = "--";
119120
ArgumentParser.REQUIRE_OPTION_NAME = "require";
120121
ArgumentParser.REQUIRE_OPTION_SHORT_NAME = "r";

lib/cucumber/cli/argument_parser/feature_path_expander.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ var FeaturePathExpander = {
77
return expandedPaths;
88
}
99
};
10-
FeaturePathExpander.FEATURE_FILES_IN_DIR_REGEXP = /\.feature$/;
10+
FeaturePathExpander.FEATURE_FILES_IN_DIR_REGEXP = /\.feature(:\d+)*$/;
1111
module.exports = FeaturePathExpander;

lib/cucumber/cli/argument_parser/path_expander.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ var PathExpander = {
1414
},
1515

1616
expandPathWithRegexp: function expandPathWithRegexp(path, regexp) {
17+
var Cucumber = require('../../../cucumber');
18+
var matches = Cucumber.Cli.ArgumentParser.FEATURE_FILENAME_AND_LINENUM_REGEXP.exec(path);
19+
var lineNum = matches && matches[2];
20+
if (lineNum) {
21+
path = matches[1];
22+
}
1723
var realPath = fs.realpathSync(path);
1824
var stats = fs.statSync(realPath);
1925
if (stats.isDirectory()) {
2026
var paths = PathExpander.expandDirectoryWithRegexp(realPath, regexp);
2127
return paths;
2228
}
2329
else
24-
return [realPath];
30+
return [realPath + (lineNum ? (':' + lineNum) : '')];
2531
},
2632

2733
expandDirectoryWithRegexp: function expandDirectoryWithRegexp(directory, regexp) {

lib/cucumber/cli/configuration.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ var Configuration = function(argv) {
3636
},
3737

3838
getAstFilter: function getAstFilter() {
39-
var tagRules = self.getTagAstFilterRules();
40-
var astFilter = Cucumber.Ast.Filter(tagRules);
39+
var rules = self.getTagAstFilterRules();
40+
rules.push(self.getSingleScenarioAstFilterRule());
41+
var astFilter = Cucumber.Ast.Filter(rules);
4142
return astFilter;
4243
},
4344

@@ -58,6 +59,11 @@ var Configuration = function(argv) {
5859
return rules;
5960
},
6061

62+
getSingleScenarioAstFilterRule: function getSingleScenarioAstFilterRule() {
63+
var rule = Cucumber.Ast.Filter.ScenarioAtLineRule();
64+
return rule;
65+
},
66+
6167
isHelpRequested: function isHelpRequested() {
6268
var isHelpRequested = argumentParser.isHelpRequested();
6369
return isHelpRequested;

lib/cucumber/cli/feature_source_loader.js

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var FeatureSourceLoader = function(featureFilePaths) {
2+
var Cucumber = require('../../cucumber');
23
var fs = require('fs');
34

45
var self = {
@@ -12,6 +13,11 @@ var FeatureSourceLoader = function(featureFilePaths) {
1213
},
1314

1415
getSource: function getSource(featureFilePath) {
16+
var matches = Cucumber.Cli.ArgumentParser.FEATURE_FILENAME_AND_LINENUM_REGEXP.exec(featureFilePath);
17+
var wasLineNumSpecified = matches && matches[2];
18+
if (wasLineNumSpecified) {
19+
featureFilePath = matches[1];
20+
}
1521
var featureSource = fs.readFileSync(featureFilePath);
1622
return featureSource;
1723
}

spec/cucumber/cli/configuration_spec.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,11 @@ describe("Cucumber.Cli.Configuration", function () {
179179

180180
beforeEach(function () {
181181
astFilter = createSpyWithStubs("AST filter");
182-
tagFilterRules = createSpy("tag specs");
182+
tagFilterRules = [];
183+
scenarioByLineFilterRules = createSpy("line specs");
183184
spyOn(Cucumber.Ast, 'Filter').andReturn(astFilter);
184185
spyOn(configuration, 'getTagAstFilterRules').andReturn(tagFilterRules);
186+
spyOn(configuration, 'getSingleScenarioAstFilterRule').andReturn(scenarioByLineFilterRules);
185187
});
186188

187189
it("gets the tag filter rules", function () {

0 commit comments

Comments
 (0)