megatest 0.1.0
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 +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +156 -0
- data/TODO.md +17 -0
- data/exe/megatest +7 -0
- data/lib/megatest/assertions.rb +474 -0
- data/lib/megatest/backtrace.rb +70 -0
- data/lib/megatest/cli.rb +249 -0
- data/lib/megatest/compat.rb +74 -0
- data/lib/megatest/config.rb +281 -0
- data/lib/megatest/differ.rb +136 -0
- data/lib/megatest/dsl.rb +164 -0
- data/lib/megatest/executor.rb +104 -0
- data/lib/megatest/multi_process.rb +263 -0
- data/lib/megatest/output.rb +158 -0
- data/lib/megatest/patience_diff.rb +340 -0
- data/lib/megatest/pretty_print.rb +309 -0
- data/lib/megatest/queue.rb +239 -0
- data/lib/megatest/queue_monitor.rb +35 -0
- data/lib/megatest/queue_reporter.rb +42 -0
- data/lib/megatest/redis_queue.rb +459 -0
- data/lib/megatest/reporters.rb +266 -0
- data/lib/megatest/runner.rb +119 -0
- data/lib/megatest/runtime.rb +168 -0
- data/lib/megatest/selector.rb +293 -0
- data/lib/megatest/state.rb +708 -0
- data/lib/megatest/subprocess/main.rb +8 -0
- data/lib/megatest/subprocess.rb +48 -0
- data/lib/megatest/test.rb +115 -0
- data/lib/megatest/test_task.rb +132 -0
- data/lib/megatest/version.rb +5 -0
- data/lib/megatest.rb +123 -0
- metadata +80 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prettyprint"
|
|
4
|
+
|
|
5
|
+
# :stopdoc:
|
|
6
|
+
|
|
7
|
+
module Megatest
|
|
8
|
+
class PrettyPrint
|
|
9
|
+
# This class is largely a copy of the `pp` gem
|
|
10
|
+
# but rewritten to not rely on monkey patches
|
|
11
|
+
# and with some small rendering modifications
|
|
12
|
+
# notably around multiline strings.
|
|
13
|
+
class Printer < ::PrettyPrint
|
|
14
|
+
class << self
|
|
15
|
+
def pp(obj, out = +"", width = 79)
|
|
16
|
+
q = new(out, width)
|
|
17
|
+
q.guard_inspect_key { q.pp obj }
|
|
18
|
+
q.flush
|
|
19
|
+
out
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Yields to a block
|
|
24
|
+
# and preserves the previous set of objects being printed.
|
|
25
|
+
def guard_inspect_key
|
|
26
|
+
@recursive_key = {}.compare_by_identity
|
|
27
|
+
|
|
28
|
+
save = @recursive_key
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
@recursive_key = {}.compare_by_identity
|
|
32
|
+
yield
|
|
33
|
+
ensure
|
|
34
|
+
@recursive_key = save
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check whether the object_id +id+ is in the current buffer of objects
|
|
39
|
+
# to be pretty printed. Used to break cycles in chains of objects to be
|
|
40
|
+
# pretty printed.
|
|
41
|
+
def check_inspect_key(id)
|
|
42
|
+
@recursive_key&.include?(id)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Adds the object_id +id+ to the set of objects being pretty printed, so
|
|
46
|
+
# as to not repeat objects.
|
|
47
|
+
def push_inspect_key(id)
|
|
48
|
+
@recursive_key[id] = true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Removes an object from the set of objects being pretty printed.
|
|
52
|
+
def pop_inspect_key(id)
|
|
53
|
+
@recursive_key.delete id
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Adds +obj+ to the pretty printing buffer
|
|
57
|
+
# using Object#pretty_print or Object#pretty_print_cycle.
|
|
58
|
+
#
|
|
59
|
+
# Object#pretty_print_cycle is used when +obj+ is already
|
|
60
|
+
# printed, a.k.a the object reference chain has a cycle.
|
|
61
|
+
def pp(obj)
|
|
62
|
+
# If obj is a Delegator then use the object being delegated to for cycle
|
|
63
|
+
# detection
|
|
64
|
+
obj = obj.__getobj__ if defined?(::Delegator) && ::Delegator === obj
|
|
65
|
+
|
|
66
|
+
if check_inspect_key(obj)
|
|
67
|
+
group { pretty_print_cycle(obj) }
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
push_inspect_key(obj)
|
|
73
|
+
group { pretty_print(obj) }
|
|
74
|
+
ensure
|
|
75
|
+
pop_inspect_key(obj)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A convenience method which is same as follows:
|
|
80
|
+
#
|
|
81
|
+
# group(1, '#<' + obj.class.name, '>') { ... }
|
|
82
|
+
def object_group(obj, &block)
|
|
83
|
+
group(1, "#<#{obj.class.name}>", &block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
using Compat::BindCall unless UnboundMethod.method_defined?(:bind_call)
|
|
87
|
+
|
|
88
|
+
# A convenience method, like object_group, but also reformats the Object's
|
|
89
|
+
# object_id.
|
|
90
|
+
def object_address_group(obj, &block)
|
|
91
|
+
str = Kernel.instance_method(:to_s).bind_call(obj)
|
|
92
|
+
str.chomp!(">")
|
|
93
|
+
group(1, str, ">", &block)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# A convenience method which is same as follows:
|
|
97
|
+
#
|
|
98
|
+
# text ','
|
|
99
|
+
# breakable
|
|
100
|
+
def comma_breakable
|
|
101
|
+
text ","
|
|
102
|
+
breakable
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Adds a separated list.
|
|
106
|
+
# The list is separated by comma with breakable space, by default.
|
|
107
|
+
#
|
|
108
|
+
# #seplist iterates the +list+ using +iter_method+.
|
|
109
|
+
# It yields each object to the block given for #seplist.
|
|
110
|
+
# The procedure +separator_proc+ is called between each yields.
|
|
111
|
+
#
|
|
112
|
+
# If the iteration is zero times, +separator_proc+ is not called at all.
|
|
113
|
+
#
|
|
114
|
+
# If +separator_proc+ is nil or not given,
|
|
115
|
+
# +lambda { comma_breakable }+ is used.
|
|
116
|
+
# If +iter_method+ is not given, :each is used.
|
|
117
|
+
#
|
|
118
|
+
# For example, following 3 code fragments has similar effect.
|
|
119
|
+
#
|
|
120
|
+
# q.seplist([1,2,3]) {|v| xxx v }
|
|
121
|
+
#
|
|
122
|
+
# q.seplist([1,2,3], lambda { q.comma_breakable }, :each) {|v| xxx v }
|
|
123
|
+
#
|
|
124
|
+
# xxx 1
|
|
125
|
+
# q.comma_breakable
|
|
126
|
+
# xxx 2
|
|
127
|
+
# q.comma_breakable
|
|
128
|
+
# xxx 3
|
|
129
|
+
def seplist(list, sep = nil, iter_method = :each)
|
|
130
|
+
sep ||= -> { comma_breakable }
|
|
131
|
+
first = true
|
|
132
|
+
list.__send__(iter_method) do |*v|
|
|
133
|
+
if first
|
|
134
|
+
first = false
|
|
135
|
+
else
|
|
136
|
+
sep.call
|
|
137
|
+
end
|
|
138
|
+
yield(*v)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# A present standard failsafe for pretty printing any given Object
|
|
143
|
+
def pp_object(obj)
|
|
144
|
+
object_address_group(obj) do
|
|
145
|
+
seplist(pretty_print_instance_variables(obj), -> { text "," }) do |v|
|
|
146
|
+
breakable
|
|
147
|
+
v = v.to_s if Symbol === v
|
|
148
|
+
text v
|
|
149
|
+
text "="
|
|
150
|
+
group(1) do
|
|
151
|
+
breakable ""
|
|
152
|
+
pp(obj.instance_eval(v))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
INSTANCE_VARIABLES = Object.instance_method(:instance_variables)
|
|
159
|
+
def pretty_print_instance_variables(obj)
|
|
160
|
+
INSTANCE_VARIABLES.bind_call(obj).sort
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# A pretty print for a Hash
|
|
164
|
+
def pp_hash(obj)
|
|
165
|
+
group(1, "{", "}") do
|
|
166
|
+
seplist(obj, nil, :each_pair) do |k, v|
|
|
167
|
+
group do
|
|
168
|
+
pp k
|
|
169
|
+
text "=>"
|
|
170
|
+
group(1) do
|
|
171
|
+
breakable ""
|
|
172
|
+
pp v
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
using Compat::ByteRIndex unless String.method_defined?(:byterindex)
|
|
180
|
+
|
|
181
|
+
CLASS = Kernel.instance_method(:class)
|
|
182
|
+
|
|
183
|
+
def pretty_print(obj)
|
|
184
|
+
case obj
|
|
185
|
+
when String
|
|
186
|
+
if obj.size > 30 && obj.byterindex("\n", -1)
|
|
187
|
+
text obj.inspect.gsub('\n', "\\n\n").sub(/\\n\n"\z/, '\n"')
|
|
188
|
+
else
|
|
189
|
+
text obj.inspect
|
|
190
|
+
end
|
|
191
|
+
when Array
|
|
192
|
+
group(1, "[", "]") do
|
|
193
|
+
seplist(obj) do |v|
|
|
194
|
+
pp v
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
when Hash
|
|
198
|
+
pp_hash(obj)
|
|
199
|
+
when Range
|
|
200
|
+
pp obj.begin
|
|
201
|
+
breakable ""
|
|
202
|
+
text(obj.exclude_end? ? "..." : "..")
|
|
203
|
+
breakable ""
|
|
204
|
+
pp obj.end if obj.end
|
|
205
|
+
when MatchData
|
|
206
|
+
nc = []
|
|
207
|
+
obj.regexp.named_captures.each do |name, indexes|
|
|
208
|
+
indexes.each { |i| nc[i] = name }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
object_group(obj) do
|
|
212
|
+
breakable
|
|
213
|
+
seplist(0...obj.size, -> { breakable }) do |i|
|
|
214
|
+
if i != 0
|
|
215
|
+
if nc[i]
|
|
216
|
+
text nc[i]
|
|
217
|
+
else
|
|
218
|
+
pp i
|
|
219
|
+
end
|
|
220
|
+
text ":"
|
|
221
|
+
pp obj[i]
|
|
222
|
+
end
|
|
223
|
+
pp obj[i]
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
when Regexp, Symbol, Numeric, Module, true, false, nil
|
|
227
|
+
text(obj.inspect)
|
|
228
|
+
when Struct
|
|
229
|
+
group(1, format("#<struct %s", CLASS.bind_call(obj)), ">") do
|
|
230
|
+
seplist(Struct.instance_method(:members).bind_call(obj), -> { text "," }) do |member|
|
|
231
|
+
breakable
|
|
232
|
+
text member.to_s
|
|
233
|
+
text "="
|
|
234
|
+
group(1) do
|
|
235
|
+
breakable ""
|
|
236
|
+
pp obj[member]
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
else
|
|
241
|
+
if ENV.equal?(obj)
|
|
242
|
+
pp_hash(ENV.sort.to_h)
|
|
243
|
+
elsif special_inspect?(obj)
|
|
244
|
+
text(obj.inspect)
|
|
245
|
+
else
|
|
246
|
+
pp_object(obj)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def pretty_print_cycle(obj)
|
|
252
|
+
case obj
|
|
253
|
+
when Array
|
|
254
|
+
text(obj.empty? ? "[]" : "[...]")
|
|
255
|
+
when Hash
|
|
256
|
+
text(obj.empty? ? "{}" : "{...}")
|
|
257
|
+
when Struct
|
|
258
|
+
text format("#<struct %s:...>", CLASS.bind_call(obj))
|
|
259
|
+
when Numeric, Symbol, FalseClass, TrueClass, NilClass, Module
|
|
260
|
+
text obj.inspect
|
|
261
|
+
else
|
|
262
|
+
object_address_group(obj) do
|
|
263
|
+
breakable
|
|
264
|
+
text "..."
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
METHOD = Object.instance_method(:method)
|
|
270
|
+
def special_inspect?(obj)
|
|
271
|
+
METHOD.bind_call(obj, :inspect).owner != Kernel
|
|
272
|
+
rescue NoMethodError
|
|
273
|
+
false
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
OBJECT_INSPECT = Object.instance_method(:inspect)
|
|
277
|
+
|
|
278
|
+
def inspect_object(obj)
|
|
279
|
+
obj.inspect
|
|
280
|
+
rescue NoMethodError # Basic Object etc.
|
|
281
|
+
OBJECT_INSPECT.bind_call(obj)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def initialize(config)
|
|
286
|
+
@config = config
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
using Compat::BindCall unless UnboundMethod.method_defined?(:bind_call)
|
|
290
|
+
|
|
291
|
+
def pretty_print(object)
|
|
292
|
+
case object
|
|
293
|
+
when Exception
|
|
294
|
+
[
|
|
295
|
+
"Class: <#{pp(object.class)}>",
|
|
296
|
+
"Message: <#{object.message.inspect}>",
|
|
297
|
+
"---Backtrace---",
|
|
298
|
+
*@config.backtrace.clean(object.backtrace),
|
|
299
|
+
"---------------",
|
|
300
|
+
].join("\n")
|
|
301
|
+
else
|
|
302
|
+
out = "".dup
|
|
303
|
+
Printer.pp(object, out)
|
|
304
|
+
out.strip
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
alias_method :pp, :pretty_print
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
|
|
5
|
+
module Megatest
|
|
6
|
+
class AbstractQueue
|
|
7
|
+
class << self
|
|
8
|
+
alias_method :build, :new
|
|
9
|
+
private :new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_reader :test_cases_index, :size
|
|
13
|
+
|
|
14
|
+
def initialize(config)
|
|
15
|
+
@config = config
|
|
16
|
+
@size = nil
|
|
17
|
+
@test_cases_index = nil
|
|
18
|
+
@populated = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def retrying?
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def sharded?
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def summary
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def distributed?
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def empty?
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def remaining_size
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def success?
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def populated?
|
|
50
|
+
@populated
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def record_lost_test(test)
|
|
54
|
+
record_result(TestCaseResult.new(test).lost)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def pop_test
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def record_result(result)
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def populate(test_cases)
|
|
66
|
+
@test_cases_index = test_cases.to_h { |t| [t.id, t] }
|
|
67
|
+
@size = test_cases.size
|
|
68
|
+
@populated = true
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def cleanup
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module ShardeableQueue
|
|
76
|
+
def sharded?
|
|
77
|
+
@config.workers_count > 1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def populate(test_cases)
|
|
81
|
+
if sharded?
|
|
82
|
+
test_cases = test_cases.select.with_index { |_t, index| (index % @config.workers_count) == @config.worker_id }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
super
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Queue < AbstractQueue
|
|
90
|
+
class Summary
|
|
91
|
+
attr_reader :results
|
|
92
|
+
|
|
93
|
+
def initialize(results = [])
|
|
94
|
+
@results = results
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# When running distributed queues, it's possible
|
|
98
|
+
# that a test is considered lost and end up with both
|
|
99
|
+
# a successful and a failed result.
|
|
100
|
+
# In such case we turn the failed result into a retry
|
|
101
|
+
# after the fact.
|
|
102
|
+
def deduplicate!
|
|
103
|
+
success = {}
|
|
104
|
+
@results.each do |result|
|
|
105
|
+
if result.success?
|
|
106
|
+
success[result.test_id] = true
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@results.map! do |result|
|
|
111
|
+
if result.bad? && success[result.test_id]
|
|
112
|
+
result.retry
|
|
113
|
+
else
|
|
114
|
+
result
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def assertions_count
|
|
120
|
+
results.sum(0, &:assertions_count)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def runs_count
|
|
124
|
+
results.size
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def total_time
|
|
128
|
+
results.sum(0.0, &:duration)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def retries_count
|
|
132
|
+
results.count(&:retried?)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def failures_count
|
|
136
|
+
results.count(&:failure?)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def errors_count
|
|
140
|
+
results.count(&:error?)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def skips_count
|
|
144
|
+
results.count(&:skipped?)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def failures
|
|
148
|
+
results.reject(&:success?)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def success?
|
|
152
|
+
!results.empty? && @results.all?(&:ok?)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def record_result(result)
|
|
156
|
+
@results << result
|
|
157
|
+
result
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
prepend ShardeableQueue
|
|
162
|
+
|
|
163
|
+
attr_reader :summary
|
|
164
|
+
alias_method :global_summary, :summary
|
|
165
|
+
|
|
166
|
+
def initialize(config)
|
|
167
|
+
super(config)
|
|
168
|
+
|
|
169
|
+
@queue = nil
|
|
170
|
+
@summary = Summary.new
|
|
171
|
+
@success = true
|
|
172
|
+
@retries = Hash.new(0)
|
|
173
|
+
@leases = {}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def distributed?
|
|
177
|
+
false
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def sharded?
|
|
181
|
+
@config.workers_count > 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def monitor
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def empty?
|
|
189
|
+
@queue.empty? && @leases.empty?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def populate(test_cases)
|
|
193
|
+
super
|
|
194
|
+
@queue = test_cases.reverse
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def remaining_size
|
|
198
|
+
@queue.size + @leases.size
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def success?
|
|
202
|
+
@success && @queue.empty?
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def pop_test
|
|
206
|
+
if test = @queue.pop
|
|
207
|
+
@leases[test.id] = true
|
|
208
|
+
end
|
|
209
|
+
test
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def record_result(result)
|
|
213
|
+
@leases.delete(result.test_id)
|
|
214
|
+
if result.failed?
|
|
215
|
+
if attempt_to_retry(result)
|
|
216
|
+
result = result.retry
|
|
217
|
+
else
|
|
218
|
+
@success &&= result.ok?
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
@summary.record_result(result)
|
|
222
|
+
result
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def attempt_to_retry(result)
|
|
228
|
+
return false unless @config.retries?
|
|
229
|
+
return false unless @summary.retries_count < @config.total_max_retries(@size)
|
|
230
|
+
return false unless @retries[result.test_id] < @config.max_retries
|
|
231
|
+
|
|
232
|
+
@retries[result.test_id] += 1
|
|
233
|
+
|
|
234
|
+
index = @config.random.rand(0..@queue.size)
|
|
235
|
+
@queue.insert(index, test_cases_index.fetch(result.test_id))
|
|
236
|
+
true
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
|
|
5
|
+
module Megatest
|
|
6
|
+
class QueueMonitor
|
|
7
|
+
class << self
|
|
8
|
+
def run(stdin, stdout)
|
|
9
|
+
config = Marshal.load(stdin)
|
|
10
|
+
stdout.puts("ready")
|
|
11
|
+
stdout.close
|
|
12
|
+
new(config, stdin).run
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(config, stdin)
|
|
17
|
+
@config = config
|
|
18
|
+
@in = stdin
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
queue = @config.build_queue
|
|
23
|
+
queue.heartbeat
|
|
24
|
+
|
|
25
|
+
queue.heartbeat until @in.wait_readable(@config.heartbeat_frequency)
|
|
26
|
+
|
|
27
|
+
0
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if __FILE__ == $PROGRAM_NAME
|
|
33
|
+
require "megatest"
|
|
34
|
+
exit(Megatest::QueueMonitor.run($stdin, $stdout))
|
|
35
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
|
|
5
|
+
module Megatest
|
|
6
|
+
class QueueReporter
|
|
7
|
+
POLL_FREQUENCY = 1
|
|
8
|
+
|
|
9
|
+
def initialize(config, queue, out)
|
|
10
|
+
@config = config
|
|
11
|
+
@queue = queue
|
|
12
|
+
@out = out
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def wall_time
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def wait
|
|
20
|
+
wait_for("Waiting for workers to start") { @queue.populated? }
|
|
21
|
+
wait_for("Waiting for tests to be ran") { @queue.empty? }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run(reporters)
|
|
25
|
+
summary = @queue.global_summary
|
|
26
|
+
summary.deduplicate!
|
|
27
|
+
reporters.each { |r| r.summary(self, @queue, summary) }
|
|
28
|
+
|
|
29
|
+
@queue.populated? && @queue.empty? && summary.success?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def wait_for(label)
|
|
35
|
+
unless yield
|
|
36
|
+
@out.puts label
|
|
37
|
+
sleep POLL_FREQUENCY
|
|
38
|
+
sleep POLL_FREQUENCY until yield
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|