megatest 0.3.0 → 0.5.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: dce0e1b9ec47a98020fabc266db9d93f73961574ed6ef0bf29b4885064114216
4
- data.tar.gz: c4dd6ed24dea6de74effca99474a850af28b77142a0c388dff2f4faf2f4b5338
3
+ metadata.gz: b0be0dcf9dd26312cec897081522ae6ac404db5447b26d50286ccc8720422743
4
+ data.tar.gz: 2580f85814d9469dfd1df3c8d3601c8b5ac89b4e4faaf706b4af47154d3c9bdb
5
5
  SHA512:
6
- metadata.gz: 827fa2e37b36c4e8d7fae1317be65d259bac20210276245c119cbf33d506e967514295d4d74cc9fefd499e60aefacfd4a752f17f83feb58a46387af3d516c302
7
- data.tar.gz: 3bfe1897107565dd27ae0eef08857b123eea0f181009669ab3e6c676f7fcbed7266fa3708f7b88589aadd518cb43cfaff8402a9648db5828373759b8219e2f6f
6
+ metadata.gz: ef25ab7ecc09133635d0ffaef659ce02574f01fd558f2437c187fd3f4f362b40cd13dfcb6d6ac455e0a1462b507dfb6e27efd74e72d18ed57e68f8c1434b25e2
7
+ data.tar.gz: 68b8dc14b4869a5dea9d51f2c507287acb5bbcce709b1514c965f2b0cc9a0d8edd4da82a89b8e120427e63ece0c159a2f4f27d92362f1b25ded3f9cbdd05c402
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2025-01-17
4
+
5
+ - Adds `megatest/autorun`
6
+ - Adds `assert_nothing_raised`.
7
+ - Adds `assert_difference`.
8
+ - Adds `assert_changes`.
9
+ - Adds `assert_not_*` aliases.
10
+ - Adds `match:` argument to `assert_raises`.
11
+
12
+ ## [0.4.0] - 2025-11-12
13
+
14
+ - Allow configuring the test glob via `config.test_globs`.
15
+
3
16
  ## [0.3.0] - 2025-06-20
4
17
 
5
18
  - Added missing MIT license.
@@ -67,6 +67,7 @@ module Megatest
67
67
  end
68
68
  end
69
69
  end
70
+ alias_method :assert_not, :refute
70
71
 
71
72
  def assert_nil(actual, msg = nil, message: nil)
72
73
  message = @__m.msg(msg, message)
@@ -85,6 +86,7 @@ module Megatest
85
86
  end
86
87
  end
87
88
  end
89
+ alias_method :assert_not_nil, :refute_nil
88
90
 
89
91
  def assert_equal(expected, actual, msg = nil, message: nil, allow_nil: false)
90
92
  message = @__m.msg(msg, message)
@@ -116,6 +118,7 @@ module Megatest
116
118
  end
117
119
  end
118
120
  end
121
+ alias_method :assert_not_equal, :refute_equal
119
122
 
120
123
  def assert_includes(collection, object, msg = nil, message: nil)
121
124
  message = @__m.msg(msg, message)
@@ -134,6 +137,7 @@ module Megatest
134
137
  end
135
138
  end
136
139
  end
140
+ alias_method :assert_not_includes, :refute_includes
137
141
 
138
142
  def assert_empty(object, msg = nil, message: nil)
139
143
  message = @__m.msg(msg, message)
@@ -152,6 +156,7 @@ module Megatest
152
156
  end
153
157
  end
154
158
  end
159
+ alias_method :assert_not_empty, :refute_empty
155
160
 
156
161
  def assert_instance_of(klass, actual, msg = nil, message: nil)
157
162
  message = @__m.msg(msg, message)
@@ -170,6 +175,7 @@ module Megatest
170
175
  end
171
176
  end
172
177
  end
178
+ alias_method :assert_not_instance_of, :refute_instance_of
173
179
 
174
180
  def assert_kind_of(klass, actual, msg = nil, message: nil)
175
181
  message = @__m.msg(msg, message)
@@ -188,6 +194,7 @@ module Megatest
188
194
  end
189
195
  end
190
196
  end
