test-queue 0.6.0 → 0.8.0
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/README.md +42 -42
- data/exe/cucumber-queue +2 -1
- data/exe/minitest-queue +3 -2
- data/exe/rspec-queue +2 -1
- data/exe/testunit-queue +2 -1
- data/lib/test-queue.rb +2 -0
- data/lib/test_queue/iterator.rb +23 -15
- data/lib/test_queue/runner/cucumber.rb +3 -0
- data/lib/test_queue/runner/{sample.rb → example.rb} +30 -9
- data/lib/test_queue/runner/minitest.rb +5 -3
- data/lib/test_queue/runner/minitest4.rb +12 -7
- data/lib/test_queue/runner/minitest5.rb +37 -22
- data/lib/test_queue/runner/puppet_lint.rb +6 -4
- data/lib/test_queue/runner/rspec.rb +15 -4
- data/lib/test_queue/runner/rspec2.rb +24 -24
- data/lib/test_queue/runner/rspec3.rb +11 -11
- data/lib/test_queue/runner/testunit.rb +6 -3
- data/lib/test_queue/runner.rb +93 -103
- data/lib/test_queue/stats.rb +17 -13
- data/lib/test_queue/test_framework.rb +2 -0
- data/lib/test_queue/version.rb +5 -0
- data/lib/test_queue.rb +1 -5
- metadata +7 -42
- data/.github/workflows/test.yml +0 -93
- data/.gitignore +0 -6
- data/Appraisals +0 -35
- data/Gemfile +0 -7
- data/Rakefile +0 -14
- data/gemfiles/cucumber1_3.gemfile +0 -9
- data/gemfiles/cucumber2_4.gemfile +0 -9
- data/gemfiles/minitest4.gemfile +0 -7
- data/gemfiles/minitest5.gemfile +0 -7
- data/gemfiles/rspec2.gemfile +0 -8
- data/gemfiles/rspec3.gemfile +0 -7
- data/gemfiles/testunit.gemfile +0 -7
- data/script/bootstrap +0 -13
- data/spec/stats_spec.rb +0 -79
- data/test/cucumber.bats +0 -57
- data/test/minitest4.bats +0 -34
- data/test/minitest5.bats +0 -194
- data/test/rspec2.bats +0 -46
- data/test/rspec3.bats +0 -56
- data/test/samples/features/bad.feature +0 -5
- data/test/samples/features/sample.feature +0 -25
- data/test/samples/features/sample2.feature +0 -29
- data/test/samples/features/step_definitions/common.rb +0 -19
- data/test/samples/sample_minispec.rb +0 -37
- data/test/samples/sample_minitest4.rb +0 -25
- data/test/samples/sample_minitest5.rb +0 -33
- data/test/samples/sample_rspec_helper.rb +0 -1
- data/test/samples/sample_shared_examples_for_spec.rb +0 -5
- data/test/samples/sample_spec.rb +0 -25
- data/test/samples/sample_split_spec.rb +0 -17
- data/test/samples/sample_testunit.rb +0 -25
- data/test/samples/sample_use_shared_example1_spec.rb +0 -8
- data/test/samples/sample_use_shared_example2_spec.rb +0 -8
- data/test/sleepy_runner.rb +0 -16
- data/test/testlib.bash +0 -89
- data/test/testunit.bats +0 -20
- data/test-queue.gemspec +0 -21
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ::RSpec::Core::ExampleGroup
|
2
4
|
def self.failure_count
|
3
|
-
examples.map {|e| e.execution_result[:status] ==
|
5
|
+
examples.map { |e| e.execution_result[:status] == 'failed' }.length
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
@@ -14,30 +16,28 @@ module RSpec::Core
|
|
14
16
|
|
15
17
|
def run_each(iterator)
|
16
18
|
@configuration.reporter.report(0, @configuration.randomize? ? @configuration.seed : nil) do |reporter|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
puts(" <%.3f>" % diff)
|
19
|
+
@configuration.run_hook(:before, :suite)
|
20
|
+
iterator.map { |g|
|
21
|
+
if g.is_a? ::RSpec::Core::Example
|
22
|
+
print " #{g.full_description}: "
|
23
|
+
example = g
|
24
|
+
g = example.example_group
|
25
|
+
::RSpec.world.filtered_examples.clear
|
26
|
+
examples = [example]
|
27
|
+
examples.extend(::RSpec::Core::Extensions::Ordered::Examples)
|
28
|
+
::RSpec.world.filtered_examples[g] = examples
|
29
|
+
else
|
30
|
+
print " #{g.description}: "
|
31
|
+
end
|
32
|
+
start = Time.now
|
33
|
+
ret = g.run(reporter)
|
34
|
+
diff = Time.now - start
|
35
|
+
puts(' <%.3f>' % diff)
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
ret
|
38
|
+
}.all? ? 0 : @configuration.failure_exit_code
|
39
|
+
ensure
|
40
|
+
@configuration.run_hook(:after, :suite)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ::RSpec::Core::ExampleGroup
|
2
4
|
def self.failure_count
|
3
|
-
examples.map {|e| e.execution_result.status ==
|
5
|
+
examples.map { |e| e.execution_result.status == 'failed' }.length
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
@@ -9,13 +11,11 @@ module RSpec::Core
|
|
9
11
|
unless Configuration.method_defined?(:with_suite_hooks)
|
10
12
|
class Configuration
|
11
13
|
def with_suite_hooks
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
hooks.run(:after, :suite, hook_context)
|
18
|
-
end
|
14
|
+
hook_context = SuiteHookContext.new
|
15
|
+
hooks.run(:before, :suite, hook_context)
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
hooks.run(:after, :suite, hook_context)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -41,14 +41,14 @@ module RSpec::Core
|
|
41
41
|
print " #{g.description}: "
|
42
42
|
end
|
43
43
|
ret = g.run(reporter)
|
44
|
-
diff = Time.now-start
|
45
|
-
puts(
|
44
|
+
diff = Time.now - start
|
45
|
+
puts(' <%.3f>' % diff)
|
46
46
|
|
47
47
|
ret
|
48
48
|
}.all? ? 0 : @configuration.failure_exit_code
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
|
-
|
52
|
+
alias run_each run_specs
|
53
53
|
end
|
54
54
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../runner'
|
2
4
|
|
3
5
|
gem 'test-unit'
|
@@ -28,7 +30,7 @@ class Test::Unit::TestSuite
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def failure_count
|
31
|
-
(@iterator || @tests).
|
33
|
+
(@iterator || @tests).sum { |t| t.instance_variable_get(:@_result).failure_count }
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -37,13 +39,14 @@ module TestQueue
|
|
37
39
|
class TestUnit < Runner
|
38
40
|
def initialize
|
39
41
|
if Test::Unit::Collector::Descendant.new.collect.tests.any?
|
40
|
-
|
42
|
+
raise 'Do not `require` test files. Pass them via ARGV instead and they will be required as needed.'
|
41
43
|
end
|
44
|
+
|
42
45
|
super(TestFramework::TestUnit.new)
|
43
46
|
end
|
44
47
|
|
45
48
|
def run_worker(iterator)
|
46
|
-
@suite = Test::Unit::TestSuite.new(
|
49
|
+
@suite = Test::Unit::TestSuite.new('specified by test-queue master')
|
47
50
|
@suite.iterator = iterator
|
48
51
|
res = Test::Unit::UI::Console::TestRunner.new(@suite).start
|
49
52
|
res.run_count - res.pass_count
|
data/lib/test_queue/runner.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
require 'socket'
|
3
5
|
require 'fileutils'
|
@@ -31,9 +33,9 @@ module TestQueue
|
|
31
33
|
attr_accessor :concurrency, :exit_when_done
|
32
34
|
attr_reader :stats
|
33
35
|
|
34
|
-
TOKEN_REGEX =
|
36
|
+
TOKEN_REGEX = /\ATOKEN=(\w+)/
|
35
37
|
|
36
|
-
def initialize(test_framework, concurrency=nil, socket=nil, relay=nil)
|
38
|
+
def initialize(test_framework, concurrency = nil, socket = nil, relay = nil)
|
37
39
|
@test_framework = test_framework
|
38
40
|
@stats = Stats.new(stats_file)
|
39
41
|
|
@@ -48,7 +50,7 @@ module TestQueue
|
|
48
50
|
|
49
51
|
@procline = $0
|
50
52
|
|
51
|
-
@allowlist = if forced = ENV['TEST_QUEUE_FORCE']
|
53
|
+
@allowlist = if (forced = ENV['TEST_QUEUE_FORCE'])
|
52
54
|
forced.split(/\s*,\s*/)
|
53
55
|
else
|
54
56
|
[]
|
@@ -57,13 +59,13 @@ module TestQueue
|
|
57
59
|
|
58
60
|
all_files = @test_framework.all_suite_files.to_set
|
59
61
|
@queue = @stats.all_suites
|
60
|
-
|
61
|
-
|
62
|
-
|
62
|
+
.select { |suite| all_files.include?(suite.path) }
|
63
|
+
.sort_by { |suite| -suite.duration }
|
64
|
+
.map { |suite| [suite.name, suite.path] }
|
63
65
|
|
64
66
|
if @allowlist.any?
|
65
|
-
@queue.select! { |suite_name,
|
66
|
-
@queue.sort_by! { |suite_name,
|
67
|
+
@queue.select! { |suite_name, _path| @allowlist.include?(suite_name) }
|
68
|
+
@queue.sort_by! { |suite_name, _path| @allowlist.index(suite_name) }
|
67
69
|
end
|
68
70
|
|
69
71
|
@awaited_suites = Set.new(@allowlist)
|
@@ -72,44 +74,32 @@ module TestQueue
|
|
72
74
|
@workers = {}
|
73
75
|
@completed = []
|
74
76
|
|
75
|
-
@concurrency =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
2
|
84
|
-
end
|
77
|
+
@concurrency = concurrency || ENV['TEST_QUEUE_WORKERS']&.to_i ||
|
78
|
+
if File.exist?('/proc/cpuinfo')
|
79
|
+
File.read('/proc/cpuinfo').split("\n").grep(/processor/).size
|
80
|
+
elsif RUBY_PLATFORM.include?('darwin')
|
81
|
+
`/usr/sbin/sysctl -n hw.activecpu`.to_i
|
82
|
+
else
|
83
|
+
2
|
84
|
+
end
|
85
85
|
unless @concurrency > 0
|
86
86
|
raise ArgumentError, "Worker count (#{@concurrency}) must be greater than 0"
|
87
87
|
end
|
88
88
|
|
89
|
-
@relay_connection_timeout =
|
90
|
-
(ENV['TEST_QUEUE_RELAY_TIMEOUT'] && ENV['TEST_QUEUE_RELAY_TIMEOUT'].to_i) ||
|
91
|
-
30
|
92
|
-
|
89
|
+
@relay_connection_timeout = ENV['TEST_QUEUE_RELAY_TIMEOUT']&.to_i || 30
|
93
90
|
@run_token = ENV['TEST_QUEUE_RELAY_TOKEN'] || SecureRandom.hex(8)
|
94
|
-
|
95
|
-
@
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
ENV['TEST_QUEUE_RELAY']
|
103
|
-
|
104
|
-
@remote_master_message = if ENV.has_key?("TEST_QUEUE_REMOTE_MASTER_MESSAGE")
|
105
|
-
ENV["TEST_QUEUE_REMOTE_MASTER_MESSAGE"]
|
106
|
-
elsif ENV.has_key?("TEST_QUEUE_SLAVE_MESSAGE")
|
107
|
-
warn("`TEST_QUEUE_SLAVE_MESSAGE` is deprecated. Use `TEST_QUEUE_REMOTE_MASTER_MESSAGE` instead.")
|
108
|
-
ENV["TEST_QUEUE_SLAVE_MESSAGE"]
|
91
|
+
@socket = socket || ENV['TEST_QUEUE_SOCKET'] || "/tmp/test_queue_#{$$}_#{object_id}.sock"
|
92
|
+
@relay = relay || ENV['TEST_QUEUE_RELAY']
|
93
|
+
|
94
|
+
@remote_master_message = if ENV.key?('TEST_QUEUE_REMOTE_MASTER_MESSAGE')
|
95
|
+
ENV['TEST_QUEUE_REMOTE_MASTER_MESSAGE']
|
96
|
+
elsif ENV.key?('TEST_QUEUE_SLAVE_MESSAGE')
|
97
|
+
warn('`TEST_QUEUE_SLAVE_MESSAGE` is deprecated. Use `TEST_QUEUE_REMOTE_MASTER_MESSAGE` instead.')
|
98
|
+
ENV['TEST_QUEUE_SLAVE_MESSAGE']
|
109
99
|
end
|
110
100
|
|
111
101
|
if @relay == @socket
|
112
|
-
|
102
|
+
warn '*** Detected TEST_QUEUE_RELAY == TEST_QUEUE_SOCKET. Disabling relay mode.'
|
113
103
|
@relay = nil
|
114
104
|
elsif @relay
|
115
105
|
@queue = []
|
@@ -144,7 +134,7 @@ module TestQueue
|
|
144
134
|
|
145
135
|
def summarize_internal
|
146
136
|
puts
|
147
|
-
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now
|
137
|
+
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now - @start_time)
|
148
138
|
puts
|
149
139
|
|
150
140
|
estatus = 0
|
@@ -167,9 +157,9 @@ module TestQueue
|
|
167
157
|
|
168
158
|
summarize_worker(worker)
|
169
159
|
|
170
|
-
@failures
|
160
|
+
@failures += worker.failure_output if worker.failure_output
|
171
161
|
|
172
|
-
puts
|
162
|
+
puts ' [%2d] %60s %4d suites in %.4fs (%s %s)' % [
|
173
163
|
worker.num,
|
174
164
|
worker.summary,
|
175
165
|
worker.suites.size,
|
@@ -181,16 +171,16 @@ module TestQueue
|
|
181
171
|
|
182
172
|
unless @failures.empty?
|
183
173
|
puts
|
184
|
-
puts
|
174
|
+
puts '==> Failures'
|
185
175
|
puts
|
186
176
|
puts @failures
|
187
177
|
end
|
188
178
|
|
189
|
-
|
179
|
+
unless relay?
|
190
180
|
unless @discovered_suites.empty?
|
191
181
|
estatus += 1
|
192
182
|
puts
|
193
|
-
puts
|
183
|
+
puts 'The following suites were discovered but were not run:'
|
194
184
|
puts
|
195
185
|
|
196
186
|
@discovered_suites.sort.each do |suite_name, path|
|
@@ -200,7 +190,7 @@ module TestQueue
|
|
200
190
|
unless unassigned_suites.empty?
|
201
191
|
estatus += 1
|
202
192
|
puts
|
203
|
-
puts
|
193
|
+
puts 'The following suites were not discovered but were run anyway:'
|
204
194
|
puts
|
205
195
|
unassigned_suites.sort.each do |suite_name, path|
|
206
196
|
puts "#{suite_name} - #{path}"
|
@@ -209,7 +199,7 @@ module TestQueue
|
|
209
199
|
unless misrun_suites.empty?
|
210
200
|
estatus += 1
|
211
201
|
puts
|
212
|
-
puts
|
202
|
+
puts 'The following suites were run on the wrong workers:'
|
213
203
|
puts
|
214
204
|
misrun_suites.each do |suite_name, path, target_host, target_pid, actual_host, actual_pid|
|
215
205
|
puts "#{suite_name} - #{path}: #{actual_host} (#{actual_pid}) - assigned to #{target_host} (#{target_pid})"
|
@@ -223,7 +213,7 @@ module TestQueue
|
|
223
213
|
|
224
214
|
summarize
|
225
215
|
|
226
|
-
estatus = @completed.inject(0){ |s, worker| s + (worker.status.exitstatus || 1)}
|
216
|
+
estatus = @completed.inject(0) { |s, worker| s + (worker.status.exitstatus || 1) }
|
227
217
|
[estatus, 255].min
|
228
218
|
end
|
229
219
|
|
@@ -231,8 +221,7 @@ module TestQueue
|
|
231
221
|
end
|
232
222
|
|
233
223
|
def stats_file
|
234
|
-
ENV['TEST_QUEUE_STATS'] ||
|
235
|
-
'.test_queue_stats'
|
224
|
+
ENV['TEST_QUEUE_STATS'] || '.test_queue_stats'
|
236
225
|
end
|
237
226
|
|
238
227
|
def execute_internal
|
@@ -250,19 +239,19 @@ module TestQueue
|
|
250
239
|
end
|
251
240
|
|
252
241
|
def start_master
|
253
|
-
|
254
|
-
if @socket =~
|
242
|
+
unless relay?
|
243
|
+
if @socket =~ /\A(?:(.+):)?(\d+)\z/
|
255
244
|
address = $1 || '0.0.0.0'
|
256
245
|
port = $2.to_i
|
257
|
-
@socket = "
|
246
|
+
@socket = "#{$1}:#{$2}"
|
258
247
|
@server = TCPServer.new(address, port)
|
259
248
|
else
|
260
|
-
FileUtils.
|
249
|
+
FileUtils.rm_f(@socket)
|
261
250
|
@server = UNIXServer.new(@socket)
|
262
251
|
end
|
263
252
|
end
|
264
253
|
|
265
|
-
desc = "test-queue master (#{relay
|
254
|
+
desc = "test-queue master (#{relay? ? "relaying to #{@relay}" : @socket})"
|
266
255
|
puts "Starting #{desc}"
|
267
256
|
$0 = "#{desc} - #{@procline}"
|
268
257
|
end
|
@@ -271,19 +260,19 @@ module TestQueue
|
|
271
260
|
return unless relay?
|
272
261
|
|
273
262
|
sock = connect_to_relay
|
274
|
-
message = @remote_master_message ? " #{@remote_master_message}" :
|
275
|
-
message.gsub
|
263
|
+
message = @remote_master_message ? " #{@remote_master_message}" : ''
|
264
|
+
message = message.gsub(/(\r|\n)/, '') # Our "protocol" is newline-separated
|
276
265
|
sock.puts("TOKEN=#{@run_token}")
|
277
266
|
sock.puts("REMOTE MASTER #{@concurrency} #{Socket.gethostname} #{message}")
|
278
267
|
response = sock.gets.strip
|
279
|
-
unless response ==
|
280
|
-
|
268
|
+
unless response == 'OK'
|
269
|
+
warn "*** Got non-OK response from master: #{response}"
|
281
270
|
sock.close
|
282
271
|
exit! 1
|
283
272
|
end
|
284
273
|
sock.close
|
285
274
|
rescue Errno::ECONNREFUSED
|
286
|
-
|
275
|
+
warn "*** Unable to connect to relay #{@relay}. Aborting..."
|
287
276
|
exit! 1
|
288
277
|
end
|
289
278
|
|
@@ -297,12 +286,12 @@ module TestQueue
|
|
297
286
|
|
298
287
|
def spawn_workers
|
299
288
|
@concurrency.times do |i|
|
300
|
-
num = i+1
|
289
|
+
num = i + 1
|
301
290
|
|
302
291
|
pid = fork do
|
303
|
-
@server
|
292
|
+
@server&.close
|
304
293
|
|
305
|
-
iterator = Iterator.new(@test_framework, relay
|
294
|
+
iterator = Iterator.new(@test_framework, relay? ? @relay : @socket, method(:around_filter), early_failure_limit: @early_failure_limit, run_token: @run_token)
|
306
295
|
after_fork_internal(num, iterator)
|
307
296
|
ret = run_worker(iterator) || 0
|
308
297
|
cleanup_worker
|
@@ -324,12 +313,12 @@ module TestQueue
|
|
324
313
|
|
325
314
|
@discovering_suites_pid = fork do
|
326
315
|
terminate = false
|
327
|
-
Signal.trap(
|
316
|
+
Signal.trap('INT') { terminate = true }
|
328
317
|
|
329
|
-
$0 =
|
318
|
+
$0 = 'test-queue suite discovery process'
|
330
319
|
|
331
320
|
@test_framework.all_suite_files.each do |path|
|
332
|
-
@test_framework.suites_from_file(path).each do |suite_name,
|
321
|
+
@test_framework.suites_from_file(path).each do |suite_name, _suite|
|
333
322
|
Kernel.exit!(0) if terminate
|
334
323
|
|
335
324
|
@server.connect_address.connect do |sock|
|
@@ -344,13 +333,9 @@ module TestQueue
|
|
344
333
|
end
|
345
334
|
|
346
335
|
def awaiting_suites?
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
# in the correct order.
|
351
|
-
true
|
352
|
-
when @queue.empty? && !!@discovering_suites_pid
|
353
|
-
# We don't have any suites yet, but we're working on it.
|
336
|
+
# We're waiting to find all the allowlisted suites so we can run them in the correct order.
|
337
|
+
# Or we don't have any suites yet, but we're working on it.
|
338
|
+
if @awaited_suites.any? || @queue.empty? && !!@discovering_suites_pid
|
354
339
|
true
|
355
340
|
else
|
356
341
|
# It's fine to run any queued suites now.
|
@@ -379,9 +364,9 @@ module TestQueue
|
|
379
364
|
if @awaited_suites.delete?(suite_name) && @awaited_suites.empty?
|
380
365
|
# We've found all the allowlisted suites. Sort the queue to match the
|
381
366
|
# allowlist.
|
382
|
-
@queue.sort_by! { |
|
367
|
+
@queue.sort_by! { |queued_suite_name, _path| @allowlist.index(queued_suite_name) }
|
383
368
|
|
384
|
-
kill_suite_discovery_process(
|
369
|
+
kill_suite_discovery_process('INT')
|
385
370
|
end
|
386
371
|
end
|
387
372
|
|
@@ -396,7 +381,7 @@ module TestQueue
|
|
396
381
|
|
397
382
|
$0 = "test-queue worker [#{num}]"
|
398
383
|
puts
|
399
|
-
puts "==> Starting
|
384
|
+
puts "==> Starting #{$0} (#{Process.pid} on #{Socket.gethostname}) - iterating over #{iterator.sock}"
|
400
385
|
puts
|
401
386
|
|
402
387
|
after_fork(num)
|
@@ -408,7 +393,7 @@ module TestQueue
|
|
408
393
|
def prepare(concurrency)
|
409
394
|
end
|
410
395
|
|
411
|
-
def around_filter(
|
396
|
+
def around_filter(_suite)
|
412
397
|
yield
|
413
398
|
end
|
414
399
|
|
@@ -425,7 +410,7 @@ module TestQueue
|
|
425
410
|
puts " #{item.inspect}"
|
426
411
|
end
|
427
412
|
|
428
|
-
|
413
|
+
0 # exit status
|
429
414
|
end
|
430
415
|
|
431
416
|
def cleanup_worker
|
@@ -436,7 +421,7 @@ module TestQueue
|
|
436
421
|
worker.failure_output = ''
|
437
422
|
end
|
438
423
|
|
439
|
-
def reap_workers(blocking=true)
|
424
|
+
def reap_workers(blocking = true)
|
440
425
|
@workers.delete_if do |_, worker|
|
441
426
|
if Process.waitpid(worker.pid, blocking ? 0 : Process::WNOHANG).nil?
|
442
427
|
next false
|
@@ -455,35 +440,37 @@ module TestQueue
|
|
455
440
|
|
456
441
|
def collect_worker_data(worker)
|
457
442
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_output")
|
458
|
-
worker.output =
|
443
|
+
worker.output = File.binread(file)
|
459
444
|
FileUtils.rm(file)
|
460
445
|
end
|
461
446
|
|
462
447
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_suites")
|
463
|
-
worker.suites.replace(Marshal.load(
|
448
|
+
worker.suites.replace(Marshal.load(File.binread(file)))
|
464
449
|
FileUtils.rm(file)
|
465
450
|
end
|
466
451
|
end
|
467
452
|
|
468
453
|
def worker_completed(worker)
|
469
454
|
return if @aborting
|
455
|
+
|
470
456
|
@completed << worker
|
471
457
|
puts worker.output if ENV['TEST_QUEUE_VERBOSE'] || worker.status.exitstatus != 0
|
472
458
|
end
|
473
459
|
|
474
460
|
def distribute_queue
|
475
461
|
return if relay?
|
462
|
+
|
476
463
|
remote_workers = 0
|
477
464
|
|
478
465
|
until !awaiting_suites? && @queue.empty? && remote_workers == 0
|
479
466
|
queue_status(@start_time, @queue.size, @workers.size, remote_workers)
|
480
467
|
|
481
|
-
if status = reap_suite_discovery_process(false)
|
482
|
-
abort(
|
483
|
-
abort("Failed to discover #{@awaited_suites.sort.join(
|
468
|
+
if (status = reap_suite_discovery_process(false))
|
469
|
+
abort('Discovering suites failed.') unless status.success?
|
470
|
+
abort("Failed to discover #{@awaited_suites.sort.join(', ')} specified in TEST_QUEUE_FORCE") if @awaited_suites.any?
|
484
471
|
end
|
485
472
|
|
486
|
-
if
|
473
|
+
if @server.wait_readable(0.1).nil?
|
487
474
|
reap_workers(false) # check for worker deaths
|
488
475
|
else
|
489
476
|
sock = @server.accept
|
@@ -493,24 +480,24 @@ module TestQueue
|
|
493
480
|
token = token[TOKEN_REGEX, 1]
|
494
481
|
# If we have a remote master from a different test run, respond with "WRONG RUN", and it will consider the test run done.
|
495
482
|
if token != @run_token
|
496
|
-
message = token.nil? ?
|
497
|
-
|
483
|
+
message = token.nil? ? 'Worker sent no token to master' : "Worker from run #{token} connected to master"
|
484
|
+
warn "*** #{message} for run #{@run_token}; ignoring."
|
498
485
|
sock.write("WRONG RUN\n")
|
499
486
|
next
|
500
487
|
end
|
501
488
|
|
502
489
|
case cmd
|
503
|
-
when
|
490
|
+
when /\APOP (\S+) (\d+)/
|
504
491
|
hostname = $1
|
505
492
|
pid = Integer($2)
|
506
493
|
if awaiting_suites?
|
507
|
-
sock.write(Marshal.dump(
|
508
|
-
elsif obj = @queue.shift
|
494
|
+
sock.write(Marshal.dump('WAIT'))
|
495
|
+
elsif (obj = @queue.shift)
|
509
496
|
data = Marshal.dump(obj)
|
510
497
|
sock.write(data)
|
511
498
|
@assignments[obj] = [hostname, pid]
|
512
499
|
end
|
513
|
-
when
|
500
|
+
when /\AREMOTE MASTER (\d+) ([\w.-]+)(?: (.+))?/
|
514
501
|
num = $1.to_i
|
515
502
|
remote_master = $2
|
516
503
|
remote_master_message = $3
|
@@ -518,23 +505,23 @@ module TestQueue
|
|
518
505
|
sock.write("OK\n")
|
519
506
|
remote_workers += num
|
520
507
|
|
521
|
-
message = "*** #{num} workers connected from #{remote_master} after #{Time.now
|
522
|
-
message
|
523
|
-
|
524
|
-
when
|
508
|
+
message = "*** #{num} workers connected from #{remote_master} after #{Time.now - @start_time}s"
|
509
|
+
message += " #{remote_master_message}" if remote_master_message
|
510
|
+
warn message
|
511
|
+
when /\AWORKER (\d+)/
|
525
512
|
data = sock.read($1.to_i)
|
526
513
|
worker = Marshal.load(data)
|
527
514
|
worker_completed(worker)
|
528
515
|
remote_workers -= 1
|
529
|
-
when
|
516
|
+
when /\ANEW SUITE (.+)/
|
530
517
|
suite_name, path = Marshal.load($1)
|
531
518
|
enqueue_discovered_suite(suite_name, path)
|
532
|
-
when
|
519
|
+
when /\AKABOOM/
|
533
520
|
# worker reporting an abnormal number of test failures;
|
534
521
|
# stop everything immediately and report the results.
|
535
522
|
break
|
536
523
|
else
|
537
|
-
|
524
|
+
warn("Ignoring unrecognized command: \"#{cmd}\"")
|
538
525
|
end
|
539
526
|
sock.close
|
540
527
|
end
|
@@ -557,7 +544,8 @@ module TestQueue
|
|
557
544
|
sock = TCPSocket.new(*@relay.split(':'))
|
558
545
|
rescue Errno::ECONNREFUSED => e
|
559
546
|
raise e if Time.now - start > @relay_connection_timeout
|
560
|
-
|
547
|
+
|
548
|
+
puts 'Master not yet available, sleeping...'
|
561
549
|
sleep 0.5
|
562
550
|
end
|
563
551
|
end
|
@@ -573,7 +561,7 @@ module TestQueue
|
|
573
561
|
sock.puts("WORKER #{data.bytesize}")
|
574
562
|
sock.write(data)
|
575
563
|
ensure
|
576
|
-
sock
|
564
|
+
sock&.close
|
577
565
|
end
|
578
566
|
|
579
567
|
def kill_subprocesses
|
@@ -582,21 +570,23 @@ module TestQueue
|
|
582
570
|
end
|
583
571
|
|
584
572
|
def kill_workers
|
585
|
-
@workers.each do |pid,
|
573
|
+
@workers.each do |pid, _worker|
|
586
574
|
Process.kill 'KILL', pid
|
587
575
|
end
|
588
576
|
|
589
577
|
reap_workers
|
590
578
|
end
|
591
579
|
|
592
|
-
def kill_suite_discovery_process(signal=
|
580
|
+
def kill_suite_discovery_process(signal = 'KILL')
|
593
581
|
return unless @discovering_suites_pid
|
582
|
+
|
594
583
|
Process.kill signal, @discovering_suites_pid
|
595
584
|
reap_suite_discovery_process
|
596
585
|
end
|
597
586
|
|
598
|
-
def reap_suite_discovery_process(blocking=true)
|
587
|
+
def reap_suite_discovery_process(blocking = true)
|
599
588
|
return unless @discovering_suites_pid
|
589
|
+
|
600
590
|
_, status = Process.waitpid2(@discovering_suites_pid, blocking ? 0 : Process::WNOHANG)
|
601
591
|
return unless status
|
602
592
|
|
@@ -612,7 +602,7 @@ module TestQueue
|
|
612
602
|
def abort(message)
|
613
603
|
@aborting = true
|
614
604
|
kill_subprocesses
|
615
|
-
Kernel
|
605
|
+
Kernel.abort("Aborting: #{message}")
|
616
606
|
end
|
617
607
|
|
618
608
|
# Subclasses can override to monitor the status of the queue.
|