rspec-mergify 2026.10.8 → 2026.10.10

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: e5715b89995827b7f5d3840e0c3c1d94288f511fedcac9f3bdd58433c1908228
4
- data.tar.gz: d70082926c396a3ac69ae778f9024be9d3cde6570cd84ef498d1e489ae9164d2
3
+ metadata.gz: addf160bfa2fcbe5d11558f76b497bdae4342d27ad69b4caadc7659854dedfde
4
+ data.tar.gz: 96d6a5c574bd767230faf016689753e8ad2f8eac4e9e68c3644cff8fe9fba47e
5
5
  SHA512:
6
- metadata.gz: 036d1307e6752c12f0c4a88a5f4625528a8ef6eb7d0247b23100127d42a1842c2a3ddadcffc2439f27bb73f49f71d478346fb51b4a1524725c6812f232e10168
7
- data.tar.gz: d3e25b1c4dda733655435cd3e28016a30127c82ed04e8d5b8e07a8e0e7c7374d572491bf8559e3f6c9262c814abe5130e4b65781b0c93cd7f91f2a72f5496de8
6
+ metadata.gz: 74839e7f18bcec97b58f5801c4c651f61dfe3f522188442f10609937ef1de454c832ef4776f3c6b7146415770ca9d6554e6b3b138742345a27f9ca17850956b5
7
+ data.tar.gz: 447fa60ca2e3f431980cb8294ea39c718b5969d263d1eb6af262dda3fd26f29c6941fbdf7e86bca736bef607b0a85563d1b8a557425dada761230edaacbb0f69
data/README.md CHANGED
@@ -6,6 +6,7 @@ RSpec plugin for [Mergify Test Engine](https://docs.mergify.com/test-engine/).
6
6
 
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
+ - **Test retry** — On the default branch, runs a failing example again, once rspec-retry's tries failed, within a time budget Mergify sets
9
10
  - **Test quarantine** — Quarantines failing tests so they don't block CI
10
11
  - **Test selection** — Runs first the examples a merge-queue bisection step failed in the step above, and stops once one fails again
11
12
 
@@ -25,12 +26,12 @@ The gem runs on `mergify-ci-core`, which Bundler installs with it. That gem ship
25
26
 
26
27
  | Platform | Requirement |
27
28
  |---|---|
28
- | Linux x86_64 / aarch64 (glibc) | **glibc 2.30 or newer** — Debian 11+, Ubuntu 20.04+, RHEL 9+, Amazon Linux 2023 |
29
- | Linux x86_64 / aarch64 (musl) | Alpine; on Ruby 3.1 also `apk add libgcc` |
29
+ | Linux x86_64 / aarch64 (glibc) | **glibc 2.28 or newer** — Debian 10+, Ubuntu 20.04+, RHEL 8+ and its rebuilds, Amazon Linux 2023 |
30
+ | Linux x86_64 / aarch64 (musl) | Alpine |
30
31
  | macOS arm64 / x86_64 | — |
31
32
  | Windows x64 (UCRT) | — |
32
33
 
33
- Anywhere else, and on glibc older than 2.30 — RHEL, Rocky, AlmaLinux and Oracle Linux 8, Amazon Linux 2, CentOS 7 — the extension cannot load. The gem still installs and your suite still runs, but nothing is reported to Mergify. Pin the last pure-Ruby release on those systems:
34
+ Anywhere else, and on glibc older than 2.28 — Amazon Linux 2, CentOS 7, Ubuntu 18.04 — the extension cannot load. The gem still installs and your suite still runs, but nothing is reported to Mergify. Pin the last pure-Ruby release on those systems:
34
35
 
35
36
  ```ruby
36
37
  gem 'rspec-mergify', '0.1.4'
@@ -53,6 +54,7 @@ The plugin activates automatically when running in CI (detected via the `CI` env
53
54
  | `MERGIFY_TRACEPARENT` | W3C distributed trace context | — |
54
55
  | `MERGIFY_TEST_JOB_NAME` | Mergify test job name | — |
55
56
  | `MERGIFY_FAIL_FIRST_ENABLE` | Set to `false` to never run first what a merge-queue bisection step of this job failed before | `true` |
57
+ | `MERGIFY_TEST_RETRY_ENABLE` | Set to `false` to have this job retry no failing test, whatever the repository sets | `true` |
56
58
 
57
59
  The names these replace, `RSPEC_MERGIFY_ENABLE` and `RSPEC_MERGIFY_DEBUG`, are
58
60
  deprecated: they still work when the new name is not set, and the summary says
@@ -143,7 +145,7 @@ test.
143
145
 
144
146
  [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:
145
147
 
146
- - flaky detection sizes each worker's rerun budget from the tests that worker runs;
148
+ - flaky detection sizes each worker's rerun budget from the tests that worker runs, and test retry gives each worker a budget of its own;
147
149
  - each worker prints its own Mergify report, so a report covers only that worker's tests.
148
150
 
149
151
  Suites split across CI jobs (knapsack, `circleci tests split`, Buildkite `parallelism`) behave the same way, one session per job.
data/THIRD_PARTY_LICENSES CHANGED
@@ -8,8 +8,8 @@ The source code of each component is available from the repository listed
8
8
  next to it, as the Mozilla Public License 2.0 requires for the components
9
9
  under it.
10
10
 
11
- Apache License 2.0 (230)
12
- MIT License (73)
11
+ Apache License 2.0 (229)
12
+ MIT License (74)
13
13
  Unicode License v3 (19)
14
14
  ISC License (5)
15
15
  BSD 3-Clause "New" or "Revised" License (4)
@@ -23,7 +23,7 @@ Apache License 2.0 (Apache-2.0)
23
23
 
24
24
  Used by:
25
25
  - cmov 0.5.4 (https://github.com/RustCrypto/utils)
26
- - ctutils 0.4.3 (https://github.com/RustCrypto/utils)
26
+ - ctutils 0.4.6 (https://github.com/RustCrypto/utils)
27
27
  - encode_unicode 1.0.0 (https://github.com/tormol/encode_unicode)
28
28
  - encoding_rs 0.8.42 (https://github.com/hsivonen/encoding_rs)
29
29
  - multiversion_no_op 1.0.0 (https://github.com/hsivonen/multiversion_no_op)
@@ -474,11 +474,7 @@ Used by:
474
474
  - windows-link 0.2.1 (https://github.com/microsoft/windows-rs)
475
475
  - windows-result 0.4.1 (https://github.com/microsoft/windows-rs)
476
476
  - windows-strings 0.5.1 (https://github.com/microsoft/windows-rs)
477
- - windows-sys 0.52.0 (https://github.com/microsoft/windows-rs)
478
477
  - windows-sys 0.61.2 (https://github.com/microsoft/windows-rs)
479
- - windows-targets 0.52.6 (https://github.com/microsoft/windows-rs)
480
- - windows_x86_64_gnu 0.52.6 (https://github.com/microsoft/windows-rs)
481
- - windows_x86_64_msvc 0.52.6 (https://github.com/microsoft/windows-rs)
482
478
 
483
479
  Apache License
484
480
  Version 2.0, January 2004
@@ -2571,14 +2567,14 @@ limitations under the License.
2571
2567
  Apache License 2.0 (Apache-2.0)
2572
2568
 
2573
2569
  Used by:
2574
- - futures-channel 0.3.33 (https://github.com/rust-lang/futures-rs)
2575
- - futures-core 0.3.33 (https://github.com/rust-lang/futures-rs)
2570
+ - futures-channel 0.3.34 (https://github.com/rust-lang/futures-rs)
2571
+ - futures-core 0.3.34 (https://github.com/rust-lang/futures-rs)
2576
2572
  - futures-executor 0.3.33 (https://github.com/rust-lang/futures-rs)
2577
- - futures-io 0.3.33 (https://github.com/rust-lang/futures-rs)
2578
- - futures-macro 0.3.33 (https://github.com/rust-lang/futures-rs)
2579
- - futures-sink 0.3.33 (https://github.com/rust-lang/futures-rs)
2580
- - futures-task 0.3.33 (https://github.com/rust-lang/futures-rs)
2581
- - futures-util 0.3.33 (https://github.com/rust-lang/futures-rs)
2573
+ - futures-io 0.3.34 (https://github.com/rust-lang/futures-rs)
2574
+ - futures-macro 0.3.34 (https://github.com/rust-lang/futures-rs)
2575
+ - futures-sink 0.3.34 (https://github.com/rust-lang/futures-rs)
2576
+ - futures-task 0.3.34 (https://github.com/rust-lang/futures-rs)
2577
+ - futures-util 0.3.34 (https://github.com/rust-lang/futures-rs)
2582
2578
  - futures 0.3.33 (https://github.com/rust-lang/futures-rs)
2583
2579
 
2584
2580
  Apache License
@@ -4882,6 +4878,7 @@ Apache License 2.0 (Apache-2.0)
4882
4878
  Used by:
4883
4879
  - ahash 0.8.12 (https://github.com/tkaitchuck/ahash)
4884
4880
  - async-channel 2.5.0 (https://github.com/smol-rs/async-channel)
4881
+ - async-compression 0.4.51 (https://github.com/Nullus157/async-compression)
4885
4882
  - async-executor 1.14.0 (https://github.com/smol-rs/async-executor)
4886
4883
  - async-io 2.6.0 (https://github.com/smol-rs/async-io)
4887
4884
  - async-lock 3.4.2 (https://github.com/smol-rs/async-lock)
@@ -4894,6 +4891,8 @@ Used by:
4894
4891
  - bitflags 2.13.1 (https://github.com/bitflags/bitflags)
4895
4892
  - blocking 1.7.0 (https://github.com/smol-rs/blocking)
4896
4893
  - cfg-if 1.0.4 (https://github.com/rust-lang/cfg-if)
4894
+ - compression-codecs 0.4.46 (https://github.com/Nullus157/async-compression)
4895
+ - compression-core 0.4.34 (https://github.com/Nullus157/async-compression)
4897
4896
  - concurrent-queue 2.5.0 (https://github.com/smol-rs/concurrent-queue)
4898
4897
  - core-foundation-sys 0.8.7 (https://github.com/servo/core-foundation-rs)
4899
4898
  - core-foundation 0.10.1 (https://github.com/servo/core-foundation-rs)
@@ -4951,7 +4950,7 @@ Used by:
4951
4950
  - secret-service 5.2.0 (https://github.com/hwchen/secret-service-rs.git)
4952
4951
  - security-framework-sys 2.17.0 (https://github.com/kornelski/rust-security-framework)
4953
4952
  - security-framework 3.7.0 (https://github.com/kornelski/rust-security-framework)
4954
- - self-replace 1.5.0 (https://github.com/mitsuhiko/self-replace)
4953
+ - self-replace 1.5.1 (https://github.com/mitsuhiko/self-replace)
4955
4954
  - signal-hook-registry 1.4.8 (https://github.com/vorner/signal-hook)
4956
4955
  - smallvec 1.15.2 (https://github.com/servo/rust-smallvec)
4957
4956
  - socket2 0.6.5 (https://github.com/rust-lang/socket2)
@@ -8791,7 +8790,7 @@ THIS SOFTWARE.
8791
8790
  ISC License (ISC)
8792
8791
 
8793
8792
  Used by:
8794
- - rustls-webpki 0.103.15 (https://github.com/rustls/webpki)
8793
+ - rustls-webpki 0.103.16 (https://github.com/rustls/webpki)
8795
8794
 
8796
8795
  Except as otherwise noted, this project is licensed under the following
8797
8796
  (ISC-style) terms:
@@ -9826,10 +9825,10 @@ SOFTWARE.
9826
9825
  MIT License (MIT)
9827
9826
 
9828
9827
  Used by:
9829
- - jsonschema-regex 0.60.0 (https://github.com/Stranger6667/jsonschema)
9830
- - jsonschema-value 0.60.0 (https://github.com/Stranger6667/jsonschema)
9831
- - jsonschema 0.60.0 (https://github.com/Stranger6667/jsonschema)
9832
- - referencing 0.60.0 (https://github.com/Stranger6667/jsonschema)
9828
+ - jsonschema-regex 0.60.2 (https://github.com/Stranger6667/jsonschema)
9829
+ - jsonschema-value 0.60.2 (https://github.com/Stranger6667/jsonschema)
9830
+ - jsonschema 0.60.2 (https://github.com/Stranger6667/jsonschema)
9831
+ - referencing 0.60.2 (https://github.com/Stranger6667/jsonschema)
9833
9832
 
9834
9833
  MIT License
9835
9834
 
@@ -10083,6 +10082,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
10083
10082
  MIT License (MIT)
10084
10083
 
10085
10084
  Used by:
10085
+ - tokio-util 0.7.19 (https://github.com/tokio-rs/tokio)
10086
10086
  - tokio 1.54.1 (https://github.com/tokio-rs/tokio)
10087
10087
 
10088
10088
  MIT License
@@ -146,44 +146,87 @@ module Mergify
146
146
  # engine around `example.run`, and its outcome read off the exception it
147
147
  # left behind. Its first run is the example's first attempt, and the
148
148
  # tries a retry plugin makes after it, inside that run; flaky detection's
149
- # reruns follow, each a try alone.
149
+ # reruns follow, or test retry's, each a try alone.
150
150
  def run_example(example, test_run)
151
151
  name = test_run.names.of(example)
152
152
  detector = test_run.flaky_detector
153
- tries = run_first(example, name, test_run, detector)
154
- return unless detector&.rerunning_test?(name)
153
+ retrier = test_run.test_retrier
154
+ retrier = nil unless retrier&.answers_for?(name)
155
+ tries = run_first(example, name, test_run, detector, retrier)
156
+ if detector&.rerunning_test?(name)
157
+ detect_flakiness(example, name, detector, retrier, tries)
158
+ elsif retrier
159
+ retry_failure(example, name, retrier, tries)
160
+ end
161
+ end
155
162
 
163
+ # Flaky detection reruns the example, within its budget. Where test
164
+ # retry answers for the example, the reruns answer its failure too.
165
+ def detect_flakiness(example, name, detector, retrier, tries)
156
166
  initial_exception = example.exception
157
167
  first_failure = tries.failure || initial_exception
158
- first_failure = rerun(example, name, detector, first_failure) if detector.start_reruns(name)
168
+ first_failure = rerun(example, name, detector, retrier, first_failure) if detector.start_reruns(name)
159
169
 
160
170
  # Which attempt decides the result is the session's rule: in 'new'
161
171
  # mode any failure fails the test, the retry plugin's tries included,
162
172
  # so a new flaky test cannot be merged; in 'unhealthy' mode the reruns
163
- # only learn, and the example's own result stands.
173
+ # only learn, and the example's own result stands, unless test retry
174
+ # says a rerun rescued it.
164
175
  final_exception = detector.rerun_failure_fails_the_test? ? first_failure : initial_exception
176
+ final_exception = nil if retrier&.rescued?(name)
165
177
  example.example.instance_variable_set(:@exception, final_exception)
166
178
  end
167
179
 
180
+ # Test retry runs the failed example again, each time the session grants
181
+ # one more attempt, until one passes: the example then passes, as a pass
182
+ # of a retry plugin's would. Otherwise its first run's failure stands.
183
+ # Each attempt is one try, as flaky detection's reruns are.
184
+ def retry_failure(example, name, retrier, tries)
185
+ failure = example.exception
186
+ return unless failure
187
+
188
+ without_retries(example) { run_retries(example, name, retrier, tries.last_ms) }
189
+ example.example.instance_variable_set(:@exception, retrier.rescued?(name) ? nil : failure)
190
+ end
191
+
192
+ # Each attempt the session grants the example, once the one before it
193
+ # failed after `attempt_ms`.
194
+ def run_retries(example, name, retrier, attempt_ms)
195
+ while retrier.grant?(name, attempt_ms)
196
+ reset_for_rerun(example)
197
+ attempt_ms = run_tries(example).last_ms
198
+ retrier.record(name, example.exception ? :failed : :passed, attempt_ms)
199
+ end
200
+ end
201
+
168
202
  # The first run of the example named `name`: its first attempt, timed by
169
203
  # the engine up to the second try where a retry plugin made one, then
170
204
  # 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)
205
+ # detection and test retry. What the first attempt was is kept for its
206
+ # span.
207
+ def run_first(example, name, test_run, detector, retrier)
173
208
  detector&.attempt_started(name)
174
209
  tries = run_tries(example) { detector&.attempt_stopped(name) }
175
- detector&.attempt_ended(name, first_failed?(example, tries) ? :failed : :passed)
210
+ first_attempt_ended(example, name, tries, detector, retrier)
176
211
  tries.later.each { |try| test_run.record_retry(name, try.status, try.duration_ms) }
177
212
  example.metadata[:mergify_first_attempt] = { try: tries.first, ended_at: tries.first_ended_at } if tries.first
178
213
  tries
179
214
  end
180
215
 
216
+ # The example's first attempt is over: flaky detection and test retry
217
+ # take it before the tries after it.
218
+ def first_attempt_ended(example, name, tries, detector, retrier)
219
+ status = first_failed?(example, tries) ? :failed : :passed
220
+ detector&.attempt_ended(name, status)
221
+ retrier&.record(name, status, tries.first_ms)
222
+ end
223
+
181
224
  # Run the example once, noting each try a retry plugin makes of it; the
182
225
  # block is called as a second one starts.
183
226
  def run_tries(example, &)
184
227
  tries = Tries.new(&)
185
228
  example.metadata[:mergify_tries] = tries
186
- example.run
229
+ tries.time { example.run }
187
230
  tries
188
231
  ensure
189
232
  example.metadata.delete(:mergify_tries)
@@ -215,16 +258,16 @@ module Mergify
215
258
  end
216
259
 
217
260
  # Which attempt decides the result is the session's rule (see
218
- # `run_example`): answers the first failure, the reruns' included.
261
+ # `detect_flakiness`): answers the first failure, the reruns' included.
219
262
  # Each rerun is one try: a retry plugin's tries would hide its failure
220
263
  # behind the one that passed.
221
- def rerun(example, name, detector, first_failure)
264
+ def rerun(example, name, detector, retrier, first_failure)
222
265
  without_retries(example) do
223
266
  until example.metadata[:is_last_rerun]
224
267
  example.metadata[:is_last_rerun] = detector.last_rerun_for_test?(name)
225
268
  reset_for_rerun(example)
226
269
 
227
- run_attempt(example, name, detector)
270
+ run_attempt(example, name, detector, retrier)
228
271
  first_failure ||= example.exception
229
272
  end
230
273
  end
@@ -260,13 +303,15 @@ module Mergify
260
303
  end
261
304
 
262
305
  # Runs what the around hook wraps -- the before and after hooks included
263
- # -- timed by the session, and answers its outcome.
264
- def run_attempt(example, name, detector)
306
+ # -- timed by the session, and answers its outcome; test retry, where it
307
+ # answers for the example, is told of it too.
308
+ def run_attempt(example, name, detector, retrier)
265
309
  detector.attempt_started(name)
266
- example.run
310
+ attempt = run_tries(example)
267
311
  failed = example.exception || example.metadata.delete(:mergify_quarantined_failure)
268
312
  status = failed ? :failed : :passed
269
313
  detector.attempt_ended(name, status)
314
+ retrier&.record(name, status, attempt.last_ms)
270
315
  status
271
316
  end
272
317
 
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mergify
4
+ module RSpec
5
+ # Test retry for one RSpec run, as its session prepared it.
6
+ #
7
+ # Which failures it answers for, whether an example runs once more, and
8
+ # whether a retry rescued it are the session's: on a push to the default
9
+ # branch, every failing example but the ones the quarantine holds and the
10
+ # ones Mergify classifies as broken, up to the repository's retries, within
11
+ # one budget for the run. What is left here is how RSpec runs an attempt,
12
+ # which `Configuration#retry_failure` does.
13
+ class TestRetrier
14
+ # `reruns` is the session's.
15
+ def initialize(reruns)
16
+ @reruns = reruns
17
+ end
18
+
19
+ # Whether a failure of the example named `name` is test retry's to
20
+ # answer for: false where it does not run.
21
+ def answers_for?(name)
22
+ @reruns.retry_answers_for?(name)
23
+ end
24
+
25
+ # An attempt at the example ended with `status` (`:passed` or
26
+ # `:failed`) after `duration_ms`: its first, a rerun of flaky
27
+ # detection's, or one of retry's. Its first reaches flaky detection
28
+ # through its phases, so test retry is told apart.
29
+ def record(name, status, duration_ms)
30
+ @reruns.record_retry_attempt(name, status.to_s, duration_ms)
31
+ end
32
+
33
+ # Whether the example, whose attempt just failed after `attempt_ms`,
34
+ # runs once more. Asked once that attempt is recorded, and once the
35
+ # retry plugin's tries of it are over.
36
+ def grant?(name, attempt_ms)
37
+ @reruns.grant_retry(name, attempt_ms)
38
+ end
39
+
40
+ # Whether an attempt after the example's first passed, rescuing it.
41
+ def rescued?(name)
42
+ @reruns.rescued?(name)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative 'utils'
4
4
  require 'mergify/ci/core'
5
5
  require_relative 'flaky_detection'
6
+ require_relative 'test_retry'
6
7
  require_relative 'names'
7
8
  require 'json'
8
9
  require 'rspec/core/version'
@@ -22,14 +23,14 @@ module Mergify
22
23
  # RSpec runs examples, in one process that can wait for every one put
23
24
  # first.
24
25
  fail_first: { unit: 'test', waits_for_all: true, workers_stop_the_run: false },
25
- # RSpec reruns an example as it goes, and retries no failure, in one
26
- # process that plans the run's budget once.
27
- reruns: { plans_repeats: false, retries_failures: false },
26
+ # RSpec reruns an example as it goes, and retries a failed one in
27
+ # place, in one process that plans the run's budgets once.
28
+ reruns: { plans_repeats: false, retries_failures: true },
28
29
  framework: { name: 'rspec', version: ::RSpec::Core::Version::STRING, language: 'ruby' }
29
30
  }.freeze
