polyrun 2.2.9 → 3.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -8
  3. data/README.md +7 -6
  4. data/docs/SETUP_PROFILE.md +2 -1
  5. data/lib/polyrun/cli/ci_shard_run_command.rb +31 -0
  6. data/lib/polyrun/cli/default_run.rb +10 -3
  7. data/lib/polyrun/cli/help.rb +5 -3
  8. data/lib/polyrun/cli/run_shards_command.rb +17 -0
  9. data/lib/polyrun/cli.rb +5 -2
  10. data/lib/polyrun/coverage/collector.rb +21 -49
  11. data/lib/polyrun/coverage/collector_blob.rb +53 -0
  12. data/lib/polyrun/data/parallel_provisioning.rb +2 -2
  13. data/lib/polyrun/hooks/dsl.rb +6 -6
  14. data/lib/polyrun/{minitest.rb → minitest_support.rb} +4 -4
  15. data/lib/polyrun/partition/paths.rb +95 -48
  16. data/lib/polyrun/partition/suite.rb +10 -6
  17. data/lib/polyrun/quick/config.rb +56 -0
  18. data/lib/polyrun/quick/example_group.rb +49 -4
  19. data/lib/polyrun/quick/example_runner.rb +30 -7
  20. data/lib/polyrun/quick/matchers.rb +6 -3
  21. data/lib/polyrun/quick/matchers_predicates.rb +122 -0
  22. data/lib/polyrun/quick/reporter.rb +40 -6
  23. data/lib/polyrun/quick/runner.rb +30 -16
  24. data/lib/polyrun/reporting/rspec_failure_fragment_formatter.rb +1 -1
  25. data/lib/polyrun/reporting/rspec_junit.rb +2 -2
  26. data/lib/polyrun/{rspec → rspec_support}/active_record_timeout_recovery.rb +1 -1
  27. data/lib/polyrun/{rspec → rspec_support}/example_debug.rb +1 -1
  28. data/lib/polyrun/{rspec → rspec_support}/example_debug_instrumentation.rb +1 -1
  29. data/lib/polyrun/{rspec → rspec_support}/sharded_formatter_compat.rb +1 -1
  30. data/lib/polyrun/{rspec.rb → rspec_support.rb} +12 -12
  31. data/lib/polyrun/sus_support.rb +55 -0
  32. data/lib/polyrun/templates/POLYRUN.md +1 -1
  33. data/lib/polyrun/timing/rspec_example_formatter.rb +2 -2
  34. data/lib/polyrun/version.rb +1 -1
  35. data/lib/polyrun/worker_ping.rb +9 -6
  36. data/sig/polyrun/{minitest.rbs → minitest_support.rbs} +1 -1
  37. data/sig/polyrun/{rspec.rbs → rspec_support.rbs} +1 -1
  38. data/sig/polyrun/sus_support.rbs +7 -0
  39. metadata +14 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd372c59e18ffd48e6e53d42240cb4893fd165af0399ca6c50ca8a4d14a74b0e
4
- data.tar.gz: 9aaf900fe002008e213c1f8b48b9c7867ad685db79946d72760f40a92c7052f6
3
+ metadata.gz: a596cf2a374f9aa883e0f8e449a93a02c303cfc4378d9a0ef2e2b7d45f66d776
4
+ data.tar.gz: bf7047e62fc1f77a0105e1aac00ff1fca794dd74d47a3aef5838c7cb32aff348
5
5
  SHA512:
6
- metadata.gz: f3ba035a9df88c6722deaf3c632a213fdc11fa94554b260136fe3500e8fbe3e26825b6ef9f59732a07db3d82cf562a09553a14b677f0e8beb0ab50574775806b
7
- data.tar.gz: aac4874eb9000f920691c8796c1ee35e91ed7555b4da84d9d9e1cb2fbce8783c3a6de8ef7dd82aaa02f941e55291a04c172a87fcba20ae4cf10f2a45b611a01e
6
+ metadata.gz: 39862a1442ecb1704ee9e4416841a70905b94a88fd6e191c4ddcfce8ad8ca02993c99802963e72c175e634e20e39595835e87b083379ebffb317117cddc5604b
7
+ data.tar.gz: 84a45dd40b1b48fcf9c71ba81103e958ab48c23832b069d3fc2f30be96e7897d37468e39677cb9089a8981390a63037165bd9cc1c8cadca2a0dbae841e84b2e6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.0.0 (2026-11-12)
4
+
5
+ - Rename `Polyrun::RSpec` to `Polyrun::RSpecSupport` and `Polyrun::Minitest` to `Polyrun::MinitestSupport` (requires `polyrun/rspec_support` / `polyrun/minitest_support`). Hard cut: old constants and require paths are removed (RFC-0007).
6
+ - Fix `Polyrun::Reporting::RspecJunit.install!` raising `NoMethodError` on `configure` when RSpec wiring was already required (`::RSpec` qualification).
7
+ - Add Sus as a first-class suite: `partition.suite: sus`, `test/**/*.rb` globs, auto-detect via `config/sus.rb`, `parallel-sus`, `ci-shard-sus`, and `Polyrun::SusSupport` (RFC-0008).
8
+ - Treat Covered-style `COVERAGE` (non-falsey) as coverage-wanted for `Coverage::Collector` / SusSupport `install_coverage!` (RFC-0008).
9
+ - Classify Sus-style `test/**/*.rb` paths as `:sus` instead of `:quick` during suite inference.
10
+ - Extend `Polyrun::Quick`: `config/polyrun_quick.rb` suite hooks and `add_load_path`, `around`, error-aware `after`, `with` / `subject`, `be` / `be_a` / `raise_exception`, slowest-example summary (RFC-0009).
11
+
3
12
  ## 2.2.9 (2026-11-07)
4
13
 
5
14
  - Add `Polyrun::Benchmark::Pressure` for load benches: wall/CPU/RSS/heap/GC plus optional storage and network byte deltas, with target ceilings for iterative pressure runs.
@@ -86,8 +95,8 @@
86
95
 