197
+ alias_method :assert_not_kind_of, :refute_kind_of
191
198
 
192
199
  def assert_predicate(actual, predicate, msg = nil, message: nil)
193
200
  message = @__m.msg(msg, message)
@@ -206,6 +213,7 @@ module Megatest
206
213
  end
207
214
  end
208
215
  end
216
+ alias_method :assert_not_predicate, :refute_predicate
209
217
 
210
218
  def assert_match(original_matcher, obj, msg = nil, message: nil)
211
219
  message = @__m.msg(msg, message)
@@ -238,6 +246,7 @@ module Megatest
238
246
  end
239
247
  end
240
248
  end
249
+ alias_method :assert_no_match, :refute_match
241
250
 
242
251
  def assert_respond_to(object, method, msg = nil, message: nil, include_all: false)
243
252
  message = @__m.msg(msg, message)
@@ -256,6 +265,7 @@ module Megatest
256
265
  end
257
266
  end
258
267
  end
268
+ alias_method :assert_not_respond_to, :refute_respond_to
259
269
 
260
270
  def assert_same(expected, actual, msg = nil, message: nil)
261
271
  message = @__m.msg(msg, message)
@@ -294,21 +304,36 @@ module Megatest
294
304
  end
295
305
  end
296
306
  end
307
+ alias_method :assert_not_same, :refute_same
297
308
 
298
- def assert_raises(expected = StandardError, *expected_exceptions, message: nil)
309
+ def assert_raises(expected = StandardError, *expected_exceptions, match: nil, message: nil)
299
310
  msg = expected_exceptions.pop if expected_exceptions.last.is_a?(String)
300
311
  message = @__m.msg(msg, message)
312
+
313
+ matcher = if match
314
+ if ::String === match
315
+ ::Regexp.new(::Regexp.escape(match))
316
+ else
317
+ match
318
+ end
319
+ end
320
+
301
321
  @__m.assert do
302
322
  @__m.fail("assert_raises requires a block to capture errors.") unless block_given?
303
323
 
304
324
  begin
325
+ before_yield = __LINE__
305
326
  yield
306
327
  rescue expected, *expected_exceptions => exception
328
+ if matcher && !matcher.match?(exception.message)
329
+ @__m.fail(message, "Expected", @__m.pp(match), "to match", @__m.pp(exception.message))
330
+ end
331
+
307
332
  return exception
308
333
  rescue ::Megatest::Assertion, *::Megatest::IGNORED_ERRORS
309
334
  raise # Pass through
310
335
  rescue ::Exception => unexepected_exception
311
- error = @__m.strip_backtrace(unexepected_exception, __FILE__, __LINE__ - 6, 0)
336
+ error = @__m.strip_backtrace(unexepected_exception, __FILE__, before_yield + 1, 0)
312
337
 
313
338
  expected_pp = if expected_exceptions.empty?
314
339
  @__m.pp(expected)
@@ -317,15 +342,26 @@ module Megatest
317
342
  end
318
343
 
319
344
  @__m.fail(message, "#{expected_pp} exception expected, not:\n#{@__m.pp(error)}")
320
- end
321
-
322
- expected_pp = if expected_exceptions.empty?
323
- @__m.pp(expected)
324
345
  else
325
- expected_exceptions.map { |e| @__m.pp(e) }.join(", ") << " or #{@__m.pp(expected)}"
346
+ expected_pp = if expected_exceptions.empty?
347
+ @__m.pp(expected)
348
+ else
349
+ expected_exceptions.map { |e| @__m.pp(e) }.join(", ") << " or #{@__m.pp(expected)}"
350
+ end
351
+
352
+ @__m.fail(message, "Expected", expected_pp, "but nothing was raised.")
326
353
  end
354
+ end
355
+ end
356
+ alias :assert_raise :assert_raises
327
357
 
328
- @__m.fail(message, "Expected", expected_pp, "but nothing was raised.")
358
+ def assert_nothing_raised
359
+ @__m.assert do
360
+ yield
361
+ rescue ::Megatest::Assertion, *::Megatest::IGNORED_ERRORS
362
+ raise # Pass through
363
+ rescue Exception => unexepected_exception
364
+ raise ::Megatest::UnexpectedError, unexepected_exception
329
365
  end
330
366
  end
331
367
 
@@ -367,6 +403,114 @@ module Megatest
367
403
  end
368
404
  end
369
405
  end
406
+ alias_method :assert_not_operator, :refute_operator
407
+
408
+ def assert_difference(expression, difference = @__m.unset, message: nil, &block)
409
+ expressions = if @__m.set?(difference)
410
+ Array(expression).to_h { |e| [e, difference] }
411
+ elsif Hash === expression
412
+ expression
413
+ else
414
+ Array(expression).to_h { |e| [e, 1] }
415
+ end
416
+
417
+ exps = expressions.keys.map { |e| @__m.expression(e, block) }
418
+
419
+ @__m.assert do
420
+ before = exps.map(&:call)
421
+
422
+ retval = @__m.safe_yield(&block)
423
+
424
+ expressions.zip(exps, before) do |(code, diff), exp, before_value|
425
+ actual = exp.call
426
+ expected = before_value + diff
427
+ unless expected == actual
428
+ error = "`#{@__m.pp_expression(code)}` didn't change by #{diff}, but by #{actual - before_value}."
429
+ @__m.fail(message, error)
430
+ end
431
+ end
432
+
433
+ retval
434
+ end
435
+ end
436
+
437
+ def refute_difference(expressions, message: nil, &block)
438
+ exps = Array(expressions).map { |e| @__m.expression(e, block) }
439
+
440
+ @__m.assert do
441
+ before = exps.map(&:call)
442
+
443
+ retval = @__m.safe_yield(&block)
444
+
445
+ exps.zip(before) do |exp, before_value|
446
+ actual = exp.call
447
+ unless before_value == actual
448
+ error = "Expected `#{@__m.pp_expression(exp)}` to not change, but it changed from #{@__m.pp(before_value)} to #{@__m.pp(actual)}."
449
+ @__m.fail(message, error)
450
+ end
451
+ end
452
+
453
+ retval
454
+ end
455
+ end
456
+ alias_method :assert_no_difference, :refute_difference
457
+
458
+ def assert_changes(expression, msg = nil, message: nil, from: @__m.unset, to: @__m.unset, &block)
459
+ message = @__m.msg(msg, message)
460
+ exp = @__m.expression(expression, block)
461
+ @__m.assert do
462
+ before = exp.call
463
+ if @__m.set?(from) && !(from === before)
464
+ @__m.fail(message, "Expected `#{@__m.pp_expression(exp)}` to starts from #{@__m.pp(from)}, but was #{@__m.pp(before)}")
465
+ end
466
+
467
+ retval = assert_nothing_raised(&block)
468
+
469
+ after = exp.call
470
+
471
+ if before == after
472
+ details = "Expected `#{@__m.pp_expression(exp)}` to change"
473
+ if @__m.set?(to)
474
+ details = "#{details} to #{@__m.pp(to)}"
475
+ end
476
+
477
+ if before == to
478
+ details = "#{details}, but it was already #{@__m.pp(to)}."
479
+ else
480
+ details = "#{details}, but it stayed #{@__m.pp(before)}"
481
+ end
482
+
483
+ @__m.fail(message, details)
484
+ end
485
+
486
+ if @__m.set?(to) && !(to === after)
487
+ @__m.fail(message, "Expected `#{@__m.pp_expression(exp)}` to change to #{@__m.pp(to)}, got #{@__m.pp(after)}")
488
+ end
489
+
490
+ retval
491
+ end
492
+ end
493
+
494
+ def refute_changes(expression, msg = nil, message: nil, from: @__m.unset, &block)
495
+ message = @__m.msg(msg, message)
496
+ exp = @__m.expression(expression, block)
497
+ @__m.assert do
498
+ before = exp.call
499
+ if @__m.set?(from) && !(from === before)
500
+ @__m.fail(message, "Expected `#{@__m.pp_expression(exp)}` to start from #{@__m.pp(from)}, but was #{@__m.pp(before)}.")
501
+ end
502
+
503
+ retval = assert_nothing_raised(&block)
504
+
505
+ after = exp.call
506
+ unless before == after
507
+ @__m.fail(message, "Expected `#{@__m.pp_expression(exp)}` to not change, but it changed from #{@__m.pp(before)} to #{@__m.pp(after)}.")
508
+ end
509
+
510
+ retval
511
+ end
512
+ end
513
+ alias_method :assert_no_changes, :refute_changes
370
514
 
