polyrun 1.4.0 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04bfc3ed1a2c01072864dd179decce4d13c68221bba4da0964f4ea600401b57c
4
- data.tar.gz: df02fc828fb8ac8c9cf3792ae08420ee87d328a89d79a7eeec26c9e239506ee4
3
+ metadata.gz: f37969f7a73313a98aa0fd343ae1ed871a32b6dc504da56dd5cb51f42ef2969a
4
+ data.tar.gz: 3e17734a4419ea15b243ad01261701106d99e3470e4408d2b545deb5876ddcf3
5
5
  SHA512:
6
- metadata.gz: 378921ebc46b80562c5ae4bb529a3035eed7366cbfbcffd9a5c4bcb1d18f1c1fe6b737dfcc580c9b7e3fb1ce4360ac9ad04c7a7edbc50e4763a2e5430a61c873
7
- data.tar.gz: 552545582ab8c34e1411834d5f5ef6d1b6f1d022ead68746059b709cd04d733c6d778b13621c003ec2f4525367bdb5e849577d109e2e04835fcf9b12407bf2ef
6
+ metadata.gz: 68b10554a23d62042db0d6d5307933ffdbb0fb58cc2452b78e1fca2e1a9b42095bc25e7e98b6b104f30a8be6cb0c4aad09212a57b1f4bbdbd2e2efad3501e3fc
7
+ data.tar.gz: 70594cb4f7cd5fb32be05aa0da60058e0fc9b50c9ca3ae4223e88a39293a5199e078eda52a2cb461f6782011aa22f05e3ca5efdc7b52cacc184f67a7326c13db
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.4.2 (2026-04-24)
6
+
7
+ - Add richer HTML coverage reports: summary cards, group coverage, sortable file tables, project-relative paths, and per-file source detail.
8
+ - Refactor HTML coverage rendering into stdlib `ERB` templates with `_*.html.erb` partials and isolated `report.css` / `report.js` assets; inline assets into final standalone report.
9
+ - Fix `track_files` coverage scope in `Collector.finish`: keep only files matched by tracked globs, drop unrelated loaded runtime files, and add unloaded tracked files only for non-sharded runs.
10
+ - Add coverage specs for divergent `track_under` / `track_files` configs in serial and sharded finish paths; add `TrackFiles.keep_tracked_files`.
11
+
12
+ ## 1.4.1 (2026-04-16)
13
+
14
+ - 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).
15
+ - 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.
16
+ - 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.
17
+ - 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.
18
+ - Fix `run_shards_plan_ready_log` to take `cfg` so debug logging for merge-failures does not raise `NameError`.
19
+
3
20
  ## 1.4.0 (2026-04-16)
4
21
 
5
22
  - Add `hooks:` in `polyrun.yml` — shell commands for `before_suite` / `after_suite`, `before_shard` / `after_shard`, `before_worker` / `after_worker` (RSpec-style YAML keys `before(:suite)`, `before(:all)`, `before(:each)` accepted). Wire hooks into `run-shards`, `parallel-rspec`, and `ci-shard-*`.
