rspec-mergify 2026.10.7 → 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 +4 -4
- data/README.md +54 -13
- data/THIRD_PARTY_LICENSES +1 -1
- data/lib/mergify/rspec/configuration.rb +40 -37
- data/lib/mergify/rspec/failure_first.rb +8 -7
- data/lib/mergify/rspec/formatter.rb +7 -6
- data/lib/mergify/rspec/names.rb +110 -0
- data/lib/mergify/rspec/quarantine.rb +10 -7
- data/lib/mergify/rspec/test_run.rb +39 -31
- data/lib/mergify/rspec/utils.rb +4 -11
- data/lib/mergify/rspec/version.rb +1 -1
- metadata +16 -2
- data/lib/mergify/rspec/native.rb +0 -130
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5715b89995827b7f5d3840e0c3c1d94288f511fedcac9f3bdd58433c1908228
|
|
4
|
+
data.tar.gz: d70082926c396a3ac69ae778f9024be9d3cde6570cd84ef498d1e489ae9164d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 036d1307e6752c12f0c4a88a5f4625528a8ef6eb7d0247b23100127d42a1842c2a3ddadcffc2439f27bb73f49f71d478346fb51b4a1524725c6812f232e10168
|
|
7
|
+
data.tar.gz: d3e25b1c4dda733655435cd3e28016a30127c82ed04e8d5b8e07a8e0e7c7374d572491bf8559e3f6c9262c814abe5130e4b65781b0c93cd7f91f2a72f5496de8
|
data/README.md
CHANGED
|
@@ -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
|
|
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
|
-
| `
|
|
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
|
|
@@ -78,18 +78,32 @@ as usual. No example is ever skipped, so a green step still comes from a full
|
|
|
78
78
|
suite. A quarantined failure does not stop the run; a `--fail-fast` of your own
|
|
79
79
|
still stops at the first failure.
|
|
80
80
|
|
|
81
|
-
**
|
|
82
|
-
`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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:
|
|
88
102
|
|
|
89
103
|
- the order applies **after** RSpec's own filters — file and line arguments,
|
|
90
104
|
`--tag`, `-e`, `--only-failures` all still narrow the run;
|
|
91
|
-
- examples are matched by
|
|
92
|
-
|
|
105
|
+
- examples are matched by the name Mergify knows them by (see
|
|
106
|
+
[Test names](#test-names));
|
|
93
107
|
- any error, timeout, or unrecognised answer runs the full suite in its usual
|
|
94
108
|
order;
|
|
95
109
|
- at the end of the run the gem sends Mergify what it concluded (the counts
|
|
@@ -100,6 +114,31 @@ Once opted in:
|
|
|
100
114
|
Each parallel_tests worker, and each CI job handed a slice of the spec files,
|
|
101
115
|
reports its own collection, by the examples it collects.
|
|
102
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
|
+
|
|
103
142
|
### Parallel runs
|
|
104
143
|
|
|
105
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:
|
|
@@ -131,8 +170,10 @@ bundle install
|
|
|
131
170
|
bundle exec rake test
|
|
132
171
|
```
|
|
133
172
|
|
|
134
|
-
`rake test` compiles
|
|
135
|
-
|
|
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.
|
|
136
177
|
|
|
137
178
|
### Linting
|
|
138
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.
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
ci.
|
|
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.
|
|
85
|
-
#
|
|
86
|
-
#
|
|
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
|
-
|
|
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)
|
|
@@ -104,23 +105,25 @@ module Mergify
|
|
|
104
105
|
# an example group starts. What each group runs is read from
|
|
105
106
|
# `RSpec.world.filtered_examples`, which RSpec memoized while counting the
|
|
106
107
|
# run.
|
|
108
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
107
109
|
def select_examples
|
|
108
110
|
ci = Mergify::RSpec.test_run
|
|
109
111
|
return unless ci
|
|
110
112
|
|
|
111
113
|
examples = collected
|
|
112
|
-
|
|
114
|
+
names = ci.names
|
|
115
|
+
return unless ci.select(examples.map { |example| names.of(example) })
|
|
113
116
|
|
|
114
117
|
# Ordered, not filtered: a served example this run did not collect is
|
|
115
118
|
# expected -- a parallel worker holds part of the job's failures.
|
|
116
119
|
fail_first = ci.fail_first
|
|
117
|
-
FailureFirst.apply(fail_first, examples, reporter: ::RSpec.configuration.reporter) if fail_first
|
|
120
|
+
FailureFirst.apply(fail_first, examples, names, reporter: ::RSpec.configuration.reporter) if fail_first
|
|
118
121
|
rescue StandardError => e
|
|
119
122
|
# This runs on every CI run, opted in or not, and RSpec would answer a
|
|
120
123
|
# raise here by running nothing: a fault of this gem must cost the run
|
|
121
124
|
# its order, never its tests.
|
|
122
125
|
::RSpec.configuration.error_stream.puts(
|
|
123
|
-
"Mergify
|
|
126
|
+
"Mergify could not apply Fail First, so the full suite runs: #{e.class}: #{e.message}"
|
|
124
127
|
)
|
|
125
128
|
end
|
|
126
129
|
|
|
@@ -145,13 +148,14 @@ module Mergify
|
|
|
145
148
|
# tries a retry plugin makes after it, inside that run; flaky detection's
|
|
146
149
|
# reruns follow, each a try alone.
|
|
147
150
|
def run_example(example, test_run)
|
|
151
|
+
name = test_run.names.of(example)
|
|
148
152
|
detector = test_run.flaky_detector
|
|
149
|
-
tries = run_first(example, test_run, detector)
|
|
150
|
-
return unless detector&.rerunning_test?(
|
|
153
|
+
tries = run_first(example, name, test_run, detector)
|
|
154
|
+
return unless detector&.rerunning_test?(name)
|
|
151
155
|
|
|
152
156
|
initial_exception = example.exception
|
|
153
157
|
first_failure = tries.failure || initial_exception
|
|
154
|
-
first_failure = rerun(example, detector, first_failure) if detector.start_reruns(
|
|
158
|
+
first_failure = rerun(example, name, detector, first_failure) if detector.start_reruns(name)
|
|
155
159
|
|
|
156
160
|
# Which attempt decides the result is the session's rule: in 'new'
|
|
157
161
|
# mode any failure fails the test, the retry plugin's tries included,
|
|
@@ -161,16 +165,15 @@ module Mergify
|
|
|
161
165
|
example.example.instance_variable_set(:@exception, final_exception)
|
|
162
166
|
end
|
|
163
167
|
|
|
164
|
-
# The
|
|
165
|
-
# the second try where a retry plugin made one, then
|
|
166
|
-
# handed to the session once the first reached flaky
|
|
167
|
-
# first attempt was is kept for its span.
|
|
168
|
-
def run_first(example, test_run, detector)
|
|
169
|
-
|
|
170
|
-
detector&.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
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) }
|
|
174
177
|
example.metadata[:mergify_first_attempt] = { try: tries.first, ended_at: tries.first_ended_at } if tries.first
|
|
175
178
|
tries
|
|
176
179
|
end
|
|
@@ -215,13 +218,13 @@ module Mergify
|
|
|
215
218
|
# `run_example`): answers the first failure, the reruns' included.
|
|
216
219
|
# Each rerun is one try: a retry plugin's tries would hide its failure
|
|
217
220
|
# behind the one that passed.
|
|
218
|
-
def rerun(example, detector, first_failure)
|
|
221
|
+
def rerun(example, name, detector, first_failure)
|
|
219
222
|
without_retries(example) do
|
|
220
223
|
until example.metadata[:is_last_rerun]
|
|
221
|
-
example.metadata[:is_last_rerun] = detector.last_rerun_for_test?(
|
|
224
|
+
example.metadata[:is_last_rerun] = detector.last_rerun_for_test?(name)
|
|
222
225
|
reset_for_rerun(example)
|
|
223
226
|
|
|
224
|
-
run_attempt(example, detector)
|
|
227
|
+
run_attempt(example, name, detector)
|
|
225
228
|
first_failure ||= example.exception
|
|
226
229
|
end
|
|
227
230
|
end
|
|
@@ -258,12 +261,12 @@ module Mergify
|
|
|
258
261
|
|
|
259
262
|
# Runs what the around hook wraps -- the before and after hooks included
|
|
260
263
|
# -- timed by the session, and answers its outcome.
|
|
261
|
-
def run_attempt(example, detector)
|
|
262
|
-
detector.attempt_started(
|
|
264
|
+
def run_attempt(example, name, detector)
|
|
265
|
+
detector.attempt_started(name)
|
|
263
266
|
example.run
|
|
264
267
|
failed = example.exception || example.metadata.delete(:mergify_quarantined_failure)
|
|
265
268
|
status = failed ? :failed : :passed
|
|
266
|
-
detector.attempt_ended(
|
|
269
|
+
detector.attempt_ended(name, status)
|
|
267
270
|
status
|
|
268
271
|
end
|
|
269
272
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'set'
|
|
4
|
-
|
|
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
|
-
#
|
|
100
|
-
|
|
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
|
|
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/
|
|
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.
|
|
38
|
-
|
|
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
|
|
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::
|
|
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::
|
|
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
|
-
|
|
20
|
-
|
|
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`
|
|
24
|
-
# `'failed'` or `'skipped'`, before the quarantine turned a
|
|
25
|
-
# `pending`), as `{ 'quarantined' => bool, 'absorbed' =>
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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'
|
|
@@ -29,7 +30,7 @@ module Mergify
|
|
|
29
30
|
|
|
30
31
|
attr_reader :session, :repo_name, :test_run_id,
|
|
31
32
|
:flaky_detector, :quarantined_tests,
|
|
32
|
-
:captured_session_verdict
|
|
33
|
+
:captured_session_verdict, :names
|
|
33
34
|
|
|
34
35
|
# `doubles` has the session play Mergify in memory, answering and keeping
|
|
35
36
|
# what it would send (the session's test doubles, by port, as a Hash:
|
|
@@ -37,12 +38,15 @@ module Mergify
|
|
|
37
38
|
# build their TestRun themselves -- or, for a run in another process,
|
|
38
39
|
# install it from a file required before `rspec_mergify`. The instance
|
|
39
40
|
# the gem builds takes none, and the gem reads no switch for them.
|
|
41
|
+
# rubocop:disable-next Metrics/MethodLength
|
|
40
42
|
def initialize(doubles: nil)
|
|
41
43
|
# What this process reads once: the CI context, the trace a caller
|
|
42
44
|
# handed down, and the run's id; and the deprecated names the run
|
|
43
45
|
# sets, which the summary names.
|
|
44
|
-
@session = Native::Session.start(
|
|
45
|
-
|
|
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
|
+
)
|
|
46
50
|
@repo_name = @session.repository_name
|
|
47
51
|
@test_run_id = @session.run_id
|
|
48
52
|
@reruns = nil
|
|
@@ -50,6 +54,7 @@ module Mergify
|
|
|
50
54
|
@quarantined_tests = nil
|
|
51
55
|
@captured_session_verdict = nil
|
|
52
56
|
@examples_to_run = nil
|
|
57
|
+
@names = Names.new(Dir.pwd)
|
|
53
58
|
|
|
54
59
|
set_up if reports?
|
|
55
60
|
end
|
|
@@ -63,8 +68,8 @@ module Mergify
|
|
|
63
68
|
|
|
64
69
|
# Hand the session what this run is about to execute, and answer how it
|
|
65
70
|
# executes it, as the session decided: a Hash whose `outcome` is `full`
|
|
66
|
-
# or `failure_first` (see `Native::Session#select`).
|
|
67
|
-
# runs.
|
|
71
|
+
# or `failure_first` (see `Mergify::CI::Core::Native::Session#select`).
|
|
72
|
+
# Every example runs.
|
|
68
73
|
#
|
|
69
74
|
# Called once RSpec has loaded the spec files and applied every filter
|
|
70
75
|
# of its own -- file and line arguments, `--tag`, `-e`,
|
|
@@ -74,11 +79,11 @@ module Mergify
|
|
|
74
79
|
# own identity: the engine matches each against the previous attempt of
|
|
75
80
|
# the same slice. It travels on the verdict and the trace of a run that
|
|
76
81
|
# asked for the selection.
|
|
77
|
-
def select(
|
|
82
|
+
def select(names)
|
|
78
83
|
return unless reports?
|
|
79
84
|
|
|
80
|
-
@examples_to_run =
|
|
81
|
-
@session.select(
|
|
85
|
+
@examples_to_run = names.size
|
|
86
|
+
@session.select(names)
|
|
82
87
|
end
|
|
83
88
|
|
|
84
89
|
# The run's Fail First, which the session hands out when Mergify
|
|
@@ -87,10 +92,10 @@ module Mergify
|
|
|
87
92
|
@session.fail_first
|
|
88
93
|
end
|
|
89
94
|
|
|
90
|
-
# Record
|
|
91
|
-
# session's verdict.
|
|
92
|
-
def record_verdict(
|
|
93
|
-
@session.record_test(
|
|
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)
|
|
94
99
|
end
|
|
95
100
|
|
|
96
101
|
# Write what this session concluded to Mergify, before the spans go.
|
|
@@ -112,19 +117,19 @@ module Mergify
|
|
|
112
117
|
end
|
|
113
118
|
|
|
114
119
|
# Hand the session an example once it finished, as the formatter says
|
|
115
|
-
# 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
|
|
116
121
|
# trace.
|
|
117
122
|
def record_case(example)
|
|
118
123
|
@session.record_case(example)
|
|
119
124
|
end
|
|
120
125
|
|
|
121
|
-
# A retry plugin (rspec-retry) tried the example
|
|
122
|
-
# attempt, which ended with `status` (`'passed'` or
|
|
123
|
-
# `duration_ms`: the session counts it on the
|
|
124
|
-
# of its runs where flaky detection runs,
|
|
125
|
-
# its execution cap.
|
|
126
|
-
def record_retry(
|
|
127
|
-
@session.record_retry(
|
|
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)
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
# Have the session send the run's trace, once, at the end: the examples
|
|
@@ -145,20 +150,23 @@ module Mergify
|
|
|
145
150
|
@session.summary(Utils.parallel_worker?)
|
|
146
151
|
end
|
|
147
152
|
|
|
148
|
-
# Prepare flaky detection for the examples this run is about to run
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
|
|
152
|
-
|
|
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)
|
|
153
160
|
detection = @reruns&.detection
|
|
154
161
|
@flaky_detector = detection && FlakyDetector.new(@reruns, detection)
|
|
155
162
|
end
|
|
156
163
|
|
|
157
|
-
# Account for
|
|
158
|
-
|
|
164
|
+
# Account for the real outcome of the example known by `keys` against
|
|
165
|
+
# the quarantine list.
|
|
166
|
+
def record_quarantine(keys, outcome)
|
|
159
167
|
return { 'quarantined' => false, 'absorbed' => false } unless @quarantined_tests
|
|
160
168
|
|
|
161
|
-
@quarantined_tests.record(
|
|
169
|
+
@quarantined_tests.record(keys, outcome)
|
|
162
170
|
end
|
|
163
171
|
|
|
164
172
|
private
|
|
@@ -188,7 +196,7 @@ module Mergify
|
|
|
188
196
|
# be on whatever its value, so that `false` silently stopped the upload;
|
|
189
197
|
# both names now take a boolean.
|
|
190
198
|
def debug_mode?
|
|
191
|
-
Utils.env_truthy?(Utils.renamed_env(Utils::CI_DEBUG_ENV))
|
|
199
|
+
Mergify::CI::Core::Utils.env_truthy?(Utils.renamed_env(Utils::CI_DEBUG_ENV))
|
|
192
200
|
end
|
|
193
201
|
|
|
194
202
|
# Where the session sends the verdict and the trace: shown in debug mode,
|
|
@@ -200,7 +208,7 @@ module Mergify
|
|
|
200
208
|
# The gem's enable switch, under every name it has had: the run then
|
|
201
209
|
# reports outside CI too.
|
|
202
210
|
def enabled?
|
|
203
|
-
Utils.env_truthy?(Utils.renamed_env(Utils::CI_ENABLE_ENV))
|
|
211
|
+
Mergify::CI::Core::Utils.env_truthy?(Utils.renamed_env(Utils::CI_ENABLE_ENV))
|
|
204
212
|
end
|
|
205
213
|
end
|
|
206
214
|
end
|
data/lib/mergify/rspec/utils.rb
CHANGED
|
@@ -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::
|
|
10
|
-
# detects identically. What is left is what is genuinely Ruby's:
|
|
11
|
-
# repository name the API needs split, and reading the plugin's
|
|
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.
|
|
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.
|
|
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/
|
|
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
|
data/lib/mergify/rspec/native.rb
DELETED
|
@@ -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' }
|
|
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
|