beaker 2.30.0 → 2.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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