test-queue 0.3.0 → 0.3.1
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/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
|