test-queue 0.7.0 → 0.8.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 +5 -3
- data/lib/test_queue/runner/minitest4.rb +12 -7
- 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 +14 -3
- data/lib/test_queue/runner/rspec2.rb +24 -24
- data/lib/test_queue/runner/rspec3.rb +11 -11
- data/lib/test_queue/runner/testunit.rb +6 -3
- data/lib/test_queue/runner.rb +87 -97
- 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 +7 -44
- 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/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,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ::RSpec::Core::ExampleGroup
|
2
4
|
def self.failure_count
|
3
|
-
examples.map {|e| e.execution_result[:status] ==
|
5
|
+
examples.map { |e| e.execution_result[:status] == 'failed' }.length
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
@@ -14,30 +16,28 @@ module RSpec::Core
|
|
14
16
|
|
15
17
|
def run_each(iterator)
|
16
18
|
@configuration.reporter.report(0, @configuration.randomize? ? @configuration.seed : nil) do |reporter|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
puts(" <%.3f>" % diff)
|
19
|
+
@configuration.run_hook(:before, :suite)
|
20
|
+
iterator.map { |g|
|
21
|
+
if g.is_a? ::RSpec::Core::Example
|
22
|
+
print " #{g.full_description}: "
|
23
|
+
example = g
|
24
|
+
g = example.example_group
|
25
|
+
::RSpec.world.filtered_examples.clear
|
26
|
+
examples = [example]
|
27
|
+
examples.extend(::RSpec::Core::Extensions::Ordered::Examples)
|
28
|
+
::RSpec.world.filtered_examples[g] = examples
|
29
|
+
else
|
30
|
+
print " #{g.description}: "
|
31
|
+
end
|
32
|
+
start = Time.now
|
33
|
+
ret = g.run(reporter)
|
34
|
+
diff = Time.now - start
|
35
|
+
puts(' <%.3f>' % diff)
|
35
36
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
ret
|
38
|
+
}.all? ? 0 : @configuration.failure_exit_code
|
39
|
+
ensure
|
40
|
+
@configuration.run_hook(:after, :suite)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class ::RSpec::Core::ExampleGroup
|
2
4
|
def self.failure_count
|
3
|
-
examples.map {|e| e.execution_result.status ==
|
5
|
+
examples.map { |e| e.execution_result.status == 'failed' }.length
|
4
6
|
end
|
5
7
|
end
|
6
8
|
|
@@ -9,13 +11,11 @@ module RSpec::Core
|
|
9
11
|
unless Configuration.method_defined?(:with_suite_hooks)
|
10
12
|
class Configuration
|
11
13
|
def with_suite_hooks
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
hooks.run(:after, :suite, hook_context)
|
18
|
-
end
|
14
|
+
hook_context = SuiteHookContext.new
|
15
|
+
hooks.run(:before, :suite, hook_context)
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
hooks.run(:after, :suite, hook_context)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -41,14 +41,14 @@ module RSpec::Core
|
|
41
41
|
print " #{g.description}: "
|
42
42
|
end
|
43
43
|
ret = g.run(reporter)
|
44
|
-
diff = Time.now-start
|
45
|
-
puts(
|
44
|
+
diff = Time.now - start
|
45
|
+
puts(' <%.3f>' % diff)
|
46
46
|
|
47
47
|
ret
|
48
48
|
}.all? ? 0 : @configuration.failure_exit_code
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
52
|
-
|
52
|
+
alias run_each run_specs
|
53
53
|
end
|
54
54
|
end
|
@@ -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,32 @@ 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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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"]
|
91
|
+
@socket = socket || ENV['TEST_QUEUE_SOCKET'] || "/tmp/test_queue_#{$$}_#{object_id}.sock"
|
92
|
+
@relay = relay || ENV['TEST_QUEUE_RELAY']
|
93
|
+
|
94
|
+
@remote_master_message = if ENV.key?('TEST_QUEUE_REMOTE_MASTER_MESSAGE')
|
95
|
+
ENV['TEST_QUEUE_REMOTE_MASTER_MESSAGE']
|
96
|
+
elsif ENV.key?('TEST_QUEUE_SLAVE_MESSAGE')
|
97
|
+
warn('`TEST_QUEUE_SLAVE_MESSAGE` is deprecated. Use `TEST_QUEUE_REMOTE_MASTER_MESSAGE` instead.')
|
98
|
+
ENV['TEST_QUEUE_SLAVE_MESSAGE']
|
109
99
|
end
|
110
100
|
|
111
101
|
if @relay == @socket
|
112
|
-
|
102
|
+
warn '*** Detected TEST_QUEUE_RELAY == TEST_QUEUE_SOCKET. Disabling relay mode.'
|
113
103
|
@relay = nil
|
114
104
|
elsif @relay
|
115
105
|
@queue = []
|
@@ -144,7 +134,7 @@ module TestQueue
|
|
144
134
|
|
145
135
|
def summarize_internal
|
146
136
|
puts
|
147
|
-
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now
|
137
|
+
puts "==> Summary (#{@completed.size} workers in %.4fs)" % (Time.now - @start_time)
|
148
138
|
puts
|
149
139
|
|
150
140
|
estatus = 0
|
@@ -167,9 +157,9 @@ module TestQueue
|
|
167
157
|
|
168
158
|
summarize_worker(worker)
|
169
159
|
|
170
|
-
@failures
|
160
|
+
@failures += worker.failure_output if worker.failure_output
|
171
161
|
|
172
|
-
puts
|
162
|
+
puts ' [%2d] %60s %4d suites in %.4fs (%s %s)' % [
|
173
163
|
worker.num,
|
174
164
|
worker.summary,
|
175
165
|
worker.suites.size,
|
@@ -181,16 +171,16 @@ module TestQueue
|
|
181
171
|
|
182
172
|
unless @failures.empty?
|
183
173
|
puts
|
184
|
-
puts
|
174
|
+
puts '==> Failures'
|
185
175
|
puts
|
186
176
|
puts @failures
|
187
177
|
end
|
188
178
|
|
189
|
-
|
179
|
+
unless relay?
|
190
180
|
unless @discovered_suites.empty?
|
191
181
|
estatus += 1
|
192
182
|
puts
|
193
|
-
puts
|
183
|
+
puts 'The following suites were discovered but were not run:'
|
194
184
|
puts
|
195
185
|
|
196
186
|
@discovered_suites.sort.each do |suite_name, path|
|
@@ -200,7 +190,7 @@ module TestQueue
|
|
200
190
|
unless unassigned_suites.empty?
|
201
191
|
estatus += 1
|
202
192
|
puts
|
203
|
-
puts
|
193
|
+
puts 'The following suites were not discovered but were run anyway:'
|
204
194
|
puts
|
205
195
|
unassigned_suites.sort.each do |suite_name, path|
|
206
196
|
puts "#{suite_name} - #{path}"
|
@@ -209,7 +199,7 @@ module TestQueue
|
|
209
199
|
unless misrun_suites.empty?
|
210
200
|
estatus += 1
|
211
201
|
puts
|
212
|
-
puts
|
202
|
+
puts 'The following suites were run on the wrong workers:'
|
213
203
|
puts
|
214
204
|
misrun_suites.each do |suite_name, path, target_host, target_pid, actual_host, actual_pid|
|
215
205
|
puts "#{suite_name} - #{path}: #{actual_host} (#{actual_pid}) - assigned to #{target_host} (#{target_pid})"
|
@@ -223,7 +213,7 @@ module TestQueue
|
|
223
213
|
|
224
214
|
summarize
|
225
215
|
|
226
|
-
estatus = @completed.inject(0){ |s, worker| s + (worker.status.exitstatus || 1)}
|
216
|
+
estatus = @completed.inject(0) { |s, worker| s + (worker.status.exitstatus || 1) }
|
227
217
|
[estatus, 255].min
|
228
218
|
end
|
229
219
|
|
@@ -231,8 +221,7 @@ module TestQueue
|
|
231
221
|
end
|
232
222
|
|
233
223
|
def stats_file
|
234
|
-
ENV['TEST_QUEUE_STATS'] ||
|
235
|
-
'.test_queue_stats'
|
224
|
+
ENV['TEST_QUEUE_STATS'] || '.test_queue_stats'
|
236
225
|
end
|
237
226
|
|
238
227
|
def execute_internal
|
@@ -250,19 +239,19 @@ module TestQueue
|
|
250
239
|
end
|
251
240
|
|
252
241
|
def start_master
|
253
|
-
|
242
|
+
unless relay?
|
254
243
|
if @socket =~ /\A(?:(.+):)?(\d+)\z/
|
255
244
|
address = $1 || '0.0.0.0'
|
256
245
|
port = $2.to_i
|
257
|
-
@socket = "
|
246
|
+
@socket = "#{$1}:#{$2}"
|
258
247
|
@server = TCPServer.new(address, port)
|
259
248
|
else
|
260
|
-
FileUtils.
|
249
|
+
FileUtils.rm_f(@socket)
|
261
250
|
@server = UNIXServer.new(@socket)
|
262
251
|
end
|
263
252
|
end
|
264
253
|
|
265
|
-
desc = "test-queue master (#{relay
|
254
|
+
desc = "test-queue master (#{relay? ? "relaying to #{@relay}" : @socket})"
|
266
255
|
puts "Starting #{desc}"
|
267
256
|
$0 = "#{desc} - #{@procline}"
|
268
257
|
end
|
@@ -271,19 +260,19 @@ module TestQueue
|
|
271
260
|
return unless relay?
|
272
261
|
|
273
262
|
sock = connect_to_relay
|
274
|
-
message = @remote_master_message ? " #{@remote_master_message}" :
|
275
|
-
message.gsub
|
263
|
+
message = @remote_master_message ? " #{@remote_master_message}" : ''
|
264
|
+
message = message.gsub(/(\r|\n)/, '') # Our "protocol" is newline-separated
|
276
265
|
sock.puts("TOKEN=#{@run_token}")
|
277
266
|
sock.puts("REMOTE MASTER #{@concurrency} #{Socket.gethostname} #{message}")
|
278
267
|
response = sock.gets.strip
|
279
|
-
unless response ==
|
280
|
-
|
268
|
+
unless response == 'OK'
|
269
|
+
warn "*** Got non-OK response from master: #{response}"
|
281
270
|
sock.close
|
282
271
|
exit! 1
|
283
272
|
end
|
284
273
|
sock.close
|
285
274
|
rescue Errno::ECONNREFUSED
|
286
|
-
|
275
|
+
warn "*** Unable to connect to relay #{@relay}. Aborting..."
|
287
276
|
exit! 1
|
288
277
|
end
|
289
278
|
|
@@ -297,12 +286,12 @@ module TestQueue
|
|
297
286
|
|
298
287
|
def spawn_workers
|
299
288
|
@concurrency.times do |i|
|
300
|
-
num = i+1
|
289
|
+
num = i + 1
|
301
290
|
|
302
291
|
pid = fork do
|
303
|
-
@server
|
292
|
+
@server&.close
|
304
293
|
|
305
|
-
iterator = Iterator.new(@test_framework, relay
|
294
|
+
iterator = Iterator.new(@test_framework, relay? ? @relay : @socket, method(:around_filter), early_failure_limit: @early_failure_limit, run_token: @run_token)
|
306
295
|
after_fork_internal(num, iterator)
|
307
296
|
ret = run_worker(iterator) || 0
|
308
297
|
cleanup_worker
|
@@ -324,12 +313,12 @@ module TestQueue
|
|
324
313
|
|
325
314
|
@discovering_suites_pid = fork do
|
326
315
|
terminate = false
|
327
|
-
Signal.trap(
|
316
|
+
Signal.trap('INT') { terminate = true }
|
328
317
|
|
329
|
-
$0 =
|
318
|
+
$0 = 'test-queue suite discovery process'
|
330
319
|
|
331
320
|
@test_framework.all_suite_files.each do |path|
|
332
|
-
@test_framework.suites_from_file(path).each do |suite_name,
|
321
|
+
@test_framework.suites_from_file(path).each do |suite_name, _suite|
|
333
322
|
Kernel.exit!(0) if terminate
|
334
323
|
|
335
324
|
@server.connect_address.connect do |sock|
|
@@ -344,13 +333,9 @@ module TestQueue
|
|
344
333
|
end
|
345
334
|
|
346
335
|
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.
|
336
|
+
# We're waiting to find all the allowlisted suites so we can run them in the correct order.
|
337
|
+
# Or we don't have any suites yet, but we're working on it.
|
338
|
+
if @awaited_suites.any? || @queue.empty? && !!@discovering_suites_pid
|
354
339
|
true
|
355
340
|
else
|
356
341
|
# It's fine to run any queued suites now.
|
@@ -379,9 +364,9 @@ module TestQueue
|
|
379
364
|
if @awaited_suites.delete?(suite_name) && @awaited_suites.empty?
|
380
365
|
# We've found all the allowlisted suites. Sort the queue to match the
|
381
366
|
# allowlist.
|
382
|
-
@queue.sort_by! { |
|
367
|
+
@queue.sort_by! { |queued_suite_name, _path| @allowlist.index(queued_suite_name) }
|
383
368
|
|
384
|
-
kill_suite_discovery_process(
|
369
|
+
kill_suite_discovery_process('INT')
|
385
370
|
end
|
386
371
|
end
|
387
372
|
|
@@ -396,7 +381,7 @@ module TestQueue
|
|
396
381
|
|
397
382
|
$0 = "test-queue worker [#{num}]"
|
398
383
|
puts
|
399
|
-
puts "==> Starting
|
384
|
+
puts "==> Starting #{$0} (#{Process.pid} on #{Socket.gethostname}) - iterating over #{iterator.sock}"
|
400
385
|
puts
|
401
386
|
|
402
387
|
after_fork(num)
|
@@ -408,7 +393,7 @@ module TestQueue
|
|
408
393
|
def prepare(concurrency)
|
409
394
|
end
|
410
395
|
|
411
|
-
def around_filter(
|
396
|
+
def around_filter(_suite)
|
412
397
|
yield
|
413
398
|
end
|
414
399
|
|
@@ -425,7 +410,7 @@ module TestQueue
|
|
425
410
|
puts " #{item.inspect}"
|
426
411
|
end
|
427
412
|
|
428
|
-
|
413
|
+
0 # exit status
|
429
414
|
end
|
430
415
|
|
431
416
|
def cleanup_worker
|
@@ -436,7 +421,7 @@ module TestQueue
|
|
436
421
|
worker.failure_output = ''
|
437
422
|
end
|
438
423
|
|
439
|
-
def reap_workers(blocking=true)
|
424
|
+
def reap_workers(blocking = true)
|
440
425
|
@workers.delete_if do |_, worker|
|
441
426
|
if Process.waitpid(worker.pid, blocking ? 0 : Process::WNOHANG).nil?
|
442
427
|
next false
|
@@ -455,35 +440,37 @@ module TestQueue
|
|
455
440
|
|
456
441
|
def collect_worker_data(worker)
|
457
442
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_output")
|
458
|
-
worker.output =
|
443
|
+
worker.output = File.binread(file)
|
459
444
|
FileUtils.rm(file)
|
460
445
|
end
|
461
446
|
|
462
447
|
if File.exist?(file = "/tmp/test_queue_worker_#{worker.pid}_suites")
|
463
|
-
worker.suites.replace(Marshal.load(
|
448
|
+
worker.suites.replace(Marshal.load(File.binread(file)))
|
464
449
|
FileUtils.rm(file)
|
465
450
|
end
|
466
451
|
end
|
467
452
|
|
468
453
|
def worker_completed(worker)
|
469
454
|
return if @aborting
|
455
|
+
|
470
456
|
@completed << worker
|
471
457
|
puts worker.output if ENV['TEST_QUEUE_VERBOSE'] || worker.status.exitstatus != 0
|
472
458
|
end
|
473
459
|
|
474
460
|
def distribute_queue
|
475
461
|
return if relay?
|
462
|
+
|
476
463
|
remote_workers = 0
|
477
464
|
|
478
465
|
until !awaiting_suites? && @queue.empty? && remote_workers == 0
|
479
466
|
queue_status(@start_time, @queue.size, @workers.size, remote_workers)
|
480
467
|
|
481
|
-
if status = reap_suite_discovery_process(false)
|
482
|
-
abort(
|
483
|
-
abort("Failed to discover #{@awaited_suites.sort.join(
|
468
|
+
if (status = reap_suite_discovery_process(false))
|
469
|
+
abort('Discovering suites failed.') unless status.success?
|
470
|
+
abort("Failed to discover #{@awaited_suites.sort.join(', ')} specified in TEST_QUEUE_FORCE") if @awaited_suites.any?
|
484
471
|
end
|
485
472
|
|
486
|
-
if
|
473
|
+
if @server.wait_readable(0.1).nil?
|
487
474
|
reap_workers(false) # check for worker deaths
|
488
475
|
else
|
489
476
|
sock = @server.accept
|
@@ -493,8 +480,8 @@ module TestQueue
|
|
493
480
|
token = token[TOKEN_REGEX, 1]
|
494
481
|
# If we have a remote master from a different test run, respond with "WRONG RUN", and it will consider the test run done.
|
495
482
|
if token != @run_token
|
496
|
-
message = token.nil? ?
|
497
|
-
|
483
|
+
message = token.nil? ? 'Worker sent no token to master' : "Worker from run #{token} connected to master"
|
484
|
+
warn "*** #{message} for run #{@run_token}; ignoring."
|
498
485
|
sock.write("WRONG RUN\n")
|
499
486
|
next
|
500
487
|
end
|
@@ -504,13 +491,13 @@ module TestQueue
|
|
504
491
|
hostname = $1
|
505
492
|
pid = Integer($2)
|
506
493
|
if awaiting_suites?
|
507
|
-
sock.write(Marshal.dump(
|
508
|
-
elsif obj = @queue.shift
|
494
|
+
sock.write(Marshal.dump('WAIT'))
|
495
|
+
elsif (obj = @queue.shift)
|
509
496
|
data = Marshal.dump(obj)
|
510
497
|
sock.write(data)
|
511
498
|
@assignments[obj] = [hostname, pid]
|
512
499
|
end
|
513
|
-
when /\AREMOTE MASTER (\d+) ([\w
|
500
|
+
when /\AREMOTE MASTER (\d+) ([\w.-]+)(?: (.+))?/
|
514
501
|
num = $1.to_i
|
515
502
|
remote_master = $2
|
516
503
|
remote_master_message = $3
|
@@ -518,9 +505,9 @@ module TestQueue
|
|
518
505
|
sock.write("OK\n")
|
519
506
|
remote_workers += num
|
520
507
|
|
521
|
-
message = "*** #{num} workers connected from #{remote_master} after #{Time.now
|
522
|
-
message
|
523
|
-
|
508
|
+
message = "*** #{num} workers connected from #{remote_master} after #{Time.now - @start_time}s"
|
509
|
+
message += " #{remote_master_message}" if remote_master_message
|
510
|
+
warn message
|
524
511
|
when /\AWORKER (\d+)/
|
525
512
|
data = sock.read($1.to_i)
|
526
513
|
worker = Marshal.load(data)
|
@@ -534,7 +521,7 @@ module TestQueue
|
|
534
521
|
# stop everything immediately and report the results.
|
535
522
|
break
|
536
523
|
else
|
537
|
-
|
524
|
+
warn("Ignoring unrecognized command: \"#{cmd}\"")
|
538
525
|
end
|
539
526
|
sock.close
|
540
527
|
end
|
@@ -557,7 +544,8 @@ module TestQueue
|
|
557
544
|
sock = TCPSocket.new(*@relay.split(':'))
|
558
545
|
rescue Errno::ECONNREFUSED => e
|
559
546
|
raise e if Time.now - start > @relay_connection_timeout
|
560
|
-
|
547
|
+
|
548
|
+
puts 'Master not yet available, sleeping...'
|
561
549
|
sleep 0.5
|
562
550
|
end
|
563
551
|
end
|
@@ -573,7 +561,7 @@ module TestQueue
|
|
573
561
|
sock.puts("WORKER #{data.bytesize}")
|
574
562
|
sock.write(data)
|
575
563
|
ensure
|
576
|
-
sock
|
564
|
+
sock&.close
|
577
565
|
end
|
578
566
|
|
579
567
|
def kill_subprocesses
|
@@ -582,21 +570,23 @@ module TestQueue
|
|
582
570
|
end
|
583
571
|
|
584
572
|
def kill_workers
|
585
|
-
@workers.each do |pid,
|
573
|
+
@workers.each do |pid, _worker|
|
586
574
|
Process.kill 'KILL', pid
|
587
575
|
end
|
588
576
|
|
589
577
|
reap_workers
|
590
578
|
end
|
591
579
|
|
592
|
-
def kill_suite_discovery_process(signal=
|
580
|
+
def kill_suite_discovery_process(signal = 'KILL')
|
593
581
|
return unless @discovering_suites_pid
|
582
|
+
|
594
583
|
Process.kill signal, @discovering_suites_pid
|
595
584
|
reap_suite_discovery_process
|
596
585
|
end
|
597
586
|
|
598
|
-
def reap_suite_discovery_process(blocking=true)
|
587
|
+
def reap_suite_discovery_process(blocking = true)
|
599
588
|
return unless @discovering_suites_pid
|
589
|
+
|
600
590
|
_, status = Process.waitpid2(@discovering_suites_pid, blocking ? 0 : Process::WNOHANG)
|
601
591
|
return unless status
|
602
592
|
|
@@ -612,7 +602,7 @@ module TestQueue
|
|
612
602
|
def abort(message)
|
613
603
|
@aborting = true
|
614
604
|
kill_subprocesses
|
615
|
-
Kernel
|
605
|
+
Kernel.abort("Aborting: #{message}")
|
616
606
|
end
|
617
607
|
|
618
608
|
# Subclasses can override to monitor the status of the queue.
|