rspec-mergify 2026.10.6 → 2026.10.8

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: 06c5a8599786bb57121e965e20ef522113020086322e1d31367623a52cc93fb5
4
- data.tar.gz: 035ef91fee443aa985ba16ed224a16a0b5b5801fe93949dc60e69afdaf8836a6
3
+ metadata.gz: e5715b89995827b7f5d3840e0c3c1d94288f511fedcac9f3bdd58433c1908228
4
+ data.tar.gz: d70082926c396a3ac69ae778f9024be9d3cde6570cd84ef498d1e489ae9164d2
5
5
  SHA512:
6
- metadata.gz: 5e41ff044db2dab090c398ff6e98c65c479b2a633d98e349399d995ab6b2f5935a2dcd8d4bddb3928821083351bc0cc50855baf96073a1285c5e16fd1429f6c8
7
- data.tar.gz: 602b67361d3d62c42d53a7a9a6dcc236a2c8a93d2a6559984d76374e9f72ad4fc939b5857a7eb241c55381d32edcddeb4379075c792534f8aedb822eed05307d
6
+ metadata.gz: 036d1307e6752c12f0c4a88a5f4625528a8ef6eb7d0247b23100127d42a1842c2a3ddadcffc2439f27bb73f49f71d478346fb51b4a1524725c6812f232e10168
7
+ data.tar.gz: d3e25b1c4dda733655435cd3e28016a30127c82ed04e8d5b8e07a8e0e7c7374d572491bf8559e3f6c9262c814abe5130e4b65781b0c93cd7f91f2a72f5496de8
data/README.md CHANGED
@@ -7,7 +7,7 @@ RSpec plugin for [Mergify Test Engine](https://docs.mergify.com/test-engine/).
7
7
  - **Test tracing** — Sends OpenTelemetry traces for every test to Mergify's API
8
8
  - **Flaky test detection** — Intelligently reruns tests to detect flakiness with budget constraints
9
9
  - **Test quarantine** — Quarantines failing tests so they don't block CI
10
- - **Test selection** — Runs only the previously-failing examples when Mergify's merge queue reruns a job
10
+ - **Test selection** — Runs first the examples a merge-queue bisection step failed in the step above, and stops once one fails again
11
11
 
12
12
  ## Installation
13
13
 
@@ -21,7 +21,7 @@ Then run `bundle install`.
21
21
 
22
22
  ### Supported platforms
23
23
 
24
- The gem ships precompiled, so nothing is built on your machine. Each platform gem carries an extension for Ruby 3.1 through 4.0:
24
+ The gem runs on `mergify-ci-core`, which Bundler installs with it. That gem ships precompiled, so nothing is built on your machine. Each of its platform gems carries an extension for Ruby 3.1 through 4.0:
25
25
 
26
26
  | Platform | Requirement |
27
27
  |---|---|
@@ -52,7 +52,7 @@ The plugin activates automatically when running in CI (detected via the `CI` env
52
52
  | `MERGIFY_CI_DEBUG` | Print the session verdict and the spans instead of sending them, and upload nothing | `false` |
53
53
  | `MERGIFY_TRACEPARENT` | W3C distributed trace context | — |
54
54
  | `MERGIFY_TEST_JOB_NAME` | Mergify test job name | — |
55
- | `MERGIFY_TEST_SELECTION_ENABLE` | Opt this job into test selection (see below) | `false` |
55
+ | `MERGIFY_FAIL_FIRST_ENABLE` | Set to `false` to never run first what a merge-queue bisection step of this job failed before | `true` |
56
56
 
57
57
  The names these replace, `RSPEC_MERGIFY_ENABLE` and `RSPEC_MERGIFY_DEBUG`, are
58
58
  deprecated: they still work when the new name is not set, and the summary says
@@ -61,61 +61,84 @@ them: `RSPEC_MERGIFY_DEBUG=false` leaves debug off.
61
61
 
62
62
  ### Test selection
63
63
 
64
- When Mergify's merge queue reruns a job — a retry, or a step of a batch
65
- bisection — only the examples that failed on the previous attempt are
66
- informative. The gem asks Mergify whether the current run is such a rerun and,
67
- if so, runs only those examples.
68
-
69
- **This is off until you turn it on, per job.** Set
70
- `MERGIFY_TEST_SELECTION_ENABLE=true` on the job you want reduced. Installing the
71
- gem is not enough — a feature that decides not to run tests starts only where
72
- you wrote that it should. Anything that is not a recognised yes (unset, empty,
73
- `false`, unparsable) means no, and a job that has not opted in never asks.
74
-
75
- Once opted in, it only ever removes work:
76
-
77
- - the answer is applied **after** RSpec's own filters — file and line
78
- arguments, `--tag`, `-e`, `--only-failures` all still narrow the run, never
79
- widen it;
80
- - examples are matched by their id (`./spec/models/user_spec.rb[1:2]`), the
81
- identity RSpec itself reruns failures by;
82
- - if Mergify says the previous attempt already ran every one of these examples
83
- and they passed, the run executes none and exits green;
84
- - if any example Mergify asks for is not collected here, the full suite runs;
85
- - any error, timeout, or unrecognised answer runs the full suite;
86
- - if Mergify refuses to pick a previous attempt (several runs of this job
87
- report under one name), the run **fails** with Mergify's explanation: give
88
- each run its own `MERGIFY_TEST_JOB_NAME`;
89
- - at the end of the run the gem sends Mergify what it concluded (the counts
90
- and the failing examples) — unless the run failed outside any example (a
91
- failing `after(:context)` or suite hook), in which case a retry runs the
92
- full suite.
93
-
94
- Each parallel_tests worker, and each CI job handed a slice of the spec files,
95
- is matched to the same slice of the previous attempt, by the examples it
96
- collects. This needs the slices to be the same on both attempts, which is the
97
- case for a split by file size or by a committed runtime log; Knapsack Pro's
98
- Queue Mode hands files out dynamically and is not supported.
99
-
100
- #### Bisection steps: failures first
101
-
102
- When the merge queue splits a failed batch, a test that passed with every pull
103
- request of the batch can fail with only some of them, so a bisection step is
104
- never reduced: it runs its full suite. Mergify may instead tell it to run first
105
- the examples this job failed in the run the step was split from. The top-level
106
- groups holding them run first, and inside each group they run before its other
107
- examples and child groups — whatever the order (`--order rand`, `config.order`,
108
- your own `register_ordering`, `order:` metadata), which still applies to
109
- everything else. RSpec runs a group's whole subtree before the next group, and a
110
- group's own examples before its child groups, so when they span several groups
111
- the rest of each of those groups runs in between. If one of them fails again,
112
- the step is red whatever the rest does, so the run stops once they have all run
64
+ When Mergify's merge queue splits a failed batch, each bisection step only
65
+ needs to tell whether it is red. The gem asks Mergify whether the current run
66
+ is such a step and, if so, runs first the examples this job failed in the run
67
+ the step was split from (Fail First). The top-level groups holding them run
68
+ first, and inside each group they run before its other examples and child
69
+ groups — whatever the order (`--order rand`, `config.order`, your own
70
+ `register_ordering`, `order:` metadata), which still applies to everything
71
+ else. RSpec runs a group's whole subtree before the next group, and a group's
72
+ own examples before its child groups, so when they span several groups the
73
+ rest of each of those groups runs in between. If one of them fails again, the
74
+ step is red whatever the rest does, so the run stops once they have all run
113
75
  (exit code 1, the remaining examples not run) and the queue can split again
114
76
  without waiting for the suite. If none fails again, the rest of the suite runs
115
77
  as usual. No example is ever skipped, so a green step still comes from a full
116
78
  suite. A quarantined failure does not stop the run; a `--fail-fast` of your own
117
79
  still stops at the first failure.
118
80
 
81
+ **It is on by default.** A job that must always run its suite in its usual
82
+ order sets `MERGIFY_FAIL_FIRST_ENABLE=false`, and never asks. Only a recognised
83
+ no (`false`, `no`, `off`, `0`) turns it off. `MERGIFY_TEST_SELECTION_ENABLE`,
84
+ its former name, is still read while the new one is unset or empty, and the
85
+ summary at the end of the run says to switch. It is also enabled per
86
+ organization on Mergify's side: until yours has it, every run is served the
87
+ full suite, says nothing about it, and still sends
88
+ its results, which show what Fail First would do there.
89
+
90
+ It only ever asks in a build of a merge-queue batch, the only builds a
91
+ bisection step can be: on GitHub Actions, the event of the draft pull request
92
+ the queue opened; on any other build, the queue's git note on the branch, one
93
+ `git ls-remote` against `origin` that runs while the tests are being collected.
94
+ Every other build asks nothing and sends nothing. Of a batch's builds, only
95
+ those below a failed batch, and not retrying their own checks, ask: the
96
+ others cannot be a bisection step, and send their results only when the
97
+ batch holds two pull requests or more, so a bisection step may read them, or
98
+ when they failed. A build where git cannot answer (no `origin`, credentials
99
+ the test step does not hold) asks, as it would without this.
100
+
101
+ When it runs:
102
+
103
+ - the order applies **after** RSpec's own filters — file and line arguments,
104
+ `--tag`, `-e`, `--only-failures` all still narrow the run;
105
+ - examples are matched by the name Mergify knows them by (see
106
+ [Test names](#test-names));
107
+ - any error, timeout, or unrecognised answer runs the full suite in its usual
108
+ order;
109
+ - at the end of the run the gem sends Mergify what it concluded (the counts
110
+ and the failing examples) — unless the run failed outside any example (a
111
+ failing `after(:context)` or suite hook), in which case the next bisection
112
+ step runs its full suite in its usual order.
113
+
114
+ Each parallel_tests worker, and each CI job handed a slice of the spec files,
115
+ reports its own collection, by the examples it collects.
116
+
117
+ ### Test names
118
+
119
+ Mergify knows each example by its file, its groups and its description:
120
+ `spec/models/user_spec.rb > User > #full_name > joins first and last`. Adding,
121
+ moving or removing an example renames no other. An example written without a
122
+ description (`it { is_expected.to validate_presence_of(:email) }`) is named by
123
+ its code, and examples a file names alike get their place after the name
124
+ (`[1:2]`), as RSpec numbers them.
125
+
126
+ Earlier releases named an example by its place instead
127
+ (`./spec/models/user_spec.rb[1:2]`). A quarantine entry or a flaky-detection
128
+ history under such a name still matches, but each example's history on the
129
+ dashboard starts over under its new name. Quarantine an example again under
130
+ its new name: an entry under its old one follows the place, so the example
131
+ that holds it changes when one is added above.
132
+
133
+ A suite that reported through
134
+ [rspec_junit_formatter](https://github.com/sj26/rspec_junit_formatter) and
135
+ `mergify ci junit-process` keeps its quarantine entries and flaky-detection
136
+ history the same way: an example is still matched under the name those
137
+ uploads gave it (`spec.models.user_spec.User#full_name joins first and last`).
138
+ An example written without a description gets that name only once it ran, so
139
+ on the pull request that switches, flaky detection may take it for a new
140
+ test.
141
+
119
142
  ### Parallel runs
120
143
 
121
144
  [parallel_tests](https://github.com/grosser/parallel_tests) and [turbo_tests](https://github.com/serpapi/turbo_tests) need no setup. Each worker is a separate RSpec process, and each reports to Mergify as its own session:
@@ -147,8 +170,10 @@ bundle install
147
170
  bundle exec rake test
148
171
  ```
149
172
 
150
- `rake test` compiles the extension in Cargo's dev profile first: running
151
- `rspec` alone skips every binding example when the extension isn't built.
173
+ `rake test` compiles `mergify-ci-core`'s extension in Cargo's dev profile
174
+ first, with that gem's own bundle (`clients/mergify-ci-core`, `bundle install`
175
+ there too): running `rspec` alone skips every binding example when the
176
+ extension isn't built.
152
177
 
153
178
  ### Linting
154
179
 
data/THIRD_PARTY_LICENSES CHANGED
@@ -8357,7 +8357,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8357
8357
  Community Data License Agreement Permissive 2.0 (CDLA-Permissive-2.0)
8358
8358
 
8359
8359
  Used by:
8360
- - webpki-root-certs 1.0.10 (https://github.com/rustls/webpki-roots)
8360
+ - webpki-root-certs 1.0.11 (https://github.com/rustls/webpki-roots)
8361
8361
 
8362
8362
  # Community Data License Agreement - Permissive - Version 2.0
8363
8363
 
@@ -40,25 +40,21 @@ module Mergify
40
40
  # Also when the native extension did not load, which nothing can
41
41
  # tell apart from a run outside CI: the summary then says why it
42
42
  # reports nothing.
43
- config.prepend_before(:suite) { Configuration.attach_formatter(config) } if reports? || !Native.available?
43
+ if reports? || !Mergify::CI::Core::Native.available?
44
+ config.prepend_before(:suite) { Configuration.attach_formatter(config) }
45
+ end
44
46
 
45
47
  # Flaky detection: prepared for every example of the run.
46
48
  config.before(:suite) do
47
49
  ci = Mergify::RSpec.test_run
48
50
  if ci
49
- all_ids = ::RSpec.world.example_groups.flat_map(&:descendants).flat_map(&:examples).map(&:id)
50
- ci.prepare_reruns(all_ids)
51
+ examples = ::RSpec.world.example_groups.flat_map(&:descendants).flat_map(&:examples)
52
+ names = ci.names
53
+ ci.prepare_reruns(examples.map { |example| names.of(example) },
54
+ examples.to_h { |example| [names.of(example), names.formers(example)] })
51
55
  end
52
56
  end
53
57
 
54
- # Quarantine: mark tests before execution
55
- config.before(:each) do |example|
56
- ci = Mergify::RSpec.test_run
57
- next unless ci&.quarantined_tests&.include?(example.id)
58
-
59
- example.metadata[:mergify_quarantined] = true
60
- end
61
-
62
58
  # Each try of an example, which a retry plugin's loop (rspec-retry's)
63
59
  # makes inside the `around` hook below: the first `before` hook every
64
60
  # try runs, and the last `after` one.
@@ -81,12 +77,17 @@ module Mergify
81
77
  test_run&.reports? ? Configuration.run_example(example, test_run) : example.run
82
78
  end
83
79
 
84
- # Quarantine: override failed quarantined test results. The failure is
85
- # noted for flaky detection first, which would otherwise take a
86
- # quarantined attempt that failed for one that passed.
80
+ # Quarantine: override failed quarantined test results. Asked once
81
+ # the example ran: one written without a description has the name a
82
+ # JUnit upload gave it only then, when RSpec has written it one from
83
+ # its matcher. The failure is noted for flaky detection first, which
84
+ # would otherwise take a quarantined attempt that failed for one
85
+ # that passed.
87
86
  config.after(:each) do |example|
88
- next unless example.metadata[:mergify_quarantined] && example.exception
87
+ ci = Mergify::RSpec.test_run
88
+ next unless example.exception && ci&.quarantined_tests&.include?(ci.names.keys(example))
89
89
 
90
+ example.metadata[:mergify_quarantined] = true
90
91
  example.metadata[:mergify_quarantined_failure] = true
91
92
  example.metadata[:mergify_quarantined_exception] = example.exception
92
93
  example.instance_variable_set(:@exception, nil)
@@ -96,74 +97,39 @@ module Mergify
96
97
  end
97
98
  end
98
99
 
99
- # Hand the session what this process is about to run, and apply what it
100
- # decided: leave out what it does not keep, put first what a bisection
101
- # step answered, or stop a refused run.
100
+ # Hand the session what this process is about to run, and put first
101
+ # what a bisection step answered.
102
102
  #
103
103
  # A `before(:suite)` hook, because that is the first moment RSpec has both
104
104
  # loaded the spec files and applied its filters, and the last one before
105
105
  # an example group starts. What each group runs is read from
106
106
  # `RSpec.world.filtered_examples`, which RSpec memoized while counting the
107
- # run; narrowing those lists is what deselects.
107
+ # run.
108
+ # rubocop:disable-next Metrics/MethodLength
108
109
  def select_examples
109
110
  ci = Mergify::RSpec.test_run
110
111
  return unless ci
111
112
 
112
- groups, examples = collected
113
- decision = ci.select(examples.map(&:id))
114
- apply_decision(ci, decision, groups, examples) if decision
113
+ examples = collected
114
+ names = ci.names
115
+ return unless ci.select(examples.map { |example| names.of(example) })
116
+
117
+ # Ordered, not filtered: a served example this run did not collect is
118
+ # expected -- a parallel worker holds part of the job's failures.
119
+ fail_first = ci.fail_first
120
+ FailureFirst.apply(fail_first, examples, names, reporter: ::RSpec.configuration.reporter) if fail_first
115
121
  rescue StandardError => e
116
122
  # This runs on every CI run, opted in or not, and RSpec would answer a
117
123
  # raise here by running nothing: a fault of this gem must cost the run
118
- # its reduction, never its tests.
124
+ # its order, never its tests.
119
125
  ::RSpec.configuration.error_stream.puts(
120
- "Mergify Test Selection failed, so the full suite runs: #{e.class}: #{e.message}"
126
+ "Mergify could not apply Fail First, so the full suite runs: #{e.class}: #{e.message}"
121
127
  )
122
128
  end
123
129
 
124
- # Every group, and the examples RSpec will run of them, in its order.
130
+ # The examples RSpec will run, in its order.
125
131
  def collected
126
- groups = ::RSpec.world.example_groups.flat_map(&:descendants)
127
- [groups, groups.flat_map { |group| ::RSpec.world.filtered_examples[group] }]
128
- end
129
-
130
- def apply_decision(test_run, decision, groups, examples)
131
- return refuse(decision['refusal']) if decision['refusal']
132
-
133
- # Ordered, not filtered: a served example this run did not collect is
134
- # expected -- a parallel worker holds part of the job's failures.
135
- fail_first = test_run.fail_first
136
- return run_failure_first(fail_first, examples) if fail_first
137
-
138
- deselect(groups, decision['keep'].to_set) if decision['keep']
139
- end
140
-
141
- # Everything runs, the examples the step above failed first.
142
- def run_failure_first(fail_first, examples)
143
- FailureFirst.apply(fail_first, examples, reporter: ::RSpec.configuration.reporter)
144
- end
145
-
146
- # Stop the run before any example, the way RSpec stops one whose suite
147
- # hook failed -- nothing runs, the exit code is red, and the summary
148
- # counts an error outside of examples -- but with the server's
149
- # explanation alone. Raised from the hook, the same failure is printed
150
- # under a "Failure/Error:" header pointing at a line of this gem, which
151
- # only buries the explanation.
152
- def refuse(message)
153
- ::RSpec.configuration.error_stream.puts("\n#{message}\n")
154
- ::RSpec.world.wants_to_quit = true
155
- reporter = ::RSpec.configuration.reporter
156
- count = reporter.instance_variable_get(:@non_example_exception_count)
157
- reporter.instance_variable_set(:@non_example_exception_count, count + 1) if count.is_a?(Integer)
158
- end
159
-
160
- # A group left with no example runs none of its `before(:context)`
161
- # hooks: RSpec decides that from these same lists, once the group starts.
162
- def deselect(groups, keep)
163
- groups.each do |group|
164
- ::RSpec.world.filtered_examples[group] =
165
- ::RSpec.world.filtered_examples[group].select { |example| keep.include?(example.id) }
166
- end
132
+ ::RSpec.world.example_groups.flat_map(&:descendants).flat_map { |group| ::RSpec.world.filtered_examples[group] }
167
133
  end
168
134
 
169
135
  # The reporter has sent `start` by the time suite hooks run, so the
@@ -182,13 +148,14 @@ module Mergify
182
148
  # tries a retry plugin makes after it, inside that run; flaky detection's
183
149
  # reruns follow, each a try alone.
184
150
  def run_example(example, test_run)
151
+ name = test_run.names.of(example)
185
152
  detector = test_run.flaky_detector
186
- tries = run_first(example, test_run, detector)
187
- return unless detector&.rerunning_test?(example.id)
153
+ tries = run_first(example, name, test_run, detector)
154
+ return unless detector&.rerunning_test?(name)
188
155
 
189
156
  initial_exception = example.exception
190
157
  first_failure = tries.failure || initial_exception
191
- first_failure = rerun(example, detector, first_failure) if detector.start_reruns(example.id)
158
+ first_failure = rerun(example, name, detector, first_failure) if detector.start_reruns(name)
192
159
 
193
160
  # Which attempt decides the result is the session's rule: in 'new'
194
161
  # mode any failure fails the test, the retry plugin's tries included,
@@ -198,16 +165,15 @@ module Mergify
198
165
  example.example.instance_variable_set(:@exception, final_exception)
199
166
  end
200
167
 
201
- # The example's first run: its first attempt, timed by the engine up to
202
- # the second try where a retry plugin made one, then each try after it,
203
- # handed to the session once the first reached flaky detection. What the
204
- # first attempt was is kept for its span.
205
- def run_first(example, test_run, detector)
206
- id = example.id
207
- detector&.attempt_started(id)
208
- tries = run_tries(example) { detector&.attempt_stopped(id) }
209
- detector&.attempt_ended(id, first_failed?(example, tries) ? :failed : :passed)
210
- tries.later.each { |try| test_run.record_retry(id, try.status, try.duration_ms) }
168
+ # The first run of the example named `name`: its first attempt, timed by
169
+ # the engine up to the second try where a retry plugin made one, then
170
+ # each try after it, handed to the session once the first reached flaky
171
+ # detection. What the first attempt was is kept for its span.
172
+ def run_first(example, name, test_run, detector)
173
+ detector&.attempt_started(name)
174
+ tries = run_tries(example) { detector&.attempt_stopped(name) }
175
+ detector&.attempt_ended(name, first_failed?(example, tries) ? :failed : :passed)
176
+ tries.later.each { |try| test_run.record_retry(name, try.status, try.duration_ms) }
211
177
  example.metadata[:mergify_first_attempt] = { try: tries.first, ended_at: tries.first_ended_at } if tries.first
212
178
  tries
213
179
  end
@@ -252,13 +218,13 @@ module Mergify
252
218
  # `run_example`): answers the first failure, the reruns' included.
253
219
  # Each rerun is one try: a retry plugin's tries would hide its failure
254
220
  # behind the one that passed.
255
- def rerun(example, detector, first_failure)
221
+ def rerun(example, name, detector, first_failure)
256
222
  without_retries(example) do
257
223
  until example.metadata[:is_last_rerun]
258
- example.metadata[:is_last_rerun] = detector.last_rerun_for_test?(example.id)
224
+ example.metadata[:is_last_rerun] = detector.last_rerun_for_test?(name)
259
225
  reset_for_rerun(example)
260
226
 
261
- run_attempt(example, detector)
227
+ run_attempt(example, name, detector)
262
228
  first_failure ||= example.exception
263
229
  end
264
230
  end
@@ -295,12 +261,12 @@ module Mergify
295
261
 
296
262
  # Runs what the around hook wraps -- the before and after hooks included
297
263
  # -- timed by the session, and answers its outcome.
298
- def run_attempt(example, detector)
299
- detector.attempt_started(example.id)
264
+ def run_attempt(example, name, detector)
265
+ detector.attempt_started(name)
300
266
  example.run
301
267
  failed = example.exception || example.metadata.delete(:mergify_quarantined_failure)
302
268
  status = failed ? :failed : :passed
303
- detector.attempt_ended(example.id, status)
269
+ detector.attempt_ended(name, status)
304
270
  status
305
271
  end
306
272
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
- require_relative 'native'
4
+ require 'mergify/ci/core'
5
5
 
6
6
  module Mergify
7
7
  module RSpec
@@ -35,7 +35,7 @@ module Mergify
35
35
  # every example and group not started yet is left out, and the run exits 1.
36
36
  #
37
37
  # What to put first, when to stop, and the terminal block's words are the
38
- # session's (`Native::Session#fail_first`), shared with every plugin: this
38
+ # session's (`Mergify::CI::Core::Native::Session#fail_first`), shared with every plugin: this
39
39
  # class reorders and quits.
40
40
  class FailureFirst
41
41
  class << self
@@ -57,8 +57,8 @@ module Mergify
57
57
 
58
58
  # Put first the examples the session puts first, and stop the run when
59
59
  # it says.
60
- def apply(fail_first, examples, reporter:)
61
- self.current = new(fail_first, examples)
60
+ def apply(fail_first, examples, names, reporter:)
61
+ self.current = new(fail_first, examples, names)
62
62
  current.reorder(top_level) if top_level
63
63
  reporter.register_listener(current, :example_finished)
64
64
  current
@@ -96,10 +96,11 @@ module Mergify
96
96
  end
97
97
  end
98
98
 
99
- # An example's id is also the name Mergify knows it by.
100
- def initialize(fail_first, examples)
99
+ # Each example goes by its id, which RSpec orders and reports it by,
100
+ # and is put first by the name Mergify knows it by (`names`).
101
+ def initialize(fail_first, examples, names)
101
102
  @fail_first = fail_first
102
- @first_ids = fail_first.put_first(examples.map { |example| [example.id, example.id] }).to_set
103
+ @first_ids = fail_first.put_first(examples.map { |example| [example.id, names.of(example)] }).to_set
103
104
  @first_groups = groups_holding(examples.select { |example| @first_ids.include?(example.id) })
104
105
  end
105
106
 
@@ -3,7 +3,7 @@
3
3
  require 'rspec/core/formatters/base_formatter'
4
4
  require_relative 'configuration'
5
5
 
6
- require 'mergify/rspec/native'
6
+ require 'mergify/ci/core'
7
7
 
8
8
  module Mergify
9
9
  module RSpec
@@ -34,8 +34,9 @@ module Mergify
34
34
 
35
35
  example = notification.example
36
36
  result = example.execution_result
37
- @test_run.record_verdict(example.id, verdict_status(example), result.run_time)
38
- recorded = @test_run.record_quarantine(example.id, real_outcome(example))
37
+ names = @test_run.names
38
+ @test_run.record_verdict(names.of(example), verdict_status(example), result.run_time)
39
+ recorded = @test_run.record_quarantine(names.keys(example), real_outcome(example))
39
40
  @has_error = true if result.status == :failed
40
41
  @test_run.record_case(case_of(example, recorded))
41
42
  end
@@ -120,7 +121,7 @@ module Mergify
120
121
  def case_of(example, recorded)
121
122
  first = first_attempt(example, recorded['absorbed'])
122
123
  {
123
- 'name' => example.id,
124
+ 'name' => @test_run.names.of(example),
124
125
  'file' => example.metadata[:file_path].delete_prefix('./'),
125
126
  'function' => example.description,
126
127
  'start_unix_nano' => unix_nano(example.execution_result.started_at),
@@ -201,7 +202,7 @@ module Mergify
201
202
  return ['Mergify Test Engine is not configured.'] unless @test_run
202
203
  # A missing token or repository is the session's to say, with the
203
204
  # trace it could not upload.
204
- return [] if Mergify::RSpec::Native.available?
205
+ return [] if Mergify::CI::Core::Native.available?
205
206
 
206
207
  [native_warning]
207
208
  end
@@ -211,7 +212,7 @@ module Mergify
211
212
  # git remote that is fine. Say what actually happened instead.
212
213
  def native_warning
213
214
  'WARNING: the Mergify native extension could not be loaded, so this run reports nothing: ' \
214
- "#{Mergify::RSpec::Native.load_error&.message}"
215
+ "#{Mergify::CI::Core::Native.load_error&.message}"
215
216
  end
216
217
 
217
218
  # `text` in the colour of `severity` when RSpec colours its output, as
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'mergify/ci/core'
5
+
6
+ module Mergify
7
+ module RSpec
8
+ # The name Mergify knows each example by, which the gem hands the session
9
+ # wherever it speaks of one: the example's span and its verdict, the
10
+ # quarantine, flaky detection and Fail First.
11
+ #
12
+ # An example is named as minitest-mergify names a spec test, by
13
+ # `CI::Core::TestNames`: `<file> > <groups> > <description>`. The file is
14
+ # the one RSpec reruns the example from, so an example a shared group
15
+ # gives a spec is that spec's, relative to the directory the run started
16
+ # in. Nothing in it counts examples: RSpec's own id,
17
+ # `./spec/x_spec.rb[1:2]`, numbers an example by its place in its group,
18
+ # so adding one renamed every one after it, which then took the
19
+ # quarantine entry and the history of the one before. An example written
20
+ # without a description is titled by its block's code: RSpec writes it
21
+ # one from its matcher only once it ran, long after the session asked
22
+ # about it. A name its file repeats is followed by the example's place
23
+ # (`[1:2]`), so those examples stay apart.
24
+ #
25
+ # An example has two former names, which Mergify's lists may still hold
26
+ # it under: its id, which every earlier release named it by, and the
27
+ # name a JUnit upload gave it, for a suite that reported through
28
+ # rspec_junit_formatter and `mergify ci junit-process` before.
29
+ class Names
30
+ # `root` is the directory the run started in.
31
+ def initialize(root)
32
+ @root = root
33
+ end
34
+
35
+ def of(example)
36
+ name_all unless example.metadata.key?(:mergify_name)
37
+ example.metadata.fetch(:mergify_name)
38
+ end
39
+
40
+ # The names the example had before, which Mergify's lists may still
41
+ # hold it under.
42
+ def formers(example)
43
+ [example.id, junit_name(example)].compact
44
+ end
45
+
46
+ # Every name Mergify's lists may hold the example under, which the
47
+ # quarantine is asked with.
48
+ def keys(example)
49
+ [of(example), *formers(example)]
50
+ end
51
+
52
+ private
53
+
54
+ # Name every example RSpec loaded, each in its metadata: whether a name
55
+ # needs the example's place depends on the others of its file.
56
+ def name_all
57
+ names = unplaced_names
58
+ repeated = names.values.compact.tally.select { |_, count| count > 1 }.keys.to_set
59
+ names.each do |example, name|
60
+ example.metadata[:mergify_name] = name.nil? || repeated.include?(name) ? placed(example) : name
61
+ end
62
+ end
63
+
64
+ # Every example RSpec loaded, by its name without its place: nil for
65
+ # one that has no title.
66
+ def unplaced_names
67
+ examples = ::RSpec.world.example_groups.flat_map(&:descendants).flat_map(&:examples)
68
+ examples.to_h do |example|
69
+ title = title(example)
70
+ [example, title && spec_name(example, title)]
71
+ end
72
+ end
73
+
74
+ # Its name with its place after its title, as RSpec writes it: `[1:2]`.
75
+ def placed(example)
76
+ spec_name(example, [title(example), "[#{example.metadata[:scoped_id]}]"].compact.join(' '))
77
+ end
78
+
79
+ # Its description, or the code of its block when it was written without
80
+ # one; nil when there is none to read.
81
+ def title(example)
82
+ description = example.metadata[:description].to_s
83
+ return description unless description.empty?
84
+
85
+ block = example.metadata[:block]
86
+ CI::Core::TestNames.block_text(block) if block
87
+ end
88
+
89
+ # The name `mergify ci junit-process` gave the example from
90
+ # rspec_junit_formatter's report, `<class name>.<name>`: the class name
91
+ # the file of its outermost group, without its extension and with `/`
92
+ # as `.`, and the name its full description. Nil for an example written
93
+ # without a description that has not run yet: RSpec writes it one only
94
+ # once it ran.
95
+ def junit_name(example)
96
+ return if example.metadata[:description].to_s.empty?
97
+
98
+ # The formatter cuts the file's name at its first dot.
99
+ directory, name = File.split(example.example_group.parent_groups.last.metadata[:file_path])
100
+ classname = "#{directory}/#{name.sub(/\..*/, '')}".tr('/', '.').gsub(/\A\.+|\.+\z/, '')
101
+ "#{classname}.#{example.full_description}"
102
+ end
103
+
104
+ def spec_name(example, title)
105
+ groups = example.example_group.parent_groups.reverse.map(&:description)
106
+ CI::Core::TestNames.spec_name(example.metadata[:rerun_file_path], groups, title, @root)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -16,15 +16,18 @@ module Mergify
16
16
  @list = list
17
17
  end
18
18
 
19
- def include?(example_id)
20
- @list.include?([example_id])
19
+ # Whether the list holds an example under any of `keys`, its names (see
20
+ # `Names#keys`).
21
+ def include?(keys)
22
+ @list.include?(keys)
21
23
  end
22
24
 
23
- # Account for an example that ended with `outcome` (`'passed'`,
24
- # `'failed'` or `'skipped'`, before the quarantine turned a failure into
25
- # `pending`), as `{ 'quarantined' => bool, 'absorbed' => bool }`.
26
- def record(example_id, outcome)
27
- @list.record([example_id], outcome)
25
+ # Account for an example known by `keys` that ended with `outcome`
26
+ # (`'passed'`, `'failed'` or `'skipped'`, before the quarantine turned a
27
+ # failure into `pending`), as `{ 'quarantined' => bool, 'absorbed' =>
28
+ # bool }`.
29
+ def record(keys, outcome)
30
+ @list.record(keys, outcome)
28
31
  end
29
32
  end
30
33
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'utils'
4
- require_relative 'native'
4
+ require 'mergify/ci/core'
5
5
  require_relative 'flaky_detection'
6
+ require_relative 'names'
6
7
  require 'json'
7
8
  require 'rspec/core/version'
8
9
  require 'set'
@@ -13,12 +14,10 @@ module Mergify
13
14
  # run's examples for its trace, and coordinates flaky detection,
14
15
  # quarantine and test selection.
15
16
  class TestRun
16
- # What RSpec is, as the session takes it: its documentation page, where
17
- # a run Test Selection refused is sent to read about
18
- # MERGIFY_TEST_JOB_NAME; what it calls a test; what it can do about
19
- # Fail First and reruns; and RSpec itself, which the trace names.
17
+ # What RSpec is, as the session takes it: what it calls a test; what it
18
+ # can do about Fail First and reruns; and RSpec itself, which the trace
19
+ # names.
20
20
  FRAMEWORK = {
21
- docs_url: 'https://docs.mergify.com/test-engine/test-frameworks/rspec/',
22
21
  test_noun: 'example',
23
22
  # RSpec runs examples, in one process that can wait for every one put
24
23
  # first.
@@ -31,7 +30,7 @@ module Mergify
31
30
 
32
31
  attr_reader :session, :repo_name, :test_run_id,
33
32
  :flaky_detector, :quarantined_tests,
34
- :captured_session_verdict
33
+ :captured_session_verdict, :names
35
34
 
36
35
  # `doubles` has the session play Mergify in memory, answering and keeping
37
36
  # what it would send (the session's test doubles, by port, as a Hash:
@@ -44,16 +43,18 @@ module Mergify
44
43
  # What this process reads once: the CI context, the trace a caller
45
44
  # handed down, and the run's id; and the deprecated names the run
46
45
  # sets, which the summary names.
47
- @session = Native::Session.start(Mergify::RSpec::VERSION, output, doubles, enabled?, **FRAMEWORK,
48
- deprecated_variables: Utils.deprecated_env_names_in_use)
46
+ @session = Mergify::CI::Core::Native::Session.start(
47
+ Mergify::RSpec::VERSION, output, doubles, enabled?, client: 'rspec-mergify', **FRAMEWORK,
48
+ deprecated_variables: Utils.deprecated_env_names_in_use
49
+ )
49
50
  @repo_name = @session.repository_name
50
51
  @test_run_id = @session.run_id
51
52
  @reruns = nil
52
53
  @flaky_detector = nil
53
54
  @quarantined_tests = nil
54
- @decision = nil
55
55
  @captured_session_verdict = nil
56
56
  @examples_to_run = nil
57
+ @names = Names.new(Dir.pwd)
57
58
 
58
59
  set_up if reports?
59
60
  end
@@ -65,9 +66,10 @@ module Mergify
65
66
  @session.reports?
66
67
  end
67
68
 
68
- # Hand the session what this run is about to execute, and answer what
69
- # it executes of it, as the session decided: a Hash with `outcome`,
70
- # `keep` and `refusal` (see `Native::Session#select`).
69
+ # Hand the session what this run is about to execute, and answer how it
70
+ # executes it, as the session decided: a Hash whose `outcome` is `full`
71
+ # or `failure_first` (see `Mergify::CI::Core::Native::Session#select`).
72
+ # Every example runs.
71
73
  #
72
74
  # Called once RSpec has loaded the spec files and applied every filter
73
75
  # of its own -- file and line arguments, `--tag`, `-e`,
@@ -77,13 +79,11 @@ module Mergify
77
79
  # own identity: the engine matches each against the previous attempt of
78
80
  # the same slice. It travels on the verdict and the trace of a run that
79
81
  # asked for the selection.
80
- def select(example_ids)
82
+ def select(names)
81
83
  return unless reports?
82
84
 
83
- @decision = @session.select(example_ids)
84
- keep = @decision['keep']&.to_set
85
- @examples_to_run = keep ? example_ids.count { |id| keep.include?(id) } : example_ids.size
86
- @decision
85
+ @examples_to_run = names.size
86
+ @session.select(names)
87
87
  end
88
88
 
89
89
  # The run's Fail First, which the session hands out when Mergify
@@ -92,10 +92,10 @@ module Mergify
92
92
  @session.fail_first
93
93
  end
94
94
 
95
- # Record an example's final status, as RSpec reported it, for the
96
- # session's verdict.
97
- def record_verdict(example_id, status, run_time)
98
- @session.record_test(example_id, status, run_time.to_f)
95
+ # Record the final status of the example named `name`, as RSpec
96
+ # reported it, for the session's verdict.
97
+ def record_verdict(name, status, run_time)
98
+ @session.record_test(name, status, run_time.to_f)
99
99
  end
100
100
 
101
101
  # Write what this session concluded to Mergify, before the spans go.
@@ -117,19 +117,19 @@ module Mergify
117
117
  end
118
118
 
119
119
  # Hand the session an example once it finished, as the formatter says
120
- # it (see `Native::Session#record_case`): its span joins the run's
120
+ # it (see `Mergify::CI::Core::Native::Session#record_case`): its span joins the run's
121
121
  # trace.
122
122
  def record_case(example)
123
123
  @session.record_case(example)
124
124
  end
125
125
 
126
- # A retry plugin (rspec-retry) tried the example again after its first
127
- # attempt, which ended with `status` (`'passed'` or `'failed'`) after
128
- # `duration_ms`: the session counts it on the example's span, and as one
129
- # of its runs where flaky detection runs, never against its budget or
130
- # its execution cap.
131
- def record_retry(example_id, status, duration_ms)
132
- @session.record_retry(example_id, status, duration_ms)
126
+ # A retry plugin (rspec-retry) tried the example named `name` again
127
+ # after its first attempt, which ended with `status` (`'passed'` or
128
+ # `'failed'`) after `duration_ms`: the session counts it on the
129
+ # example's span, and as one of its runs where flaky detection runs,
130
+ # never against its budget or its execution cap.
131
+ def record_retry(name, status, duration_ms)
132
+ @session.record_retry(name, status, duration_ms)
133
133
  end
134
134
 
135
135
  # Have the session send the run's trace, once, at the end: the examples
@@ -150,20 +150,23 @@ module Mergify
150
150
  @session.summary(Utils.parallel_worker?)
151
151
  end
152
152
 
153
- # Prepare flaky detection for the examples this run is about to run.
154
- # Which of them it reruns, and whether it runs at all, are the
155
- # session's.
156
- def prepare_reruns(example_ids)
157
- @reruns = @session.prepare_reruns(example_ids)
153
+ # Prepare flaky detection for the examples this run is about to run, by
154
+ # their names, each one Mergify's lists may still hold under its former
155
+ # names (`former_names`, a list by name). Which of them it reruns, and whether
156
+ # it runs at all, are the session's.
157
+ def prepare_reruns(names, former_names)
158
+ @session.state_former_names(former_names)
159
+ @reruns = @session.prepare_reruns(names)
158
160
  detection = @reruns&.detection
159
161
  @flaky_detector = detection && FlakyDetector.new(@reruns, detection)
160
162
  end
161
163
 
162
- # Account for an example's real outcome against the quarantine list.
163
- def record_quarantine(example_id, outcome)
164
+ # Account for the real outcome of the example known by `keys` against
165
+ # the quarantine list.
166
+ def record_quarantine(keys, outcome)
164
167
  return { 'quarantined' => false, 'absorbed' => false } unless @quarantined_tests
165
168
 
166
- @quarantined_tests.record(example_id, outcome)
169
+ @quarantined_tests.record(keys, outcome)
167
170
  end
168
171
 
169
172
  private
@@ -193,7 +196,7 @@ module Mergify
193
196
  # be on whatever its value, so that `false` silently stopped the upload;
194
197
  # both names now take a boolean.
195
198
  def debug_mode?
196
- Utils.env_truthy?(Utils.renamed_env(Utils::CI_DEBUG_ENV))
199
+ Mergify::CI::Core::Utils.env_truthy?(Utils.renamed_env(Utils::CI_DEBUG_ENV))
197
200
  end
198
201
 
199
202
  # Where the session sends the verdict and the trace: shown in debug mode,
@@ -205,7 +208,7 @@ module Mergify
205
208
  # The gem's enable switch, under every name it has had: the run then
206
209
  # reports outside CI too.
207
210
  def enabled?
208
- Utils.env_truthy?(Utils.renamed_env(Utils::CI_ENABLE_ENV))
211
+ Mergify::CI::Core::Utils.env_truthy?(Utils.renamed_env(Utils::CI_ENABLE_ENV))
209
212
  end
210
213
  end
211
214
  end
@@ -6,18 +6,16 @@ module Mergify
6
6
  #
7
7
  # CI detection used to live here -- the provider table, the git shelling
8
8
  # out, the per-provider environment mappings. It is the Rust core's now, via
9
- # the extension in Mergify::RSpec::Native, so that every Mergify test client
10
- # detects identically. What is left is what is genuinely Ruby's: parsing a
11
- # repository name the API needs split, and reading the plugin's own
12
- # switches.
9
+ # the extension in Mergify::CI::Core::Native, so that every Mergify test
10
+ # client detects identically. What is left is what is genuinely Ruby's:
11
+ # parsing a repository name the API needs split, and reading the plugin's
12
+ # own switches.
13
13
  module Utils
14
14
  module_function
15
15
 
16
16
  # Raised when a repository full name (owner/repo) is malformed.
17
17
  class InvalidRepositoryFullNameError < StandardError; end
18
18
 
19
- TRUTHY_STRINGS = %w[y yes t true on 1].freeze
20
-
21
19
  CI_ENABLE_ENV = 'MERGIFY_CI_ENABLE'
22
20
  CI_DEBUG_ENV = 'MERGIFY_CI_DEBUG'
23
21
 
@@ -29,11 +27,6 @@ module Mergify
29
27
  CI_DEBUG_ENV => 'RSPEC_MERGIFY_DEBUG'
30
28
  }.freeze
31
29
 
32
- # Returns true when the named environment variable holds a truthy value.
33
- def env_truthy?(key)
34
- TRUTHY_STRINGS.include?(ENV.fetch(key, '').downcase)
35
- end
36
-
37
30
  # The variable to read for a setting in DEPRECATED_ENV_NAMES: its new
38
31
  # name once that is set to something, its deprecated one until then.
39
32
  def renamed_env(name)
@@ -8,6 +8,6 @@ module Mergify
8
8
  # which resolved against whatever repository happened to be the working
9
9
  # directory at load time -- the monorepo's namespaced tags here, the user's
10
10
  # own tags once the gem was installed.
11
- VERSION = '2026.10.6'
11
+ VERSION = '2026.10.8'
12
12
  end
13
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-mergify
3
3
  version: !ruby/object:Gem::Version
4
- version: 2026.10.6
4
+ version: 2026.10.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mergify
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: '3.12'
26
+ - !ruby/object:Gem::Dependency
27
+ name: mergify-ci-core
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - '='
31
+ - !ruby/object:Gem::Version
32
+ version: 2026.10.8
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 2026.10.8
26
40
  description: 'RSpec integration for Mergify Test Engine: OpenTelemetry tracing, flaky
27
41
  test detection, and test quarantine.'
28
42
  email:
@@ -40,7 +54,7 @@ files:
40
54
  - lib/mergify/rspec/failure_first.rb
41
55
  - lib/mergify/rspec/flaky_detection.rb
42
56
  - lib/mergify/rspec/formatter.rb
43
- - lib/mergify/rspec/native.rb
57
+ - lib/mergify/rspec/names.rb
44
58
  - lib/mergify/rspec/quarantine.rb
45
59
  - lib/mergify/rspec/test_run.rb
46
60
  - lib/mergify/rspec/tries.rb
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Mergify
4
- module RSpec
5
- # The Rust extension over mergify-ci-session: the session shared with the
6
- # pytest and TypeScript clients.
7
- #
8
- # Loading is best-effort by design. A precompiled gem carries one extension
9
- # per Ruby under `<ruby>/`, the source gem compiles one alongside this file,
10
- # and a platform we publish neither for gets neither. Detection is one input
11
- # to telemetry, not the point of the gem, so an absent extension degrades
12
- # what we can report rather than breaking the suite under test -- the same
13
- # fail-open posture the napi binding takes when a platform has no prebuilt
14
- # binary. `load_error` keeps the reason, for callers that want to say so.
15
- module Native
16
- class << self
17
- # The LoadError that prevented the extension loading, or nil.
18
- attr_accessor :load_error
19
-
20
- def available?
21
- load_error.nil?
22
- end
23
-
24
- # Which of the two failed requires actually explains the absence.
25
- #
26
- # A precompiled gem ships lib/mergify/rspec/<ruby>/mergify_ci.<dlext>,
27
- # so when one is packed for this Ruby that require is the real attempt:
28
- # it fails with something like "version `GLIBC_2.29' not found", and the
29
- # fallback that follows only ever adds "cannot load such file", which
30
- # sends the reader looking for the wrong thing. With no extension for
31
- # this Ruby -- the source gem, or a platform we publish no gem for --
32
- # it is the other way round.
33
- # Require a file next to this one, returning the LoadError instead of
34
- # raising it. Two attempts read better as values than as nested rescues.
35
- def attempt_require(path)
36
- require_relative path
37
- nil
38
- rescue LoadError => e
39
- e
40
- end
41
-
42
- def load_failure_reason(versioned, fallback)
43
- packed = Dir.glob(File.join(__dir__, RUBY_VERSION.to_f.to_s, 'mergify_ci.*')).any?
44
- packed ? versioned : fallback
45
- end
46
- end
47
- end
48
- end
49
- end
50
-
51
- # Precompiled gems ship lib/mergify/rspec/<ruby>/mergify_ci.<dlext>; the source
52
- # gem and any local `rake compile` put one beside this file instead.
53
- versioned_error = Mergify::RSpec::Native.attempt_require("#{RUBY_VERSION.to_f}/mergify_ci")
54
- fallback_error = versioned_error && Mergify::RSpec::Native.attempt_require('mergify_ci')
55
-
56
- if fallback_error
57
- Mergify::RSpec::Native.load_error =
58
- Mergify::RSpec::Native.load_failure_reason(versioned_error, fallback_error)
59
- end
60
-
61
- unless Mergify::RSpec::Native.available?
62
- # Stand in for the extension so callers can just ask, and get the same answer
63
- # they would get from a machine that is not in CI. Branching on availability
64
- # at every call site would only spread the same nil back through the caller.
65
- module Mergify
66
- module RSpec
67
- module Native
68
- # A session that detects nothing, as on a machine that is not in CI.
69
- class Session
70
- def self.start(*)
71
- new
72
- end
73
-
74
- # Nothing detected, so nothing to report to.
75
- def reports?
76
- false
77
- end
78
-
79
- def repository_name
80
- nil
81
- end
82
-
83
- # Nothing detected: no run to name.
84
- def run_id
85
- nil
86
- end
87
-
88
- # Nothing to send a verdict with: it neither folds nor reports.
89
- def record_test(_example_id, _status, _run_time); end
90
-
91
- # Nothing to ask with: every example runs.
92
- def select(_example_ids)
93
- { 'outcome' => 'full', 'keep' => nil, 'refusal' => nil }
94
- end
95
-
96
- def executed_count
97
- 0
98
- end
99
-
100
- def report_verdict(_failed_outside_examples); end
101
-
102
- def captured_verdict
103
- nil
104
- end
105
-
106
- # Nothing to send a trace with either.
107
- def record_case(_example); end
108
-
109
- def record_retry(_example_id, _status, _duration_ms); end
110
-
111
- def report_trace(_failed); end
112
-
113
- # Nothing reported: nothing to say.
114
- def summary(_partial)
115
- []
116
- end
117
-
118
- def captured_trace
119
- nil
120
- end
121
-
122
- # Nothing to rerun with.
123
- def prepare_reruns(_example_ids)
124
- nil
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end