87
96
  - Add `Polyrun::WorkerOutput` for per-shard worker stdout/stderr routing (`POLYRUN_WORKER_OUTPUT_ROUTING=1` or `POLYRUN_WORKER_LOG_DIR`); optional prefixed TTY echo (`POLYRUN_WORKER_OUTPUT_PREFIX=0` for log-only).
88
97
  - Add `partition.paths_build.exclude_prefixes` to omit path prefixes from `build-paths` / `start` while keeping files runnable outside polyrun.
89
- - Add `Polyrun::RSpec.install_example_debug!`, `install_example_timeout!`, `install_example_rails_logging!`, `install_example_prosopite!` (`POLYRUN_EXAMPLE_DEBUG=1` and related env vars).
90
- - Add `Polyrun::RSpec.install_sharded_formatter_compat!` for progress formatters (e.g. Fuubar) under `POLYRUN_SHARD_*`.
98
+ - Add `Polyrun::RSpecSupport.install_example_debug!`, `install_example_timeout!`, `install_example_rails_logging!`, `install_example_prosopite!` (`POLYRUN_EXAMPLE_DEBUG=1` and related env vars).
99
+ - Add `Polyrun::RSpecSupport.install_sharded_formatter_compat!` for progress formatters (e.g. Fuubar) under `POLYRUN_SHARD_*`.
91
100
 
92
101
  ## 2.1.2 (2026-07-05)
93
102
 
@@ -100,7 +109,7 @@
100
109
  ## 2.1.0 (2026-07-03)
101
110
 
102
111
  - Add experimental per-example spec quality (`Polyrun::SpecQuality`): `POLYRUN_SPEC_QUALITY=1`, worker JSONL fragments, `merge-spec-quality`, `report-spec-quality`, and `run-shards --merge-spec-quality`.
103
- - Add `Polyrun::RSpec.install_spec_quality!`, `Polyrun::Minitest.install_spec_quality!`, and Quick example hooks; `polyrun init --profile spec-quality`.
112
+ - Add `Polyrun::RSpecSupport.install_spec_quality!`, `Polyrun::MinitestSupport.install_spec_quality!`, and Quick example hooks; `polyrun init --profile spec-quality`.
104
113
  - Add `Polyrun::Coverage::ExampleDiff` for per-example line hit deltas from `Coverage.peek_result`; record per-example factory counts via `Data::FactoryCounts`.
105
114
  - Report zero-hit examples, line churn, hot lines, shard attribution, optional partition hints (`report-spec-quality --plan`), optional CPU/GC/IO profiling (stdlib), and CI gate thresholds in `config/polyrun_spec_quality.yml`.
106
115
  - Document spec quality in `docs/SPEC_QUALITY.md`.
@@ -127,13 +136,13 @@
127
136
 
128
137
  - Add `run-shards --worker-timeout SEC` and `POLYRUN_WORKER_TIMEOUT_SEC` (wall time per worker since spawn); stop stuck workers; record exit 124 for that shard.
129
138
  - Add `run-shards --worker-idle-timeout SEC` and `POLYRUN_WORKER_IDLE_TIMEOUT_SEC`; parent reads monotonic timestamps from `POLYRUN_WORKER_PING_FILE`; record exit 125 when the last ping is stale. Idle applies only after a valid positive ping (use wall timeout until the first ping).
130
- - Add `Polyrun::WorkerPing` (`ping!`, `ensure_interval_ping_thread!` when `POLYRUN_WORKER_PING_THREAD`). Add `Polyrun::RSpec.install_worker_ping!` and `Polyrun::Minitest.install_worker_ping!`; Polyrun Quick calls `WorkerPing.ping!` around each example. Parent creates ping paths under `tmp/polyrun/` and unlinks files after workers exit.
139
+ - Add `Polyrun::WorkerPing` (`ping!`, `ensure_interval_ping_thread!` when `POLYRUN_WORKER_PING_THREAD`). Add `Polyrun::RSpecSupport.install_worker_ping!` and `Polyrun::MinitestSupport.install_worker_ping!`; Polyrun Quick calls `WorkerPing.ping!` around each example. Parent creates ping paths under `tmp/polyrun/` and unlinks files after workers exit.
131
140
  - Poll every live shard worker together when timeouts are enabled so idle and wall limits apply to all children, not only the first waiter.
132
141
  - Split parallel worker teardown into `RunShardsParallelWait` and `RunShardsWorkerInterrupt`; keep spawn logic in `RunShardsParallelChildren`.
133
142
  - Add `Polyrun::Log.orchestration_warn`; when `POLYRUN_ORCHESTRATION_STDERR=1`, copy one line to process `$stderr` if `Log.stderr` is not the same object (custom/null sinks).
134
143
  - Wire `env_worker_timeout_sec` / `env_worker_idle_timeout_sec` into `ci-shard-run` plan context. Rescue `Interrupt` around `after_suite` in `run-shards` and `ci-shard` orchestration where suite hooks run.
135
144
  - In `Polyrun::Hooks#run_phase`, rescue `Interrupt` for Ruby DSL and shell hook phases (return 130).
136
- - Document worker timeout, idle ping, and `POLYRUN_ORCHESTRATION_STDERR` in `polyrun help`. Add `sig/polyrun/worker_ping.rbs` and extend `Polyrun::RSpec` / `Polyrun::Minitest` installer signatures.
145
+ - Document worker timeout, idle ping, and `POLYRUN_ORCHESTRATION_STDERR` in `polyrun help`. Add `sig/polyrun/worker_ping.rbs` and extend `Polyrun::RSpecSupport` / `Polyrun::MinitestSupport` installer signatures.
137
146
 
138
147
  ## 1.4.2 (2026-04-24)
139
148
 
@@ -145,7 +154,7 @@
145
154
  ## 1.4.1 (2026-04-16)
146
155
 
147
156
  - Add `polyrun merge-failures` and `run-shards --merge-failures` / `--merge-failures-output` / `--merge-failures-format`; merge per-worker JSONL under `tmp/polyrun_failures/polyrun-failure-fragment-*.jsonl` (or RSpec JSON via `-i`). Run merge after all workers exit, including when a shard failed (`--merge-coverage` still runs only after all shards succeed).
