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 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