From af985073448385b70c814189ff1b58a6f9420a46 Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 17:32:20 +0400 Subject: [PATCH 1/6] added reporter for minitest --- lib/ci/reporter/minitest.rb | 218 ++++++++++++++++++++++++ lib/ci/reporter/rake/minitest.rb | 15 ++ lib/ci/reporter/rake/minitest_loader.rb | 9 + tasks/ci_reporter.rake | 2 + 4 files changed, 244 insertions(+) create mode 100644 lib/ci/reporter/minitest.rb create mode 100644 lib/ci/reporter/rake/minitest.rb create mode 100644 lib/ci/reporter/rake/minitest_loader.rb diff --git a/lib/ci/reporter/minitest.rb b/lib/ci/reporter/minitest.rb new file mode 100644 index 0000000..dc5bd10 --- /dev/null +++ b/lib/ci/reporter/minitest.rb @@ -0,0 +1,218 @@ +# Copyright (c) 2012 Alexander Shcherbinin +# See the file LICENSE.txt included with the distribution for +# software license details. + +require 'ci/reporter/core' + +require 'minitest/unit' + +module CI + module Reporter + class Failure + def self.new(fault, type = nil, meth = nil) + return MiniTestSkipped.new(fault) if type == :skip + return MiniTestFailure.new(fault, meth) if type == :failure + MiniTestError.new(fault) + end + + def location(e) + last_before_assertion = "" + e.backtrace.reverse_each do |s| + break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/ + last_before_assertion = s + end + last_before_assertion.sub(/:in .*$/, '') + end + end + + class MiniTestSkipped < Failure + def initialize(fault) @fault = fault end + def failure?() false end + def error?() false end + def name() @fault.class.name end + def message() @fault.message end + def location() super @fault end + end + + class MiniTestFailure < Failure + def initialize(fault, meth) @fault = fault; @meth = meth end + def failure?() true end + def error?() false end + def name() @meth end + def message() @fault.message end + def location() super @fault end + end + + class MiniTestError + def initialize(fault) @fault = fault end + def failure?() false end + def error?() true end + def name() @fault.exception.class.name end + def message() @fault.exception.message end + def location() @fault.exception.backtrace.join("\n") end + end + + class Runner < MiniTest::Unit + + @@out = $stdout + + def initialize + super + @report_manager = ReportManager.new("test") + end + + def _run_anything(type) + suites = MiniTest::Unit::TestCase.send "#{type}_suites" + return if suites.empty? + + started_anything type + + sync = output.respond_to? :"sync=" # stupid emacs + old_sync, output.sync = output.sync, true if sync + + _run_suites(suites, type) + + output.sync = old_sync if sync + + finished_anything(type) + end + + def _run_suites(suites, type) + suites.map { |suite| _run_suite suite, type } + end + + def _run_suite(suite, type) + start_suite(suite) + + header = "#{type}_suite_header" + puts send(header, suite) if respond_to? header + + filter_suite_methods(suite, type).each do |method| + _run_test(suite, method) + end + + finish_suite + end + + def _run_test(suite, method) + start_case(method) + + result = run_test(suite, method) + + @assertion_count += result._assertions + @test_count += 1 + + finish_case + end + + def puke(klass, meth, e) + e = case e + when MiniTest::Skip then + @skips += 1 + fault(e, :skip) + return "S" unless @verbose + "Skipped:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n" + when MiniTest::Assertion then + @failures += 1 + fault(e, :failure, meth) + "Failure:\n#{meth}(#{klass}) [#{location e}]:\n#{e.message}\n" + else + @errors += 1 + fault(e, :error) + bt = MiniTest::filter_backtrace(e.backtrace).join "\n " + "Error:\n#{meth}(#{klass}):\n#{e.class}: #{e.message}\n #{bt}\n" + end + @report << e + e[0, 1] + end + + private + + def started_anything(type) + @test_count = 0 + @assertion_count = 0 + @last_assertion_count = 0 + @result_assertion_count = 0 + @start = Time.now + + puts + puts "# Running #{type}s:" + puts + end + + def finished_anything(type) + t = Time.now - @start + puts + puts + puts "Finished #{type}s in %.6fs, %.4f tests/s, %.4f assertions/s." % + [t, @test_count / t, @assertion_count / t] + + report.each_with_index do |msg, i| + puts "\n%3d) %s" % [i + 1, msg] + end + + puts + + status + end + + def filter_suite_methods(suite, type) + filter = options[:filter] || '/./' + filter = Regexp.new $1 if filter =~ /\/(.*)\// + + suite.send("#{type}_methods").grep(filter) + end + + def run_test(suite, method) + inst = suite.new method + inst._assertions = 0 + + print "#{suite}##{method} = " if @verbose + + @start_time = Time.now + result = inst.run self + time = Time.now - @start_time + + print "%.2f s = " % time if @verbose + print result + puts if @verbose + + return inst + end + + def start_suite(suite_name) + @current_suite = CI::Reporter::TestSuite.new(suite_name) + @current_suite.start + end + + def finish_suite + if @current_suite + @current_suite.finish + @current_suite.assertions = @assertion_count - @last_assertion_count + @last_assertion_count = @assertion_count + @report_manager.write_report(@current_suite) + end + end + + def start_case(test_name) + tc = CI::Reporter::TestCase.new(test_name) + tc.start + @current_suite.testcases << tc + end + + def finish_case + tc = @current_suite.testcases.last + tc.finish + tc.assertions = @assertion_count - @result_assertion_count + @result_assertion_count = @assertion_count + end + + def fault(fault, type = nil, meth = nil) + tc = @current_suite.testcases.last + tc.failures << Failure.new(fault, type, meth) + end + + end + + end +end diff --git a/lib/ci/reporter/rake/minitest.rb b/lib/ci/reporter/rake/minitest.rb new file mode 100644 index 0000000..8a821d6 --- /dev/null +++ b/lib/ci/reporter/rake/minitest.rb @@ -0,0 +1,15 @@ +# Copyright (c) 2012 Alexander Shcherbinin +# See the file LICENSE.txt included with the distribution for +# software license details. + +require File.expand_path('../utils', __FILE__) + +namespace :ci do + namespace :setup do + task :minitest do + rm_rf ENV["CI_REPORTS"] || "test/reports" + test_loader = CI::Reporter.maybe_quote_filename "#{File.dirname(__FILE__)}/minitest_loader.rb" + ENV["TESTOPTS"] = "#{ENV["TESTOPTS"]} #{test_loader}" + end + end +end diff --git a/lib/ci/reporter/rake/minitest_loader.rb b/lib/ci/reporter/rake/minitest_loader.rb new file mode 100644 index 0000000..cc5c3c8 --- /dev/null +++ b/lib/ci/reporter/rake/minitest_loader.rb @@ -0,0 +1,9 @@ +# Copyright (c) 2012 Alexander Shcherbinin +# See the file LICENSE.txt included with the distribution for +# software license details. + +$: << File.dirname(__FILE__) + "/../../.." +require 'ci/reporter/minitest' + +# set defaults +MiniTest::Unit.runner = CI::Reporter::Runner.new diff --git a/tasks/ci_reporter.rake b/tasks/ci_reporter.rake index f0743f8..f35f31e 100644 --- a/tasks/ci_reporter.rake +++ b/tasks/ci_reporter.rake @@ -10,9 +10,11 @@ end require 'ci/reporter/rake/rspec' require 'ci/reporter/rake/cucumber' require 'ci/reporter/rake/test_unit' +require 'ci/reporter/rake/minitest' namespace :ci do task :setup_rspec => "ci:setup:rspec" task :setup_cucumber => "ci:setup:cucumber" task :setup_testunit => "ci:setup:testunit" + task :setup_minitest => "ci:setup:minitest" end From 8713d227dd0b0daf29e916f67289446243c3b2c7 Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 17:52:53 +0400 Subject: [PATCH 2/6] fix gemspec --- ci_reporter.gemspec | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ci_reporter.gemspec b/ci_reporter.gemspec index 1c1c60c..dccd905 100644 --- a/ci_reporter.gemspec +++ b/ci_reporter.gemspec @@ -1,23 +1,23 @@ # -*- encoding: utf-8 -*- Gem::Specification.new do |s| - s.name = %q{ci_reporter} + s.name = "ci_reporter" s.version = "1.6.9" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = [%q{Nick Sieger}] - s.date = %q{2011-12-15} - s.description = %q{CI::Reporter is an add-on to Test::Unit, RSpec and Cucumber that allows you to generate XML reports of your test, spec and/or feature runs. The resulting files can be read by a continuous integration system that understands Ant's JUnit report XML format, thus allowing your CI system to track test/spec successes and failures.} - s.email = %q{nick@nicksieger.com} - s.extra_rdoc_files = [%q{History.txt}, %q{Manifest.txt}, %q{LICENSE.txt}, %q{README.rdoc}] - s.files = [%q{History.txt}, %q{Manifest.txt}, %q{README.rdoc}, %q{LICENSE.txt}, %q{Rakefile}, %q{stub.rake}, %q{lib/ci/reporter/core.rb}, %q{lib/ci/reporter/cucumber.rb}, %q{lib/ci/reporter/report_manager.rb}, %q{lib/ci/reporter/rspec.rb}, %q{lib/ci/reporter/test_suite.rb}, %q{lib/ci/reporter/test_unit.rb}, %q{lib/ci/reporter/version.rb}, %q{lib/ci/reporter/rake/cucumber.rb}, %q{lib/ci/reporter/rake/cucumber_loader.rb}, %q{lib/ci/reporter/rake/rspec.rb}, %q{lib/ci/reporter/rake/rspec_loader.rb}, %q{lib/ci/reporter/rake/test_unit.rb}, %q{lib/ci/reporter/rake/test_unit_loader.rb}, %q{lib/ci/reporter/rake/utils.rb}, %q{spec/spec_helper.rb}, %q{spec/ci/reporter/cucumber_spec.rb}, %q{spec/ci/reporter/output_capture_spec.rb}, %q{spec/ci/reporter/report_manager_spec.rb}, %q{spec/ci/reporter/rspec_spec.rb}, %q{spec/ci/reporter/test_suite_spec.rb}, %q{spec/ci/reporter/test_unit_spec.rb}, %q{spec/ci/reporter/rake/rake_tasks_spec.rb}, %q{tasks/ci_reporter.rake}] - s.homepage = %q{http://caldersphere.rubyforge.org/ci_reporter} - s.rdoc_options = [%q{--main}, %q{README.rdoc}, %q{-SHN}, %q{-f}, %q{darkfish}] - s.require_paths = [%q{lib}] - s.rubyforge_project = %q{caldersphere} - s.rubygems_version = %q{1.8.9} - s.summary = %q{CI::Reporter allows you to generate reams of XML for use with continuous integration systems.} - s.test_files = [%q{spec/ci/reporter/cucumber_spec.rb}, %q{spec/ci/reporter/output_capture_spec.rb}, %q{spec/ci/reporter/report_manager_spec.rb}, %q{spec/ci/reporter/rspec_spec.rb}, %q{spec/ci/reporter/test_suite_spec.rb}, %q{spec/ci/reporter/test_unit_spec.rb}, %q{spec/ci/reporter/rake/rake_tasks_spec.rb}] + s.authors = ["Nick Sieger"] + s.date = "2012-01-27" + s.description = "CI::Reporter is an add-on to Test::Unit, RSpec and Cucumber that allows you to generate XML reports of your test, spec and/or feature runs. The resulting files can be read by a continuous integration system that understands Ant's JUnit report XML format, thus allowing your CI system to track test/spec successes and failures." + s.email = "nick@nicksieger.com" + s.extra_rdoc_files = ["History.txt", "LICENSE.txt", "README.rdoc"] + Dir.glob("Manifest.txt") + s.files = ["History.txt", "README.rdoc", "LICENSE.txt", "Rakefile", "stub.rake", "lib/ci/reporter/minitest.rb", "lib/ci/reporter/report_manager.rb", "lib/ci/reporter/test_suite.rb", "lib/ci/reporter/rspec.rb", "lib/ci/reporter/core.rb", "lib/ci/reporter/cucumber.rb", "lib/ci/reporter/rake/minitest.rb", "lib/ci/reporter/rake/minitest_loader.rb", "lib/ci/reporter/rake/cucumber_loader.rb", "lib/ci/reporter/rake/rspec.rb", "lib/ci/reporter/rake/rspec_loader.rb", "lib/ci/reporter/rake/utils.rb", "lib/ci/reporter/rake/test_unit_loader.rb", "lib/ci/reporter/rake/cucumber.rb", "lib/ci/reporter/rake/test_unit.rb", "lib/ci/reporter/test_unit.rb", "lib/ci/reporter/version.rb", "spec/spec_helper.rb", "spec/ci/reporter/rspec_spec.rb", "spec/ci/reporter/test_suite_spec.rb", "spec/ci/reporter/report_manager_spec.rb", "spec/ci/reporter/test_unit_spec.rb", "spec/ci/reporter/rake/rake_tasks_spec.rb", "spec/ci/reporter/cucumber_spec.rb", "spec/ci/reporter/output_capture_spec.rb", "tasks/ci_reporter.rake"] + s.homepage = "http://caldersphere.rubyforge.org/ci_reporter" + s.rdoc_options = ["--main", "README.rdoc", "-SHN", "-f", "darkfish"] + s.require_paths = ["lib"] + s.rubyforge_project = "caldersphere" + s.rubygems_version = "1.8.10" + s.summary = "CI::Reporter allows you to generate reams of XML for use with continuous integration systems." + s.test_files = ["spec/ci/reporter/rspec_spec.rb", "spec/ci/reporter/test_suite_spec.rb", "spec/ci/reporter/report_manager_spec.rb", "spec/ci/reporter/test_unit_spec.rb", "spec/ci/reporter/rake/rake_tasks_spec.rb", "spec/ci/reporter/cucumber_spec.rb", "spec/ci/reporter/output_capture_spec.rb"] if s.respond_to? :specification_version then s.specification_version = 3 From 9e8389c144a05161db9987c5d14a74c85d59c283 Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 18:09:32 +0400 Subject: [PATCH 3/6] added minitest to stub.rake --- stub.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stub.rake b/stub.rake index 0271416..e5f1875 100644 --- a/stub.rake +++ b/stub.rake @@ -2,7 +2,7 @@ # See the file LICENSE.txt included with the distribution for # software license details. # -# Use this stub rakefile as a wrapper around a regular Rakefile. Run in the +# Use this stub rakefile as a wrapper around a regular Rakefile. Run in the # same directory as the real Rakefile. # # rake -f /path/to/ci_reporter/lib/ci/reporter/rake/stub.rake ci:setup:rspec default @@ -11,4 +11,5 @@ load File.dirname(__FILE__) + '/lib/ci/reporter/rake/rspec.rb' load File.dirname(__FILE__) + '/lib/ci/reporter/rake/cucumber.rb' load File.dirname(__FILE__) + '/lib/ci/reporter/rake/test_unit.rb' +load File.dirname(__FILE__) + '/lib/ci/reporter/rake/minitest.rb' load 'Rakefile' From fea2340a50dceb504def0d31ca43538cd437ddde Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 18:11:35 +0400 Subject: [PATCH 4/6] updated readme --- README.rdoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rdoc b/README.rdoc index 3060fb9..d3df02b 100644 --- a/README.rdoc +++ b/README.rdoc @@ -16,6 +16,7 @@ CI::Reporter works best with projects that use a +Rakefile+ along with the stand require 'ci/reporter/rake/rspec' # use this if you're using RSpec require 'ci/reporter/rake/cucumber' # use this if you're using Cucumber require 'ci/reporter/rake/test_unit' # use this if you're using Test::Unit + require 'ci/reporter/rake/minitest' # use this if you're using MiniTest::Unit 2. Next, either modify your Rakefile to make the ci:setup:rspec, ci:setup:cucumber or ci:setup:testunit task a dependency of your test tasks, or include them on the Rake command-line before the name of the task that runs the tests or specs. @@ -28,6 +29,7 @@ Report files are written, by default, to the test/reports, fe If you don't have control over the Rakefile or don't want to modify it, CI::Reporter has a substitute rake file that you can specify on the command-line. It assumes that the main project rake file is called +Rakefile+ and lives in the current directory. Run like so: rake -f GEM_PATH/stub.rake ci:setup:testunit test + rake -f GEM_PATH/stub.rake ci:setup:minitest test rake -f GEM_PATH/stub.rake ci:setup:rspec spec rake -f GEM_PATH/stub.rake ci:setup:cucumber features From 8233b05a3b67ec430df6aca4cb42e39b56b5a460 Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 21:09:01 +0400 Subject: [PATCH 5/6] fixed problem with error in suites --- lib/ci/reporter/minitest.rb | 8 +++++--- lib/ci/reporter/test_suite.rb | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ci/reporter/minitest.rb b/lib/ci/reporter/minitest.rb index dc5bd10..f02175c 100644 --- a/lib/ci/reporter/minitest.rb +++ b/lib/ci/reporter/minitest.rb @@ -14,7 +14,9 @@ def self.new(fault, type = nil, meth = nil) return MiniTestFailure.new(fault, meth) if type == :failure MiniTestError.new(fault) end + end + class FailureCore def location(e) last_before_assertion = "" e.backtrace.reverse_each do |s| @@ -25,7 +27,7 @@ def location(e) end end - class MiniTestSkipped < Failure + class MiniTestSkipped < FailureCore def initialize(fault) @fault = fault end def failure?() false end def error?() false end @@ -34,7 +36,7 @@ def message() @fault.message end def location() super @fault end end - class MiniTestFailure < Failure + class MiniTestFailure < FailureCore def initialize(fault, meth) @fault = fault; @meth = meth end def failure?() true end def error?() false end @@ -43,7 +45,7 @@ def message() @fault.message end def location() super @fault end end - class MiniTestError + class MiniTestError < FailureCore def initialize(fault) @fault = fault end def failure?() false end def error?() true end diff --git a/lib/ci/reporter/test_suite.rb b/lib/ci/reporter/test_suite.rb index f23eb05..00ca4a9 100644 --- a/lib/ci/reporter/test_suite.rb +++ b/lib/ci/reporter/test_suite.rb @@ -140,7 +140,7 @@ def to_xml(builder) failures.each do |failure| tag = case failure.class.name when /TestUnitSkipped/ then :skipped - when /TestUnitError/ then :error + when /TestUnitError/, /MiniTestError/ then :error else :failure end builder.tag!(tag, :type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do From a9d62ea5ef639abec4f91654562c0e524a14c4a8 Mon Sep 17 00:00:00 2001 From: Alexander Shcherbinin Date: Fri, 27 Jan 2012 21:09:28 +0400 Subject: [PATCH 6/6] added acceptance spec --- Rakefile | 1 + acceptance/minitest_example_test.rb | 17 ++++++++++++++ acceptance/verification_spec.rb | 35 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 acceptance/minitest_example_test.rb diff --git a/Rakefile b/Rakefile index b324527..cda223e 100644 --- a/Rakefile +++ b/Rakefile @@ -97,6 +97,7 @@ task :generate_output do begin result_proc = proc {|ok,*| puts "Failures above are expected." unless ok } ruby "-Ilib #{opts} -rci/reporter/rake/test_unit_loader acceptance/test_unit_example_test.rb", &result_proc + ruby "-Ilib #{opts} -rci/reporter/rake/minitest_loader acceptance/minitest_example_test.rb", &result_proc ruby "-Ilib #{opts} -S #{@spec_bin} --require ci/reporter/rake/rspec_loader --format CI::Reporter::RSpec acceptance/rspec_example_spec.rb", &result_proc ruby "-Ilib #{opts} -rci/reporter/rake/cucumber_loader -S cucumber --format CI::Reporter::Cucumber acceptance/cucumber", &result_proc ensure diff --git a/acceptance/minitest_example_test.rb b/acceptance/minitest_example_test.rb new file mode 100644 index 0000000..687724a --- /dev/null +++ b/acceptance/minitest_example_test.rb @@ -0,0 +1,17 @@ +require 'minitest/autorun' + +class MiniTestExampleTestOne < MiniTest::Unit::TestCase + def test_one + puts "Some " + assert false + end + def teardown + raise "second failure" + end +end + +class MiniTestExampleTestTwo < MiniTest::Unit::TestCase + def test_two + assert true + end +end diff --git a/acceptance/verification_spec.rb b/acceptance/verification_spec.rb index fe6d915..260bb43 100644 --- a/acceptance/verification_spec.rb +++ b/acceptance/verification_spec.rb @@ -37,6 +37,41 @@ end end +describe "MiniTest::Unit acceptance" do + it "should generate two XML files" do + File.exist?(File.join(REPORTS_DIR, 'TEST-MiniTestExampleTestOne.xml')).should == true + File.exist?(File.join(REPORTS_DIR, 'TEST-MiniTestExampleTestTwo.xml')).should == true + end + + it "should have one error and one failure for MiniTestExampleTestOne" do + doc = File.open(File.join(REPORTS_DIR, 'TEST-MiniTestExampleTestOne.xml')) do |f| + REXML::Document.new(f) + end + doc.root.attributes["errors"].should == "1" + doc.root.attributes["failures"].should == "1" + doc.root.attributes["assertions"].should == "1" + doc.root.attributes["tests"].should == "1" + doc.root.elements.to_a("/testsuite/testcase").size.should == 1 + doc.root.elements.to_a("/testsuite/testcase/error").size.should == 1 + doc.root.elements.to_a("/testsuite/testcase/failure").size.should == 1 + doc.root.elements.to_a("/testsuite/system-out").first.texts.inject("") do |c,e| + c << e.value; c + end.strip.should == "Some " + end + + it "should have no errors or failures for MiniTestExampleTestTwo" do + doc = File.open(File.join(REPORTS_DIR, 'TEST-MiniTestExampleTestTwo.xml')) do |f| + REXML::Document.new(f) + end + doc.root.attributes["errors"].should == "0" + doc.root.attributes["failures"].should == "0" + doc.root.attributes["assertions"].should == "1" + doc.root.attributes["tests"].should == "1" + doc.root.elements.to_a("/testsuite/testcase").size.should == 1 + doc.root.elements.to_a("/testsuite/testcase/failure").size.should == 0 + end +end + describe "RSpec acceptance" do it "should generate two XML files" do File.exist?(File.join(REPORTS_DIR, 'SPEC-RSpec-example.xml')).should == true