30
31
 
31
32
  attr_reader :session, :repo_name, :test_run_id,
32
- :flaky_detector, :quarantined_tests,
33
+ :flaky_detector, :test_retrier, :quarantined_tests,
33
34
  :captured_session_verdict, :names
34
35
 
35
36
  # `doubles` has the session play Mergify in memory, answering and keeping
@@ -51,6 +52,7 @@ module Mergify
51
52
  @test_run_id = @session.run_id
52
53
  @reruns = nil
53
54
  @flaky_detector = nil
55
+ @test_retrier = nil
54
56
  @quarantined_tests = nil
55
57
  @captured_session_verdict = nil
56
58
  @examples_to_run = nil
@@ -150,15 +152,17 @@ module Mergify
150
152
  @session.summary(Utils.parallel_worker?)
151
153
  end
152
154
 
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.
155
+ # Prepare flaky detection and test retry for the examples this run is
156
+ # about to run, by their names, each one Mergify's lists may still hold
157
+ # under its former names (`former_names`, a list by name). Which of them
158
+ # each reruns, and whether it runs at all, are the session's. No example
159
+ # opts out of test retry: RSpec's metadata has no key of Mergify's.
157
160
  def prepare_reruns(names, former_names)
158
161
  @session.state_former_names(former_names)
