minitest-fork_executor 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e166bd6d5d09dc03b3888c59beb8a30ba259c7cef826f0bcfa373375b232151c
|
4
|
+
data.tar.gz: caadad6c3971b6acafbe50fc0e1a3f76f7062d8c03bfe27ba11a578ecd91920e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5775181f930796a6b81c36cf265a0fe562f216e21fa6ae16c42b0dc1318c5ceacd078d580071d8d8a6f8c511d8f78b5fe7af52258930bea6787bb015a172bffc
|
7
|
+
data.tar.gz: 9a683822a4dd87af11d56088854e2cc10caf537388644f5f0ddabe8612b5ea92196b0b6f277a64758914fd0bc6712e33c3f90511d42d29bf707b40bc55c8dc69
|
@@ -1,13 +1,17 @@
|
|
1
1
|
module Minitest
|
2
|
+
# Minitest runs individual test cases via Minitest.run_one_method. When
|
3
|
+
# ForkExecutor is started, we need to override it and implement the fork
|
4
|
+
# algorithm. See the detailed comments below to understand how it's done.
|
5
|
+
#
|
6
|
+
# Please keep in mind we support Ruby 1.9 and hence can't use conveniences
|
7
|
+
# offered by more modern Rubies.
|
8
|
+
|
2
9
|
class ForkExecutor
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# algorithm. See the detailed comments below to understand how it's done.
|
6
|
-
#
|
7
|
-
# Please keep in mind we support Ruby 1.9 and hence can't use conveniences
|
8
|
-
# offered by more modern Rubies.
|
10
|
+
# #start is called by Minitest when initializing the executor. This is where
|
11
|
+
# we override some Minitest internals to implement fork-based execution.
|
9
12
|
def start
|
10
|
-
# Store the reference to the original run_one_method
|
13
|
+
# Store the reference to the original run_one_method method in order to
|
14
|
+
# use it to actually run the test case.
|
11
15
|
original_run_one_method = Minitest.method(:run_one_method)
|
12
16
|
|
13
17
|
# Remove the original singleton method from Minitest in order to avoid
|
@@ -18,31 +22,66 @@ module Minitest
|
|
18
22
|
|
19
23
|
# Define a new version of run_one_method that forks, calls the original
|
20
24
|
# run_one_method in the child process, and sends results back to the
|
21
|
-
# parent.
|
25
|
+
# parent. klass and method_name are the two parameters accepted by the
|
26
|
+
# original run_one_method - they're the test class (e.g. UserTest) and
|
27
|
+
# the test method name (e.g. :test_email_must_be_unique).
|
22
28
|
Minitest.define_singleton_method(:run_one_method) do |klass, method_name|
|
29
|
+
# Set up a binary pipe for transporting test results from the child
|
30
|
+
# to the parent process.
|
23
31
|
read_io, write_io = IO.pipe
|
24
32
|
read_io.binmode
|
25
33
|
write_io.binmode
|
26
34
|
|
27
|
-
if fork
|
28
|
-
#
|
35
|
+
if Process.fork
|
36
|
+
# The parent process responsible for collecting results.
|
29
37
|
|
38
|
+
# The parent process doesn't write anything.
|
30
39
|
write_io.close
|
40
|
+
|
41
|
+
# Load the result object passed by the child process.
|
31
42
|
result = Marshal.load(read_io)
|
43
|
+
|
44
|
+
# Unwrap all failures from FailureTransport so that they can be
|
45
|
+
# safely presented to the user.
|
46
|
+
result.failures.map!(&:failure)
|
47
|
+
|
48
|
+
# We're done reading results from the child so it's safe to close the
|
49
|
+
# IO object now.
|
32
50
|
read_io.close
|
33
51
|
|
52
|
+
# Wait for the child process to finish before returning the result.
|
34
53
|
Process.wait
|
35
54
|
else
|
36
|
-
#
|
37
|
-
|
55
|
+
# The child process responsible for running the test case.
|
56
|
+
|
57
|
+
# Run the test case method via the original .run_one_method.
|
38
58
|
result = original_run_one_method.call(klass, method_name)
|
39
59
|
|
60
|
+
# Wrap failures in FailureTransport to avoid issue when marshalling.
|
61
|
+
# Some failures correspond to exceptions referencing unmarshallable
|
62
|
+
# objects. For example, a PostgreSQL exception may reference
|
63
|
+
# PG::Connection that cannot be marshalled. In those case, we replace
|
64
|
+
# the original error with UnmarshallableError retaining as much
|
65
|
+
# detail as possible.
|
66
|
+
result.failures.map! { |failure| FailureTransport.new(failure) }
|
67
|
+
|
68
|
+
# The child process doesn't read anything.
|
40
69
|
read_io.close
|
70
|
+
|
71
|
+
# Dump the result object to the write IO object so that it can be
|
72
|
+
# read by the parent process.
|
41
73
|
Marshal.dump(result, write_io)
|
74
|
+
|
75
|
+
# We're done sending results to the parent so it's safe to close the
|
76
|
+
# IO object now.
|
42
77
|
write_io.close
|
78
|
+
|
79
|
+
# Exit the child process as its job is now done.
|
43
80
|
exit
|
44
81
|
end
|
45
82
|
|
83
|
+
# This value is returned ONLY in the parent process, not in the child
|
84
|
+
# process.
|
46
85
|
result
|
47
86
|
end
|
48
87
|
end
|
@@ -51,5 +90,73 @@ module Minitest
|
|
51
90
|
# Nothing to do here but required by Minitest. In a future version, we may
|
52
91
|
# reinstate the original Minitest.run_one_method here.
|
53
92
|
end
|
93
|
+
|
94
|
+
# A Minitest Failure transport class enabling passing non-marshallable
|
95
|
+
# objects (e.g. IO or sockets) via Marshal. The basic idea is replacing
|
96
|
+
# Minitest failures referencing unmarshallable objects with
|
97
|
+
# UnmarshallableError retaining as much detail as possible.
|
98
|
+
class FailureTransport
|
99
|
+
attr_reader :failure
|
100
|
+
|
101
|
+
def initialize(failure)
|
102
|
+
@failure = failure
|
103
|
+
end
|
104
|
+
|
105
|
+
def marshal_dump
|
106
|
+
Marshal.dump(failure)
|
107
|
+
rescue TypeError
|
108
|
+
# CAREFUL! WE'RE MODIFYING FAILURE IN PLACE UNDER THE ASSUMPTION THAT
|
109
|
+
# IT LIVES IN A MEMORY SPACE OF A SHORT-LIVED PROCESS, NAMELY THE CHILD
|
110
|
+
# PROCESS RESPONSIBLE FOR RUNNING A SINGLE TEST. IF THIS ASSUMPTION IS
|
111
|
+
# VIOLATED THEN AN ALTERNATIVE APPROACH (E.G. DUPLICATING THE FAILURE)
|
112
|
+
# MIGHT BE NECESSARY.
|
113
|
+
|
114
|
+
if failure.respond_to?(:exception) && failure.respond_to?(:exception=)
|
115
|
+
failure.exception = UnmarshallableError.new(failure.exception)
|
116
|
+
elsif failure.respond_to?(:error) && failure.respond_to?(:error=)
|
117
|
+
failure.error = UnmarshallableError.new(failure.error)
|
118
|
+
else
|
119
|
+
raise(<<ERROR)
|
120
|
+
Minitest failures should respond respond to exception/exception= (versions prior
|
121
|
+
to 5.14.0) or error/error= (version 5.14.0 and newer). The received failure does
|
122
|
+
responds to neither. Are you using an newer Minitest version?
|
123
|
+
ERROR
|
124
|
+
end
|
125
|
+
|
126
|
+
Marshal.dump(failure)
|
127
|
+
end
|
128
|
+
|
129
|
+
def marshal_load(dump)
|
130
|
+
@failure = Marshal.load(dump)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# An always marshallable exception class that can be derived from another
|
135
|
+
# (potentially non-marshallable exception). It's actually not intended to be
|
136
|
+
# raised but merely instantiated when passing Minitest failures from the
|
137
|
+
# runner to the reporter.
|
138
|
+
class UnmarshallableError < RuntimeError
|
139
|
+
def initialize(exc)
|
140
|
+
super(<<MESSAGE)
|
141
|
+
An unmarshallable error has occured. Below is its best-effort representation.
|
142
|
+
In order to receive the error itself, please disable Minitest::ForkExecutor.
|
143
|
+
|
144
|
+
Error class:
|
145
|
+
#{exc.class.name}
|
146
|
+
|
147
|
+
Error message:
|
148
|
+
#{exc.message}
|
149
|
+
|
150
|
+
Attributes:
|
151
|
+
#{exc.instance_variables.map do |name|
|
152
|
+
" #{name} = #{exc.instance_variable_get(name).inspect}"
|
153
|
+
end.join("\n")}
|
154
|
+
|
155
|
+
END OF ERROR MESSAGE (ORIGINAL BACKTRACE MAY FOLLOW)
|
156
|
+
MESSAGE
|
157
|
+
|
158
|
+
set_backtrace(exc.backtrace)
|
159
|
+
end
|
160
|
+
end
|
54
161
|
end
|
55
162
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/fork_executor'
|
5
|
+
|
6
|
+
Minitest.parallel_executor = Minitest::ForkExecutor.new
|
7
|
+
|
8
|
+
class UnmarshallableException < RuntimeError
|
9
|
+
def initialize(io)
|
10
|
+
super("Unmarshallable test exception")
|
11
|
+
@io = io
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class UnmarshallableTest < Minitest::Test
|
16
|
+
def test_unmarshallable_wrapping
|
17
|
+
# We'd like to see a backtrace in the output hence several raise_in_* calls.
|
18
|
+
raise_in_2
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def raise_in_2
|
24
|
+
raise_in_1
|
25
|
+
end
|
26
|
+
|
27
|
+
def raise_in_1
|
28
|
+
raise_in_0
|
29
|
+
end
|
30
|
+
|
31
|
+
def raise_in_0
|
32
|
+
raise UnmarshallableException.new($stdout)
|
33
|
+
end
|
34
|
+
end
|
File without changes
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-fork_executor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Navis
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2019-01-03 00:00:00.000000000 Z
|
@@ -46,12 +46,13 @@ extensions: []
|
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
48
|
- lib/minitest/fork_executor.rb
|
49
|
-
- test/
|
49
|
+
- test/acceptance/unmarshallable_test.rb
|
50
|
+
- test/unit/minitest/fork_executor_test.rb
|
50
51
|
homepage: https://github.com/gregnavis/minitest-fork_executor
|
51
52
|
licenses:
|
52
53
|
- MIT
|
53
54
|
metadata: {}
|
54
|
-
post_install_message:
|
55
|
+
post_install_message:
|
55
56
|
rdoc_options: []
|
56
57
|
require_paths:
|
57
58
|
- lib
|
@@ -66,9 +67,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
67
|
- !ruby/object:Gem::Version
|
67
68
|
version: '0'
|
68
69
|
requirements: []
|
69
|
-
rubygems_version: 3.
|
70
|
-
signing_key:
|
70
|
+
rubygems_version: 3.2.22
|
71
|
+
signing_key:
|
71
72
|
specification_version: 4
|
72
73
|
summary: Near-perfect process-level test case isolation.
|
73
74
|
test_files:
|
74
|
-
- test/
|
75
|
+
- test/acceptance/unmarshallable_test.rb
|
76
|
+
- test/unit/minitest/fork_executor_test.rb
|