148
- - Add `Polyrun::Reporting::FailureMerge`, `Polyrun::RSpec.install_failure_fragments!`, and `Polyrun::Reporting::RspecFailureFragmentFormatter`; parent sets `POLYRUN_FAILURE_FRAGMENTS=1` on workers when merge-failures is enabled.
157
+ - Add `Polyrun::Reporting::FailureMerge`, `Polyrun::RSpecSupport.install_failure_fragments!`, and `Polyrun::Reporting::RspecFailureFragmentFormatter`; parent sets `POLYRUN_FAILURE_FRAGMENTS=1` on workers when merge-failures is enabled.
149
158
  - Add optional `reporting:` in `polyrun.yml` and `Polyrun::Config#reporting` for merge-failures toggles and paths; honor `POLYRUN_MERGE_FAILURES`, `POLYRUN_MERGED_FAILURES_OUT`, `POLYRUN_MERGED_FAILURES_FORMAT`; set `POLYRUN_MERGED_FAILURES_PATH` for `after_suite` when merge wrote a file.
150
159
  - On bad JSONL or non-RSpec JSON, raise `Polyrun::Error` with path and line where applicable; `merge-failures` exits 1 without a full stack trace; a failed merge after successful workers forces `run-shards` exit 1.
151
160
  - Fix `run_shards_plan_ready_log` to take `cfg` so debug logging for merge-failures does not raise `NameError`.
@@ -180,11 +189,11 @@
180
189
  ## 1.1.0 (2026-04-15)
181
190
 
182
191
  - Add `ci-shard-run` / `ci-shard-rspec` for matrix-style sharding (one job per `POLYRUN_SHARD_INDEX` / `POLYRUN_SHARD_TOTAL`): resolve paths via the same plan as `polyrun plan`, then `exec` the given command with this shard’s paths (unlike `run-shards`, which fans out multiple workers on one host).
183
- - Add experimental per-example partition timing: `partition.timing_granularity` / `--timing-granularity` (`file` default, `example` for `path:line` items), `POLYRUN_TIMING_GRANULARITY`, merged timing JSON with `absolute_path:line` keys, `TimingKeys.load_costs_json_file`, constraints matching pins/globs on the file part of locators, queue init support, and optional `Polyrun::Timing::RSpecExampleFormatter` plus `Polyrun::RSpec.install_example_timing!`.
192
+ - Add experimental per-example partition timing: `partition.timing_granularity` / `--timing-granularity` (`file` default, `example` for `path:line` items), `POLYRUN_TIMING_GRANULARITY`, merged timing JSON with `absolute_path:line` keys, `TimingKeys.load_costs_json_file`, constraints matching pins/globs on the file part of locators, queue init support, and optional `Polyrun::Timing::RSpecExampleFormatter` plus `Polyrun::RSpecSupport.install_example_timing!`.
184
193
  - Add `Polyrun::ProcessStdio.inherit_stdio_spawn_wait` and `spawn_wait` for subprocesses with inherited stdio (or temp-file capture when `silent: true`) to avoid Open3 pipe-thread noise on interrupt; used by prepare (shell / custom assets), `Prepare::Assets.precompile!`, and `Provision.prepare_template!` (`bin/rails db:prepare`). On failure, `db:prepare` / `assets:precompile` embed captured stdout/stderr in `Polyrun::Error` (truncated when huge).
185
194
  - Refactor `polyrun plan` around `plan_command_compute_manifest` and `plan_command_build_manifest`; `cmd_plan` output stays aligned with `plan_command_compute_manifest` (tests guard drift).
186
195
  - `TimingKeys.load_costs_json_file` accepts optional `root:` for key normalization; warns when two JSON keys normalize to the same entry with different seconds; `TimingKeys.canonical_file_path` / `normalize_locator` resolve directory symlinks so `/var/…` and `/private/var/…` (macOS) map to one key.
187
- - `Polyrun::RSpec.install_example_timing!(output_path:)` no longer sets `ENV` when an explicit path is passed; formatter uses `timing_output_path` (override or `ENV` / default filename).
196
+ - `Polyrun::RSpecSupport.install_example_timing!(output_path:)` no longer sets `ENV` when an explicit path is passed; formatter uses `timing_output_path` (override or `ENV` / default filename).
188
197
  - Fix noisy `IOError` / broken-pipe behavior when interrupting long-running prepare / Rails subprocesses that previously used `Open3.capture3`.
189
198
 
190
199
  ## 1.0.0 (2026-04-14)
data/README.md CHANGED
@@ -10,11 +10,11 @@ Running tests in parallel across processes still requires a single merged covera
10
10
 
11
11
  Polyrun provides:
12
12
 
13
- - Orchestration: `plan`, `run-shards`, and `parallel-rspec` / `parallel-minitest` / `parallel-quick` (run-shards plus merge-coverage), with an optional on-disk queue and constraints for file lists and load balancing. Bare `polyrun` (no subcommand) picks the suite from `partition.suite`, else `partition.paths_file`, else filesystem auto-detect. For **GitHub Actions-style matrix sharding** (one job per global shard), use `ci-shard-run -- …` (any test runner) or `ci-shard-rspec`—not `run-shards` / `parallel-*`, which fan out N workers on one machine.
13
+ - Orchestration: `plan`, `run-shards`, and `parallel-rspec` / `parallel-minitest` / `parallel-sus` / `parallel-quick` (run-shards plus merge-coverage), with an optional on-disk queue and constraints for file lists and load balancing. Bare `polyrun` (no subcommand) picks the suite from `partition.suite`, else `partition.paths_file`, else filesystem auto-detect (Sus when `config/sus.rb` and `test/**/*.rb` exist). For **GitHub Actions-style matrix sharding** (one job per global shard), use `ci-shard-run -- …` (any test runner), `ci-shard-rspec`, or `ci-shard-sus`—not `run-shards` / `parallel-*`, which fan out N workers on one machine.
14
14
  - Coverage: merge SimpleCov-compatible JSON fragments; emit JSON, LCOV, Cobertura, or console summaries (you can drop separate SimpleCov merge plugins for this path).
15
15
  - CI reporting: JUnit XML from RSpec JSON; slow-file reports from merged timing JSON.
16
16
  - Parallel hygiene: asset digest markers, SQL snapshots, YAML fixture batches, and DB URL or shard helpers aligned with `POLYRUN_SHARD_*`.