371
515
  def assert_in_delta(expected, actual, delta = 0.001, msg = nil, message: nil)
372
516
  message = @__m.msg(msg, message)
@@ -387,6 +531,7 @@ module Megatest
387
531
  end
388
532
  end
389
533
  end
534
+ alias_method :assert_not_in_delta, :refute_in_delta
390
535
 
391
536
  def assert_in_epsilon(expected, actual, epsilon = 0.001, msg = nil, message: nil)
392
537
  message = @__m.msg(msg, message)
@@ -409,6 +554,7 @@ module Megatest
409
554
  end
410
555
  end
411
556
  end
557
+ alias_method :assert_not_epsilon, :refute_in_epsilon
412
558
 
413
559
  def skip(message = nil)
414
560
  message ||= "Skipped, no message given"
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "megatest"
4
+
5
+ unless Megatest.running
6
+ root = File.expand_path("../../", __dir__)
7
+ rubyopt = "#{ENV.fetch("RUBYOPT", nil)} -I#{root}/lib"
8
+ exec({ "RUBYOPT" => rubyopt }, "#{root}/exe/megatest", $PROGRAM_NAME, *ARGV)
9
+ end
data/lib/megatest/cli.rb CHANGED
@@ -10,7 +10,7 @@ module Megatest
10
10
 
11
11
  class << self
12
12
  def run!
13
- program_name = $PROGRAM_NAME
13
+ program_name = ENV.fetch("MEGATEST_PROGRAM_NAME", $PROGRAM_NAME)
14
14
  if paths = ENV["PATH"]
15
15
  paths.split(":").each do |path|
16
16
  if program_name.start_with?(path)
@@ -69,6 +69,8 @@ module Megatest
69
69
  end
70
70
 
71
71
  def configure
72
+ Megatest.running = true
73
+
72
74
  if @runner = RUNNERS[@argv.first]
73
75
  @argv.shift
74
76
  end
@@ -92,7 +94,7 @@ module Megatest
92
94
  end
93
95
  end
94
96
 
95
- @config.selectors = Selector.parse(@argv)
97
+ @config.selectors = Selector.new(@config).parse(@argv)
96
98
  Megatest.load_config(@config)
97
99
  Megatest.init(@config)
98
100
  test_cases = Megatest.load_tests(@config)
@@ -125,7 +127,7 @@ module Megatest
125
127
  queue = @config.build_queue
126
128
  raise InvalidArgument, "Distributed queues can't be bisected" if queue.distributed?
127
129
 
128
- @config.selectors = Selector.parse(@argv)
130
+ @config.selectors = Selector.new(@config).parse(@argv)
129
131
  Megatest.load_config(@config)
130
132
  Megatest.init(@config)
131
133
  test_cases = Megatest.load_tests(@config)
@@ -139,7 +139,7 @@ module Megatest
139
139
  attr_accessor :queue_url, :retry_tolerance, :max_retries, :jobs_count, :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
- :worker_id, :workers_count
142
+ :worker_id, :workers_count, :test_globs
143
143
  attr_writer :differ, :pretty_printer, :program_name, :colors
144
144
 
145
145
  def initialize(env)
@@ -168,12 +168,18 @@ module Megatest
168
168
  @pretty_printer = PrettyPrint.new(self)
169
169
  @minitest_compatibility = false
170
170
  @selectors = nil
171
+ @test_globs = [DEFAULT_TEST_GLOB]
171
172
  CIService.configure(self, env)
172
173
  end
173
174
 
175
+ def test_globs=(patterns)
176
+ @test_globs = normalize_test_glob(patterns)
177
+ end
178
+
174
179
  def initialize_dup(_)
175
180
  super
176
181
  @circuit_breaker = @circuit_breaker.dup
