schubert-ci_reporter 1.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,151 @@
1
+ # (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'delegate'
6
+ require 'stringio'
7
+
8
+ module CI
9
+ module Reporter
10
+ # Emulates/delegates IO to $stdout or $stderr in order to capture output to report in the XML file.
11
+ class OutputCapture < DelegateClass(IO)
12
+ # Start capturing IO, using the given block to assign self to the proper IO global.
13
+ def initialize(io, &assign)
14
+ super
15
+ @delegate_io = io
16
+ @captured_io = StringIO.new
17
+ @assign_block = assign
18
+ @assign_block.call self
19
+ end
20
+
21
+ # Finalize the capture and reset to the original IO object.
22
+ def finish
23
+ @assign_block.call @delegate_io
24
+ @captured_io.string
25
+ end
26
+
27
+ # setup tee methods
28
+ %w(<< print printf putc puts write).each do |m|
29
+ module_eval(<<-EOS, __FILE__, __LINE__)
30
+ def #{m}(*args, &block)
31
+ @delegate_io.send(:#{m}, *args, &block)
32
+ @captured_io.send(:#{m}, *args, &block)
33
+ end
34
+ EOS
35
+ end
36
+ end
37
+
38
+ # Basic structure representing the running of a test suite. Used to time tests and store results.
39
+ class TestSuite < Struct.new(:name, :classname, :tests, :time, :failures, :errors, :assertions)
40
+ attr_accessor :testcases
41
+ attr_accessor :stdout, :stderr
42
+ def initialize(name)
43
+ suite_name = name.to_s.gsub(/#/, "%23")
44
+ super(suite_name) # RSpec passes a "description" object instead of a string
45
+ if potential_classname = suite_name.split(" ").first
46
+ self.classname = potential_classname if defined?(potential_classname) == "constant"
47
+ end
48
+ @testcases = []
49
+ end
50
+
51
+ # Starts timing the test suite.
52
+ def start
53
+ @start = Time.now
54
+ unless ENV['CI_CAPTURE'] == "off"
55
+ @capture_out = OutputCapture.new($stdout) {|io| $stdout = io }
56
+ @capture_err = OutputCapture.new($stderr) {|io| $stderr = io }
57
+ end
58
+ end
59
+
60
+ # Finishes timing the test suite.
61
+ def finish
62
+ self.tests = testcases.size
63
+ self.time = Time.now - @start
64
+ self.failures = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.failure? }.size }
65
+ self.errors = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.error? }.size }
66
+ self.stdout = @capture_out.finish if @capture_out
67
+ self.stderr = @capture_err.finish if @capture_err
68
+ end
69
+
70
+ # Creates the xml builder instance used to create the report xml document.
71
+ def create_builder
72
+ begin
73
+ require 'rubygems'
74
+ gem 'builder'
75
+ rescue LoadError
76
+ end
77
+
78
+ require 'builder'
79
+ # :escape_attrs is obsolete in a newer version, but should do no harm
80
+ Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
81
+ end
82
+
83
+ # Creates an xml string containing the test suite results.
84
+ def to_xml
85
+ builder = create_builder
86
+ # more recent version of Builder doesn't need the escaping
87
+ def builder.trunc!(txt)
88
+ txt.sub(/\n.*/m, '...')
89
+ end
90
+ builder.instruct!
91
+ attrs = {}
92
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty? }
93
+ builder.testsuite(attrs) do
94
+ @testcases.each do |tc|
95
+ tc.to_xml(builder)
96
+ end
97
+ builder.tag! "system-out" do
98
+ builder.cdata! self.stdout
99
+ end
100
+ builder.tag! "system-err" do
101
+ builder.cdata! self.stderr
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Structure used to represent an individual test case. Used to time the test and store the result.
108
+ class TestCase < Struct.new(:name, :time, :assertions)
109
+ attr_accessor :failures
110
+
111
+ def initialize(*args)
112
+ super
113
+ @failures = []
114
+ end
115
+
116
+ # Starts timing the test.
117
+ def start
118
+ @start = Time.now
119
+ end
120
+
121
+ # Finishes timing the test.
122
+ def finish
123
+ self.time = Time.now - @start
124
+ end
125
+
126
+ # Returns non-nil if the test failed.
127
+ def failure?
128
+ !failures.empty? && failures.detect {|f| f.failure? }
129
+ end
130
+
131
+ # Returns non-nil if the test had an error.
132
+ def error?
133
+ !failures.empty? && failures.detect {|f| f.error? }
134
+ end
135
+
136
+ # Writes xml representing the test result to the provided builder.
137
+ def to_xml(builder)
138
+ attrs = {}
139
+ each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty?}
140
+ builder.testcase(attrs) do
141
+ failures.each do |failure|
142
+ builder.failure(:type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
143
+ builder.text!(failure.message + " (#{failure.name})\n")
144
+ builder.text!(failure.location)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,124 @@
1
+ # (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require 'ci/reporter/core'
6
+ require 'test/unit'
7
+ require 'test/unit/ui/console/testrunner'
8
+
9
+ module CI
10
+ module Reporter
11
+ # Factory for constructing either a CI::Reporter::TestUnitFailure or CI::Reporter::TestUnitError depending on the result
12
+ # of the test.
13
+ class Failure
14
+ def self.new(fault)
15
+ fault.kind_of?(Test::Unit::Failure) ? TestUnitFailure.new(fault) : TestUnitError.new(fault)
16
+ end
17
+ end
18
+
19
+ # Wrapper around a <code>Test::Unit</code> error to be used by the test suite to interpret results.
20
+ class TestUnitError
21
+ def initialize(fault)
22
+ @fault = fault
23
+ end
24
+ def failure?() false end
25
+ def error?() true end
26
+ def name() @fault.exception.class.name end
27
+ def message() @fault.exception.message end
28
+ def location() @fault.exception.backtrace.join("\n") end
29
+ end
30
+
31
+ # Wrapper around a <code>Test::Unit</code> failure to be used by the test suite to interpret results.
32
+ class TestUnitFailure
33
+ def initialize(fault)
34
+ @fault = fault
35
+ end
36
+ def failure?() true end
37
+ def error?() false end
38
+ def name() Test::Unit::AssertionFailedError.name end
39
+ def message() @fault.message end
40
+ def location() @fault.location.join("\n") end
41
+ end
42
+
43
+ # Replacement Mediator that adds listeners to capture the results of the <code>Test::Unit</code> runs.
44
+ class TestUnit < Test::Unit::UI::TestRunnerMediator
45
+ def initialize(suite, report_mgr = nil)
46
+ super(suite)
47
+ @report_manager = report_mgr || ReportManager.new("test")
48
+ add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:started))
49
+ add_listener(Test::Unit::TestCase::STARTED, &method(:test_started))
50
+ add_listener(Test::Unit::TestCase::FINISHED, &method(:test_finished))
51
+ add_listener(Test::Unit::TestResult::FAULT, &method(:fault))
52
+ add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:finished))
53
+ end
54
+
55
+ def started(result)
56
+ @suite_result = result
57
+ @last_assertion_count = 0
58
+ @current_suite = nil
59
+ @unknown_count = 0
60
+ @result_assertion_count = 0
61
+ end
62
+
63
+ def test_started(name)
64
+ test_name, suite_name = extract_names(name)
65
+ unless @current_suite && @current_suite.name == suite_name
66
+ finish_suite
67
+ start_suite(suite_name)
68
+ end
69
+ start_test(test_name)
70
+ end
71
+
72
+ def test_finished(name)
73
+ finish_test
74
+ end
75
+
76
+ def fault(fault)
77
+ tc = @current_suite.testcases.last
78
+ tc.failures << Failure.new(fault)
79
+ end
80
+
81
+ def finished(elapsed_time)
82
+ finish_suite
83
+ end
84
+
85
+ private
86
+ def extract_names(name)
87
+ match = name.match(/(.*)\(([^)]*)\)/)
88
+ if match
89
+ [match[1], match[2]]
90
+ else
91
+ @unknown_count += 1
92
+ [name, "unknown-#{@unknown_count}"]
93
+ end
94
+ end
95
+
96
+ def start_suite(suite_name)
97
+ @current_suite = TestSuite.new(suite_name)
98
+ @current_suite.start
99
+ end
100
+
101
+ def finish_suite
102
+ if @current_suite
103
+ @current_suite.finish
104
+ @current_suite.assertions = @suite_result.assertion_count - @last_assertion_count
105
+ @last_assertion_count = @suite_result.assertion_count
106
+ @report_manager.write_report(@current_suite)
107
+ end
108
+ end
109
+
110
+ def start_test(test_name)
111
+ tc = TestCase.new(test_name)
112
+ tc.start
113
+ @current_suite.testcases << tc
114
+ end
115
+
116
+ def finish_test
117
+ tc = @current_suite.testcases.last
118
+ tc.finish
119
+ tc.assertions = @suite_result.assertion_count - @result_assertion_count
120
+ @result_assertion_count = @suite_result.assertion_count
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,5 @@
1
+ module CI
2
+ module Reporter
3
+ VERSION = "1.6.1"
4
+ end
5
+ end
@@ -0,0 +1,57 @@
1
+ # (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require File.dirname(__FILE__) + "/../../spec_helper.rb"
6
+ require 'rexml/document'
7
+
8
+ describe "Output capture" do
9
+ before(:each) do
10
+ @suite = CI::Reporter::TestSuite.new "test"
11
+ end
12
+
13
+ it "should save stdout and stderr messages written during the test run" do
14
+ @suite.start
15
+ puts "Hello"
16
+ $stderr.print "Hi"
17
+ @suite.finish
18
+ @suite.stdout.should == "Hello\n"
19
+ @suite.stderr.should == "Hi"
20
+ end
21
+
22
+ it "should include system-out and system-err elements in the xml output" do
23
+ @suite.start
24
+ puts "Hello"
25
+ $stderr.print "Hi"
26
+ @suite.finish
27
+
28
+ root = REXML::Document.new(@suite.to_xml).root
29
+ root.elements.to_a('//system-out').length.should == 1
30
+ root.elements.to_a('//system-err').length.should == 1
31
+ root.elements.to_a('//system-out').first.cdatas.first.to_s.should == "Hello\n"
32
+ root.elements.to_a('//system-err').first.cdatas.first.to_s.should == "Hi"
33
+ end
34
+
35
+ it "should return $stdout and $stderr to original value after finish" do
36
+ out, err = $stdout, $stderr
37
+ @suite.start
38
+ $stdout.object_id.should_not == out.object_id
39
+ $stderr.object_id.should_not == err.object_id
40
+ @suite.finish
41
+ $stdout.object_id.should == out.object_id
42
+ $stderr.object_id.should == err.object_id
43
+ end
44
+
45
+ it "should capture only during run of owner test suite" do
46
+ $stdout.print "A"
47
+ $stderr.print "A"
48
+ @suite.start
49
+ $stdout.print "B"
50
+ $stderr.print "B"
51
+ @suite.finish
52
+ $stdout.print "C"
53
+ $stderr.print "C"
54
+ @suite.stdout.should == "B"
55
+ @suite.stderr.should == "B"
56
+ end
57
+ end
@@ -0,0 +1,73 @@
1
+ # (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require File.dirname(__FILE__) + "/../../../spec_helper.rb"
6
+ require 'rake'
7
+
8
+ def save_env(v)
9
+ ENV["PREV_#{v}"] = ENV[v]
10
+ end
11
+ def restore_env(v)
12
+ ENV[v] = ENV["PREV_#{v}"]
13
+ ENV.delete("PREV_#{v}")
14
+ end
15
+
16
+ describe "ci_reporter ci:setup:testunit task" do
17
+ before(:each) do
18
+ @rake = Rake::Application.new
19
+ Rake.application = @rake
20
+ load CI_REPORTER_LIB + '/ci/reporter/rake/test_unit.rb'
21
+ save_env "CI_REPORTS"
22
+ save_env "TESTOPTS"
23
+ ENV["CI_REPORTS"] = "some-bogus-nonexistent-directory-that-wont-fail-rm_rf"
24
+ end
25
+ after(:each) do
26
+ restore_env "TESTOPTS"
27
+ restore_env "CI_REPORTS"
28
+ Rake.application = nil
29
+ end
30
+
31
+ it "should set ENV['TESTOPTS'] to include test/unit setup file" do
32
+ @rake["ci:setup:testunit"].invoke
33
+ ENV["TESTOPTS"].should =~ /test_unit_loader/
34
+ end
35
+
36
+ it "should append to ENV['TESTOPTS'] if it already contains a value" do
37
+ ENV["TESTOPTS"] = "somevalue".freeze
38
+ @rake["ci:setup:testunit"].invoke
39
+ ENV["TESTOPTS"].should =~ /somevalue.*test_unit_loader/
40
+ end
41
+ end
42
+
43
+ describe "ci_reporter ci:setup:rspec task" do
44
+ before(:each) do
45
+ @rake = Rake::Application.new
46
+ Rake.application = @rake
47
+ load CI_REPORTER_LIB + '/ci/reporter/rake/rspec.rb'
48
+ save_env "CI_REPORTS"
49
+ save_env "SPEC_OPTS"
50
+ ENV["CI_REPORTS"] = "some-bogus-nonexistent-directory-that-wont-fail-rm_rf"
51
+ end
52
+ after(:each) do
53
+ restore_env "SPEC_OPTS"
54
+ restore_env "CI_REPORTS"
55
+ Rake.application = nil
56
+ end
57
+
58
+ it "should set ENV['SPEC_OPTS'] to include rspec formatter args" do
59
+ @rake["ci:setup:rspec"].invoke
60
+ ENV["SPEC_OPTS"].should =~ /--require.*rspec_loader.*--format.*CI::Reporter::RSpec/
61
+ end
62
+
63
+ it "should set ENV['SPEC_OPTS'] to include rspec doc formatter if task is ci:setup:rspecdoc" do
64
+ @rake["ci:setup:rspecdoc"].invoke
65
+ ENV["SPEC_OPTS"].should =~ /--require.*rspec_loader.*--format.*CI::Reporter::RSpecDoc/
66
+ end
67
+
68
+ it "should append to ENV['SPEC_OPTS'] if it already contains a value" do
69
+ ENV["SPEC_OPTS"] = "somevalue".freeze
70
+ @rake["ci:setup:rspec"].invoke
71
+ ENV["SPEC_OPTS"].should =~ /somevalue.*--require.*rspec_loader.*--format.*CI::Reporter::RSpec/
72
+ end
73
+ end
@@ -0,0 +1,39 @@
1
+ # (c) Copyright 2006-2007 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require File.dirname(__FILE__) + "/../../spec_helper.rb"
6
+
7
+ describe "The ReportManager" do
8
+ before(:each) do
9
+ @reports_dir = REPORTS_DIR
10
+ end
11
+
12
+ after(:each) do
13
+ FileUtils.rm_rf @reports_dir
14
+ ENV["CI_REPORTS"] = nil
15
+ end
16
+
17
+ it "should create the report directory according to the given prefix" do
18
+ CI::Reporter::ReportManager.new("spec")
19
+ File.directory?(@reports_dir).should be_true
20
+ end
21
+
22
+ it "should create the report directory based on CI_REPORTS environment variable if set" do
23
+ @reports_dir = "#{Dir.getwd}/dummy"
24
+ ENV["CI_REPORTS"] = @reports_dir
25
+ CI::Reporter::ReportManager.new("spec")
26
+ File.directory?(@reports_dir).should be_true
27
+ end
28
+
29
+ it "should write reports based on name and xml content of a test suite" do
30
+ reporter = CI::Reporter::ReportManager.new("spec")
31
+ suite = mock("test suite")
32
+ suite.should_receive(:name).and_return("some test suite name")
33
+ suite.should_receive(:to_xml).and_return("<xml></xml>")
34
+ reporter.write_report(suite)
35
+ filename = "#{REPORTS_DIR}/SPEC-some-test-suite-name.xml"
36
+ File.exist?(filename).should be_true
37
+ File.open(filename) {|f| f.read.should == "<xml></xml>"}
38
+ end
39
+ end
@@ -0,0 +1,108 @@
1
+ # (c) Copyright 2006-2008 Nick Sieger <nicksieger@gmail.com>
2
+ # See the file LICENSE.txt included with the distribution for
3
+ # software license details.
4
+
5
+ require File.dirname(__FILE__) + "/../../spec_helper.rb"
6
+ require 'stringio'
7
+
8
+ describe "The RSpec reporter" do
9
+ before(:each) do
10
+ @error = mock("error")
11
+ @error.stub!(:expectation_not_met?).and_return(false)
12
+ @error.stub!(:pending_fixed?).and_return(false)
13
+ @report_mgr = mock("report manager")
14
+ @options = mock("options")
15
+ @args = [@options, StringIO.new("")]
16
+ @args.shift if Spec::VERSION::MAJOR == 1 && Spec::VERSION::MINOR < 1
17
+ @fmt = CI::Reporter::RSpec.new *@args
18
+ @fmt.report_manager = @report_mgr
19
+ @formatter = mock("formatter")
20
+ @fmt.formatter = @formatter
21
+ end
22
+
23
+ it "should use a progress bar formatter by default" do
24
+ fmt = CI::Reporter::RSpec.new *@args
25
+ fmt.formatter.should be_instance_of(Spec::Runner::Formatter::ProgressBarFormatter)
26
+ end
27
+
28
+ it "should use a specdoc formatter for RSpecDoc" do
29
+ fmt = CI::Reporter::RSpecDoc.new *@args
30
+ fmt.formatter.should be_instance_of(Spec::Runner::Formatter::SpecdocFormatter)
31
+ end
32
+
33
+ it "should create a test suite with one success, one failure, and one pending" do
34
+ @report_mgr.should_receive(:write_report).and_return do |suite|
35
+ suite.testcases.length.should == 3
36
+ suite.testcases[0].should_not be_failure
37
+ suite.testcases[0].should_not be_error
38
+ suite.testcases[1].should be_error
39
+ suite.testcases[2].name.should =~ /\(PENDING\)/
40
+ end
41
+
42
+ example_group = mock "example group"
43
+ example_group.stub!(:description).and_return "A context"
44
+
45
+ @formatter.should_receive(:start).with(3)
46
+ @formatter.should_receive(:example_group_started).with(example_group)
47
+ @formatter.should_receive(:example_started).exactly(3).times
48
+ @formatter.should_receive(:example_passed).once
49
+ @formatter.should_receive(:example_failed).once
50
+ @formatter.should_receive(:example_pending).once
51
+ @formatter.should_receive(:start_dump).once
52
+ @formatter.should_receive(:dump_failure).once
53
+ @formatter.should_receive(:dump_summary).once
54
+ @formatter.should_receive(:dump_pending).once
55
+ @formatter.should_receive(:close).once
56
+
57
+ @fmt.start(3)
58
+ @fmt.example_group_started(example_group)
59
+ @fmt.example_started("should pass")
60
+ @fmt.example_passed("should pass")
61
+ @fmt.example_started("should fail")
62
+ @fmt.example_failed("should fail", 1, @error)
63
+ @fmt.example_started("should be pending")
64
+ @fmt.example_pending("A context", "should be pending", "Not Yet Implemented")
65
+ @fmt.start_dump
66
+ @fmt.dump_failure(1, mock("failure"))
67
+ @fmt.dump_summary(0.1, 3, 1, 1)
68
+ @fmt.dump_pending
69
+ @fmt.close
70
+ end
71
+
72
+ it "should support RSpec 1.0.8 #add_behavior" do
73
+ @formatter.should_receive(:start)
74
+ @formatter.should_receive(:add_behaviour).with("A context")
75
+ @formatter.should_receive(:example_started).once
76
+ @formatter.should_receive(:example_passed).once
77
+ @formatter.should_receive(:dump_summary)
78
+ @report_mgr.should_receive(:write_report)
79
+
80
+ @fmt.start(2)
81
+ @fmt.add_behaviour("A context")
82
+ @fmt.example_started("should pass")
83
+ @fmt.example_passed("should pass")
84
+ @fmt.dump_summary(0.1, 1, 0, 0)
85
+ end
86
+
87
+ it "should use the example #description method when available" do
88
+ group = mock "example group"
89
+ group.stub!(:description).and_return "group description"
90
+ example = mock "example"
91
+ example.stub!(:description).and_return "should do something"
92
+
93
+ @formatter.should_receive(:start)
94
+ @formatter.should_receive(:example_group_started).with(group)
95
+ @formatter.should_receive(:example_started).with(example).once
96
+ @formatter.should_receive(:example_passed).once
97
+ @formatter.should_receive(:dump_summary)
98
+ @report_mgr.should_receive(:write_report).and_return do |suite|
99
+ suite.testcases.last.name.should == "should do something"
100
+ end
101
+
102
+ @fmt.start(2)
103
+ @fmt.example_group_started(group)
104
+ @fmt.example_started(example)
105
+ @fmt.example_passed(example)
106
+ @fmt.dump_summary(0.1, 1, 0, 0)
107
+ end
108
+ end