17
- - Optional **`polyrun quick`**: `Polyrun::Quick` — nested `describe`, `it` / `test`, `before` / `after`, `let` / `let!`, `expect(x).to …` matchers, `assert_*` (Minitest-like), and optional **`Polyrun::Quick.capybara!`** (extends `Capybara::DSL` when the **capybara** gem is loaded). Stdlib-only in Polyrun itself. Coverage uses the same `Polyrun::Coverage::Collector` path as RSpec when `POLYRUN_COVERAGE=1` or `config/polyrun_coverage.yml` is present (not when `POLYRUN_COVERAGE_DISABLE=1`).
17
+ - Optional **`polyrun quick`**: `Polyrun::Quick` — nested `describe` / `with`, `it` / `test`, `before` / `after` / `around`, `let` / `let!` / `subject`, `expect(x).to …` / `expect { }.to raise_exception`, `assert_*`, optional `config/polyrun_quick.rb` (`before_suite` / `after_suite` / `add_load_path`), and optional **`Polyrun::Quick.capybara!`**. Stdlib-only in Polyrun itself. Coverage when `POLYRUN_COVERAGE=1`, Covered-style `COVERAGE`, or `config/polyrun_coverage.yml` + `POLYRUN_QUICK_COVERAGE=1` (not when `POLYRUN_COVERAGE_DISABLE=1`).
18
18
  - No runtime gems in the gemspec: stdlib and vendored pieces only.
19
19
 
20
20
  Capybara and Playwright stay in your application; Polyrun does not replace browser drivers.
@@ -93,6 +93,7 @@ bin/polyrun ci-shard-run -- bundle exec rspec # CI matrix: shard plan + append
93
93
  bin/polyrun ci-shard-rspec # same as ci-shard-run -- bundle exec rspec
94
94
  bin/polyrun parallel-rspec --workers 5 # run-shards + merge-coverage (default: bundle exec rspec)
95
95
  bin/polyrun parallel-minitest --workers 5 # same; default rails test or ruby -I test
96
+ bin/polyrun parallel-sus --workers 5 # same; default bundle exec sus (OS processes; use with config/sus.rb)
96
97
  bin/polyrun parallel-quick --workers 5 # same; default bundle exec polyrun quick
97
98
  bin/polyrun run-shards --workers 5 --merge-coverage -- bundle exec rspec
98
99
  bin/polyrun merge-coverage -i cov1.json -i cov2.json -o merged.json --format json,lcov,cobertura,console
@@ -122,8 +123,8 @@ Runnable Rails demos (multi-DB, Vite, Capybara, Docker, Postgres sketches) live
122
123
 
123
124
  That single require loads the CLI and core library **without** loading RSpec or Minitest. Optional integrations (use only what your app needs):
124
125
 
125
- - `require "polyrun/rspec"` — `Polyrun::RSpec` registers `ParallelProvisioning` in `before(:suite)` (your app must use RSpec).
126
- - `require "polyrun/minitest"` — `Polyrun::Minitest` is a thin alias for `ParallelProvisioning.run_suite_hooks!` (does not `require "minitest"`).
126
+ - `require "polyrun/rspec_support"` — `Polyrun::RSpecSupport` registers `ParallelProvisioning` in `before(:suite)` (your app must use RSpec).
127
+ - `require "polyrun/minitest_support"` — `Polyrun::MinitestSupport` is a thin alias for `ParallelProvisioning.run_suite_hooks!` (does not `require "minitest"`).
127
128
  - `require "polyrun/reporting/rspec_junit"` — `Polyrun::Reporting::RspecJunit` adds RSpec’s JSON formatter and writes JUnit on exit; RSpec is loaded only inside `RspecJunit.install!`.
128
129
 
129
130
  | API | Purpose |
@@ -141,8 +142,8 @@ That single require loads the CLI and core library **without** loading RSpec or
141
142
  | `Polyrun::Data::FactoryInstrumentation` | Opt-in FactoryBot patch → `FactoryCounts` (after `require "factory_bot"`). |
142
143
  | `Polyrun::Data::SqlSnapshot` | PostgreSQL `pg_dump` / `psql` snapshots under `spec/fixtures/sql_snapshots/`. |
143
144
  | `Polyrun::Data::FactoryCounts` | Factory/build counters + summary text. |
144
- | `Polyrun::RSpec` (`require "polyrun/rspec"`) | `install_parallel_provisioning!` → `before(:suite)` hooks. |
145
- | `Polyrun::Minitest` (`require "polyrun/minitest"`) | `install_parallel_provisioning!` → same as `ParallelProvisioning.run_suite_hooks!` (no Minitest gem dependency). |
145
+ | `Polyrun::RSpecSupport` (`require "polyrun/rspec_support"`) | `install_parallel_provisioning!` → `before(:suite)` hooks. |
146
+ | `Polyrun::MinitestSupport` (`require "polyrun/minitest_support"`) | `install_parallel_provisioning!` → same as `ParallelProvisioning.run_suite_hooks!` (no Minitest gem dependency). |
146
147
  | `Polyrun::Reporting::RspecJunit` (`require "polyrun/reporting/rspec_junit"`) | CI: RSpec JSON formatter + JUnit from `install!` (RSpec loaded only there). |
147
148
  | `Polyrun::Prepare::Assets` | Digest trees, marker file, `assets:precompile`. |
148
149
  | `Polyrun::Database::Shard` | Shard env map, `%{shard}` DB names, URL path suffix for `postgres://`, `mysql2://`, `mongodb://`, etc. |
@@ -44,6 +44,7 @@ Rule: anything expensive (compile, `yarn`, Playwright download) belongs in prepa
44
44
  | Field | Options |
45
45
  |--------|---------|
46
46
  | Plain glob | `partition.paths_build.all_glob: spec/**/*_spec.rb` and empty or minimal `stages` |
47
+ | Sus suite | `partition.suite: sus` (or auto via `config/sus.rb`); default glob `test/**/*.rb`; CLI `parallel-sus` / `ci-shard-sus`; wire `polyrun/sus_support` in `config/sus.rb`. Prefer Polyrun collector fragments for shard merge; do not enable Covered::Sus in the same process |
47
48
  | Ordered stages | `partition.paths_build.stages`: regex (e.g. slow integration first) or `sort_by_substring_order` for stable ordering |
