turbo_tests2 3.2.17 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d0f84a37aeb74853501933d16cd9476a1f82800b3d33e9da544af8ef0e0c145
4
- data.tar.gz: 581d982f38436235925c6bc8180a4ac29513c198599ae841949e47bce114186f
3
+ metadata.gz: 7e14a9905e357ba16f5489987c5fc3638078587c35f232443698a02961cacd21
4
+ data.tar.gz: 9ea2481ca612f26d150e8514f6ccf12a758d49080a6ddd545a05f558a76dfe9a
5
5
  SHA512:
6
- metadata.gz: 9d0f7b4e2080fd36f4da47ff4bdebed7fce369995e9c1b6cb305bc2caf9ad935ec2d65eaa84483721b361c57a12eb24b36fd3d0c5fc0ce16a62638f14df16636
7
- data.tar.gz: ffc015acc34a5ad4658a2a5aa53ceb02ec6621eea4e625bc1a7d2b6558c1cc728037ad984c32cc52e08108f8c322011dee41926dfdde3400c5c65ae7021c82dc
6
+ metadata.gz: 86f70203dd5f5ea3824dbc3ef6acae6b4f469ec4b2c7798e4e6e0c847316a51fb7883a61885df45acc09b01d26373416c381b5632b82e9c27c1dd50d5be03bf5
7
+ data.tar.gz: 6ae7d9b0015581bda3e25ee998593becab94daffc2806039e52dc22b73bceb57d3117b95be595380ee9c9bd4db427321aa47cc8971600890256d63e0891fa6f0
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -30,6 +30,23 @@ Please file a bug if you notice a violation of semantic versioning.
30
30
 
31
31
  ### Security
32
32
 
33
+ ## [4.0.0] - 2026-10-15
34
+
35
+ - TAG: [v4.0.0][4.0.0t]
36
+ - COVERAGE: 96.01% -- 1034/1077 lines in 12 files
37
+ - BRANCH COVERAGE: 82.42% -- 272/330 branches in 12 files
38
+ - 37.39% documented
39
+
40
+ ### Changed
41
+
42
+ - Breaking: replace the parallel_tests runtime dependency with runtime_groups (~> 0.1, >= 0.1.2) for process counts, pid bookkeeping, options-file discovery, and runtime-weighted grouping; turbo_tests2 now owns a TurboTests::RuntimeLogger RSpec formatter and reimplements the Rails db:create bootstrap directly on Process.spawn
43
+
44
+ - Breaking: the stall watchdog is now ON by default (TURBO_TESTS2_WATCHDOG_SECONDS defaults to 900, killing silent workers and failing the run with a stall report); set TURBO_TESTS2_WATCHDOG_SECONDS=0 to restore the 3.x wait-forever behavior. The 3.x deprecation notice is replaced by a provider-neutral one-time warning when the watchdog is disabled and workers go silent 900s, naming the most common cause of total silence in a parallel lane (silently OOM-killed workers) and the fix (cap workers x per-worker memory)
45
+
46
+ ### Fixed
47
+
48
+ - --allowed-missing now actually bounds the share of files allowed to miss the runtime log when grouping (parallel_tests silently ignored the option); runtime log is now written by the first-party TurboTests::RuntimeLogger through RuntimeGroups::RuntimeLog, and the watchdog's tracked-pid fallback reads the read-before-write pid ledger correctly
49
+
33
50
  ## [3.2.17] - 2026-10-14
34
51
 
35
52
  - TAG: [v3.2.17][3.2.17t]
@@ -774,7 +791,9 @@ Please file a bug if you notice a violation of semantic versioning.
774
791
 
775
792
  - Initial release
776
793
 
777
- [Unreleased]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.2.17...HEAD
794
+ [Unreleased]: https://github.com/galtzo-floss/turbo_tests2/compare/v4.0.0...HEAD
795
+ [4.0.0]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.2.17...v4.0.0
796
+ [4.0.0t]: https://github.com/galtzo-floss/turbo_tests2/releases/tag/v4.0.0
778
797
  [3.2.17]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.2.16...v3.2.17
779
798
  [3.2.17t]: https://github.com/galtzo-floss/turbo_tests2/releases/tag/v3.2.17