@@ -0,0 +1,92 @@
1
+ require "json"
2
+ require "fileutils"
3
+ require "optparse"
4
+
5
+ require_relative "../reporting/failure_merge"
6
+
7
+ module Polyrun
8
+ class CLI
9
+ module FailureCommands
10
+ private
11
+
12
+ def cmd_merge_failures(argv, _config_path)
13
+ inputs, output, format = merge_failures_parse_argv(argv)
14
+ if inputs.empty?
15
+ Polyrun::Log.warn "merge-failures: need at least one existing -i FILE (after glob expansion)"
16
+ return 2
17
+ end
18
+ Polyrun::Log.warn "merge-failures: merging #{inputs.size} fragment(s)" if @verbose
19
+ n = merge_failures_merge_files_or_warn!(inputs, output: output, format: format)
20
+ return 1 if n.nil?
21
+
22
+ Polyrun::Log.puts File.expand_path(output)
23
+ Polyrun::Log.warn "merge-failures: #{n} failure row(s)" if @verbose
24
+ 0
25
+ end
26
+
27
+ def merge_failures_merge_files_or_warn!(inputs, output:, format:)
28
+ Polyrun::Reporting::FailureMerge.merge_files!(inputs, output: output, format: format)
29
+ rescue Polyrun::Error => e
30
+ Polyrun::Log.warn e.message.to_s
31
+ nil
32
+ end
33
+
34
+ def merge_failures_parse_argv(argv)
35
+ inputs = []
36
+ output = File.join("tmp", "polyrun_failures", "merged.jsonl")
37
+ format = "jsonl"
38
+ OptionParser.new do |opts|
39
+ opts.banner = "usage: polyrun merge-failures -i FILE [-i FILE] [-o PATH] [--format jsonl|json]"
40
+ opts.on("-i", "--input FILE", "JSONL fragment or RSpec JSON (repeatable; globs ok)") do |f|
41
+ expand_merge_input_pattern(f).each { |x| inputs << x }
42
+ end
43
+ opts.on("-o", "--output PATH", String) { |v| output = v }
44
+ opts.on("--format VAL", "jsonl (default) or json") { |v| format = v }
45
+ end.parse!(argv)
46
+ inputs.uniq!
47
+ inputs.select! { |p| File.file?(p) }
48
+ [inputs, output, format]
49
+ end
50
+
51
+ # After run-shards workers exit: merge polyrun failure fragments when requested.
52
+ # Runs even when shards failed (unlike --merge-coverage).
53
+ # @return [String, nil] absolute path to merged file, or nil when skipped / nothing written
54
+ def merge_failures_after_shards(ctx)
55
+ return nil unless ctx[:merge_failures]
56
+
57
+ pattern = Polyrun::Reporting::FailureMerge.default_fragment_glob
58
+ files = Dir.glob(pattern).sort
59
+ if files.empty?
60
+ Polyrun::Log.warn "polyrun run-shards: --merge-failures: no #{Polyrun::Reporting::FailureMerge::FRAGMENT_GLOB} under fragment dir (enable Polyrun::RSpec.install_failure_fragments! in spec_helper?)"
61
+ return nil
62
+ end
63
+
64
+ fmt = merge_failures_resolved_format(ctx)
65
+ out = merge_failures_resolved_output_path(ctx, fmt)
66
+ Polyrun::Log.warn "polyrun run-shards: merging #{files.size} failure fragment(s) → #{out} (#{fmt})"
67
+ Polyrun::Debug.log_kv(merge_failures: "start", output: out, inputs: files, format: fmt)
68
+ n = Polyrun::Reporting::FailureMerge.merge_files!(files, output: out, format: fmt)
69
+ Polyrun::Debug.log_kv(merge_failures: "done", rows: n, output: File.expand_path(out))
70
+ File.expand_path(out)
71
+ end
72
+
73
+ def merge_failures_resolved_format(ctx)
74
+ f = ctx[:merge_failures_format].to_s.strip.downcase
75
+ return "jsonl" if f.empty?
76
+ return "jsonl" if f == "jsonl"
77
+ return "json" if f == "json"
78
+
79
+ Polyrun::Log.warn "polyrun run-shards: unknown merge_failures_format=#{ctx[:merge_failures_format].inspect}; using jsonl"
80
+ "jsonl"
81
+ end
82
+
83
+ def merge_failures_resolved_output_path(ctx, fmt)
84
+ raw = ctx[:merge_failures_output]
85
+ return File.expand_path(raw) if raw && !raw.to_s.strip.empty?
86
+
87
+ ext = (fmt == "json") ? "json" : "jsonl"
88
+ File.expand_path(File.join("tmp", "polyrun_failures", "merged.#{ext}"))
89
+ end
90
+ end
91
+ end
92
+ end
@@ -21,6 +21,7 @@ module Polyrun
21
21
  Skip start auto-prepare / auto DB provision: POLYRUN_START_SKIP_PREPARE=1, POLYRUN_START_SKIP_DATABASES=1
22
22
  Skip writing paths_file from partition.paths_build: POLYRUN_SKIP_PATHS_BUILD=1
23
23
  Warn if merge-coverage wall time exceeds N seconds (default 10): POLYRUN_MERGE_SLOW_WARN_SECONDS (0 disables)
