Permalink
Browse files

Allow several features to run at once and improve support code loadin…

…g (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
  • Loading branch information...
1 parent dc16a2c commit 4ac1d6af9e4c1fd96db3bd56a21fec80cea268fa @jbpros jbpros committed Sep 3, 2011
Showing with 1,578 additions and 287 deletions.
  1. +19 −21 README.md
  2. +25 −0 bin/cucumber.js
  3. +0 −36 cucumber.js
  4. +2 −1 example/server.js
  5. +93 −0 features/cli.feature
  6. +62 −0 features/step_definitions/cli_steps.js
  7. +3 −3 features/step_definitions/cucumber_js_mappings.rb
  8. +16 −12 lib/cucumber.js
  9. +1 −2 lib/cucumber/ast.js
  10. +19 −0 lib/cucumber/cli.js
  11. +89 −0 lib/cucumber/cli/argument_parser.js
  12. +15 −0 lib/cucumber/cli/argument_parser/feature_path_expander.js
  13. +25 −0 lib/cucumber/cli/argument_parser/path_expander.js
  14. +11 −0 lib/cucumber/cli/argument_parser/support_code_path_expander.js
  15. +24 −0 lib/cucumber/cli/configuration.js
  16. +21 −0 lib/cucumber/cli/feature_source_loader.js
  17. +64 −0 lib/cucumber/cli/support_code_loader.js
  18. +5 −2 lib/cucumber/parser.js
  19. +15 −15 lib/cucumber/runtime.js
  20. +21 −21 lib/cucumber/{ast/tree_walker.js → runtime/ast_tree_walker.js}
  21. +6 −6 lib/cucumber/{ast/tree_walker → runtime/ast_tree_walker}/event.js
  22. +19 −0 lib/cucumber/volatile_configuration.js
  23. +8 −3 package.json
  24. +0 −57 run_all_features.js
  25. +26 −0 spec/cucumber/cli/argument_parser/feature_path_expander_spec.js
  26. +96 −0 spec/cucumber/cli/argument_parser/path_expander_spec.js
  27. +26 −0 spec/cucumber/cli/argument_parser/support_code_path_expander_spec.js
  28. +272 −0 spec/cucumber/cli/argument_parser_spec.js
  29. +94 −0 spec/cucumber/cli/configuration_spec.js
  30. +55 −0 spec/cucumber/cli/feature_source_loader_spec.js
  31. +201 −0 spec/cucumber/cli/support_code_loader_spec.js
  32. +51 −0 spec/cucumber/cli_spec.js
  33. +6 −6 spec/cucumber/parser_spec.js
  34. +11 −11 spec/cucumber/{ast/tree_walker → runtime/ast_tree_walker}/event_spec.js
  35. +16 −16 spec/cucumber/{ast/tree_walker_spec.js → runtime/ast_tree_walker_spec.js}
  36. +52 −61 spec/cucumber/runtime_spec.js
  37. +41 −0 spec/cucumber/volatile_configuration_spec.js
  38. +16 −10 spec/cucumber_spec.js
  39. +15 −0 spec/support/configurations_shared_examples.js
  40. +1 −0 spec/support/initializer_stub1.js
  41. +1 −0 spec/support/initializer_stub2.js
  42. +35 −4 spec/support/spec_helper.js
View
@@ -20,17 +20,17 @@ It still needs a lot of work. Only a few feature elements are supported at the m
And probably lots of other browsers.
-## Play with it!
+## Setup for using in Node.js and running tests
- $ node example/server.js
+Install the required dependencies:
-Then go to [localhost:9797](http://localhost:9797/).
+ $ npm link
-## Setup for using in Node.js and running tests
+## Play with it!
-The only dependency of cucumber.js is Gherkin:
+ $ node example/server.js
- $ npm link
+Then go to [localhost:9797](http://localhost:9797/).
## Run tests
@@ -39,37 +39,35 @@ The only dependency of cucumber.js is Gherkin:
$ cd spec
$ ../node_modules/.bin/jasmine-node .
-### Features
-
-Features run through cucumber.js have to be executed one at a time for the moment. We are working on it :)
+### Features & documentation
-#### Cucumber-features
+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.
-There is a common set of features shared between all cucumber implementations. Find more on the [Github repository](http://github.com/cucumber/cucumber-features).
-
-Ruby and Bundler are required for this to work.
+The official way of running them is through Cucumber-ruby and Aruba. Ruby and Bundler are required for this to work.
$ git submodule update --init
$ bundle
- $ rm -rf doc; ARUBA_REPORT_DIR=doc cucumber features/cucumber-features/core.feature -r features
+ $ rm -rf doc; ARUBA_REPORT_DIR=doc cucumber features/cucumber-features -r features
+
+You can then open the generated documentation:
+
$ open doc/features/cucumber-features/*.html # might open a lot of files ;)
-#### Run Cucumber.js to test itself
+In addition to that, Cucumber.js is able to run the features for itself too:
-This is still a work in progress; some step definition mappings are missing to run the core.feature with Cucumber.js.
+ $ ./bin/cucumber.js features/cucumber-features -r features
-You can run the following script which will execute cucumber.js recursively against all known passing features and "core.feature":
+There are a few other Cucumber.js-dependent features. Execute everything:
- $ ./run_all_features.js
+ $ ./bin/cucumber.js
### Debug messages
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.
- $ DEBUG_LEVEL=5 ./run_all_features.js
- $ DEBUG_LEVEL=5 ./cucumber.js features/cucumber-features/core.feature
+ $ DEBUG_LEVEL=5 ./bin/cucumber.js
It even works with Aruba:
- $ rm -rf doc; DEBUG_LEVEL=5 ARUBA_REPORT_DIR=doc cucumber features/cucumber-features/core.feature -r features
+ $ rm -rf doc; DEBUG_LEVEL=5 ARUBA_REPORT_DIR=doc cucumber features/cucumber-features -r features
$ open doc/features/cucumber-features/*.html # you'll see debug messages in Aruba-generated docs
View
@@ -0,0 +1,25 @@
+#!/usr/bin/env node
+var Cucumber = require('../lib/cucumber');
+var cli = Cucumber.Cli(process.argv);
+cli.run(function(succeeded) {
+ var code = succeeded ? 0 : 1;
+ var exitFunction = function() {
+ process.exit(code);
+ };
+
+ // --- exit after waiting for all pending output ---
+ var waitingIO = false;
+ process.stdout.on('drain', function() {
+ if (waitingIO) {
+ // the kernel buffer is now empty
+ exitFunction();
+ }
+ });
+ if (process.stdout.write("")) {
+ // no buffer left, exit now:
+ exitFunction();
+ } else {
+ // write() returned false, kernel buffer is not empty yet...
+ waitingIO = true;
+ }
+});
View
@@ -1,36 +0,0 @@
-#!/usr/bin/env node
-var fs = require('fs');
-var Cucumber = require('./lib/cucumber');
-var featurePath = process.ARGV[2];
-var supportCodePath = process.ARGV[3] ? process.cwd() + '/' + process.ARGV[3] : './features/step_definitions/cucumber_steps';
-
-if (typeof(featurePath) == 'undefined') {
- throw(new Error("Please give me a feature, try something like `" + process.ARGV[1] + " features/cucumber-features/core.feature`."));
-}
-
-var supportCode = require(supportCodePath);
-var cucumber = Cucumber(fs.readFileSync(featurePath), supportCode);
-var formatter = Cucumber.Listener.ProgressFormatter();
-cucumber.attachListener(formatter);
-cucumber.start(function(succeeded) {
- var code = succeeded ? 0 : 1;
- var exitFunction = function() {
- process.exit(code);
- };
-
- // --- exit after waiting for all pending output ---
- var waitingIO = false;
- process.stdout.on('drain', function() {
- if (waitingIO) {
- // the kernel buffer is now empty
- exitFunction();
- }
- });
- if (process.stdout.write("")) {
- // no buffer left, exit now:
- exitFunction();
- } else {
- // write() returned false, kernel buffer is not empty yet...
- waitingIO = true;
- }
-});
View
@@ -4,7 +4,8 @@ var server = connect.createServer();
server.use(connect.static(__dirname));
server.use(require('browserify')({
require: {'cucumber': './lib/cucumber.js',
- './gherkin/lexer/en': 'gherkin/lib/gherkin/lexer/en'}
+ './gherkin/lexer/en': 'gherkin/lib/gherkin/lexer/en'},
+ ignore: ['./cucumber/cli']
}));
server.listen(9797);
console.log('Listening on port 9797...');
View
@@ -0,0 +1,93 @@
+Feature: Command line interface
+ In order to run cucumber in different contexts
+ As a person who wants to run features
+ I want to run Cucumber on the command line
+
+ Scenario: run a single feature
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+ Scenario:
+ When a step is passing
+ """
+ Given a file named "features/step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ When(/^a step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js features/a.feature`
+ Then it should pass with exactly:
+ """
+ .
+
+ 1 scenario (1 passed)
+ 1 step (1 passed)
+
+ """
+
+ Scenario: run a single feature without step definitions
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+ Scenario:
+ When a step is undefined
+ """
+ When I run `cucumber.js features/a.feature`
+ Then it should pass with exactly:
+ """
+ U
+
+ 1 scenario (1 undefined)
+ 1 step (1 undefined)
+
+ """
+
+ Scenario: run feature with non-default step definitions file location specified (-r option)
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+ Scenario:
+ When a step is passing
+ """
+ Given a file named "step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ When(/^a step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js features/a.feature -r step_definitions/cucumber_steps.js`
+ Then it should pass with exactly:
+ """
+ .
+
+ 1 scenario (1 passed)
+ 1 step (1 passed)
+
+ """
+
+ Scenario: run feature with step definitions in required directory (-r option)
+ Given a file named "features/a.feature" with:
+ """
+ Feature: some feature
+ Scenario:
+ When a step is passing
+ """
+ Given a file named "step_definitions/cucumber_steps.js" with:
+ """
+ var cucumberSteps = function() {
+ When(/^a step is passing$/, function(callback) { callback(); });
+ };
+ module.exports = cucumberSteps;
+ """
+ When I run `cucumber.js features/a.feature -r step_definitions`
+ Then it should pass with exactly:
+ """
+ .
+
+ 1 scenario (1 passed)
+ 1 step (1 passed)
+
+ """
@@ -0,0 +1,62 @@
+var cliSteps = function cliSteps() {
+ var fs = require('fs');
+ var rimraf = require('rimraf');
+ var mkdirp = require('mkdirp');
+ var exec = require('child_process').exec;
+
+ var baseDir = fs.realpathSync(__dirname + "/../..");
+ var tmpDir = baseDir + "/tmp/cucumber-js-sandbox";
+ var cleansingNeeded = true;
+
+ var lastRun;
+
+ function tmpPath(path) {
+ return (tmpDir + "/" + path);
+ };
+
+ function cleanseIfNeeded() {
+ if (cleansingNeeded) {
+ try { rimraf.sync(tmpDir); } catch(e) {}
+ lastRun = { error: null, stdout: "", stderr: "" };
+ cleansingNeeded = false;
+ }
+ };
+
+ Given(/^a file named "(.*)" with:$/, function(filePath, fileContent, callback) {
+ cleanseIfNeeded();
+ var absoluteFilePath = tmpPath(filePath);
+ var filePathParts = absoluteFilePath.split('/');
+ var fileName = filePathParts.pop();
+ var dirName = filePathParts.join('/');
+ mkdirp(dirName, 0755, function(err) {
+ if (err) { throw new Error(err); }
+ fs.writeFile(absoluteFilePath, fileContent, function(err) {
+ if (err) { throw new Error(err); }
+ callback();
+ });
+ });
+ });
+
+ When(/^I run `cucumber.js(| .+)`$/, function(args, callback) {
+ var initialCwd = process.cwd();
+ process.chdir(tmpDir);
+ var command = baseDir + "/bin/cucumber.js" + args;
+ exec(command,
+ function (error, stdout, stderr) {
+ lastRun['error'] = error;
+ lastRun['stdout'] = stdout;
+ lastRun['stderr'] = stderr;
+ process.chdir(initialCwd);
+ cleansingNeeded = true;
+ callback();
+ });
+ });
+
+ Then(/^it should pass with exactly:$/, function(expectedOutput, callback) {
+ var actualOutput = lastRun['stdout'];
+ if (actualOutput != expectedOutput)
+ throw new Error("Expected output to match the following:\n'" + expectedOutput + "'\nGot:\n'" + actualOutput + "'.");
+ callback();
+ });
+};
+module.exports = cliSteps;
@@ -12,16 +12,16 @@ def run_scenario(scenario_name)
# FIXME: do not run the whole feature but only the scenario:
# run_simple "#{cucumber_bin} #{FEATURE_FILE} --name '#{scenario_name}'", false
write_main_step_definitions_file
- run_simple "#{cucumber_bin} #{FEATURE_FILE} #{STEP_DEFINITIONS_FILE}", false
+ run_simple "#{cucumber_bin} #{FEATURE_FILE}", false
end
def run_feature
write_main_step_definitions_file
- run_simple "#{cucumber_bin} #{FEATURE_FILE} #{STEP_DEFINITIONS_FILE}", false
+ run_simple "#{cucumber_bin} #{FEATURE_FILE}", false
end
def cucumber_bin
- File.expand_path(File.dirname(__FILE__) + '/../../cucumber.js')
+ File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber.js')
end
def write_passing_mapping(step_name)
View
@@ -1,13 +1,17 @@
-var Cucumber = function(featuresSource, supportCodeDefinition) {
- return Cucumber.Runtime(featuresSource, supportCodeDefinition);
+var Cucumber = function(featureSource, supportCodeInitializer) {
+ var configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer);
+ var runtime = Cucumber.Runtime(configuration);
+ return runtime;
};
-Cucumber.START_MISSING_CALLBACK_ERROR = "Cucumber.start() expects a callback.";
-Cucumber.Parser = require('./cucumber/parser');
-Cucumber.Ast = require('./cucumber/ast');
-Cucumber.SupportCode = require('./cucumber/support_code');
-Cucumber.Runtime = require('./cucumber/runtime');
-Cucumber.Listener = require('./cucumber/listener');
-Cucumber.Type = require('./cucumber/type');
-Cucumber.Util = require('./cucumber/util');
-Cucumber.Debug = require('./cucumber/debug'); // Untested namespace
-module.exports = Cucumber;
+Cucumber.Ast = require('./cucumber/ast');
+// browserify won't load ./cucumber/cli and throw an exception:
+try { Cucumber.Cli = require('./cucumber/cli'); } catch(e) {}
+Cucumber.Debug = require('./cucumber/debug'); // Untested namespace
+Cucumber.Listener = require('./cucumber/listener');
+Cucumber.Parser = require('./cucumber/parser');
+Cucumber.Runtime = require('./cucumber/runtime');
+Cucumber.SupportCode = require('./cucumber/support_code');
+Cucumber.Type = require('./cucumber/type');
+Cucumber.Util = require('./cucumber/util');
+Cucumber.VolatileConfiguration = require('./cucumber/volatile_configuration');
+module.exports = Cucumber;
View
@@ -4,5 +4,4 @@ Ast.Feature = require('./ast/feature');
Ast.Scenario = require('./ast/scenario');
Ast.Step = require('./ast/step');
Ast.DocString = require('./ast/doc_string');
-Ast.TreeWalker = require('./ast/tree_walker');
-module.exports = Ast;
+module.exports = Ast;
View
@@ -0,0 +1,19 @@
+var Cli = function(argv) {
+ var Cucumber = require('../cucumber');
+
+ var self = {
+ run: function run(callback) {
+ var configuration = Cli.Configuration(argv);
+ var runtime = Cucumber.Runtime(configuration);
+ var progressFormatter = Cucumber.Listener.ProgressFormatter();
+ runtime.attachListener(progressFormatter);
+ runtime.start(callback);
+ }
+ };
+ return self;
+};
+Cli.ArgumentParser = require('./cli/argument_parser');
+Cli.Configuration = require('./cli/configuration');
+Cli.FeatureSourceLoader = require('./cli/feature_source_loader');
+Cli.SupportCodeLoader = require('./cli/support_code_loader');
+module.exports = Cli;
Oops, something went wrong.

2 comments on commit 4ac1d6a

Contributor

Impressive progress. Congratulations. I've been looking at cucumis and it supports some things cucumber-js still doesn't (like tables, backgrounds and scenario outlines) and has a pretty reporter. But other things are broken in cucumis, and cucumber-js seems to be more active and is able to run the core features and more.

Owner

Thanks Fernando.

I know Cucumber.js is progressing a bit slower than its alternatives. This is mainly due to the short number of people working on it (basically: one and that guy needs to work for food too ;) ).

Backgrounds and tables are the next features I'll be working on. Scenario outlines are planned for v0.3.

Please sign in to comment.