megatest 0.1.1 → 0.2.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: b3e0e26b309c6dc9a83268b28c18cb4d47b0faec34fe15f6fb5914a694350ae7
4
- data.tar.gz: bcfe062aa918605ef748d2bf5464a95a46f99ed84b3d2e7881877f68de96cf01
3
+ metadata.gz: 5ef5044b5a70005d87f32c0da4add1e5270d3a298b20a12f4da2fefcb3b64781
4
+ data.tar.gz: 273e19eed16d3ad7ade77b297ee8e5819629e48d4c106808d52fbf0572538643
5
5
  SHA512:
6
- metadata.gz: afa34f618e96539d243b93c08f51b8e431c90eba33d253c90ae0ead4bfcd8e87ba20f5a484a62211d160eeefd9017bcf24a5a8299b744013e210350e3adb99d9
7
- data.tar.gz: c6ae412861315e06207a462ae528230778075e9fec55b7e4f91860b90feea5bad4a784a44fa0034c81d69c77be4b7d27821703810162d5367f537f0380a3df44
6
+ metadata.gz: 1179b7d68e1bfd7fd614404ef3ab4e9c724af6be4944a68160de44ec0857c120f08ba363acacee46efed133d532cc9aec27d8b8432831d61373a8c845df54a33
7
+ data.tar.gz: 81950c2704d1553a4f6239c3bf45b96213b1418c79293d8dd9070405f884c0514e2851cb8b528ce12e84525cd96b7e0eb59a76a3a24d25abe958a56ec5f142cd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Make the VerboseReporter work with concurrent executors.
4
+ - Fix isolated tests on forkless platforms when the config contains procs.
5
+ - Add a `job_teardown callback` to to stand off for at_exit.
6
+ - Add `stub`, `stub_const` and `stub_any_instance_of`.
7
+ - Add support for `-I` in the CLI.
8
+
3
9
  ## [0.1.1] - 2024-08-20
4
10
 
5
11
  - Fix `$PATH` prefix detection.
data/TODO.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ### Wants
2
2
 
3
+ - Improve run by line number: if no exact match fallback to the previous one.
4
+
3
5
  - Test leak bisect
4
6
  - See ci-queue bisect.
5
7
 
data/lib/megatest/cli.rb CHANGED
@@ -196,6 +196,12 @@ module Megatest
196
196
  opts.separator "Options:"
197
197
  opts.separator ""
198
198
 
199
+ opts.on("-I PATHS", "specify $LOAD_PATH directory (may be used more than once)") do |paths|
200
+ paths.split(":").each do |path|
201
+ $LOAD_PATH.unshift(path)
202
+ end
203
+ end
204
+
199
205
  opts.on("-b", "--backtrace", "Print full backtraces") do
200
206
  @config.backtrace.full!
201
207
  end
@@ -158,6 +158,7 @@ module Megatest
158
158
  @before_fork_callbacks = []
159
159
  @global_setup_callbacks = []
160
160
  @job_setup_callbacks = []
161
+ @job_teardown_callbacks = []
161
162
  @heartbeat_frequency = 5
162
163
  @backtrace = Backtrace.new
163
164
  @program_name = nil
@@ -264,6 +265,14 @@ module Megatest
264
265
  @job_setup_callbacks << block
265
266
  end
266
267
 
268
+ def run_job_teardown_callbacks(job_index)
269
+ @job_teardown_callbacks.each { |c| c.call(self, job_index) }
270
+ end
271
+
272
+ def job_teardown(&block)
273
+ @job_teardown_callbacks << block
274
+ end
275
+
267
276
  def retries?
268
277
  @max_retries.positive?
269
278
  end
@@ -275,6 +284,19 @@ module Megatest
275
284
  @max_retries * size
276
285
  end
277
286
  end
