Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiple formatters #425

Closed
wants to merge 14 commits into from
37 changes: 37 additions & 0 deletions features/multiple_formatters.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Feature: Multiple Formatter

Scenario: Ability to specify multiple formatters
Given a file named "features/a.feature" with:
"""
Feature: some feature

Scenario: I've declared one step which passes
Given This step is passing
"""
And a file named "features/step_definitions/cucumber_steps.js" with:
"""
var cucumberSteps = function() {
this.Given(/^This step is passing$/, function(callback) { callback(); });
};
module.exports = cucumberSteps;
"""
When I run cucumber.js with `-f progress -f pretty:pretty.txt`
Then it outputs this text:
"""
.

1 scenario (1 passed)
1 step (1 passed)
<duration-stat>
"""
And the file "pretty.txt" has the text:
"""
Feature: some feature

Scenario: I've declared one step which passes # features/a.feature:3
Given This step is passing # features/a.feature:4

1 scenario (1 passed)
1 step (1 passed)
<duration-stat>
"""
15 changes: 15 additions & 0 deletions features/step_definitions/cli_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,21 @@ var cliSteps = function cliSteps() {
callback();
});

this.Then(/^the file "([^"]*)" has the text:$/, function (filePath, expectedContent, callback) {
var absoluteFilePath = tmpPath(filePath);
fs.readFile(absoluteFilePath, 'utf8', function (err, content){
if (err) { return callback(err); }

actualContent = normalizeText(content);
expectedContent = normalizeText(expectedContent);

if (actualContent != expectedContent)
throw new Error("Expected " + filePath + " to have content matching:\n'" + expectedContent + "'\n" +
"Got:\n'" + actualContent + "'.\n");
callback();
})
});

