ActiveSupport::Testing::Isolation: gracefully handle the subprocess dying
Right now if the subprocess exit uncleanly, it straight out bring the parent down with it because it will fail to parse the (likely empty) Marshal payload: ``` <internal:marshal>:34:in `load': marshal data too short (ArgumentError) from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:23:in `run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:1094:in `run_one_method' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:179:in `block in run' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:168:in `with_timestamps' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:178:in `run' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:229:in `block in run_from_queue' from 3.3.0+0/gems/ci-queue-0.38.0/lib/ci/queue/redis/worker.rb:55:in `poll' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:228:in `run_from_queue' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:213:in `__run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:162:in `run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:86:in `block in autorun' - XXXX::XXXXTest#test_xxxxx - /tmp/bundle/ruby/3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `write': closed stream (IOError) from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `puts' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `block (2 levels) in run_in_isolation' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:32:in `fork' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:32:in `block in run_in_isolation' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:28:in `pipe' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:28:in `run_in_isolation' from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:19:in `run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:1094:in `run_one_method' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:179:in `block in run' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:168:in `with_timestamps' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:178:in `run' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:229:in `block in run_from_queue' from 3.3.0+0/gems/ci-queue-0.38.0/lib/ci/queue/redis/worker.rb:55:in `poll' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:228:in `run_from_queue' from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:213:in `__run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:162:in `run' from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:86:in `block in autorun' ``` This breaks the Minitest contract that `run_one_method` shouldn't raise ever, and return a `Minitest::Result`. By properly checking the sub process status, we can turn this crash into a test failure, allowing the original test process to go on.
This commit is contained in:
parent
139c5678aa
commit
b07362cffa
@ -5,6 +5,8 @@ module Testing
|
||||
module Isolation
|
||||
require "thread"
|
||||
|
||||
SubprocessCrashed = Class.new(StandardError)
|
||||
|
||||
def self.included(klass) # :nodoc:
|
||||
klass.class_eval do
|
||||
parallelize_me!
|
||||
@ -16,10 +18,17 @@ def self.forking_env?
|
||||
end
|
||||
|
||||
def run
|
||||
serialized = run_in_isolation do
|
||||
status, serialized = run_in_isolation do
|
||||
super
|
||||
end
|
||||
|
||||
unless status&.success?
|
||||
error = SubprocessCrashed.new("Subprocess exited with an error: #{status.inspect}\noutput: #{serialized.inspect}")
|
||||
error.set_backtrace(caller)
|
||||
self.failures << Minitest::UnexpectedError.new(error)
|
||||
return defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
|
||||
end
|
||||
|
||||
Marshal.load(serialized)
|
||||
end
|
||||
|
||||
@ -50,13 +59,13 @@ def run_in_isolation(&blk)
|
||||
end
|
||||
|
||||
write.puts [result].pack("m")
|
||||
exit!
|
||||
exit!(0)
|
||||
end
|
||||
|
||||
write.close
|
||||
result = read.read
|
||||
Process.wait2(pid)
|
||||
result.unpack1("m")
|
||||
_, status = Process.wait2(pid)
|
||||
return status, result.unpack1("m")
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -75,7 +84,7 @@ def run_in_isolation(&blk)
|
||||
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
|
||||
file.puts [Marshal.dump(test_result)].pack("m")
|
||||
end
|
||||
exit!
|
||||
exit!(0)
|
||||
else
|
||||
Tempfile.open("isolation") do |tmpfile|
|
||||
env = {
|
||||
@ -93,13 +102,14 @@ def run_in_isolation(&blk)
|
||||
|
||||
child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
|
||||
|
||||
status = nil
|
||||
begin
|
||||
Process.wait(child.pid)
|
||||
_, status = Process.wait2(child.pid)
|
||||
rescue Errno::ECHILD # The child process may exit before we wait
|
||||
nil
|
||||
end
|
||||
|
||||
return tmpfile.read.unpack1("m")
|
||||
return status, tmpfile.read.unpack1("m")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user