182
+ @test_globs = @test_globs.dup
177
183
  end
178
184
 
179
185
  def program_name
@@ -305,6 +311,16 @@ module Megatest
305
311
  instance_variable_set(name, value)
306
312
  end
307
313
  end
314
+
315
+ private
316
+
317
+ def normalize_test_glob(patterns)
318
+ if patterns
319
+ Array(patterns).compact.map(&:to_s)
320
+ else
321
+ [DEFAULT_TEST_GLOB]
322
+ end
323
+ end
308
324
  end
309
325
 
310
326
  @config = Config.new({})
@@ -232,11 +232,14 @@ module Megatest
232
232
  def populate(test_cases)
233
233
  super
234
234
 
235
- leader_key_set, = @redis.pipelined do |pipeline|
236
- pipeline.call("setnx", key("leader-status"), "setup")
235
+ value = key("leader-setup", worker_id)
236
+ # NB: If we assumed redis 7+ this could be a single command: `SET <key> NX GET EX @ttl <value>`.
237
+ _, _, leader = @redis.multi do |pipeline|
238
+ pipeline.call("setnx", key("leader-status"), value)
237
239
  pipeline.call("expire", key("leader-status"), @ttl)
240
+ pipeline.call("get", key("leader-status"))
238
241
  end
239
- @leader = leader_key_set == 1
242
+ @leader = leader == value
240
243
 
241
244
  if @leader
242
245
  @redis.multi do |transaction|
@@ -113,8 +113,8 @@ module Megatest
113
113
  p99 = sorted_results[(size * 0.99).to_i].duration
114
114
 
115
115
  @out.puts "Finished in #{s(executor.wall_time.to_f)}, average: #{ms(average)}, median: #{ms(median)}, p90: #{ms(p90)}, p99: #{ms(p99)}"
116
- cuttoff = p90 * 10
117
- slowest_tests = sorted_results.last(5).select { |r| r.duration > cuttoff }
116
+ cutoff = p90 * 10
117
+ slowest_tests = sorted_results.last(5).select { |r| r.duration > cutoff }
118
118
  unless slowest_tests.empty?
119
119
  @out.puts "Slowest tests:"
120
120
  slowest_tests.reverse_each do |result|
@@ -139,6 +139,49 @@ module Megatest
139
139
  end
140
140
  end
141
141
 
142
+ def safe_yield
143
+ yield
144
+ rescue Assertion, *IGNORED_ERRORS
145
+ raise
146
+ rescue ::Exception => unexepected_exception
147
+ raise UnexpectedError, unexepected_exception
148
+ end
149
+
150
+ UNSET = BasicObject.new
151
+
152
+ def unset
153
+ UNSET
154
+ end
155
+
156
+ def unset?(arg)
157
+ UNSET.equal?(arg)
158
+ end
159
+
160
+ def set?(arg)
161
+ !UNSET.equal?(arg)
162
+ end
163
+
164
+ class Expression
165
+ attr_reader :string
166
+
167
+ def initialize(string, block)
168
+ @string = string
169
+ @block = block
170
+ end
171
+
172
+ def call
173
+ eval(@string, @block.binding)
174
+ end
175
+ end
176
+
177
+ def expression(expression, block)
178
+ if String === expression
179
+ Expression.new(expression, block)
180
+ else
181
+ expression
182
+ end
183
+ end
184
+
142
185
  def minitest_compatibility?
143
186
  @config.minitest_compatibility
144
187
  end
@@ -147,6 +190,53 @@ module Megatest
147
190
  @config.render_object(object)
148
191
  end
149
192
 
