Skip to content

Commit 592d8ea

Browse files
author
Brian Miller
committed
Added junit format support
Modified the snapdragon binary to accept a format parameter to enable results to be outputted in junit. Added the junit reporter and modified the run html template to load the junit formatter when the format has been set to junit.
1 parent 0d0cf66 commit 592d8ea

10 files changed

+250
-32
lines changed

bin/snapdragon

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ require 'rubygems'
66
require 'snapdragon/command_line_parser'
77
require 'snapdragon/cli_application'
88

9-
Snapdragon::CommandLineParser.parse(ARGV)
10-
app = Snapdragon::CliApplication.new(ARGV)
9+
opts = Snapdragon::CommandLineParser.parse(ARGV)
10+
app = Snapdragon::CliApplication.new(opts, ARGV)
1111
exit(app.run)

bin/snapdragon_server

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ require 'rubygems'
66
require 'snapdragon/command_line_parser'
77
require 'snapdragon/cli_application'
88

9-
Snapdragon::CommandLineParser.parse(ARGV)
10-
app = Snapdragon::CliApplication.new(ARGV)
9+
opts = Snapdragon::CommandLineParser.parse(ARGV)
10+
app = Snapdragon::CliApplication.new(opts, ARGV)
1111
app.serve

lib/snapdragon/cli_application.rb

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313

1414
module Snapdragon
1515
class CliApplication
16-
def initialize(arguements)
17-
@args = arguements
18-
@suite = Snapdragon::Suite.new(arguements)
16+
def initialize(options, paths)
17+
@suite = Snapdragon::Suite.new(options, paths)
1918
end
2019

2120
def run

lib/snapdragon/command_line_parser.rb

+8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
require 'optparse'
2+
require 'ostruct'
23
require_relative './version'
34