287
+
288
+ NOT_SERIALIZED = %i(@job_teardown_callbacks @job_setup_callbacks @global_setup_callbacks).freeze
289
+ def marshal_dump
290
+ instance_variables.reject { |k| NOT_SERIALIZED.include?(k) }.to_h do |name|
291
+ [name, instance_variable_get(name)]
292
+ end
293
+ end
294
+
295
+ def marshal_load(hash)
296
+ hash.each do |name, value|
297
+ instance_variable_set(name, value)
298
+ end
299
+ end
278
300
  end
279
301
 
280
302
  @config = Config.new({})
@@ -53,6 +53,10 @@ module Megatest
53
53
  @out = Output.new(out, colors: @config.colors)
54
54
  end
55
55
 
56
+ def concurrent?
57
+ false
58
+ end
59
+
56
60
  def run(queue, reporters)
57
61
  start_time = Megatest.now
58
62
 
@@ -98,6 +102,8 @@ module Megatest
98
102
  @out.error("Exited early because too many failures were encountered")
99
103
  end
100
104
 
105
+ @config.run_job_teardown_callbacks(nil)
106
+
101
107
  queue.cleanup
102
108
  end
103
109
  end
@@ -27,7 +27,7 @@ module Megatest
27
27
  def <<(message)
28
28
  begin
29
29
  @socket.write(Marshal.dump(message))
30
- rescue Errno::EPIPE
30
+ rescue Errno::EPIPE, Errno::ENOTCONN
31
31
  return nil # Other side was closed
32
32
  end
33
33
  self
@@ -113,6 +113,7 @@ module Megatest
113
113
 
114
114
  # We don't want to run at_exit hooks the app may have
115
115
  # installed.
116
+ @config.run_job_teardown_callbacks(@index)
116
117
  Process.exit!(0)
117
118
  end
118
119
  @child_socket.close
@@ -157,15 +158,17 @@ module Megatest
157
158
  @parent_socket.close
158
159
  when :pop
159
160
  if @assigned_test = queue.pop_test
161
+ reporters.each { |r| r.before_test_case(queue, @assigned_test) }
160
162
  @parent_socket << @assigned_test&.id
161
163
  else
162
164
  @idle = true
163
165
  end
164
166
  when :record
165
167
  result = queue.record_result(*args)
168
+ test_case = @assigned_test
166
169
  @assigned_test = nil
167
170
  @parent_socket << result
168
- reporters.each { |r| r.after_test_case(queue, nil, result) }
171
+ reporters.each { |r| r.after_test_case(queue, test_case, result) }
169
172
  @config.circuit_breaker.record_result(result)
170
173
  else
171
174
  raise "Unexpected message: #{message.inspect}"
@@ -208,6 +211,10 @@ module Megatest
208
211
  @out = Output.new(out, colors: config.colors)
209
212
  end
210
213
 
214
+ def concurrent?
215
+ true
216
+ end
217
+
211
218
  def after_fork_in_child(active_job)
212
219
  @jobs.each do |job|
213
220
  job.close unless job == active_job
@@ -217,6 +224,7 @@ module Megatest
217
224
  def run(queue, reporters)
218
225
  start_time = Megatest.now
219
226
  @config.run_global_setup_callbacks
227
+ reporters.each { |r| r.start(self, queue) }
220
228
  @jobs = @config.jobs_count.times.map { |index| Job.new(@config, index) }
221
229
 
222
230
  @config.before_fork_callbacks.each(&:call)
@@ -125,12 +125,37 @@ module Megatest
125
125
  end
126
126
 
127
127
  class VerboseReporter < SimpleReporter
128
+ def start(executor, _queue)
129
+ @concurrent = executor.concurrent?
130
+ end
131
+
128
132
  def before_test_case(_queue, test_case)
129
- @out.print("#{test_case.id} = ")
133
+ unless @concurrent
134
+ @out.print("#{test_case.id} = ")
135
+ end
130
136
  end
131
137
 