159
162
  @reruns = @session.prepare_reruns(names)
160
163
  detection = @reruns&.detection
161
164
  @flaky_detector = detection && FlakyDetector.new(@reruns, detection)
165
+ @test_retrier = @reruns && TestRetrier.new(@reruns)
162
166
  end
163
167
 
164
168
  # Account for the real outcome of the example known by `keys` against
@@ -184,8 +188,8 @@ module Mergify
184
188
  end
185
189
 
186
190
  # What a run that reports loads before its examples: the session loads
187
- # the quarantine list for the branch they target, then flaky
188
- # detection's context. Without a list (no branch, a failed fetch, no
191
+ # the quarantine list for the branch they target and the reruns'
192
+ # contexts, all at once. Without a list (no branch, a failed fetch, no
189
193
  # token) nothing is quarantined, and its block says why.
190
194
  def set_up
191
195
  require_relative 'quarantine'
@@ -25,6 +25,15 @@ module Mergify
25
25
  @on_second = on_second
26
26
  @started_ms = nil
27
27
  @first_ended_at = nil
28
+ @run_ms = 0.0
29
+ end
30
+
31
+ # Run the block, the run of the example its tries are made in, timed.
32
+ def time
33
+ started_ms = now_ms
34
+ yield
35
+ ensure
36
+ @run_ms = now_ms - started_ms
28
37
  end
