diff --git a/ext/jasmine-webkit-specrunner/specrunner.cpp b/ext/jasmine-webkit-specrunner/specrunner.cpp index 111100b..c553a5f 100644 --- a/ext/jasmine-webkit-specrunner/specrunner.cpp +++ b/ext/jasmine-webkit-specrunner/specrunner.cpp @@ -72,7 +72,7 @@ public: public slots: void log(const QString &msg); void specPassed(); - void specFailed(); + void specFailed(const QString &specDetail); void printName(const QString &name); void printResult(const QString &result); void finishSuite(const QString &duration, const QString &total, const QString& failed); @@ -96,6 +96,7 @@ private: bool consoleNotUsedThisRun; QQueue runnerFiles; QString reportFilename; + QStack failedSpecs; void red(); void green(); @@ -201,12 +202,13 @@ void HeadlessSpecRunner::specPassed() fflush(stdout); } -void HeadlessSpecRunner::specFailed() +void HeadlessSpecRunner::specFailed(const QString &specDetail) { consoleNotUsedThisRun = true; didFail = true; red(); std::cout << 'F'; + failedSpecs.push(specDetail); clear(); fflush(stdout); } @@ -292,6 +294,14 @@ void HeadlessSpecRunner::finishSuite(const QString &duration, const QString &tot report << qPrintable(total) << "/" << qPrintable(failed) << "/"; report << (usedConsole ? "T" : "F"); report << "/" << qPrintable(duration) << "\n"; + + QString failedSpec; + + while (!failedSpecs.isEmpty()) { + failedSpec = failedSpecs.pop(); + report << qPrintable(failedSpec) << "\n"; + } + reportFH.close(); } } diff --git a/jasmine-headless-webkit.gemspec b/jasmine-headless-webkit.gemspec index 7d54a0f..dc2cb46 100644 --- a/jasmine-headless-webkit.gemspec +++ b/jasmine-headless-webkit.gemspec @@ -23,4 +23,5 @@ Gem::Specification.new do |s| s.add_dependency 'jasmine', '~>1.1.beta' s.add_dependency 'coffee-script', '>= 2.2' s.add_dependency 'rainbow' + s.add_dependency 'multi_json' end diff --git a/jasmine/jasmine.headless-reporter.coffee b/jasmine/jasmine.headless-reporter.coffee index d34e792..d743a84 100644 --- a/jasmine/jasmine.headless-reporter.coffee +++ b/jasmine/jasmine.headless-reporter.coffee @@ -2,16 +2,42 @@ if !jasmine? throw new Error("jasmine not laoded!") class HeadlessReporterResult - constructor: (name) -> - @name = name + constructor: (@name, @splitName) -> @results = [] addResult: (message) -> @results.push(message) print: -> - JHW.printName(@name) + output = @name + bestChoice = this._findSpecLine() + output += " (#{bestChoice.file}:#{bestChoice.lineNumber})" if bestChoice.file + + JHW.printName(output) for result in @results do (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 constructor: -> @@ -32,9 +58,9 @@ class jasmine.HeadlessReporter if results.passed() JHW.specPassed() else - JHW.specFailed() + JHW.specFailed(spec.getSpecSplitName().join('||')) @failedCount++ - failureResult = new HeadlessReporterResult(spec.getFullName()) + failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName()) for result in results.getItems() do (result) => if result.type == 'expect' and !result.passed_ diff --git a/jasmine/jasmine.headless-reporter.js b/jasmine/jasmine.headless-reporter.js index ca8ed50..ea6f51a 100644 --- a/jasmine/jasmine.headless-reporter.js +++ b/jasmine/jasmine.headless-reporter.js @@ -5,16 +5,22 @@ throw new Error("jasmine not laoded!"); } HeadlessReporterResult = (function() { - function HeadlessReporterResult(name) { + function HeadlessReporterResult(name, splitName) { this.name = name; + this.splitName = splitName; this.results = []; } HeadlessReporterResult.prototype.addResult = function(message) { return this.results.push(message); }; HeadlessReporterResult.prototype.print = function() { - var result, _i, _len, _ref, _results; - JHW.printName(this.name); + var bestChoice, output, result, _i, _len, _ref, _results; + output = this.name; + bestChoice = this._findSpecLine(); + if (bestChoice.file) { + output += " (" + bestChoice.file + ":" + bestChoice.lineNumber + ")"; + } + JHW.printName(output); _ref = this.results; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { @@ -25,8 +31,44 @@ } 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; })(); + 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() { function HeadlessReporter() { this.results = []; @@ -55,9 +97,9 @@ if (results.passed()) { return JHW.specPassed(); } else { - JHW.specFailed(); + JHW.specFailed(spec.getSpecSplitName().join('||')); this.failedCount++; - failureResult = new HeadlessReporterResult(spec.getFullName()); + failureResult = new HeadlessReporterResult(spec.getFullName(), spec.getSpecSplitName()); _ref = results.getItems(); _fn = __bind(function(result) { if (result.type === 'expect' && !result.passed_) { diff --git a/lib/jasmine/files_list.rb b/lib/jasmine/files_list.rb index 9849410..aed55de 100644 --- a/lib/jasmine/files_list.rb +++ b/lib/jasmine/files_list.rb @@ -19,11 +19,22 @@ module Jasmine 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 = {}) @options = options @files = DEFAULT_FILES.dup @filtered_files = @files.dup @spec_outside_scope = false + @spec_files = [] use_config! if config? @code_for_file = {} @@ -45,6 +56,18 @@ module Jasmine to_html(filtered_files) 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 def to_html(files) coffeescript_run = [] @@ -115,6 +138,10 @@ module Jasmine @files += found_files + if searches == 'spec_files' + @spec_files = @files + spec_filter + end + @filtered_files += (if searches == 'spec_files' @spec_outside_scope = ((spec_filter | found_files).sort != found_files.sort) spec_filter.empty? ? found_files : (spec_filter || found_files) diff --git a/lib/jasmine/template_writer.rb b/lib/jasmine/template_writer.rb index e37c7bf..da51297 100644 --- a/lib/jasmine/template_writer.rb +++ b/lib/jasmine/template_writer.rb @@ -1,4 +1,5 @@ require 'jasmine/files_list' +require 'multi_json' module Jasmine class TemplateWriter @@ -11,14 +12,14 @@ module Jasmine output.unshift([ "specrunner.#{$$}.filter.html", files_list.filtered_files_to_html ]) if files_list.filtered? 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 output.collect(&:first) end private - def template_for(files) + def template_for(files, spec_lines) <<-HTML @@ -32,6 +33,9 @@ module Jasmine } }; #{files.join("\n")} + diff --git a/spec/lib/jasmine/files_list_spec.rb b/spec/lib/jasmine/files_list_spec.rb index dec8a9b..9a8c4be 100644 --- a/spec/lib/jasmine/files_list_spec.rb +++ b/spec/lib/jasmine/files_list_spec.rb @@ -206,5 +206,68 @@ describe Jasmine::FilesList do 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 diff --git a/spec/lib/jasmine/headless/runner_spec.rb b/spec/lib/jasmine/headless/runner_spec.rb index bed7f8c..c39b9a4 100644 --- a/spec/lib/jasmine/headless/runner_spec.rb +++ b/spec/lib/jasmine/headless/runner_spec.rb @@ -68,5 +68,15 @@ describe Jasmine::Headless::Runner 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 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 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b138097..1f9e555 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,7 +25,17 @@ RSpec::Matchers.define :be_a_report_containing do |total, fails, used_console| end 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