beaker 2.30.0 → 2.30.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,410 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- require 'nokogiri'
3
- require 'fileutils'
4
- [ 'test_case', 'logger' ].each do |lib|
5
- require "beaker/#{lib}"
6
- end
7
-
8
- module Beaker
9
- module Runner
10
- module Native
11
- #A collection of {TestCase} objects are considered a {TestSuite}.
12
- #Handles executing the set of {TestCase} instances and reporting results as post summary text and JUnit XML.
13
- class TestSuite
14
-
15
- #Holds the output of a test suite, formats in plain text or xml
16
- class TestSuiteResult
17
- attr_accessor :start_time, :stop_time, :total_tests
18
-
19
- #Create a {TestSuiteResult} instance.
20
- #@param [Hash{Symbol=>String}] options Options for this object
21
- #@option options [Logger] :logger The Logger object to report information to
22
- #@param [String] name The name of the {TestSuite} that the results are for
23
- def initialize( options, name )
24
- @options = options
25
- @logger = options[:logger]
26
- @name = name
27
- @test_cases = []
28
- #Set some defaults, just in case you attempt to print without including them
29
- start_time = Time.at(0)
30
- stop_time = Time.at(1)
31
- end
32
-
33
- #Add a {TestCase} to this {TestSuiteResult} instance, used in calculating {TestSuiteResult} data.
34
- #@param [TestCase] test_case An individual, completed {TestCase} to be included in this set of {TestSuiteResult}.
35
- def add_test_case( test_case )
36
- @test_cases << test_case
37
- end
38
-
39
- #How many {TestCase} instances are in this {TestSuiteResult}
40
- def test_count
41
- @test_cases.length
42
- end
43
-
44
- #How many passed {TestCase} instances are in this {TestSuiteResult}
45
- def passed_tests
46
- @test_cases.select { |c| c.test_status == :pass }.length
47
- end
48
-
49
- #How many errored {TestCase} instances are in this {TestSuiteResult}
50
- def errored_tests
51
- @test_cases.select { |c| c.test_status == :error }.length
52
- end
53
-
54
- #How many failed {TestCase} instances are in this {TestSuiteResult}
55
- def failed_tests
56
- @test_cases.select { |c| c.test_status == :fail }.length
57
- end
58
-
59
- #How many skipped {TestCase} instances are in this {TestSuiteResult}
60
- def skipped_tests
61
- @test_cases.select { |c| c.test_status == :skip }.length
62
- end
63
-
64
- #How many pending {TestCase} instances are in this {TestSuiteResult}
65
- def pending_tests
66
- @test_cases.select {|c| c.test_status == :pending}.length
67
- end
68
-
69
- #How many {TestCase} instances failed in this {TestSuiteResult}
70
- def sum_failed
71
- failed_tests + errored_tests
72
- end
73
-
74
- #Did all the {TestCase} instances in this {TestSuiteResult} pass?
75
- def success?
76
- sum_failed == 0
77
- end
78
-
79
- #Did one or more {TestCase} instances in this {TestSuiteResult} fail?
80
- def failed?
81
- !success?
82
- end
83
-
84
- #The sum of all {TestCase} runtimes in this {TestSuiteResult}
85
- def elapsed_time
86
- @test_cases.inject(0.0) {|r, t| r + t.runtime.to_f }
87
- end
88
-
89
- #Plain text summay of test suite
90
- #@param [Logger] summary_logger The logger we will print the summary to
91
- def summarize(summary_logger)
92
-
93
- summary_logger.notify <<-HEREDOC
94
- Test Suite: #{@name} @ #{start_time}
95
-
96
- - Host Configuration Summary -
97
- HEREDOC
98
-
99
- average_test_time = elapsed_time / test_count
100
-
101
- summary_logger.notify %Q[
102
-
103
- - Test Case Summary for suite '#{@name}' -
104
- Total Suite Time: %.2f seconds
105
- Average Test Time: %.2f seconds
106
- Attempted: #{test_count}
107
- Passed: #{passed_tests}
108
- Failed: #{failed_tests}
109
- Errored: #{errored_tests}
110
- Skipped: #{skipped_tests}
111
- Pending: #{pending_tests}
112
- Total: #{@total_tests}
113
-
114
- - Specific Test Case Status -
115
- ] % [elapsed_time, average_test_time]
116
-
117
- grouped_summary = @test_cases.group_by{|test_case| test_case.test_status }
118
-
119
- summary_logger.notify "Failed Tests Cases:"
120
- (grouped_summary[:fail] || []).each do |test_case|
121
- print_test_result(test_case)
122
- end
123
-
124
- summary_logger.notify "Errored Tests Cases:"
125
- (grouped_summary[:error] || []).each do |test_case|
126
- print_test_result(test_case)
127
- end
128
-
129
- summary_logger.notify "Skipped Tests Cases:"
130
- (grouped_summary[:skip] || []).each do |test_case|
131
- print_test_result(test_case)
132
- end
133
-
134
- summary_logger.notify "Pending Tests Cases:"
135
- (grouped_summary[:pending] || []).each do |test_case|
136
- print_test_result(test_case)
137
- end
138
-
139
- summary_logger.notify("\n\n")
140
- end
141
-
142
- #A convenience method for printing the results of a {TestCase}
143
- #@param [TestCase] test_case The {TestCase} to examine and print results for
144
- def print_test_result(test_case)
145
- test_reported = if test_case.exception
146
- "reported: #{test_case.exception.inspect}"
147
- else
148
- test_case.test_status
149
- end
150
- @logger.notify " Test Case #{test_case.path} #{test_reported}"
151
- end
152
-
153
- # Writes Junit XML of this {TestSuiteResult}
154
- #
155
- # @param [String] xml_file Path to the XML file (from Beaker's running directory)
156
- # @param [String] file_to_link Path to the paired file that should be linked
157
- # from this one (this is relative to the XML
158
- # file itself, so it would just be the different
159
- # file name if they're in the same directory)
160
- # @param [Boolean] time_sort Whether the test results should be output in
161
- # order of time spent in the test, or in the
162
- # order of test execution (default)
163
- #
164
- # @return nil
165
- # @api private
166
- def write_junit_xml(xml_file, file_to_link = nil, time_sort = false)
167
- stylesheet = File.join(@options[:project_root], @options[:xml_stylesheet])
168
-
169
- begin
170
- LoggerJunit.write_xml(xml_file, stylesheet) do |doc, suites|
171
-
172
- meta_info = Nokogiri::XML::Node.new('meta_test_info', doc)
173
- unless file_to_link.nil?
174
- meta_info['page_active'] = time_sort ? 'performance' : 'execution'
175
- meta_info['link_url'] = file_to_link
176
- else
177
- meta_info['page_active'] = 'no-links'
178
- meta_info['link_url'] = ''
179
- end
180
- suites.add_child(meta_info)
181
-
182
- suite = Nokogiri::XML::Node.new('testsuite', doc)
183
- suite['name'] = @name
184
- suite['tests'] = test_count
185
- suite['errors'] = errored_tests
186
- suite['failures'] = failed_tests
187
- suite['skip'] = skipped_tests
188
- suite['pending'] = pending_tests
189
- suite['total'] = @total_tests
190
- suite['time'] = "%f" % (stop_time - start_time)
191
- properties = Nokogiri::XML::Node.new('properties', doc)
192
- @options.each_pair do | name, value |
193
- property = Nokogiri::XML::Node.new('property', doc)
194
- property['name'] = name
195
- property['value'] = value
196
- properties.add_child(property)
197
- end
198
- suite.add_child(properties)
199
-
200
- test_cases_to_report = @test_cases
201
- test_cases_to_report = @test_cases.sort { |x,y| y.runtime <=> x.runtime } if time_sort
202
- test_cases_to_report.each do |test|
203
- item = Nokogiri::XML::Node.new('testcase', doc)
204
- item['classname'] = File.dirname(test.path)
205
- item['name'] = File.basename(test.path)
206
- item['time'] = "%f" % test.runtime
207
-
208
- # Did we fail? If so, report that.
209
- # We need to remove the escape character from colorized text, the
210
- # substitution of other entities is handled well by Rexml
211
- if test.test_status == :fail || test.test_status == :error then
212
- status = Nokogiri::XML::Node.new('failure', doc)
213
- status['type'] = test.test_status.to_s
214
- if test.exception then
215
- status['message'] = test.exception.to_s.gsub(/\e/, '')
216
- data = LoggerJunit.format_cdata(test.exception.backtrace.join('\n'))
217
- status.add_child(status.document.create_cdata(data))
218
- end
219
- item.add_child(status)
220
- end
221
-
222
- if test.test_status == :skip
223
- status = Nokogiri::XML::Node.new('skip', doc)
224
- status['type'] = test.test_status.to_s
225
- item.add_child(status)
226
- end
227
-
228
- if test.test_status == :pending
229
- status = Nokogiri::XML::Node.new('pending', doc)
230
- status['type'] = test.test_status.to_s
231
- item.add_child(status)
232
- end
233
-
234
- if test.sublog then
235
- stdout = Nokogiri::XML::Node.new('system-out', doc)
236
- data = LoggerJunit.format_cdata(test.sublog)
237
- stdout.add_child(stdout.document.create_cdata(data))
238
- item.add_child(stdout)
239
- end
240
-
241
- if test.last_result and test.last_result.stderr and not test.last_result.stderr.empty? then
242
- stderr = Nokogiri::XML::Node.new('system-err', doc)
243
- data = LoggerJunit.format_cdata(test.last_result.stderr)
244
- stderr.add_child(stderr.document.create_cdata(data))
245
- item.add_child(stderr)
246
- end
247
-
248
- suite.add_child(item)
249
- end
250
- suites.add_child(suite)
251
- end
252
- rescue Exception => e
253
- @logger.error "failure in XML output:\n#{e.to_s}\n" + e.backtrace.join("\n")
254
- end
255
-
256
- end
257
- end
258
-
259
- attr_reader :name, :options, :fail_mode
260
-
261
- #Create {TestSuite} instance
262
- #@param [String] name The name of the {TestSuite}
263
- #@param [Array<Host>] hosts An Array of Hosts to act upon.
264
- #@param [Hash{Symbol=>String}] options Options for this object
265
- #@option options [Logger] :logger The Logger object to report information to
266
- #@option options [String] :log_dir The directory where text run logs will be written
267
- #@option options [String] :xml_dir The directory where JUnit XML file will be written
268
- #@option options [String] :xml_file The name of the JUnit XML file to be written to
269
- #@option options [String] :project_root The full path to the Beaker lib directory
270
- #@option options [String] :xml_stylesheet The path to a stylesheet to be applied to the generated XML output
271
- #@param [Symbol] fail_mode One of :slow, :fast
272
- #@param [Time] timestamp Beaker execution start time
273
- def initialize(name, hosts, options, timestamp, fail_mode=nil)
274
- @logger = options[:logger]
275
- @test_cases = []
276
- @test_files = options[name]
277
- @name = name.to_s.gsub(/\s+/, '-')
278
- @hosts = hosts
279
- @run = false
280
- @options = options
281
- @fail_mode = fail_mode || @options[:fail_mode] || :slow
282
- @test_suite_results = TestSuiteResult.new(@options, name)
283
- @timestamp = timestamp
284
-
285
- report_and_raise(@logger, RuntimeError.new("#{@name}: no test files found..."), "TestSuite: initialize") if @test_files.empty?
286
-
287
- rescue => e
288
- report_and_raise(@logger, e, "TestSuite: initialize")
289
- end
290
-
291
- #Execute all the {TestCase} instances and then report the results as both plain text and xml. The text result
292
- #is reported to a newly created run log.
293
- #Execution is dependent upon the fail_mode. If mode is :fast then stop running any additional {TestCase} instances
294
- #after first failure, if mode is :slow continue execution no matter what {TestCase} results are.
295
- def run
296
- @run = true
297
- start_time = Time.now
298
-
299
- #Create a run log for this TestSuite.
300
- run_log = log_path("#{@name}-run.log", @options[:log_dated_dir])
301
- @logger.add_destination(run_log)
302
-
303
- # This is an awful hack to maintain backward compatibility until tests
304
- # are ported to use logger. Still in use in PuppetDB tests
305
- ::Beaker.const_set(:Log, @logger) unless defined?( ::Beaker::Log )
306
-
307
- @test_suite_results.start_time = start_time
308
- @test_suite_results.total_tests = @test_files.length
309
-
310
- @test_files.each do |test_file|
311
- @logger.info "Begin #{test_file}"
312
- start = Time.now
313
- test_case = TestCase.new(@hosts, @logger, options, test_file).run_test
314
- duration = Time.now - start
315
- @test_suite_results.add_test_case(test_case)
316
- @test_cases << test_case
317
-
318
- state = test_case.test_status == :skip ? 'skipp' : test_case.test_status
319
- msg = "#{test_file} #{state}ed in %.2f seconds" % duration.to_f
320
- case test_case.test_status
321
- when :pass
322
- @logger.success msg
323
- when :skip
324
- @logger.warn msg
325
- when :fail
326
- @logger.error msg
327
- break if @fail_mode.to_s !~ /slow/ #all failure modes except slow cause us to kick out early on failure
328
- when :error
329
- @logger.warn msg
330
- break if @fail_mode.to_s !~ /slow/ #all failure modes except slow cause us to kick out early on failure
331
- end
332
- end
333
- @test_suite_results.stop_time = Time.now
334
-
335
- # REVISIT: This changes global state, breaking logging in any future runs
336
- # of the suite – or, at least, making them highly confusing for anyone who
337
- # has not studied the implementation in detail. --daniel 2011-03-14
338
- @test_suite_results.summarize( Logger.new(log_path("#{name}-summary.txt", @options[:log_dated_dir]), STDOUT) )
339
-
340
- junit_file_log = log_path(@options[:xml_file], @options[:xml_dated_dir])
341
- if @options[:xml_time_enabled]
342
- junit_file_time = log_path(@options[:xml_time], @options[:xml_dated_dir])
343
- @test_suite_results.write_junit_xml( junit_file_log, @options[:xml_time] )
344
- @test_suite_results.write_junit_xml( junit_file_time, @options[:xml_file], true )
345
- else
346
- @test_suite_results.write_junit_xml( junit_file_log )
347
- end
348
- #All done with this run, remove run log
349
- @logger.remove_destination(run_log)
350
-
351
- # Allow chaining operations...
352
- return self
353
- end
354
-
355
- #Execute all the TestCases in this suite.
356
- #This is a wrapper that catches any failures generated during TestSuite::run.
357
- def run_and_raise_on_failure
358
- begin
359
- run
360
- return self if @test_suite_results.success?
361
- rescue => e
362
- #failed during run
363
- report_and_raise(@logger, e, "TestSuite :run_and_raise_on_failure")
364
- else
365
- #failed during test
366
- report_and_raise(@logger, RuntimeError.new("Failed while running the #{name} suite"), "TestSuite: report_and_raise_on_failure")
367
- end
368
- end
369
-
370
- # Gives a full file path for output to be written to, maintaining the latest symlink
371
- # @param [String] name The file name that we want to write to.
372
- # @param [String] log_dir The desired output directory.
373
- # A symlink will be made from ./basedir/latest to that.
374
- # @example
375
- # log_path('output.txt', 'log/2014-06-02_16_31_22')
376
- #
377
- # This will create the structure:
378
- #
379
- # ./log/2014-06-02_16_31_22/output.txt
380
- # ./log/latest -> 2014-06-02_16_31_22
381
- #
382
- # @example
383
- # log_path('foo.log', 'log/man/date')
384
- #
385
- # This will create the structure:
386
- #
387
- # ./log/man/date/foo.log
388
- # ./log/latest -> man/date
389
- def log_path(name, log_dir)
390
- FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir)
391
-
392
- base_dir = log_dir
393
- link_dir = ''
394
- while File.dirname(base_dir) != '.' do
395
- link_dir = link_dir == '' ? File.basename(base_dir) : File.join(File.basename(base_dir), link_dir)
396
- base_dir = File.dirname(base_dir)
397
- end
398
-
399
- latest = File.join(base_dir, "latest")
400
- if !File.exist?(latest) or File.symlink?(latest) then
401
- File.delete(latest) if File.exist?(latest) || File.symlink?(latest)
402
- File.symlink(link_dir, latest)
403
- end
404
-
405
- File.join(log_dir, name)
406
- end
407
- end
408
- end
409
- end
410
- end
@@ -1,34 +0,0 @@
1
- require "spec_helper"
2
-
3
- # safely set values for ARGV in block, restoring original value on block leave
4
- def with_ARGV(value, &block)
5
- if defined? ARGV
6
- defined_ARGV, old_ARGV = true, ARGV
7
- Object.send(:remove_const, :ARGV)
8
- else
9
- defined_ARGV, old_ARGV = false, nil
10
- end
11
-
12
- Object.send(:const_set, :ARGV, value)
13
-
14
- yield
15
- ensure
16
- Object.send(:remove_const, :ARGV)
17
- Object.send(:const_set, :ARGV, old_ARGV) if defined_ARGV
18
- end
19
-
20
- describe "Beaker Options" do
21
- let (:parser) { Beaker::Options::Parser.new }
22
-
23
- it "defaults :runner to 'native'" do
24
- with_ARGV([]) do
25
- expect(parser.parse_args[:runner]).to be == "native"
26
- end
27
- end
28
-
29
- it "accepts :runner from command-line" do
30
- with_ARGV(["--runner", "minitest"]) do
31
- expect(parser.parse_args[:runner]).to be == "minitest"
32
- end
33
- end
34
- end
@@ -1,147 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Beaker
4
- module Runner
5
- module Native
6
- describe TestCase do
7
- let(:logger) { double('logger').as_null_object }
8
- let(:path) { @path || '/tmp/nope' }
9
- let(:testcase) { TestCase.new({}, logger, {}, path) }
10
-
11
- context 'run_test' do
12
- it 'defaults to test_status :pass on success' do
13
- path = 'test.rb'
14
- File.open(path, 'w') do |f|
15
- f.write ""
16
- end
17
- @path = path
18
- expect( testcase ).to_not receive( :log_and_fail_test )
19
- testcase.run_test
20
- status = testcase.instance_variable_get(:@test_status)
21
- expect(status).to be === :pass
22
- end
23
-
24
- it 'updates test_status to :skip on SkipTest' do
25
- path = 'test.rb'
26
- File.open(path, 'w') do |f|
27
- f.write "raise SkipTest"
28
- end
29
- @path = path
30
- expect( testcase ).to_not receive( :log_and_fail_test )
31
- testcase.run_test
32
- status = testcase.instance_variable_get(:@test_status)
33
- expect(status).to be === :skip
34
- end
35
-
36
- it 'updates test_status to :pending on PendingTest' do
37
- path = 'test.rb'
38
- File.open(path, 'w') do |f|
39
- f.write "raise PendingTest"
40
- end
41
- @path = path
42
- expect( testcase ).to_not receive( :log_and_fail_test )
43
- testcase.run_test
44
- status = testcase.instance_variable_get(:@test_status)
45
- expect(status).to be === :pending
46
- end
47
-
48
- it 'updates test_status to :fail on FailTest' do
49
- path = 'test.rb'
50
- File.open(path, 'w') do |f|
51
- f.write "raise FailTest"
52
- end
53
- @path = path
54
- expect( testcase ).to_not receive( :log_and_fail_test )
55
- testcase.run_test
56
- status = testcase.instance_variable_get(:@test_status)
57
- expect(status).to be === :fail
58
- end
59
-
60
- it 'correctly handles RuntimeError' do
61
- path = 'test.rb'
62
- File.open(path, 'w') do |f|
63
- f.write "raise RuntimeError"
64
- end
65
- @path = path
66
- expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(RuntimeError))
67
- testcase.run_test
68
- end
69
-
70
- it 'correctly handles ScriptError' do
71
- path = 'test.rb'
72
- File.open(path, 'w') do |f|
73
- f.write "raise ScriptError"
74
- end
75
- @path = path
76
- expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(ScriptError))
77
- testcase.run_test
78
- end
79
-
80
- it 'correctly handles Timeout::Error' do
81
- path = 'test.rb'
82
- File.open(path, 'w') do |f|
83
- f.write "raise Timeout::Error"
84
- end
85
- @path = path
86
- expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Timeout::Error))
87
- testcase.run_test
88
- end
89
-
90
- it 'correctly handles CommandFailure' do
91
- path = 'test.rb'
92
- File.open(path, 'w') do |f|
93
- f.write "raise Host::CommandFailure"
94
- end
95
- @path = path
96
- expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Host::CommandFailure))
97
- testcase.run_test
98
- end
99
-
100
- it 'records a test failure if an assertion fails in a teardown block' do
101
- path = 'test.rb'
102
- File.open(path, 'w') do |f|
103
- f.write <<-EOF
104
- teardown do
105
- assert_equal(1, 2, 'Oh noes!')
106
- end
107
- EOF
108
- end
109
- @path = path
110
- expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Minitest::Assertion))
111
- testcase.run_test
112
- end
113
- end
114
-
115
- context 'metadata' do
116
- it 'sets the filename correctly from the path' do
117
- answer = 'jacket'
118
- path = "#{answer}.rb"
119
- File.open(path, 'w') do |f|
120
- f.write ""
121
- end
122
- @path = path
123
- testcase.run_test
124
- metadata = testcase.instance_variable_get(:@metadata)
125
- expect(metadata[:case][:file_name]).to be === answer
126
- end
127
-
128
- it 'resets the step name' do
129
- path = 'test.rb'
130
- File.open(path, 'w') do |f|
131
- f.write ""
132
- end
133
- @path = path
134
- # we have to create a TestCase by hand, so that we can set old
135
- tc = TestCase.new({}, logger, {}, path)
136
- # metadata on it, so that we can test that it's being reset correctly
137
- old_metadata = { :step => { :name => 'CharlieBrown' } }
138
- tc.instance_variable_set(:@metadata, old_metadata)
139
- tc.run_test
140
- metadata = tc.instance_variable_get(:@metadata)
141
- expect(metadata[:step][:name]).to be_nil
142
- end
143
- end
144
- end
145
- end
146
- end
147
- end