minitest-fork_executor 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|