提交 0d72489b 编写于 作者: K Kasper Timm Hansen

* Don't eagerly require Rails' minitest plugin.

By making the Rails minitest behave like a standard minitest plugin
we're much more likely to not break when people use other minitest
plugins. Like minitest-focus and pride.

To do this, we need to behave like minitest: require files up front
and then perform the plugin behavior via the at_exit hook.
This also saves us a fair bit of wrangling with test file loading.

Finally, since the environment and warnings options have to be applied
as early as possible, and since minitest loads plugins at_exit, they
have to be moved to the test command.

* Don't expect the root method.

It's likely this worked because we eagerly loaded the Rails minitest plugin
and that somehow defined a root method on `Rails`.

* Assign a backtrace to failed exceptions.

Otherwise Minitest pukes when attempting to filter the backtrace (which
Rails' backtrace cleaner then removes).

Means the exception message test has to be revised too.

This is likely caused by the rails minitest plugin now being loaded for
these tests and assigning a default backtrace cleaner.
上级 b6300f3e
......@@ -260,7 +260,7 @@ def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, e
def check_schema_file(filename)
unless File.exist?(filename)
message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}
message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails)
message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root)
Kernel.abort message
end
end
......
......@@ -3,8 +3,4 @@
require "minitest"
if Minitest.respond_to?(:run_via) && !Minitest.run_via.set?
Minitest.run_via = :ruby
end
Minitest.autorun
* Make Rails' test runner work better with minitest plugins.
By demoting the Rails test runner to just another minitest plugin —
and thereby not eager loading it — we can co-exist much better with
other minitest plugins such as pride and minitest-focus.
*Kasper Timm Hansen*
* Load environment file in `dbconsole` command.
Fixes #29717
......
require "active_support/core_ext/module/attribute_accessors"
require "rails/test_unit/reporter"
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)
opts.on("-b", "--backtrace", "Show the complete backtrace") do
options[:full_backtrace] = true
end
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
end
# Owes great inspiration to test runner trailblazers like RSpec,
# minitest-reporters, maxitest and others.
def self.plugin_rails_init(options)
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
# 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
# Backwardscompatibility with Rails 5.0 generated plugin test scripts
mattr_reader :run_via, default: {}
end
require_relative "../../command"
require_relative "../../test_unit/minitest_plugin"
require_relative "../../test_unit/runner"
module Rails
module Command
class TestCommand < Base # :nodoc:
no_commands do
def help
perform # Hand over help printing to minitest.
require "optparse"
require "minitest/rails_plugin"
opts = OptionParser.new
opts.banner = "Usage: #{Rails::TestUnitReporter.executable} [options] [files or directories]"
opts.separator ""
opts.separator "You can run a single test by appending a line number to a filename:"
opts.separator ""
opts.separator " #{Rails::TestUnitReporter.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 " #{Rails::TestUnitReporter.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:"
Rails::TestUnit::Runner.options(opts)
Minitest.plugin_rails_options(opts, {})
say opts
end
end
def perform(*)
$LOAD_PATH << Rails::Command.root.join("test").to_s
Minitest.run_via = :rails
require "active_support/testing/autorun"
Rails::TestUnit::Runner.parse_options(ARGV)
Rails::TestUnit::Runner.run(ARGV)
end
end
end
......
require_relative "../test_unit/minitest_plugin"
require_relative "../test_unit/runner"
require_relative "../test_unit/reporter"
Rails::TestUnitReporter.executable = "bin/test"
Minitest.run_via = :rails
require "active_support/testing/autorun"
Rails::TestUnit::Runner.parse_options(ARGV)
Rails::TestUnit::Runner.run(ARGV)
......@@ -2,7 +2,6 @@
# so fixtures aren't loaded into that environment
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
require_relative "test_unit/minitest_plugin"
require "active_support/test_case"
require "action_controller"
require "action_controller/test_case"
......
require "method_source"
require "rails/test_unit/runner"
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
options[:filter] = Rails::TestUnit::Runner.compose_filter(self, options[:filter])
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
require "active_support/core_ext/module/attribute_accessors"
require_relative "reporter"
require_relative "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: #{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 " #{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 " #{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",
"Run tests in the ENV environment") do |env|
options[:environment] = env.strip
end
opts.on("-b", "--backtrace",
"Show the complete backtrace") do
options[:full_backtrace] = true
end
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
opts.on("-w", "--warnings",
"Enable ruby warnings") do
$VERBOSE = true
end
options[:color] = true
options[:output_inline] = true
options[:patterns] = opts.order! unless run_via.rake?
end
def self.rake_run(patterns, exclude_patterns = []) # :nodoc:
self.run_via = :rake unless run_via.set?
::Rails::TestRequirer.require_files(patterns, exclude_patterns)
autorun
end
module RunRespectingRakeTestopts
def run(args = [])
if run_via.rake?
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"
# If run via `ruby` we've been passed the files to run directly, or if run
# via `rake` then they have already been eagerly required.
unless run_via.ruby? || run_via.rake?
# If there are no given patterns, we can assume that the user
# simply runs the `bin/rails test` command without extra arguments.
if options[:patterns].empty?
::Rails::TestRequirer.require_files(options[:patterns], ["test/system/**/*"])
else
::Rails::TestRequirer.require_files(options[:patterns])
end
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
# 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
def self.run_via=(runner)
if run_via.set?
raise ArgumentError, "run_via already assigned"
else
run_via.runner = runner
end
end
class RunVia
attr_accessor :runner
alias set? runner
# Backwardscompatibility with Rails 5.0 generated plugin test scripts.
def []=(runner, *)
@runner = runner
end
def ruby?
runner == :ruby
end
def rake?
runner == :rake
end
end
mattr_reader :run_via, default: RunVia.new
end
# 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"
require "shellwords"
require "method_source"
require "rake/file_list"
require "active_support/core_ext/module/attribute_accessors"
module Rails
module TestUnit
class Runner
mattr_reader :filters, default: []
class << self
def options(opts)
opts.on("--warnings", "-w", "Run with Ruby warnings enabled") {}
opts.on("--environment", "-e", "Run tests in the ENV environment") {}
end
def parse_options(argv)
# Perform manual parsing and cleanup since option parser raises on unknown options.
env_index = argv.index("--environment") || argv.index("-e")
if env_index
argv.delete_at(env_index)
environment = argv.delete_at(env_index).strip
end
ENV["RAILS_ENV"] = environment || "test"
w_index = argv.index("--warnings") || argv.index("-w")
$VERBOSE = argv.delete_at(w_index) if w_index
end
def rake_run(argv = [])
ARGV.replace Shellwords.split(ENV["TESTOPTS"] || "")
run(argv)
end
def run(argv = [])
load_tests(argv)
require "active_support/testing/autorun"
end
def load_tests(argv)
patterns = extract_filters(argv)
tests = Rake::FileList[patterns.any? ? patterns : "test/**/*_test.rb"]
tests.exclude("test/system/**/*") if patterns.empty?
tests.to_a.each { |path| require File.expand_path(path) }
end
def compose_filter(runnable, filter)
if filters.any? { |_, lines| lines.any? }
CompositeFilter.new(runnable, filter, filters)
else
filter
end
end
private
def extract_filters(argv)
argv.select { |arg| arg =~ /^\w+\// }.map do |path|
case
when path =~ /(:\d+)+$/
file, *lines = path.split(":")
filters << [ file, lines ]
file
when Dir.exist?(path)
"#{path}/**/*_test.rb"
else
filters << [ path, [] ]
path
end
end
end
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, lines|
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
end
require "active_support/core_ext/object/blank"
require "rake/file_list"
module Rails
class TestRequirer # :nodoc:
class << self
def require_files(patterns, exclude_patterns = [])
patterns = expand_patterns(patterns)
file_list = Rake::FileList[patterns.compact.presence || "test/**/*_test.rb"]
file_list.exclude(exclude_patterns)
file_list.to_a.each do |file|
require File.expand_path(file)
end
end
private
def expand_patterns(patterns)
patterns.map do |arg|
arg = arg.gsub(/(:\d+)+?$/, "")
if Dir.exist?(arg)
"#{arg}/**/*_test.rb"
else
arg
end
end
end
end
end
end
gem "minitest"
require "minitest"
require_relative "minitest_plugin"
require_relative "runner"
task default: :test
......@@ -9,9 +9,9 @@ task :test do
$: << "test"
if ENV.key?("TEST")
Minitest.rake_run([ENV["TEST"]])
Rails::TestUnit::Runner.rake_run([ENV["TEST"]])
else
Minitest.rake_run(["test"], ["test/system/**/*"])
Rails::TestUnit::Runner.rake_run
end
end
......@@ -29,28 +29,28 @@ namespace :test do
["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name|
task name => "test:prepare" do
$: << "test"
Minitest.rake_run(["test/#{name}"])
Rails::TestUnit::Runner.rake_run(["test/#{name}"])
end
end
task generators: "test:prepare" do
$: << "test"
Minitest.rake_run(["test/lib/generators"])
Rails::TestUnit::Runner.rake_run(["test/lib/generators"])
end
task units: "test:prepare" do
$: << "test"
Minitest.rake_run(["test/models", "test/helpers", "test/unit"])
Rails::TestUnit::Runner.rake_run(["test/models", "test/helpers", "test/unit"])
end
task functionals: "test:prepare" do
$: << "test"
Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"])
Rails::TestUnit::Runner.rake_run(["test/controllers", "test/mailers", "test/functional"])
end
desc "Run system tests only"
task system: "test:prepare" do
$: << "test"
Minitest.rake_run(["test/system"])
Rails::TestUnit::Runner.rake_run(["test/system"])
end
end
......@@ -16,13 +16,13 @@ def teardown
end
def test_run_via_backwardscompatibility
require "rails/test_unit/minitest_plugin"
require "minitest/rails_plugin"
assert_nothing_raised do
Minitest.run_via[:ruby] = true
end
assert_predicate Minitest.run_via, :ruby?
assert Minitest.run_via[:ruby]
end
def test_run_single_file
......
......@@ -70,7 +70,7 @@ def woot; end
@reporter.record(errored_test)
@reporter.report
expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\nbin/rails test .*test/test_unit/reporter_test\.rb:\d+\n\n\z}
expect = %r{\AE\n\nError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n \n\nbin/rails test .*test/test_unit/reporter_test\.rb:\d+\n\n\z}
assert_match expect, @output.string
end
......@@ -150,7 +150,7 @@ def woot; end
colored = Rails::TestUnitReporter.new @output, color: true, output_inline: true
colored.record(errored_test)
expected = %r{\e\[31mE\e\[0m\n\n\e\[31mError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n No backtrace\n\e\[0m}
expected = %r{\e\[31mE\e\[0m\n\n\e\[31mError:\nTestUnitReporterTest::ExampleTest#woot:\nArgumentError: wups\n \n\e\[0m}
assert_match expected, @output.string
end
end
......@@ -171,8 +171,11 @@ def failed_test
end
def errored_test
error = ArgumentError.new("wups")
error.set_backtrace([ "some_test.rb:4" ])
et = ExampleTest.new(:woot)
et.failures << Minitest::UnexpectedError.new(ArgumentError.new("wups"))
et.failures << Minitest::UnexpectedError.new(error)
et
end
......
......@@ -3,8 +3,10 @@
require "bundler"
Bundler.setup
require "rails/test_unit/minitest_plugin"
require "rails/test_unit/runner"
require "rails/test_unit/reporter"
require "rails/test_unit/line_filtering"
require "active_support"
require "active_support/test_case"
class << Rails
......@@ -17,5 +19,6 @@ def root
ActiveSupport::TestCase.extend Rails::LineFiltering
Rails::TestUnitReporter.executable = "bin/test"
Minitest.run_via = :rails
require "active_support/testing/autorun"
Rails::TestUnit::Runner.parse_options(ARGV)
Rails::TestUnit::Runner.run(ARGV)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册