48
49
  | Exclude prefixes | `partition.paths_build.exclude_prefixes`: omit paths (e.g. `spec/benchmark/`) from `paths_file` while keeping them runnable outside polyrun |
49
50
 
@@ -77,7 +78,7 @@ Orchestration tracing stays on `DEBUG=1` / `POLYRUN_DEBUG=1`. Per-example toolin
77
78
  | `DEBUG_LOG_LEVEL` | Rails / ActiveRecord logger severity: `debug`, `info`, `warn`, `error`, `fatal`, or Ruby Logger integers (`0` debug … `4` fatal); default `debug` |
78
79
  | `RSPEC_EXAMPLE_TIMEOUT_SEC` | Per-example timeout (disabled while example debug is on); on timeout, ActiveRecord connection pools are disconnected so the next example gets fresh connections (any adapter) |
79
80
 
80
- In `spec_helper` / `spec/support`: `require "polyrun/rspec"` then `Polyrun::RSpec.install_sharded_formatter_compat!` (silences per-worker seed/summary/pending under `POLYRUN_SHARD_*`), `install_example_debug!`, `install_example_rails_logging!`, `install_example_prosopite!`, `install_example_timeout!`. Pair with `install_worker_ping!` for idle timeouts.
81
+ In `spec_helper` / `spec/support`: `require "polyrun/rspec_support"` then `Polyrun::RSpecSupport.install_sharded_formatter_compat!` (silences per-worker seed/summary/pending under `POLYRUN_SHARD_*`), `install_example_debug!`, `install_example_rails_logging!`, `install_example_prosopite!`, `install_example_timeout!`. Pair with `install_worker_ping!` for idle timeouts.
81
82
 
82
83
  ## 6. Coverage and CI reports
83
84
 
@@ -141,6 +141,37 @@ module Polyrun
141
141
  Polyrun::Log.warn "polyrun ci-shard-rspec: #{paths.size} path(s) → #{shard_processes} process(es) on this host (NxM: matrix jobs × local processes)"
142
142
  ci_shard_run_fanout!(ctx)
143
143
  end
144
+
145
+ # Same as +ci-shard-run -- bundle exec sus+ with an optional second segment for Sus-only flags:
146
+ # +polyrun ci-shard-sus [plan options] [-- [sus args]]+
147
+ def cmd_ci_shard_sus(argv, config_path)
148
+ sep = argv.index("--")
149
+ plan_argv = sep ? argv[0...sep] : argv
150
+ sus_argv = sep ? argv[(sep + 1)..] : []
151
+
152
+ cfg = Polyrun::Config.load(path: config_path || ENV["POLYRUN_CONFIG"])
153
+ pc = cfg.partition
154
+ shard_processes, perr = ci_shard_parse_shard_processes!(plan_argv, pc)
155
+ return perr if perr
156
+
157
+ shard_processes, err = ci_shard_normalize_shard_processes(shard_processes)
158
+ return err if err
159
+
160
+ paths, code = ci_shard_planned_paths!(plan_argv, config_path, command_label: "ci-shard-sus")
161
+ return code if code != 0
162
+
163
+ cmd = ["bundle", "exec", "sus", *sus_argv]
164
+
165
+ if shard_processes <= 1
166
+ return ci_shard_run_single!(cmd, paths, cfg, pc, config_path)
167
+ end
168
+
169
+ ctx = ci_shard_fanout_context(
170
+ cfg: cfg, pc: pc, paths: paths, shard_processes: shard_processes, cmd: cmd, config_path: config_path
171
+ )
172
+ Polyrun::Log.warn "polyrun ci-shard-sus: #{paths.size} path(s) → #{shard_processes} process(es) on this host (NxM: matrix jobs × local processes)"
173
+ ci_shard_run_fanout!(ctx)
174
+ end
144
175
  end
145
176
  end
146
177
  end
@@ -29,6 +29,11 @@ module Polyrun
29
29
  return code if code != 0
30
30
 
31
31
  cmd_parallel_minitest([], config_path)
32
+ when :sus
33
+ code = start_bootstrap!(cfg, [], config_path)
34
+ return code if code != 0
35
+
36
+ cmd_parallel_sus([], config_path)
32
37
  when :quick
33
38
  code = start_bootstrap!(cfg, [], config_path)
34
39
  return code if code != 0
@@ -69,7 +74,7 @@ module Polyrun
69
74
 
70
75
  suite = Polyrun::Partition::Paths.infer_suite_from_paths(expanded)
71
76
  if suite == :invalid
72
- Polyrun::Log.warn "polyrun: mixing _spec.rb and _test.rb paths in one run is not supported"
77
+ Polyrun::Log.warn "polyrun: mixing path families in one run is not supported"
73
78
  return 2
74
79
  end
75
80
  if suite.nil?
@@ -88,6 +93,8 @@ module Polyrun
88
93
  cmd_start(combined, config_path)
89
94
  when :minitest
90
95
  cmd_parallel_minitest(combined, config_path)
96
+ when :sus
97
+ cmd_parallel_sus(combined, config_path)
91
98
  when :quick
92
99
  cmd_parallel_quick(combined, config_path)
93
100
  else
@@ -119,10 +126,10 @@ module Polyrun
119
126
  if File.directory?(abs)
120
127
  spec = Dir.glob(File.join(abs, "**", "*_spec.rb")).sort
121
128
  test = Dir.glob(File.join(abs, "**", "*_test.rb")).sort
122
- quick = Dir.glob(File.join(abs, "**", "*.rb")).sort.reject do |f|
129
+ other = Dir.glob(File.join(abs, "**", "*.rb")).sort.reject do |f|
123
130
  File.basename(f).end_with?("_spec.rb", "_test.rb")
124
131
  end