24
+ Failure fragments (run-shards --merge-failures): POLYRUN_MERGE_FAILURES=1; parent sets POLYRUN_FAILURE_FRAGMENTS=1 in workers; POLYRUN_FAILURE_FRAGMENT_DIR, POLYRUN_MERGED_FAILURES_OUT, POLYRUN_MERGED_FAILURES_FORMAT; after_suite sets POLYRUN_MERGED_FAILURES_PATH when merge ran
24
25
  Parallel RSpec workers: POLYRUN_WORKERS default 5, max 10 (run-shards / parallel-rspec / start); distinct from POLYRUN_SHARD_PROCESSES / ci-shard --shard-processes (local processes per CI matrix job)
25
26
  Partition timing granularity (default file): POLYRUN_TIMING_GRANULARITY=file|example (experimental per-example; see partition.timing_granularity)
26
27
 
@@ -29,7 +30,8 @@ module Polyrun
29
30
  plan emit partition manifest JSON
30
31
  prepare run prepare recipe: default | assets (optional prepare.command overrides bin/rails assets:precompile) | shell (prepare.command required)
31
32
  merge-coverage merge SimpleCov JSON fragments (json/lcov/cobertura/console)
32
- run-shards fan out N parallel OS processes (POLYRUN_SHARD_*; not Ruby threads); optional --merge-coverage
33
+ merge-failures merge per-shard failure JSONL fragments or RSpec JSON files (jsonl/json)
34
+ run-shards fan out N parallel OS processes (POLYRUN_SHARD_*; not Ruby threads); optional --merge-coverage / --merge-failures
33
35
  parallel-rspec run-shards + merge-coverage (defaults to: bundle exec rspec after --)
34
36
  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
35
37
  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
@@ -2,12 +2,14 @@ require "optparse"
2
2
  require "rbconfig"
3
3
 
4
4
  require_relative "start_bootstrap"
5
+ require_relative "failure_commands"
5
6
  require_relative "run_shards_run"
6
7
 
7
8
  module Polyrun
8
9
  class CLI
9
10
  module RunShardsCommand
10
11
  include StartBootstrap
12
+ include FailureCommands
11
13
  include RunShardsRun
12
14
 
13
15
  private
@@ -114,10 +116,11 @@ module Polyrun
114
116
  # ENV for a worker process: POLYRUN_SHARD_* plus per-shard database URLs from polyrun.yml or DATABASE_URL.
115
117
  # When +matrix_total+ > 1 with multiple local workers, sets +POLYRUN_SHARD_MATRIX_INDEX+ / +POLYRUN_SHARD_MATRIX_TOTAL+
116
118
  # so {Coverage::Collector} can name fragments uniquely across CI matrix jobs (NxM sharding).
117
- def shard_child_env(cfg:, workers:, shard:, matrix_index: nil, matrix_total: nil)
119
+ def shard_child_env(cfg:, workers:, shard:, matrix_index: nil, matrix_total: nil, failure_fragments: false)
118
120
  child_env = ENV.to_h.merge(
119
121
  Polyrun::Database::Shard.env_map(shard_index: shard, shard_total: workers)
120
122
  )
123
+ child_env["POLYRUN_FAILURE_FRAGMENTS"] = "1" if failure_fragments
121
124
  mt = matrix_total.nil? ? 0 : Integer(matrix_total)
122
125
  if mt > 1
123
126
  if matrix_index.nil?
@@ -34,7 +34,14 @@ module Polyrun
34
34
  return [pids, code]
35
35
  end
36
36
 
37
- child_env = shard_child_env(cfg: cfg, workers: workers, shard: shard, matrix_index: mx, matrix_total: mt)
37
+ child_env = shard_child_env(
38
+ cfg: cfg,
39
+ workers: workers,
40
+ shard: shard,
41
+ matrix_index: mx,
42
+ matrix_total: mt,
43
+ failure_fragments: ctx[:merge_failures]
44
+ )
38
45
  child_env = child_env.merge("POLYRUN_HOOK_ORCHESTRATOR" => "0")
39
46
  child_env = hook_cfg.merge_worker_ruby_env(child_env)
40
47
 
@@ -31,7 +31,7 @@ module Polyrun
31
31
  costs, strategy, err = run_shards_resolve_costs(o[:timing_path], o[:strategy], o[:timing_granularity])
32
32
  return [err, nil] if err
