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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbdc02f98f8e6e07a67693e13a244f5d50f5b6e5
4
- data.tar.gz: 377d47b3e6b54fd3bbcb5d526d749480df6265dc
3
+ metadata.gz: 9254be449a22d8e9de7c7e36aa771229334e418b
4
+ data.tar.gz: 0ea2f7f7483c30a362c2e252f793e4e0b64fc5e7
5
5
  SHA512:
6
- metadata.gz: 6bc6bc66ba5d4e4fe5a5a7c6a19522f0cf5dc1e3d044d33c9b462b36ad01097cdbaf133102f53bc61e0559bb33419574a10f4d17af0e20449b80a346eeb36440
7
- data.tar.gz: 90885077e4c321b0f7c576b3ff7814e46aa43a98e7fb723cfbd770a2874f7655639cc5d4e8e1e943247779a31d092941303a03ebd8b3e886197ffbb09cd44033
6
+ metadata.gz: 4c75ab395ca7a1263b01f23e1208e3bc5701f65b15bdd45bbafc1ea11638bcb3e10df6f393480d5f1e5b591c0ddd40566f332b85e044d0b42b44a8645e3bfd5b
7
+ data.tar.gz: 62fa0a40aba6e1e0462ec04bb1b2cb6e54976d0cebc7fc7e0735b5d608b4d5f51d8d4c6b2956ab7b139f8345b76d152941056a7a122c781ef1f5c643f606ce4b
@@ -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 = "#{@procline} - Waiting for work"
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 = "#{@procline} - #{suite.respond_to?(:description) ? suite.description : suite}"
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)
@@ -45,7 +45,12 @@ module TestQueue
45
45
 
46
46
  @procline = $0
47
47
 
48
- @whitelist = Set.new
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 forced = ENV['TEST_QUEUE_FORCE']
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| forced.index(suite_name) }
63
+ @queue.sort_by! { |suite_name, path| @whitelist.index(suite_name) }
61
64
  end
62
65
 
63
- @whitelist.freeze
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
- kill_workers
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 @discovering_suites_pid.nil? && @queue.empty? && remote_workers == 0
414
+ until !awaiting_suites? && @queue.empty? && remote_workers == 0
375
415
  queue_status(@start_time, @queue.size, @workers.size, remote_workers)
376
416
 
377
- # Make sure our discovery process is still doing OK.
378
- if @discovering_suites_pid && Process.waitpid(@discovering_suites_pid, Process::WNOHANG) != nil
379
- @discovering_suites_pid = nil
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 obj = @queue.shift
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
- kill_workers
539
+ kill_subprocesses
481
540
  Kernel::abort("Aborting: #{message}")
482
541
  end
483
542
 
data/test-queue.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'test-queue'
3
- s.version = '0.3.0'
3
+ s.version = '0.3.1'
4
4
  s.summary = 'parallel test runner'
5
5
  s.description = 'minitest/rspec parallel test runner for CI environments'
6
6
 
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
@@ -79,3 +79,11 @@ refute_output_matches() {
79
79
  }
80
80
  return 0
81
81
  }
82
+
83
+ assert_equal() {
84
+ [ "$1" = "$2" ] || {
85
+ echo "Expected \"$1\" to equal \"$2\""
86
+ return 1
87
+ }
88
+ return 0
89
+ }
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.0
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-20 00:00:00.000000000 Z
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