megatest 0.5.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0be0dcf9dd26312cec897081522ae6ac404db5447b26d50286ccc8720422743
4
- data.tar.gz: 2580f85814d9469dfd1df3c8d3601c8b5ac89b4e4faaf706b4af47154d3c9bdb
3
+ metadata.gz: a5522d82ae4b5780fa9fdfa8029df9cf1e1b4ec3afaeacbea2a3f4491e93dcff
4
+ data.tar.gz: a3f5e2553b5c4a8654d2d0d300b2ec604e982ef19a1851a67d84c851a149e2e9
5
5
  SHA512:
6
- metadata.gz: ef25ab7ecc09133635d0ffaef659ce02574f01fd558f2437c187fd3f4f362b40cd13dfcb6d6ac455e0a1462b507dfb6e27efd74e72d18ed57e68f8c1434b25e2
7
- data.tar.gz: 68b8dc14b4869a5dea9d51f2c507287acb5bbcce709b1514c965f2b0cc9a0d8edd4da82a89b8e120427e63ece0c159a2f4f27d92362f1b25ded3f9cbdd05c402
6
+ metadata.gz: b603f4ea08c46dd91937b4214a3f82da29c4a1788dcfe06d53ead22aaaa7bebc3f8247bd6ca163e3f744f7215ef72eb1ed1fb389c9dd99c035c2f0af33f6a2ad
7
+ data.tar.gz: 0cccdb03115bb61e7ed7ab0d1ef2d0bb6dab8acdb61e40e628958ca1e5b34b16ce51549402834d780adb08f1dc2c9ad0106af14a5a6ad94e8379a8956ffc3b24
data/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.5.0] - 2025-01-17
3
+ ## [0.7.0] - 2026-03-21
4
+
5
+ - Automatic parallelization using cgroups or nprocs.
6
+ - Don't output escape codes when output is not a TTY.
7
+ - Don't retry skipped tests.
8
+ - Improve help message.
9
+
10
+ ## [0.6.0] - 2026-01-17
11
+
12
+ - Allow defining setup and teardown with method names.
13
+ - Allow multiple `setup`, `around` and `teardown` blocks in the same test suite.
14
+
15
+ ## [0.5.0] - 2026-01-17
4
16
 
5
17
  - Adds `megatest/autorun`
6
18
  - Adds `assert_nothing_raised`.
data/README.md CHANGED
@@ -142,7 +142,7 @@ and will generally expose environment variables to help split the workload.
142
142
 
143
143
  ```yaml
144
144
  - label: "Run Unit Tests"
145
- run: megatest --workers-count $CI_NODE_INDEX --worker-id $CI_NODE_TOTAL
145
+ run: megatest --workers-count $CI_NODE_TOTAL --worker-id $CI_NODE_INDEX
146
146
  parallel: 8
147
147
  ```
148
148
 
@@ -154,7 +154,7 @@ very large test suites containing lots of slow test cases being sharded as one u
154
154
  If you are using CircleCI, Buildkite or HerokuCI, the workers count and worker id
155
155
  will be automatically inferred from the environment.
156
156
 
157
- ### Redis Distribution
157
+ #### Redis Distribution
158
158
 
159
159
  A more efficient way to parallelize tests on CI is to use a Redis server to act as a queue.
160
160
 
data/lib/megatest/cli.rb CHANGED
@@ -27,7 +27,7 @@ module Megatest
27
27
 
28
28
  undef_method :puts, :print # Should only use @out.puts or @err.puts
29
29
 
