polyrun 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/lib/polyrun/cli/coverage_commands.rb +1 -1
- data/lib/polyrun/cli/failure_commands.rb +1 -1
- data/lib/polyrun/cli/help.rb +14 -16
- data/lib/polyrun/cli/run_shards_parallel_wait.rb +5 -1
- data/lib/polyrun/cli/run_shards_plan_options.rb +3 -3
- data/lib/polyrun/cli/run_shards_run.rb +1 -1
- data/lib/polyrun/cli/spec_quality_commands.rb +1 -1
- data/lib/polyrun/coverage/merge/formatters_html.rb +5 -5
- data/lib/polyrun/queue/file_store.rb +7 -0
- data/lib/polyrun/rspec.rb +2 -0
- data/lib/polyrun/timing/rspec_example_formatter.rb +14 -7
- data/lib/polyrun/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 756cf8e1b8e2176c4097520752dfdd4b856d03b134a999462641f90a9f217e71
|
|
4
|
+
data.tar.gz: 284e536d886b4bfc913415b0e3301a31ca98ef3446da1b24163bcc6af485e642
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 445f64e0e62be50ab6c63026f27ad8f5f093521bbeed4a05b0ce7adc6a13bcb0496f1489d8243b403f859826e599194c3399a761a9210cbbf5d8027f3fdba52e
|
|
7
|
+
data.tar.gz: 554a292c2c7ac40e3df11d886ca7a71c10b81f1fa144af00c5c1d20b950ff6d999306be1e3fa75a9982450b2d202bc2bee34d5201ab39dbb386b1b73b42f1403
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 2.1.2 (2026-07-05)
|
|
6
|
+
|
|
7
|
+
- Fix per-example timing JSON when RSpec supplies `absolute_file_path` metadata; fix formatter registration when `install_example_timing!` uses a custom output path.
|
|
8
|
+
|
|
9
|
+
## 2.1.1 (2026-07-05)
|
|
10
|
+
|
|
11
|
+
- Fix HTML coverage report `Encoding::CompatibilityError` when source files contain UTF-8; read templates, assets, and source lines as UTF-8 in `formatters_html.rb`.
|
|
12
|
+
|
|
5
13
|
## 2.1.0 (2026-07-03)
|
|
6
14
|
|
|
7
15
|
- 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`.
|
|
@@ -92,7 +92,7 @@ module Polyrun
|
|
|
92
92
|
def merge_coverage_after_shards(output:, format_list:, config_path:)
|
|
93
93
|
files = merge_coverage_fragment_json_files
|
|
94
94
|
if files.empty?
|
|
95
|
-
Polyrun::Log.warn "polyrun run-shards: --merge-coverage: no coverage
|
|
95
|
+
Polyrun::Log.warn "polyrun run-shards: --merge-coverage: no coverage fragments found under coverage (enable coverage collection in your test setup)"
|
|
96
96
|
return 0
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -57,7 +57,7 @@ module Polyrun
|
|
|
57
57
|
pattern = Polyrun::Reporting::FailureMerge.default_fragment_glob
|
|
58
58
|
files = Dir.glob(pattern).sort
|
|
59
59
|
if files.empty?
|
|
60
|
-
Polyrun::Log.warn "polyrun run-shards: --merge-failures: no
|
|
60
|
+
Polyrun::Log.warn "polyrun run-shards: --merge-failures: no failure fragments found under tmp/polyrun_failures (enable failure fragments in your test setup)"
|
|
61
61
|
return nil
|
|
62
62
|
end
|
|
63
63
|
|
data/lib/polyrun/cli/help.rb
CHANGED
|
@@ -13,21 +13,19 @@ module Polyrun
|
|
|
13
13
|
-h, --help
|
|
14
14
|
|
|
15
15
|
Trace timing (stderr): DEBUG=1 or POLYRUN_DEBUG=1
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Merge wall time (stderr): POLYRUN_PROFILE_MERGE=1 (or verbose / DEBUG)
|
|
16
|
+
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
|
|
17
|
+
Merge profiling (stderr): POLYRUN_PROFILE_MERGE=1 (or verbose / DEBUG)
|
|
19
18
|
Post-merge formats (run-shards): POLYRUN_MERGE_FORMATS (default: json,lcov,cobertura,console,html)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Per-worker
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Partition timing granularity (default file): POLYRUN_TIMING_GRANULARITY=file|example (experimental per-example; see partition.timing_granularity)
|
|
19
|
+
Start skips: POLYRUN_SKIP_BUILD_SPEC_PATHS=1, POLYRUN_START_SKIP_PREPARE=1, POLYRUN_START_SKIP_DATABASES=1
|
|
20
|
+
Paths build skip: POLYRUN_SKIP_PATHS_BUILD=1
|
|
21
|
+
Slow merge warning (seconds, default 10; 0 disables): POLYRUN_MERGE_SLOW_WARN_SECONDS
|
|
22
|
+
Failure merge: run-shards --merge-failures (enable failure fragments in test setup); POLYRUN_MERGE_FAILURES=1, POLYRUN_FAILURE_FRAGMENT_DIR, POLYRUN_MERGED_FAILURES_OUT
|
|
23
|
+
Parallel workers: POLYRUN_WORKERS default 5, max 10 (run-shards / parallel-rspec / start). CI local processes per job: POLYRUN_SHARD_PROCESSES or ci-shard --shard-processes (not POLYRUN_WORKERS)
|
|
24
|
+
Per-worker wall timeout: --worker-timeout SEC or POLYRUN_WORKER_TIMEOUT_SEC. Exit 124; parent stops remaining workers.
|
|
25
|
+
Per-worker idle timeout: --worker-idle-timeout SEC or POLYRUN_WORKER_IDLE_TIMEOUT_SEC after a progress ping (POLYRUN_WORKER_PING_FILE). Enable pings in test setup. Exit 125. Optional periodic pings: POLYRUN_WORKER_PING_THREAD=1 (POLYRUN_WORKER_PING_INTERVAL_SEC).
|
|
26
|
+
Orchestration warnings on process stderr: POLYRUN_ORCHESTRATION_STDERR=1
|
|
27
|
+
Spec quality (opt-in): POLYRUN_SPEC_QUALITY=1; run-shards --merge-spec-quality; merge-spec-quality / report-spec-quality
|
|
28
|
+
Partition timing granularity (default file): POLYRUN_TIMING_GRANULARITY=file|example (experimental; see partition.timing_granularity)
|
|
31
29
|
Partition strategies: round_robin (default, sorted), preserve_order_round_robin (paths-file order), lazy_robin (sorted RR + timing diagnostics), cost_binpack (LPT), hrw. partition.timing_file without strategy implies cost_binpack.
|
|
32
30
|
|
|
33
31
|
commands:
|
|
@@ -45,7 +43,7 @@ module Polyrun
|
|
|
45
43
|
init write a starter polyrun.yml or POLYRUN.md from built-in templates (see docs/SETUP_PROFILE.md)
|
|
46
44
|
queue file-backed batch queue: init (optional --shard/--total etc. as plan, then claim/ack/reclaim/status --json)
|
|
47
45
|
run-queue init queue and run N workers that claim batches until drained
|
|
48
|
-
quick
|
|
46
|
+
quick quick test runner (describe/it, before/after, let, expect…to, assert_*; optional capybara!)
|
|
49
47
|
hook run <phase> run one shell hook from polyrun.yml hooks: (e.g. before_suite); optional --shard/--total
|
|
50
48
|
report-coverage write all coverage formats from one JSON file
|
|
51
49
|
report-junit RSpec JSON or Polyrun testcase JSON → JUnit XML (CI)
|
|
@@ -53,7 +51,7 @@ module Polyrun
|
|
|
53
51
|
merge-timing merge polyrun_timing_*.json shards
|
|
54
52
|
merge-spec-quality merge polyrun-spec-quality-fragment-*.jsonl shards
|
|
55
53
|
report-spec-quality spec quality report from merged JSON (zero-hit, hot lines, churn)
|
|
56
|
-
config print effective config by dotted path (
|
|
54
|
+
config print effective config by dotted path (loaded YAML plus merged prepare.env, resolved partition shard fields, workers)
|
|
57
55
|
env print shard + database env (see polyrun.yml databases)
|
|
58
56
|
db:setup-template migrate template DB (PostgreSQL)
|
|
59
57
|
db:setup-shard CREATE DATABASE shard FROM template (one POLYRUN_SHARD_INDEX)
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
module Polyrun
|
|
3
3
|
class CLI
|
|
4
4
|
# Wait, wall/idle timeout, and +after_shard+ hooks for parallel workers (+run-shards+ / +ci-shard-*+).
|
|
5
|
+
#
|
|
6
|
+
# Per-worker wait states: running → normal_exit | wall_timeout (124) | idle_timeout (125).
|
|
7
|
+
# When wall or idle caps are set, the multiplex loop polls every live PID each tick so timeouts
|
|
8
|
+
# apply fairly across shards (not only the worker currently in Process.wait).
|
|
5
9
|
module RunShardsParallelWait
|
|
6
10
|
WORKER_TIMEOUT_EXIT_STATUS = 124
|
|
7
11
|
WORKER_IDLE_TIMEOUT_EXIT_STATUS = 125
|
|
@@ -215,7 +219,7 @@ module Polyrun
|
|
|
215
219
|
ping_suffix = (loc && !loc.to_s.strip.empty?) ? "; last ping #{loc.to_s.strip}" : ""
|
|
216
220
|
Polyrun::Log.orchestration_warn "polyrun run-shards: WORKER IDLE TIMEOUT after #{idle_sec}s since last per-example progress ping — shard #{h[:shard]} pid #{h[:pid]}#{ping_suffix}."
|
|
217
221
|
Polyrun::Log.warn "polyrun run-shards: idle shard file sample: #{sample}#{suffix}"
|
|
218
|
-
Polyrun::Log.warn "polyrun run-shards:
|
|
222
|
+
Polyrun::Log.warn "polyrun run-shards: enable per-example worker progress pings in your test setup so idle timeouts reflect real work; exit #{WORKER_IDLE_TIMEOUT_EXIT_STATUS}."
|
|
219
223
|
end
|
|
220
224
|
|
|
221
225
|
def run_shards_wait_or_force_stop_status(pid)
|
|
@@ -48,7 +48,7 @@ module Polyrun
|
|
|
48
48
|
opts.banner = "usage: polyrun run-shards [--workers N] [--worker-timeout SEC] [--worker-idle-timeout SEC] [--strategy NAME] [--paths-file P] [--timing P] [--timing-granularity VAL] [--constraints P] [--seed S] [--merge-coverage] [--merge-output P] [--merge-format LIST] [--merge-failures] [--merge-failures-output P] [--merge-failures-format jsonl|json] [--merge-spec-quality] [--merge-spec-quality-output P] [--no-report-spec-quality] [--] <command> [args...]"
|
|
49
49
|
opts.on("--workers N", Integer) { |v| st[:workers] = v }
|
|
50
50
|
opts.on("--worker-timeout SEC", Float, "Max seconds per worker since spawn (also POLYRUN_WORKER_TIMEOUT_SEC); kills stuck workers (exit 124)") { |v| st[:worker_timeout_sec] = v }
|
|
51
|
-
opts.on("--worker-idle-timeout SEC", Float, "Max seconds since last
|
|
51
|
+
opts.on("--worker-idle-timeout SEC", Float, "Max seconds since last worker progress ping (POLYRUN_WORKER_PING_FILE); enable pings in test setup; exit 125") { |v| st[:worker_idle_timeout_sec] = v }
|
|
52
52
|
opts.on("--strategy NAME", String) do |v|
|
|
53
53
|
st[:strategy] = v
|
|
54
54
|
st[:strategy_explicit] = true
|
|
@@ -61,10 +61,10 @@ module Polyrun
|
|
|
61
61
|
opts.on("--merge-coverage", "After success, merge coverage/polyrun-fragment-*.json (Polyrun coverage must be enabled)") { st[:merge_coverage] = true }
|
|
62
62
|
opts.on("--merge-output PATH", String) { |v| st[:merge_output] = v }
|
|
63
63
|
opts.on("--merge-format LIST", String) { |v| st[:merge_format] = v }
|
|
64
|
-
opts.on("--merge-failures", "After all workers exit, merge tmp/polyrun_failures
|
|
64
|
+
opts.on("--merge-failures", "After all workers exit, merge failure fragments from tmp/polyrun_failures (requires failure fragments in test setup)") { st[:merge_failures] = true }
|
|
65
65
|
opts.on("--merge-failures-output PATH", String) { |v| st[:merge_failures_output] = v }
|
|
66
66
|
opts.on("--merge-failures-format VAL", "jsonl (default) or json") { |v| st[:merge_failures_format] = v }
|
|
67
|
-
opts.on("--merge-spec-quality", "After workers exit, merge
|
|
67
|
+
opts.on("--merge-spec-quality", "After workers exit, merge spec-quality fragments from coverage (enable spec-quality collection in test setup)") { st[:merge_spec_quality] = true }
|
|
68
68
|
opts.on("--merge-spec-quality-output PATH", String) { |v| st[:merge_spec_quality_output] = v }
|
|
69
69
|
opts.on("--no-report-spec-quality", "Skip printing spec-quality report after merge") { st[:report_spec_quality] = false }
|
|
70
70
|
end
|
|
@@ -162,7 +162,7 @@ module Polyrun
|
|
|
162
162
|
Polyrun::Log.warn "polyrun run-shards: shard #{s} re-run (same spec list, no interleave): #{rerun}"
|
|
163
163
|
end
|
|
164
164
|
unless merge_failures
|
|
165
|
-
Polyrun::Log.warn "polyrun run-shards: one
|
|
165
|
+
Polyrun::Log.warn "polyrun run-shards: for one combined failure report, add --merge-failures (requires failure fragments enabled in your test setup)"
|
|
166
166
|
end
|
|
167
167
|
end
|
|
168
168
|
end
|
|
@@ -96,7 +96,7 @@ module Polyrun
|
|
|
96
96
|
def merge_spec_quality_after_shards(ctx)
|
|
97
97
|
files = merge_spec_quality_fragment_files
|
|
98
98
|
if files.empty?
|
|
99
|
-
Polyrun::Log.warn "polyrun run-shards: --merge-spec-quality: no
|
|
99
|
+
Polyrun::Log.warn "polyrun run-shards: --merge-spec-quality: no spec-quality fragments found under coverage (enable spec-quality collection in your test setup)"
|
|
100
100
|
return nil
|
|
101
101
|
end
|
|
102
102
|
|
|
@@ -23,14 +23,14 @@ module Polyrun
|
|
|
23
23
|
)
|
|
24
24
|
file_list_html = render_html_partial("file_list", file_rows_html: files.map { |file| html_file_list_row(file) }.join("\n"))
|
|
25
25
|
file_sections_html = files.map { |file| render_html_partial("file_section", file: file) }.join("\n")
|
|
26
|
-
ERB.new(File.read(html_template_path), trim_mode: "-").result_with_hash(
|
|
26
|
+
ERB.new(File.read(html_template_path, encoding: Encoding::UTF_8), trim_mode: "-").result_with_hash(
|
|
27
27
|
title: CGI.escapeHTML(title.to_s),
|
|
28
28
|
generated_label: html_generated_label(generated_at),
|
|
29
29
|
overview_html: overview_html,
|
|
30
30
|
file_list_html: file_list_html,
|
|
31
31
|
file_sections_html: file_sections_html,
|
|
32
|
-
stylesheet: File.read(html_stylesheet_path),
|
|
33
|
-
javascript: File.read(html_javascript_path)
|
|
32
|
+
stylesheet: File.read(html_stylesheet_path, encoding: Encoding::UTF_8),
|
|
33
|
+
javascript: File.read(html_javascript_path, encoding: Encoding::UTF_8)
|
|
34
34
|
)
|
|
35
35
|
end
|
|
36
36
|
# rubocop:enable Metrics/AbcSize
|
|
@@ -56,7 +56,7 @@ module Polyrun
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def render_html_partial(name, locals = {})
|
|
59
|
-
ERB.new(File.read(html_partial_path(name)), trim_mode: "-").result_with_hash(locals)
|
|
59
|
+
ERB.new(File.read(html_partial_path(name), encoding: Encoding::UTF_8), trim_mode: "-").result_with_hash(locals)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def html_file_payload(path, file, root)
|
|
@@ -152,7 +152,7 @@ module Polyrun
|
|
|
152
152
|
def html_source_lines(path, fallback_length)
|
|
153
153
|
return Array.new(fallback_length, "") unless File.file?(path.to_s)
|
|
154
154
|
|
|
155
|
-
File.readlines(path.to_s, chomp: true)
|
|
155
|
+
File.readlines(path.to_s, chomp: true, encoding: Encoding::UTF_8)
|
|
156
156
|
rescue Errno::ENOENT, Errno::EACCES, ArgumentError
|
|
157
157
|
Array.new(fallback_length, "")
|
|
158
158
|
end
|
|
@@ -6,6 +6,13 @@ require "time"
|
|
|
6
6
|
module Polyrun
|
|
7
7
|
module Queue
|
|
8
8
|
# File-backed queue (spec_queue.md): +queue.json+, +pending/*.json+ chunks, +done.jsonl+, +leases.json+ (OS flock).
|
|
9
|
+
#
|
|
10
|
+
# Path lifecycle (lease transitions):
|
|
11
|
+
# init! → paths in pending chunks only
|
|
12
|
+
# claim! → pending −batch → active lease in leases.json
|
|
13
|
+
# ack! → lease removed; paths appended to done.jsonl
|
|
14
|
+
# reclaim! → stale or matching lease → paths returned to pending
|
|
15
|
+
# reclaim_lease! → one lease by id → paths returned to pending
|
|
9
16
|
class FileStore
|
|
10
17
|
CHUNK_SIZE = 500
|
|
11
18
|
|
data/lib/polyrun/rspec.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
|
|
3
|
-
require "rspec/core/formatters
|
|
3
|
+
require "rspec/core/formatters"
|
|
4
4
|
|
|
5
5
|
module Polyrun
|
|
6
6
|
module Timing
|
|
@@ -13,23 +13,23 @@ module Polyrun
|
|
|
13
13
|
# Or {Polyrun::RSpec.install_example_timing!} (+output_path:+ avoids touching +ENV+).
|
|
14
14
|
#
|
|
15
15
|
# Default output path: +ENV["POLYRUN_EXAMPLE_TIMING_OUT"]+ if set, else +polyrun_timing_examples.json+.
|
|
16
|
-
class RSpecExampleFormatter
|
|
17
|
-
RSpec::Core::Formatters.register self, :example_finished, :close
|
|
16
|
+
class RSpecExampleFormatter
|
|
17
|
+
::RSpec::Core::Formatters.register self, :example_finished, :close
|
|
18
18
|
|
|
19
19
|
def initialize(output)
|
|
20
|
-
|
|
20
|
+
@output = output
|
|
21
21
|
@times = {}
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def example_finished(notification)
|
|
25
25
|
ex = notification.example
|
|
26
|
-
|
|
27
|
-
return if result.pending?
|
|
26
|
+
return if ex.pending?
|
|
28
27
|
|
|
28
|
+
result = ex.execution_result
|
|
29
29
|
t = result.run_time
|
|
30
30
|
return unless t
|
|
31
31
|
|
|
32
|
-
path = ex
|
|
32
|
+
path = example_absolute_path(ex)
|
|
33
33
|
return unless path
|
|
34
34
|
|
|
35
35
|
line = ex.metadata[:line_number]
|
|
@@ -48,6 +48,13 @@ module Polyrun
|
|
|
48
48
|
def timing_output_path
|
|
49
49
|
ENV["POLYRUN_EXAMPLE_TIMING_OUT"] || "polyrun_timing_examples.json"
|
|
50
50
|
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def example_absolute_path(example)
|
|
55
|
+
metadata = example.metadata
|
|
56
|
+
metadata[:absolute_file_path] || metadata[:file_path] || metadata[:absolute_path]
|
|
57
|
+
end
|
|
51
58
|
end
|
|
52
59
|
end
|
|
53
60
|
end
|
data/lib/polyrun/version.rb
CHANGED