aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails/test_unit
diff options
context:
space:
mode:
Diffstat (limited to 'railties/lib/rails/test_unit')
-rw-r--r--railties/lib/rails/test_unit/line_filtering.rb78
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb81
-rw-r--r--railties/lib/rails/test_unit/railtie.rb10
-rw-r--r--railties/lib/rails/test_unit/reporter.rb74
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb10
-rw-r--r--railties/lib/rails/test_unit/testing.rake31
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