30
- RUNNERS = {
30
+ COMMANDS = {
31
31
  "bisect" => :bisect,
32
32
  "report" => :report,
33
33
  "run" => :run,
@@ -40,17 +40,17 @@ module Megatest
40
40
  @processes = nil
41
41
  @config = Config.new(env)
42
42
  @program_name = @config.program_name = program_name
43
- @runner = nil
43
+ @command = nil
44
44
  @verbose = false
45
45
  @junit = false
46
46
  end
47
47
 
48
48
  def run
49
49
  configure
50
- case @runner
50
+ case @command
51
51
  when :report
52
52
  report
53
- when nil, :run
53
+ when :run
54
54
  run_tests
55
55
  when :bisect
56
56
  bisect_tests
@@ -71,24 +71,24 @@ module Megatest
71
71
  def configure
72
72
  Megatest.running = true
73
73
 
74
- if @runner = RUNNERS[@argv.first]
74
+ if @command = COMMANDS[@argv.first]
75
75
  @argv.shift
76
76
  end
77
77
 
78
78
  Megatest.config = @config
79
- @parser = build_parser(@runner)
79
+ @parser = build_parser
80
80
  @parser.parse!(@argv)
81
81
  @argv.shift if @argv.first == "--"
82
+ @queue = @config.build_queue
83
+ @config.parallelize_maybe if @command == :run && !@queue.distributed? && !@queue.sharded?
82
84
  @config
83
85
  end
84
86
 
85
87
  def run_tests
86
- queue = @config.build_queue
87
-
88
- if queue.distributed?
88
+ if @queue.distributed?
89
89
  raise InvalidArgument, "Distributed queues require a build-id" unless @config.build_id
90
90
  raise InvalidArgument, "Distributed queues require a worker-id" unless @config.worker_id
91
- elsif queue.sharded?
91
+ elsif @queue.sharded?
92
92
  unless @config.valid_worker_index?
93
93
  raise InvalidArgument, "Splitting the queue requires a worker-id lower than workers-count, got: #{@config.worker_id.inspect}"
94
94
  end
@@ -102,44 +102,43 @@ module Megatest
102
102
  if test_cases.empty?
103
103
  @err.puts "No tests to run"
104
104
  return 1
105
+ elsif test_cases.size == 1
106
+ @config.jobs_count = 1
105
107
  end
106
108
 
107
- queue.populate(test_cases)
108
- executor.run(queue, default_reporters)
109
- queue.success? ? 0 : 1
109
+ @queue.populate(test_cases)
110
+ executor.run(@queue, default_reporters)
111
+ @queue.success? ? 0 : 1
110
112
  end
111
113
 
112
114
  def report
113
- queue = @config.build_queue
114
-
115
- raise InvalidArgument, "Only distributed queues can be summarized" unless queue.distributed?
115
+ raise InvalidArgument, "Only distributed queues can be summarized" unless @queue.distributed?
116
116
  raise InvalidArgument, "Distributed queues require a build-id" unless @config.build_id
117
117
  raise InvalidArgument, @argv.join(" ") unless @argv.empty?
118
118
 
119
- Megatest.load_config(@argv)
119
+ @config.selectors = Selector.new(@config).parse(@argv)
120
+ Megatest.load_config(@config)
120
121
 
121
- QueueReporter.new(@config, queue, @out).run(default_reporters) ? 0 : 1
122
+ QueueReporter.new(@config, @queue, @out).run(default_reporters) ? 0 : 1
122
123
  end
123
124
 
124
125
  def bisect_tests
125
126
  require "megatest/multi_process"
126
-
127
- queue = @config.build_queue
128
- raise InvalidArgument, "Distributed queues can't be bisected" if queue.distributed?
127
+ raise InvalidArgument, "Distributed queues can't be bisected" if @queue.distributed?
129
128
 
130
129
  @config.selectors = Selector.new(@config).parse(@argv)
131
130
  Megatest.load_config(@config)
132
131
  Megatest.init(@config)
133
132
  test_cases = Megatest.load_tests(@config)
134
- queue.populate(test_cases)
135
- candidates = queue.dup
133
+ @queue.populate(test_cases)
134
+ candidates = @queue.dup
136
135
 
137
136
  if test_cases.empty?
138
137
  @err.puts "No tests to run"
139
138
  return 1
140
139
  end
141
140
 
142
- unless failure = find_failing_test(queue)
141
+ unless failure = find_failing_test
143
142
  @err.puts "No failing test"
144
143
  return 1
145
144
  end
@@ -172,13 +171,13 @@ module Megatest
172
171
  reporters
173
172
  end
174
173
 
175
- def find_failing_test(queue)
174
+ def find_failing_test
176
175
  @config.max_consecutive_failures = 1
177
176
  @config.jobs_count = 1
178
177
 
179
178
  executor = MultiProcess::Executor.new(@config.dup, @out)
180
- executor.run(queue, default_reporters)
181
- queue.summary.failures.first
179
+ executor.run(@queue, default_reporters)
180
+ @queue.summary.failures.first
182
181
  end
183
182
 
184
183
  def bisect_queue(queue, failing_test_id)
@@ -260,10 +259,9 @@ module Megatest
260
259
  end
261
260
  end
262
261
 
263
- def build_parser(runner)
264
- runner = :run if runner.nil?
262
+ def build_parser
265
263
  OptionParser.new do |opts|
266
- case runner
264
+ case @command
267
265
  when :report
268
266
  opts.banner = "Usage: #{@program_name} report [options]"
269
267
  when :run
@@ -281,7 +279,7 @@ module Megatest
281
279
  opts.separator "\t\t\t $ #{@program_name} test/my_test.rb:42 test/another_test.rb:36"
282
280
  opts.separator ""
283
281
 
284
- opts.separator "\treport\t\tWait for the queue to be entirely processed and report the status"
282
+ opts.separator "\treport\t\tWait for the queue to be entirely processed and report the status."
285
283
  opts.separator "\t\t\t $ #{@program_name} report --queue redis://ci-queue.example.com --build-id $CI_BUILD_ID"
286
284
  opts.separator ""
287
285
 
@@ -290,50 +288,51 @@ module Megatest
290
288
  opts.separator "\t\t\t $ #{@program_name} bisect --queue path/to/test_order.log"
291
289
  opts.separator ""
292
290
  end
291
+ @command ||= :run
293
292
 
294
293
  opts.separator ""
295
294
  opts.separator "Options:"
296
295
  opts.separator ""
297
296
 
298
- opts.on("-I PATHS", "specify $LOAD_PATH directory (may be used more than once)") do |paths|
297
+ opts.on("-I PATHS", "Specify $LOAD_PATH directory (may be used more than once).") do |paths|
299
298
  paths.split(":").each do |path|
300
299
  $LOAD_PATH.unshift(path)
301
300
  end
302
301
  end
303
302
 
304
- opts.on("-b", "--backtrace", "Print full backtraces") do
303
+ opts.on("-b", "--backtrace", "Print full backtraces.") do
305
304
  @config.backtrace.full!
306
305
  end
307
306
 
308
- opts.on("-v", "--verbose", "Use the verbose reporter") do
307
+ opts.on("-v", "--verbose", "Use the verbose reporter.") do
309
308
  @verbose = true
310
309
  end
311
310
 
312
- opts.on("--junit [PATH]", String, "Generate a junit.xml file") do |path|
311
+ opts.on("--junit [PATH]", String, "Generate a junit.xml file.") do |path|
313
312
  @junit = path
314
313
  end
315
314
 
316
- if %i[run bisect].include?(runner)
317
- opts.on("--seed SEED", Integer, "The seed used to define run order") do |seed|
315
+ if %i[run bisect].include?(@command)
316
+ opts.on("--seed SEED", Integer, "The seed used to define run order.") do |seed|
318
317
  @config.seed = seed
319
318
  end
320
319
  end
321
320
 
322
- if runner == :run
323
- opts.on("-j", "--jobs JOBS", Integer, "Number of processes to use") do |jobs|
324
- @config.jobs_count = jobs
321
+ if @command == :run
322
+ opts.on("-j", "--jobs [JOBS]", Integer, "Number of processes to use. Defaults to the number of processors.") do |jobs|
323
+ @config.jobs_count = jobs || :number_of_processors
325
324
  end
326
325
 
327
- help = "Number of consecutive failures before exiting. Default to 1"
326
+ help = "Number of consecutive failures before exiting. Defaults to 1."
328
327
  opts.on("-f", "--fail-fast [COUNT]", Integer, help) do |max|
329
328
  @config.max_consecutive_failures = (max || 1)
330
329
  end
331
330
 
332
- opts.on("--max-retries COUNT", Integer, "How many times a given test may be retried") do |max_retries|
331
+ opts.on("--max-retries COUNT", Integer, "How many times a given test may be retried.") do |max_retries|
333
332
  @config.max_retries = max_retries
334
333
  end
335
334
 
336
- opts.on("--retry-tolerance RATE", Float, "The proportion of tests that may be retried. e.g. 0.05 for 5% of retried tests") do |retry_tolerance|
335
+ opts.on("--retry-tolerance RATE", Float, "The proportion of tests that may be retried, e.g. 0.05 for 5% of retried tests.") do |retry_tolerance|
337
336
  @config.retry_tolerance = retry_tolerance
338
337
  end
339
338
  end
@@ -342,22 +341,22 @@ module Megatest
342
341
  opts.separator "Test distribution and sharding:"
343
342
  opts.separator ""
344
343
 
345
- opts.on("--queue URL", String, "URL of queue server to use for test distribution. Default to $MEGATEST_QUEUE_URL") do |queue_url|
344
+ opts.on("--queue URL", String, "URL of queue server to use for test distribution. Default to $MEGATEST_QUEUE_URL.") do |queue_url|
346
345
  @config.queue_url = queue_url
347
346
  end
348
347
 
349
- if %i[run report].include?(runner)
350
- opts.on("--build-id ID", String, "Unique identifier for the CI build") do |build_id|
348
+ if %i[run report].include?(@command)
349
+ opts.on("--build-id ID", String, "Unique identifier for the CI build.") do |build_id|
351
350
  @config.build_id = build_id
352
351
  end
353
352
  end
354
353
 
355
- if runner == :run
356
- opts.on("--worker-id ID", String, "Unique identifier for the CI job") do |worker_id|
354
+ if @command == :run
355
+ opts.on("--worker-id ID", String, "Unique identifier for the CI job.") do |worker_id|
357
356
  @config.worker_id = worker_id
358
357
  end
359
358
 
360
- opts.on("--workers-count COUNT", Integer, "Number of CI jobs") do |workers_count|
359
+ opts.on("--workers-count COUNT", Integer, "Number of CI jobs.") do |workers_count|
361
360
  @config.workers_count = workers_count
362
361
  end
363
362
  end
@@ -4,18 +4,6 @@
4
4
 
5
5
  module Megatest
6
6
  module Compat
7
- unless Enumerable.method_defined?(:filter_map) # RUBY_VERSION >= "2.7"
8
- module FilterMap
9
- refine Enumerable do
10
- def filter_map(&block)
11
- result = map(&block)
12
- result.compact!
13
- result
14
- end
15
- end
16
- end
17
- end
18
-
19
7
  unless Symbol.method_defined?(:start_with?) # RUBY_VERSION >= "2.7"
20
8
  module StartWith
21
9
  refine Symbol do
@@ -136,11 +136,11 @@ module Megatest
136
136
  end
137
137
 
138
138
  class Config
139
- attr_accessor :queue_url, :retry_tolerance, :max_retries, :jobs_count, :job_index, :load_paths, :deprecations,
139
+ attr_accessor :queue_url, :retry_tolerance, :max_retries, :job_index, :load_paths, :deprecations,
140
140
  :build_id, :heartbeat_frequency, :minitest_compatibility, :ci, :selectors
141
141
  attr_reader :before_fork_callbacks, :global_setup_callbacks, :backtrace, :circuit_breaker, :seed,
142
142
  :worker_id, :workers_count, :test_globs
143
- attr_writer :differ, :pretty_printer, :program_name, :colors
143
+ attr_writer :jobs_count, :differ, :pretty_printer, :program_name, :colors
144
144
 
145
145
  def initialize(env)
146
146
  @load_paths = ["test"] # For easier transition from other frameworks
@@ -153,7 +153,7 @@ module Megatest
153
153
  @build_id = nil
154
154
  @worker_id = nil
155
155
  @workers_count = 1
156
- @jobs_count = 1
156
+ @jobs_count = nil
157
157
  @colors = nil # auto
158
158
  @before_fork_callbacks = []
159
159
  @global_setup_callbacks = []
@@ -186,6 +186,25 @@ module Megatest
186
186
  @program_name || "megatest"
187
187
  end
188
188
 
189
+ def jobs_count
190
+ if @jobs_count == :number_of_processors
191
+ if Megatest.fork?
192
+ require "etc"
193
+ nprocessors = Etc.nprocessors
194
+ jobs = [nprocessors, cgroups_cpu_quota&.to_i || nprocessors].min
195
+ @jobs_count = [jobs, 1].max
196
+ else
197
+ @jobs_count = 1
198
+ end
199
+ end
200
+ @jobs_count ||= 1
201
+ end
202
+
203
+ def parallelize_maybe
204
+ @jobs_count ||= :number_of_processors if Megatest.fork?
205
+ self
206
+ end
207
+
189
208
  def worker_id=(id)
190
209
  @worker_id = if id.is_a?(String) && /\A\d+\z/.match?(id)
191
210
  Integer(id)
@@ -314,6 +333,28 @@ module Megatest
314
333
 
315
334
  private
316
335
 
336
+ def cgroups_cpu_quota
337
+ if RbConfig::CONFIG["target_os"].include?("linux")
338
+ if File.exist?("/sys/fs/cgroup/cpu.max")
339
+ # cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
340
+ cpu_max = File.read("/sys/fs/cgroup/cpu.max")
341
+ return nil if cpu_max.start_with?("max ") # no limit
342
+
343
+ max, period = cpu_max.split.map(&:to_f)
344
+ max / period
345
+ elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
346
+ # cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
347
+ max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
348
+ # If the cpu.cfs_quota_us is -1, cgroup does not adhere to any CPU time restrictions
349
+ # https://docs.kernel.org/scheduler/sched-bwc.html#management
350
+ return nil if max <= 0
351
+
352
+ period = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us").to_f
353
+ max / period
354
+ end
355
+ end
356
+ end
357
+
317
358
  def normalize_test_glob(patterns)
318
359
  if patterns
319
360
  Array(patterns).compact.map(&:to_s)
data/lib/megatest/dsl.rb CHANGED
@@ -108,11 +108,11 @@ module Megatest
108
108
  # end
109
109
  # end
110
110
  #
111
- # Setup and teardown callbacks are not allowed withing a context blocks,
111
+ # Setup and teardown callbacks are not allowed within a context blocks,
112
112
  # as it too easily lead to "write only" tests. It's only meant to help
113
113
  # group test cases together.
114
114
  #
115
- # If you need a common setup procedure, just define a helper method, and explictly call it.
115
+ # If you need a common setup procedure, just define a helper method, and explicitly call it.
116
116
  #
117
117
  # Example:
118
118
  #
@@ -136,12 +136,18 @@ module Megatest
136
136
  end
137
137
 
138
138
  # Registers a block to be invoked before every test cases.
139
- def setup(&block)
140
- ::Megatest.registry.suite(self).on_setup(block)
139
+ def setup(*methods, &block)
140
+ suite = ::Megatest.registry.suite(self)
141
+ methods.each do |m|
142
+ suite.on_setup(-> { send(m) })
143
+ end
144
+ if block
145
+ suite.on_setup(block)
146
+ end
141
147
  end
142
148
 
143
149
  # Registers a block to be invoked around every test cases.
144
- # The block will recieve a Proc as first argument and MUST
150
+ # The block will receive a Proc as first argument and MUST
145
151
  # call it.
146
152
  #
147
153
  # Example:
@@ -157,8 +163,14 @@ module Megatest
157
163
 
158
164
  # Registers a block to be invoked after every test cases,
159
165
  # regardless of whether it passed or failed.
160
- def teardown(&block)
161
- ::Megatest.registry.suite(self).on_teardown(block)
166
+ def teardown(*methods, &block)
167
+ suite = ::Megatest.registry.suite(self)
168
+ methods.each do |m|
169
+ suite.on_teardown(-> { send(m) })
170
+ end
171
+ if block
172
+ suite.on_teardown(block)
173
+ end
162
174
  end
163
175
  end
164
176
  end
@@ -3,7 +3,26 @@
3
3
  # :stopdoc:
4
4
 
5
5
  module Megatest
6
- class Executor
6
+ class AbstractExecutor
7
+ def initialize(config, out)
8
+ @config = config
9
+ @out = Output.new(out, colors: @config.colors(out))
10
+ end
11
+
12
+ def run(queue, reporters)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def concurrent?
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def wall_time
21
+ raise NotImplementedError
22
+ end
23
+ end
24
+
25
+ class Executor < AbstractExecutor
7
26
  class ExternalMonitor
8
27
  def initialize(config)
9
28
  require "rbconfig"
@@ -48,11 +67,6 @@ module Megatest
48
67
 
49
68
  attr_reader :wall_time
50
69
 
51
- def initialize(config, out)
52
- @config = config
53
- @out = Output.new(out, colors: @config.colors)
54
- end
55
-
56
70
  def concurrent?
57
71
  false
58
72
  end
@@ -35,7 +35,7 @@ module Megatest
35
35
 
36
36
  def read
37
37
  Marshal.load(@socket)
38
- rescue EOFError
38
+ rescue EOFError, Errno::ECONNRESET
39
39
  nil # Other side was closed
40
40
  end
41
41
 
@@ -203,12 +203,11 @@ module Megatest
203
203
  end
204
204
  end
205
205
 
206
- class Executor
206
+ class Executor < AbstractExecutor
207
207
  attr_reader :wall_time
208
208
 
209
- def initialize(config, out, managed: false)
210
- @config = config
211
- @out = Output.new(out, colors: config.colors)
209
+ def initialize(*args, managed: false)
210
+ super(*args)
212
211
  @managed = managed
213
212
  end
214
213
 
@@ -122,7 +122,7 @@ module Megatest
122
122
  last_b_pos = b_lo - 1
123
123
 
124
124
  longest_unique_subsequence(a[a_lo...a_hi], b[b_lo...b_hi]).each do |(a_pos, b_pos)|
125
- # recurse betwen unique lines
125
+ # recurse between unique lines
126
126
  a_pos += a_lo
127
127
  b_pos += b_lo
128
128
  if (last_a_pos + 1 != a_pos) || (last_b_pos + 1 != b_pos)
@@ -38,7 +38,7 @@ module Megatest
38
38
  # Check whether the object_id +id+ is in the current buffer of objects
39
39
  # to be pretty printed. Used to break cycles in chains of objects to be
40
40
  # pretty printed.
41
- def check_inspect_key(id)
41
+ def check_inspect_key?(id)
42
42
  @recursive_key&.include?(id)
43
43
  end
44
44
 
@@ -63,7 +63,7 @@ module Megatest
63
63
  # detection
64
64
  obj = obj.__getobj__ if defined?(::Delegator) && ::Delegator === obj
65
65
 
66
- if check_inspect_key(obj)
66
+ if check_inspect_key?(obj)
67
67
  group { pretty_print_cycle(obj) }
68
68
  return
69
69
  end
@@ -169,7 +169,7 @@ module Megatest
169
169
  alias_method :global_summary, :summary
170
170
 
171
171
  def initialize(config)
172
- super(config)
172
+ super
173
173
 
174
174
  @queue = nil
175
175
  @summary = Summary.new
@@ -228,8 +228,8 @@ module Megatest
228
228
 
229
229
  def record_result(result)
230
230
  @leases.delete(result.test_id)
231
- if result.failed?
232
- if attempt_to_retry(result)
231
+ if result.failed? && !result.skipped?
232
+ if attempt_to_retry?(result)
233
233
  result = result.retry
234
234
  else
235
235
  @success &&= result.ok?
@@ -241,7 +241,7 @@ module Megatest
241
241
 
242
242
  private
243
243
 
244
- def attempt_to_retry(result)
244
+ def attempt_to_retry?(result)
245
245
  return false unless @config.retries?
246
246
  return false unless @summary.retries_count < @config.total_max_retries(@size)
247
247
  return false unless @retries[result.test_id] < @config.max_retries
@@ -13,7 +13,7 @@ module Megatest
13
13
  #
14
14
  # - "leader-status": String, either `setup` or `ready`
15
15
  #
16
- # - "queue": List, contains the test ids that haven't yet been poped.
16
+ # - "queue": List, contains the test ids that haven't yet been popped.
17
17
  #
18
18
  # - "running": SortedSet, members are the test ids currently being processed.
19
19
  # Scores are the lease expiration timestamp. If the score is lower than
@@ -284,8 +284,8 @@ module Megatest
284
284
 
285
285
  def record_result(original_result)
286
286
  result = original_result
287
- if result.failed?
288
- if attempt_to_retry(result)
287
+ if result.failed? && !result.skipped?
288
+ if attempt_to_retry?(result)
289
289
  result = result.retry
290
290
  else
291
291
  @success = false
@@ -381,8 +381,9 @@ module Megatest
381
381
 
382
382
  return true
383
383
  LUA
384
+ private_constant :REQUEUE
384
385
 
385
- def attempt_to_retry(result)
386
+ def attempt_to_retry?(result)
386
387
  return false unless @config.retries?
387
388
 
388
389
  index = @config.random.rand(0..@redis.call("llen", key("queue")))
@@ -435,7 +436,7 @@ module Megatest
435
436
  end
436
437
 
437
438
  def worker_id
438
- @worker_id or raise Error, "RedisQueue not configued with a worker id"
439
+ @worker_id or raise Error, "RedisQueue not configured with a worker id"
439
440
  end
440
441
 
441
442
  class RetryQueue < Queue
@@ -9,7 +9,7 @@ module Megatest
9
9
 
10
10
  def initialize(config, out)
11
11
  @config = config
12
- @out = Output.new(out, colors: config.colors)
12
+ @out = Output.new(out, colors: config.colors(out))
13
13
  end
14
14
 
15
15
  def start(_executor, _queue)
@@ -32,6 +32,7 @@ module Megatest
32
32
  failure: "Failure",
33
33
  skipped: "Skipped",
34
34
  }.freeze
35
+ private_constant :LABELS
35
36
 
36
37
  def render_failure(result, command: true)
37
38
  str = "#{LABELS.fetch(result.status)}: #{result.test_id}\n"
@@ -70,7 +71,9 @@ module Megatest
70
71
 
71
72
  class SimpleReporter < AbstractReporter
72
73
  def start(_executor, queue)
73
- @out.puts("Running #{queue.size} test cases with --seed #{@config.seed}")
74
+ @out.print("Running #{queue.size} test cases with --seed #{@config.seed}")
75
+ @out.print(" in #{@config.jobs_count} processes") if @config.jobs_count > 1
76
+ @out.puts
74
77
  @out.puts
75
78
  end
76
79
 
@@ -125,7 +128,7 @@ module Megatest
125
128
  end
126
129
  end
127
130
 
128
- @out.puts format(
131
+ @out.print format(
129
132
  "Ran %d cases, %d assertions, %d failures, %d errors, %d retries, %d skips",
130
133
  summary.runs_count,
131
134
  summary.assertions_count,
@@ -134,6 +137,8 @@ module Megatest
134
137
  summary.retries_count,
135
138
  summary.skips_count,
136
139
  )
140
+ @out.print(" in #{@config.jobs_count} processes") if @config.jobs_count > 1
141
+ @out.puts
137
142
  end
138
143
 
139
144
  def s(duration)
@@ -10,9 +10,7 @@ module Megatest
10
10
 
11
11
  def execute(test_case)
12
12
  if test_case.tag(:isolated)
13
- isolate(test_case) do
14
- run(test_case)
15
- end
13
+ isolate(test_case)
16
14
  else
17
15
  run(test_case)
18
16
  end
@@ -23,7 +21,7 @@ module Megatest
23
21
  read, write = IO.pipe.each(&:binmode)
24
22
  pid = Process.fork do
25
23
  read.close
26
- result = yield
24
+ result = run(test_case)
27
25
  Marshal.dump(result, write)
28
26
  write.close
29
27
  # We don't want to run at_exit hooks the app may have
@@ -98,7 +98,7 @@ module Megatest
98
98
  elsif !keyword.nil?
99
99
  raise ArgumentError, "Can't pass both a positional and keyword assertion message"
100
100
  else
101
- positional # TODO: deprecation mecanism
101
+ positional # TODO: deprecation mechanism
102
102
  end
103
103
  end
104
104
 
@@ -28,14 +28,14 @@ module Megatest
28
28
  using Compat::StartWith unless Symbol.method_defined?(:start_with?)
29
29
 
30
30
  class Suite
31
- attr_reader :setup_callback, :teardown_callback, :around_callback
31
+ attr_reader :setup_callbacks, :teardown_callbacks, :around_callbacks
32
32
 
33
33
  def initialize(registry)
34
34
  @registry = registry
35
35
  @tags = nil
36
- @setup_callback = nil
37
- @teardown_callback = nil
38
- @around_callback = nil
36
+ @setup_callbacks = []
37
+ @teardown_callbacks = []
38
+ @around_callbacks = []
39
39
  @current_context = nil
40
40
  @current_tags = nil
41
41
  end
@@ -87,26 +87,21 @@ module Megatest
87
87
  end
88
88
 
89
89
  def on_setup(block)
90
- raise Error, "The setup block is already defined" if @setup_callback
91
90
  raise Error, "setup blocks can't be defined in context blocks" if @current_context
92
91
 
93
- @setup_callback = block
92
+ @setup_callbacks.unshift(block)
94
93
  end
95
94
 
96
95
  def on_around(block)
97
- raise Error, "The around block is already defined" if @around_callback
98
96
  raise Error, "around blocks can't be defined in context blocks" if @current_context
99
97
 
100
- @around_callback = block
98
+ @around_callbacks << block
101
99
  end
102
100
 
103
101
  def on_teardown(block)
104
- if @teardown_callback
105
- raise Error, "The teardown block was already defined as #{@teardown_callback}"
106
- end
107
102
  raise Error, "teardown blocks can't be defined in context blocks" if @current_context
108
103
 
109
- @teardown_callback = block
104
+ @teardown_callbacks << block
110
105
  end
111
106
  end
112
107
 
@@ -370,21 +365,19 @@ module Megatest
370
365
  cmp || 0
371
366
  end
372
367
 
373
- def each_setup_callback
368
+ def each_setup_callback(&block)
374
369
  @test_suite.ancestors.reverse_each do |test_suite|
375
- yield test_suite.setup_callback if test_suite.setup_callback
370
+ test_suite.setup_callbacks.each(&block)
376
371
  end
377
372
  end
378
373
 
379
- using Compat::FilterMap unless Enumerable.method_defined?(:filter_map)
380
-
381
374
  def around_callbacks
382
- @test_suite.ancestors.filter_map(&:around_callback)
375
+ @test_suite.ancestors.flat_map(&:around_callbacks)
383
376
  end
384
377
 
385
- def each_teardown_callback
378
+ def each_teardown_callback(&block)
386
379
  @test_suite.ancestors.each do |test_suite|
387
- yield test_suite.teardown_callback if test_suite.teardown_callback
380
+ test_suite.teardown_callbacks.each(&block)
388
381
  end
389
382
  end
390
383
 
@@ -105,7 +105,7 @@ module Megatest # :nodoc:
105
105
  end
106
106
 
107
107
  def define # :nodoc:
108
- desc "Run the test suite. Use N, X, A, and TESTOPTS to add flags/args."
108
+ desc "Run the test suite."
109
109
  task name => Array(deps) do
110
110
  sh(*make_test_cmd, verbose: verbose)
111
111
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Megatest
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/megatest.rb CHANGED
@@ -73,14 +73,14 @@ module Megatest
73
73
  end
74
74
 
75
75
  def load_files(paths, name)
76
- scaned = {}
76
+ scanned = {}
77
77
  paths.each do |path|
78
78
  path = File.dirname(path) unless File.directory?(path)
79
79
 
80
80
  while path.start_with?(PWD)
81
- break if scaned[path]
81
+ break if scanned[path]
82
82
 
83
- scaned[path] = true
83
+ scanned[path] = true
84
84
 
85
85
  config_path = File.join(path, name)
86
86
  if File.exist?(config_path)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: megatest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier