lib/minitest.rb in spec-5.0.19 vs lib/minitest.rb in spec-5.3.3

- old
+ new

@@ -1,22 +1,30 @@ require "optparse" -require 'chronic_duration' +require "thread" +require "mutex_m" +require "minitest/parallel" ## # :include: README.txt module Minitest - VERSION = '5.0.19' # :nodoc: - DATE = '2013-08-19' # :nodoc: + VERSION = '5.3.3' # :nodoc: + DATE = '2014-04-21' # :nodoc: @@installed_at_exit ||= false @@after_run = [] @extensions = [] mc = (class << self; self; end) ## + # Parallel test executor + + mc.send :attr_accessor, :parallel_executor + self.parallel_executor = Parallel::Executor.new((ENV['N'] || 2).to_i) + + ## # Filter object for backtraces. mc.send :attr_accessor, :backtrace_filter ## @@ -34,11 +42,11 @@ ## # Registers Minitest to run at process exit def self.autorun at_exit { - next if $! and not $!.kind_of? SystemExit + next if $! and not ($!.kind_of? SystemExit and $!.success?) exit_code = nil at_exit { @@after_run.reverse_each(&:call) @@ -70,10 +78,12 @@ def self.load_plugins # :nodoc: return unless self.extensions.empty? seen = {} + require "rubygems" unless defined? Gem + Gem.find_files("minitest/*_plugin.rb").each do |plugin_path| name = File.basename plugin_path, "_plugin.rb" next if seen[name] seen[name] = true @@ -90,31 +100,34 @@ # # The overall structure of a run looks like this: # # Minitest.autorun # Minitest.run(args) - # __run(reporter, options) + # Minitest.__run(reporter, options) # Runnable.runnables.each # runnable.run(reporter, options) # self.runnable_methods.each - # self.new(runnable_method).run + # self.run_one_method(self, runnable_method, reporter) + # Minitest.run_one_method(klass, runnable_method, reporter) + # klass.new(runnable_method).run def self.run args = [] self.load_plugins options = process_args args reporter = CompositeReporter.new - reporter << ProgressReporter.new(options[:io], options) reporter << SummaryReporter.new(options[:io], options) + reporter << ProgressReporter.new(options[:io], options) self.reporter = reporter # this makes it available to plugins self.init_plugins options self.reporter = nil # runnables shouldn't depend on the reporter, ever reporter.start __run reporter, options + self.parallel_executor.shutdown reporter.report reporter.passed? end @@ -124,92 +137,26 @@ # # NOTE: this method is redefined in parallel_each.rb, which is # loaded if a Runnable calls parallelize_me!. def self.__run reporter, options - Runnable.runnables.each do |runnable| - runnable.run reporter, options - end - end + suites = Runnable.runnables + parallel, serial = suites.partition { |s| s.test_order == :parallel } - ## - # Trace file source to :io (default $stdout) - # - # spec_opts = {} - # - # @param :trace [Array<String>] the files to trace - # @param :io [IO] io to print to - def self.trace_specs spec_opts - targets = [] - files = {} - last_file = '' - last_line = -1 - - files_to_trace = spec_opts.fetch(:trace, []); - io = spec_opts.fetch(:io, $stdout) - color = spec_opts.fetch(:color, "\e[32m") # ANSI.green default - # target only existing readable files - files_to_trace.each do |f| - targets.push(File.expand_path(f)) if File.exists?(f) && File.readable?(f) - end - return if targets.empty? - - set_trace_func(lambda do |event, file, line, id, binding, classname| - return unless targets.include? file - - # never repeat a line - return if file == last_file && line == last_line - - file_sym = file.intern - files[file_sym] = IO.readlines(file) if files[file_sym].nil? - lines = files[file_sym] - - # arrays are 0 indexed and line numbers start at one. - io.print color if color # ANSI code - io.puts lines[ line - 1] - io.print "\e[0m" if color # ANSI.clear - - last_file = file - last_line = line - - end) + # If we run the parallel tests before the serial tests, the parallel tests + # could run in parallel with the serial tests. This would be bad because + # the serial tests won't lock around Reporter#record. Run the serial tests + # first, so that after they complete, the parallel tests will lock when + # recording results. + serial.map { |suite| suite.run reporter, options } + + parallel.map { |suite| suite.run reporter, options } end - def self.on_exit exit_code - @@after_run.reverse_each(&:call) - exit exit_code || false - end - - ## - # Run specs. Does not print dots (ProgressReporter) - # - # spec_opts - # @param :io [Array<String>] defaults to $stdout - # @param :trace [Array<String>] files to trace - - def self.run_specs spec_opts={} - options = { :io => spec_opts.fetch(:io, $stdout) } - reporter = Minitest::CompositeReporter.new - reporter << Minitest::SummaryReporter.new(options[:io], options) - reporter.start - - at_exit { on_exit reporter.passed? } - # exit on ctrl+c to trigger at_exit - trap('SIGINT') { exit } - - trace_specs spec_opts - - begin - Minitest.__run reporter, options - reporter.reporters.each { |r| r.report } - rescue Minitest::Runnable::ExitAfterFirstFail - # Minitest calls .report on exception - end - end - def self.process_args args = [] # :nodoc: - options = { :io => $stdout } + options = { + :io => $stdout, + } orig_args = args.dup OptionParser.new do |opts| opts.banner = "minitest options:" opts.version = Minitest::VERSION @@ -321,96 +268,33 @@ @@runnables = [] end reset - class ExitAfterFirstFail < RuntimeError; end - - def self.check_failures result, reporter - # skip is not a failure. - true_fails = result.failures.reject { |obj| obj.class == Minitest::Skip } - if !true_fails.empty? - begin - reporter.reporters.each { |r| r.report } - ensure - raise ExitAfterFirstFail - end - end - end - ## # Responsible for running all runnable methods in a given class, # each in its own instance. Each instance is passed to the # reporter to record. def self.run reporter, options = {} - io = options.fetch :io, $stdout filter = options[:filter] || '/./' filter = Regexp.new $1 if filter =~ /\/(.*)\// filtered_methods = self.runnable_methods.find_all { |m| filter === m || filter === "#{self}##{m}" } - begin - # before_first - method1 = self.new(filtered_methods.first) - # run method and capture exceptions. - method1.capture_exceptions do - method1.before_first_method + with_info_handler reporter do + filtered_methods.each do |method_name| + run_one_method self, method_name, reporter end - # save exceptions to reporter and check for failures - with_info_handler reporter do - # only record if failures not empty, otherwise - # the count (runs) are messed up. each call to .record - # increases count by one. - if !method1.failures.empty? - reporter.record method1 - check_failures method1, reporter - end - - # run the other methods - filtered_methods.each do |method_name| - method = self.new(method_name) - matched_name = method_name.match /test_(\d+)_/ - if matched_name - test_number = matched_name[1].to_i - test_name = method_name.split(/_\d+_/).last - file_path, line_number = method.method(method_name).source_location - # /5/4/3/2/1/test.rb => 2/1/test.rb - file_path = file_path.split(File::SEPARATOR).reject(&:empty?) - file_path = (file_path.length >= 3 ? file_path[-3..-1] : - file_path).join(File::SEPARATOR) - # 36 = cyan, 0 = clear - test_output_title = "\e[36m#{test_name} | #{test_number} |" + - "#{file_path}:#{line_number}\e[0m" - io.puts test_output_title - end - result = method.run - raise "#{self}#run _must_ return self" unless self === result - reporter.record result - check_failures result, reporter - end - end - ensure # ensure after last runs - # after_last - # init method1 again - method1 = self.new(filtered_methods.first) - method1.capture_exceptions do - method1.after_last_method - end - with_info_handler reporter do - if !method1.failures.empty? - reporter.record method1 - check_failures method1, reporter - end - end end end - def before_first_method; end - def after_last_method; end + def self.run_one_method klass, method_name, reporter + reporter.record Minitest.run_one_method(klass, method_name) + end def self.with_info_handler reporter, &block # :nodoc: handler = lambda do unless reporter.passed? then warn "Current results:" @@ -505,10 +389,12 @@ ## # Defines the API for Reporters. Subclass this and override whatever # you want. Go nuts. class AbstractReporter + include Mutex_m + ## # Starts reporting on the run. def start end @@ -544,10 +430,11 @@ # Command-line options for this run. attr_accessor :options def initialize io = $stdout, options = {} # :nodoc: + super() self.io = io self.options = options end end @@ -643,10 +530,15 @@ # :startdoc: def start # :nodoc: super + io.puts "Run options: #{options[:args]}" + io.puts + io.puts "# Running:" + io.puts + self.sync = io.respond_to? :"sync=" # stupid emacs self.old_sync, io.sync = io.sync, true if self.sync end def report # :nodoc: @@ -660,11 +552,12 @@ io.puts aggregated_results io.puts summary end def statistics # :nodoc: - "Finished in #{ChronicDuration.output(total_time.round) || '0s'}" + "Finished in %.6fs, %.4f runs/s, %.4f assertions/s." % + [total_time, count / total_time, assertions / total_time] end def aggregated_results # :nodoc: filtered_results = results.dup filtered_results.reject!(&:skipped?) unless options[:verbose] @@ -672,10 +565,12 @@ filtered_results.each_with_index.map do |result, i| "\n%3d) %s" % [i+1, result] end.join("\n") + "\n" end + alias to_s aggregated_results + def summary # :nodoc: extra = "" extra = "\n\nYou have skipped tests. Run with --verbose for details." if results.any?(&:skipped?) unless options[:verbose] or ENV["MT_NO_SKIP_MSG"] @@ -693,10 +588,11 @@ # The list of reporters to dispatch to. attr_accessor :reporters def initialize *reporters # :nodoc: + super() self.reporters = reporters end ## # Add another reporter to the mix. @@ -857,9 +753,15 @@ new_bt end end self.backtrace_filter = BacktraceFilter.new + + def self.run_one_method klass, method_name # :nodoc: + result = klass.new(method_name).run + raise "#{klass}#run _must_ return self" unless klass === result + result + end end require "minitest/test" require "minitest/unit" unless defined?(MiniTest) # compatibility layer only