45
module Snapdragon
56
class CommandLineParser
67
def self.parse(args)
8+
options = OpenStruct.new
9+
options.format = "console"
10+
711
opts = OptionParser.new do |opts|
812
opts.banner = "Usage: snapdragon [options] [files or directories]"
913
opts.on('-v', '--version', "Show the current version of this gem") do
@@ -12,11 +16,15 @@ def self.parse(args)
1216
opts.on('-h', '--help', "show usage") do
1317
puts opts; exit
1418
end
19+
opts.on('-f', '--format [FORMAT]', "set output format") do |format|
20+
options.format = format
21+
end
1522
if args.empty?
1623
puts opts; exit
1724
end
1825
end
1926
opts.parse!(args)
27+
options
2028
end
2129
end
2230
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* This is the junit reporter provided in the jasmine-reporters project
3+
* available at https://github.com/larrymyers/jasmine-reporters
4+
*/
5+
(function() {
6+
7+
if (typeof jasmine == 'undefined') {
8+
throw new Error("jasmine library does not exist in global namespace!");
9+
}
10+
11+
function elapsed(startTime, endTime) {
12+
return (endTime - startTime)/1000;
13+
}
14+
15+
function ISODateString(d) {
16+
function pad(n) { return n < 10 ? '0'+n : n; }
17+
18+
return d.getFullYear() + '-' +
19+
pad(d.getMonth()+1) + '-' +
20+
pad(d.getDate()) + 'T' +
21+
pad(d.getHours()) + ':' +
22+
pad(d.getMinutes()) + ':' +
23+
pad(d.getSeconds());
24+
}
25+
26+
function trim(str) {
27+
return str.replace(/^\s+/, "" ).replace(/\s+$/, "" );
28+
}
29+
30+
function escapeInvalidXmlChars(str) {
31+
return str.replace(/\&/g, "&amp;")
32+
.replace(/</g, "&lt;")
33+
.replace(/\>/g, "&gt;")
34+
.replace(/\"/g, "&quot;")
35+
.replace(/\'/g, "&apos;");
36+
}
37+
38+
var JUnitXmlReporter = function() {
39+
this.useDotNotation = true;
40+
};
41+
42+
JUnitXmlReporter.prototype = {
43+
reportSpecStarting: function(spec) {
44+
spec.startTime = new Date();
45+
46+
if (!spec.suite.startTime) {
47+
spec.suite.startTime = spec.startTime;
48+
}
49+
},
50+
51+
reportSpecResults: function(spec) {
52+
var results = spec.results();
53+
spec.didFail = !results.passed();
54+
spec.duration = elapsed(spec.startTime, new Date());
55+
spec.output = '<testcase classname="' + this.getFullName(spec.suite) +
56+
'" name="' + escapeInvalidXmlChars(spec.description) + '" time="' + spec.duration + '">';
57+
if(results.skipped) {
58+
spec.output = spec.output + "<skipped />";
59+
}
60+
61+
var failure = "";
62+
var failures = 0;
63+
var resultItems = results.getItems();
64+
for (var i = 0; i < resultItems.length; i++) {
65+
var result = resultItems[i];
66+
67+
if (result.type == 'expect' && result.passed && !result.passed()) {
68+
failures += 1;
69+
failure += '<failure type="' + result.type + '" message="' + trim(escapeInvalidXmlChars(result.message)) + '">';
70+
failure += escapeInvalidXmlChars(result.trace.stack || result.message);
71+
failure += "</failure>";
72+
}
73+
}
74+
if (failure) {
75+
spec.output += failure;
76+
}
77+
spec.output += "</testcase>";
78+
},
79+
80+
reportSuiteResults: function(suite) {
81+
var results = suite.results();
82+
var specs = suite.specs();
83+
var specOutput = "";
84+
// for JUnit results, let's only include directly failed tests (not nested suites')
85+
var failedCount = 0;
86+
87+
suite.status = results.passed() ? 'Passed.' : 'Failed.';
88+
if (results.totalCount === 0) { // todo: change this to check results.skipped
89+
suite.status = 'Skipped.';
90+
}
91+
92+
// if a suite has no (active?) specs, reportSpecStarting is never called
93+
// and thus the suite has no startTime -- account for that here
94+
suite.startTime = suite.startTime || new Date();
95+
suite.duration = elapsed(suite.startTime, new Date());
96+
97+
for (var i = 0; i < specs.length; i++) {
98+
failedCount += specs[i].didFail ? 1 : 0;
99+
specOutput += "\n " + specs[i].output;
100+
}
101+
suite.output = '\n<testsuite name="' + this.getFullName(suite) +
102+
'" errors="0" tests="' + specs.length + '" failures="' + failedCount +
103+
'" time="' + suite.duration + '" timestamp="' + ISODateString(suite.startTime) + '">';
104+
suite.output += specOutput;
105+
suite.output += "\n</testsuite>";
106+
},
107+
108+
reportRunnerResults: function(runner) {
109+
var suites = runner.suites();
110+
for (var i = 0; i < suites.length; i++) {
111+
var suite = suites[i];
112+
var output = '<?xml version="1.0" encoding="UTF-8" ?>';
113+
// if we are consolidating, only write out top-level suites
114+
if (suite.parentSuite) {
115+
continue;
116+
}
117+
else {
118+
output += "\n<testsuites>";
119+
output += this.getNestedOutput(suite);
120+
output += "\n</testsuites>";
121+
this.print(output);
122+
}
123+
}
124+
this.signalCapybaraTestsFinishedRunning();
125+
},
126+
127+
signalCapybaraTestsFinishedRunning: function() {
128+
var div = document.createElement('div');
129+
div.id = 'testscomplete';
130+
document.body.appendChild(div);
131+
},
132+
133+
getNestedOutput: function(suite) {
134+
var output = suite.output;
135+
for (var i = 0; i < suite.suites().length; i++) {
136+
output += this.getNestedOutput(suite.suites()[i]);
137+
}
138+
return output;
139+
},
140+
141+
getFullName: function(suite, isFilename) {
142+
var fullName;
143+
if (this.useDotNotation) {
144+
fullName = suite.description;
145+
for (var parentSuite = suite.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
146+
fullName = parentSuite.description + '.' + fullName;
147+
}
148+
}
149+
else {
150+
fullName = suite.getFullName();
151+
}
152+
153+
// Either remove or escape invalid XML characters
154+
if (isFilename) {
155+
return fullName.replace(/[^\w]/g, "");
156+
}
157+
return escapeInvalidXmlChars(fullName);
158+
},
159+
160+
print: function(str) {
161+
var console = jasmine.getGlobal().console;
162+
163+
if (console && console.log) {
164+
console.log(str);
165+
}
166+
}
167+
};
168+
169+
// export public
170+
jasmine.SnapdragonJUnitReporter = JUnitXmlReporter;
171+
})();

lib/snapdragon/suite.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
module Snapdragon
44
class Suite
5-
def initialize(paths)
5+
def initialize(options, paths)
6+
@options = options
67
@paths = paths
78
end
89

10+
def formatter
11+
@options.format
12+
end
13+
914
def spec_files
1015
spec_file_objs = []
1116

lib/snapdragon/views/run.erb

+18-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
<script type="text/javascript" src="/jasmine-core/jasmine.js"></script>
33
<script type="text/javascript" src="/jasmine-core/jasmine-html.js"></script>
44

5+
<% if @suite.formatter == "junit" %>
6+
<script type="text/javascript" src="/resources/SnapdragonJUnitReporter.js"></script>
7+
<% else %>
58
<script type="text/javascript" src="/resources/SnapdragonConsoleReporter.js"></script>
9+
<% end %>
610

711
<!-- The implementation code the spec files being tested need -->
812
<% @suite.require_file_relative_url_paths.each do |path| %>
@@ -19,16 +23,20 @@
1923
var jasmineEnv = jasmine.getEnv();
2024
jasmineEnv.updateInterval = 1000;
2125

22-
var htmlReporter = new jasmine.HtmlReporter();
23-
24-
jasmineEnv.addReporter(htmlReporter);
25-
26-
var snapdragonConsoleReporter = new jasmine.SnapdragonConsoleReporter({});
27-
jasmineEnv.addReporter(snapdragonConsoleReporter);
28-
29-
jasmineEnv.specFilter = function(spec) {
30-
return htmlReporter.specFilter(spec);
31-
};
26+
<% if @suite.formatter == "junit" %>
27+
var snapdragonJUnitReporter = new jasmine.SnapdragonJUnitReporter();
28+
jasmineEnv.addReporter(new jasmine.TrivialReporter());
29+
jasmineEnv.addReporter(snapdragonJUnitReporter);
30+
<% else %>
31+
var htmlReporter = new jasmine.HtmlReporter();
32+
jasmineEnv.addReporter(htmlReporter);
33+
var snapdragonConsoleReporter = new jasmine.SnapdragonConsoleReporter({});
34+
jasmineEnv.addReporter(snapdragonConsoleReporter);
35+
36+
jasmineEnv.specFilter = function(spec) {
37+
return htmlReporter.specFilter(spec);
38+
};
39+
<% end %>
3240

3341
var currentWindowOnload = window.onload;
3442

spec/lib/snapdragon/cli_application_spec.rb

+5-10
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,23 @@
22

33
describe Snapdragon::CliApplication do
44
describe "#initialize" do
5-
it "stores a copy of the given command line arguments" do
6-
cmd_line_args = stub('command_line_args')
7-
cli_app = Snapdragon::CliApplication.new(cmd_line_args)
8-
cli_app.instance_variable_get(:@args).should eq(cmd_line_args)
9-
end
10-
115
it "creates an empty Suite" do
126
Snapdragon::Suite.should_receive(:new)
13-
Snapdragon::CliApplication.new(stub)
7+
Snapdragon::CliApplication.new(stub, stub)
148
end
159

1610
it "assigns the new Suite to an instance variable" do
1711
suite = stub('suite')
1812
Snapdragon::Suite.stub(:new).and_return(suite)
19-
app = Snapdragon::CliApplication.new(stub)
13+
app = Snapdragon::CliApplication.new(stub, stub)
2014
app.instance_variable_get(:@suite).should eq(suite)
2115
end
2216
end
2317

2418
describe "#run" do
25-
let(:arguements) { stub('arguments') }
26-
subject { Snapdragon::CliApplication.new(arguements) }
19+
let(:paths) { stub('paths') }
20+
let(:options) { stub('options') }
21+
subject { Snapdragon::CliApplication.new(options, paths) }
2722

2823
it "creates a capybara session" do
2924
suite = stub(filtered?: false)

spec/lib/snapdragon/command_line_parser_spec.rb

+12
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,17 @@
3434
lambda { subject.parse(["spec/hello_spec.rb"]) }.should_not raise_error(SystemExit)
3535
end
3636
end
37+
38+
context "when format is provided" do
39+
it "sets the format value" do
40+
subject.parse(["--format", "junit", "spec/hello_spec.rb"]).format.should eq "junit"
41+
end
42+
end
43+
44+
context "when format is not provided" do
45+
it "defaults to console" do
46+
subject.parse(["spec/hello_spec.rb"]).format.should eq "console"
47+
end
48+
end
3749
end
3850
end

spec/lib/snapdragon/suite_spec.rb

+24-4
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,49 @@
33
describe Snapdragon::Suite do
44
describe "#initialize" do
55
it "constucts an instance of a Suite given an array of paths" do
6+
options = stub
67
paths = [stub, stub, stub]
7-
Snapdragon::Suite.new(paths)
8+
Snapdragon::Suite.new(options, paths)
89
end
910

1011
it "stores the paths in an instance variable" do
12+
options = stub
1113
paths = [stub, stub, stub]
12-
suite = Snapdragon::Suite.new(paths)
14+
suite = Snapdragon::Suite.new(options, paths)
1315
suite.instance_variable_get(:@paths).should eq(paths)
1416
end
17+
18+
it 'stores the options in an instance variable' do
19+
options = stub
20+
paths = [stub, stub, stub]
21+
suite = Snapdragon::Suite.new(options, paths)
22+
suite.instance_variable_get(:@options).should eq(options)
23+
end
24+
end
25+
26+
describe "#formatter" do
27+
it "returns the configured formatter to use" do
28+
formatter = stub
29+
options = stub(format: formatter)
30+
suite = Snapdragon::Suite.new(options, stub)
31+
suite.formatter.should eq (formatter)
32+
end
1533
end
1634

1735
describe "#spec_files" do
1836
it "creates a path object to represent the path" do
37+
options = stub
1938
paths = ['path_a_str', 'path_b_str']
20-
suite = Snapdragon::Suite.new(paths)
39+
suite = Snapdragon::Suite.new(options, paths)
2140
Snapdragon::Path.should_receive(:new).with('path_a_str').and_return(stub(spec_files: []))
2241
Snapdragon::Path.should_receive(:new).with('path_b_str').and_return(stub(spec_files: []))
2342
suite.spec_files
2443
end
2544

2645
it "returns the collection of the spec files of all of the paths" do
46+
options = stub
2747
paths = ['path_a_str', 'path_b_str']
28-
suite = Snapdragon::Suite.new(paths)
48+
suite = Snapdragon::Suite.new(options, paths)
2949
spec_file_a = stub('spec_file_a'), spec_file_b = stub('spec_file_b')
3050
Snapdragon::Path.stub(:new).with('path_a_str').and_return(stub(spec_files: [spec_file_a]))
3151
Snapdragon::Path.stub(:new).with('path_b_str').and_return(stub(spec_files: [spec_file_b]))

0 commit comments

Comments
 (0)