29
38
 
30
39
  def start
@@ -53,6 +62,18 @@ module Mergify
53
62
  @ended.drop(1)
54
63
  end
55
64
 
65
+ # How long the first try took; the whole run where no try started (a
66
+ # skipped example, an `around` hook that raised before running it).
67
+ def first_ms
68
+ first ? first.duration_ms : @run_ms
69
+ end
70
+
71
+ # How long the last try took, as `first_ms`: what the next attempt is
72
+ # expected to take.
73
+ def last_ms
74
+ @ended.empty? ? @run_ms : @ended.last.duration_ms
75
+ end
76
+
56
77
  # The first failure a try left on the example, the quarantine's aside.
57
78
  def failure
58
79
  @ended.find(&:exception)&.exception
@@ -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.8'
11
+ VERSION = '2026.10.10'
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.8
4
+ version: 2026.10.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mergify
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 2026.10.8
32
+ version: 2026.10.10
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 2026.10.8
39
+ version: 2026.10.10
40
40
  description: 'RSpec integration for Mergify Test Engine: OpenTelemetry tracing, flaky
41
41
  test detection, and test quarantine.'
42
42
  email:
@@ -56,6 +56,7 @@ files:
56
56
  - lib/mergify/rspec/formatter.rb
57
57
  - lib/mergify/rspec/names.rb
58
58
  - lib/mergify/rspec/quarantine.rb
59
+ - lib/mergify/rspec/test_retry.rb
59
60
  - lib/mergify/rspec/test_run.rb
60
61
  - lib/mergify/rspec/tries.rb
61
62
  - lib/mergify/rspec/utils.rb