this.Then(/^I see the version of Cucumber$/, function(callback) {
var world = this;

Expand Down
11 changes: 7 additions & 4 deletions lib/cucumber/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ function Cli(argv) {
},

runSuiteWithConfiguration: function runSuiteWithConfiguration(configuration, callback) {
var runtime = Cucumber.Runtime(configuration);
var formatter = configuration.getFormatter();
runtime.attachListener(formatter);
var runtime = Cucumber.Runtime(configuration);
var formatters = configuration.getFormatters();
formatters.forEach(function (formatter) {
runtime.attachListener(formatter);
});
runtime.start(callback);
},

Expand Down Expand Up @@ -58,7 +60,8 @@ function Cli(argv) {
tags to exclude several tags you have to use\n\
logical AND: --tags ~@fixme --tags ~@buggy.\n\
\n\
-f, --format FORMAT How to format features (default: progress).\n\
-f, --format FORMAT[:PATH] How to format features (default: progress).\n\
Supply PATH to redirect that formatters output.\n\
Available formats:\n\
pretty : prints the feature as is\n\
progress: prints one character per scenario\n\
Expand Down
19 changes: 15 additions & 4 deletions lib/cucumber/cli/argument_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ function ArgumentParser(argv) {
var nopt = require('nopt');
var path = require('path');
var _ = require('underscore');
var fs = require('fs');
var options;

var self = {
Expand Down Expand Up @@ -106,9 +107,19 @@ function ArgumentParser(argv) {
return modules;
},

getFormat: function getFormat() {
var format = self.getOptionOrDefault(ArgumentParser.FORMAT_OPTION_NAME, ArgumentParser.DEFAULT_FORMAT_VALUE);
return format;
getFormats: function getFormats() {
var formats = self.getOptionOrDefault(ArgumentParser.FORMAT_OPTION_NAME, [ArgumentParser.DEFAULT_FORMAT_VALUE]);
var outputMapping = {};
formats.forEach(function (format) {
var parts = format.split(':');
var type = parts[0];
var outputTo = parts[1] || '';
outputMapping[outputTo] = type;
});
return _.map(outputMapping, function (type, outputTo) {
var stream = outputTo ? fs.createWriteStream(outputTo) : process.stdout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing a bad path will currently silently fail. We should listen to errors (.on('error', ...) and fail the whole process if something wrong happens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@charlierudolph could you add a test for that case as well? Cucumber's return code should also be non-zero.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbpros added

return {stream: stream, type: type};
});
},

getKnownOptionDefinitions: function getKnownOptionDefinitions() {
Expand All @@ -117,7 +128,7 @@ function ArgumentParser(argv) {
definitions[ArgumentParser.TAGS_OPTION_NAME] = [String, Array];
definitions[ArgumentParser.COMPILER_OPTION_NAME] = [String, Array];
definitions[ArgumentParser.PROFILE_OPTION_NAME] = [String, Array];
definitions[ArgumentParser.FORMAT_OPTION_NAME] = String;
definitions[ArgumentParser.FORMAT_OPTION_NAME] = [String, Array];
definitions[ArgumentParser.HELP_FLAG_NAME] = Boolean;
definitions[ArgumentParser.VERSION_FLAG_NAME] = Boolean;
definitions[ArgumentParser.COFFEE_SCRIPT_SNIPPETS_FLAG_NAME] = Boolean;
Expand Down
49 changes: 24 additions & 25 deletions lib/cucumber/cli/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,30 @@ function Configuration(argv) {
argumentParser.parse();

var self = {
getFormatter: function getFormatter() {
var formatter;
var format = argumentParser.getFormat();
var options = {
coffeeScriptSnippets: self.shouldSnippetsBeInCoffeeScript(),
snippets: self.shouldSnippetsBeShown(),
showSource: self.shouldShowSource()
};
switch(format) {
case Configuration.JSON_FORMAT_NAME:
formatter = Cucumber.Listener.JsonFormatter(options);
break;
case Configuration.PROGRESS_FORMAT_NAME:
formatter = Cucumber.Listener.ProgressFormatter(options);
break;
case Configuration.PRETTY_FORMAT_NAME:
formatter = Cucumber.Listener.PrettyFormatter(options);
break;
case Configuration.SUMMARY_FORMAT_NAME:
formatter = Cucumber.Listener.SummaryFormatter(options);
break;
default:
throw new Error('Unknown formatter name "' + format + '".');
}
return formatter;
getFormatters: function getFormatters() {
var formats = argumentParser.getFormats();
var formatters = formats.map(function (format) {
var options = {
coffeeScriptSnippets: self.shouldSnippetsBeInCoffeeScript(),
snippets: self.shouldSnippetsBeShown(),
showSource: self.shouldShowSource(),
stream: format.stream
};

switch(format.type) {
case Configuration.JSON_FORMAT_NAME:
return Cucumber.Listener.JsonFormatter(options);
case Configuration.PROGRESS_FORMAT_NAME:
return Cucumber.Listener.ProgressFormatter(options);
case Configuration.PRETTY_FORMAT_NAME:
return Cucumber.Listener.PrettyFormatter(options);
case Configuration.SUMMARY_FORMAT_NAME:
return Cucumber.Listener.SummaryFormatter(options);
default:
throw new Error('Unknown formatter name "' + format + '".');
}
});
return formatters;
},

getFeatureSources: function getFeatureSources() {
Expand Down
11 changes: 9 additions & 2 deletions lib/cucumber/listener/formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ function Formatter(options) {

self.log = function log(string) {
logs += string;
if (options.logToConsole)
process.stdout.write(string);
if (options.stream)
options.stream.write(string);
if (typeof(options.logToFunction) === 'function')
options.logToFunction(string);
};

self.finish = function finish(callback) {
if (options.stream && options.stream !== process.stdout)
options.stream.end(callback);
else
callback();
};

self.getLogs = function getLogs() {
return logs;
};
Expand Down
2 changes: 1 addition & 1 deletion lib/cucumber/listener/json_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function JsonFormatter(options) {
self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
gherkinJsonFormatter.eof();
gherkinJsonFormatter.done();
callback();
self.finish(callback);
};

return self;
Expand Down
2 changes: 1 addition & 1 deletion lib/cucumber/listener/pretty_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function PrettyFormatter(options) {
self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
var summaryLogs = summaryFormatter.getLogs();
self.log(summaryLogs);
callback();
self.finish(callback);
};

self.formatDataTable = function formatDataTable(stepResult, dataTable) {
Expand Down
2 changes: 1 addition & 1 deletion lib/cucumber/listener/progress_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function ProgressFormatter(options) {
var summaryLogs = summaryFormatter.getLogs();
self.log('\n\n');
self.log(summaryLogs);
callback();
self.finish(callback);
};

return self;
Expand Down
2 changes: 1 addition & 1 deletion lib/cucumber/listener/summary_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function SummaryFormatter(options) {

self.handleAfterFeaturesEvent = function handleAfterFeaturesEvent(event, callback) {
self.logSummary();
callback();
self.finish(callback);
};

self.storeFailedStepResult = function storeFailedStepResult(failedStepResult) {
Expand Down
32 changes: 17 additions & 15 deletions spec/cucumber/cli/argument_parser_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ require('../../support/spec_helper');
describe("Cucumber.Cli.ArgumentParser", function () {
var Cucumber = requireLib('cucumber');
var path = require('path');
var fs = require('fs');
var nopt;

var argumentParser, argv, slicedArgv;
Expand Down Expand Up @@ -196,8 +197,8 @@ describe("Cucumber.Cli.ArgumentParser", function () {
expect(knownOptionDefinitions['compiler']).toEqual([String, Array]);
});

it("defines a --format option", function () {
expect(knownOptionDefinitions['format']).toEqual(String);
it("defines a repeatable --format option", function () {
expect(knownOptionDefinitions['format']).toEqual([String, Array]);
});

it("defines a --strict flag", function () {
Expand Down Expand Up @@ -451,21 +452,22 @@ describe("Cucumber.Cli.ArgumentParser", function () {
});
});

describe("getFormat()", function () {
var format;
describe("getFormats()", function () {
var formats, stream;

beforeEach(function () {
format = createSpy("format");
spyOn(argumentParser, 'getOptionOrDefault').and.returnValue(format);
});

it("gets the format option value", function () {
argumentParser.getFormat();
expect(argumentParser.getOptionOrDefault).toHaveBeenCalledWith(Cucumber.Cli.ArgumentParser.FORMAT_OPTION_NAME, 'pretty');
});

it("returns the format", function () {
expect(argumentParser.getFormat()).toBe(format);
formats = ['progress', 'summary', 'pretty:path/to/file'];
stream = createSpy('stream');
spyOn(argumentParser, 'getOptionOrDefault').and.returnValue(formats);
spyOn(fs, 'createWriteStream').and.returnValue(stream);
});

it("returns the formats", function () {
expect(argumentParser.getFormats()).toEqual([
{stream: process.stdout, type: 'summary'},
{stream: stream, type: 'pretty'},
]);
expect(fs.createWriteStream).toHaveBeenCalledWith('path/to/file');
});
});

Expand Down
Loading