test-queue 0.7.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|