193
+ def pp_expression(callable)
194
+ case callable
195
+ when Expression
196
+ callable.string
197
+ when Proc
198
+ # Logic borrowed from Active Support.
199
+ if defined?(RubyVM::InstructionSequence)
200
+ iseq = RubyVM::InstructionSequence.of(callable)
201
+ return pp(callable) unless iseq
202
+
203
+ source = if RubyVM::InstructionSequence.method_defined?(:script_lines) && iseq.script_lines
204
+ iseq.script_lines.join("\n")
205
+ elsif File.readable?(iseq.absolute_path)
206
+ File.read(iseq.absolute_path)
207
+ end
208
+ return pp(callable) unless source
209
+
210
+ location = iseq.to_a[4][:code_location]
211
+ return pp(callable) unless location
212
+
213
+ lines = source.lines[(location[0] - 1)..(location[2] - 1)]
214
+ lines[-1] = lines[-1].byteslice(0...location[3])
215
+ lines[0] = lines[0].byteslice(location[1]...-1)
216
+ source = lines.join.strip
217
+
218
+ # Ruby 4.1.0dev includes the `->`
219
+ source.delete_prefix!("->")
220
+ source.strip!
221
+
222
+ # We ignore procs defined with do/end as they are likely multi-line anyway.
223
+ if source.start_with?("{")
224
+ source.delete_suffix!("}")
225
+ source.delete_prefix!("{")
226
+ source.strip!
227
+ # It won't read nice if the callable contains multiple
228
+ # lines, and it should be a rare occurrence anyway.
229
+ # Same if it takes arguments.
230
+ if !source.include?("\n") && !source.start_with?("|")
231
+ return source
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ pp(callable)
238
+ end
239
+
150
240
  def diff(expected, actual)
151
241
  @config.diff(expected, actual)
152
242
  end
@@ -3,12 +3,12 @@
3
3
  # :stopdoc:
4
4
 
5
5
  module Megatest
6
- module Selector
6
+ class Selector
7
7
  class List
8
- def initialize(loaders, filters)
8
+ def initialize(config, loaders, filters)
9
9
  @loaders = loaders
10
10
  if loaders.empty?
11
- @loaders = [Loader.new("test")]
11
+ @loaders = [Loader.new(config, "test")]
12
12
  end
13
13
  @filters = filters
14
14
  end
@@ -56,14 +56,13 @@ module Megatest
56
56
  class Loader
57
57
  attr_reader :path
58
58
 
59
- def initialize(path, filter = nil)
59
+ def initialize(config, path, filter = nil)
60
+ @config = config
60
61
  @path = File.expand_path(path)
61
62
  if @directory = File.directory?(@path)
62
63
  @path = File.join(@path, "/")
63
- @paths = Megatest.glob(@path)
64
- else
65
- @paths = [@path]
66
64
  end
65
+ @paths = nil
67
66
  @filter = filter
68
67
  end
69
68
 
@@ -81,7 +80,7 @@ module Megatest
81
80
  end
82
81
 
83
82
  def append_paths(paths_to_load)
84
- paths_to_load.concat(@paths)
83
+ paths_to_load.concat(paths)
85
84
  end
86
85
 
87
86
  def select(registry)
@@ -99,6 +98,16 @@ module Megatest
99
98
  test_cases
100
99
  end
101
100
  end
101
+
102
+ private
103
+
104
+ def paths
105
+ @paths ||= if @directory
106
+ Megatest.glob(@config.test_globs.map { |pattern| File.join(@path, pattern) })
107
+ else
108
+ [@path]
109
+ end
110
+ end
102
111
  end
103
112
 
104
113
  class NegativeLoader
@@ -244,54 +253,56 @@ module Megatest
244
253
  NameFilter,
245
254
  ].freeze
246
255
 
247
- class << self
248
- def parse(argv)
249
- if argv.empty?
250
- return List.new([], [])
251
- end
256
+ def initialize(config)
257
+ @config = config
258
+ end
252
259
 
253
- argv = argv.dup
254
- loaders = []
255
- filters = []
260
+ def parse(argv)
261
+ if argv.empty?
262
+ return List.new(@config, [], [])
263
+ end
256
264
 
257
- negative = false
265
+ argv = argv.dup
266
+ loaders = []
267
+ filters = []
258
268
 