780
799
  [3.2.16]: https://github.com/galtzo-floss/turbo_tests2/compare/v3.2.15...v3.2.16
data/README.md CHANGED
@@ -25,7 +25,7 @@ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-ta
25
25
  |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
26
26
  | I ([@pboling](https://github.com/pboling)) discussed merging this project back into the original `turbo_tests` with [@ilyazub](https://github.com/ilyazub), and we might move in that direction, but it will take time. Since I need this now I'm releasing it as a separate gem/project. We'll be discussing potentially unifying the projects again soon. |
27
27
 
28
- `turbo_tests2` is an RSpec parallel test runner built on [grosser/parallel_tests](https://github.com/grosser/parallel_tests). It uses `parallel_tests` for process counts, file grouping, and runtime balancing, then replaces interleaved worker output with incremental summarized RSpec reporting. Source code of this gem is based on [Discourse](https://github.com/discourse/discourse/blob/6b9784cf8a18636bce281a7e4d18e65a0cbc6290/lib/turbo_tests.rb) and [RubyGems](https://github.com/rubygems/rubygems/tree/390335ceb351668cd433bd5bb9823dd021f82533/bundler/tool) work in this area.
28
+ `turbo_tests2` is an RSpec parallel test runner. It uses [runtime_groups](https://github.com/galtzo-floss/runtime_groups) for process counts, pid bookkeeping, file grouping, and runtime balancing, then replaces interleaved worker output with incremental summarized RSpec reporting. Source code of this gem is based on [Discourse](https://github.com/discourse/discourse/blob/6b9784cf8a18636bce281a7e4d18e65a0cbc6290/lib/turbo_tests.rb) and [RubyGems](https://github.com/rubygems/rubygems/tree/390335ceb351668cd433bd5bb9823dd021f82533/bundler/tool) work in this area. As of v4.0.0 the `parallel_tests` dependency is gone; the CLI keeps compatibility with the `parallel_tests` grouping surface documented below.
29
29
  The project namespace remains `TurboTests`; the published gem and executable are `turbo_tests2`.
30
30
 
31
31
  Incremental summarized output doesn't [fit the vision](https://github.com/grosser/parallel_tests/issues/708) of the `parallel_tests` author, and [RSpec doesn't support built-in parallel testing yet](https://github.com/rspec/rspec-rails/issues/2104#issuecomment-658474900). This gem targets RSpec, not every framework supported by `parallel_tests`, and may not be useful once one of the issues above is implemented.
@@ -445,8 +445,9 @@ producing example/group messages — a run can otherwise hang until the CI
445
445
  provider kills the job (GitHub Actions: 6 hours).
446
446
 
447
447
  `TURBO_TESTS2_WATCHDOG_SECONDS=N` (integer, `0` = disabled) bounds that
448
- silence. **Default is `0` (disabled) in 3.x.** In 4.0 the default becomes
449
- `900` and the kill behavior is always on.
448
+ silence. **Default is `900` as of 4.0.** (3.x defaulted to `0` — disabled —
449
+ with a one-time deprecation notice; set `0` to keep the old wait-forever
450
+ behavior.)
450
451
 
451
452
  When the watchdog fires it kills the remaining worker processes, prints the
452
453
  unfinished groups and worker pids, and fails the run.
@@ -468,12 +469,12 @@ Notes:
468
469
  limit; also set `timeout-minutes` on the CI job. Locally, an outer
469
470
  supervisor (process watcher, container timeout) plays the same role.
470
471
  - When the watchdog is disabled (`0`), a 900s silence prints a one-time
471
- deprecation notice describing the 4.0 default change and both options
472
- (adopt the kill via `TURBO_TESTS2_WATCHDOG_SECONDS`, or explicitly opt out
473
- with `0`). The notice is provider-neutral: it warns that with the
474
- watchdog off, whatever outer process watcher exists — a CI job timeout,
475
- a container orchestrator, or a local supervisor — is what will eventually
476
- end a hung run.
472
+ provider-neutral notice: with the watchdog off, whatever outer process
473
+ watcher exists — a CI job timeout, a container orchestrator, or a local
474
+ supervisor — is what will eventually end a hung run, usually without a
475
+ useful stall report. The notice also names the most common cause of total
476
+ silence in a parallel lane (silently OOM-killed workers) and the fix (cap
477
+ `workers x per-worker memory`).
477
478
 
478
479
  `turbo_tests2` supports custom formatter such as Fuubar, but you might need to require it:
479
480
 
@@ -913,7 +914,7 @@ Thanks for RTFM. ☺️
913
914
  [📌gitmoji]: https://gitmoji.dev
914
915
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
915
916
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
916
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.035-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
917
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-1.077-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
917
918
  [🔐security]: https://github.com/galtzo-floss/turbo_tests2/blob/main/SECURITY.md
918
919
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
919
920
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -939,7 +940,7 @@ Thanks for RTFM. ☺️
939
940
  | Field | Value |
940
941
  |---|---|
941
942
  | Package | turbo_tests2 |
942
- | Description | 🚀 turbo_tests2` is an RSpec parallel test runner built on `parallel_tests`. It uses `parallel_tests` for process counts, file grouping, and runtime balancing, then replaces interleaved worker output with incremental summarized RSpec reporting. Source code of `turbo_test2` gem is based on Discourse and Rubygems work in this area (see README.md). |
943
+ | Description | 🚀 turbo_tests2` is an RSpec parallel test runner. It uses runtime_groups for process counts, pid bookkeeping, file grouping, and runtime balancing, and replaces interleaved worker output with incremental summarized RSpec reporting. Source code of `turbo_test2` gem is based on Discourse and Rubygems work in this area (see README.md). |
943
944
  | Homepage | https://github.com/galtzo-floss/turbo_tests2 |
944
945
  | Source | https://github.com/galtzo-floss/turbo_tests2 |
945
946
  | License | `MIT` |
@@ -363,20 +363,19 @@ module TurboTests
363
363
  exit(1)
364
364
  end
365
365
 
366
- processes = ParallelTests.determine_number_of_processes(count)
367
- pids = ParallelTests.with_pid_file do
368
- (1..processes).map do |process_id|
366
+ processes = RuntimeGroups.process_count(count)
367
+ RuntimeGroups::PidFile.with do |pid_path|
368
+ pids = (1..processes).map do |process_id|
369
369
  env = {
370
370
  "TEST_ENV_NUMBER" => process_id.to_s,
371
371
  "PARALLEL_TEST_GROUPS" => processes.to_s,
372
- "PARALLEL_PID_FILE" => ParallelTests.pid_file_path
372
+ "PARALLEL_PID_FILE" => pid_path
373
373
  }
374
374
  Process.spawn(env, *args)
375
375
  end
376
- end
377
- statuses = pids.map { |pid| Process.wait2(pid).last }
378
-
379
- exit(statuses.all?(&:success?) ? 0 : 1)
376
+ statuses = pids.map { |pid| Process.wait2(pid).last }
377
+ statuses.all?(&:success?) ? 0 : 1
378
+ end.tap { |code| exit(code) }
380
379
  rescue OptionParser::ParseError => e
381
380
  warn(e.message)
382
381
  warn(parser)
@@ -80,7 +80,7 @@ module TurboTests
80
80
  end
81
81
 
82
82
  def report_number_of_tests(groups)
83
- name = ParallelTests::RSpec::Runner.test_file_name
83
+ name = "spec"
84
84
 
85
85
  num_processes = groups.size
86
86
  num_tests = groups.map(&:size).sum
@@ -3,7 +3,7 @@
3
3
  require "json"
4
4
  require "timeout"
5
5
  require "fileutils"
6
- require "parallel_tests/rspec/runner"
6
+ require "runtime_groups"
7
7
  require "rspec/core"
8
8
  require "shellwords"
9
9
  require "tempfile"
@@ -30,14 +30,18 @@ module TurboTests
30
30
 
31
31
  class << self
32
32
  def create(count)
33
- # We are unable to load parallel tests' tasks in the normal way (top of file)
34
- # because it requires that the Rails.application instance already be configured
35
- require "parallel_tests/tasks"
36
-
37
- ENV["PARALLEL_TEST_FIRST_IS_1"] = "true"
38
- command = ["bundle", "exec", "rake", "db:create", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"]
39
- args = {count: count.to_s}
40
- ParallelTests::Tasks.run_in_parallel(command, args)
33
+ # Rails bootstrap: prepare the database once per worker, in parallel.
34
+ # Implemented directly with Process.spawn so the runner does not
35
+ # depend on parallel_tests for anything.
36
+ count = RuntimeGroups.process_count(count)
37
+ pids = (1..count).map do |process_id|
38
+ Process.spawn(
39
+ {"TEST_ENV_NUMBER" => process_id.to_s, "PARALLEL_TEST_GROUPS" => count.to_s},
40
+ "bundle", "exec", "rake", "db:create", "RAILS_ENV=#{ENV.fetch("RAILS_ENV", "test")}"
41
+ )
42
+ end
43
+ statuses = pids.map { |pid| Process.wait2(pid).last }
44
+ statuses.all?(&:success?) ? 0 : 1
41
45
  end
42
46
 
43
47
  def run(opts = {})
@@ -249,10 +253,11 @@ module TurboTests
249
253
  @load_time = 0
250
254
  @load_count = 0
251
255
  @failure_count = 0
252
- # Deprecation-period default: disabled. In 4.0 the kill behavior
253
- # becomes the default (TURBO_TESTS2_WATCHDOG_SECONDS=900) and this
254
- # warning is removed. See README "Stall watchdog".
255
- @watchdog_seconds = Float(ENV.fetch("TURBO_TESTS2_WATCHDOG_SECONDS", "0"))
256
+ # 4.0 default: kill behavior on (900s of total worker silence). Set
257
+ # TURBO_TESTS2_WATCHDOG_SECONDS=0 to disable explicitly (a long-hang
258
+ # run then ends via whatever outer watcher exists). See README
259
+ # "Stall watchdog".
260
+ @watchdog_seconds = Float(ENV.fetch("TURBO_TESTS2_WATCHDOG_SECONDS", "900"))
256
261
  @watchdog_warned = false
257
262
  @last_message_monotonic = nil
258
263
 
@@ -279,10 +284,9 @@ module TurboTests
279
284
 
280
285
  def run
281
286
  parallel_tests_options = @parallel_options.reject { |key, _value| key == :only_group }
282
- tests_with_size = ParallelTests::RSpec::Runner.tests_with_size(@files, parallel_tests_options.merge(quiet: true))
283
287
  @num_processes = [
284
- ParallelTests.determine_number_of_processes(@count),
285
- tests_with_size.size
288
+ RuntimeGroups.process_count(@count),
289
+ @files.size
286
290
  ].min
287
291
 
288
292
  if @num_processes.zero?
@@ -292,10 +296,13 @@ module TurboTests
292
296
  end
293
297
 
294
298
  tests_in_groups =
295
- ParallelTests::RSpec::Runner.tests_in_groups(
299
+ RuntimeGroups::Grouping.in_groups(
296
300
  @files,
297
301
  @num_processes,
298
- **parallel_tests_options
302
+ group_by: parallel_tests_options[:group_by] || :runtime,
303
+ runtime_log: parallel_tests_options[:runtime_log],
304
+ allowed_missing: (parallel_tests_options[:allowed_missing_percent] || 100).to_f / 100,
305
+ unknown_runtime: parallel_tests_options[:unknown_runtime]
299
306
  )
300
307
  tests_in_groups = selected_groups(tests_in_groups) if @parallel_options[:only_group]
301
308
  @tests_in_groups = tests_in_groups
@@ -304,60 +311,72 @@ module TurboTests
304
311
  record_runtime: @record_runtime
305
312
  }
306
313
 
307
- ParallelTests.with_pid_file do
308
- exit_status = nil
309
- report_coverage = false
310
-
311
- @reporter.report(tests_in_groups) do |_reporter|
312
- old_signal = Signal.trap(:INT) { handle_interrupt }
313
-
314
- @wait_threads = tests_in_groups.map.with_index do |tests, process_id|
315
- start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
316
- end.compact
317
- @interrupt_handled = false
318
-
319
- handle_messages
320
-
321
- if @watchdog_fired
322
- # Workers were SIGKILLed by the watchdog; their reader threads end
323
- # at EOF and waiters return as the OS reaps them — but if a kill
324
- # failed (permissions, zombie, pid reuse) an unbounded join here
325
- # would hang the run anyway, defeating the watchdog. Bound every
326
- # join and treat any worker that still has not exited as failed.
327
- @threads.each do |thread|
328
- thread.join(FORCE_STOP_JOIN)
329
- thread.kill if thread.alive?
330
- end
331
- @wait_threads.each do |wait_thr|
332
- wait_thr.join(FORCE_STOP_JOIN)
333
- end
334
- flush_worker_output unless stream_worker_output?
335
- exit_status = 1
336
- else
337
- @threads.each(&:join)
314
+ pid_file_was_set = ENV.key?("PARALLEL_PID_FILE")
315
+ pid_file_before = ENV["PARALLEL_PID_FILE"]
316
+ begin
317
+ RuntimeGroups::PidFile.with do |pid_path|
318
+ ENV["PARALLEL_PID_FILE"] = pid_path
319
+
320
+ exit_status = nil
321
+ report_coverage = false
322
+
323
+ @reporter.report(tests_in_groups) do |_reporter|
324
+ old_signal = Signal.trap(:INT) { handle_interrupt }
325
+
326
+ @wait_threads = tests_in_groups.map.with_index do |tests, process_id|
327
+ start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
328
+ end.compact
329
+ @interrupt_handled = false
330
+
331
+ handle_messages
332
+
333
+ if @watchdog_fired
334
+ # Workers were SIGKILLed by the watchdog; their reader threads end
335
+ # at EOF and waiters return as the OS reaps them — but if a kill
336
+ # failed (permissions, zombie, pid reuse) an unbounded join here
337
+ # would hang the run anyway, defeating the watchdog. Bound every
338
+ # join and treat any worker that still has not exited as failed.
339
+ @threads.each do |thread|
340
+ thread.join(FORCE_STOP_JOIN)
341
+ thread.kill if thread.alive?
342
+ end
343
+ @wait_threads.each do |wait_thr|
344
+ wait_thr.join(FORCE_STOP_JOIN)
345
+ end
346
+ flush_worker_output unless stream_worker_output?
347
+ exit_status = 1
348
+ else
349
+ @threads.each(&:join)
338
350
 
339
- report_workers_without_results
351
+ report_workers_without_results
340
352
 
341
- report_failed_group(tests_in_groups) if @print_failed_group
353
+ report_failed_group(tests_in_groups) if @print_failed_group
342
354
 
343
- Signal.trap(:INT, old_signal)
355
+ Signal.trap(:INT, old_signal)
344
356
 
345
- statuses = @wait_threads.map(&:value)
357
+ statuses = @wait_threads.map(&:value)
346
358
 
347
- if @reporter.failed_examples.empty? && statuses.all?(&:success?)
348
- flush_successful_worker_output
349
- report_coverage = true
350
- exit_status = 0
351
- else
352
- flush_worker_output unless stream_worker_output?
353
- # From https://github.com/galtzo-floss/turbo_tests2/pull/20/
354
- exit_status = statuses.map(&:exitstatus).max
359
+ if @reporter.failed_examples.empty? && statuses.all?(&:success?)
360
+ flush_successful_worker_output
361
+ report_coverage = true
362
+ exit_status = 0
363
+ else
364
+ flush_worker_output unless stream_worker_output?
365
+ # From https://github.com/galtzo-floss/turbo_tests2/pull/20/
366
+ exit_status = statuses.map(&:exitstatus).max
367
+ end
355
368
  end
356
369
  end
357
- end
358
370
 
359
- flush_coverage_summary if report_coverage
360
- exit_status
371
+ flush_coverage_summary if report_coverage
372
+ exit_status
373
+ end
374
+ ensure
375
+ if pid_file_was_set
376
+ ENV["PARALLEL_PID_FILE"] = pid_file_before
377
+ else
378
+ ENV.delete("PARALLEL_PID_FILE")
379
+ end
361
380
  end
362
381
  end
363
382
 
@@ -447,7 +466,7 @@ module TurboTests
447
466
  FileUtils.mkdir_p(File.dirname(@runtime_log))
448
467
  [
449
468
  "--format",
450
- "ParallelTests::RSpec::RuntimeLogger",
469
+ "TurboTests::RuntimeLogger",
451
470
  "--out",
452
471
  @runtime_log
453
472
  ]
@@ -467,7 +486,7 @@ module TurboTests
467
486
  []
468
487
  end
469
488
 
470
- spec_opts = self.class.worker_spec_opts(ParallelTests::RSpec::Runner.send(:spec_opts))
489
+ spec_opts = self.class.worker_spec_opts(RuntimeGroups::Options.spec_opts)
471
490
 
472
491
  command = [
473
492
  *command_name,
@@ -556,11 +575,11 @@ module TurboTests
556
575
  end
557
576
 
558
577
  def track_parallel_pid(pid, pid_file_path = parallel_pid_file_path)
559
- ParallelTests::Pids.new(pid_file_path).add(pid) if pid && pid_file_path
578
+ RuntimeGroups::Pids.new(pid_file_path).add(pid) if pid && pid_file_path
560
579
  end
561
580
 
562
581
  def untrack_parallel_pid(pid, pid_file_path = parallel_pid_file_path)
563
- ParallelTests::Pids.new(pid_file_path).delete(pid) if pid && pid_file_path
582
+ RuntimeGroups::Pids.new(pid_file_path).delete(pid) if pid && pid_file_path
564
583
  end
565
584
 
566
585
  def start_copy_thread(src, process_id, stream)
@@ -821,10 +840,10 @@ module TurboTests
821
840
  end
822
841
 
823
842
  def tracked_pids
824
- path = ParallelTests.pid_file_path
843
+ path = parallel_pid_file_path
825
844
  return [] unless path && File.file?(path)
826
845
 
827
- File.read(path).lines.map { |l| l.strip.to_i }.reject(&:zero?)
846
+ RuntimeGroups::Pids.new(path).to_a
828
847
  rescue
829
848
  []
830
849
  end
@@ -866,16 +885,13 @@ module TurboTests
866
885
  if silence >= 900
867
886
  warn(
868
887
  "turbo_tests2: workers have been silent for #{silence.to_i}s. " \
869
- "In turbo_tests2 4.0 (and now when TURBO_TESTS2_WATCHDOG_SECONDS is set) a stall this long kills the " \
870
- "remaining workers and fails the run. To adopt the kill now set TURBO_TESTS2_WATCHDOG_SECONDS=900 (or " \
871
- "another value). To keep waiting indefinitely set TURBO_TESTS2_WATCHDOG_SECONDS=0; be aware that an " \
872
- "outer process watcher (a CI job timeout, a container orchestrator, or a local supervisor) will then " \
873
- "be what eventually ends the run, usually without a useful stall report. A single long test only " \
874
- "trips this in serial lanes (TURBO_TESTS2_MAX_PROCESSES=1); in parallel lanes other workers keep " \
875
- "messages flowing. Total silence in a PARALLEL lane usually means workers died rather than slowed " \
876
- "down — the most common cause is the kernel OOM killer (each worker is a real spawned process; on " \
877
- "JVM engines each is a full JVM whose default heap is 1/4 of RAM). Cap workers x per-worker memory: " \
878
- "TURBO_TESTS2_MAX_PROCESSES=2 with a capped heap (e.g. JRUBY_OPTS=\"-J-Xmx1280m\")."
888
+ "The stall watchdog is disabled (TURBO_TESTS2_WATCHDOG_SECONDS=0), so this run will keep waiting " \
889
+ "until some outer watcher ends it — a CI job timeout, a container orchestrator, or a local " \
890
+ "supervisor — usually without a useful stall report. Total silence in a PARALLEL lane usually means " \
891
+ "workers died rather than slowed down — the most common cause is the kernel OOM killer (each worker " \
892
+ "is a real spawned process; on JVM engines each is a full JVM whose default heap is 1/4 of RAM). " \
893
+ "Cap workers x per-worker memory: TURBO_TESTS2_MAX_PROCESSES=2 with a capped heap (e.g. " \
894
+ "JRUBY_OPTS=\"-J-Xmx1280m\"), or re-enable the watchdog to convert hangs into fast failures."
879
895
  )
880
896
  warned_silence = true
881
897
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/core"
4
+ require "runtime_groups"
5
+
6
+ # RSpec formatter that records per-file runtimes into a runtime log
7
+ # (TurboGroups path:seconds format, read back by RuntimeGroups::Grouping).
8
+ # Semantics retained from parallel_tests' RuntimeLogger:
9
+ # - only records when running in parallel (TEST_ENV_NUMBER is set)
10
+ # - accumulates time across nested example groups per top-level file
11
+ # - appends at dump time, sorted slowest-first
12
+ # Min-merging across re-runs happens in RuntimeGroups::RuntimeLog.read via
13
+ # last-write-wins per line, and worker groups are disjoint, so concurrent
14
+ # workers never write the same file.
15
+ class TurboTests::RuntimeLogger
16
+ RSpec::Core::Formatters.register self, :example_group_started, :example_group_finished, :start_dump
17
+
18
+ def initialize(output)
19
+ @output = output
20
+ @example_times = Hash.new(0)
21
+ @group_nesting = 0
22
+ @time = 0.0
23
+ end
24
+
25
+ def example_group_started(_notification)
26
+ @group_nesting += 1
27
+ @time = monotonic if @group_nesting == 1
28
+ end
29
+
30
+ def example_group_finished(notification)
31
+ @group_nesting -= 1
32
+ if @group_nesting.zero?
33
+ @example_times[notification.group.file_path] += monotonic - @time
34
+ end
35
+ end
36
+
37
+ def seed(*)
38
+ end
39
+
40
+ def dump_summary(*)
41
+ end
42
+
43
+ def dump_failures(*)
44
+ end
45
+
46
+ def dump_pending(*)
47
+ end
48
+
49
+ def message(*)
50
+ end
51
+
52
+ def close(*)
53
+ end
54
+
55
+ def start_dump(*)
56
+ return unless ENV["TEST_ENV_NUMBER"] # only record when running in parallel
57
+
58
+ log_path = output_path
59
+ return unless log_path
60
+
61
+ log = RuntimeGroups::RuntimeLog.new(log_path)
62
+ @example_times.sort_by(&:last).reverse_each do |file, time|
63
+ relative_path = file.sub(%r{^#{Regexp.escape(Dir.pwd)}/}, "").sub(%r{^\./}, "")
64
+ log.log(relative_path, [time, 0].max)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def monotonic
71
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ end
73
+
74
+ def output_path
75
+ @output.respond_to?(:path) ? @output.path : @output.to_s
76
+ end
77
+ end
@@ -4,7 +4,7 @@ module TurboTests
4
4
  # Version namespace for this gem.
5
5
  module Version
6
6
  # Current gem version.
7
- VERSION = "3.2.17"
7
+ VERSION = "4.0.0"
8
8
  end
9
9
  # Current gem version exposed at the traditional constant location.
10
10
  VERSION = Version::VERSION # Traditional Constant Location
data/lib/turbo_tests.rb CHANGED
@@ -7,10 +7,10 @@ require "json"
7
7
 
8
8
  require "rspec"
9
9
 
10
- require "parallel_tests"
11
- require "parallel_tests/rspec/runner"
10
+ require "runtime_groups"
12
11
 
13
12
  require "turbo_tests/reporter"
13
+ require "turbo_tests/runtime_logger"
14
14
  require "turbo_tests/runner"
15
15
  require "turbo_tests/json_rows_formatter"
16
16
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo_tests2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.17
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Illia
@@ -49,39 +49,39 @@ cert_chain:
49
49
  date: 1980-01-02 00:00:00.000000000 Z
50
50
  dependencies:
51
51
  - !ruby/object:Gem::Dependency
52
- name: parallel_tests
52
+ name: rspec
53
53
  requirement: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - ">="
56
56
  - !ruby/object:Gem::Version
57
- version: 3.3.0
58
- - - "<"
59
- - !ruby/object:Gem::Version
60
- version: '6'
57
+ version: '3.10'
61
58
  type: :runtime
62
59
  prerelease: false
63
60
  version_requirements: !ruby/object:Gem::Requirement
64
61
  requirements:
65
62
  - - ">="
66
63
  - !ruby/object:Gem::Version
67
- version: 3.3.0
68
- - - "<"
69
- - !ruby/object:Gem::Version
70
- version: '6'
64
+ version: '3.10'
71
65
  - !ruby/object:Gem::Dependency
72
- name: rspec
66
+ name: runtime_groups
73
67
  requirement: !ruby/object:Gem::Requirement
74
68
  requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '0.1'
75
72
  - - ">="
76
73
  - !ruby/object:Gem::Version
77
- version: '3.10'
74
+ version: 0.1.3
78
75
  type: :runtime
79
76
  prerelease: false
80
77
  version_requirements: !ruby/object:Gem::Requirement
81
78
  requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.1'
82
82
  - - ">="
83
83
  - !ruby/object:Gem::Version
84
- version: '3.10'
84
+ version: 0.1.3
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: version_gem
87
87
  requirement: !ruby/object:Gem::Requirement
@@ -284,11 +284,10 @@ dependencies:
284
284
  - - ">="
285
285
  - !ruby/object:Gem::Version
286
286
  version: 2.0.15
287
- description: "\U0001F680 turbo_tests2` is an RSpec parallel test runner built on `parallel_tests`.
288
- It uses `parallel_tests` for process counts, file grouping, and runtime balancing,
289
- then replaces interleaved worker output with incremental summarized RSpec reporting.
290
- Source code of `turbo_test2` gem is based on Discourse and Rubygems work in this
291
- area (see README.md)."
287
+ description: "\U0001F680 turbo_tests2` is an RSpec parallel test runner. It uses runtime_groups
288
+ for process counts, pid bookkeeping, file grouping, and runtime balancing, and replaces
289
+ interleaved worker output with incremental summarized RSpec reporting. Source code
290
+ of `turbo_test2` gem is based on Discourse and Rubygems work in this area (see README.md)."
292
291
  email:
293
292
  - floss@galtzo.com
294
293
  executables:
@@ -305,6 +304,7 @@ files:
305
304
  - lib/turbo_tests/json_rows_formatter.rb
306
305
  - lib/turbo_tests/reporter.rb
307
306
  - lib/turbo_tests/runner.rb
307
+ - lib/turbo_tests/runtime_logger.rb
308
308
  - lib/turbo_tests/shim.rb
309
309
  - lib/turbo_tests/utils/paths.rb
310
310
  - lib/turbo_tests/version.rb
@@ -316,10 +316,10 @@ licenses:
316
316
  - MIT
317
317
  metadata:
318
318
  homepage_uri: https://turbo-tests2.galtzo.com
319
- source_code_uri: https://github.com/galtzo-floss/turbo_tests2/tree/v3.2.17
320
- changelog_uri: https://github.com/galtzo-floss/turbo_tests2/blob/v3.2.17/CHANGELOG.md
319
+ source_code_uri: https://github.com/galtzo-floss/turbo_tests2/tree/v4.0.0
320
+ changelog_uri: https://github.com/galtzo-floss/turbo_tests2/blob/v4.0.0/CHANGELOG.md
321
321
  bug_tracker_uri: https://github.com/galtzo-floss/turbo_tests2/issues
322
- documentation_uri: https://www.rubydoc.info/gems/turbo_tests2/3.2.17
322
+ documentation_uri: https://www.rubydoc.info/gems/turbo_tests2/4.0.0
323
323
  funding_uri: https://github.com/sponsors/pboling
324
324
  wiki_uri: https://github.com/galtzo-floss/turbo_tests2/wiki
325
325
  news_uri: https://www.railsbling.com/tags/turbo_tests2
@@ -328,8 +328,8 @@ metadata:
328
328
  rubygems_mfa_required: 'true'
329
329
  rdoc_options:
330
330
  - "--title"
331
- - "turbo_tests2 - \U0001F680 RSpec parallel test runner built on `parallel_tests`,
332
- with incremental summarized output"
331
+ - "turbo_tests2 - \U0001F680 RSpec parallel test runner with incremental summarized
332
+ output"
333
333
  - "--main"
334
334
  - README.md
335
335
  - "--exclude"
@@ -352,6 +352,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
352
352
  requirements: []
353
353
  rubygems_version: 4.0.20
354
354
  specification_version: 4
355
- summary: "\U0001F680 RSpec parallel test runner built on `parallel_tests`, with incremental
356
- summarized output"
355
+ summary: "\U0001F680 RSpec parallel test runner with incremental summarized output"
357
356
  test_files: []
metadata.gz.sig CHANGED
Binary file