Module: Polyrun::Reporting::FailureMerge
- Defined in:
- lib/polyrun/reporting/failure_merge.rb
Overview
Merge per-worker / per-shard failure fragments (JSONL or RSpec JSON) into one report. Fragment basenames align with Coverage::CollectorFragmentMeta (worker index and optional matrix shard).
Constant Summary collapse
- DEFAULT_FRAGMENT_DIR =
"tmp/polyrun_failures".freeze
- FRAGMENT_GLOB =
"polyrun-failure-fragment-*.jsonl".freeze
- CSV_HEADERS =
%w[ id full_description location file_path line_number message exception_class polyrun_shard_index polyrun_shard_total source ].freeze
Class Method Summary collapse
- .collect_rows(paths) ⇒ Object
- .default_fragment_glob(dir = nil) ⇒ Object
- .failures_from_rspec_examples(examples) ⇒ Object
-
.merge_files!(paths, output:, format: "jsonl") ⇒ Integer
Count of failure rows merged.
- .merge_fragment_paths(quiet: false) ⇒ Object
- .parse_jsonl_line!(path, line_number, line) ⇒ Object
- .rows_from_jsonl_file(path) ⇒ Object
- .rows_from_path(path) ⇒ Object
- .rspec_example_to_row(ex) ⇒ Object
- .write_csv_output!(out_abs, rows) ⇒ Object
- .write_json_output!(out_abs, paths, rows) ⇒ Object
- .write_jsonl_output!(out_abs, rows) ⇒ Object
- .write_markdown_output!(out_abs, rows) ⇒ Object
Class Method Details
.collect_rows(paths) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 90 def collect_rows(paths) rows = [] paths.each do |p| rows.concat(rows_from_path(p)) end rows end |
.default_fragment_glob(dir = nil) ⇒ Object
21 22 23 24 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 21 def default_fragment_glob(dir = nil) root = File.(dir || DEFAULT_FRAGMENT_DIR, Dir.pwd) File.join(root, FRAGMENT_GLOB) end |
.failures_from_rspec_examples(examples) ⇒ Object
145 146 147 148 149 150 151 152 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 145 def failures_from_rspec_examples(examples) examples.each_with_object([]) do |ex, acc| next unless ex.is_a?(Hash) next unless ex["status"].to_s == "failed" acc << rspec_example_to_row(ex) end end |
.merge_files!(paths, output:, format: "jsonl") ⇒ Integer
Returns count of failure rows merged.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 37 def merge_files!(paths, output:, format: "jsonl") fmt = format.to_s.downcase rows = collect_rows(paths) out_abs = File.(output) FileUtils.mkdir_p(File.dirname(out_abs)) case fmt when "json" write_json_output!(out_abs, paths, rows) when "jsonl" write_jsonl_output!(out_abs, rows) when "csv" write_csv_output!(out_abs, rows) when "markdown", "md" write_markdown_output!(out_abs, rows) else raise Polyrun::Error, "merge-failures: unknown format #{fmt.inspect} (use jsonl, json, csv, or markdown)" end rows.size end |
.merge_fragment_paths(quiet: false) ⇒ Object
26 27 28 29 30 31 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 26 def merge_fragment_paths(quiet: false) p = default_fragment_glob Dir.glob(p).sort.tap do |paths| Polyrun::Log.warn "merge-failures: no files matched #{p}" if paths.empty? && !quiet end end |
.parse_jsonl_line!(path, line_number, line) ⇒ Object
138 139 140 141 142 143 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 138 def parse_jsonl_line!(path, line_number, line) JSON.parse(line) rescue JSON::ParserError => e raise Polyrun::Error, "merge-failures: invalid JSONL at #{path} line #{line_number}: #{e.}" end |
.rows_from_jsonl_file(path) ⇒ Object
127 128 129 130 131 132 133 134 135 136 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 127 def rows_from_jsonl_file(path) acc = [] File.readlines(path, chomp: true).each_with_index do |line, idx| line = line.strip next if line.empty? acc << parse_jsonl_line!(path, idx + 1, line) end acc end |
.rows_from_path(path) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 98 def rows_from_path(path) ext = File.extname(path).downcase if ext == ".jsonl" return rows_from_jsonl_file(path) end text = File.read(path) data = begin JSON.parse(text) rescue JSON::ParserError => e raise Polyrun::Error, "merge-failures: #{path} is not valid JSON: #{e.}" end if data.is_a?(Hash) && data["examples"].is_a?(Array) return failures_from_rspec_examples(data["examples"]) end hint = if data.is_a?(Hash) keys = data.keys "got JSON object with keys: #{keys.take(12).join(", ")}" + ((keys.size > 12) ? ", …" : "") else "got #{data.class}" end raise Polyrun::Error, "merge-failures: #{path} is not RSpec JSON (expected top-level \"examples\" array). #{hint}. " \ "Use RSpec --format json, or polyrun failure JSONL (.jsonl fragments)." end |
.rspec_example_to_row(ex) ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 154 def rspec_example_to_row(ex) ex = ex.transform_keys(&:to_s) exc = ex["exception"] || {} exc = exc.transform_keys(&:to_s) if exc.is_a?(Hash) { "id" => ex["id"], "full_description" => ex["full_description"], "location" => (ex["file_path"] && ex["line_number"]) ? "#{ex["file_path"]}:#{ex["line_number"]}" : ex["full_description"], "file_path" => ex["file_path"], "line_number" => ex["line_number"], "message" => exc["message"] || ex["full_description"], "exception_class" => exc["class"], "source" => "rspec_json" }.compact end |
.write_csv_output!(out_abs, rows) ⇒ Object
73 74 75 76 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 73 def write_csv_output!(out_abs, rows) csv_rows = rows.map { |row| CSV_HEADERS.map { |key| row[key] } } File.write(out_abs, Export::Csv.generate(CSV_HEADERS, csv_rows)) end |
.write_json_output!(out_abs, paths, rows) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 57 def write_json_output!(out_abs, paths, rows) doc = { "meta" => { "polyrun_merge" => true, "inputs" => paths.map { |p| File.(p) }, "failure_count" => rows.size }, "failures" => rows } File.write(out_abs, JSON.generate(doc)) end |
.write_jsonl_output!(out_abs, rows) ⇒ Object
69 70 71 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 69 def write_jsonl_output!(out_abs, rows) File.write(out_abs, rows.map { |h| JSON.generate(h) }.join("\n") + (rows.empty? ? "" : "\n")) end |
.write_markdown_output!(out_abs, rows) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/polyrun/reporting/failure_merge.rb', line 78 def write_markdown_output!(out_abs, rows) sections = rows.map.with_index(1) do |row, index| { heading: "Failure #{index}: #{row["full_description"] || row["location"] || row["id"]}", headers: %w[field value], rows: row.sort_by { |key, _| key.to_s }.map { |key, value| [key, value] } } end sections = [{heading: "Failures", body: "(none)"}] if sections.empty? File.write(out_abs, Export::Markdown.document("Polyrun failure report", sections)) end |