33
33
 
34
- run_shards_plan_ready_log(o, strategy, cmd, paths_source, items.size)
34
+ run_shards_plan_ready_log(o, cfg, strategy, cmd, paths_source, items.size)
35
35
 
36
36
  constraints = load_partition_constraints(pc, o[:constraints_path])
37
37
  plan = run_shards_make_plan(items, o[:workers], strategy, o[:seed], costs, constraints, o[:timing_granularity])
@@ -59,12 +59,13 @@ module Polyrun
59
59
  [run_t0, head, cmd, cfg, cfg.partition]
60
60
  end
61
61
 
62
- def run_shards_plan_ready_log(o, strategy, cmd, paths_source, item_count)
62
+ def run_shards_plan_ready_log(o, cfg, strategy, cmd, paths_source, item_count)
63
63
  Polyrun::Debug.log_kv(
64
64
  run_shards: "ready to partition",
65
65
  workers: o[:workers],
66
66
  strategy: strategy,
67
67
  merge_coverage: o[:merge_coverage],
68
+ merge_failures: run_shards_merge_failures_flag(o, cfg),
68
69
  command: cmd,
69
70
  timing_path: o[:timing_path],
70
71
  paths_source: paths_source,
@@ -72,6 +73,37 @@ module Polyrun
72
73
  )
73
74
  end
74
75
 
76
+ def run_shards_merge_failures_flag(o, cfg)
77
+ return true if o[:merge_failures]
78
+ return true if %w[1 true yes].include?(ENV["POLYRUN_MERGE_FAILURES"].to_s.downcase)
79
+
80
+ rep = cfg.reporting
81
+ v = rep["merge_failures"] || rep[:merge_failures]
82
+ v == true || %w[1 true yes].include?(v.to_s.downcase)
83
+ end
84
+
85
+ def run_shards_merge_failures_output_opt(o, cfg)
86
+ x = o[:merge_failures_output]
87
+ return x if x && !x.to_s.strip.empty?
88
+
89
+ x = ENV["POLYRUN_MERGED_FAILURES_OUT"]
90
+ return x if x && !x.to_s.strip.empty?
91
+
92
+ rep = cfg.reporting
93
+ rep["merge_failures_output"] || rep[:merge_failures_output]
94
+ end
95
+
96
+ def run_shards_merge_failures_format_opt(o, cfg)
97
+ x = o[:merge_failures_format]
98
+ return x if x && !x.to_s.strip.empty?
99
+
100
+ x = ENV["POLYRUN_MERGED_FAILURES_FORMAT"]
101
+ return x if x && !x.to_s.strip.empty?
102
+
103
+ rep = cfg.reporting
104
+ rep["merge_failures_format"] || rep[:merge_failures_format]
105
+ end
106
+
75
107
  def run_shards_plan_context_hash(o, cmd, cfg, plan, run_t0, parallel, config_path)
76
108
  {
77
109
  workers: o[:workers],
@@ -83,6 +115,9 @@ module Polyrun
83
115
  merge_coverage: o[:merge_coverage],
84
116
  merge_output: o[:merge_output],
85
117
  merge_format: o[:merge_format],
118
+ merge_failures: run_shards_merge_failures_flag(o, cfg),
119
+ merge_failures_output: run_shards_merge_failures_output_opt(o, cfg),
120
+ merge_failures_format: run_shards_merge_failures_format_opt(o, cfg),
86
121
  config_path: config_path
87
122
  }
88
123
  end
@@ -24,7 +24,10 @@ module Polyrun
24
24
  timing_granularity: nil,
25
25
  merge_coverage: false,
26
26
  merge_output: nil,
27
- merge_format: nil
27
+ merge_format: nil,
28
+ merge_failures: false,
29
+ merge_failures_output: nil,
30
+ merge_failures_format: nil
28
31
  }
29
32
  end
30
33
 
@@ -34,8 +37,9 @@ module Polyrun
34
37
  end.parse!(head)
35
38
  end
36
39
 
40
+ # rubocop:disable Metrics/AbcSize -- one argv block for run-shards
37
41
  def run_shards_plan_options_register!(opts, st)