125
- spec + test + quick
132
+ spec + test + other
126
133
  elsif /[*?\[]/.match?(p)
127
134
  Dir.glob(abs).sort
128
135
  elsif File.file?(abs)
@@ -5,7 +5,7 @@ module Polyrun
5
5
  Polyrun::Log.puts <<~HELP
6
6
  usage: polyrun [global options] [<command> | <paths...>]
7
7
 
8
- With no command, runs parallel tests for the resolved suite: partition.suite when set; else suite inferred from partition.paths_file; else filesystem auto-detect (RSpec under spec/ before Minitest under test/, then Polyrun Quick). If the first argument is a known subcommand name, it is dispatched. Otherwise, path-like tokens (optionally with run-shards flags such as --workers) shard those files in parallel; see commands below.
8
+ With no command, runs parallel tests for the resolved suite: partition.suite when set; else suite inferred from partition.paths_file; else filesystem auto-detect (RSpec under spec/, then Sus when config/sus.rb + test/**/*.rb, then Minitest *_test.rb, then Polyrun Quick). If the first argument is a known subcommand name, it is dispatched. Otherwise, path-like tokens (optionally with run-shards flags such as --workers) shard those files in parallel; see commands below.
9
9
 
10
10
  global:
11
11
  -c, --config PATH polyrun.yml path (or POLYRUN_CONFIG)
@@ -14,7 +14,7 @@ module Polyrun
14
14
  -h, --help
15
15
 
16
16
  Trace timing (stderr): DEBUG=1 or POLYRUN_DEBUG=1
17
- Coverage: POLYRUN_COVERAGE=1 (or config/polyrun_coverage.yml + POLYRUN_QUICK_COVERAGE=1); POLYRUN_COVERAGE_DISABLE=1 skips; POLYRUN_COVERAGE_BRANCHES=1 for branch data in fragments; POLYRUN_COVERAGE_VERBOSE=1 for per-worker and merged console summaries
17
+ Coverage: POLYRUN_COVERAGE=1 or Covered-style COVERAGE=<report>; (or config/polyrun_coverage.yml + POLYRUN_QUICK_COVERAGE=1); POLYRUN_COVERAGE_DISABLE=1 skips; POLYRUN_COVERAGE_BRANCHES=1 for branch data in fragments; POLYRUN_COVERAGE_VERBOSE=1 for per-worker and merged console summaries
18
18
  Benchmark profiles: POLYRUN_BENCH=1 (stdout); JSON sidecar in tmp/benchmarks/; POLYRUN_BENCH_FORMATS=csv,markdown for extra exports; report-benchmark / bench
19
19
  Hooks shell output: POLYRUN_HOOKS_VERBOSE=1 or -v / POLYRUN_VERBOSE=1 (or DEBUG); failures always print
20
20
  Merge profiling (stderr): POLYRUN_PROFILE_MERGE=1 (or verbose / DEBUG)
@@ -45,15 +45,17 @@ module Polyrun
45
45
  run-shards fan out N parallel OS processes (POLYRUN_SHARD_*; not Ruby threads); optional --merge-coverage / --merge-failures / --merge-spec-quality
46
46
  parallel-rspec run-shards + merge-coverage (defaults to: bundle exec rspec after --); no start prepare/DB bootstrap
47
47
  parallel-minitest run-shards + merge-coverage (defaults to: bundle exec rails test or ruby -I test after --); no start prepare/DB bootstrap
48
+ parallel-sus run-shards + merge-coverage (defaults to: bundle exec sus after --); no start prepare/DB bootstrap
48
49
  parallel-quick run-shards + merge-coverage (defaults to: bundle exec polyrun quick after --); no start prepare/DB bootstrap
49
50
  start parallel-rspec; auto-runs prepare (shell/assets) and db:setup-* when polyrun.yml configures them; legacy script/build_spec_paths.rb if paths_build absent
50
51
  ci-shard-run CI matrix: build-paths + plan for POLYRUN_SHARD_INDEX / POLYRUN_SHARD_TOTAL (or config), then run your command with that shard's paths after --; optional --shard-processes M or --workers M (POLYRUN_SHARD_PROCESSES; not POLYRUN_WORKERS) for N×M jobs × processes on this host
51
52
  ci-shard-rspec same as ci-shard-run -- bundle exec rspec; optional --shard-processes / --workers / -- [rspec-only flags]
53
+ ci-shard-sus same as ci-shard-run -- bundle exec sus; optional --shard-processes / --workers / -- [sus-only flags]
52
54
  build-paths write partition.paths_file from partition.paths_build (same as auto step before plan/run-shards)
53
55
  init write a starter polyrun.yml or POLYRUN.md from built-in templates (see docs/SETUP_PROFILE.md)
54
56
  queue file-backed batch queue: init (optional --shard/--total etc. as plan, then claim/ack/reclaim/status --json)
55
57
  run-queue init queue and run N workers that claim batches until drained
56
- quick quick test runner (describe/it, before/after, let, expect…to, assert_*; optional capybara!)
58
+ quick quick test runner (describe/with/it, before/after/around, let/subject, expect…to, assert_*; config/polyrun_quick.rb; optional capybara!)
57
59
  hook run <phase> run one shell hook from polyrun.yml hooks: (e.g. before_suite); optional --shard/--total
58
60
  report-coverage write all coverage formats from one JSON file
59
61
  report-junit RSpec JSON or Polyrun testcase JSON → JUnit XML, CSV, or Markdown
@@ -72,6 +72,23 @@ module Polyrun
72
72
  cmd_run_shards(combined, config_path)
73
73
  end
74
74
 
75
+ # Same as parallel-rspec but runs +bundle exec sus+ after +--+ (one Sus process per shard).
76
+ # Does not run +start+ prepare/database bootstrap (use +polyrun start+ or bare +polyrun+ for that).
77
+ # Prefer OS-process shards here when workers need isolated databases; Sus's own +sus-parallel+ is in-process threads.
78
+ def cmd_parallel_sus(argv, config_path)
79
+ sep = argv.index("--")
80
+ combined =
81
+ if sep
82
+ head = argv[0...sep]
83
+ tail = argv[sep..]
84
+ head + ["--merge-coverage"] + tail
85
+ else
86
+ argv + ["--merge-coverage", "--", "bundle", "exec", "sus"]
87
+ end
88
+ Polyrun::Debug.log_kv(parallel_sus: "combined argv", argv: combined)
89
+ cmd_run_shards(combined, config_path)
90
+ end
91
+
75
92
  def minitest_parallel_cmd
76
93
  rails_bin = File.expand_path("bin/rails", Dir.pwd)
77
94
  if File.file?(rails_bin)
data/lib/polyrun/cli.rb CHANGED
@@ -30,14 +30,15 @@ module Polyrun
30
30
  class CLI
31
31
  CI_SHARD_COMMANDS = {
32
32
  "ci-shard-run" => :cmd_ci_shard_run,
33
- "ci-shard-rspec" => :cmd_ci_shard_rspec
33
+ "ci-shard-rspec" => :cmd_ci_shard_rspec,
34
+ "ci-shard-sus" => :cmd_ci_shard_sus
34
35
  }.freeze
35
36
 
36
37
  # Keep in sync with +dispatch_cli_command_subcommands+ (+when+ branches). Used for implicit path routing.
37
38
  DISPATCH_SUBCOMMAND_NAMES = %w[
38
39
  plan prepare merge-coverage merge-failures merge-spec-quality report-coverage report-junit report-timing report-spec-quality report-benchmark bench
39
40
  env config merge-timing db:setup-template db:setup-shard db:clone-shards
40
- run-shards parallel-rspec parallel-minitest parallel-quick start build-paths init queue run-queue quick hook validate doctor
41
+ run-shards parallel-rspec parallel-minitest parallel-sus parallel-quick start build-paths init queue run-queue quick hook validate doctor
41
42
  ].freeze
42
43
 
43
44
  # First argv token that is a normal subcommand (not a path); if argv[0] is not here but looks like paths, run implicit parallel.
@@ -219,6 +220,8 @@ module Polyrun
219
220
  cmd_parallel_rspec(argv, config_path)
220
221
  when "parallel-minitest"
221
222
  cmd_parallel_minitest(argv, config_path)
223
+ when "parallel-sus"
224
+ cmd_parallel_sus(argv, config_path)
222
225
  when "parallel-quick"
223
226
  cmd_parallel_quick(argv, config_path)
224
227
  when "start"
@@ -96,11 +96,13 @@ module Polyrun
96
96
  instance_variable_defined?(:@config) && @config
97
97
  end
98
98
 
99
- # Whether +polyrun quick+ should call {Rails.start!} before loading quick files: not disabled,
100
- # and (+POLYRUN_COVERAGE=1+ or (+config/polyrun_coverage.yml+ exists and +POLYRUN_QUICK_COVERAGE=1+)).
99
+ # Whether +polyrun quick+ / Sus helpers should call {Rails.start!} / {start!} before loading tests:
100
+ # not disabled, and (+POLYRUN_COVERAGE=1+ or Covered-style +COVERAGE+ set, or
101
+ # (+config/polyrun_coverage.yml+ exists and +POLYRUN_QUICK_COVERAGE=1+)).
101
102
  def self.coverage_requested_for_quick?(root = Dir.pwd)
102
103
  return false if disabled?
103
- return true if %w[1 true yes].include?(ENV["POLYRUN_COVERAGE"]&.to_s&.downcase)
104
+ return true if polyrun_coverage_env_enabled?
105
+ return true if covered_env_enabled?
104
106
 
105
107
  path = File.join(File.expand_path(root), "config", "polyrun_coverage.yml")
106
108
  return false unless File.file?(path)
@@ -110,6 +112,21 @@ module Polyrun
110
112
  %w[1 true yes].include?(ENV["POLYRUN_QUICK_COVERAGE"]&.to_s&.downcase)
111
113
  end
112
114
 
115
+ # +POLYRUN_COVERAGE=1/true/yes+.
116
+ def self.polyrun_coverage_env_enabled?
117
+ %w[1 true yes].include?(ENV["POLYRUN_COVERAGE"]&.to_s&.downcase)
118
+ end
119
+
120
+ # Covered sets +COVERAGE+ to a report name (e.g. +PartialSummary+) or +true+.
121
+ # Empty / +0+ / +false+ / +no+ / +off+ stay off.
122
+ def self.covered_env_enabled?
123
+ value = ENV["COVERAGE"].to_s
124
+ return false if value.empty?
125
+ return false if %w[0 false no off].include?(value.downcase)
126
+
127
+ true
128
+ end
129
+
113
130
  # When +POLYRUN_SHARD_TOTAL+ > 1, each worker only writes the JSON fragment; merged reports
114
131
  # (+merge-coverage+ / +report-coverage+) are authoritative. Set +POLYRUN_COVERAGE_WORKER_FORMATS=1+
115
132
  # to force per-worker formatter output (debug only; duplicates work N times).
@@ -138,54 +155,9 @@ module Polyrun
138
155
  end
139
156
  m
140
157
  end
141
-
142
- # Normalizes stdlib Coverage.result to merge-compatible file entries (lines; branches when collected).
143
- def result_to_blob(raw)
144
- out = {}
145
- raw.each do |path, cov|
146
- next unless cov.is_a?(Hash)
147
-
148
- lines = cov[:lines] || cov["lines"]
149
- next unless lines.is_a?(Array)
150
-
151
- entry = {"lines" => lines.map { |x| x }}
152
- br = cov[:branches] || cov["branches"]
153
- entry["branches"] = br if br
154
- out[path.to_s] = entry
155
- end
156
- out
157
- end
158
-
159
- def normalize_blob_paths(blob, root)
160
- root = File.expand_path(root)
161
- blob.each_with_object({}) do |(path, entry), acc|
162
- acc[File.expand_path(path.to_s, root)] = entry
163
- end
164
- end
165
-
166
- def normalize_groups(groups)
167
- return nil if groups.nil?
168
-
169
- h = groups.is_a?(Hash) ? groups : {}
170
- return nil if h.empty?
171
-
172
- h.transform_keys(&:to_s).transform_values(&:to_s)
173
- end
174
-
175
- def keep_under_root(blob, root, track_under)
176
- return blob if track_under.nil? || track_under.empty?
177
-
178
- root = File.expand_path(root)
179
- prefixes = track_under.map { |d| File.join(root, d) }
180
- blob.each_with_object({}) do |(path, entry), acc|
181
- p = path.to_s
182
- next unless prefixes.any? { |pre| p == pre || p.start_with?(pre + "/") }
183
-
184
- acc[path] = entry
185
- end
186
- end
187
158
  end
188
159
  end
189
160
  end
190
161
 
162
+ require_relative "collector_blob"
191
163
  require_relative "collector_finish"
@@ -0,0 +1,53 @@
1
+ module Polyrun
2
+ module Coverage
3
+ module Collector
4
+ module_function
5
+
6
+ # Normalizes stdlib Coverage.result to merge-compatible file entries (lines; branches when collected).
7
+ def result_to_blob(raw)
8
+ out = {}
9
+ raw.each do |path, cov|
10
+ next unless cov.is_a?(Hash)
11
+
12
+ lines = cov[:lines] || cov["lines"]
13
+ next unless lines.is_a?(Array)
14
+
15
+ entry = {"lines" => lines.map { |x| x }}
16
+ br = cov[:branches] || cov["branches"]
17
+ entry["branches"] = br if br
18
+ out[path.to_s] = entry
19
+ end
20
+ out
21
+ end
22
+
23
+ def normalize_blob_paths(blob, root)
24
+ root = File.expand_path(root)
25
+ blob.each_with_object({}) do |(path, entry), acc|
26
+ acc[File.expand_path(path.to_s, root)] = entry
27
+ end
28
+ end
29
+
30
+ def normalize_groups(groups)
31
+ return nil if groups.nil?
32
+
33
+ h = groups.is_a?(Hash) ? groups : {}
34
+ return nil if h.empty?
35
+
36
+ h.transform_keys(&:to_s).transform_values(&:to_s)
37
+ end
38
+
39
+ def keep_under_root(blob, root, track_under)
40
+ return blob if track_under.nil? || track_under.empty?
41
+
42
+ root = File.expand_path(root)
43
+ prefixes = track_under.map { |d| File.join(root, d) }
44
+ blob.each_with_object({}) do |(path, entry), acc|
45
+ p = path.to_s
46
+ next unless prefixes.any? { |pre| p == pre || p.start_with?(pre + "/") }
47
+
48
+ acc[path] = entry
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -12,8 +12,8 @@ module Polyrun
12
12
  # # In spec_helper after configure:
13
13
  # Polyrun::Data::ParallelProvisioning.run_suite_hooks!
14
14
  #
15
- # Or use {Polyrun::RSpec.install_parallel_provisioning!} (+before(:suite)+) or {Polyrun::Minitest.install_parallel_provisioning!}
16
- # (+require+ +polyrun/minitest+ from +test/test_helper.rb+).
15
+ # Or use {Polyrun::RSpecSupport.install_parallel_provisioning!} (+before(:suite)+) or {Polyrun::MinitestSupport.install_parallel_provisioning!}
16
+ # (+require+ +polyrun/minitest_support+ from +test/test_helper.rb+).
17
17
  module ParallelProvisioning
18
18
  class Configuration
19
19
  attr_accessor :serial_hook, :parallel_worker_hook
@@ -85,6 +85,10 @@ module Polyrun
85
85
  end
86
86
  end
87
87
 
88
+ # Load-time constant: `||=` can create two monitors.
89
+ CACHE_MU = Monitor.new
90
+ private_constant :CACHE_MU
91
+
88
92
  class << self
89
93
  # @return [Registry, nil]
90
94
  def load_registry(path)
@@ -94,7 +98,7 @@ module Polyrun
94
98
  return nil unless File.file?(full)
95
99
 
96
100
  mtime = File.mtime(full)
97
- cache_mu.synchronize do
101
+ CACHE_MU.synchronize do
98
102
  hit = registry_cache[full]
99
103
  return hit[:registry] if hit && hit[:mtime] == mtime
100
104
 
@@ -108,16 +112,12 @@ module Polyrun
108
112
  end
109
113
 
110
114
  def clear_cache!
111
- cache_mu.synchronize { registry_cache.clear }
115
+ CACHE_MU.synchronize { registry_cache.clear }
112
116
  end
113
117
 
114
118
  private
115
119
 
116
120
  # rubocop:disable ThreadSafety/ClassInstanceVariable -- single-threaded hook load; Monitor protects cache
117
- def cache_mu
118
- @cache_mu ||= Monitor.new
119
- end
120
-
121
121
  def registry_cache
122
122
  @registry_cache ||= {}
123
123
  end
@@ -1,12 +1,12 @@
1
1
  require_relative "../polyrun"
2
2
 
3
3
  module Polyrun
4
- # Optional Minitest-oriented wiring (require +polyrun/minitest+ explicitly).
4
+ # Optional Minitest-oriented wiring (require +polyrun/minitest_support+ explicitly).
5
5
  #
6
6
  # Does not load the +minitest+ gem. Call {install_parallel_provisioning!} from +test/test_helper.rb+
7
7
  # after Rails / DB configuration (same timing as a direct call to
8
8
  # {Data::ParallelProvisioning.run_suite_hooks!}).
9
- module Minitest
9
+ module MinitestSupport
10
10
  module WorkerPingTestHook
11
11
  def setup
12
12
  Polyrun::WorkerPing.ping!(location: polyrun_minitest_location)
@@ -35,12 +35,12 @@ module Polyrun
35
35
  Polyrun::Data::ParallelProvisioning.run_suite_hooks!
36
36
  end
37
37
 
38
- # Same ping semantics as {RSpec.install_worker_ping!}: +ping!+ at test +setup+ and +teardown+.
38
+ # Same ping semantics as {RSpecSupport.install_worker_ping!}: +ping!+ at test +setup+ and +teardown+.
39
39
  # Requires +minitest+ to be loaded first (+Minitest::Test+ defined).
40
40
  def install_worker_ping!
41
41
  require_relative "worker_ping"
42
42
  unless defined?(::Minitest::Test)
43
- Polyrun::Log.warn "polyrun minitest: install_worker_ping! skipped (load minitest/autorun or minitest/test first)"
43
+ Polyrun::Log.warn "polyrun minitest_support: install_worker_ping! skipped (load minitest/autorun or minitest/test first)"
44
44
  return
45
45
  end
46
46