132
- def after_test_case(_queue, _test_case, result)
133
- super
138
+ def after_test_case(_queue, test_case, result)
139
+ if @concurrent
140
+ @out.print("#{test_case.id} = ")
141
+ end
142
+
143
+ if result.skipped?
144
+ @out.print(@out.yellow("SKIPPED"))
145
+ elsif result.retried?
146
+ @out.print(@out.yellow("RETRIED"))
147
+ elsif result.error?
148
+ @out.print(@out.red("ERROR"))
149
+ elsif result.failed?
150
+ @out.print(@out.red("FAILED"))
151
+ else
152
+ @out.print(@out.green("SUCCESS"))
153
+ end
154
+
155
+ if result.duration
156
+ @out.print " (in #{result.duration.round(3)}s)"
157
+ end
158
+
134
159
  @out.puts
135
160
  if result.bad?
136
161
  @out.puts @out.colored(render_failure(result))
@@ -88,6 +88,8 @@ module Megatest
88
88
 
89
89
  result.ensure_assertions unless @config.minitest_compatibility
90
90
  ensure
91
+ runtime.teardown
92
+
91
93
  runtime.record_failures do
92
94
  instance.before_teardown
93
95
  end
@@ -4,13 +4,14 @@
4
4
 
5
5
  module Megatest
6
6
  class Runtime
7
- attr_reader :test_case, :result
7
+ attr_reader :config, :test_case, :result, :on_teardown
8
8
 
9
9
  def initialize(config, test_case, result)
10
10
  @config = config
11
11
  @test_case = test_case
12
12
  @result = result
13
13
  @asserting = false
14
+ @on_teardown = []
14
15
  end
15
16
 
16
17
  support_locations = begin
@@ -150,6 +151,14 @@ module Megatest
150
151
  @config.diff(expected, actual)
151
152
  end
152
153
 
154
+ def teardown
155
+ until @on_teardown.empty?
156
+ record_failures do
157
+ @on_teardown.pop.call
158
+ end
159
+ end
160
+ end
161
+
153
162
  def record_failures(downlevel: 1, &block)
154
163
  expect_no_failures(&block)
155
164
  rescue Assertion => assertion
@@ -106,6 +106,10 @@ module Megatest
106
106
  @loader = loader
107
107
  end
108
108
 
109
+ def path
110
+ nil
111
+ end
112
+
109
113
  def partial?
110
114
  @loader.partial?
111
115
  end
@@ -232,7 +232,7 @@ module Megatest
232
232
  class SharedSuite < Suite
233
233
  def initialize(registry, test_suite)
234
234
  super(registry)
235
- @mod = test_suite
235
+ @klass = test_suite
236
236
  @test_cases = {}
237
237
  test_suite.instance_methods.each do |name|