38
- opts.banner = "usage: polyrun run-shards [--workers N] [--strategy NAME] [--paths-file P] [--timing P] [--timing-granularity VAL] [--constraints P] [--seed S] [--merge-coverage] [--merge-output P] [--merge-format LIST] [--] <command> [args...]"
42
+ opts.banner = "usage: polyrun run-shards [--workers N] [--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] [--] <command> [args...]"
39
43
  opts.on("--workers N", Integer) { |v| st[:workers] = v }
40
44
  opts.on("--strategy NAME", String) { |v| st[:strategy] = v }
41
45
  opts.on("--seed VAL") { |v| st[:seed] = v }
@@ -46,7 +50,11 @@ module Polyrun
46
50
  opts.on("--merge-coverage", "After success, merge coverage/polyrun-fragment-*.json (Polyrun coverage must be enabled)") { st[:merge_coverage] = true }
47
51
  opts.on("--merge-output PATH", String) { |v| st[:merge_output] = v }
48
52
  opts.on("--merge-format LIST", String) { |v| st[:merge_format] = v }
53
+ opts.on("--merge-failures", "After all workers exit, merge tmp/polyrun_failures/polyrun-failure-fragment-*.jsonl (use Polyrun::RSpec.install_failure_fragments!)") { st[:merge_failures] = true }
54
+ opts.on("--merge-failures-output PATH", String) { |v| st[:merge_failures_output] = v }
55
+ opts.on("--merge-failures-format VAL", "jsonl (default) or json") { |v| st[:merge_failures_format] = v }
49
56
  end
57
+ # rubocop:enable Metrics/AbcSize
50
58
  end
51
59
  end
52
60
  end
@@ -25,6 +25,8 @@ module Polyrun
25
25
  hook_cfg = Polyrun::Hooks.from_config(ctx[:cfg])
26
26
  suite_started = false
27
27
  exit_code = 1
28
+ merged_failures_path = nil
29
+ merge_failures_errored = false
28
30
 
29
31
  begin
