rspec-tracer 1.2.2 → 1.2.3
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/CHANGELOG.md +25 -0
- data/lib/rspec_tracer/version.rb +1 -1
- data/lib/rspec_tracer.rb +118 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c0ed3fbdb4a082d56a4d83a528dff26bd174abeb28cf1669dc5dd79de521cee
|
|
4
|
+
data.tar.gz: ebc2fbdfde4c6ac82072b5464f5dc0441d73efd24b4410ed96d3892ea765f1a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c28a6a997cf74dbb8d910972d9dd8e0b6e9f36aaacc9670e8a50a6bf7c0d4100f6cf421befcba8349750c3ba179a05b86d2a0cbdf740280d490d4bd42c37512
|
|
7
|
+
data.tar.gz: ce834032d235ea1e2775fb127ac040d6f12f560e7cf9babe5fd208a68cb5dd7260a2ac1f2af78b2a782a622902d9b8291adb2ca339837de70b9f12259e05a456
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## [1.2.3] - 2026-05-06
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **Parallel-tests purge race when a sibling worker is still mid-flush**
|
|
6
|
+
— the elected worker trusted only `parallel_tests`'s pid-file barrier
|
|
7
|
+
(`ParallelTests.wait_for_other_processes_to_finish`), which under
|
|
8
|
+
specific scheduling/I/O timing on GHA Linux x86_64 can return while a
|
|
9
|
+
sibling's `parallel_tests_N/` dir hasn't fully flushed. The elected
|
|
10
|
+
then merged + purged, racing the in-progress sibling. Symptoms:
|
|
11
|
+
intermittent leftover `parallel_tests_N/` dir post-purge AND/OR
|
|
12
|
+
silently dropped peer caches in the merge.
|
|
13
|
+
|
|
14
|
+
Backport of upstream PR
|
|
15
|
+
[#168](https://github.com/avmnu-sng/rspec-tracer/pull/168). Adds a
|
|
16
|
+
filesystem barrier layered on top of the pid-file wait. Each worker
|
|
17
|
+
writes a `.rspec_tracer_boot` marker at `RSpecTracer.start` time and
|
|
18
|
+
a `.rspec_tracer_done` marker as the first step of its at_exit tasks;
|
|
19
|
+
the elected worker waits for every booted peer's `.done` to
|
|
20
|
+
materialize before proceeding to merge + purge. Two independent
|
|
21
|
+
signals (pid file + filesystem) must agree before the elected worker
|
|
22
|
+
declares the peer set stable. Bounded at 5 s with a graceful warn for
|
|
23
|
+
crashed peers — their dirs are purged regardless of completion state,
|
|
24
|
+
and the merge accepts whatever's on disk.
|
|
25
|
+
|
|
1
26
|
## [1.2.2] - 2026-05-04
|
|
2
27
|
|
|
3
28
|
### Fixed
|
data/lib/rspec_tracer/version.rb
CHANGED
data/lib/rspec_tracer.rb
CHANGED
|
@@ -30,6 +30,17 @@ require_relative 'rspec_tracer/time_formatter'
|
|
|
30
30
|
require_relative 'rspec_tracer/version'
|
|
31
31
|
|
|
32
32
|
module RSpecTracer
|
|
33
|
+
# Filesystem barrier markers, layered on top of parallel_tests's
|
|
34
|
+
# pid-file wait to defend against the GHA-observed race where the
|
|
35
|
+
# gem's `wait_for_other_processes_to_finish` returns while a sibling
|
|
36
|
+
# worker hasn't fully flushed its `parallel_tests_N/` dir yet. Each
|
|
37
|
+
# worker writes BOOT at setup-time and DONE as the first step of its
|
|
38
|
+
# at_exit tasks; the elected worker waits for every booted peer's
|
|
39
|
+
# DONE marker (deadline-bounded) before proceeding to merge + purge.
|
|
40
|
+
PARALLEL_TESTS_BOOT_MARKER_FILENAME = '.rspec_tracer_boot'
|
|
41
|
+
PARALLEL_TESTS_DONE_MARKER_FILENAME = '.rspec_tracer_done'
|
|
42
|
+
PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS = 5
|
|
43
|
+
|
|
33
44
|
class << self
|
|
34
45
|
attr_accessor :running, :pid, :no_examples, :duplicate_examples
|
|
35
46
|
|
|
@@ -186,6 +197,29 @@ module RSpecTracer
|
|
|
186
197
|
RSpecTracer.logger.error "Failed to load parallel tests (Error: #{e.message})"
|
|
187
198
|
ensure
|
|
188
199
|
track_parallel_tests_test_env_number
|
|
200
|
+
parallel_tests_touch_boot!
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Per-worker boot marker. Source-of-truth for "this worker booted
|
|
204
|
+
# past `RSpecTracer.start`", consumed by the elected worker's
|
|
205
|
+
# finalize-time peer enumeration. Idempotent; failures are warned
|
|
206
|
+
# and absorbed (boot-marker write must never block test execution).
|
|
207
|
+
def parallel_tests_touch_boot!
|
|
208
|
+
return unless parallel_tests?
|
|
209
|
+
|
|
210
|
+
FileUtils.mkdir_p(RSpecTracer.cache_path)
|
|
211
|
+
File.write(
|
|
212
|
+
File.join(RSpecTracer.cache_path, PARALLEL_TESTS_BOOT_MARKER_FILENAME),
|
|
213
|
+
JSON.generate(
|
|
214
|
+
pid: Process.pid,
|
|
215
|
+
test_env_number: ENV.fetch('TEST_ENV_NUMBER', ''),
|
|
216
|
+
started_at: Time.now.utc.iso8601
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
rescue StandardError => e
|
|
220
|
+
RSpecTracer.logger.warn(
|
|
221
|
+
"RSpec tracer: failed to write boot marker (#{e.class}: #{e.message})"
|
|
222
|
+
)
|
|
189
223
|
end
|
|
190
224
|
|
|
191
225
|
def track_parallel_tests_test_env_number
|
|
@@ -315,6 +349,15 @@ module RSpecTracer
|
|
|
315
349
|
end
|
|
316
350
|
|
|
317
351
|
def run_parallel_tests_exit_tasks
|
|
352
|
+
# Every worker — elected or not — drops its `.done` marker as the
|
|
353
|
+
# first thing in finalize so the elected worker's
|
|
354
|
+
# `parallel_tests_wait_for_peer_done_markers!` can observe it.
|
|
355
|
+
# Non-elected workers stop here; the elected worker proceeds to
|
|
356
|
+
# the merge + purge sequence (gated by `parallel_tests_executed?`,
|
|
357
|
+
# which now layers the peer-done barrier on top of the existing
|
|
358
|
+
# pid-file wait).
|
|
359
|
+
parallel_tests_touch_done!
|
|
360
|
+
|
|
318
361
|
return unless parallel_tests_executed?
|
|
319
362
|
|
|
320
363
|
merge_parallel_tests_reports
|
|
@@ -324,6 +367,26 @@ module RSpecTracer
|
|
|
324
367
|
purge_parallel_tests_reports
|
|
325
368
|
end
|
|
326
369
|
|
|
370
|
+
# Per-worker done marker. Written by every worker (elected or not)
|
|
371
|
+
# as the first step of `run_parallel_tests_exit_tasks`. Pairs with
|
|
372
|
+
# the boot marker for the elected worker's peer-done barrier:
|
|
373
|
+
# presence of `.done` means "this worker has signalled completion
|
|
374
|
+
# of its own writes"; absence (with `.boot` present) means "still
|
|
375
|
+
# mid-flush or crashed". Idempotent; failures are warned + absorbed.
|
|
376
|
+
def parallel_tests_touch_done!
|
|
377
|
+
return unless parallel_tests?
|
|
378
|
+
|
|
379
|
+
FileUtils.mkdir_p(RSpecTracer.cache_path)
|
|
380
|
+
File.write(
|
|
381
|
+
File.join(RSpecTracer.cache_path, PARALLEL_TESTS_DONE_MARKER_FILENAME),
|
|
382
|
+
Time.now.utc.iso8601
|
|
383
|
+
)
|
|
384
|
+
rescue StandardError => e
|
|
385
|
+
RSpecTracer.logger.warn(
|
|
386
|
+
"RSpec tracer: failed to write done marker (#{e.class}: #{e.message})"
|
|
387
|
+
)
|
|
388
|
+
end
|
|
389
|
+
|
|
327
390
|
def merge_parallel_tests_reports
|
|
328
391
|
return unless parallel_tests_executed?
|
|
329
392
|
|
|
@@ -423,9 +486,64 @@ module RSpecTracer
|
|
|
423
486
|
|
|
424
487
|
ParallelTests.wait_for_other_processes_to_finish
|
|
425
488
|
|
|
489
|
+
# Belt-and-suspenders barrier: pid-file said everyone's done, but
|
|
490
|
+
# the gem's `wait_for_other_processes_to_finish` has been observed
|
|
491
|
+
# on GHA Linux x86_64 to return while a sibling's `parallel_tests_N/`
|
|
492
|
+
# is still mid-flush. Cross-check via the `.boot`/`.done` filesystem
|
|
493
|
+
# markers before declaring the peer set stable. Idempotent: once
|
|
494
|
+
# all peers have flushed, subsequent calls just glob, find nothing
|
|
495
|
+
# missing, and return.
|
|
496
|
+
parallel_tests_wait_for_peer_done_markers!
|
|
497
|
+
|
|
426
498
|
true
|
|
427
499
|
end
|
|
428
500
|
|
|
501
|
+
# Block until every peer that wrote `.boot` has also written `.done`,
|
|
502
|
+
# or the deadline elapses. Polled at 50ms — fine enough to close the
|
|
503
|
+
# typical "barrier returned a tick early" case within a poll or two,
|
|
504
|
+
# coarse enough not to dominate CPU.
|
|
505
|
+
#
|
|
506
|
+
# On timeout we log a warn and proceed: a peer that never wrote
|
|
507
|
+
# `.done` either crashed (then its dir is orphan content; the
|
|
508
|
+
# subsequent purge cleans it) or is genuinely hung (the elected
|
|
509
|
+
# can't fix that — we choose merge correctness over indefinite wait).
|
|
510
|
+
def parallel_tests_wait_for_peer_done_markers!
|
|
511
|
+
base_dir = File.dirname(RSpecTracer.cache_path)
|
|
512
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS
|
|
513
|
+
|
|
514
|
+
loop do
|
|
515
|
+
missing = parallel_tests_peer_dirs_missing_done(base_dir)
|
|
516
|
+
return if missing.empty?
|
|
517
|
+
|
|
518
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
519
|
+
RSpecTracer.logger.warn(
|
|
520
|
+
'RSpec tracer: peers booted without finishing within ' \
|
|
521
|
+
"#{PARALLEL_TESTS_PEER_DONE_DEADLINE_SECONDS}s: #{missing.inspect}; " \
|
|
522
|
+
'proceeding (peer dirs will be purged regardless of completion state)'
|
|
523
|
+
)
|
|
524
|
+
return
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
sleep 0.05
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
# Set difference of `.boot`-bearing peer dirs and `.done`-bearing
|
|
532
|
+
# peer dirs under `base_dir`. A returned entry means "this peer
|
|
533
|
+
# registered but has not signalled completion yet" — either still
|
|
534
|
+
# mid-flush or crashed.
|
|
535
|
+
def parallel_tests_peer_dirs_missing_done(base_dir)
|
|
536
|
+
boot_dirs = parallel_tests_peer_dirs_with_marker(base_dir, PARALLEL_TESTS_BOOT_MARKER_FILENAME)
|
|
537
|
+
done_dirs = parallel_tests_peer_dirs_with_marker(base_dir, PARALLEL_TESTS_DONE_MARKER_FILENAME)
|
|
538
|
+
boot_dirs - done_dirs
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def parallel_tests_peer_dirs_with_marker(base_dir, marker_filename)
|
|
542
|
+
Dir.glob(File.join(base_dir, 'parallel_tests_*', marker_filename)).map do |path|
|
|
543
|
+
File.dirname(path)
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
429
547
|
# Elects the worker that performs the per-run merge. Delegates to
|
|
430
548
|
# `::ParallelTests.first_process?`, which returns true iff
|
|
431
549
|
# `TEST_ENV_NUMBER.to_i <= 1` — i.e. for exactly one worker
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-tracer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abhimanyu Singh
|
|
@@ -111,7 +111,7 @@ licenses:
|
|
|
111
111
|
- MIT
|
|
112
112
|
metadata:
|
|
113
113
|
homepage_uri: https://github.com/avmnu-sng/rspec-tracer
|
|
114
|
-
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.2.
|
|
114
|
+
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.2.3
|
|
115
115
|
changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
|
|
116
116
|
bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
|
|
117
117
|
rdoc_options: []
|