diff options
Diffstat (limited to 'railties/lib/rails/test_unit')
-rw-r--r-- | railties/lib/rails/test_unit/line_filtering.rb | 78 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/minitest_plugin.rb | 81 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/railtie.rb | 10 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/reporter.rb | 74 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/test_requirer.rb | 10 | ||||
-rw-r--r-- | railties/lib/rails/test_unit/testing.rake | 31 |
6 files changed, 247 insertions, 37 deletions
diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb new file mode 100644 index 0000000000..32ba744701 --- /dev/null +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -0,0 +1,78 @@ +require "method_source" + +module Rails + module LineFiltering # :nodoc: + def run(reporter, options = {}) + if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ } + options[:filter] = \ + CompositeFilter.new(self, options[:filter], options[:patterns]) + end + + super + end + end + + class CompositeFilter # :nodoc: + attr_reader :named_filter + + def initialize(runnable, filter, patterns) + @runnable = runnable + @named_filter = derive_named_filter(filter) + @filters = [ @named_filter, *derive_line_filters(patterns) ].compact + end + + # Minitest uses === to find matching filters. + def ===(method) + @filters.any? { |filter| filter === method } + end + + private + def derive_named_filter(filter) + if filter.respond_to?(:named_filter) + filter.named_filter + elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest. + Regexp.new $1 + elsif filter.is_a?(String) + filter + end + end + + def derive_line_filters(patterns) + patterns.flat_map do |file_and_line| + file, *lines = file_and_line.split(":") + + if lines.empty? + Filter.new(@runnable, file, nil) if file + else + lines.map { |line| Filter.new(@runnable, file, line) } + end + end + end + end + + class Filter # :nodoc: + def initialize(runnable, file, line) + @runnable, @file = runnable, File.expand_path(file) + @line = line.to_i if line + end + + def ===(method) + return unless @runnable.method_defined?(method) + + if @line + test_file, test_range = definition_for(@runnable.instance_method(method)) + test_file == @file && test_range.include?(@line) + else + @runnable.instance_method(method).source_location.first == @file + end + end + + private + def definition_for(method) + file, start_line = method.source_location + end_line = method.source.count("\n") + start_line - 1 + + return file, start_line..end_line + end + end +end diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index ab71298509..6e196a32ab 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -1,22 +1,33 @@ require "active_support/core_ext/module/attribute_accessors" require "rails/test_unit/reporter" require "rails/test_unit/test_requirer" +require "shellwords" module Minitest + class SuppressedSummaryReporter < SummaryReporter + # Disable extra failure output after a run if output is inline. + def aggregated_results + super unless options[:output_inline] + end + end + def self.plugin_rails_options(opts, options) + executable = ::Rails::TestUnitReporter.executable opts.separator "" - opts.separator "Usage: bin/rails test [options] [files or directories]" + opts.separator "Usage: #{executable} [options] [files or directories]" opts.separator "You can run a single test by appending a line number to a filename:" opts.separator "" - opts.separator " bin/rails test test/models/user_test.rb:27" + opts.separator " #{executable} test/models/user_test.rb:27" opts.separator "" opts.separator "You can run multiple files and directories at the same time:" opts.separator "" - opts.separator " bin/rails test test/controllers test/integration/login_test.rb" + opts.separator " #{executable} test/controllers test/integration/login_test.rb" + opts.separator "" + opts.separator "By default test failures and errors are reported inline during a run." opts.separator "" opts.separator "Rails options:" - opts.on("-e", "--environment [ENV]", + opts.on("-e", "--environment ENV", "Run tests in the ENV environment") do |env| options[:environment] = env.strip end @@ -26,26 +37,72 @@ module Minitest options[:full_backtrace] = true end - options[:patterns] = opts.order! + opts.on("-d", "--defer-output", + "Output test failures and errors after the test run") do + options[:output_inline] = false + end + + opts.on("-f", "--fail-fast", + "Abort test run on first failure or error") do + options[:fail_fast] = true + end + + opts.on("-c", "--[no-]color", + "Enable color in the output") do |value| + options[:color] = value + end + + options[:color] = true + options[:output_inline] = true + options[:patterns] = defined?(@rake_patterns) ? @rake_patterns : opts.order! end - def self.plugin_rails_init(options) - self.run_with_rails_extension = true + # Running several Rake tasks in a single command would trip up the runner, + # as the patterns would also contain the other Rake tasks. + def self.rake_run(patterns) # :nodoc: + @rake_patterns = patterns + autorun + end + + module RunRespectingRakeTestopts + def run(args = []) + if defined?(@rake_patterns) + args = Shellwords.split(ENV["TESTOPTS"] || "") + end + super + end + end + + singleton_class.prepend RunRespectingRakeTestopts + + # Owes great inspiration to test runner trailblazers like RSpec, + # minitest-reporters, maxitest and others. + def self.plugin_rails_init(options) ENV["RAILS_ENV"] = options[:environment] || "test" - ::Rails::TestRequirer.require_files options[:patterns] unless run_with_autorun + # If run via `ruby` we've been passed the files to run directly. + unless run_via[:ruby] + ::Rails::TestRequirer.require_files(options[:patterns]) + end unless options[:full_backtrace] || ENV["BACKTRACE"] # Plugin can run without Rails loaded, check before filtering. Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner) end - self.reporter << ::Rails::TestUnitReporter.new(options[:io], options) + # Replace progress reporter for colors. + reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } + reporter << SuppressedSummaryReporter.new(options[:io], options) + reporter << ::Rails::TestUnitReporter.new(options[:io], options) end - mattr_accessor(:run_with_autorun) { false } - mattr_accessor(:run_with_rails_extension) { false } + mattr_accessor(:run_via) { Hash.new } end -Minitest.extensions << 'rails' +# Put Rails as the first plugin minitest initializes so other plugins +# can override or replace our default reporter setup. +# Since minitest only loads plugins if its extensions are empty we have +# to call `load_plugins` first. +Minitest.load_plugins +Minitest.extensions.unshift "rails" diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index 75180ff978..746120e6a1 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,5 +1,7 @@ +require "rails/test_unit/line_filtering" + if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? - ENV['RAILS_ENV'] ||= 'test' + ENV["RAILS_ENV"] ||= "test" end module Rails @@ -11,6 +13,12 @@ module Rails c.integration_tool :test_unit end + initializer "test_unit.line_filtering" do + ActiveSupport.on_load(:active_support_test_case) { + ActiveSupport::TestCase.extend Rails::LineFiltering + } + end + rake_tasks do load "rails/test_unit/testing.rake" end diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index 09b8675cf8..fe11664d5e 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -6,8 +6,31 @@ module Rails class_attribute :executable self.executable = "bin/rails test" + def record(result) + super + + if options[:verbose] + io.puts color_output(format_line(result), by: result) + else + io.print color_output(result.result_code, by: result) + end + + if output_inline? && result.failure && (!result.skipped? || options[:verbose]) + io.puts + io.puts + io.puts color_output(result, by: result) + io.puts + io.puts format_rerun_snippet(result) + io.puts + end + + if fail_fast? && result.failure && !result.skipped? + raise Interrupt + end + end + def report - return if filtered_results.empty? + return if output_inline? || filtered_results.empty? io.puts io.puts "Failed tests:" io.puts @@ -15,10 +38,7 @@ module Rails end def aggregated_results # :nodoc: - filtered_results.map do |result| - location, line = result.method(result.name).source_location - "#{self.executable} #{relative_path_for(location)}:#{line}" - end.join "\n" + filtered_results.map { |result| format_rerun_snippet(result) }.join "\n" end def filtered_results @@ -30,7 +50,49 @@ module Rails end def relative_path_for(file) - file.sub(/^#{Rails.root}\/?/, '') + file.sub(/^#{app_root}\/?/, "") end + + private + def output_inline? + options[:output_inline] + end + + def fail_fast? + options[:fail_fast] + end + + def format_line(result) + "%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code] + end + + def format_rerun_snippet(result) + location, line = result.method(result.name).source_location + "#{executable} #{relative_path_for(location)}:#{line}" + end + + def app_root + @app_root ||= defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + end + + def colored_output? + options[:color] && io.respond_to?(:tty?) && io.tty? + end + + codes = { red: 31, green: 32, yellow: 33 } + COLOR_BY_RESULT_CODE = { + "." => codes[:green], + "E" => codes[:red], + "F" => codes[:red], + "S" => codes[:yellow] + } + + def color_output(string, by:) + if colored_output? + "\e[#{COLOR_BY_RESULT_CODE[by.result_code]}m#{string}\e[0m" + else + string + end + end end end diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb index 84c2256729..fe35934abc 100644 --- a/railties/lib/rails/test_unit/test_requirer.rb +++ b/railties/lib/rails/test_unit/test_requirer.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/object/blank' -require 'rake/file_list' +require "active_support/core_ext/object/blank" +require "rake/file_list" module Rails class TestRequirer # :nodoc: @@ -7,7 +7,7 @@ module Rails def require_files(patterns) patterns = expand_patterns(patterns) - Rake::FileList[patterns.compact.presence || 'test/**/*_test.rb'].to_a.each do |file| + Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"].to_a.each do |file| require File.expand_path(file) end end @@ -15,10 +15,10 @@ module Rails private def expand_patterns(patterns) patterns.map do |arg| - arg = arg.gsub(/:(\d+)?$/, '') + arg = arg.gsub(/(:\d+)+?$/, "") if Dir.exist?(arg) "#{arg}/**/*_test.rb" - elsif File.file?(arg) + else arg end end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index dda492f974..4c157c1262 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,13 +1,18 @@ -gem 'minitest' -require 'minitest' -require 'rails/test_unit/minitest_plugin' +gem "minitest" +require "minitest" +require "rails/test_unit/minitest_plugin" task default: :test desc "Runs all tests in test folder" task :test do $: << "test" - Minitest.run(['test']) + pattern = if ENV.key?("TEST") + ENV["TEST"] + else + "test" + end + Minitest.rake_run([pattern]) end namespace :test do @@ -16,30 +21,30 @@ namespace :test do # If used with Active Record, this task runs before the database schema is synchronized. end - task :run => %w[test] + task run: %w[test] desc "Run tests quickly, but also reset db" - task :db => %w[db:test:prepare test] + task db: %w[db:test:prepare test] ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| task name => "test:prepare" do $: << "test" - Minitest.run(["test/#{name}"]) + Minitest.rake_run(["test/#{name}"]) end end - task :generators => "test:prepare" do + task generators: "test:prepare" do $: << "test" - Minitest.run(["test/lib/generators"]) + Minitest.rake_run(["test/lib/generators"]) end - task :units => "test:prepare" do + task units: "test:prepare" do $: << "test" - Minitest.run(["test/models", "test/helpers", "test/unit"]) + Minitest.rake_run(["test/models", "test/helpers", "test/unit"]) end - task :functionals => "test:prepare" do + task functionals: "test:prepare" do $: << "test" - Minitest.run(["test/controllers", "test/mailers", "test/functional"]) + Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"]) end end |