30
32
  env_suite = ENV.to_h.merge(
@@ -60,8 +62,20 @@ module Polyrun
60
62
  Polyrun::Log.warn "polyrun run-shards: finished #{pids.size} worker(s)" + (failed.any? ? " (some failed)" : " (exit 0)")
61
63
  end
62
64
 
65
+ if ctx[:merge_failures]
66
+ begin
67
+ merged_failures_path = merge_failures_after_shards(ctx)
68
+ rescue Polyrun::Error => e
69
+ Polyrun::Log.warn e.message.to_s
70
+ merge_failures_errored = true
71
+ end
72
+ end
73
+
63
74
  if failed.any?
64
- run_shards_log_failed_reruns(failed, shard_results, ctx[:plan], ctx[:parallel], ctx[:workers], ctx[:cmd])
75
+ run_shards_log_failed_reruns(
76
+ failed, shard_results, ctx[:plan], ctx[:parallel], ctx[:workers], ctx[:cmd],
77
+ merge_failures: ctx[:merge_failures]
78
+ )
65
79
  exit_code = 1
66
80
  exit_code = 1 if wait_hook_err != 0
67
81
  return exit_code
@@ -69,13 +83,15 @@ module Polyrun
69
83
 
70
84
  exit_code = run_shards_merge_or_hint_coverage(ctx)
71
85
  exit_code = 1 if wait_hook_err != 0 && exit_code == 0
86
+ exit_code = 1 if merge_failures_errored && exit_code == 0
72
87
  exit_code
73
88
  ensure
74
89
  if suite_started
75
90
  env_after = ENV.to_h.merge(
76
91
  "POLYRUN_HOOK_ORCHESTRATOR" => "1",
77
92
  "POLYRUN_SHARD_TOTAL" => ctx[:workers].to_s,
78
- "POLYRUN_SUITE_EXIT_STATUS" => exit_code.to_s
93
+ "POLYRUN_SUITE_EXIT_STATUS" => exit_code.to_s,
94
+ "POLYRUN_MERGED_FAILURES_PATH" => merged_failures_path.to_s
79
95
  )
80
96
  hook_cfg.run_phase_if_enabled(:after_suite, env_after)
81
97
  end
@@ -139,7 +155,7 @@ module Polyrun
139
155
  0
140
156
  end
141
157
 
142
- def run_shards_log_failed_reruns(failed, shard_results, plan, parallel, workers, cmd)
158
+ def run_shards_log_failed_reruns(failed, shard_results, plan, parallel, workers, cmd, merge_failures: false)
143
159
  exit_by_shard = shard_results.each_with_object({}) { |r, h| h[r[:shard]] = r[:exitstatus] }
144
160
  failed_detail = failed.sort.map { |s| "#{s} (exit #{exit_by_shard[s]})" }.join(", ")
145
161
  Polyrun::Log.warn "polyrun run-shards: failed shard(s): #{failed_detail}"
@@ -154,6 +170,9 @@ module Polyrun
154
170
  rerun << Shellwords.join(cmd + paths)
155
171
  Polyrun::Log.warn "polyrun run-shards: shard #{s} re-run (same spec list, no interleave): #{rerun}"
156
172
  end
173
+ unless merge_failures
174
+ Polyrun::Log.warn "polyrun run-shards: one merged failure report — use run-shards --merge-failures with Polyrun::RSpec.install_failure_fragments!; POLYRUN_MERGED_FAILURES_PATH is set on after_suite when merge runs."
175
+ end
157
176
  end
158
177
  end
159
178
  end
data/lib/polyrun/cli.rb CHANGED
@@ -28,7 +28,7 @@ module Polyrun
28
28
 
29
29
  # Keep in sync with +dispatch_cli_command_subcommands+ (+when+ branches). Used for implicit path routing.
30
30
  DISPATCH_SUBCOMMAND_NAMES = %w[
31
- plan prepare merge-coverage report-coverage report-junit report-timing
31
+ plan prepare merge-coverage merge-failures report-coverage report-junit report-timing
32
32
  env config merge-timing db:setup-template db:setup-shard db:clone-shards
33
33
  run-shards parallel-rspec start build-paths init queue quick hook
34
34
  ].freeze
@@ -145,6 +145,8 @@ module Polyrun
145
145
  cmd_prepare(argv, config_path)
146
146
  when "merge-coverage"
147
147
  cmd_merge_coverage(argv, config_path)
148
+ when "merge-failures"
149
+ cmd_merge_failures(argv, config_path)
148
150
  when "report-coverage"
149
151
  cmd_report_coverage(argv)
150
152
  when "report-junit"
@@ -66,6 +66,11 @@ module Polyrun
66
66
  def hooks
67
67
  raw["hooks"] || raw[:hooks] || {}
68
68
  end
69
+
70
+ # Optional +reporting:+ block (merge-failures output paths, etc.).
71
+ def reporting
72
+ raw["reporting"] || raw[:reporting] || {}
73
+ end
69
74
  end
70
75
  end
71
76
 
@@ -41,9 +41,10 @@ module Polyrun
41
41
  def self.track_blob_for_finish(cfg, blob)
42
42
  sharded = ENV["POLYRUN_SHARD_TOTAL"].to_i > 1
43
43
  if cfg[:track_files]
44
- return Collector.keep_under_root(blob, cfg[:root], cfg[:track_under]) if sharded
44
+ filtered = TrackFiles.keep_tracked_files(blob, cfg[:root], cfg[:track_files])
45
+ return filtered if sharded
45
46
 
46
- TrackFiles.merge_untracked_into_blob(blob, cfg[:root], cfg[:track_files])
47
+ TrackFiles.merge_untracked_into_blob(filtered, cfg[:root], cfg[:track_files])
47
48
  else
48
49
  Collector.keep_under_root(blob, cfg[:root], cfg[:track_under])
49
50
  end
@@ -106,7 +106,8 @@ module Polyrun
106
106
  def write_files(result, output_dir, basename)
107
107
  path = File.join(output_dir, "#{basename}.html")
108
108
  title = (result.meta && result.meta["title"]) || (result.meta && result.meta[:title]) || "Polyrun coverage"
109
- File.write(path, Merge.emit_html(result.coverage_blob, title: title))
109
+ root = result.meta && (result.meta["polyrun_coverage_root"] || result.meta[:polyrun_coverage_root])
110
+ File.write(path, Merge.emit_html(result.coverage_blob, title: title, root: root, groups: result.groups))
110
111
  {html: path}
111
112
  end
112
113
  end