2017-07-25 15:01:34 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-06-25 13:08:26 -04:00
|
|
|
require "shellwords"
|
|
|
|
require "method_source"
|
|
|
|
require "rake/file_list"
|
Disable parallel testing when running individual files
Setting up the parallel workers could be an overhead when running
individual files.
This patch disables that process in case the number of files to run
is less than one.
Results running a sample file:
Before:
```
actionpack $ bin/test test/controller/parameters/accessors_test.rb
Run options: --seed 48261
........................................................................
Finished in 0.211923s, 339.7460 runs/s, 552.0873 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips
```
After
```
actionpack $ bin/test test/controller/parameters/accessors_test.rb
Run options: --seed 5461
........................................................................
Finished in 0.008411s, 8560.2189 runs/s, 13910.3557 assertions/s.
72 runs, 117 assertions, 0 failures, 0 errors, 0 skips
```
2021-02-24 17:42:34 -05:00
|
|
|
require "active_support"
|
2017-06-25 13:08:26 -04:00
|
|
|
require "active_support/core_ext/module/attribute_accessors"
|
|
|
|
|
|
|
|
module Rails
|
|
|
|
module TestUnit
|
|
|
|
class Runner
|
|
|
|
mattr_reader :filters, default: []
|
|
|
|
|
|
|
|
class << self
|
Show minitest options in test runner help
Since #29572, minitest options are available but are no longer showed
in help.
This fixed to show minitest option in help as with Rails 5.1.2.
**before**
```
./bin/rails t --help
You can run a single test by appending a line number to a filename:
bin/rails test test/models/user_test.rb:27
You can run multiple files and directories at the same time:
bin/rails test test/controllers test/integration/login_test.rb
By default test failures and errors are reported inline during a run.
Rails options:
-w, --warnings Run with Ruby warnings enabled
-e, --environment Run tests in the ENV environment
-b, --backtrace Show the complete backtrace
-d, --defer-output Output test failures and errors after the test run
-f, --fail-fast Abort test run on first failure or error
-c, --[no-]color Enable color in the output
```
**after**
```
./bin/rails t --help
You can run a single test by appending a line number to a filename:
bin/rails test test/models/user_test.rb:27
You can run multiple files and directories at the same time:
bin/rails test test/controllers test/integration/login_test.rb
By default test failures and errors are reported inline during a run.
minitest options:
-h, --help Display this help.
-s, --seed SEED Sets random seed. Also via env. Eg: SEED=n rake
-v, --verbose Verbose. Show progress processing files.
-n, --name PATTERN Filter run on /regexp/ or string.
--exclude PATTERN Exclude /regexp/ or string from run.
Known extensions: rails, pride
-w, --warnings Run with Ruby warnings enabled
-e, --environment Run tests in the ENV environment
-b, --backtrace Show the complete backtrace
-d, --defer-output Output test failures and errors after the test run
-f, --fail-fast Abort test run on first failure or error
-c, --[no-]color Enable color in the output
-p, --pride Pride. Show your testing pride!
```
2017-07-29 19:21:22 -04:00
|
|
|
def attach_before_load_options(opts)
|
2018-09-25 13:18:20 -04:00
|
|
|
opts.on("--warnings", "-w", "Run with Ruby warnings enabled") { }
|
|
|
|
opts.on("-e", "--environment ENV", "Run tests in the ENV environment") { }
|
2017-06-25 13:08:26 -04:00
|
|
|
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 = [])
|
2021-01-15 15:52:32 -05:00
|
|
|
# Ensure the tests run during the Rake Task action, not when the process exits
|
|
|
|
success = system("rails", "test", *argv, *Shellwords.split(ENV["TESTOPTS"] || ""))
|
|
|
|
success || exit(false)
|
2017-06-25 13:08:26 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def run(argv = [])
|
|
|
|
load_tests(argv)
|
|
|
|
|
|
|
|
require "active_support/testing/autorun"
|
|
|
|
end
|
|
|
|
|
|
|
|
def load_tests(argv)
|
|
|
|
patterns = extract_filters(argv)
|
|
|
|
|
2020-05-03 09:08:49 -04:00
|
|
|
tests = Rake::FileList[patterns.any? ? patterns : default_test_glob]
|
|
|
|
tests.exclude(default_test_exclude_glob) if patterns.empty?
|
2017-06-25 13:08:26 -04:00
|
|
|
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)
|
2017-07-25 15:01:34 -04:00
|
|
|
# Extract absolute and relative paths but skip -n /.*/ regexp filters.
|
2021-04-26 17:55:33 -04:00
|
|
|
argv.filter_map do |path|
|
|
|
|
next unless path_argument?(path) && !regexp_filter?(path)
|
|
|
|
|
2020-04-21 01:46:49 -04:00
|
|
|
path = path.tr("\\", "/")
|
2017-06-25 13:08:26 -04:00
|
|
|
case
|
2018-07-28 17:37:17 -04:00
|
|
|
when /(:\d+)+$/.match?(path)
|
2017-06-25 13:08:26 -04:00
|
|
|
file, *lines = path.split(":")
|
|
|
|
filters << [ file, lines ]
|
|
|
|
file
|
|
|
|
when Dir.exist?(path)
|
|
|
|
"#{path}/**/*_test.rb"
|
|
|
|
else
|
|
|
|
filters << [ path, [] ]
|
|
|
|
path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2020-03-24 20:54:12 -04:00
|
|
|
|
2020-05-03 09:08:49 -04:00
|
|
|
def default_test_glob
|
|
|
|
ENV["DEFAULT_TEST"] || "test/**/*_test.rb"
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_test_exclude_glob
|
|
|
|
ENV["DEFAULT_TEST_EXCLUDE"] || "test/{system,dummy}/**/*_test.rb"
|
|
|
|
end
|
|
|
|
|
2020-03-24 20:54:12 -04:00
|
|
|
def regexp_filter?(arg)
|
|
|
|
arg.start_with?("/") && arg.end_with?("/")
|
|
|
|
end
|
|
|
|
|
|
|
|
def path_argument?(arg)
|
2020-04-21 01:46:49 -04:00
|
|
|
%r"^[/\\]?\w+[/\\]".match?(arg)
|
2020-03-24 20:54:12 -04:00
|
|
|
end
|
2017-06-25 13:08:26 -04:00
|
|
|
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
|
|
|
|
|
2018-09-11 17:52:27 -04:00
|
|
|
# minitest uses === to find matching filters.
|
2017-06-25 13:08:26 -04:00
|
|
|
def ===(method)
|
|
|
|
@filters.any? { |filter| filter === method }
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def derive_named_filter(filter)
|
|
|
|
if filter.respond_to?(:named_filter)
|
|
|
|
filter.named_filter
|
2018-09-11 17:52:27 -04:00
|
|
|
elsif filter =~ %r%/(.*)/% # Regexp filtering copied from minitest.
|
2017-06-25 13:08:26 -04:00
|
|
|
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
|