mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Retry hung tests after parallel runs
This commit is contained in:
parent
478187e9a3
commit
13716898df
Notes:
git
2021-10-17 16:34:19 +09:00
4 changed files with 86 additions and 16 deletions
|
@ -343,6 +343,7 @@ module Test
|
|||
attr_reader :quit_called
|
||||
attr_accessor :start_time
|
||||
attr_accessor :response_at
|
||||
attr_accessor :current
|
||||
|
||||
@@worker_number = 0
|
||||
|
||||
|
@ -526,7 +527,7 @@ module Test
|
|||
|
||||
def quit_workers(&cond)
|
||||
return if @workers.empty?
|
||||
closed = []
|
||||
closed = [] if cond
|
||||
@workers.reject! do |worker|
|
||||
next unless cond&.call(worker)
|
||||
begin
|
||||
|
@ -536,22 +537,33 @@ module Test
|
|||
rescue Errno::EPIPE
|
||||
rescue Timeout::Error
|
||||
end
|
||||
closed << worker
|
||||
worker.close
|
||||
closed&.push worker
|
||||
begin
|
||||
Timeout.timeout(0.2) do
|
||||
worker.close
|
||||
end
|
||||
rescue Timeout::Error
|
||||
worker.kill
|
||||
retry
|
||||
end
|
||||
@ios.delete worker.io
|
||||
end
|
||||
|
||||
return closed if cond
|
||||
return if @workers.empty?
|
||||
return if (closed ||= @workers).empty?
|
||||
pids = closed.map(&:pid)
|
||||
begin
|
||||
Timeout.timeout(0.2 * @workers.size) do
|
||||
Timeout.timeout(0.2 * closed.size) do
|
||||
Process.waitall
|
||||
end
|
||||
rescue Timeout::Error
|
||||
@workers.each do |worker|
|
||||
worker.kill
|
||||
if pids
|
||||
Process.kill(:KILL, *pids) rescue nil
|
||||
pids = nil
|
||||
retry
|
||||
end
|
||||
@workers.clear
|
||||
end
|
||||
@workers.clear unless cond
|
||||
closed
|
||||
end
|
||||
|
||||
FakeClass = Struct.new(:name)
|
||||
|
@ -585,6 +597,8 @@ module Test
|
|||
@test_count += 1
|
||||
|
||||
jobs_status(worker)
|
||||
when /^start (.+?)$/
|
||||
worker.current = Marshal.load($1.unpack1("m"))
|
||||
when /^done (.+?)$/
|
||||
begin
|
||||
r = Marshal.load($1.unpack1("m"))
|
||||
|
@ -664,7 +678,9 @@ module Test
|
|||
|
||||
if !(_io = IO.select(@ios, nil, nil, timeout))
|
||||
timeout = Time.now - @worker_timeout
|
||||
@tasks.unshift(*quit_workers {|w| w.response_at < timeout}&.map(&:real_file))
|
||||
quit_workers {|w| w.response_at < timeout}&.map {|w|
|
||||
rep << {file: w.real_file, result: nil, testcase: w.current[0], error: w.current}
|
||||
}
|
||||
elsif _io.first.any? {|io|
|
||||
@need_quit or
|
||||
(deal(io, type, result, rep).nil? and
|
||||
|
@ -672,6 +688,7 @@ module Test
|
|||
}
|
||||
break
|
||||
end
|
||||
break if @tasks.empty? and @workers.empty?
|
||||
if @jobserver and @job_tokens and !@tasks.empty? and
|
||||
((newjobs = [@tasks.size, @options[:parallel]].min) > @workers.size or
|
||||
!@workers.any? {|x| x.status == :ready})
|
||||
|
@ -707,14 +724,20 @@ module Test
|
|||
unless @interrupt || !@options[:retry] || @need_quit
|
||||
parallel = @options[:parallel]
|
||||
@options[:parallel] = false
|
||||
suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(Test::Unit::PendedError)}}
|
||||
suites, rep = rep.partition {|r|
|
||||
r[:testcase] && r[:file] &&
|
||||
(!r.key?(:report) || r[:report].any? {|e| !e[2].is_a?(Test::Unit::PendedError)})
|
||||
}
|
||||
suites.map {|r| File.realpath(r[:file])}.uniq.each {|file| require file}
|
||||
suites.map! {|r| eval("::"+r[:testcase])}
|
||||
del_status_line or puts
|
||||
unless suites.empty?
|
||||
puts "\n""Retrying..."
|
||||
@verbose = options[:verbose]
|
||||
error, suites = suites.partition {|r| r[:error]}
|
||||
suites.map! {|r| ::Object.const_get(r[:testcase])}
|
||||
error.map! {|r| ::Object.const_get(r[:testcase])}
|
||||
_run_suites(suites, type)
|
||||
result.concat _run_suites(error, type)
|
||||
end
|
||||
@options[:parallel] = parallel
|
||||
end
|
||||
|
@ -723,14 +746,21 @@ module Test
|
|||
end
|
||||
unless rep.empty?
|
||||
rep.each do |r|
|
||||
r[:report].each do |f|
|
||||
if r[:error]
|
||||
puke(*r[:error], Timeout::Error)
|
||||
next
|
||||
end
|
||||
r[:report]&.each do |f|
|
||||
puke(*f) if f
|
||||
end
|
||||
end
|
||||
if @options[:retry]
|
||||
@errors += rep.map{|x| x[:result][0] }.inject(:+)
|
||||
@failures += rep.map{|x| x[:result][1] }.inject(:+)
|
||||
@skips += rep.map{|x| x[:result][2] }.inject(:+)
|
||||
rep.each do |x|
|
||||
(e, f, s = x[:result]) or next
|
||||
@errors += e
|
||||
@failures += f
|
||||
@skips += s
|
||||
end
|
||||
end
|
||||
end
|
||||
unless @warnings.empty?
|
||||
|
@ -1473,6 +1503,7 @@ module Test
|
|||
assertions = all_test_methods.map { |method|
|
||||
|
||||
inst = suite.new method
|
||||
_start_method(inst)
|
||||
inst._assertions = 0
|
||||
|
||||
print "#{suite}##{method} = " if @verbose
|
||||
|
@ -1494,11 +1525,18 @@ module Test
|
|||
leakchecker.check("#{inst.class}\##{inst.__name__}")
|
||||
end
|
||||
|
||||
_end_method(inst)
|
||||
|
||||
inst._assertions
|
||||
}
|
||||
return assertions.size, assertions.inject(0) { |sum, n| sum + n }
|
||||
end
|
||||
|
||||
def _start_method(inst)
|
||||
end
|
||||
def _end_method(inst)
|
||||
end
|
||||
|
||||
##
|
||||
# Record the result of a single test. Makes it very easy to gather
|
||||
# information. Eg:
|
||||
|
|
|
@ -31,6 +31,10 @@ module Test
|
|||
end
|
||||
end
|
||||
|
||||
def _start_method(inst)
|
||||
_report "start", Marshal.dump([inst.class.name, inst.__name__])
|
||||
end
|
||||
|
||||
def _run_suite(suite, type) # :nodoc:
|
||||
@partial_report = []
|
||||
orig_testout = Test::Unit::Runner.output
|
||||
|
|
|
@ -49,6 +49,7 @@ module TestParallel
|
|||
assert_match(/^ready/,@worker_out.gets)
|
||||
@worker_in.puts "run #{TESTS}/ptest_first.rb test"
|
||||
assert_match(/^okay/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
|
@ -61,9 +62,11 @@ module TestParallel
|
|||
assert_match(/^ready/,@worker_out.gets)
|
||||
@worker_in.puts "run #{TESTS}/ptest_second.rb test"
|
||||
assert_match(/^okay/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
|
@ -76,15 +79,18 @@ module TestParallel
|
|||
assert_match(/^ready/,@worker_out.gets)
|
||||
@worker_in.puts "run #{TESTS}/ptest_first.rb test"
|
||||
assert_match(/^okay/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
assert_match(/^ready/,@worker_out.gets)
|
||||
@worker_in.puts "run #{TESTS}/ptest_second.rb test"
|
||||
assert_match(/^okay/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
assert_match(/^start/,@worker_out.gets)
|
||||
assert_match(/^record/,@worker_out.gets)
|
||||
assert_match(/^p/,@worker_out.gets)
|
||||
assert_match(/^done/,@worker_out.gets)
|
||||
|
@ -202,5 +208,12 @@ module TestParallel
|
|||
assert(buf.scan(/^\[\s*\d+\/\d+\]\s*(\d+?)=/).flatten.uniq.size > 1,
|
||||
message("retried tests should run in different processes") {buf})
|
||||
end
|
||||
|
||||
def test_hungup
|
||||
spawn_runner "--worker-timeout=1", "test4test_hungup.rb"
|
||||
buf = Timeout.timeout(TIMEOUT) {@test_out.read}
|
||||
assert_match(/^Retrying\.+$/, buf)
|
||||
assert_match(/^2 tests,.* 0 failures,/, buf)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
15
tool/test/testunit/tests_for_parallel/test4test_hungup.rb
Normal file
15
tool/test/testunit/tests_for_parallel/test4test_hungup.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
require_relative '../../../lib/test/unit'
|
||||
|
||||
class TestHung < Test::Unit::TestCase
|
||||
def test_success_at_worker
|
||||
assert true
|
||||
end
|
||||
|
||||
def test_hungup_at_worker
|
||||
if on_parallel_worker?
|
||||
sleep 10
|
||||
end
|
||||
assert true
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue