discourse/lib/turbo_tests/runner.rb
Osama Sayegh 45ccadeeeb
DEV: Upgrade Rails to 6.1.3.1 (#12688)
Rails 6.1.3.1 deprecates a few API and has some internal changes that break our tests suite, so this commit fixes all the deprecations and errors and now Discourse should be fully compatible with Rails 6.1.3.1. We also have a new release of the rails_failover gem that's compatible with Rails 6.1.3.1.
2021-04-21 12:36:32 +03:00

269 lines
6.4 KiB
Ruby

# frozen_string_literal: true
module TurboTests
class Runner
def self.run(opts = {})
files = opts[:files]
formatters = opts[:formatters]
start_time = opts.fetch(:start_time) { Time.now }
verbose = opts.fetch(:verbose, false)
fail_fast = opts.fetch(:fail_fast, nil)
if verbose
STDERR.puts "VERBOSE"
end
reporter = Reporter.from_config(formatters, start_time)
new(
reporter: reporter,
files: files,
verbose: verbose,
fail_fast: fail_fast
).run
end
def initialize(opts)
@reporter = opts[:reporter]
@files = opts[:files]
@verbose = opts[:verbose]
@fail_fast = opts[:fail_fast]
@failure_count = 0
@messages = Queue.new
@threads = []
@error = false
end
def run
check_for_migrations
@num_processes = ParallelTests.determine_number_of_processes(nil)
use_runtime_info = @files == ['spec']
group_opts = {}
if use_runtime_info
group_opts[:runtime_log] = "tmp/turbo_rspec_runtime.log"
else
group_opts[:group_by] = :filesize
end
tests_in_groups =
ParallelTests::RSpec::Runner.tests_in_groups(
@files,
@num_processes,
**group_opts,
)
setup_tmp_dir
subprocess_opts = {
record_runtime: use_runtime_info
}
start_multisite_subprocess(@files, **subprocess_opts)
tests_in_groups.each_with_index do |tests, process_id|
start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
end
handle_messages
@reporter.finish
@threads.each(&:join)
@reporter.failed_examples.empty? && !@error
end
protected
def check_for_migrations
config =
ActiveRecord::Base
.configurations
.find_db_config("test")
.configuration_hash
.merge("database" => "discourse_test_1")
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = ['db/migrate', 'db/post_migrate']
conn = ActiveRecord::Base.establish_connection(config).connection
begin
ActiveRecord::Migration.check_pending!(conn)
rescue ActiveRecord::PendingMigrationError
puts "There are pending migrations, run rake parallel:migrate"
exit 1
ensure
conn.close
end
end
def setup_tmp_dir
begin
FileUtils.rm_r('tmp/test-pipes')
rescue Errno::ENOENT
end
FileUtils.mkdir_p('tmp/test-pipes/')
end
def start_multisite_subprocess(tests, **opts)
start_subprocess(
{},
["--tag", "type:multisite"],
tests,
"multisite",
**opts
)
end
def start_regular_subprocess(tests, process_id, **opts)
start_subprocess(
{ 'TEST_ENV_NUMBER' => process_id.to_s },
["--tag", "~type:multisite"],
tests,
process_id,
**opts
)
end
def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
if tests.empty?
@messages << {
type: 'exit',
process_id: process_id
}
else
tmp_filename = "tmp/test-pipes/subprocess-#{process_id}"
begin
File.mkfifo(tmp_filename)
rescue Errno::EEXIST
end
env['RSPEC_SILENCE_FILTER_ANNOUNCEMENTS'] = '1'
record_runtime_options =
if record_runtime
[
"--format", "ParallelTests::RSpec::RuntimeLogger",
"--out", "tmp/turbo_rspec_runtime.log",
]
else
[]
end
command = [
"bundle", "exec", "rspec",
*extra_args,
"--seed", rand(2**16).to_s,
"--format", "TurboTests::JsonRowsFormatter",
"--out", tmp_filename,
*record_runtime_options,
*tests
]
if @verbose
command_str = [
env.map { |k, v| "#{k}=#{v}" }.join(' '),
command.join(' ')
].select { |x| x.size > 0 }.join(' ')
STDERR.puts "Process #{process_id}: #{command_str}"
end
stdin, stdout, stderr, wait_thr = Open3.popen3(env, *command)
stdin.close
@threads <<
Thread.new do
File.open(tmp_filename) do |fd|
fd.each_line do |line|
message = JSON.parse(line)
message = message.symbolize_keys
message[:process_id] = process_id
@messages << message
end
end
@messages << { type: 'exit', process_id: process_id }
end
@threads << start_copy_thread(stdout, STDOUT)
@threads << start_copy_thread(stderr, STDERR)
@threads << Thread.new do
if wait_thr.value.exitstatus != 0
@messages << { type: 'error' }
end
end
end
end
def start_copy_thread(src, dst)
Thread.new do
while true
begin
msg = src.readpartial(4096)
rescue EOFError
src.close
break
else
dst.write(msg)
end
end
end
end
def handle_messages
exited = 0
begin
while true
message = @messages.pop
case message[:type]
when 'example_passed'
example = FakeExample.from_obj(message[:example])
@reporter.example_passed(example)
when 'example_pending'
example = FakeExample.from_obj(message[:example])
@reporter.example_pending(example)
when 'example_failed'
example = FakeExample.from_obj(message[:example])
@reporter.example_failed(example)
@failure_count += 1
if fail_fast_met
@threads.each(&:kill)
break
end
when 'message'
@reporter.message(message[:message])
when 'seed'
when 'close'
when 'error'
@reporter.error_outside_of_examples
@error = true
when 'exit'
exited += 1
if exited == @num_processes + 1
break
end
else
STDERR.puts("Unhandled message in main process: #{message}")
end
STDOUT.flush
end
rescue Interrupt
end
end
def fail_fast_met
!@fail_fast.nil? && @fail_fast >= @failure_count
end
end
end