238
238
  if name.start_with?("test_")
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Megatest
4
+ class Stubber < Module
5
+ DEFAULT = ->(*) {}
6
+ DEFAULT.ruby2_keywords if DEFAULT.respond_to?(:ruby2_keywords)
7
+
8
+ class << self
9
+ def for(object)
10
+ for_class(class << object; self; end)
11
+ end
12
+
13
+ def for_class(klass)
14
+ unless stubber = klass.included_modules.find { |m| Stubber === m }
15
+ stubber = Stubber.new
16
+ klass.prepend(stubber)
17
+ end
18
+ stubber
19
+ end
20
+ end
21
+
22
+ def stub_method(method, proc)
23
+ proc ||= DEFAULT
24
+
25
+ if method_defined?(method, false) # Already stubbed that method
26
+ old_method = instance_method(method)
27
+ alias_method(method, method) # Silence redefinition warnings
28
+ define_method(method, &proc)
29
+ -> do
30
+ alias_method(method, method) # Silence redefinition warnings
31
+ define_method(method, old_method)
32
+ end
33
+ else
34
+ define_method(method, &proc)
35
+ -> { remove_method(method) }
36
+ end
37
+ end
38
+ end
39
+
40
+ module Stubs
41
+ def stub(object, method, proc = nil)
42
+ stubber = ::Megatest::Stubber.for(object)
43
+ teardown = stubber.stub_method(method, proc)
44
+
45
+ if block_given?
46
+ begin
47
+ yield
48
+ ensure
49
+ teardown.call
50
+ end
51
+ else
52
+ @__m.on_teardown << teardown
53
+ end
54
+ end
55
+
56
+ def stub_any_instance_of(klass, method, proc = nil)
57
+ raise ArgumentError, "stub_any_instance_of expects a Module or Class" unless Module === klass
58
+
59
+ stubber = ::Megatest::Stubber.for_class(klass)
60
+ teardown = stubber.stub_method(method, proc)
61
+
62
+ if block_given?
63
+ begin
64
+ yield
65
+ ensure
66
+ teardown.call
67
+ end
68
+ else
69
+ @__m.on_teardown << teardown
70
+ end
71
+ end
72
+
73
+ def stub_const(mod, constant, new_value, exists: true)
74
+ if exists
75
+ old_value = mod.const_get(constant, false)
76
+ teardown = -> do
77
+ mod.send(:remove_const, constant) if mod.const_defined?(constant, false)
78
+ mod.const_set(constant, old_value)
79
+ end
80
+ else
81
+ if mod.const_defined?(constant)
82
+ raise NameError, "already defined constant #{constant} in #{mod.name || mod.inspect}"
83
+ end
84
+
85
+ teardown = -> do
86
+ mod.send(:remove_const, constant) if mod.const_defined?(constant, false)
87
+ end
88
+ end
89
+
90
+ apply = -> do
91
+ mod.send(:remove_const, constant) if exists
92
+ mod.const_set(constant, new_value)
93
+ end
94
+
95
+ if block_given?
96
+ begin
97
+ apply.call
98
+ yield
99
+ ensure
100
+ teardown.call
101
+ end
102
+ else
103
+ begin
104
+ apply.call
105
+ rescue
106
+ teardown.call
107
+ raise
108
+ end
109
+ @__m.on_teardown << teardown
110
+ end
111
+ end
112
+ end
113
+ end
data/lib/megatest/test.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "megatest/assertions"
4
+ require "megatest/stubs"
4
5
 
5
6
  module Megatest
6
7
  ##
@@ -99,6 +100,7 @@ module Megatest
99
100
  # :startdoc:
100
101
  extend DSL
101
102
  include Assertions
103
+ include Stubs
102
104
 
103
105
  # Returns the current Megatest::State::TestCase instance
104
106
  # Can be used for self introspection
@@ -106,6 +108,11 @@ module Megatest
106
108
  @__m.test_case
107
109
  end
108
110
 
111
+ # Returns the global megatest config object.
112
+ def __config__
113
+ @__m.config
114
+ end
115
+
109
116
  # Returns the current Megatest::TestCaseResult instance
110
117
  # Can be used for self introspection during teardown
111
118
  def __result__
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Megatest
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: megatest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-20 00:00:00.000000000 Z
11
+ date: 2024-08-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Largely API compatible with test-unit / minitest, but with lots of extra
14
14
  modern niceties like a proper CLI, test distribution, etc.
@@ -45,6 +45,7 @@ files:
45
45
  - lib/megatest/runtime.rb
46
46
  - lib/megatest/selector.rb
47
47
  - lib/megatest/state.rb
48
+ - lib/megatest/stubs.rb
48
49
  - lib/megatest/subprocess.rb
49
50
  - lib/megatest/subprocess/main.rb
50
51
  - lib/megatest/test.rb
@@ -73,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
74
  - !ruby/object:Gem::Version
74
75
  version: '0'
75
76
  requirements: []
76
- rubygems_version: 3.0.3.1
77
+ rubygems_version: 3.5.11
77
78
  signing_key:
78
79
  specification_version: 4
79
80
  summary: Modern test-unit style test framework