test-queue 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/test_queue/iterator.rb +5 -3
- data/lib/test_queue/runner.rb +75 -16
- data/test-queue.gemspec +1 -1
- data/test/minitest5.bats +37 -0
- data/test/testlib.bash +8 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9254be449a22d8e9de7c7e36aa771229334e418b
|
4
|
+
data.tar.gz: 0ea2f7f7483c30a362c2e252f793e4e0b64fc5e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c75ab395ca7a1263b01f23e1208e3bc5701f65b15bdd45bbafc1ea11638bcb3e10df6f393480d5f1e5b591c0ddd40566f332b85e044d0b42b44a8645e3bfd5b
|
7
|
+
data.tar.gz: 62fa0a40aba6e1e0462ec04bb1b2cb6e54976d0cebc7fc7e0735b5d608b4d5f51d8d4c6b2956ab7b139f8345b76d152941056a7a122c781ef1f5c643f606ce4b
|
data/lib/test_queue/iterator.rb
CHANGED
@@ -6,7 +6,6 @@ module TestQueue
|
|
6
6
|
@test_framework = test_framework
|
7
7
|
@done = false
|
8
8
|
@suite_stats = []
|
9
|
-
@procline = $0
|
10
9
|
@sock = sock
|
11
10
|
@filter = filter
|
12
11
|
if @sock =~ /^(.+):(\d+)$/
|
@@ -20,6 +19,8 @@ module TestQueue
|
|
20
19
|
def each
|
21
20
|
fail "already used this iterator. previous caller: #@done" if @done
|
22
21
|
|
22
|
+
procline = $0
|
23
|
+
|
23
24
|
while true
|
24
25
|
# If we've hit too many failures in one worker, assume the entire
|
25
26
|
# test suite is broken, and notify master so the run
|
@@ -39,7 +40,7 @@ module TestQueue
|
|
39
40
|
item = Marshal.load(data)
|
40
41
|
break if item.nil? || item.empty?
|
41
42
|
if item == "WAIT"
|
42
|
-
$0 = "#{
|
43
|
+
$0 = "#{procline} - Waiting for work"
|
43
44
|
sleep 0.1
|
44
45
|
next
|
45
46
|
end
|
@@ -49,7 +50,7 @@ module TestQueue
|
|
49
50
|
# Maybe we were told to load a suite that doesn't exist anymore.
|
50
51
|
next unless suite
|
51
52
|
|
52
|
-
$0 = "#{
|
53
|
+
$0 = "#{procline} - #{suite.respond_to?(:description) ? suite.description : suite}"
|
53
54
|
start = Time.now
|
54
55
|
if @filter
|
55
56
|
@filter.call(suite){ yield suite }
|
@@ -64,6 +65,7 @@ module TestQueue
|
|
64
65
|
end
|
65
66
|
rescue Errno::ENOENT, Errno::ECONNRESET, Errno::ECONNREFUSED
|
66
67
|
ensure
|
68
|
+
$0 = procline
|
67
69
|
@done = caller.first
|
68
70
|
File.open("/tmp/test_queue_worker_#{$$}_suites", "wb") do |f|
|
69
71
|
Marshal.dump(@suite_stats, f)
|
data/lib/test_queue/runner.rb
CHANGED
@@ -45,7 +45,12 @@ module TestQueue
|
|
45
45
|
|
46
46
|
@procline = $0
|
47
47
|
|
48
|
-
@whitelist =
|
48
|
+
@whitelist = if forced = ENV['TEST_QUEUE_FORCE']
|
49
|
+
forced.split(/\s*,\s*/)
|
50
|
+
else
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
@whitelist.freeze
|
49
54
|
|
50
55
|
all_files = @test_framework.all_suite_files.to_set
|
51
56
|
@queue = @stats.all_suites
|
@@ -53,14 +58,12 @@ module TestQueue
|
|
53
58
|
.sort_by { |suite| -suite.duration }
|
54
59
|
.map { |suite| [suite.name, suite.path] }
|
55
60
|
|
56
|
-
if
|
57
|
-
forced = forced.split(/\s*,\s*/)
|
58
|
-
@whitelist.merge(forced)
|
61
|
+
if @whitelist.any?
|
59
62
|
@queue.select! { |suite_name, path| @whitelist.include?(suite_name) }
|
60
|
-
@queue.sort_by! { |suite_name, path|
|
63
|
+
@queue.sort_by! { |suite_name, path| @whitelist.index(suite_name) }
|
61
64
|
end
|
62
65
|
|
63
|
-
@whitelist.
|
66
|
+
@awaited_suites = Set.new(@whitelist - @queue.map(&:first))
|
64
67
|
@original_queue = Set.new(@queue).freeze
|
65
68
|
|
66
69
|
@workers = {}
|
@@ -187,7 +190,7 @@ module TestQueue
|
|
187
190
|
ensure
|
188
191
|
stop_master
|
189
192
|
|
190
|
-
|
193
|
+
kill_subprocesses
|
191
194
|
end
|
192
195
|
|
193
196
|
def start_master
|
@@ -254,10 +257,24 @@ module TestQueue
|
|
254
257
|
end
|
255
258
|
|
256
259
|
def discover_suites
|
260
|
+
# Remote masters don't discover suites; the central master does and
|
261
|
+
# distributes them to remote masters.
|
257
262
|
return if relay?
|
263
|
+
|
264
|
+
# No need to discover suites if all whitelisted suites are already
|
265
|
+
# queued.
|
266
|
+
return if @whitelist.any? && @awaited_suites.empty?
|
267
|
+
|
258
268
|
@discovering_suites_pid = fork do
|
269
|
+
terminate = false
|
270
|
+
Signal.trap("INT") { terminate = true }
|
271
|
+
|
272
|
+
$0 = "test-queue suite discovery process"
|
273
|
+
|
259
274
|
@test_framework.all_suite_files.each do |path|
|
260
275
|
@test_framework.suites_from_file(path).each do |suite_name, suite|
|
276
|
+
Kernel.exit!(0) if terminate
|
277
|
+
|
261
278
|
@server.connect_address.connect do |sock|
|
262
279
|
sock.puts("NEW SUITE #{Marshal.dump([suite_name, path])}")
|
263
280
|
end
|
@@ -268,6 +285,21 @@ module TestQueue
|
|
268
285
|
end
|
269
286
|
end
|
270
287
|
|
288
|
+
def awaiting_suites?
|
289
|
+
case
|
290
|
+
when @awaited_suites.any?
|
291
|
+
# We're waiting to find all the whitelisted suites so we can run them
|
292
|
+
# in the correct order.
|
293
|
+
true
|
294
|
+
when @queue.empty? && !!@discovering_suites_pid
|
295
|
+
# We don't have any suites yet, but we're working on it.
|
296
|
+
true
|
297
|
+
else
|
298
|
+
# It's fine to run any queued suites now.
|
299
|
+
false
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
271
303
|
def enqueue_discovered_suite(suite_name, path)
|
272
304
|
if @whitelist.any? && !@whitelist.include?(suite_name)
|
273
305
|
return
|
@@ -282,6 +314,14 @@ module TestQueue
|
|
282
314
|
# the front of the queue. It's better to run a fast suite early than to
|
283
315
|
# run a slow suite late.
|
284
316
|
@queue.unshift [suite_name, path]
|
317
|
+
|
318
|
+
if @awaited_suites.delete?(suite_name) && @awaited_suites.empty?
|
319
|
+
# We've found all the whitelisted suites. Sort the queue to match the
|
320
|
+
# whitelist.
|
321
|
+
@queue.sort_by! { |suite_name, path| @whitelist.index(suite_name) }
|
322
|
+
|
323
|
+
kill_suite_discovery_process("INT")
|
324
|
+
end
|
285
325
|
end
|
286
326
|
|
287
327
|
def after_fork_internal(num, iterator)
|
@@ -371,13 +411,12 @@ module TestQueue
|
|
371
411
|
return if relay?
|
372
412
|
remote_workers = 0
|
373
413
|
|
374
|
-
until
|
414
|
+
until !awaiting_suites? && @queue.empty? && remote_workers == 0
|
375
415
|
queue_status(@start_time, @queue.size, @workers.size, remote_workers)
|
376
416
|
|
377
|
-
|
378
|
-
|
379
|
-
@
|
380
|
-
abort("Discovering suites failed.") unless $?.success?
|
417
|
+
if status = reap_suite_discovery_process(false)
|
418
|
+
abort("Discovering suites failed.") unless status.success?
|
419
|
+
abort("Failed to discover #{@awaited_suites.sort.join(", ")} specified in TEST_QUEUE_FORCE") if @awaited_suites.any?
|
381
420
|
end
|
382
421
|
|
383
422
|
if IO.select([@server], nil, nil, 0.1).nil?
|
@@ -388,11 +427,11 @@ module TestQueue
|
|
388
427
|
case cmd
|
389
428
|
when /^POP/
|
390
429
|
# If we have a slave from a different test run, don't respond, and it will consider the test run done.
|
391
|
-
if
|
430
|
+
if awaiting_suites?
|
431
|
+
sock.write(Marshal.dump("WAIT"))
|
432
|
+
elsif obj = @queue.shift
|
392
433
|
data = Marshal.dump(obj)
|
393
434
|
sock.write(data)
|
394
|
-
elsif @discovering_suites_pid
|
395
|
-
sock.write(Marshal.dump("WAIT"))
|
396
435
|
end
|
397
436
|
when /^SLAVE (\d+) ([\w\.-]+) (\w+)(?: (.+))?/
|
398
437
|
num = $1.to_i
|
@@ -462,6 +501,11 @@ module TestQueue
|
|
462
501
|
sock.close if sock
|
463
502
|
end
|
464
503
|
|
504
|
+
def kill_subprocesses
|
505
|
+
kill_workers
|
506
|
+
kill_suite_discovery_process
|
507
|
+
end
|
508
|
+
|
465
509
|
def kill_workers
|
466
510
|
@workers.each do |pid, worker|
|
467
511
|
Process.kill 'KILL', pid
|
@@ -470,6 +514,21 @@ module TestQueue
|
|
470
514
|
reap_workers
|
471
515
|
end
|
472
516
|
|
517
|
+
def kill_suite_discovery_process(signal="KILL")
|
518
|
+
return unless @discovering_suites_pid
|
519
|
+
Process.kill signal, @discovering_suites_pid
|
520
|
+
reap_suite_discovery_process
|
521
|
+
end
|
522
|
+
|
523
|
+
def reap_suite_discovery_process(blocking=true)
|
524
|
+
return unless @discovering_suites_pid
|
525
|
+
_, status = Process.waitpid2(@discovering_suites_pid, blocking ? 0 : Process::WNOHANG)
|
526
|
+
return unless status
|
527
|
+
|
528
|
+
@discovering_suites_pid = nil
|
529
|
+
status
|
530
|
+
end
|
531
|
+
|
473
532
|
# Stop the test run immediately.
|
474
533
|
#
|
475
534
|
# message - String message to print to the console when exiting.
|
@@ -477,7 +536,7 @@ module TestQueue
|
|
477
536
|
# Doesn't return.
|
478
537
|
def abort(message)
|
479
538
|
@aborting = true
|
480
|
-
|
539
|
+
kill_subprocesses
|
481
540
|
Kernel::abort("Aborting: #{message}")
|
482
541
|
end
|
483
542
|
|
data/test-queue.gemspec
CHANGED
data/test/minitest5.bats
CHANGED
@@ -37,6 +37,43 @@ teardown() {
|
|
37
37
|
refute_output_contains "MiniTestSleep9"
|
38
38
|
}
|
39
39
|
|
40
|
+
assert_test_queue_force_ordering() {
|
41
|
+
run bundle exec minitest-queue "$@"
|
42
|
+
assert_status 0
|
43
|
+
assert_output_contains "Starting test-queue master"
|
44
|
+
|
45
|
+
# Turn the list of suites that were run into a comma-separated list. Input
|
46
|
+
# looks like:
|
47
|
+
# SuiteName: . <0.001>
|
48
|
+
actual_tests=$(echo "$output" | \
|
49
|
+
egrep '^ .*: \.+ <' | \
|
50
|
+
sed -E -e 's/^ (.*): \.+.*/\1/' | \
|
51
|
+
tr '\n' ',' | \
|
52
|
+
sed -e 's/,$//')
|
53
|
+
assert_equal "$TEST_QUEUE_FORCE" "$actual_tests"
|
54
|
+
}
|
55
|
+
|
56
|
+
@test "TEST_QUEUE_FORCE ensures test ordering" {
|
57
|
+
export TEST_QUEUE_WORKERS=1 TEST_QUEUE_FORCE="Meme::when asked about cheeseburgers,MiniTestEqual"
|
58
|
+
|
59
|
+
# Without stats file
|
60
|
+
rm -f .test_queue_stats
|
61
|
+
assert_test_queue_force_ordering ./test/samples/sample_minitest5.rb ./test/samples/sample_minispec.rb
|
62
|
+
rm -f .test_queue_stats
|
63
|
+
assert_test_queue_force_ordering ./test/samples/sample_minispec.rb ./test/samples/sample_minitest5.rb
|
64
|
+
|
65
|
+
# With stats file
|
66
|
+
assert_test_queue_force_ordering ./test/samples/sample_minitest5.rb ./test/samples/sample_minispec.rb
|
67
|
+
assert_test_queue_force_ordering ./test/samples/sample_minispec.rb ./test/samples/sample_minitest5.rb
|
68
|
+
}
|
69
|
+
|
70
|
+
@test "minitest-queue fails if TEST_QUEUE_FORCE specifies nonexistent tests" {
|
71
|
+
export TEST_QUEUE_WORKERS=1 TEST_QUEUE_FORCE="MiniTestSleep21,DoesNotExist"
|
72
|
+
run bundle exec minitest-queue ./test/samples/*_minitest5.rb
|
73
|
+
assert_status 1
|
74
|
+
assert_output_contains "Failed to discover DoesNotExist specified in TEST_QUEUE_FORCE"
|
75
|
+
}
|
76
|
+
|
40
77
|
@test "multi-master succeeds when all tests pass" {
|
41
78
|
export TEST_QUEUE_RELAY_TOKEN=$(date | cksum | cut -d' ' -f1)
|
42
79
|
TEST_QUEUE_RELAY=0.0.0.0:12345 bundle exec minitest-queue ./test/samples/sample_minitest5.rb || true &
|
data/test/testlib.bash
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: test-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aman Gupta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-09-
|
11
|
+
date: 2016-09-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: minitest/rspec parallel test runner for CI environments
|
14
14
|
email: ruby@tmm1.net
|