show line numbers for spec failures, whoa nelly

This commit is contained in:
John Bintz 2011-07-13 14:42:47 -04:00
parent 05e7d07d40
commit 3c4e557517
9 changed files with 208 additions and 15 deletions

View File

@ -72,7 +72,7 @@ public:
public slots: public slots:
void log(const QString &msg); void log(const QString &msg);
void specPassed(); void specPassed();
void specFailed(); void specFailed(const QString &specDetail);
void printName(const QString &name); void printName(const QString &name);
void printResult(const QString &result); void printResult(const QString &result);
void finishSuite(const QString &duration, const QString &total, const QString& failed); void finishSuite(const QString &duration, const QString &total, const QString& failed);
@ -96,6 +96,7 @@ private:
bool consoleNotUsedThisRun; bool consoleNotUsedThisRun;
QQueue<QString> runnerFiles; QQueue<QString> runnerFiles;
QString reportFilename; QString reportFilename;
QStack<QString> failedSpecs;
void red(); void red();
void green(); void green();
@ -201,12 +202,13 @@ void HeadlessSpecRunner::specPassed()
fflush(stdout); fflush(stdout);
} }
void HeadlessSpecRunner::specFailed() void HeadlessSpecRunner::specFailed(const QString &specDetail)
{ {
consoleNotUsedThisRun = true; consoleNotUsedThisRun = true;
didFail = true; didFail = true;
red(); red();
std::cout << 'F'; std::cout << 'F';
failedSpecs.push(specDetail);
clear(); clear();
fflush(stdout); fflush(stdout);
} }
@ -292,6 +294,14 @@ void HeadlessSpecRunner::finishSuite(const QString &duration, const QString &tot
report << qPrintable(total) << "/" << qPrintable(failed) << "/"; report << qPrintable(total) << "/" << qPrintable(failed) << "/";
report << (usedConsole ? "T" : "F"); report << (usedConsole ? "T" : "F");
report << "/" << qPrintable(duration) << "\n"; report << "/" << qPrintable(duration) << "\n";
QString failedSpec;
while (!failedSpecs.isEmpty()) {
failedSpec = failedSpecs.pop();
report << qPrintable(failedSpec) << "\n";
}
reportFH.close(); reportFH.close();
} }
} }

View File

@ -23,4 +23,5 @@ Gem::Specification.new do |s|
s.add_dependency 'jasmine', '~>1.1.beta' s.add_dependency 'jasmine', '~>1.1.beta'
s.add_dependency 'coffee-script', '>= 2.2' s.add_dependency 'coffee-script', '>= 2.2'
s.add_dependency 'rainbow' s.add_dependency 'rainbow'
s.add_dependency 'multi_json'
end end

View File

@ -2,16 +2,42 @@ if !jasmine?
throw new Error("jasmine not laoded!") throw new Error("jasmine not laoded!")
class HeadlessReporterResult class HeadlessReporterResult
constructor: (name) -> constructor: (@name, @splitName) ->
@name = name
@results = [] @results = []
addResult: (message) -> addResult: (message) ->
@results.push(message) @results.push(message)
print: -> print: ->
JHW.printName(@name) output = @name
bestChoice = this._findSpecLine()
output += " (#{bestChoice.file}:#{bestChoice.lineNumber})" if bestChoice.file
JHW.printName(output)
for result in @results for result in @results
do (result) => do (result) =>
JHW.printResult(result) JHW.printResult(result)
_findSpecLine: ->
bestChoice = { accuracy: 0, file: null, lineNumber: null }
for file, lines of SPEC_LINE_NUMBERS
index = 0
while newLineNumber = lines[@splitName[index]]
index++
lineNumber = newLineNumber
if index > bestChoice.accuracy
bestChoice = { accuracy: index, file: file, lineNumber: lineNumber }
bestChoice
jasmine.Suite.prototype.getSuiteSplitName = ->
parts = if @parentSuite then @parentSuite.getSuiteSplitName() else []
parts.push(@description)
parts
jasmine.Spec.prototype.getSpecSplitName = ->
parts = @suite.getSuiteSplitName()
parts.push(@description)
parts
class jasmine.HeadlessReporter class jasmine.HeadlessReporter
constructor: -> constructor: ->
@ -32,9 +58,9 @@ class jasmine.HeadlessReporter
if results.passed() if results.passed()
JHW.specPassed() JHW.specPassed()
else else
JHW.specFailed() JHW.specFailed(spec.getSpecSplitName().join('||'))
@failedCount++ @failedCount++
failureResult = new HeadlessReporterResult(spec.getFullName()) failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName())
for result in results.getItems() for result in results.getItems()
do (result) => do (result) =>
if result.type == 'expect' and !result.passed_ if result.type == 'expect' and !result.passed_

View File

@ -5,16 +5,22 @@
throw new Error("jasmine not laoded!"); throw new Error("jasmine not laoded!");
} }
HeadlessReporterResult = (function() { HeadlessReporterResult = (function() {
function HeadlessReporterResult(name) { function HeadlessReporterResult(name, splitName) {
this.name = name; this.name = name;
this.splitName = splitName;
this.results = []; this.results = [];
} }
HeadlessReporterResult.prototype.addResult = function(message) { HeadlessReporterResult.prototype.addResult = function(message) {
return this.results.push(message); return this.results.push(message);
}; };
HeadlessReporterResult.prototype.print = function() { HeadlessReporterResult.prototype.print = function() {
var result, _i, _len, _ref, _results; var bestChoice, output, result, _i, _len, _ref, _results;
JHW.printName(this.name); output = this.name;
bestChoice = this._findSpecLine();
if (bestChoice.file) {
output += " (" + bestChoice.file + ":" + bestChoice.lineNumber + ")";
}
JHW.printName(output);
_ref = this.results; _ref = this.results;
_results = []; _results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -25,8 +31,44 @@
} }
return _results; return _results;
}; };
HeadlessReporterResult.prototype._findSpecLine = function() {
var bestChoice, file, index, lineNumber, lines, newLineNumber;
bestChoice = {
accuracy: 0,
file: null,
lineNumber: null
};
for (file in SPEC_LINE_NUMBERS) {
lines = SPEC_LINE_NUMBERS[file];
index = 0;
while (newLineNumber = lines[this.splitName[index]]) {
index++;
lineNumber = newLineNumber;
}
if (index > bestChoice.accuracy) {
bestChoice = {
accuracy: index,
file: file,
lineNumber: lineNumber
};
}
}
return bestChoice;
};
return HeadlessReporterResult; return HeadlessReporterResult;
})(); })();
jasmine.Suite.prototype.getSuiteSplitName = function() {
var parts;
parts = this.parentSuite ? this.parentSuite.getSuiteSplitName() : [];
parts.push(this.description);
return parts;
};
jasmine.Spec.prototype.getSpecSplitName = function() {
var parts;
parts = this.suite.getSuiteSplitName();
parts.push(this.description);
return parts;
};
jasmine.HeadlessReporter = (function() { jasmine.HeadlessReporter = (function() {
function HeadlessReporter() { function HeadlessReporter() {
this.results = []; this.results = [];
@ -55,9 +97,9 @@
if (results.passed()) { if (results.passed()) {
return JHW.specPassed(); return JHW.specPassed();
} else { } else {
JHW.specFailed(); JHW.specFailed(spec.getSpecSplitName().join('||'));
this.failedCount++; this.failedCount++;
failureResult = new HeadlessReporterResult(spec.getFullName()); failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName());
_ref = results.getItems(); _ref = results.getItems();
_fn = __bind(function(result) { _fn = __bind(function(result) {
if (result.type === 'expect' && !result.passed_) { if (result.type === 'expect' && !result.passed_) {

View File

@ -19,11 +19,22 @@ module Jasmine
File.expand_path('../../../jasmine/jasmine.headless-reporter.js', __FILE__) File.expand_path('../../../jasmine/jasmine.headless-reporter.js', __FILE__)
] ]
class << self
def get_spec_line_numbers(file)
Hash[file.lines.each_with_index.collect { |line, index|
if description = line[%r{(describe|context|it)[( ]*(["'])(.*)\2}, 3]
[ description, index + 1 ]
end
}.compact]
end
end
def initialize(options = {}) def initialize(options = {})
@options = options @options = options
@files = DEFAULT_FILES.dup @files = DEFAULT_FILES.dup
@filtered_files = @files.dup @filtered_files = @files.dup
@spec_outside_scope = false @spec_outside_scope = false
@spec_files = []
use_config! if config? use_config! if config?
@code_for_file = {} @code_for_file = {}
@ -45,6 +56,18 @@ module Jasmine
to_html(filtered_files) to_html(filtered_files)
end end
def spec_file_line_numbers
@spec_file_line_numbers ||= Hash[@spec_files.collect { |file|
if File.exist?(file)
if !(lines = self.class.get_spec_line_numbers(File.read(file))).empty?
[ file, lines ]
end
else
nil
end
}.compact]
end
private private
def to_html(files) def to_html(files)
coffeescript_run = [] coffeescript_run = []
@ -115,6 +138,10 @@ module Jasmine
@files += found_files @files += found_files
if searches == 'spec_files'
@spec_files = @files + spec_filter
end
@filtered_files += (if searches == 'spec_files' @filtered_files += (if searches == 'spec_files'
@spec_outside_scope = ((spec_filter | found_files).sort != found_files.sort) @spec_outside_scope = ((spec_filter | found_files).sort != found_files.sort)
spec_filter.empty? ? found_files : (spec_filter || found_files) spec_filter.empty? ? found_files : (spec_filter || found_files)

View File

@ -1,4 +1,5 @@
require 'jasmine/files_list' require 'jasmine/files_list'
require 'multi_json'
module Jasmine module Jasmine
class TemplateWriter class TemplateWriter
@ -11,14 +12,14 @@ module Jasmine
output.unshift([ "specrunner.#{$$}.filter.html", files_list.filtered_files_to_html ]) if files_list.filtered? output.unshift([ "specrunner.#{$$}.filter.html", files_list.filtered_files_to_html ]) if files_list.filtered?
output.each do |name, files| output.each do |name, files|
File.open(name, 'w') { |fh| fh.print template_for(files) } File.open(name, 'w') { |fh| fh.print template_for(files, files_list.spec_file_line_numbers) }
end end
output.collect(&:first) output.collect(&:first)
end end
private private
def template_for(files) def template_for(files, spec_lines)
<<-HTML <<-HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html> <html>
@ -32,6 +33,9 @@ module Jasmine
} }; } };
</script> </script>
#{files.join("\n")} #{files.join("\n")}
<script type="text/javascript">
SPEC_LINE_NUMBERS = #{MultiJson.encode(spec_lines)};
</script>
</head> </head>
<body> <body>

View File

@ -206,5 +206,68 @@ describe Jasmine::FilesList do
end end
end end
end end
describe '.get_spec_line_numbers' do
let(:line_numbers) do
described_class.get_spec_line_numbers(file)
end
context 'coffeescript' do
let(:file) do
<<-SPEC
describe 'test', ->
context 'yes', ->
it 'should do something', ->
"yes"
SPEC
end
it 'should get the line numbers' do
line_numbers['test'].should == 1
line_numbers['yes'].should == 2
line_numbers['should do something'].should == 3
end
end
context 'javascript' do
let(:file) do
<<-SPEC
describe('test', function() {
context('yes', function() {
it('should do something', function() {
});
});
});
SPEC
end
it 'should get the line numbers' do
line_numbers['test'].should == 1
line_numbers['yes'].should == 2
line_numbers['should do something'].should == 3
end
end
end
describe '#spec_file_line_numbers' do
include FakeFS::SpecHelpers
before do
files_list.instance_variable_set(:@spec_files, [
'test.coffee',
'test2.coffee'
])
File.open('test.coffee', 'w') { |fh| fh.print "describe('cat')" }
File.open('test2.coffee', 'w') { |fh| fh.print "no matches" }
end
it 'should generate filenames and line number info' do
files_list.spec_file_line_numbers.should == {
'test.coffee' => { 'cat' => 1 }
}
end
end
end end

View File

@ -68,5 +68,15 @@ describe Jasmine::Headless::Runner do
it 'should succeed but with javascript error' do it 'should succeed but with javascript error' do
Jasmine::Headless::Runner.run(:jasmine_config => 'spec/jasmine/success_with_error/success_with_error.yml').should == 1 Jasmine::Headless::Runner.run(:jasmine_config => 'spec/jasmine/success_with_error/success_with_error.yml').should == 1
end end
it 'should fail on one test' do
Jasmine::Headless::Runner.run(
:jasmine_config => 'spec/jasmine/failure/failure.yml',
:report => report
).should == 1
report.should be_a_report_containing(1, 1, false)
report.should contain_a_failing_spec(['failure', 'should fail with error code of 1'])
end
end end
end end

View File

@ -25,7 +25,17 @@ RSpec::Matchers.define :be_a_report_containing do |total, fails, used_console|
end end
def parts(filename = nil) def parts(filename = nil)
@parts ||= File.read(filename).strip.split('/') @parts ||= File.readlines(filename).first.strip.split('/')
end
end
RSpec::Matchers.define :contain_a_failing_spec do |*parts|
match do |filename|
report(filename).include?(parts.join("||")).should be_true
end
def report(filename)
@report ||= File.readlines(filename)[1..-1].collect(&:strip)
end end
end end