test-queue 0.7.0 → 0.9.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 +22 -14
- 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 +9 -9
- 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 +16 -10
- data/lib/test_queue/runner/{rspec3.rb → rspec_ext.rb} +11 -11
- data/lib/test_queue/runner/testunit.rb +6 -3
- data/lib/test_queue/runner.rb +82 -98
- 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 +8 -47
- data/.github/workflows/test.yml +0 -94
- data/.gitignore +0 -7
- data/Appraisals +0 -43
- 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/rspec4.gemfile +0 -11
- data/gemfiles/testunit.gemfile +0 -7
- data/lib/test_queue/runner/minitest4.rb +0 -88
- data/lib/test_queue/runner/rspec2.rb +0 -44
- 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/rspec4.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 -3
- 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 -7
- data/test/samples/sample_use_shared_example2_spec.rb +0 -7
- 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,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'
|
@@ -33,7 +35,7 @@ module TestQueue
|
|
33
35
|
|
34
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,26 @@ 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
|
-
ENV['TEST_QUEUE_SOCKET'] ||
|
98
|
-
"/tmp/test_queue_#{$$}_#{object_id}.sock"
|
99
|
-
|
100
|
-
@relay =
|
101
|
-
relay ||
|
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"]
|
109
|
-
end
|
91
|
+
@socket = socket || ENV['TEST_QUEUE_SOCKET'] || "/tmp/test_queue_#{$$}_#{object_id}.sock"
|
92
|
+
@relay = relay || ENV['TEST_QUEUE_RELAY']
|
93
|
+
@remote_master_message = ENV['TEST_QUEUE_REMOTE_MASTER_MESSAGE'] if ENV.key?('TEST_QUEUE_REMOTE_MASTER_MESSAGE')
|
110
94
|
|
111
95
|
if @relay == @socket
|
112
|
-
|
96
|
+
warn '*** Detected TEST_QUEUE_RELAY == TEST_QUEUE_SOCKET. Disabling relay mode.'
|
113
97
|
@relay = nil
|
114
98
|
elsif @relay
|
115
99
|
@queue = []
|
@@ -144,7 +128,7 @@ module TestQueue
|
|
144
128
|
|
145
129
|
def summarize_internal
|
146
130
|
puts
|
147
|
-
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now
|
131
|
+
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now - @start_time)
|
148
132
|
puts
|
149
133
|
|
150
134
|
estatus = 0
|
@@ -167,9 +151,9 @@ module TestQueue
|
|
167
151
|
|
168
152
|
summarize_worker(worker)
|
169
153
|
|
170
|
-
@failures
|
154
|
+
@failures += worker.failure_output if worker.failure_output
|
171
155
|
|
172
|
-
puts
|
156
|
+
puts ' [%2d] %60s %4d suites in %.4fs (%s %s)' % [
|
173
157
|
worker.num,
|
174
158
|
worker.summary,
|
175
159
|
worker.suites.size,
|
@@ -181,16 +165,16 @@ module TestQueue
|
|
181
165
|
|
182
166
|
unless @failures.empty?
|
183
167
|
puts
|
184
|
-
puts
|
168
|
+
puts '==> Failures'
|
185
169
|
puts
|
186
170
|
puts @failures
|
187
171
|
end
|
188
172
|
|
189
|
-
|
173
|
+
unless relay?
|
190
174
|
unless @discovered_suites.empty?
|
191
175
|
estatus += 1
|
192
176
|
puts
|
193
|
-
puts
|
177
|
+
puts 'The following suites were discovered but were not run:'
|
194
178
|
puts
|
195
179
|
|
196
180
|
@discovered_suites.sort.each do |suite_name, path|
|
@@ -200,7 +184,7 @@ module TestQueue
|
|
200
184
|
unless unassigned_suites.empty?
|
201
185
|
estatus += 1
|
202
186
|
puts
|
203
|
-
puts
|
187
|
+
puts 'The following suites were not discovered but were run anyway:'
|
204
188
|
puts
|
205
189
|
unassigned_suites.sort.each do |suite_name, path|
|
206
190
|
puts "#{suite_name} - #{path}"
|
@@ -209,7 +193,7 @@ module TestQueue
|
|
209
193
|
unless misrun_suites.empty?
|
210
194
|
estatus += 1
|
211
195
|
puts
|
212
|
-
puts
|
196
|
+
puts 'The following suites were run on the wrong workers:'
|
213
197
|
puts
|
214
198
|
misrun_suites.each do |suite_name, path, target_host, target_pid, actual_host, actual_pid|
|
215
199
|
puts "#{suite_name} - #{path}: #{actual_host} (#{actual_pid}) - assigned to #{target_host} (#{target_pid})"
|
@@ -223,7 +207,7 @@ module TestQueue
|
|
223
207
|
|
224
208
|
summarize
|
225
209
|
|
226
|
-
estatus = @completed.inject(0){ |s, worker| s + (worker.status.exitstatus || 1)}
|
210
|
+
estatus = @completed.inject(0) { |s, worker| s + (worker.status.exitstatus || 1) }
|
227
211
|
[estatus, 255].min
|
228
212
|
end
|
229
213
|
|
@@ -231,8 +215,7 @@ module TestQueue
|
|
231
215
|
end
|
232
216
|
|
233
217
|
def stats_file
|
234
|
-
ENV['TEST_QUEUE_STATS'] ||
|
235
|
-
'.test_queue_stats'
|
218
|
+
ENV['TEST_QUEUE_STATS'] || '.test_queue_stats'
|
236
219
|
end
|
237
220
|
|
238
221
|
def execute_internal
|
@@ -250,19 +233,19 @@ module TestQueue
|
|
250
233
|
end
|
251
234
|
|
252
235
|
def start_master
|
253
|
-
|
236
|
+
unless relay?
|
254
237
|
if @socket =~ /\A(?:(.+):)?(\d+)\z/
|
255
238
|
address = $1 || '0.0.0.0'
|
256
239
|
port = $2.to_i
|
257
|
-
@socket = "
|
240
|
+
@socket = "#{$1}:#{$2}"
|
258
241
|
@server = TCPServer.new(address, port)
|
259
242
|
else
|
260
|
-
FileUtils.
|
243
|
+
FileUtils.rm_f(@socket)
|
261
244
|
@server = UNIXServer.new(@socket)
|
262
245
|
end
|
263
246
|
end
|
264
247
|
|
265
|
-
desc = "test-queue master (#{relay
|
248
|
+
desc = "test-queue master (#{relay? ? "relaying to #{@relay}" : @socket})"
|
266
249
|
puts "Starting #{desc}"
|
267
250
|
$0 = "#{desc} - #{@procline}"
|
268
251
|
end
|
@@ -271,19 +254,19 @@ module TestQueue
|
|
271
254
|
return unless relay?
|
272
255
|
|
273
256
|
sock = connect_to_relay
|
274
|
-
message = @remote_master_message ? " #{@remote_master_message}" :
|
275
|
-
message.gsub
|
257
|
+
message = @remote_master_message ? " #{@remote_master_message}" : ''
|
258
|
+
message = message.gsub(/(\r|\n)/, '') # Our "protocol" is newline-separated
|
276
259
|
sock.puts("TOKEN=#{@run_token}")
|
277
260
|
sock.puts("REMOTE MASTER #{@concurrency} #{Socket.gethostname} #{message}")
|
278
261
|
response = sock.gets.strip
|
279
|
-
unless response ==
|
280
|
-
|
262
|
+
unless response == 'OK'
|
263
|
+
warn "*** Got non-OK response from master: #{response}"
|
281
264
|
sock.close
|
282
265
|
exit! 1
|
283
266
|
end
|
284
267
|
sock.close
|
285
268
|
rescue Errno::ECONNREFUSED
|
286
|
-
|
269
|
+
warn "*** Unable to connect to relay #{@relay}. Aborting..."
|
287
270
|
exit! 1
|
288
271
|
end
|
289
272
|
|
@@ -297,12 +280,12 @@ module TestQueue
|
|
297
280
|
|
298
281
|
def spawn_workers
|
299
282
|
@concurrency.times do |i|
|
300
|
-
num = i+1
|
283
|
+
num = i + 1
|
301
284
|
|
302
285
|
pid = fork do
|
303
|
-
@server
|
286
|
+
@server&.close
|
304
287
|
|
305
|
-
iterator = Iterator.new(@test_framework, relay
|
288
|
+
iterator = Iterator.new(@test_framework, relay? ? @relay : @socket, method(:around_filter), early_failure_limit: @early_failure_limit, run_token: @run_token)
|
306
289
|
after_fork_internal(num, iterator)
|
307
290
|
ret = run_worker(iterator) || 0
|
308
291
|
cleanup_worker
|
@@ -324,12 +307,12 @@ module TestQueue
|
|
324
307
|
|
325
308
|
@discovering_suites_pid = fork do
|
326
309
|
terminate = false
|
327
|
-
Signal.trap(
|
310
|
+
Signal.trap('INT') { terminate = true }
|
328
311
|
|
329
|
-
$0 =
|
312
|
+
$0 = 'test-queue suite discovery process'
|
330
313
|
|
331
314
|
@test_framework.all_suite_files.each do |path|
|
332
|
-
@test_framework.suites_from_file(path).each do |suite_name,
|
315
|
+
@test_framework.suites_from_file(path).each do |suite_name, _suite|
|
333
316
|
Kernel.exit!(0) if terminate
|
334
317
|
|
335
318
|
@server.connect_address.connect do |sock|
|
@@ -344,13 +327,9 @@ module TestQueue
|
|
344
327
|
end
|
345
328
|
|
346
329
|
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.
|
330
|
+
# We're waiting to find all the allowlisted suites so we can run them in the correct order.
|
331
|
+
# Or we don't have any suites yet, but we're working on it.
|
332
|
+
if @awaited_suites.any? || @queue.empty? && !!@discovering_suites_pid
|
354
333
|
true
|
355
334
|
else
|
356
335
|
# It's fine to run any queued suites now.
|
@@ -379,9 +358,9 @@ module TestQueue
|
|
379
358
|
if @awaited_suites.delete?(suite_name) && @awaited_suites.empty?
|
380
359
|
# We've found all the allowlisted suites. Sort the queue to match the
|
381
360
|
# allowlist.
|
382
|
-
@queue.sort_by! { |
|
361
|
+
@queue.sort_by! { |queued_suite_name, _path| @allowlist.index(queued_suite_name) }
|
383
362
|
|
384
|
-
kill_suite_discovery_process(
|
363
|
+
kill_suite_discovery_process('INT')
|
385
364
|
end
|
386
365
|
end
|
387
366
|
|
@@ -396,7 +375,7 @@ module TestQueue
|
|
396
375
|
|
397
376
|
$0 = "test-queue worker [#{num}]"
|
398
377
|
puts
|
399
|
-
puts "==> Starting
|
378
|
+
puts "==> Starting #{$0} (#{Process.pid} on #{Socket.gethostname}) - iterating over #{iterator.sock}"
|
400
379
|
puts
|
401
380
|
|
402
381
|
after_fork(num)
|
@@ -408,7 +387,7 @@ module TestQueue
|
|
408
387
|
def prepare(concurrency)
|
409
388
|
end
|
410
389
|
|
411
|
-
def around_filter(
|
390
|
+
def around_filter(_suite)
|
412
391
|
yield
|
413
392
|
end
|
414
393
|
|
@@ -425,7 +404,7 @@ module TestQueue
|
|
425
404
|
puts " #{item.inspect}"
|
426
405
|
end
|
427
406
|
|
428
|
-
|
407
|
+
0 # exit status
|
429
408
|
end
|
430
409
|
|
431
410
|
def cleanup_worker
|
@@ -436,7 +415,7 @@ module TestQueue
|
|
436
415
|
worker.failure_output = ''
|
437
416
|
end
|
438
417
|
|
439
|
-
def reap_workers(blocking=true)
|
418
|
+
def reap_workers(blocking = true)
|
440
419
|
@workers.delete_if do |_, worker|
|
441
420
|
if Process.waitpid(worker.pid, blocking ? 0 : Process::WNOHANG).nil?
|
442
421
|
next false
|
@@ -455,35 +434,37 @@ module TestQueue
|
|
455
434
|
|
456
435
|
def collect_worker_data(worker)
|
457
436
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_output")
|
458
|
-
worker.output =
|
437
|
+
worker.output = File.binread(file)
|
459
438
|
FileUtils.rm(file)
|
460
439
|
end
|
461
440
|
|
462
441
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_suites")
|
463
|
-
worker.suites.replace(Marshal.load(
|
442
|
+
worker.suites.replace(Marshal.load(File.binread(file)))
|
464
443
|
FileUtils.rm(file)
|
465
444
|
end
|
466
445
|
end
|
467
446
|
|
468
447
|
def worker_completed(worker)
|
469
448
|
return if @aborting
|
449
|
+
|
470
450
|
@completed << worker
|
471
451
|
puts worker.output if ENV['TEST_QUEUE_VERBOSE'] || worker.status.exitstatus != 0
|
472
452
|
end
|
473
453
|
|
474
454
|
def distribute_queue
|
475
455
|
return if relay?
|
456
|
+
|
476
457
|
remote_workers = 0
|
477
458
|
|
478
459
|
until !awaiting_suites? && @queue.empty? && remote_workers == 0
|
479
460
|
queue_status(@start_time, @queue.size, @workers.size, remote_workers)
|
480
461
|
|
481
|
-
if status = reap_suite_discovery_process(false)
|
482
|
-
abort(
|
483
|
-
abort("Failed to discover #{@awaited_suites.sort.join(
|
462
|
+
if (status = reap_suite_discovery_process(false))
|
463
|
+
abort('Discovering suites failed.') unless status.success?
|
464
|
+
abort("Failed to discover #{@awaited_suites.sort.join(', ')} specified in TEST_QUEUE_FORCE") if @awaited_suites.any?
|
484
465
|
end
|
485
466
|
|
486
|
-
if
|
467
|
+
if @server.wait_readable(0.1).nil?
|
487
468
|
reap_workers(false) # check for worker deaths
|
488
469
|
else
|
489
470
|
sock = @server.accept
|
@@ -493,8 +474,8 @@ module TestQueue
|
|
493
474
|
token = token[TOKEN_REGEX, 1]
|
494
475
|
# If we have a remote master from a different test run, respond with "WRONG RUN", and it will consider the test run done.
|
495
476
|
if token != @run_token
|
496
|
-
message = token.nil? ?
|
497
|
-
|
477
|
+
message = token.nil? ? 'Worker sent no token to master' : "Worker from run #{token} connected to master"
|
478
|
+
warn "*** #{message} for run #{@run_token}; ignoring."
|
498
479
|
sock.write("WRONG RUN\n")
|
499
480
|
next
|
500
481
|
end
|
@@ -504,13 +485,13 @@ module TestQueue
|
|
504
485
|
hostname = $1
|
505
486
|
pid = Integer($2)
|
506
487
|
if awaiting_suites?
|
507
|
-
sock.write(Marshal.dump(
|
508
|
-
elsif obj = @queue.shift
|
488
|
+
sock.write(Marshal.dump('WAIT'))
|
489
|
+
elsif (obj = @queue.shift)
|
509
490
|
data = Marshal.dump(obj)
|
510
491
|
sock.write(data)
|
511
492
|
@assignments[obj] = [hostname, pid]
|
512
493
|
end
|
513
|
-
when /\AREMOTE MASTER (\d+) ([\w
|
494
|
+
when /\AREMOTE MASTER (\d+) ([\w.-]+)(?: (.+))?/
|
514
495
|
num = $1.to_i
|
515
496
|
remote_master = $2
|
516
497
|
remote_master_message = $3
|
@@ -518,9 +499,9 @@ module TestQueue
|
|
518
499
|
sock.write("OK\n")
|
519
500
|
remote_workers += num
|
520
501
|
|
521
|
-
message = "*** #{num} workers connected from #{remote_master} after #{Time.now
|
522
|
-
message
|
523
|
-
|
502
|
+
message = "*** #{num} workers connected from #{remote_master} after #{Time.now - @start_time}s"
|
503
|
+
message += " #{remote_master_message}" if remote_master_message
|
504
|
+
warn message
|
524
505
|
when /\AWORKER (\d+)/
|
525
506
|
data = sock.read($1.to_i)
|
526
507
|
worker = Marshal.load(data)
|
@@ -534,7 +515,7 @@ module TestQueue
|
|
534
515
|
# stop everything immediately and report the results.
|
535
516
|
break
|
536
517
|
else
|
537
|
-
|
518
|
+
warn("Ignoring unrecognized command: \"#{cmd}\"")
|
538
519
|
end
|
539
520
|
sock.close
|
540
521
|
end
|
@@ -557,7 +538,8 @@ module TestQueue
|
|
557
538
|
sock = TCPSocket.new(*@relay.split(':'))
|
558
539
|
rescue Errno::ECONNREFUSED => e
|
559
540
|
raise e if Time.now - start > @relay_connection_timeout
|
560
|
-
|
541
|
+
|
542
|
+
puts 'Master not yet available, sleeping...'
|
561
543
|
sleep 0.5
|
562
544
|
end
|
563
545
|
end
|
@@ -573,7 +555,7 @@ module TestQueue
|
|
573
555
|
sock.puts("WORKER #{data.bytesize}")
|
574
556
|
sock.write(data)
|
575
557
|
ensure
|
576
|
-
sock
|
558
|
+
sock&.close
|
577
559
|
end
|
578
560
|
|
579
561
|
def kill_subprocesses
|
@@ -582,21 +564,23 @@ module TestQueue
|
|
582
564
|
end
|
583
565
|
|
584
566
|
def kill_workers
|
585
|
-
@workers.each do |pid,
|
567
|
+
@workers.each do |pid, _worker|
|
586
568
|
Process.kill 'KILL', pid
|
587
569
|
end
|
588
570
|
|
589
571
|
reap_workers
|
590
572
|
end
|
591
573
|
|
592
|
-
def kill_suite_discovery_process(signal=
|
574
|
+
def kill_suite_discovery_process(signal = 'KILL')
|
593
575
|
return unless @discovering_suites_pid
|
576
|
+
|
594
577
|
Process.kill signal, @discovering_suites_pid
|
595
578
|
reap_suite_discovery_process
|
596
579
|
end
|
597
580
|
|
598
|
-
def reap_suite_discovery_process(blocking=true)
|
581
|
+
def reap_suite_discovery_process(blocking = true)
|
599
582
|
return unless @discovering_suites_pid
|
583
|
+
|
600
584
|
_, status = Process.waitpid2(@discovering_suites_pid, blocking ? 0 : Process::WNOHANG)
|
601
585
|
return unless status
|
602
586
|
|
@@ -612,7 +596,7 @@ module TestQueue
|
|
612
596
|
def abort(message)
|
613
597
|
@aborting = true
|
614
598
|
kill_subprocesses
|
615
|
-
Kernel
|
599
|
+
Kernel.abort("Aborting: #{message}")
|
616
600
|
end
|
617
601
|
|
618
602
|
# Subclasses can override to monitor the status of the queue.
|
data/lib/test_queue/stats.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TestQueue
|
2
4
|
class Stats
|
3
5
|
class Suite
|
@@ -19,17 +21,17 @@ module TestQueue
|
|
19
21
|
duration == other.duration &&
|
20
22
|
last_seen_at == other.last_seen_at
|
21
23
|
end
|
22
|
-
|
24
|
+
alias eql? ==
|
23
25
|
|
24
26
|
def to_h
|
25
|
-
{ :
|
27
|
+
{ name: name, path: path, duration: duration, last_seen_at: last_seen_at.to_i }
|
26
28
|
end
|
27
29
|
|
28
30
|
def self.from_hash(hash)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
new(hash.fetch(:name),
|
32
|
+
hash.fetch(:path),
|
33
|
+
hash.fetch(:duration),
|
34
|
+
Time.at(hash.fetch(:last_seen_at)))
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -56,7 +58,7 @@ module TestQueue
|
|
56
58
|
def save
|
57
59
|
prune
|
58
60
|
|
59
|
-
File.open(@path,
|
61
|
+
File.open(@path, 'wb') do |f|
|
60
62
|
Marshal.dump(to_h, f)
|
61
63
|
end
|
62
64
|
end
|
@@ -68,15 +70,17 @@ module TestQueue
|
|
68
70
|
def to_h
|
69
71
|
suites = @suites.each_value.map(&:to_h)
|
70
72
|
|
71
|
-
{ :
|
73
|
+
{ version: CURRENT_VERSION, suites: suites }
|
72
74
|
end
|
73
75
|
|
74
76
|
def load
|
75
77
|
data = begin
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
File.open(@path, 'rb') { |f| Marshal.load(f) }
|
79
|
+
rescue Errno::ENOENT, EOFError, TypeError, ArgumentError
|
80
|
+
# noop
|
81
|
+
end
|
82
|
+
return unless data.is_a?(Hash) && data[:version] == CURRENT_VERSION
|
83
|
+
|
80
84
|
data[:suites].each do |suite_hash|
|
81
85
|
suite = Suite.from_hash(suite_hash)
|
82
86
|
@suites[suite.name] = suite
|
@@ -87,7 +91,7 @@ module TestQueue
|
|
87
91
|
|
88
92
|
def prune
|
89
93
|
earliest = Time.now - EIGHT_DAYS_S
|
90
|
-
@suites.delete_if do |
|
94
|
+
@suites.delete_if do |_name, suite|
|
91
95
|
suite.last_seen_at < earliest
|
92
96
|
end
|
93
97
|
end
|