Skip to content

Commit 4ac1d6a

Browse files
committed
Allow several features to run at once and improve support code loading (CLI) (close #14)
- Allow several features to run at once - Add support for --require - Improve features and support code API - Add "Cli" and "Volatile" configurations - Internal refactoring and cleanup - Cucumber.js can now fully test itself - Remove run_all_features script in favor of bin/cucumber.js - Refactorings
1 parent dc16a2c commit 4ac1d6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1578
-287
lines changed

README.md

+19-21
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ It still needs a lot of work. Only a few feature elements are supported at the m
2020

2121
And probably lots of other browsers.
2222

23-
## Play with it!
23+
## Setup for using in Node.js and running tests
2424

25-
$ node example/server.js
25+
Install the required dependencies:
2626

27-
Then go to [localhost:9797](http://localhost:9797/).
27+
$ npm link
2828

29-
## Setup for using in Node.js and running tests
29+
## Play with it!
3030

31-
The only dependency of cucumber.js is Gherkin:
31+
$ node example/server.js
3232

33-
$ npm link
33+
Then go to [localhost:9797](http://localhost:9797/).
3434

3535
## Run tests
3636

@@ -39,37 +39,35 @@ The only dependency of cucumber.js is Gherkin:
3939
$ cd spec
4040
$ ../node_modules/.bin/jasmine-node .
4141

42-
### Features
43-
44-
Features run through cucumber.js have to be executed one at a time for the moment. We are working on it :)
42+
### Features & documentation
4543

46-
#### Cucumber-features
44+
There is a common set of features shared between all cucumber implementations. Find more on the [cucumber-features](http://github.com/cucumber/cucumber-features) repository.
4745

48-
There is a common set of features shared between all cucumber implementations. Find more on the [Github repository](http://github.com/cucumber/cucumber-features).
49-
50-
Ruby and Bundler are required for this to work.
46+
The official way of running them is through Cucumber-ruby and Aruba. Ruby and Bundler are required for this to work.
5147

5248
$ git submodule update --init
5349
$ bundle
54-
$ rm -rf doc; ARUBA_REPORT_DIR=doc cucumber features/cucumber-features/core.feature -r features
50+
$ rm -rf doc; ARUBA_REPORT_DIR=doc cucumber features/cucumber-features -r features
51+
52+
You can then open the generated documentation:
53+
5554
$ open doc/features/cucumber-features/*.html # might open a lot of files ;)
5655

57-
#### Run Cucumber.js to test itself
56+
In addition to that, Cucumber.js is able to run the features for itself too:
5857

59-
This is still a work in progress; some step definition mappings are missing to run the core.feature with Cucumber.js.
58+
$ ./bin/cucumber.js features/cucumber-features -r features
6059

61-
You can run the following script which will execute cucumber.js recursively against all known passing features and "core.feature":
60+
There are a few other Cucumber.js-dependent features. Execute everything:
6261

63-
$ ./run_all_features.js
62+
$ ./bin/cucumber.js
6463

6564
### Debug messages
6665

6766
You can display debug messages by setting the DEBUG_LEVEL environment variable. It goes from `1` to `5`. `5` will diplay everything, `1` will only print out the critical things.
6867

69-
$ DEBUG_LEVEL=5 ./run_all_features.js
70-
$ DEBUG_LEVEL=5 ./cucumber.js features/cucumber-features/core.feature
68+
$ DEBUG_LEVEL=5 ./bin/cucumber.js
7169

7270
It even works with Aruba:
7371

74-
$ rm -rf doc; DEBUG_LEVEL=5 ARUBA_REPORT_DIR=doc cucumber features/cucumber-features/core.feature -r features
72+
$ rm -rf doc; DEBUG_LEVEL=5 ARUBA_REPORT_DIR=doc cucumber features/cucumber-features -r features
7573
$ open doc/features/cucumber-features/*.html # you'll see debug messages in Aruba-generated docs

bin/cucumber.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env node
2+
var Cucumber = require('../lib/cucumber');
3+
var cli = Cucumber.Cli(process.argv);
4+
cli.run(function(succeeded) {
5+
var code = succeeded ? 0 : 1;
6+
var exitFunction = function() {
7+
process.exit(code);
8+
};
9+
10+
// --- exit after waiting for all pending output ---
11+
var waitingIO = false;
12+
process.stdout.on('drain', function() {
13+
if (waitingIO) {
14+
// the kernel buffer is now empty
15+
exitFunction();
16+
}
17+
});
18+
if (process.stdout.write("")) {
19+
// no buffer left, exit now:
20+
exitFunction();
21+
} else {
22+
// write() returned false, kernel buffer is not empty yet...
23+
waitingIO = true;
24+
}
25+
});

cucumber.js

-36
This file was deleted.

example/server.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ var server = connect.createServer();
44
server.use(connect.static(__dirname));
55
server.use(require('browserify')({
66
require: {'cucumber': './lib/cucumber.js',
7-
'./gherkin/lexer/en': 'gherkin/lib/gherkin/lexer/en'}
7+
'./gherkin/lexer/en': 'gherkin/lib/gherkin/lexer/en'},
8+
ignore: ['./cucumber/cli']
89
}));
910
server.listen(9797);
1011
console.log('Listening on port 9797...');

features/cli.feature

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Feature: Command line interface
2+
In order to run cucumber in different contexts
3+
As a person who wants to run features
4+
I want to run Cucumber on the command line
5+
6+
Scenario: run a single feature
7+
Given a file named "features/a.feature" with:
8+
"""
9+
Feature: some feature
10+
Scenario:
11+
When a step is passing
12+
"""
13+
Given a file named "features/step_definitions/cucumber_steps.js" with:
14+
"""
15+
var cucumberSteps = function() {
16+
When(/^a step is passing$/, function(callback) { callback(); });
17+
};
18+
module.exports = cucumberSteps;
19+
"""
20+
When I run `cucumber.js features/a.feature`
21+
Then it should pass with exactly:
22+
"""
23+
.
24+
25+
1 scenario (1 passed)
26+
1 step (1 passed)
27+
28+
"""
29+
30+
Scenario: run a single feature without step definitions
31+
Given a file named "features/a.feature" with:
32+
"""
33+
Feature: some feature
34+
Scenario:
35+
When a step is undefined
36+
"""
37+
When I run `cucumber.js features/a.feature`
38+
Then it should pass with exactly:
39+
"""
40+
U
41+
42+
1 scenario (1 undefined)
43+
1 step (1 undefined)
44+
45+
"""
46+
47+
Scenario: run feature with non-default step definitions file location specified (-r option)
48+
Given a file named "features/a.feature" with:
49+
"""
50+
Feature: some feature
51+
Scenario:
52+
When a step is passing
53+
"""
54+
Given a file named "step_definitions/cucumber_steps.js" with:
55+
"""
56+
var cucumberSteps = function() {
57+
When(/^a step is passing$/, function(callback) { callback(); });
58+
};
59+
module.exports = cucumberSteps;
60+
"""
61+
When I run `cucumber.js features/a.feature -r step_definitions/cucumber_steps.js`
62+
Then it should pass with exactly:
63+
"""
64+
.
65+
66+
1 scenario (1 passed)
67+
1 step (1 passed)
68+
69+
"""
70+
71+
Scenario: run feature with step definitions in required directory (-r option)
72+
Given a file named "features/a.feature" with:
73+
"""
74+
Feature: some feature
75+
Scenario:
76+
When a step is passing
77+
"""
78+
Given a file named "step_definitions/cucumber_steps.js" with:
79+
"""
80+
var cucumberSteps = function() {
81+
When(/^a step is passing$/, function(callback) { callback(); });
82+
};
83+
module.exports = cucumberSteps;
84+
"""
85+
When I run `cucumber.js features/a.feature -r step_definitions`
86+
Then it should pass with exactly:
87+
"""
88+
.
89+
90+
1 scenario (1 passed)
91+
1 step (1 passed)
92+
93+
"""
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
var cliSteps = function cliSteps() {
2+
var fs = require('fs');
3+
var rimraf = require('rimraf');
4+
var mkdirp = require('mkdirp');
5+
var exec = require('child_process').exec;
6+
7+
var baseDir = fs.realpathSync(__dirname + "/../..");
8+
var tmpDir = baseDir + "/tmp/cucumber-js-sandbox";
9+
var cleansingNeeded = true;
10+
11+
var lastRun;
12+
13+
function tmpPath(path) {
14+
return (tmpDir + "/" + path);
15+
};
16+
17+
function cleanseIfNeeded() {
18+
if (cleansingNeeded) {
19+
try { rimraf.sync(tmpDir); } catch(e) {}
20+
lastRun = { error: null, stdout: "", stderr: "" };
21+
cleansingNeeded = false;
22+
}
23+
};
24+
25+
Given(/^a file named "(.*)" with:$/, function(filePath, fileContent, callback) {
26+
cleanseIfNeeded();
27+
var absoluteFilePath = tmpPath(filePath);
28+
var filePathParts = absoluteFilePath.split('/');
29+
var fileName = filePathParts.pop();
30+
var dirName = filePathParts.join('/');
31+
mkdirp(dirName, 0755, function(err) {
32+
if (err) { throw new Error(err); }
33+
fs.writeFile(absoluteFilePath, fileContent, function(err) {
34+
if (err) { throw new Error(err); }
35+
callback();
36+
});
37+
});
38+
});
39+
40+
When(/^I run `cucumber.js(| .+)`$/, function(args, callback) {
41+
var initialCwd = process.cwd();
42+
process.chdir(tmpDir);
43+
var command = baseDir + "/bin/cucumber.js" + args;
44+
exec(command,
45+
function (error, stdout, stderr) {
46+
lastRun['error'] = error;
47+
lastRun['stdout'] = stdout;
48+
lastRun['stderr'] = stderr;
49+
process.chdir(initialCwd);
50+
cleansingNeeded = true;
51+
callback();
52+
});
53+
});
54+
55+
Then(/^it should pass with exactly:$/, function(expectedOutput, callback) {
56+
var actualOutput = lastRun['stdout'];
57+
if (actualOutput != expectedOutput)
58+
throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
59+
callback();
60+
});
61+
};
62+
module.exports = cliSteps;

features/step_definitions/cucumber_js_mappings.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ def run_scenario(scenario_name)
1212
# FIXME: do not run the whole feature but only the scenario:
1313
# run_simple "#{cucumber_bin} #{FEATURE_FILE} --name '#{scenario_name}'", false
1414
write_main_step_definitions_file
15-
run_simple "#{cucumber_bin} #{FEATURE_FILE} #{STEP_DEFINITIONS_FILE}", false
15+
run_simple "#{cucumber_bin} #{FEATURE_FILE}", false
1616
end
1717

1818
def run_feature
1919
write_main_step_definitions_file
20-
run_simple "#{cucumber_bin} #{FEATURE_FILE} #{STEP_DEFINITIONS_FILE}", false
20+
run_simple "#{cucumber_bin} #{FEATURE_FILE}", false
2121
end
2222

2323
def cucumber_bin
24-
File.expand_path(File.dirname(__FILE__) + '/../../cucumber.js')
24+
File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber.js')
2525
end
2626

2727
def write_passing_mapping(step_name)

lib/cucumber.js

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
var Cucumber = function(featuresSource, supportCodeDefinition) {
2-
return Cucumber.Runtime(featuresSource, supportCodeDefinition);
1+
var Cucumber = function(featureSource, supportCodeInitializer) {
2+
var configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer);
3+
var runtime = Cucumber.Runtime(configuration);
4+
return runtime;
35
};
4-
Cucumber.START_MISSING_CALLBACK_ERROR = "Cucumber.start() expects a callback.";
5-
Cucumber.Parser = require('./cucumber/parser');
6-
Cucumber.Ast = require('./cucumber/ast');
7-
Cucumber.SupportCode = require('./cucumber/support_code');
8-
Cucumber.Runtime = require('./cucumber/runtime');
9-
Cucumber.Listener = require('./cucumber/listener');
10-
Cucumber.Type = require('./cucumber/type');
11-
Cucumber.Util = require('./cucumber/util');
12-
Cucumber.Debug = require('./cucumber/debug'); // Untested namespace
13-
module.exports = Cucumber;
6+
Cucumber.Ast = require('./cucumber/ast');
7+
// browserify won't load ./cucumber/cli and throw an exception:
8+
try { Cucumber.Cli = require('./cucumber/cli'); } catch(e) {}
9+
Cucumber.Debug = require('./cucumber/debug'); // Untested namespace
10+
Cucumber.Listener = require('./cucumber/listener');
11+
Cucumber.Parser = require('./cucumber/parser');
12+
Cucumber.Runtime = require('./cucumber/runtime');
13+
Cucumber.SupportCode = require('./cucumber/support_code');
14+
Cucumber.Type = require('./cucumber/type');
15+
Cucumber.Util = require('./cucumber/util');
16+
Cucumber.VolatileConfiguration = require('./cucumber/volatile_configuration');
17+
module.exports = Cucumber;

lib/cucumber/ast.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ Ast.Feature = require('./ast/feature');
44
Ast.Scenario = require('./ast/scenario');
55
Ast.Step = require('./ast/step');
66
Ast.DocString = require('./ast/doc_string');
7-
Ast.TreeWalker = require('./ast/tree_walker');
8-
module.exports = Ast;
7+
module.exports = Ast;

lib/cucumber/cli.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var Cli = function(argv) {
2+
var Cucumber = require('../cucumber');
3+
4+
var self = {
5+
run: function run(callback) {
6+
var configuration = Cli.Configuration(argv);
7+
var runtime = Cucumber.Runtime(configuration);
8+
var progressFormatter = Cucumber.Listener.ProgressFormatter();
9+
runtime.attachListener(progressFormatter);
10+
runtime.start(callback);
11+
}
12+
};
13+
return self;
14+
};
15+
Cli.ArgumentParser = require('./cli/argument_parser');
16+
Cli.Configuration = require('./cli/configuration');
17+
Cli.FeatureSourceLoader = require('./cli/feature_source_loader');
18+
Cli.SupportCodeLoader = require('./cli/support_code_loader');
19+
module.exports = Cli;

0 commit comments

Comments
 (0)