polyrun 1.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 +7 -0
- data/CODE_OF_CONDUCT.md +31 -0
- data/CONTRIBUTING.md +84 -0
- data/LICENSE +21 -0
- data/README.md +140 -0
- data/SECURITY.md +27 -0
- data/bin/polyrun +6 -0
- data/docs/SETUP_PROFILE.md +106 -0
- data/lib/polyrun/cli/coverage_commands.rb +150 -0
- data/lib/polyrun/cli/coverage_merge_io.rb +124 -0
- data/lib/polyrun/cli/database_commands.rb +149 -0
- data/lib/polyrun/cli/env_commands.rb +43 -0
- data/lib/polyrun/cli/helpers.rb +113 -0
- data/lib/polyrun/cli/init_command.rb +99 -0
- data/lib/polyrun/cli/plan_command.rb +134 -0
- data/lib/polyrun/cli/prepare_command.rb +71 -0
- data/lib/polyrun/cli/prepare_recipe.rb +77 -0
- data/lib/polyrun/cli/queue_command.rb +101 -0
- data/lib/polyrun/cli/quick_command.rb +13 -0
- data/lib/polyrun/cli/report_commands.rb +94 -0
- data/lib/polyrun/cli/run_shards_command.rb +88 -0
- data/lib/polyrun/cli/run_shards_plan_boot_phases.rb +91 -0
- data/lib/polyrun/cli/run_shards_plan_options.rb +45 -0
- data/lib/polyrun/cli/run_shards_planning.rb +124 -0
- data/lib/polyrun/cli/run_shards_run.rb +168 -0
- data/lib/polyrun/cli/start_bootstrap.rb +99 -0
- data/lib/polyrun/cli/timing_command.rb +31 -0
- data/lib/polyrun/cli.rb +184 -0
- data/lib/polyrun/config.rb +61 -0
- data/lib/polyrun/coverage/cobertura_zero_lines.rb +32 -0
- data/lib/polyrun/coverage/collector.rb +184 -0
- data/lib/polyrun/coverage/collector_finish.rb +95 -0
- data/lib/polyrun/coverage/filter.rb +22 -0
- data/lib/polyrun/coverage/formatter.rb +115 -0
- data/lib/polyrun/coverage/merge/formatters.rb +181 -0
- data/lib/polyrun/coverage/merge/formatters_html.rb +55 -0
- data/lib/polyrun/coverage/merge.rb +127 -0
- data/lib/polyrun/coverage/merge_fragment_meta.rb +47 -0
- data/lib/polyrun/coverage/merge_merge_two.rb +117 -0
- data/lib/polyrun/coverage/rails.rb +128 -0
- data/lib/polyrun/coverage/reporting.rb +41 -0
- data/lib/polyrun/coverage/result.rb +18 -0
- data/lib/polyrun/coverage/track_files.rb +141 -0
- data/lib/polyrun/data/cached_fixtures.rb +122 -0
- data/lib/polyrun/data/factory_counts.rb +35 -0
- data/lib/polyrun/data/factory_instrumentation.rb +50 -0
- data/lib/polyrun/data/fixtures.rb +68 -0
- data/lib/polyrun/data/parallel_provisioning.rb +93 -0
- data/lib/polyrun/data/snapshot.rb +84 -0
- data/lib/polyrun/database/clone_shards.rb +81 -0
- data/lib/polyrun/database/provision.rb +72 -0
- data/lib/polyrun/database/shard.rb +63 -0
- data/lib/polyrun/database/url_builder/connection/infer.rb +49 -0
- data/lib/polyrun/database/url_builder/connection/url_builders.rb +43 -0
- data/lib/polyrun/database/url_builder/connection.rb +191 -0
- data/lib/polyrun/database/url_builder/template_prepare.rb +21 -0
- data/lib/polyrun/database/url_builder.rb +160 -0
- data/lib/polyrun/debug.rb +81 -0
- data/lib/polyrun/env/ci.rb +65 -0
- data/lib/polyrun/log.rb +70 -0
- data/lib/polyrun/minitest.rb +17 -0
- data/lib/polyrun/partition/constraints.rb +69 -0
- data/lib/polyrun/partition/hrw.rb +33 -0
- data/lib/polyrun/partition/min_heap.rb +64 -0
- data/lib/polyrun/partition/paths.rb +28 -0
- data/lib/polyrun/partition/paths_build.rb +128 -0
- data/lib/polyrun/partition/plan.rb +189 -0
- data/lib/polyrun/partition/plan_lpt.rb +49 -0
- data/lib/polyrun/partition/plan_sharding.rb +48 -0
- data/lib/polyrun/partition/stable_shuffle.rb +18 -0
- data/lib/polyrun/prepare/artifacts.rb +40 -0
- data/lib/polyrun/prepare/assets.rb +57 -0
- data/lib/polyrun/queue/file_store.rb +199 -0
- data/lib/polyrun/queue/file_store_pending.rb +48 -0
- data/lib/polyrun/quick/assertions.rb +32 -0
- data/lib/polyrun/quick/errors.rb +6 -0
- data/lib/polyrun/quick/example_group.rb +66 -0
- data/lib/polyrun/quick/example_runner.rb +93 -0
- data/lib/polyrun/quick/matchers.rb +156 -0
- data/lib/polyrun/quick/reporter.rb +42 -0
- data/lib/polyrun/quick/runner.rb +180 -0
- data/lib/polyrun/quick.rb +1 -0
- data/lib/polyrun/railtie.rb +7 -0
- data/lib/polyrun/reporting/junit.rb +125 -0
- data/lib/polyrun/reporting/junit_emit.rb +58 -0
- data/lib/polyrun/reporting/rspec_junit.rb +39 -0
- data/lib/polyrun/rspec.rb +15 -0
- data/lib/polyrun/templates/POLYRUN.md +45 -0
- data/lib/polyrun/templates/ci_matrix.polyrun.yml +14 -0
- data/lib/polyrun/templates/minimal_gem.polyrun.yml +13 -0
- data/lib/polyrun/templates/rails_prepare.polyrun.yml +31 -0
- data/lib/polyrun/timing/merge.rb +35 -0
- data/lib/polyrun/timing/summary.rb +25 -0
- data/lib/polyrun/version.rb +3 -0
- data/lib/polyrun.rb +58 -0
- data/polyrun.gemspec +37 -0
- data/sig/polyrun/cli.rbs +6 -0
- data/sig/polyrun/config.rbs +20 -0
- data/sig/polyrun/debug.rbs +12 -0
- data/sig/polyrun/log.rbs +12 -0
- data/sig/polyrun/minitest.rbs +5 -0
- data/sig/polyrun/quick.rbs +19 -0
- data/sig/polyrun/rspec.rbs +5 -0
- data/sig/polyrun.rbs +11 -0
- metadata +288 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ca0c4afee7136f1cdf3ffe319d55c08457323119102a0a4d01478b02df91ce31
|
|
4
|
+
data.tar.gz: d468805765ee230bed530fdce182f189c0521e3dff7516be5c75bc620c362fc8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 98e04c5388e51da737bb73407497830d82bf61daabdfd77e9024fd2d973663dfed934c87691c6f2e864d7642e457a1eaaff27e3cf3f79bfa102a92ec9a0034aa
|
|
7
|
+
data.tar.gz: b6c407039df97914ed8e07a04bdb0dc4c8e044d9b0ffd3802e44e600a6c4002fe22b8673266263c5981bb4bde90246c992defb589eb3b785952b0c56f67c263e
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our pledge
|
|
4
|
+
|
|
5
|
+
We pledge to make participation in the Polyrun community a harassment-free experience for everyone. We operate on principles of mutual respect, privacy, and authentic engagement. We value substantive contributions and clarity on intentions.
|
|
6
|
+
|
|
7
|
+
## Our standards
|
|
8
|
+
|
|
9
|
+
Examples of behavior that contributes to a positive environment include:
|
|
10
|
+
|
|
11
|
+
- Authenticity: engaging with genuine curiosity and admitting uncertainty rather than feigning knowledge.
|
|
12
|
+
- Responsible innovation: taking full responsibility for any content or code contributed, whether manually written or generated by automation tools.
|
|
13
|
+
- Gentle correction: responding politely to errors. We view mistakes as opportunities for learning, provided they are addressed with humility.
|
|
14
|
+
- Inclusive language: using language that welcomes diverse perspectives and respects the privacy and identity of all participants.
|
|
15
|
+
|
|
16
|
+
Examples of unacceptable behavior include:
|
|
17
|
+
|
|
18
|
+
- Harassment: public or private harassment, trolling, or insulting comments.
|
|
19
|
+
- Weaponized complexity: using jargon or overwhelming volume (including automated spam) to silence others.
|
|
20
|
+
- Publishing private information: sharing others' data or personal context without explicit permission.
|
|
21
|
+
|
|
22
|
+
## Artificial intelligence and automation
|
|
23
|
+
|
|
24
|
+
In accordance with our commitment to collective awareness:
|
|
25
|
+
|
|
26
|
+
- Contributors are responsible for the accuracy and security of any AI-generated artifacts they submit.
|
|
27
|
+
- "The AI wrote it" is not a valid excuse for introducing bugs, security vulnerabilities, or bias.
|
|
28
|
+
|
|
29
|
+
## Enforcement
|
|
30
|
+
|
|
31
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@kiskolabs.com. All complaints will be reviewed and investigated promptly and fairly.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing to Polyrun
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bundle install
|
|
7
|
+
bundle exec appraisal install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Tests
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bundle exec rspec
|
|
14
|
+
# or
|
|
15
|
+
bundle exec rake spec
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Coverage merge performance (large synthetic payloads):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle exec rake bench_merge
|
|
22
|
+
# or: ruby benchmark/merge_coverage.rb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run the suite under alternate Ruby constraints (see `Appraisals` and `gemfiles/`):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bundle exec appraisal ruby32 rspec
|
|
29
|
+
bundle exec appraisal ruby34 rspec
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Linting
|
|
33
|
+
|
|
34
|
+
[RuboCop](https://rubocop.org/) with [Standard](https://github.com/standardrb/standard) style, plus `rubocop-rspec` and `rubocop-thread_safety`. Project-specific cop tweaks and metric `Exclude` lists live in `.rubocop.yml`.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bundle exec rubocop
|
|
38
|
+
bundle exec rubocop -a # safe autocorrect
|
|
39
|
+
bundle exec rake rubocop
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## RBS
|
|
43
|
+
|
|
44
|
+
Type signatures live under `sig/` and ship with the gem. Validate them after changes:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
bundle exec rake rbs
|
|
48
|
+
# equivalent: bundle exec rbs -I sig validate
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Keep `require "polyrun"` free of RSpec/Minitest: optional wiring stays in `polyrun/rspec`, `polyrun/minitest`, and `polyrun/reporting/rspec_junit` (see README).
|
|
52
|
+
|
|
53
|
+
[Trunk](https://trunk.io/) aggregates RuboCop, YAML, Markdown, shellcheck, and more (see `.trunk/trunk.yaml`):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
trunk check
|
|
57
|
+
trunk fmt
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
CI runs `rake ci` (RSpec + RuboCop). Optional Trunk workflow: `.github/workflows/trunk.yml`.
|
|
61
|
+
|
|
62
|
+
## Adopting Polyrun in other repos
|
|
63
|
+
|
|
64
|
+
- [docs/SETUP_PROFILE.md](docs/SETUP_PROFILE.md) — agent or human checklist (CI model A vs B, database, prepare).
|
|
65
|
+
- `bundle exec polyrun init --list` — lists starter `polyrun.yml` and `POLYRUN.md` templates (see `examples/templates/README.md`).
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
Runnable demos live under `examples/`. After changing the gem, smoke them:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd examples && ./bin/ci_prepare
|
|
73
|
+
cd examples/simple/simple_demo && RAILS_ENV=test bundle exec rspec
|
|
74
|
+
# optional: complex polyrepo demo (multi-DB + three Vite clients + RSpec E2E)
|
|
75
|
+
# cd examples/complex/polyrepo_demo && RAILS_ENV=test bin/rails db:prepare && bundle exec rspec
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See `examples/README.md`.
|
|
79
|
+
|
|
80
|
+
## Pull requests
|
|
81
|
+
|
|
82
|
+
- One logical change per PR when possible.
|
|
83
|
+
- Add or update specs for behavior changes.
|
|
84
|
+
- Run `bundle exec rake ci` before pushing.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrei Makarov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Polyrun
|
|
2
|
+
|
|
3
|
+
Ruby gem for parallel test runs, merged coverage (SimpleCov-compatible JSON to JSON, LCOV, Cobertura, or console output), CI reporting (JUnit, timing), and parallel-test hygiene (fixtures, snapshots, per-shard databases, asset preparation). Ship it as one development dependency: no runtime gem dependencies beyond the standard library and vendored code.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Running tests in parallel across processes still requires a single merged coverage report, stable shard assignment, isolated databases per worker so rows are not shared, and reliable timing data for cost-based splits—without wiring together many small tools and shell scripts.
|
|
8
|
+
|
|
9
|
+
Polyrun provides:
|
|
10
|
+
|
|
11
|
+
- Orchestration: `plan`, `run-shards`, and `parallel-rspec` (run-shards plus merge-coverage), with an optional on-disk queue and constraints for file lists and load balancing.
|
|
12
|
+
- Coverage: merge SimpleCov-compatible JSON fragments; emit JSON, LCOV, Cobertura, or console summaries (you can drop separate SimpleCov merge plugins for this path).
|
|
13
|
+
- CI reporting: JUnit XML from RSpec JSON; slow-file reports from merged timing JSON.
|
|
14
|
+
- Parallel hygiene: asset digest markers, SQL snapshots, YAML fixture batches, and DB URL or shard helpers aligned with `POLYRUN_SHARD_*`.
|
|
15
|
+
- 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`).
|
|
16
|
+
- No runtime gems in the gemspec: stdlib and vendored pieces only.
|
|
17
|
+
|
|
18
|
+
Capybara and Playwright stay in your application; Polyrun does not replace browser drivers.
|
|
19
|
+
|
|
20
|
+
## How?
|
|
21
|
+
|
|
22
|
+
1. Add the gem (path or RubyGems) and `require "polyrun"` where you integrate—for example coverage merge in CI or prepare hooks.
|
|
23
|
+
2. Add a `polyrun.yml` beside the app, or pass `-c` to point at one. Configure `partition` (paths, shard index and total, strategy), and optionally `databases` (Postgres template and `shard_db_pattern`), `prepare`, and `coverage`. If you use `partition.paths_build`, Polyrun can write `partition.paths_file` (for example `spec/spec_paths.txt`) from globs and ordered stages—substring priorities for integration specs, or a regex stage for “Rails-heavy files first”—without a per-project Ruby script. That step runs before `plan` and `run-shards`. Use `bin/polyrun build-paths` to refresh the paths file only.
|
|
24
|
+
3. Run prepare once before fan-out—for example `script/ci_prepare` for Vite or webpack builds, and `Polyrun::Prepare::Assets` digest markers. See `examples/TESTING_REQUIREMENTS.md`.
|
|
25
|
+
4. Run workers with `bin/polyrun run-shards --workers N -- bundle exec rspec`: N separate OS processes, each running RSpec with its own file list from `partition.paths_file`, or `spec/spec_paths.txt`, or else `spec/**/*_spec.rb`. Stderr shows where paths came from; after a successful multi-worker run it reminds you to run merge-coverage unless you use `parallel-rspec` or `run-shards --merge-coverage`.
|
|
26
|
+
5. Merge artifacts with `bin/polyrun merge-coverage` on `coverage/polyrun-fragment-*.json` (one fragment per `POLYRUN_SHARD_INDEX` when coverage is on), or use `bin/polyrun parallel-rspec` or `run-shards --merge-coverage` so Polyrun runs merge for you. Optional: `merge-timing`, `report-timing`, `report-junit`.
|
|
27
|
+
|
|
28
|
+
Quick CLI samples:
|
|
29
|
+
|
|
30
|
+
If the current directory already has `polyrun.yml` or `config/polyrun.yml`, you can omit `-c` (same as `Config.load` default discovery). Pass `-c PATH` or set `POLYRUN_CONFIG` when the file lives elsewhere or uses another name.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bin/polyrun version
|
|
34
|
+
bin/polyrun build-paths # write spec/spec_paths.txt from partition.paths_build (uses polyrun.yml in cwd)
|
|
35
|
+
bin/polyrun parallel-rspec --workers 5 # run-shards + merge-coverage (default: bundle exec rspec)
|
|
36
|
+
bin/polyrun run-shards --workers 5 --merge-coverage -- bundle exec rspec
|
|
37
|
+
bin/polyrun merge-coverage -i cov1.json -i cov2.json -o merged.json --format json,lcov,cobertura,console
|
|
38
|
+
bin/polyrun env --shard 0 --total 4 # print DATABASE_URL exports from polyrun.yml in cwd
|
|
39
|
+
bin/polyrun init --list
|
|
40
|
+
bin/polyrun init --profile gem -o polyrun.yml # starter YAML; see docs/SETUP_PROFILE.md
|
|
41
|
+
bin/polyrun quick # Polyrun::Quick examples under spec/polyrun_quick/ or test/polyrun_quick/
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Adopting Polyrun (setup profile and scaffolds)
|
|
45
|
+
|
|
46
|
+
- [docs/SETUP_PROFILE.md](docs/SETUP_PROFILE.md) — Checklist for project type (gem, Rails, Appraisal), parallelism target (one CI job with N workers, matrix shards, or a single non-matrix runner), database layout, prepare, spec order, coverage, and CI model A (single runner with `parallel-rspec`) versus model B (matrix plus a merge-coverage job). Treat `polyrun.yml` as the contract; bin scripts and `database.yml` are adapters.
|
|
47
|
+
- `polyrun init` writes a starter `polyrun.yml` or `POLYRUN.md` from built-in templates (`--profile gem`, `rails`, `ci-matrix`, `doc`). Profiles are listed in `examples/templates/README.md`.
|
|
48
|
+
|
|
49
|
+
Runnable Rails demos (multi-DB, Vite, Capybara, Docker, Postgres sketches) live in `examples/README.md`. For development (tests, RuboCop, Appraisal), see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Library (after `require "polyrun"`)
|
|
54
|
+
|
|
55
|
+
That single require loads the CLI and core library **without** loading RSpec or Minitest. Optional integrations (use only what your app needs):
|
|
56
|
+
|
|
57
|
+
- `require "polyrun/rspec"` — `Polyrun::RSpec` registers `ParallelProvisioning` in `before(:suite)` (your app must use RSpec).
|
|
58
|
+
- `require "polyrun/minitest"` — `Polyrun::Minitest` is a thin alias for `ParallelProvisioning.run_suite_hooks!` (does not `require "minitest"`).
|
|
59
|
+
- `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!`.
|
|
60
|
+
|
|
61
|
+
| API | Purpose |
|
|
62
|
+
|-----|---------|
|
|
63
|
+
| `Polyrun::Quick` (`require "polyrun/quick"`) | Nested `describe`; `it` / `test`; `before` / `after`; `let` / `let!`; `expect(x).to eq` / `be_truthy` / `be_falsey` / `match` / `include`; `assert_*`. Call `Polyrun::Quick.capybara!` after `require "capybara"` (and configure `Capybara.app` in your app) to use `visit`, `page`, etc. Run: `polyrun quick` (defaults: `spec/polyrun_quick/**/*.rb`, `test/polyrun_quick/**/*.rb`). |
|
|
64
|
+
| `Polyrun::Log` | Swappable stderr/stdout for all CLI and library messages. Set `Polyrun.stderr` / `Polyrun.stdout` (or `Polyrun::Log.stderr` / `stdout`) to an `IO`, `StringIO`, or Ruby `Logger`. `Polyrun::Log.reset_io!` clears custom sinks. |
|
|
65
|
+
| `Polyrun::Coverage::Merge` | Merge fragments; formatters for JSON, LCOV, Cobertura, console summary. |
|
|
66
|
+
| `Polyrun::Coverage::Collector` | Stdlib `Coverage` → JSON fragment (`POLYRUN_COVERAGE_DISABLE` to skip); `track_under: %w[lib app]`, filters, optional % gate. |
|
|
67
|
+
| `Polyrun::Coverage::Reporting` | Write all formats to a directory from a blob or merged JSON file. |
|
|
68
|
+
| `Polyrun::Reporting::JUnit` | RSpec `--format json` output or Polyrun testcase JSON → JUnit XML (CI). For RSpec formatter wiring see `Polyrun::Reporting::RspecJunit` (`require "polyrun/reporting/rspec_junit"`). |
|
|
69
|
+
| `Polyrun::Timing::Summary` | Text report of slowest files from merged `polyrun_timing.json`. |
|
|
70
|
+
| `Polyrun::Data::Fixtures` | YAML table batches (`each_table`, `load_directory`, optional `apply_insert_all!` with ActiveRecord). |
|
|
71
|
+
| `Polyrun::Data::CachedFixtures` | Process-local memoized fixture blocks (`register` / `fetch`, stats, `reset!`). |
|
|
72
|
+
| `Polyrun::Data::ParallelProvisioning` | Serial vs parallel-worker suite hooks from `POLYRUN_SHARD_*` / `TEST_ENV_NUMBER`. |
|
|
73
|
+
| `Polyrun::Data::FactoryInstrumentation` | Opt-in FactoryBot patch → `FactoryCounts` (after `require "factory_bot"`). |
|
|
74
|
+
| `Polyrun::Data::SqlSnapshot` | PostgreSQL `pg_dump` / `psql` snapshots under `spec/fixtures/sql_snapshots/`. |
|
|
75
|
+
| `Polyrun::Data::FactoryCounts` | Factory/build counters + summary text. |
|
|
76
|
+
| `Polyrun::RSpec` (`require "polyrun/rspec"`) | `install_parallel_provisioning!` → `before(:suite)` hooks. |
|
|
77
|
+
| `Polyrun::Minitest` (`require "polyrun/minitest"`) | `install_parallel_provisioning!` → same as `ParallelProvisioning.run_suite_hooks!` (no Minitest gem dependency). |
|
|
78
|
+
| `Polyrun::Reporting::RspecJunit` (`require "polyrun/reporting/rspec_junit"`) | CI: RSpec JSON formatter + JUnit from `install!` (RSpec loaded only there). |
|
|
79
|
+
| `Polyrun::Prepare::Assets` | Digest trees, marker file, `assets:precompile`. |
|
|
80
|
+
| `Polyrun::Database::Shard` | Shard env map, `%{shard}` DB names, URL path suffix for `postgres://`, `mysql2://`, `mongodb://`, etc. |
|
|
81
|
+
| `Polyrun::Database::UrlBuilder` | URLs from `polyrun.yml` `databases:` — nested blocks or `adapter:` for common Rails stacks (`postgresql`, `mysql`/`mysql2`, `trilogy`, `sqlserver`/`mssql`, `sqlite3`/`sqlite`, `mongodb`/`mongo`). |
|
|
82
|
+
|
|
83
|
+
## Development
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
bundle install
|
|
87
|
+
bundle exec rake spec
|
|
88
|
+
bundle exec rake rbs # optional: validate RBS in sig/
|
|
89
|
+
bundle exec rake ci # RSpec + RuboCop
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Tests include subprocess CLI coverage (`spec/polyrun/cli_spec.rb`: merge-coverage across formats, merge-timing, report-junit, report-timing, plan, env with `databases:`, prepare, report-coverage, db:* dry-run and errors) and unit specs for `Coverage::Merge`, `Timing::Merge`, `Timing::Summary`, `Reporting::JUnit`, `Partition::Plan`, `Database::UrlBuilder` and `Shard`, `Prepare::Artifacts`, `Data::*`, `Env::Ci`, `Queue::FileStore`, and `SqlSnapshot` (with `Open3` stubbed for `pg_dump`).
|
|
93
|
+
|
|
94
|
+
Merge performance defaults align with `spec/polyrun/coverage/merge_scale_spec.rb` (~110 files × 310 lines per fragment):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
ruby benchmark/merge_coverage.rb
|
|
98
|
+
bundle exec rake bench_merge
|
|
99
|
+
# MERGE_FRAGMENTS=16 MERGE_REPS=2 ruby benchmark/merge_coverage.rb
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The script benchmarks `merge_two`, balanced `merge_blob_tree` (same reduction as `merge-coverage` / `merge_files`), a naive left-fold, JSON on disk via `merge_files`, and `merge_fragments` with `meta` and `polyrun_coverage_groups` (group recomputation).
|
|
103
|
+
|
|
104
|
+
Merge is JSON aggregation over coverage fragments; for typical apps it stays well under one second. If merge exceeds ten seconds, Polyrun prints a warning on stderr (override with `POLYRUN_MERGE_SLOW_WARN_SECONDS`; set to `0` to disable).
|
|
105
|
+
|
|
106
|
+
## CLI (reference)
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bin/polyrun plan --total 2 --shard 0 a.rb b.rb c.rb
|
|
110
|
+
bin/polyrun plan --total 3 --shard 0 --timing polyrun_timing.json --paths-file spec/spec_paths.txt
|
|
111
|
+
bin/polyrun plan --strategy hrw --total 4 --shard 0 --paths-file spec/spec_paths.txt
|
|
112
|
+
bin/polyrun queue init --paths-file spec/spec_paths.txt --timing polyrun_timing.json --dir .polyrun-queue
|
|
113
|
+
bin/polyrun report-coverage -i merged.json -o coverage/out --format json,lcov,cobertura,console
|
|
114
|
+
bin/polyrun report-junit -i rspec.json -o junit.xml
|
|
115
|
+
bin/polyrun report-timing -i polyrun_timing.json --top 20
|
|
116
|
+
bin/polyrun plan --shard 0 --total 4 # polyrun.yml in cwd
|
|
117
|
+
bin/polyrun prepare --recipe assets --dry-run
|
|
118
|
+
bin/polyrun parallel-rspec --workers 4
|
|
119
|
+
bin/polyrun quick spec/polyrun_quick/smoke.rb
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`polyrun.yml` can set `partition.*`, `prepare.recipe` (`default` or `assets`), `prepare.rails_root`, and related keys. `POLYRUN_SHARD_*` overrides configuration where documented; CLI flags override environment variables.
|
|
123
|
+
|
|
124
|
+
Shard index and total in CI (`Polyrun::Env::Ci`): when set, `POLYRUN_SHARD_INDEX` and `POLYRUN_SHARD_TOTAL` take precedence. When `CI` is truthy, `CI_NODE_INDEX` / `CI_NODE_TOTAL` and other parallel-job environment variables are read if present. If your runner does not export those, set `POLYRUN_SHARD_*` from the job matrix.
|
|
125
|
+
|
|
126
|
+
File queue (`polyrun queue …`): batches live on disk under a lock file; paths move from `pending` to `leases` on claim and to `done` on ack. There is no lease TTL: if a worker dies after claiming, paths remain in `leases` until you recover them (manually or with a future reclaim command).
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
See [`examples/README.md`](examples/README.md) for Rails apps (Capybara, Playwright, Vite, multi-database, Docker Compose, polyrepo). Parallel CI practices: [`examples/TESTING_REQUIREMENTS.md`](examples/TESTING_REQUIREMENTS.md). Behavioral contracts: `spec/polyrun/mandatory_parallel_support_spec.rb`.
|
|
131
|
+
|
|
132
|
+
You can replace SimpleCov and simplecov plugins, parallel_tests, and rspec_junit_formatter with Polyrun for those roles. Use `merge-timing`, `report-timing`, and `Data::FactoryCounts` (optionally with `Data::FactoryInstrumentation`) for slow-file and factory metrics. YAML fixture batches and bulk inserts can use `Data::Fixtures` and `ParallelProvisioning` for shard-aware seeding; wire your own `truncate` and `load_seed` in hooks.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
137
|
+
|
|
138
|
+
<a href="https://www.kiskolabs.com">
|
|
139
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
140
|
+
</a>
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SECURITY
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Do not open a public GitHub issue for security vulnerabilities.
|
|
6
|
+
|
|
7
|
+
Email security details to security@kiskolabs.com.
|
|
8
|
+
|
|
9
|
+
Include: description, steps to reproduce, potential impact, and suggested fix (if available).
|
|
10
|
+
|
|
11
|
+
### Response Timeline
|
|
12
|
+
|
|
13
|
+
- We will acknowledge receipt of your report
|
|
14
|
+
- We will provide an initial assessment
|
|
15
|
+
- We will keep you informed of our progress and resolution timeline
|
|
16
|
+
|
|
17
|
+
### Disclosure Policy
|
|
18
|
+
|
|
19
|
+
- We will work with you to understand and resolve the issue
|
|
20
|
+
- We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
21
|
+
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
+
- We will coordinate public disclosure with you
|
|
23
|
+
|
|
24
|
+
## Automation security
|
|
25
|
+
|
|
26
|
+
- Context isolation: do not include production credentials, API keys, or personally identifiable information in prompts sent to third-party LLMs or automation services.
|
|
27
|
+
- Supply chain: verify automated dependencies.
|
data/bin/polyrun
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Polyrun setup profile (agent / human checklist)
|
|
2
|
+
|
|
3
|
+
Use this as a fill-in worksheet before editing a host project. `polyrun.yml` is the contract; everything else is an adapter that must stay aligned with it.
|
|
4
|
+
|
|
5
|
+
## 1. Project shape
|
|
6
|
+
|
|
7
|
+
| Field | Options / notes |
|
|
8
|
+
|--------|------------------|
|
|
9
|
+
| Project type | Gem (library, no Rails app), Rails (full app), or multi-gemfile (Appraisal / multiple `gemfiles/*.gemfile`) |
|
|
10
|
+
| Gemfile path to polyrun | For example `gem "polyrun", path: "../polyrun.rb"` or `gem "polyrun"` from RubyGems—note the path relative to each gemfile |
|
|
11
|
+
| Appraisal / Docker | If Appraisal: polyrun must appear in `Appraisals` and each generated gemfile. If Docker: document working directory and whether prepare is one-shot or repeated with tests |
|
|
12
|
+
|
|
13
|
+
## 2. Parallelism target (pick one primary story)
|
|
14
|
+
|
|
15
|
+
| Target | Typical CLI / CI |
|
|
16
|
+
|--------|-------------------|
|
|
17
|
+
| Single CI job, N workers on one runner | One workflow job runs `polyrun parallel-rspec --workers N` (or `start`); merge coverage in the same job or a small follow-up step; upload artifacts |
|
|
18
|
+
| Matrix: one shard per CI job | Matrix sets `POLYRUN_SHARD_INDEX` / `POLYRUN_SHARD_TOTAL` (or CI vendor equivalents—see `Polyrun::Env::Ci` and the README); each job runs `plan` plus the test command for its shard; upload `coverage/polyrun-fragment-<i>.json`; a merge job runs `merge-coverage` |
|
|
19
|
+
|
|
20
|
+
Do not mix “fan out N workers inside one job” with “matrix shard index” in the same workflow without a clear story for which process writes which fragment and where merge runs.
|
|
21
|
+
|
|
22
|
+
## 3. Database
|
|
23
|
+
|
|
24
|
+
| Field | Options |
|
|
25
|
+
|--------|---------|
|
|
26
|
+
| Single test DB | Omitting `databases:` in `polyrun.yml` may be enough; ensure parallel workers do not share one DB if they mutate data |
|
|
27
|
+
| Multi-DB or shard suffixes | `databases:` in `polyrun.yml` (`shard_db_pattern`, `template_db`, optional `connections` and `env_key`). `database.yml` or `DATABASE_URL` may use `%{shard}` or `POLYRUN_SHARD_INDEX` suffixes—the same convention as `polyrun env` |
|
|
28
|
+
| External provisioning | Sometimes `db:prepare` plus shell clone scripts (multi-DB apps)—document ordering: prepare databases before `run-shards` |
|
|
29
|
+
|
|
30
|
+
## 4. Prepare (run once before workers)
|
|
31
|
+
|
|
32
|
+
| Field | Options |
|
|
33
|
+
|--------|---------|
|
|
34
|
+
| None | Typical for gems without assets |
|
|
35
|
+
| Assets | `Polyrun::Prepare::Assets`, `prepare.recipe: assets` or `default`, digest markers |
|
|
36
|
+
| Playwright / browsers | Install once in prepare; workers skip reinstall (`SKIP_*` env flags in app code if needed) |
|
|
37
|
+
| Custom shell | `prepare.recipe: shell` with `prepare.command:`—must not repeat heavy work inside each worker |
|
|
38
|
+
|
|
39
|
+
Rule: anything expensive (compile, `yarn`, Playwright download) belongs in prepare or a CI cache step, not in `before(:suite)` per worker unless gated by `POLYRUN_SHARD_TOTAL` or similar env.
|
|
40
|
+
|
|
41
|
+
## 5. Spec list and ordering
|
|
42
|
+
|
|
43
|
+
| Field | Options |
|
|
44
|
+
|--------|---------|
|
|
45
|
+
| Plain glob | `partition.paths_build.all_glob: spec/**/*_spec.rb` and empty or minimal `stages` |
|
|
46
|
+
| Ordered stages | `partition.paths_build.stages`: regex (e.g. slow integration first) or `sort_by_substring_order` for stable ordering |
|
|
47
|
+
|
|
48
|
+
Refresh list: `polyrun -c polyrun.yml build-paths` (also runs automatically before `plan` / `run-shards` when configured).
|
|
49
|
+
|
|
50
|
+
## 6. Coverage and CI reports
|
|
51
|
+
|
|
52
|
+
| Field | Options |
|
|
53
|
+
|--------|---------|
|
|
54
|
+
| Collector | `require "polyrun"` plus `Polyrun::Coverage::Collector.start!` in `spec_helper` (non-Rails gems) |
|
|
55
|
+
| Rails | `require "polyrun/coverage/rails"` (or documented Rails integration) in `spec_helper` / `test_helper` |
|
|
56
|
+
| Fragments | Per shard: `coverage/polyrun-fragment-<shard>.json` |
|
|
57
|
+
| Merge | `polyrun merge-coverage` on fragments → merged JSON; then `polyrun report-coverage` (formats: json, lcov, cobertura, console, html, …) |
|
|
58
|
+
| JUnit | `polyrun report-junit` from RSpec JSON if needed |
|
|
59
|
+
|
|
60
|
+
## 7. `polyrun.yml` as contract — adapters
|
|
61
|
+
|
|
62
|
+
After `polyrun.yml` is fixed, add or adjust adapters (same shard semantics):
|
|
63
|
+
|
|
64
|
+
| Adapter | Role |
|
|
65
|
+
|---------|------|
|
|
66
|
+
| `bin/rspec_parallel` / `bin/rspec_ci_shard` | Thin wrappers: prepare, start, or plan + rspec |
|
|
67
|
+
| `bin/polyrun-rspec` | Single shard (`env` + plan + rspec), e.g. one matrix cell or ad-hoc run |
|
|
68
|
+
| `config/database.yml` | ERB / suffixes matching `databases.shard_db_pattern` |
|
|
69
|
+
| Prepare script | Referenced from `prepare.command` |
|
|
70
|
+
| `POLYRUN.md` (or `spec/README`) | Canonical commands for this repo; CI model (below) |
|
|
71
|
+
|
|
72
|
+
Bot workflow: read and write `polyrun.yml` first, then generate or patch wrappers and docs to match.
|
|
73
|
+
|
|
74
|
+
## 8. CI models (document one in `POLYRUN.md`)
|
|
75
|
+
|
|
76
|
+
Model A — one job, N worker processes
|
|
77
|
+
|
|
78
|
+
- Command: `polyrun -c polyrun.yml parallel-rspec --workers N` (or `polyrun start`). Global `-c` / `-v` / `-h` must appear before the subcommand.
|
|
79
|
+
- Coverage fragments appear on the same runner; `merge-coverage` can run in the same job after workers finish.
|
|
80
|
+
|
|
81
|
+
Model B — matrix of jobs (one shard per job)
|
|
82
|
+
|
|
83
|
+
- Each job sets `POLYRUN_SHARD_INDEX` / `POLYRUN_SHARD_TOTAL` (and DB URLs per shard if needed).
|
|
84
|
+
- Run `polyrun build-paths`, `polyrun plan`, then `bundle exec rspec` (or `bin/rspec_ci_shard`) for that shard only.
|
|
85
|
+
- Upload `coverage/polyrun-fragment-*.json` (or named per shard).
|
|
86
|
+
- A final `merge-coverage` job downloads artifacts and merges.
|
|
87
|
+
|
|
88
|
+
GitHub Actions does not set `CI_NODE_INDEX` / `CI_NODE_TOTAL` by default—set `POLYRUN_*` explicitly in the matrix.
|
|
89
|
+
|
|
90
|
+
## 9. Chatbot / agent context blocks (high signal)
|
|
91
|
+
|
|
92
|
+
1. Upstream README sections: partition, prepare, databases, merge-coverage, `Env::Ci` (shard detection).
|
|
93
|
+
2. Project `polyrun.yml` (filled in) plus one of: `POLYRUN.md` or `spec/README.md` with the canonical test command and CI model (A or B).
|
|
94
|
+
3. Exact `Gemfile` / Appraisal lines for `polyrun` and any Docker or path notes.
|
|
95
|
+
|
|
96
|
+
## 10. Scaffold from this repo
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
polyrun init --list
|
|
100
|
+
polyrun init --profile gem -o polyrun.yml
|
|
101
|
+
polyrun init --profile rails -o polyrun.yml
|
|
102
|
+
polyrun init --profile ci-matrix -o polyrun.yml
|
|
103
|
+
polyrun init --profile doc -o POLYRUN.md # host-project doc template
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Templates live under `lib/polyrun/templates/` in the gem. See `examples/templates/README.md` for profile descriptions.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
require_relative "coverage_merge_io"
|
|
6
|
+
|
|
7
|
+
module Polyrun
|
|
8
|
+
class CLI
|
|
9
|
+
module CoverageCommands
|
|
10
|
+
include CoverageMergeIo
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def cmd_merge_coverage(argv, _config_path)
|
|
15
|
+
inputs, output, formats = merge_coverage_parse_argv(argv)
|
|
16
|
+
if inputs.empty?
|
|
17
|
+
Polyrun::Log.warn "merge-coverage: need at least one existing -i FILE (after glob expansion)"
|
|
18
|
+
return 2
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Polyrun::Log.warn "merge-coverage: merging #{inputs.size} fragment(s)" if @verbose
|
|
22
|
+
Polyrun::Debug.log_kv(
|
|
23
|
+
merge_coverage: "start",
|
|
24
|
+
output: output,
|
|
25
|
+
formats: formats,
|
|
26
|
+
input_paths: inputs
|
|
27
|
+
)
|
|
28
|
+
input_bytes = inputs.sum { |p| File.size(p) }
|
|
29
|
+
Polyrun::Debug.log("merge-coverage: input_bytes=#{input_bytes}")
|
|
30
|
+
|
|
31
|
+
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
32
|
+
r = merge_coverage_merge_fragments(inputs)
|
|
33
|
+
merged = r[:blob]
|
|
34
|
+
Polyrun::Debug.log("merge-coverage: merged_blob file_count=#{merged.size}")
|
|
35
|
+
|
|
36
|
+
payload = Polyrun::Coverage::Merge.to_simplecov_json(merged, meta: r[:meta], groups: r[:groups])
|
|
37
|
+
out_abs = File.expand_path(output)
|
|
38
|
+
merge_coverage_write_json_payload(out_abs, payload)
|
|
39
|
+
merge_coverage_write_format_outputs(merged, r, out_abs, formats)
|
|
40
|
+
|
|
41
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
|
|
42
|
+
merge_coverage_log_finish(elapsed, inputs)
|
|
43
|
+
0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Warn when wall time exceeds this many seconds (default 10). Set POLYRUN_MERGE_SLOW_WARN_SECONDS=0 to disable.
|
|
47
|
+
def merge_slow_warn_threshold_seconds
|
|
48
|
+
v = ENV["POLYRUN_MERGE_SLOW_WARN_SECONDS"]
|
|
49
|
+
return 10.0 if v.nil? || v.to_s.strip.empty?
|
|
50
|
+
|
|
51
|
+
s = v.to_s.strip.downcase
|
|
52
|
+
return nil if %w[0 false no].include?(s)
|
|
53
|
+
|
|
54
|
+
Float(v)
|
|
55
|
+
rescue ArgumentError
|
|
56
|
+
10.0
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def cmd_report_coverage(argv)
|
|
60
|
+
input = nil
|
|
61
|
+
output_dir = "coverage/polyrun"
|
|
62
|
+
basename = "polyrun-coverage"
|
|
63
|
+
formats = Polyrun::Coverage::Reporting::DEFAULT_FORMATS.dup
|
|
64
|
+
|
|
65
|
+
parser = OptionParser.new do |opts|
|
|
66
|
+
opts.banner = "usage: polyrun report-coverage -i FILE [-o DIR] [--basename NAME] [--format json,lcov,cobertura,console,html]"
|
|
67
|
+
opts.on("-i", "--input PATH", "Merged or raw SimpleCov JSON") { |v| input = v }
|
|
68
|
+
opts.on("-o", "--output DIR", "Output directory") { |v| output_dir = v }
|
|
69
|
+
opts.on("--basename NAME", "File name prefix") { |v| basename = v }
|
|
70
|
+
opts.on("--format LIST", String) { |v| formats = v.split(",").map(&:strip) }
|
|
71
|
+
end
|
|
72
|
+
parser.parse!(argv)
|
|
73
|
+
input ||= argv.first
|
|
74
|
+
|
|
75
|
+
unless input && File.file?(input)
|
|
76
|
+
Polyrun::Log.warn "report-coverage: need -i FILE or a path argument"
|
|
77
|
+
return 2
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
paths = Polyrun::Debug.time("report-coverage: write_from_json_file") do
|
|
81
|
+
Polyrun::Coverage::Reporting.write_from_json_file(
|
|
82
|
+
File.expand_path(input),
|
|
83
|
+
output_dir: File.expand_path(output_dir),
|
|
84
|
+
basename: basename,
|
|
85
|
+
formats: formats
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
Polyrun::Log.puts JSON.generate(paths.transform_keys(&:to_s))
|
|
89
|
+
0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def merge_coverage_after_shards(output:, format_list:, config_path:)
|
|
93
|
+
files = merge_coverage_fragment_json_files
|
|
94
|
+
if files.empty?
|
|
95
|
+
Polyrun::Log.warn "polyrun run-shards: --merge-coverage: no coverage/polyrun-fragment-*.json found (enable Polyrun coverage in spec_helper?)"
|
|
96
|
+
return 0
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
merge_coverage_after_shards_log_start(files, output, format_list)
|
|
100
|
+
code = merge_coverage_after_shards_run_merge(files, output, format_list, config_path)
|
|
101
|
+
return code unless code == 0
|
|
102
|
+
|
|
103
|
+
merge_coverage_after_shards_strict_gate(output, code)
|
|
104
|
+
rescue JSON::ParserError => e
|
|
105
|
+
Polyrun::Log.warn "polyrun run-shards: merged coverage JSON parse failed: #{e.message}"
|
|
106
|
+
1
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def merge_coverage_fragment_json_files
|
|
110
|
+
pattern = File.join(Dir.pwd, "coverage", "polyrun-fragment-*.json")
|
|
111
|
+
Dir.glob(pattern).sort
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def merge_coverage_after_shards_log_start(files, output, format_list)
|
|
115
|
+
Polyrun::Log.warn "polyrun run-shards: merging #{files.size} coverage fragment(s) → #{output}"
|
|
116
|
+
Polyrun::Debug.log("merge-coverage-after-shards: #{files.size} fragment(s) → #{output} format=#{format_list}")
|
|
117
|
+
Polyrun::Debug.log("merge-coverage-after-shards: fragments=#{files.join(", ")}")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def merge_coverage_after_shards_run_merge(files, output, format_list, config_path)
|
|
121
|
+
merge_argv = []
|
|
122
|
+
files.each { |f| merge_argv.push("-i", f) }
|
|
123
|
+
merge_argv += ["-o", output, "--format", format_list]
|
|
124
|
+
Polyrun::Debug.time("merge-coverage (parent after workers)") do
|
|
125
|
+
cmd_merge_coverage(merge_argv, config_path)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def merge_coverage_after_shards_strict_gate(output, code)
|
|
130
|
+
gate = coverage_minimum_line_gate_from_polyrun_coverage_yml
|
|
131
|
+
Polyrun::Debug.log_kv(
|
|
132
|
+
coverage_gate_config: "polyrun_coverage.yml",
|
|
133
|
+
gate: gate.inspect
|
|
134
|
+
)
|
|
135
|
+
return code if gate.nil? || !gate[:strict]
|
|
136
|
+
|
|
137
|
+
merged_path = File.expand_path(output)
|
|
138
|
+
unless File.file?(merged_path)
|
|
139
|
+
Polyrun::Log.warn "polyrun run-shards: --merge-coverage: expected merged JSON at #{merged_path} missing"
|
|
140
|
+
return 1
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
below = merge_coverage_min_line_gate_below?(merged_path, gate)
|
|
144
|
+
return 1 if below
|
|
145
|
+
|
|
146
|
+
code
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|