/
isolation.rb
121 lines (101 loc) · 3.41 KB
/
isolation.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# frozen_string_literal: true
module ActiveSupport
module Testing
module Isolation
require "thread"
SubprocessCrashed = Class.new(StandardError)
def self.included(klass) # :nodoc:
klass.class_eval do
parallelize_me! unless Minitest.parallel_executor.is_a?(ActiveSupport::Testing::ParallelizeExecutor)
end
end
def self.forking_env?
!ENV["NO_FORK"] && Process.respond_to?(:fork)
end
def run
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
module Forking
def run_in_isolation(&blk)
IO.pipe do |read, write|
read.binmode
write.binmode
pid = fork do
read.close
yield
begin
if error?
failures.map! { |e|
begin
Marshal.dump e
e
rescue TypeError
ex = Exception.new e.message
ex.set_backtrace e.backtrace
Minitest::UnexpectedError.new ex
end
}
end
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
result = Marshal.dump(test_result)
end
write.puts [result].pack("m")
exit!(0)
end
write.close
result = read.read
_, status = Process.wait2(pid)
return status, result.unpack1("m")
end
end
end
module Subprocess
ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
# Complicated H4X to get this working in Windows / JRuby with
# no forking.
def run_in_isolation(&blk)
require "tempfile"
if ENV["ISOLATION_TEST"]
yield
test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
file.puts [Marshal.dump(test_result)].pack("m")
end
exit!(0)
else
Tempfile.open("isolation") do |tmpfile|
env = {
"ISOLATION_TEST" => self.class.name,
"ISOLATION_OUTPUT" => tmpfile.path
}
test_opts = "-n#{self.class.name}##{name}"
load_path_args = []
$-I.each do |p|
load_path_args << "-I"
load_path_args << File.expand_path(p)
end
child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts])
status = nil
begin
_, status = Process.wait2(child.pid)
rescue Errno::ECHILD # The child process may exit before we wait
nil
end
return status, tmpfile.read.unpack1("m")
end
end
end
end
include forking_env? ? Forking : Subprocess
end
end
end