test-unit 3.7.3 → 3.7.4
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 +4 -4
- data/doc/text/news.md +43 -0
- data/lib/test/unit/autorunner.rb +4 -0
- data/lib/test/unit/process-test-result.rb +55 -0
- data/lib/test/unit/process-worker.rb +83 -0
- data/lib/test/unit/test-process-run-context.rb +21 -0
- data/lib/test/unit/test-suite-process-runner.rb +197 -0
- data/lib/test/unit/test-suite-runner.rb +18 -16
- data/lib/test/unit/test-suite-thread-runner.rb +43 -31
- data/lib/test/unit/test-thread-run-context.rb +6 -3
- data/lib/test/unit/testcase.rb +76 -5
- data/lib/test/unit/testsuite.rb +23 -2
- data/lib/test/unit/ui/testrunnermediator.rb +11 -6
- data/lib/test/unit/version.rb +1 -1
- data/lib/test/unit/worker-context.rb +20 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d16114e7ad17600025f6908efd1fea35e59c5b5ff953e31bf0048a573a742e5
|
|
4
|
+
data.tar.gz: 857951ac70e9f668102998ace9099388fd66c5fa6a889d36b2e9ecb1decc3315
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d2d8e9bf468101d11b2436cbe02d66d76d8a1c262f6e3b09792fb6d068876caa6dce7d64516792b69342bd3e0077b805f038123063f9ca8cc9d93ead8b28edc
|
|
7
|
+
data.tar.gz: 01c8e73369f1a4d63c62de6fc181539b4866bbac3dee7307658d7f4c121557e132aacfd15f7afcc4376d30415bee03a7ab690a799a8587da71b5fda0cd4453a4
|
data/doc/text/news.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# News
|
|
2
2
|
|
|
3
|
+
## 3.7.4 - 2025-12-23 {#version-3-7-4}
|
|
4
|
+
|
|
5
|
+
### Improvements
|
|
6
|
+
|
|
7
|
+
* Added support for process based parallel test running. You can use
|
|
8
|
+
it by the `--parallel=process` option. You need to specify load
|
|
9
|
+
path options (e.g. `-Ilib`). Tests that startup/shutdown (test case
|
|
10
|
+
level setup/teardown) are defined run in the same process.
|
|
11
|
+
* GH-235
|
|
12
|
+
* GH-339
|
|
13
|
+
* GH-340
|
|
14
|
+
* GH-341
|
|
15
|
+
* GH-342
|
|
16
|
+
* GH-344
|
|
17
|
+
* GH-346
|
|
18
|
+
* GH-348
|
|
19
|
+
* Co authored by Naoto Ono
|
|
20
|
+
|
|
21
|
+
* Added support for `worker_id` for the parallel runner. You can
|
|
22
|
+
use it in your test, setup/teardown and startup/shutdown (test case
|
|
23
|
+
level setup/teardown).
|
|
24
|
+
* GH-235
|
|
25
|
+
* GH-345
|
|
26
|
+
* Co authored by Naoto Ono
|
|
27
|
+
|
|
28
|
+
* parallel: thread: Improved shutdown (test case level teardown)
|
|
29
|
+
timing. As a result, tests that startup (test case level setup) or
|
|
30
|
+
teardown are defined now run in the same thread.
|
|
31
|
+
* GH-235
|
|
32
|
+
* GH-343
|
|
33
|
+
* Co authored by Naoto Ono
|
|
34
|
+
|
|
35
|
+
### Fixes
|
|
36
|
+
|
|
37
|
+
* parallel: thread: Fixed a bug that running tests failed with the
|
|
38
|
+
`--verbose` option.
|
|
39
|
+
* GH-350
|
|
40
|
+
* GH-351
|
|
41
|
+
|
|
42
|
+
### Thanks
|
|
43
|
+
|
|
44
|
+
* Naoto Ono
|
|
45
|
+
|
|
3
46
|
## 3.7.3 - 2025-11-26 {#version-3-7-3}
|
|
4
47
|
|
|
5
48
|
### Improvements
|
data/lib/test/unit/autorunner.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "priority"
|
|
|
6
6
|
require_relative "attribute-matcher"
|
|
7
7
|
require_relative "testcase"
|
|
8
8
|
require_relative "test-suite-thread-runner"
|
|
9
|
+
require_relative "test-suite-process-runner"
|
|
9
10
|
require_relative "version"
|
|
10
11
|
|
|
11
12
|
module Test
|
|
@@ -415,6 +416,7 @@ module Test
|
|
|
415
416
|
|
|
416
417
|
parallel_options = [
|
|
417
418
|
:thread,
|
|
419
|
+
:process,
|
|
418
420
|
]
|
|
419
421
|
o.on("--[no-]parallel=[thread]", parallel_options,
|
|
420
422
|
"Runs tests in parallel",
|
|
@@ -422,6 +424,8 @@ module Test
|
|
|
422
424
|
case parallel
|
|
423
425
|
when nil, :thread
|
|
424
426
|
@test_suite_runner_class = TestSuiteThreadRunner
|
|
427
|
+
when :process
|
|
428
|
+
@test_suite_runner_class = TestSuiteProcessRunner
|
|
425
429
|
else
|
|
426
430
|
@test_suite_runner_class = TestSuiteRunner
|
|
427
431
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Tsutomu Katsube.
|
|
4
|
+
# Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
|
|
5
|
+
# License:: Ruby license.
|
|
6
|
+
|
|
7
|
+
module Test
|
|
8
|
+
module Unit
|
|
9
|
+
class ProcessTestResult
|
|
10
|
+
def initialize(output)
|
|
11
|
+
@output = output
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_run
|
|
15
|
+
send_result(__method__)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_pass
|
|
19
|
+
send_result(__method__)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Records an individual assertion.
|
|
23
|
+
def add_assertion
|
|
24
|
+
send_result(__method__)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_error(error)
|
|
28
|
+
send_result(__method__, error)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_failure(failure)
|
|
32
|
+
send_result(__method__, failure)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_pending(pending)
|
|
36
|
+
send_result(__method__, pending)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_omission(omission)
|
|
40
|
+
send_result(__method__, omission)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def add_notification(notification)
|
|
44
|
+
send_result(__method__, notification)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def send_result(action, *args)
|
|
50
|
+
Marshal.dump({status: :result, action: action, args: args}, @output)
|
|
51
|
+
@output.flush
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Tsutomu Katsube.
|
|
4
|
+
# Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
|
|
5
|
+
# License:: Ruby license.
|
|
6
|
+
|
|
7
|
+
require "optparse"
|
|
8
|
+
require "socket"
|
|
9
|
+
|
|
10
|
+
parser = OptionParser.new
|
|
11
|
+
parser.on("--load-path=PATH") do |path|
|
|
12
|
+
$LOAD_PATH << path
|
|
13
|
+
end
|
|
14
|
+
base_directory = nil
|
|
15
|
+
parser.on("--base-directory=PATH") do |path|
|
|
16
|
+
base_directory = path
|
|
17
|
+
end
|
|
18
|
+
worker_id = nil
|
|
19
|
+
parser.on("--worker-id=ID", Integer) do |id|
|
|
20
|
+
worker_id = id
|
|
21
|
+
end
|
|
22
|
+
remote_ip_address = nil
|
|
23
|
+
parser.on("--ip-address=ADDRESS") do |address|
|
|
24
|
+
remote_ip_address = address
|
|
25
|
+
end
|
|
26
|
+
remote_ip_port = nil
|
|
27
|
+
parser.on("--ip-port=PORT", Integer) do |port|
|
|
28
|
+
remote_ip_port = port
|
|
29
|
+
end
|
|
30
|
+
test_paths = parser.parse!
|
|
31
|
+
|
|
32
|
+
require_relative "../unit"
|
|
33
|
+
require_relative "collector/load"
|
|
34
|
+
require_relative "process-test-result"
|
|
35
|
+
require_relative "worker-context"
|
|
36
|
+
Test::Unit::AutoRunner.need_auto_run = false
|
|
37
|
+
collector = Test::Unit::Collector::Load.new
|
|
38
|
+
collector.base = base_directory
|
|
39
|
+
suite = collector.collect(*test_paths)
|
|
40
|
+
|
|
41
|
+
io_open = lambda do |&block|
|
|
42
|
+
if Gem.win_platform?
|
|
43
|
+
TCPSocket.open(remote_ip_address, remote_ip_port) do |data_socket|
|
|
44
|
+
block.call(data_socket, data_socket)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
IO.open(Test::Unit::TestSuiteProcessRunner::MAIN_TO_WORKER_INPUT_FILENO) do |data_input|
|
|
48
|
+
IO.open(Test::Unit::TestSuiteProcessRunner::WORKER_TO_MAIN_OUTPUT_FILENO) do |data_output|
|
|
49
|
+
block.call(data_input, data_output)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
io_open.call do |data_input, data_output|
|
|
56
|
+
loop do
|
|
57
|
+
Marshal.dump({status: :ready}, data_output)
|
|
58
|
+
data_output.flush
|
|
59
|
+
test_name = Marshal.load(data_input)
|
|
60
|
+
break if test_name.nil?
|
|
61
|
+
test = suite.find(test_name)
|
|
62
|
+
result = Test::Unit::ProcessTestResult.new(data_output)
|
|
63
|
+
run_context = Test::Unit::TestRunContext.new(Test::Unit::TestSuiteRunner)
|
|
64
|
+
|
|
65
|
+
event_listener = lambda do |event_name, *args|
|
|
66
|
+
Marshal.dump({status: :event, event_name: event_name, args: args}, data_output)
|
|
67
|
+
data_output.flush
|
|
68
|
+
end
|
|
69
|
+
if test.is_a?(Test::Unit::TestSuite)
|
|
70
|
+
test_suite = test
|
|
71
|
+
else
|
|
72
|
+
test_suite = Test::Unit::TestSuite.new(test.class.name, test.class)
|
|
73
|
+
test_suite << test
|
|
74
|
+
end
|
|
75
|
+
runner = Test::Unit::TestSuiteRunner.new(test_suite)
|
|
76
|
+
worker_context = Test::Unit::WorkerContext.new(worker_id, run_context, result)
|
|
77
|
+
runner.run(worker_context, &event_listener)
|
|
78
|
+
end
|
|
79
|
+
Marshal.dump({status: :done}, data_output)
|
|
80
|
+
data_output.flush
|
|
81
|
+
|
|
82
|
+
Marshal.load(data_input)
|
|
83
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Tsutomu Katsube.
|
|
4
|
+
# Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
|
|
5
|
+
# License:: Ruby license.
|
|
6
|
+
|
|
7
|
+
require_relative "test-run-context"
|
|
8
|
+
|
|
9
|
+
module Test
|
|
10
|
+
module Unit
|
|
11
|
+
class TestProcessRunContext < TestRunContext
|
|
12
|
+
attr_reader :test_names
|
|
13
|
+
attr_accessor :progress_block
|
|
14
|
+
def initialize(runner_class)
|
|
15
|
+
super(runner_class)
|
|
16
|
+
@test_names = []
|
|
17
|
+
@progress_block = nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Tsutomu Katsube.
|
|
4
|
+
# Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
|
|
5
|
+
# License:: Ruby license.
|
|
6
|
+
|
|
7
|
+
require "socket"
|
|
8
|
+
|
|
9
|
+
require_relative "process-test-result"
|
|
10
|
+
require_relative "sub-test-result"
|
|
11
|
+
require_relative "test-process-run-context"
|
|
12
|
+
require_relative "test-suite-runner"
|
|
13
|
+
|
|
14
|
+
module Test
|
|
15
|
+
module Unit
|
|
16
|
+
class TestSuiteProcessRunner < TestSuiteRunner
|
|
17
|
+
MAIN_TO_WORKER_INPUT_FILENO = 3
|
|
18
|
+
WORKER_TO_MAIN_OUTPUT_FILENO = 4
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
class Worker
|
|
22
|
+
attr_reader :main_to_worker_output, :worker_to_main_input
|
|
23
|
+
def initialize(pid, main_to_worker_output, worker_to_main_input)
|
|
24
|
+
@pid = pid
|
|
25
|
+
@main_to_worker_output = main_to_worker_output
|
|
26
|
+
@worker_to_main_input = worker_to_main_input
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def receive
|
|
30
|
+
Marshal.load(@worker_to_main_input)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def send(data)
|
|
34
|
+
Marshal.dump(data, @main_to_worker_output)
|
|
35
|
+
@main_to_worker_output.flush
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def wait
|
|
39
|
+
begin
|
|
40
|
+
Process.waitpid(@pid)
|
|
41
|
+
ensure
|
|
42
|
+
begin
|
|
43
|
+
@main_to_worker_output.close
|
|
44
|
+
ensure
|
|
45
|
+
@worker_to_main_input.close
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def run_all_tests(result, options)
|
|
52
|
+
n_workers = TestSuiteRunner.n_workers
|
|
53
|
+
test_suite = options[:test_suite]
|
|
54
|
+
|
|
55
|
+
start_tcp_server do |tcp_server|
|
|
56
|
+
workers = []
|
|
57
|
+
begin
|
|
58
|
+
n_workers.times do |i|
|
|
59
|
+
load_paths = options[:load_paths]
|
|
60
|
+
base_directory = options[:base_directory]
|
|
61
|
+
test_paths = options[:test_paths]
|
|
62
|
+
command_line = [Gem.ruby, File.join(__dir__, "process-worker.rb")]
|
|
63
|
+
load_paths.each do |load_path|
|
|
64
|
+
command_line << "--load-path" << load_path
|
|
65
|
+
end
|
|
66
|
+
unless base_directory.nil?
|
|
67
|
+
command_line << "--base-directory" << base_directory
|
|
68
|
+
end
|
|
69
|
+
command_line << "--worker-id" << (i + 1).to_s
|
|
70
|
+
if Gem.win_platform?
|
|
71
|
+
local_address = tcp_server.local_address
|
|
72
|
+
command_line << "--ip-address" << local_address.ip_address
|
|
73
|
+
command_line << "--ip-port" << local_address.ip_port.to_s
|
|
74
|
+
end
|
|
75
|
+
command_line.concat(test_paths)
|
|
76
|
+
if Gem.win_platform?
|
|
77
|
+
# On Windows, file descriptors 3 and above cannot be passed to
|
|
78
|
+
# child processes.
|
|
79
|
+
pid = spawn(*command_line)
|
|
80
|
+
data_socket = tcp_server.accept
|
|
81
|
+
workers << Worker.new(pid, data_socket, data_socket)
|
|
82
|
+
else
|
|
83
|
+
main_to_worker_input, main_to_worker_output = IO.pipe
|
|
84
|
+
worker_to_main_input, worker_to_main_output = IO.pipe
|
|
85
|
+
pid = spawn(*command_line, {MAIN_TO_WORKER_INPUT_FILENO => main_to_worker_input,
|
|
86
|
+
WORKER_TO_MAIN_OUTPUT_FILENO => worker_to_main_output})
|
|
87
|
+
main_to_worker_input.close
|
|
88
|
+
worker_to_main_output.close
|
|
89
|
+
workers << Worker.new(pid, main_to_worker_output, worker_to_main_input)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
run_context = TestProcessRunContext.new(self)
|
|
94
|
+
yield(run_context)
|
|
95
|
+
run_context.progress_block.call(TestSuite::STARTED, test_suite.name)
|
|
96
|
+
run_context.progress_block.call(TestSuite::STARTED_OBJECT, test_suite)
|
|
97
|
+
|
|
98
|
+
worker_inputs = workers.collect(&:worker_to_main_input)
|
|
99
|
+
until run_context.test_names.empty? do
|
|
100
|
+
select_each_worker(worker_inputs, workers) do |_, worker, data|
|
|
101
|
+
case data[:status]
|
|
102
|
+
when :ready
|
|
103
|
+
test_name = run_context.test_names.shift
|
|
104
|
+
break if test_name.nil?
|
|
105
|
+
worker.send(test_name)
|
|
106
|
+
when :result
|
|
107
|
+
add_result(result, data)
|
|
108
|
+
when :event
|
|
109
|
+
emit_event(options[:event_listener], data)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
workers.each do |worker|
|
|
114
|
+
worker.send(nil)
|
|
115
|
+
end
|
|
116
|
+
until worker_inputs.empty? do
|
|
117
|
+
select_each_worker(worker_inputs, workers) do |worker_to_main_input, worker, data|
|
|
118
|
+
case data[:status]
|
|
119
|
+
when :result
|
|
120
|
+
add_result(result, data)
|
|
121
|
+
when :event
|
|
122
|
+
emit_event(options[:event_listener], data)
|
|
123
|
+
when :done
|
|
124
|
+
worker_inputs.delete(worker_to_main_input)
|
|
125
|
+
worker.send(nil)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
ensure
|
|
130
|
+
workers.each do |worker|
|
|
131
|
+
worker.wait
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
run_context.progress_block.call(TestSuite::FINISHED, test_suite.name)
|
|
136
|
+
run_context.progress_block.call(TestSuite::FINISHED_OBJECT, test_suite)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
def start_tcp_server
|
|
142
|
+
if Gem.win_platform?
|
|
143
|
+
TCPServer.open("127.0.0.1", 0) do |tcp_server|
|
|
144
|
+
yield(tcp_server)
|
|
145
|
+
end
|
|
146
|
+
else
|
|
147
|
+
yield
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def select_each_worker(worker_inputs, workers)
|
|
152
|
+
readables, = IO.select(worker_inputs)
|
|
153
|
+
readables.each do |worker_to_main_input|
|
|
154
|
+
worker = workers.find do |w|
|
|
155
|
+
w.worker_to_main_input == worker_to_main_input
|
|
156
|
+
end
|
|
157
|
+
data = worker.receive
|
|
158
|
+
yield(worker_to_main_input, worker, data)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def add_result(result, data)
|
|
163
|
+
action = data[:action]
|
|
164
|
+
args = data[:args]
|
|
165
|
+
result.__send__(action, *args)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def emit_event(event_listener, data)
|
|
169
|
+
event_name = data[:event_name]
|
|
170
|
+
args = data[:args]
|
|
171
|
+
event_listener.call(event_name, *args)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def run(worker_context, &progress_block)
|
|
176
|
+
worker_context.run_context.progress_block = progress_block
|
|
177
|
+
run_tests_recursive(@test_suite, worker_context, &progress_block)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
private
|
|
181
|
+
def run_tests_recursive(test_suite, worker_context, &progress_block)
|
|
182
|
+
run_context = worker_context.run_context
|
|
183
|
+
if test_suite.have_fixture?
|
|
184
|
+
run_context.test_names << test_suite.name
|
|
185
|
+
else
|
|
186
|
+
test_suite.tests.each do |test|
|
|
187
|
+
if test.is_a?(TestSuite)
|
|
188
|
+
run_tests_recursive(test, worker_context, &progress_block)
|
|
189
|
+
else
|
|
190
|
+
run_context.test_names << test.name
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Author:: Nathaniel Talbott.
|
|
4
4
|
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
|
5
5
|
# Copyright:: Copyright (c) 2008-2011 Kouhei Sutou. All rights reserved.
|
|
6
|
-
# Copyright:: Copyright (c) 2024 Tsutomu Katsube. All rights reserved.
|
|
6
|
+
# Copyright:: Copyright (c) 2024-2025 Tsutomu Katsube. All rights reserved.
|
|
7
7
|
# License:: Ruby license.
|
|
8
8
|
|
|
9
9
|
require "etc"
|
|
@@ -15,7 +15,7 @@ module Test
|
|
|
15
15
|
class TestSuiteRunner
|
|
16
16
|
@n_workers = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 1
|
|
17
17
|
class << self
|
|
18
|
-
def run_all_tests
|
|
18
|
+
def run_all_tests(result, options)
|
|
19
19
|
yield(TestRunContext.new(self))
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -32,14 +32,14 @@ module Test
|
|
|
32
32
|
@test_suite = test_suite
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def run(
|
|
35
|
+
def run(worker_context, &progress_block)
|
|
36
36
|
yield(TestSuite::STARTED, @test_suite.name)
|
|
37
37
|
yield(TestSuite::STARTED_OBJECT, @test_suite)
|
|
38
|
-
run_startup(
|
|
39
|
-
run_tests(
|
|
38
|
+
run_startup(worker_context)
|
|
39
|
+
run_tests(worker_context, &progress_block)
|
|
40
40
|
ensure
|
|
41
41
|
begin
|
|
42
|
-
run_shutdown(
|
|
42
|
+
run_shutdown(worker_context)
|
|
43
43
|
ensure
|
|
44
44
|
yield(TestSuite::FINISHED, @test_suite.name)
|
|
45
45
|
yield(TestSuite::FINISHED_OBJECT, @test_suite)
|
|
@@ -47,23 +47,24 @@ module Test
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
private
|
|
50
|
-
def run_startup(
|
|
50
|
+
def run_startup(worker_context)
|
|
51
51
|
test_case = @test_suite.test_case
|
|
52
52
|
return if test_case.nil? or !test_case.respond_to?(:startup)
|
|
53
|
+
test_case.worker_id = worker_context.id
|
|
53
54
|
begin
|
|
54
55
|
test_case.startup
|
|
55
56
|
rescue Exception
|
|
56
|
-
raise unless handle_exception($!, result)
|
|
57
|
+
raise unless handle_exception($!, worker_context.result)
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
def run_tests(
|
|
61
|
+
def run_tests(worker_context, &progress_block)
|
|
61
62
|
@test_suite.tests.each do |test|
|
|
62
|
-
run_test(test,
|
|
63
|
+
run_test(test, worker_context, &progress_block)
|
|
63
64
|
end
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
def run_test(test,
|
|
67
|
+
def run_test(test, worker_context)
|
|
67
68
|
finished_is_yielded = false
|
|
68
69
|
finished_object_is_yielded = false
|
|
69
70
|
previous_event_name = nil
|
|
@@ -92,12 +93,12 @@ module Test
|
|
|
92
93
|
yield(event_name, *args)
|
|
93
94
|
end
|
|
94
95
|
|
|
95
|
-
if test.method(:run).
|
|
96
|
-
test.run(
|
|
96
|
+
if test.method(:run).parameters[0] == [:req, :worker_context]
|
|
97
|
+
test.run(worker_context, &event_listener)
|
|
97
98
|
else
|
|
98
99
|
# For backward compatibility. There are scripts that overrides
|
|
99
100
|
# Test::Unit::TestCase#run without keyword arguments.
|
|
100
|
-
test.run(result, &event_listener)
|
|
101
|
+
test.run(worker_context.result, &event_listener)
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
if finished_is_yielded and not finished_object_is_yielded
|
|
@@ -105,13 +106,14 @@ module Test
|
|
|
105
106
|
end
|
|
106
107
|
end
|
|
107
108
|
|
|
108
|
-
def run_shutdown(
|
|
109
|
+
def run_shutdown(worker_context)
|
|
109
110
|
test_case = @test_suite.test_case
|
|
110
111
|
return if test_case.nil? or !test_case.respond_to?(:shutdown)
|
|
112
|
+
test_case.worker_id = worker_context.id
|
|
111
113
|
begin
|
|
112
114
|
test_case.shutdown
|
|
113
115
|
rescue Exception
|
|
114
|
-
raise unless handle_exception($!, result)
|
|
116
|
+
raise unless handle_exception($!, worker_context.result)
|
|
115
117
|
end
|
|
116
118
|
end
|
|
117
119
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#--
|
|
2
2
|
#
|
|
3
3
|
# Author:: Tsutomu Katsube.
|
|
4
|
-
# Copyright:: Copyright (c) 2024 Tsutomu Katsube. All rights reserved.
|
|
4
|
+
# Copyright:: Copyright (c) 2024-2025 Tsutomu Katsube. All rights reserved.
|
|
5
5
|
# License:: Ruby license.
|
|
6
6
|
|
|
7
7
|
require_relative "sub-test-result"
|
|
@@ -12,12 +12,16 @@ module Test
|
|
|
12
12
|
module Unit
|
|
13
13
|
class TestSuiteThreadRunner < TestSuiteRunner
|
|
14
14
|
class << self
|
|
15
|
-
def run_all_tests
|
|
15
|
+
def run_all_tests(result, options)
|
|
16
16
|
n_workers = TestSuiteRunner.n_workers
|
|
17
|
+
test_suite = options[:test_suite]
|
|
17
18
|
|
|
18
19
|
queue = Thread::Queue.new
|
|
19
|
-
|
|
20
|
-
yield(
|
|
20
|
+
run_context = TestThreadRunContext.new(self, queue)
|
|
21
|
+
yield(run_context)
|
|
22
|
+
run_context.progress_block.call(TestSuite::STARTED, test_suite.name)
|
|
23
|
+
run_context.progress_block.call(TestSuite::STARTED_OBJECT, test_suite)
|
|
24
|
+
run_context.parallel_unsafe_tests.each(&:call)
|
|
21
25
|
n_workers.times do
|
|
22
26
|
queue << nil
|
|
23
27
|
end
|
|
@@ -25,13 +29,13 @@ module Test
|
|
|
25
29
|
workers = []
|
|
26
30
|
sub_exceptions = []
|
|
27
31
|
n_workers.times do |i|
|
|
28
|
-
workers << Thread.new(i) do |worker_id|
|
|
32
|
+
workers << Thread.new(i + 1) do |worker_id|
|
|
29
33
|
begin
|
|
30
34
|
loop do
|
|
31
35
|
task = queue.pop
|
|
32
36
|
break if task.nil?
|
|
33
37
|
catch do |stop_tag|
|
|
34
|
-
task.call(stop_tag)
|
|
38
|
+
task.call(stop_tag, worker_id)
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
rescue Exception => exception
|
|
@@ -41,41 +45,49 @@ module Test
|
|
|
41
45
|
end
|
|
42
46
|
workers.each(&:join)
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
run_context.progress_block.call(TestSuite::FINISHED, test_suite.name)
|
|
49
|
+
run_context.progress_block.call(TestSuite::FINISHED_OBJECT, test_suite)
|
|
50
|
+
|
|
45
51
|
sub_exceptions.each do |exception|
|
|
46
52
|
raise exception
|
|
47
53
|
end
|
|
48
54
|
end
|
|
49
55
|
end
|
|
50
56
|
|
|
51
|
-
def run(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
run_startup(result)
|
|
55
|
-
run_tests(result, run_context: run_context, &progress_block)
|
|
56
|
-
ensure
|
|
57
|
-
run_context.shutdowns << lambda do
|
|
58
|
-
begin
|
|
59
|
-
run_shutdown(result)
|
|
60
|
-
ensure
|
|
61
|
-
yield(TestSuite::FINISHED, @test_suite.name)
|
|
62
|
-
yield(TestSuite::FINISHED_OBJECT, @test_suite)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
57
|
+
def run(worker_context, &progress_block)
|
|
58
|
+
worker_context.run_context.progress_block = progress_block
|
|
59
|
+
run_tests_recursive(@test_suite, worker_context, &progress_block)
|
|
65
60
|
end
|
|
66
61
|
|
|
67
62
|
private
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
def run_tests_recursive(test_suite, worker_context, &progress_block)
|
|
64
|
+
run_context = worker_context.run_context
|
|
65
|
+
if test_suite.have_fixture?
|
|
66
|
+
task = lambda do |stop_tag, worker_id|
|
|
67
|
+
sub_result = SubTestResult.new(worker_context.result)
|
|
68
|
+
sub_result.stop_tag = stop_tag
|
|
69
|
+
sub_runner = TestSuiteRunner.new(test_suite)
|
|
70
|
+
sub_worker_context = WorkerContext.new(worker_id, run_context, sub_result)
|
|
71
|
+
sub_runner.run(sub_worker_context, &progress_block)
|
|
72
|
+
end
|
|
73
|
+
run_context.queue << task
|
|
74
|
+
else
|
|
75
|
+
test_suite.tests.each do |test|
|
|
76
|
+
if test.is_a?(TestSuite)
|
|
77
|
+
run_tests_recursive(test, worker_context, &progress_block)
|
|
78
|
+
elsif test_suite.parallel_safe?
|
|
79
|
+
task = lambda do |stop_tag, worker_id|
|
|
80
|
+
sub_result = SubTestResult.new(worker_context.result)
|
|
81
|
+
sub_result.stop_tag = stop_tag
|
|
82
|
+
sub_worker_context = WorkerContext.new(worker_id, run_context, sub_result)
|
|
83
|
+
run_test(test, sub_worker_context, &progress_block)
|
|
84
|
+
end
|
|
85
|
+
run_context.queue << task
|
|
86
|
+
else
|
|
87
|
+
run_context.parallel_unsafe_tests << lambda do
|
|
88
|
+
run_test(test, worker_context, &progress_block)
|
|
89
|
+
end
|
|
77
90
|
end
|
|
78
|
-
run_context.queue << task
|
|
79
91
|
end
|
|
80
92
|
end
|
|
81
93
|
end
|
|
@@ -9,11 +9,14 @@ require_relative "test-run-context"
|
|
|
9
9
|
module Test
|
|
10
10
|
module Unit
|
|
11
11
|
class TestThreadRunContext < TestRunContext
|
|
12
|
-
attr_reader :queue
|
|
13
|
-
|
|
12
|
+
attr_reader :queue
|
|
13
|
+
attr_accessor :progress_block
|
|
14
|
+
attr_accessor :parallel_unsafe_tests
|
|
15
|
+
def initialize(runner_class, queue)
|
|
14
16
|
super(runner_class)
|
|
15
17
|
@queue = queue
|
|
16
|
-
@
|
|
18
|
+
@progress_block = nil
|
|
19
|
+
@parallel_unsafe_tests = []
|
|
17
20
|
end
|
|
18
21
|
end
|
|
19
22
|
end
|
data/lib/test/unit/testcase.rb
CHANGED
|
@@ -439,8 +439,9 @@ module Test
|
|
|
439
439
|
#
|
|
440
440
|
# The difference of them are the following:
|
|
441
441
|
#
|
|
442
|
-
# * Test case created by {sub_test_case} is an anonymous class
|
|
443
|
-
#
|
|
442
|
+
# * Test case created by {sub_test_case} is backed by an anonymous class,
|
|
443
|
+
# but it is assigned to an auto generated constant. So it can be referred
|
|
444
|
+
# to by name (e.g. for `Marshal.dump` and across process use).
|
|
444
445
|
# * The class name of class style must follow
|
|
445
446
|
# constant naming rule in Ruby. But the name of test case
|
|
446
447
|
# created by {sub_test_case} doesn't need to follow the rule.
|
|
@@ -500,6 +501,24 @@ module Test
|
|
|
500
501
|
available_locations
|
|
501
502
|
end
|
|
502
503
|
|
|
504
|
+
# Returns a current worker ID for the test case. This return depends on
|
|
505
|
+
# how tests are run:
|
|
506
|
+
#
|
|
507
|
+
# * Sequential: always returns `0`
|
|
508
|
+
#
|
|
509
|
+
# * Parallel: returns a one-based to the number of workers.
|
|
510
|
+
#
|
|
511
|
+
# @return [Integer]
|
|
512
|
+
#
|
|
513
|
+
# @since 3.7.4
|
|
514
|
+
def worker_id
|
|
515
|
+
@worker_id || 0
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def worker_id=(worker_id) # :nodoc:
|
|
519
|
+
@worker_id = worker_id
|
|
520
|
+
end
|
|
521
|
+
|
|
503
522
|
private
|
|
504
523
|
# @private
|
|
505
524
|
@@method_locations = {}
|
|
@@ -545,12 +564,16 @@ module Test
|
|
|
545
564
|
# @private
|
|
546
565
|
def sub_test_case_class(name)
|
|
547
566
|
parent_test_case = self
|
|
548
|
-
Class.new(self) do
|
|
567
|
+
sub_test_case = Class.new(self) do
|
|
549
568
|
singleton_class = class << self; self; end
|
|
550
569
|
singleton_class.__send__(:define_method, :name) do
|
|
551
570
|
[parent_test_case.name, name].compact.join("::")
|
|
552
571
|
end
|
|
553
572
|
end
|
|
573
|
+
# Give the anonymous class a unique, Base64 encoded constant name.
|
|
574
|
+
# So it becomes a named class that `Marshal` can safely dump even across processes.
|
|
575
|
+
const_set(:"TEST_#{[sub_test_case.name].pack("m").delete("\n=")}", sub_test_case)
|
|
576
|
+
sub_test_case
|
|
554
577
|
end
|
|
555
578
|
end
|
|
556
579
|
|
|
@@ -585,11 +608,17 @@ module Test
|
|
|
585
608
|
# Runs the individual test method represented by this
|
|
586
609
|
# instance of the fixture, collecting statistics, failures
|
|
587
610
|
# and errors in result.
|
|
588
|
-
def run(
|
|
611
|
+
def run(worker_context)
|
|
589
612
|
begin
|
|
613
|
+
unless worker_context.is_a?(WorkerContext)
|
|
614
|
+
result = worker_context
|
|
615
|
+
worker_context = WorkerContext.new(nil, nil, result)
|
|
616
|
+
end
|
|
617
|
+
result = worker_context.result
|
|
590
618
|
@_result = result
|
|
591
619
|
instance_variables_before = instance_variables
|
|
592
|
-
@internal_data.run_context = run_context
|
|
620
|
+
@internal_data.run_context = worker_context.run_context
|
|
621
|
+
@internal_data.worker_id = worker_context.id
|
|
593
622
|
@internal_data.test_started
|
|
594
623
|
yield(STARTED, name)
|
|
595
624
|
yield(STARTED_OBJECT, self)
|
|
@@ -782,6 +811,16 @@ module Test
|
|
|
782
811
|
1
|
|
783
812
|
end
|
|
784
813
|
|
|
814
|
+
# Returns a current worker ID for the test. See {TestCase.worker_id}
|
|
815
|
+
# for details.
|
|
816
|
+
#
|
|
817
|
+
# @return [Integer]
|
|
818
|
+
#
|
|
819
|
+
# @since 3.7.4
|
|
820
|
+
def worker_id
|
|
821
|
+
@internal_data.worker_id || 0
|
|
822
|
+
end
|
|
823
|
+
|
|
785
824
|
# Returns a label of test data for the test. If the
|
|
786
825
|
# test isn't associated with any test data, it returns
|
|
787
826
|
# `nil`.
|
|
@@ -887,6 +926,18 @@ module Test
|
|
|
887
926
|
current_result.add_pass
|
|
888
927
|
end
|
|
889
928
|
|
|
929
|
+
def marshal_dump
|
|
930
|
+
{
|
|
931
|
+
method_name: @method_name,
|
|
932
|
+
internal_data: @internal_data,
|
|
933
|
+
}
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
def marshal_load(data)
|
|
937
|
+
@method_name = data[:method_name]
|
|
938
|
+
@internal_data = data[:internal_data]
|
|
939
|
+
end
|
|
940
|
+
|
|
890
941
|
private
|
|
891
942
|
def current_result
|
|
892
943
|
@_result
|
|
@@ -935,6 +986,7 @@ module Test
|
|
|
935
986
|
attr_reader :start_time, :elapsed_time
|
|
936
987
|
attr_reader :test_data_label, :test_data
|
|
937
988
|
attr_accessor :run_context
|
|
989
|
+
attr_accessor :worker_id
|
|
938
990
|
def initialize
|
|
939
991
|
@start_time = nil
|
|
940
992
|
@elapsed_time = nil
|
|
@@ -943,6 +995,7 @@ module Test
|
|
|
943
995
|
@test_data_label = nil
|
|
944
996
|
@test_data = nil
|
|
945
997
|
@run_context = nil
|
|
998
|
+
@worker_id = nil
|
|
946
999
|
end
|
|
947
1000
|
|
|
948
1001
|
def passed?
|
|
@@ -977,6 +1030,24 @@ module Test
|
|
|
977
1030
|
def interrupted
|
|
978
1031
|
@interrupted = true
|
|
979
1032
|
end
|
|
1033
|
+
|
|
1034
|
+
def marshal_dump
|
|
1035
|
+
{
|
|
1036
|
+
start_time: @start_time,
|
|
1037
|
+
elapsed_time: @elapsed_time,
|
|
1038
|
+
passed: @passed,
|
|
1039
|
+
interrupted: @interrupted,
|
|
1040
|
+
test_data_label: @test_data_label,
|
|
1041
|
+
}
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
def marshal_load(data)
|
|
1045
|
+
@start_time = data[:start_time]
|
|
1046
|
+
@elapsed_time = data[:elapsed_time]
|
|
1047
|
+
@passed = data[:passed]
|
|
1048
|
+
@interrupted = data[:interrupted]
|
|
1049
|
+
@test_data_label = data[:test_data_label]
|
|
1050
|
+
end
|
|
980
1051
|
end
|
|
981
1052
|
end
|
|
982
1053
|
end
|
data/lib/test/unit/testsuite.rb
CHANGED
|
@@ -40,20 +40,41 @@ module Test
|
|
|
40
40
|
@elapsed_time = nil
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
def find(name)
|
|
44
|
+
return self if @name == name
|
|
45
|
+
@tests.each do |test|
|
|
46
|
+
if test.is_a?(self.class)
|
|
47
|
+
t = test.find(name)
|
|
48
|
+
return t if t
|
|
49
|
+
else
|
|
50
|
+
return test if test.name == name
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
43
56
|
def parallel_safe?
|
|
44
57
|
return true if @test_case.nil?
|
|
45
58
|
@test_case.parallel_safe?
|
|
46
59
|
end
|
|
47
60
|
|
|
61
|
+
def have_fixture?
|
|
62
|
+
return false if @test_case.nil?
|
|
63
|
+
return true if @test_case.method(:startup).owner != TestCase.singleton_class
|
|
64
|
+
return true if @test_case.method(:shutdown).owner != TestCase.singleton_class
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
# Runs the tests and/or suites contained in this
|
|
49
69
|
# TestSuite.
|
|
50
|
-
def run(
|
|
70
|
+
def run(worker_context, &progress_block)
|
|
71
|
+
run_context = worker_context.run_context
|
|
51
72
|
if run_context
|
|
52
73
|
runner_class = run_context.runner_class
|
|
53
74
|
else
|
|
54
75
|
runner_class = TestSuiteRunner
|
|
55
76
|
end
|
|
56
|
-
runner_class.new(self).run(
|
|
77
|
+
runner_class.new(self).run(worker_context) do |event, *args|
|
|
57
78
|
case event
|
|
58
79
|
when STARTED
|
|
59
80
|
@start_time = Time.now
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
require_relative '../util/observable'
|
|
8
8
|
require_relative '../testresult'
|
|
9
|
+
require_relative '../worker-context'
|
|
9
10
|
|
|
10
11
|
module Test
|
|
11
12
|
module Unit
|
|
@@ -40,13 +41,18 @@ module Test
|
|
|
40
41
|
start_time = Time.now
|
|
41
42
|
begin
|
|
42
43
|
with_listener(result) do
|
|
43
|
-
|
|
44
|
+
event_listener = lambda do |channel, value|
|
|
45
|
+
notify_listeners(channel, value)
|
|
46
|
+
end
|
|
47
|
+
@options[:event_listener] = event_listener
|
|
48
|
+
@options[:test_suite] = @suite
|
|
49
|
+
@test_suite_runner_class.run_all_tests(result, @options) do |run_context|
|
|
44
50
|
catch do |stop_tag|
|
|
45
51
|
result.stop_tag = stop_tag
|
|
46
52
|
notify_listeners(RESET, @suite.size)
|
|
47
53
|
notify_listeners(STARTED, result)
|
|
48
54
|
|
|
49
|
-
run_suite(result, run_context)
|
|
55
|
+
run_suite(result, run_context: run_context)
|
|
50
56
|
end
|
|
51
57
|
end
|
|
52
58
|
end
|
|
@@ -65,13 +71,12 @@ module Test
|
|
|
65
71
|
#
|
|
66
72
|
# See GitHub#38
|
|
67
73
|
# https://github.com/test-unit/test-unit/issues/38
|
|
68
|
-
def run_suite(result=nil, run_context
|
|
74
|
+
def run_suite(result=nil, run_context: nil)
|
|
69
75
|
if result.nil?
|
|
70
76
|
run
|
|
71
77
|
else
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
78
|
+
worker_context = WorkerContext.new(nil, run_context, result)
|
|
79
|
+
@suite.run(worker_context, &@options[:event_listener])
|
|
75
80
|
end
|
|
76
81
|
end
|
|
77
82
|
|
data/lib/test/unit/version.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#--
|
|
2
|
+
#
|
|
3
|
+
# Author:: Tsutomu Katsube.
|
|
4
|
+
# Copyright:: Copyright (c) 2025 Tsutomu Katsube. All rights reserved.
|
|
5
|
+
# License:: Ruby license.
|
|
6
|
+
|
|
7
|
+
module Test
|
|
8
|
+
module Unit
|
|
9
|
+
class WorkerContext
|
|
10
|
+
attr_reader :id
|
|
11
|
+
attr_reader :run_context
|
|
12
|
+
attr_reader :result
|
|
13
|
+
def initialize(id, run_context, result)
|
|
14
|
+
@id = id
|
|
15
|
+
@run_context = run_context
|
|
16
|
+
@result = result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: test-unit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.7.
|
|
4
|
+
version: 3.7.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kouhei Sutou
|
|
@@ -75,12 +75,16 @@ files:
|
|
|
75
75
|
- lib/test/unit/omission.rb
|
|
76
76
|
- lib/test/unit/pending.rb
|
|
77
77
|
- lib/test/unit/priority.rb
|
|
78
|
+
- lib/test/unit/process-test-result.rb
|
|
79
|
+
- lib/test/unit/process-worker.rb
|
|
78
80
|
- lib/test/unit/runner/console.rb
|
|
79
81
|
- lib/test/unit/runner/emacs.rb
|
|
80
82
|
- lib/test/unit/runner/xml.rb
|
|
81
83
|
- lib/test/unit/sub-test-result.rb
|
|
84
|
+
- lib/test/unit/test-process-run-context.rb
|
|
82
85
|
- lib/test/unit/test-run-context.rb
|
|
83
86
|
- lib/test/unit/test-suite-creator.rb
|
|
87
|
+
- lib/test/unit/test-suite-process-runner.rb
|
|
84
88
|
- lib/test/unit/test-suite-runner.rb
|
|
85
89
|
- lib/test/unit/test-suite-thread-runner.rb
|
|
86
90
|
- lib/test/unit/test-thread-run-context.rb
|
|
@@ -102,6 +106,7 @@ files:
|
|
|
102
106
|
- lib/test/unit/util/procwrapper.rb
|
|
103
107
|
- lib/test/unit/version.rb
|
|
104
108
|
- lib/test/unit/warning.rb
|
|
109
|
+
- lib/test/unit/worker-context.rb
|
|
105
110
|
- sample/adder.rb
|
|
106
111
|
- sample/subtracter.rb
|
|
107
112
|
- sample/test_adder.rb
|