259
- until argv.empty?
260
- case argument = argv.shift
261
- when "!"
262
- negative = true
263
- else
264
- loader_str, filter_str = argument.split(":", 2)
265
- loader_str = nil if loader_str.empty?
266
-
267
- filter = nil
268
- if filter_str
269
- FILTERS.each do |filter_class|
270
- if filter = filter_class.parse(filter_str)
271
- break
272
- end
269
+ negative = false
270
+
271
+ until argv.empty?
272
+ case argument = argv.shift
273
+ when "!"
274
+ negative = true
275
+ else
276
+ loader_str, filter_str = argument.split(":", 2)
277
+ loader_str = nil if loader_str.empty?
278
+
279
+ filter = nil
280
+ if filter_str
281
+ FILTERS.each do |filter_class|
282
+ if filter = filter_class.parse(filter_str)
283
+ break
273
284
  end
274
285
  end
286
+ end
275
287
 
276
- if loader_str
277
- loader = Loader.new(loader_str, filter)
278
- if negative
279
- loader = NegativeLoader.new(loader)
280
- negative = false
281
- end
282
- loaders << loader
283
- else
284
- if negative
285
- filter = NegativeFilter.new(filter)
286
- negative = false
287
- end
288
- filters << filter
288
+ if loader_str
289
+ loader = Loader.new(@config, loader_str, filter)
290
+ if negative
291
+ loader = NegativeLoader.new(loader)
292
+ negative = false
289
293
  end
294
+ loaders << loader
295
+ else
296
+ if negative
297
+ filter = NegativeFilter.new(filter)
298
+ negative = false
299
+ end
300
+ filters << filter
290
301
  end
291
302
  end
292
-
293
- List.new(loaders, filters)
294
303
  end
304
+
305
+ List.new(@config, loaders, filters)
295
306
  end
296
307
  end
297
308
  end
@@ -101,7 +101,9 @@ module Megatest
101
101
  end
102
102
 
103
103
  def on_teardown(block)
104
- raise Error, "The teardown block is already defined" if @teardown_callback
104
+ if @teardown_callback
105
+ raise Error, "The teardown block was already defined as #{@teardown_callback}"
106
+ end
105
107
  raise Error, "teardown blocks can't be defined in context blocks" if @current_context
106
108
 
107
109
  @teardown_callback = block
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Megatest
4
- VERSION = "0.3.0"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/megatest.rb CHANGED
@@ -12,8 +12,13 @@ module Megatest
12
12
  ROOT = -File.expand_path("../", __FILE__)
13
13
  PWD = File.join(Dir.pwd, "/")
14
14
  IGNORED_ERRORS = [NoMemoryError, SignalException, SystemExit].freeze
15
+ DEFAULT_TEST_GLOB = "**/{test_*,*_test}.rb"
16
+
17
+ @running = false
15
18
 
16
19
  class << self
20
+ attr_accessor :running
21
+
17
22
  def fork?
18
23
  Process.respond_to?(:fork) && !ENV["NO_FORK"]
19
24
  end
@@ -90,12 +95,12 @@ module Megatest
90
95
  end
91
96
 
92
97
  if Dir.method(:glob).parameters.include?(%i(key sort)) # Ruby 2.7+
93
- def glob(path)
94
- Dir.glob(File.join(path, "**/{test_*,*_test}.rb"))
98
+ def glob(pattern)
99
+ Dir.glob(pattern)
95
100
  end
96
101
  else
97
- def glob(path)
98
- paths = Dir.glob(File.join(path, "**/{test_*,*_test}.rb"))
102
+ def glob(pattern)
103
+ paths = Dir.glob(pattern)
99
104
  paths.sort!
100
105
  paths
101
106
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: megatest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-20 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Largely API compatible with test-unit / minitest, but with lots of extra
13
13
  modern niceties like a proper CLI, test distribution, etc.
@@ -25,6 +25,7 @@ files:
25
25
  - exe/megatest
26
26
  - lib/megatest.rb
27
27
  - lib/megatest/assertions.rb
28
+ - lib/megatest/autorun.rb
28
29
  - lib/megatest/backtrace.rb
29
30
  - lib/megatest/cli.rb
30
31
  - lib/megatest/compat.rb
@@ -74,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
75
  - !ruby/object:Gem::Version
75
76
  version: '0'
76
77
  requirements: []
77
- rubygems_version: 3.6.2
78
+ rubygems_version: 4.0.3
78
79
  specification_version: 4
79
80
  summary: Modern test-unit style test framework
80
81
  test_files: []