betatest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 757d652e84bb4276928a3dd5c7741bb0a8314e8c
4
+ data.tar.gz: e7553121aee3b19a146bc905d92211b875e087cb
5
+ SHA512:
6
+ metadata.gz: daa0d1c3386ee49e5f722fca46408b731e7f7d9983e375f6a7170979ebac1e2d85b52599b20bbc78ddb487afe5985bebd7956f7b46fe1f48b061912486053905
7
+ data.tar.gz: cd02e9cc115abbf5f6fd0eba7d2e5219218a80b9ad1dc911cdef162a53fe31180f02e770ba87f5793960d1156699db53aa89e0b51bc7c81aa2d7588577419535
@@ -0,0 +1,34 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+ require 'autotest/rcov' if ENV['RCOV']
5
+
6
+ Autotest.add_hook :initialize do |at|
7
+ at.testlib = 'minitest/autorun'
8
+
9
+ bench_tests = %w(TestMinitestBenchmark)
10
+ mock_tests = %w(TestMinitestMock TestMinitestStub)
11
+ spec_tests = %w(TestMinitestReporter TestMetaStatic TestMeta
12
+ TestSpecInTestCase)
13
+ unit_tests = %w(TestMinitestGuard TestMinitestRunnable
14
+ TestMinitestRunner TestMinitestTest TestMinitestUnit
15
+ TestMinitestUnitInherited TestMinitestUnitOrder
16
+ TestMinitestUnitRecording TestMinitestUnitTestCase)
17
+
18
+ {
19
+ bench_tests => "test/minitest/test_minitest_benchmark.rb",
20
+ mock_tests => "test/minitest/test_minitest_mock.rb",
21
+ spec_tests => "test/minitest/test_minitest_reporter.rb",
22
+ unit_tests => "test/minitest/test_minitest_unit.rb",
23
+ }.each do |klasses, file|
24
+ klasses.each do |klass|
25
+ at.extra_class_map[klass] = file
26
+ end
27
+ end
28
+
29
+ at.add_exception 'coverage.info'
30
+ at.add_exception 'coverage'
31
+ end
32
+
33
+ # require 'autotest/rcov'
34
+ # Autotest::RCov.command = 'rcov_info'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in betatest.gemspec
4
+ gemspec
@@ -0,0 +1,54 @@
1
+ # Betatest
2
+
3
+ A backwards compatible fork of `betatest` but with less bugs.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'betatest'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install betatest
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/betatest/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
30
+
31
+ ## License
32
+
33
+ Released under the MIT license.
34
+
35
+ Copyright, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
36
+ Copyright, 2014, by Ryan Davis, seattle.rb.
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining a copy
39
+ of this software and associated documentation files (the "Software"), to deal
40
+ in the Software without restriction, including without limitation the rights
41
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
42
+ copies of the Software, and to permit persons to whom the Software is
43
+ furnished to do so, subject to the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be included in
46
+ all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
49
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
50
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
51
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
52
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
53
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54
+ THE SOFTWARE.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'betatest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "betatest"
8
+ spec.version = Betatest::VERSION
9
+ spec.authors = ["Ryan Davis", "Samuel Williams"]
10
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
11
+ spec.summary = %q{A fork of minitest with less bugs.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.6"
21
+ spec.add_development_dependency "rake"
22
+ end
@@ -0,0 +1,773 @@
1
+
2
+ require "betatest/version"
3
+
4
+ require "optparse"
5
+ require "thread"
6
+ require "mutex_m"
7
+ require "betatest/parallel"
8
+
9
+ ##
10
+ # :include: README.txt
11
+
12
+ module Betatest
13
+ @@installed_at_exit ||= false
14
+ @@after_run = []
15
+ @extensions = []
16
+
17
+ mc = (class << self; self; end)
18
+
19
+ ##
20
+ # Parallel test executor
21
+
22
+ mc.send :attr_accessor, :parallel_executor
23
+ self.parallel_executor = Parallel::Executor.new((ENV['N'] || 2).to_i)
24
+
25
+ ##
26
+ # Filter object for backtraces.
27
+
28
+ mc.send :attr_accessor, :backtrace_filter
29
+
30
+ ##
31
+ # Reporter object to be used for all runs.
32
+ #
33
+ # NOTE: This accessor is only available during setup, not during runs.
34
+
35
+ mc.send :attr_accessor, :reporter
36
+
37
+ ##
38
+ # Names of known extension plugins.
39
+
40
+ mc.send :attr_accessor, :extensions
41
+
42
+ ##
43
+ # Registers Betatest to run at process exit
44
+
45
+ def self.autorun
46
+ at_exit {
47
+ next if $! and not ($!.kind_of? SystemExit and $!.success?)
48
+
49
+ # Keep track of the testing process pid, so that we don't accidentally run the @@after_run handlers in children processes (e.g. Process.fork).
50
+ @@testing_process_pid = Process.pid
51
+
52
+ exit_code = nil
53
+
54
+ at_exit {
55
+ # Don't do anything if we aren't the testing process.
56
+ next if Process.pid != @@testing_process_pid
57
+
58
+ @@after_run.reverse_each(&:call)
59
+ exit exit_code || false
60
+ }
61
+
62
+ exit_code = Betatest.run ARGV
63
+ } unless @@installed_at_exit
64
+ @@installed_at_exit = true
65
+ end
66
+
67
+ ##
68
+ # A simple hook allowing you to run a block of code after everything
69
+ # is done running. Eg:
70
+ #
71
+ # Betatest.after_run { p $debugging_info }
72
+
73
+ def self.after_run &block
74
+ @@after_run << block
75
+ end
76
+
77
+ def self.init_plugins options # :nodoc:
78
+ self.extensions.each do |name|
79
+ msg = "plugin_#{name}_init"
80
+ send msg, options if self.respond_to? msg
81
+ end
82
+ end
83
+
84
+ def self.load_plugins # :nodoc:
85
+ return unless self.extensions.empty?
86
+
87
+ seen = {}
88
+
89
+ require "rubygems" unless defined? Gem
90
+
91
+ Gem.find_files("betatest/*_plugin.rb").each do |plugin_path|
92
+ name = File.basename plugin_path, "_plugin.rb"
93
+
94
+ next if seen[name]
95
+ seen[name] = true
96
+
97
+ require plugin_path
98
+ self.extensions << name
99
+ end
100
+ end
101
+
102
+ ##
103
+ # This is the top-level run method. Everything starts from here. It
104
+ # tells each Runnable sub-class to run, and each of those are
105
+ # responsible for doing whatever they do.
106
+ #
107
+ # The overall structure of a run looks like this:
108
+ #
109
+ # Betatest.autorun
110
+ # Betatest.run(args)
111
+ # Betatest.__run(reporter, options)
112
+ # Runnable.runnables.each
113
+ # runnable.run(reporter, options)
114
+ # self.runnable_methods.each
115
+ # self.run_one_method(self, runnable_method, reporter)
116
+ # Betatest.run_one_method(klass, runnable_method, reporter)
117
+ # klass.new(runnable_method).run
118
+
119
+ def self.run args = []
120
+ self.load_plugins
121
+
122
+ options = process_args args
123
+
124
+ reporter = CompositeReporter.new
125
+ reporter << SummaryReporter.new(options[:io], options)
126
+ reporter << ProgressReporter.new(options[:io], options)
127
+
128
+ self.reporter = reporter # this makes it available to plugins
129
+ self.init_plugins options
130
+ self.reporter = nil # runnables shouldn't depend on the reporter, ever
131
+
132
+ reporter.start
133
+ __run reporter, options
134
+ self.parallel_executor.shutdown
135
+ reporter.report
136
+
137
+ reporter.passed?
138
+ end
139
+
140
+ ##
141
+ # Internal run method. Responsible for telling all Runnable
142
+ # sub-classes to run.
143
+ #
144
+ # NOTE: this method is redefined in parallel_each.rb, which is
145
+ # loaded if a Runnable calls parallelize_me!.
146
+
147
+ def self.__run reporter, options
148
+ suites = Runnable.runnables.shuffle
149
+ parallel, serial = suites.partition { |s| s.test_order == :parallel }
150
+
151
+ # If we run the parallel tests before the serial tests, the parallel tests
152
+ # could run in parallel with the serial tests. This would be bad because
153
+ # the serial tests won't lock around Reporter#record. Run the serial tests
154
+ # first, so that after they complete, the parallel tests will lock when
155
+ # recording results.
156
+ serial.map { |suite| suite.run reporter, options } +
157
+ parallel.map { |suite| suite.run reporter, options }
158
+ end
159
+
160
+ def self.process_args args = [] # :nodoc:
161
+ options = {
162
+ :io => $stdout,
163
+ }
164
+ orig_args = args.dup
165
+
166
+ OptionParser.new do |opts|
167
+ opts.banner = "betatest options:"
168
+ opts.version = Betatest::VERSION
169
+
170
+ opts.on "-h", "--help", "Display this help." do
171
+ puts opts
172
+ exit
173
+ end
174
+
175
+ opts.on "-s", "--seed SEED", Integer, "Sets random seed" do |m|
176
+ options[:seed] = m.to_i
177
+ end
178
+
179
+ opts.on "-v", "--verbose", "Verbose. Show progress processing files." do
180
+ options[:verbose] = true
181
+ end
182
+
183
+ opts.on "-n", "--name PATTERN","Filter run on /pattern/ or string." do |a|
184
+ options[:filter] = a
185
+ end
186
+
187
+ unless extensions.empty?
188
+ opts.separator ""
189
+ opts.separator "Known extensions: #{extensions.join(', ')}"
190
+
191
+ extensions.each do |meth|
192
+ msg = "plugin_#{meth}_options"
193
+ send msg, opts, options if self.respond_to?(msg)
194
+ end
195
+ end
196
+
197
+ begin
198
+ opts.parse! args
199
+ rescue OptionParser::InvalidOption => e
200
+ puts
201
+ puts e
202
+ puts
203
+ puts opts
204
+ exit 1
205
+ end
206
+
207
+ orig_args -= args
208
+ end
209
+
210
+ unless options[:seed] then
211
+ srand
212
+ options[:seed] = srand % 0xFFFF
213
+ orig_args << "--seed" << options[:seed].to_s
214
+ end
215
+
216
+ srand options[:seed]
217
+
218
+ options[:args] = orig_args.map { |s|
219
+ s =~ /[\s|&<>$()]/ ? s.inspect : s
220
+ }.join " "
221
+
222
+ options
223
+ end
224
+
225
+ def self.filter_backtrace bt # :nodoc:
226
+ backtrace_filter.filter bt
227
+ end
228
+
229
+ ##
230
+ # Represents anything "runnable", like Test, Spec, Benchmark, or
231
+ # whatever you can dream up.
232
+ #
233
+ # Subclasses of this are automatically registered and available in
234
+ # Runnable.runnables.
235
+
236
+ class Runnable
237
+ ##
238
+ # Number of assertions executed in this run.
239
+
240
+ attr_accessor :assertions
241
+
242
+ ##
243
+ # An assertion raised during the run, if any.
244
+
245
+ attr_accessor :failures
246
+
247
+ ##
248
+ # Name of the run.
249
+
250
+ def name
251
+ @NAME
252
+ end
253
+
254
+ ##
255
+ # Set the name of the run.
256
+
257
+ def name= o
258
+ @NAME = o
259
+ end
260
+
261
+ def self.inherited klass # :nodoc:
262
+ self.runnables << klass
263
+ super
264
+ end
265
+
266
+ ##
267
+ # Returns all instance methods matching the pattern +re+.
268
+
269
+ def self.methods_matching re
270
+ public_instance_methods(true).grep(re).map(&:to_s)
271
+ end
272
+
273
+ def self.reset # :nodoc:
274
+ @@runnables = []
275
+ end
276
+
277
+ reset
278
+
279
+ ##
280
+ # Responsible for running all runnable methods in a given class,
281
+ # each in its own instance. Each instance is passed to the
282
+ # reporter to record.
283
+
284
+ def self.run reporter, options = {}
285
+ filter = options[:filter] || '/./'
286
+ filter = Regexp.new $1 if filter =~ /\/(.*)\//
287
+
288
+ filtered_methods = self.runnable_methods.find_all { |m|
289
+ filter === m || filter === "#{self}##{m}"
290
+ }
291
+
292
+ with_info_handler reporter do
293
+ filtered_methods.each do |method_name|
294
+ run_one_method self, method_name, reporter
295
+ end
296
+ end
297
+ end
298
+
299
+ def self.run_one_method klass, method_name, reporter
300
+ reporter.record Betatest.run_one_method(klass, method_name)
301
+ end
302
+
303
+ def self.with_info_handler reporter, &block # :nodoc:
304
+ handler = lambda do
305
+ unless reporter.passed? then
306
+ warn "Current results:"
307
+ warn ""
308
+ warn reporter.reporters.first
309
+ warn ""
310
+ end
311
+ end
312
+
313
+ on_signal "INFO", handler, &block
314
+ end
315
+
316
+ def self.on_signal name, action # :nodoc:
317
+ supported = Signal.list[name]
318
+
319
+ old_trap = trap name do
320
+ old_trap.call if old_trap.respond_to? :call
321
+ action.call
322
+ end if supported
323
+
324
+ yield
325
+ ensure
326
+ trap name, old_trap if supported
327
+ end
328
+
329
+ ##
330
+ # Each subclass of Runnable is responsible for overriding this
331
+ # method to return all runnable methods. See #methods_matching.
332
+
333
+ def self.runnable_methods
334
+ raise NotImplementedError, "subclass responsibility"
335
+ end
336
+
337
+ ##
338
+ # Returns all subclasses of Runnable.
339
+
340
+ def self.runnables
341
+ @@runnables
342
+ end
343
+
344
+ def marshal_dump # :nodoc:
345
+ [self.name, self.failures, self.assertions]
346
+ end
347
+
348
+ def marshal_load ary # :nodoc:
349
+ self.name, self.failures, self.assertions = ary
350
+ end
351
+
352
+ def failure # :nodoc:
353
+ self.failures.first
354
+ end
355
+
356
+ def initialize name # :nodoc:
357
+ self.name = name
358
+ self.failures = []
359
+ self.assertions = 0
360
+ end
361
+
362
+ ##
363
+ # Runs a single method. Needs to return self.
364
+
365
+ def run
366
+ raise NotImplementedError, "subclass responsibility"
367
+ end
368
+
369
+ ##
370
+ # Did this run pass?
371
+ #
372
+ # Note: skipped runs are not considered passing, but they don't
373
+ # cause the process to exit non-zero.
374
+
375
+ def passed?
376
+ raise NotImplementedError, "subclass responsibility"
377
+ end
378
+
379
+ ##
380
+ # Returns a single character string to print based on the result
381
+ # of the run. Eg ".", "F", or "E".
382
+
383
+ def result_code
384
+ raise NotImplementedError, "subclass responsibility"
385
+ end
386
+
387
+ ##
388
+ # Was this run skipped? See #passed? for more information.
389
+
390
+ def skipped?
391
+ raise NotImplementedError, "subclass responsibility"
392
+ end
393
+ end
394
+
395
+ ##
396
+ # Defines the API for Reporters. Subclass this and override whatever
397
+ # you want. Go nuts.
398
+
399
+ class AbstractReporter
400
+ include Mutex_m
401
+
402
+ ##
403
+ # Starts reporting on the run.
404
+
405
+ def start
406
+ end
407
+
408
+ ##
409
+ # Record a result and output the Runnable#result_code. Stores the
410
+ # result of the run if the run did not pass.
411
+
412
+ def record result
413
+ end
414
+
415
+ ##
416
+ # Outputs the summary of the run.
417
+
418
+ def report
419
+ end
420
+
421
+ ##
422
+ # Did this run pass?
423
+
424
+ def passed?
425
+ true
426
+ end
427
+ end
428
+
429
+ class Reporter < AbstractReporter # :nodoc:
430
+ ##
431
+ # The IO used to report.
432
+
433
+ attr_accessor :io
434
+
435
+ ##
436
+ # Command-line options for this run.
437
+
438
+ attr_accessor :options
439
+
440
+ def initialize io = $stdout, options = {} # :nodoc:
441
+ super()
442
+ self.io = io
443
+ self.options = options
444
+ end
445
+ end
446
+
447
+ ##
448
+ # A very simple reporter that prints the "dots" during the run.
449
+ #
450
+ # This is added to the top-level CompositeReporter at the start of
451
+ # the run. If you want to change the output of betatest via a
452
+ # plugin, pull this out of the composite and replace it with your
453
+ # own.
454
+
455
+ class ProgressReporter < Reporter
456
+ def record result # :nodoc:
457
+ io.print "%s#%s = %.2f s = " % [result.class, result.name, result.time] if
458
+ options[:verbose]
459
+ io.print result.result_code
460
+ io.puts if options[:verbose]
461
+ end
462
+ end
463
+
464
+ ##
465
+ # A reporter that gathers statistics about a test run. Does not do
466
+ # any IO because meant to be used as a parent class for a reporter
467
+ # that does.
468
+ #
469
+ # If you want to create an entirely different type of output (eg,
470
+ # CI, HTML, etc), this is the place to start.
471
+
472
+ class StatisticsReporter < Reporter
473
+ # :stopdoc:
474
+ attr_accessor :assertions
475
+ attr_accessor :count
476
+ attr_accessor :results
477
+ attr_accessor :start_time
478
+ attr_accessor :total_time
479
+ attr_accessor :failures
480
+ attr_accessor :errors
481
+ attr_accessor :skips
482
+ # :startdoc:
483
+
484
+ def initialize io = $stdout, options = {} # :nodoc:
485
+ super
486
+
487
+ self.assertions = 0
488
+ self.count = 0
489
+ self.results = []
490
+ self.start_time = nil
491
+ self.total_time = nil
492
+ self.failures = nil
493
+ self.errors = nil
494
+ self.skips = nil
495
+ end
496
+
497
+ def passed? # :nodoc:
498
+ results.all?(&:skipped?)
499
+ end
500
+
501
+ def start # :nodoc:
502
+ self.start_time = Time.now
503
+ end
504
+
505
+ def record result # :nodoc:
506
+ self.count += 1
507
+ self.assertions += result.assertions
508
+
509
+ results << result if not result.passed? or result.skipped?
510
+ end
511
+
512
+ def report # :nodoc:
513
+ aggregate = results.group_by { |r| r.failure.class }
514
+ aggregate.default = [] # dumb. group_by should provide this
515
+
516
+ self.total_time = Time.now - start_time
517
+ self.failures = aggregate[Assertion].size
518
+ self.errors = aggregate[UnexpectedError].size
519
+ self.skips = aggregate[Skip].size
520
+ end
521
+ end
522
+
523
+ ##
524
+ # A reporter that prints the header, summary, and failure details at
525
+ # the end of the run.
526
+ #
527
+ # This is added to the top-level CompositeReporter at the start of
528
+ # the run. If you want to change the output of betatest via a
529
+ # plugin, pull this out of the composite and replace it with your
530
+ # own.
531
+
532
+ class SummaryReporter < StatisticsReporter
533
+ # :stopdoc:
534
+ attr_accessor :sync
535
+ attr_accessor :old_sync
536
+ # :startdoc:
537
+
538
+ def start # :nodoc:
539
+ super
540
+
541
+ io.puts "Run options: #{options[:args]}"
542
+ io.puts
543
+ io.puts "# Running:"
544
+ io.puts
545
+
546
+ self.sync = io.respond_to? :"sync=" # stupid emacs
547
+ self.old_sync, io.sync = io.sync, true if self.sync
548
+ end
549
+
550
+ def report # :nodoc:
551
+ super
552
+
553
+ io.sync = self.old_sync
554
+
555
+ io.puts unless options[:verbose] # finish the dots
556
+ io.puts
557
+ io.puts statistics
558
+ io.puts aggregated_results
559
+ io.puts summary
560
+ end
561
+
562
+ def statistics # :nodoc:
563
+ "Finished in %.6fs, %.4f runs/s, %.4f assertions/s." %
564
+ [total_time, count / total_time, assertions / total_time]
565
+ end
566
+
567
+ def aggregated_results # :nodoc:
568
+ filtered_results = results.dup
569
+ filtered_results.reject!(&:skipped?) unless options[:verbose]
570
+
571
+ filtered_results.each_with_index.map do |result, i|
572
+ "\n%3d) %s" % [i+1, result]
573
+ end.join("\n") + "\n"
574
+ end
575
+
576
+ alias to_s aggregated_results
577
+
578
+ def summary # :nodoc:
579
+ extra = ""
580
+
581
+ extra = "\n\nYou have skipped tests. Run with --verbose for details." if
582
+ results.any?(&:skipped?) unless options[:verbose] or ENV["MT_NO_SKIP_MSG"]
583
+
584
+ "%d runs, %d assertions, %d failures, %d errors, %d skips%s" %
585
+ [count, assertions, failures, errors, skips, extra]
586
+ end
587
+ end
588
+
589
+ ##
590
+ # Dispatch to multiple reporters as one.
591
+
592
+ class CompositeReporter < AbstractReporter
593
+ ##
594
+ # The list of reporters to dispatch to.
595
+
596
+ attr_accessor :reporters
597
+
598
+ def initialize *reporters # :nodoc:
599
+ super()
600
+ self.reporters = reporters
601
+ end
602
+
603
+ ##
604
+ # Add another reporter to the mix.
605
+
606
+ def << reporter
607
+ self.reporters << reporter
608
+ end
609
+
610
+ def passed? # :nodoc:
611
+ self.reporters.all?(&:passed?)
612
+ end
613
+
614
+ def start # :nodoc:
615
+ self.reporters.each(&:start)
616
+ end
617
+
618
+ def record result # :nodoc:
619
+ self.reporters.each do |reporter|
620
+ reporter.record result
621
+ end
622
+ end
623
+
624
+ def report # :nodoc:
625
+ self.reporters.each(&:report)
626
+ end
627
+ end
628
+
629
+ ##
630
+ # Represents run failures.
631
+
632
+ class Assertion < Exception
633
+ def error # :nodoc:
634
+ self
635
+ end
636
+
637
+ ##
638
+ # Where was this run before an assertion was raised?
639
+
640
+ def location
641
+ last_before_assertion = ""
642
+ self.backtrace.reverse_each do |s|
643
+ break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
644
+ last_before_assertion = s
645
+ end
646
+ last_before_assertion.sub(/:in .*$/, "")
647
+ end
648
+
649
+ def result_code # :nodoc:
650
+ result_label[0, 1]
651
+ end
652
+
653
+ def result_label # :nodoc:
654
+ "Failure"
655
+ end
656
+ end
657
+
658
+ ##
659
+ # Assertion raised when skipping a run.
660
+
661
+ class Skip < Assertion
662
+ def result_label # :nodoc:
663
+ "Skipped"
664
+ end
665
+ end
666
+
667
+ ##
668
+ # Assertion wrapping an unexpected error that was raised during a run.
669
+
670
+ class UnexpectedError < Assertion
671
+ attr_accessor :exception # :nodoc:
672
+
673
+ def initialize exception # :nodoc:
674
+ super
675
+ self.exception = exception
676
+ end
677
+
678
+ def backtrace # :nodoc:
679
+ self.exception.backtrace
680
+ end
681
+
682
+ def error # :nodoc:
683
+ self.exception
684
+ end
685
+
686
+ def message # :nodoc:
687
+ bt = Betatest::filter_backtrace(self.backtrace).join "\n "
688
+ "#{self.exception.class}: #{self.exception.message}\n #{bt}"
689
+ end
690
+
691
+ def result_label # :nodoc:
692
+ "Error"
693
+ end
694
+ end
695
+
696
+ ##
697
+ # Provides a simple set of guards that you can use in your tests
698
+ # to skip execution if it is not applicable. These methods are
699
+ # mixed into Test as both instance and class methods so you
700
+ # can use them inside or outside of the test methods.
701
+ #
702
+ # def test_something_for_mri
703
+ # skip "bug 1234" if jruby?
704
+ # # ...
705
+ # end
706
+ #
707
+ # if windows? then
708
+ # # ... lots of test methods ...
709
+ # end
710
+
711
+ module Guard
712
+
713
+ ##
714
+ # Is this running on jruby?
715
+
716
+ def jruby? platform = RUBY_PLATFORM
717
+ "java" == platform
718
+ end
719
+
720
+ ##
721
+ # Is this running on maglev?
722
+
723
+ def maglev? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
724
+ "maglev" == platform
725
+ end
726
+
727
+ ##
728
+ # Is this running on mri?
729
+
730
+ def mri? platform = RUBY_DESCRIPTION
731
+ /^ruby/ =~ platform
732
+ end
733
+
734
+ ##
735
+ # Is this running on rubinius?
736
+
737
+ def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
738
+ "rbx" == platform
739
+ end
740
+
741
+ ##
742
+ # Is this running on windows?
743
+
744
+ def windows? platform = RUBY_PLATFORM
745
+ /mswin|mingw/ =~ platform
746
+ end
747
+ end
748
+
749
+ class BacktraceFilter # :nodoc:
750
+ def filter bt
751
+ return ["No backtrace"] unless bt
752
+
753
+ return bt.dup if $DEBUG
754
+
755
+ new_bt = bt.take_while { |line| line !~ /lib\/betatest/ }
756
+ new_bt = bt.select { |line| line !~ /lib\/betatest/ } if new_bt.empty?
757
+ new_bt = bt.dup if new_bt.empty?
758
+
759
+ new_bt
760
+ end
761
+ end
762
+
763
+ self.backtrace_filter = BacktraceFilter.new
764
+
765
+ def self.run_one_method klass, method_name # :nodoc:
766
+ result = klass.new(method_name).run
767
+ raise "#{klass}#run _must_ return self" unless klass === result
768
+ result
769
+ end
770
+ end
771
+
772
+ require "betatest/test"
773
+ require "betatest/unit" unless defined?(MiniTest) # compatibility layer only