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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/polyrun/cli/failure_commands.rb +92 -0
- data/lib/polyrun/cli/help.rb +3 -1
- data/lib/polyrun/cli/run_shards_command.rb +4 -1
- data/lib/polyrun/cli/run_shards_parallel_children.rb +8 -1
- data/lib/polyrun/cli/run_shards_plan_boot_phases.rb +37 -2
- data/lib/polyrun/cli/run_shards_plan_options.rb +10 -2
- data/lib/polyrun/cli/run_shards_run.rb +22 -3
- data/lib/polyrun/cli.rb +3 -1
- data/lib/polyrun/config.rb +5 -0
- data/lib/polyrun/coverage/collector_finish.rb +3 -2
- data/lib/polyrun/coverage/formatter.rb +2 -1
- data/lib/polyrun/coverage/merge/formatters_html.rb +187 -43
- data/lib/polyrun/coverage/merge/html/_file_list.html.erb +21 -0
- data/lib/polyrun/coverage/merge/html/_file_section.html.erb +26 -0
- data/lib/polyrun/coverage/merge/html/_groups_table.html.erb +18 -0
- data/lib/polyrun/coverage/merge/html/_overview.html.erb +47 -0
- data/lib/polyrun/coverage/merge/html/report.css +147 -0
- data/lib/polyrun/coverage/merge/html/report.js +48 -0
- data/lib/polyrun/coverage/merge/html/template.html.erb +30 -0
- data/lib/polyrun/coverage/track_files.rb +9 -0
- data/lib/polyrun/reporting/failure_merge.rb +135 -0
- data/lib/polyrun/reporting/rspec_failure_fragment_formatter.rb +95 -0
- data/lib/polyrun/rspec.rb +14 -0
- data/lib/polyrun/version.rb +1 -1
- metadata +11 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Polyrun
|
|
5
|
+
module Reporting
|
|
6
|
+
# Merge per-worker / per-shard failure fragments (JSONL or RSpec JSON) into one report.
|
|
7
|
+
# Fragment basenames align with {Coverage::CollectorFragmentMeta} (worker index and optional matrix shard).
|
|
8
|
+
module FailureMerge
|
|
9
|
+
DEFAULT_FRAGMENT_DIR = "tmp/polyrun_failures".freeze
|
|
10
|
+
FRAGMENT_GLOB = "polyrun-failure-fragment-*.jsonl".freeze
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def default_fragment_glob(dir = nil)
|
|
15
|
+
root = File.expand_path(dir || DEFAULT_FRAGMENT_DIR, Dir.pwd)
|
|
16
|
+
File.join(root, FRAGMENT_GLOB)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def merge_fragment_paths(quiet: false)
|
|
20
|
+
p = default_fragment_glob
|
|
21
|
+
Dir.glob(p).sort.tap do |paths|
|
|
22
|
+
Polyrun::Log.warn "merge-failures: no files matched #{p}" if paths.empty? && !quiet
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param paths [Array<String>] fragment paths (.jsonl and/or RSpec --format json outputs)
|
|
27
|
+
# @param format [String] "jsonl" or "json"
|
|
28
|
+
# @param output [String] destination path
|
|
29
|
+
# @return [Integer] count of failure rows merged
|
|
30
|
+
def merge_files!(paths, output:, format: "jsonl")
|
|
31
|
+
fmt = format.to_s.downcase
|
|
32
|
+
rows = collect_rows(paths)
|
|
33
|
+
out_abs = File.expand_path(output)
|
|
34
|
+
FileUtils.mkdir_p(File.dirname(out_abs))
|
|
35
|
+
case fmt
|
|
36
|
+
when "json"
|
|
37
|
+
doc = {
|
|
38
|
+
"meta" => {
|
|
39
|
+
"polyrun_merge" => true,
|
|
40
|
+
"inputs" => paths.map { |p| File.expand_path(p) },
|
|
41
|
+
"failure_count" => rows.size
|
|
42
|
+
},
|
|
43
|
+
"failures" => rows
|
|
44
|
+
}
|
|
45
|
+
File.write(out_abs, JSON.generate(doc))
|
|
46
|
+
when "jsonl"
|
|
47
|
+
File.write(out_abs, rows.map { |h| JSON.generate(h) }.join("\n") + (rows.empty? ? "" : "\n"))
|
|
48
|
+
else
|
|
49
|
+
raise Polyrun::Error, "merge-failures: unknown format #{fmt.inspect} (use jsonl or json)"
|
|
50
|
+
end
|
|
51
|
+
rows.size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def collect_rows(paths)
|
|
55
|
+
rows = []
|
|
56
|
+
paths.each do |p|
|
|
57
|
+
rows.concat(rows_from_path(p))
|
|
58
|
+
end
|
|
59
|
+
rows
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rows_from_path(path)
|
|
63
|
+
ext = File.extname(path).downcase
|
|
64
|
+
if ext == ".jsonl"
|
|
65
|
+
return rows_from_jsonl_file(path)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
text = File.read(path)
|
|
69
|
+
data =
|
|
70
|
+
begin
|
|
71
|
+
JSON.parse(text)
|
|
72
|
+
rescue JSON::ParserError => e
|
|
73
|
+
raise Polyrun::Error, "merge-failures: #{path} is not valid JSON: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
if data.is_a?(Hash) && data["examples"].is_a?(Array)
|
|
76
|
+
return failures_from_rspec_examples(data["examples"])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
hint =
|
|
80
|
+
if data.is_a?(Hash)
|
|
81
|
+
keys = data.keys
|
|
82
|
+
"got JSON object with keys: #{keys.take(12).join(", ")}" + ((keys.size > 12) ? ", …" : "")
|
|
83
|
+
else
|
|
84
|
+
"got #{data.class}"
|
|
85
|
+
end
|
|
86
|
+
raise Polyrun::Error,
|
|
87
|
+
"merge-failures: #{path} is not RSpec JSON (expected top-level \"examples\" array). #{hint}. " \
|
|
88
|
+
"Use RSpec --format json, or polyrun failure JSONL (.jsonl fragments)."
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def rows_from_jsonl_file(path)
|
|
92
|
+
acc = []
|
|
93
|
+
File.readlines(path, chomp: true).each_with_index do |line, idx|
|
|
94
|
+
line = line.strip
|
|
95
|
+
next if line.empty?
|
|
96
|
+
|
|
97
|
+
acc << parse_jsonl_line!(path, idx + 1, line)
|
|
98
|
+
end
|
|
99
|
+
acc
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_jsonl_line!(path, line_number, line)
|
|
103
|
+
JSON.parse(line)
|
|
104
|
+
rescue JSON::ParserError => e
|
|
105
|
+
raise Polyrun::Error,
|
|
106
|
+
"merge-failures: invalid JSONL at #{path} line #{line_number}: #{e.message}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def failures_from_rspec_examples(examples)
|
|
110
|
+
examples.each_with_object([]) do |ex, acc|
|
|
111
|
+
next unless ex.is_a?(Hash)
|
|
112
|
+
next unless ex["status"].to_s == "failed"
|
|
113
|
+
|
|
114
|
+
acc << rspec_example_to_row(ex)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def rspec_example_to_row(ex)
|
|
119
|
+
ex = ex.transform_keys(&:to_s)
|
|
120
|
+
exc = ex["exception"] || {}
|
|
121
|
+
exc = exc.transform_keys(&:to_s) if exc.is_a?(Hash)
|
|
122
|
+
{
|
|
123
|
+
"id" => ex["id"],
|
|
124
|
+
"full_description" => ex["full_description"],
|
|
125
|
+
"location" => (ex["file_path"] && ex["line_number"]) ? "#{ex["file_path"]}:#{ex["line_number"]}" : ex["full_description"],
|
|
126
|
+
"file_path" => ex["file_path"],
|
|
127
|
+
"line_number" => ex["line_number"],
|
|
128
|
+
"message" => exc["message"] || ex["full_description"],
|
|
129
|
+
"exception_class" => exc["class"],
|
|
130
|
+
"source" => "rspec_json"
|
|
131
|
+
}.compact
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module Polyrun
|
|
5
|
+
module Reporting
|
|
6
|
+
# RSpec formatter: appends one JSON object per failed example to the shard fragment file.
|
|
7
|
+
# Enable via +Polyrun::RSpec.install_failure_fragments!+ and +POLYRUN_FAILURE_FRAGMENTS=1+ (set by run-shards --merge-failures).
|
|
8
|
+
#
|
|
9
|
+
# Output: +tmp/polyrun_failures/polyrun-failure-fragment-<workerN|shardM-workerN>.jsonl+
|
|
10
|
+
# (same basename rules as {Coverage::CollectorFragmentMeta}.)
|
|
11
|
+
class RspecFailureFragmentFormatter
|
|
12
|
+
::RSpec::Core::Formatters.register self, :start, :example_failed
|
|
13
|
+
|
|
14
|
+
attr_reader :output
|
|
15
|
+
|
|
16
|
+
def initialize(output)
|
|
17
|
+
@output = output
|
|
18
|
+
@path = fragment_path
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def start(_notification)
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
23
|
+
File.write(@path, "")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def example_failed(notification)
|
|
27
|
+
ex = notification.example
|
|
28
|
+
exc = notification.exception
|
|
29
|
+
row = {
|
|
30
|
+
"id" => ex.id,
|
|
31
|
+
"full_description" => ex.full_description,
|
|
32
|
+
"location" => ex.location,
|
|
33
|
+
"file_path" => ex.file_path,
|
|
34
|
+
"line_number" => example_line_number(ex),
|
|
35
|
+
"message" => exc.message.to_s,
|
|
36
|
+
"exception_class" => exc.class.name,
|
|
37
|
+
"polyrun_shard_index" => ENV["POLYRUN_SHARD_INDEX"],
|
|
38
|
+
"polyrun_shard_total" => ENV["POLYRUN_SHARD_TOTAL"],
|
|
39
|
+
"polyrun_shard_matrix_index" => matrix_env_or_nil("POLYRUN_SHARD_MATRIX_INDEX"),
|
|
40
|
+
"polyrun_shard_matrix_total" => matrix_env_or_nil("POLYRUN_SHARD_MATRIX_TOTAL"),
|
|
41
|
+
"rspec_seed" => seed_if_known,
|
|
42
|
+
"rspec_order" => order_if_known
|
|
43
|
+
}
|
|
44
|
+
trim_backtrace!(row, exc)
|
|
45
|
+
File.open(@path, "a") { |f| f.puts(JSON.generate(row.compact)) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def example_line_number(ex)
|
|
51
|
+
return ex.line_number if ex.respond_to?(:line_number)
|
|
52
|
+
|
|
53
|
+
ex.metadata[:line_number]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fragment_path
|
|
57
|
+
dir = ENV.fetch("POLYRUN_FAILURE_FRAGMENT_DIR", FailureMerge::DEFAULT_FRAGMENT_DIR)
|
|
58
|
+
base = Polyrun::Coverage::CollectorFragmentMeta.fragment_default_basename_from_env
|
|
59
|
+
File.expand_path(File.join(dir, "polyrun-failure-fragment-#{base}.jsonl"))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def matrix_env_or_nil(name)
|
|
63
|
+
v = ENV[name]
|
|
64
|
+
return nil if v.nil? || v.to_s.strip.empty?
|
|
65
|
+
|
|
66
|
+
v
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def seed_if_known
|
|
70
|
+
return unless defined?(::RSpec) && ::RSpec.respond_to?(:configuration)
|
|
71
|
+
|
|
72
|
+
::RSpec.configuration.seed
|
|
73
|
+
rescue
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def order_if_known
|
|
78
|
+
return unless defined?(::RSpec) && ::RSpec.respond_to?(:configuration)
|
|
79
|
+
|
|
80
|
+
::RSpec.configuration.order.to_s
|
|
81
|
+
rescue
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
MAX_BT = 20
|
|
86
|
+
|
|
87
|
+
def trim_backtrace!(row, exc)
|
|
88
|
+
bt = exc.backtrace
|
|
89
|
+
return unless bt.is_a?(Array) && bt.any?
|
|
90
|
+
|
|
91
|
+
row["backtrace"] = bt.first(MAX_BT)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/polyrun/rspec.rb
CHANGED
|
@@ -30,5 +30,19 @@ module Polyrun
|
|
|
30
30
|
config.add_formatter fmt
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
# Per-worker failure JSONL fragments for +polyrun run-shards --merge-failures+ (parity with coverage shards).
|
|
35
|
+
# Requires +POLYRUN_FAILURE_FRAGMENTS=1+ (set by the parent when --merge-failures is used) unless +only_if+ overrides.
|
|
36
|
+
# Writes +tmp/polyrun_failures/polyrun-failure-fragment-*.jsonl+ (override dir with +POLYRUN_FAILURE_FRAGMENT_DIR+).
|
|
37
|
+
def install_failure_fragments!(only_if: nil)
|
|
38
|
+
pred = only_if || -> { %w[1 true yes].include?(ENV["POLYRUN_FAILURE_FRAGMENTS"].to_s.downcase) }
|
|
39
|
+
return unless pred.call
|
|
40
|
+
|
|
41
|
+
require "rspec/core"
|
|
42
|
+
require_relative "reporting/rspec_failure_fragment_formatter"
|
|
43
|
+
::RSpec.configure do |config|
|
|
44
|
+
config.add_formatter Polyrun::Reporting::RspecFailureFragmentFormatter
|
|
45
|
+
end
|
|
46
|
+
end
|
|
33
47
|
end
|
|
34
48
|
end
|
data/lib/polyrun/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: polyrun
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -175,6 +175,7 @@ files:
|
|
|
175
175
|
- lib/polyrun/cli/database_commands.rb
|
|
176
176
|
- lib/polyrun/cli/default_run.rb
|
|
177
177
|
- lib/polyrun/cli/env_commands.rb
|
|
178
|
+
- lib/polyrun/cli/failure_commands.rb
|
|
178
179
|
- lib/polyrun/cli/help.rb
|
|
179
180
|
- lib/polyrun/cli/helpers.rb
|
|
180
181
|
- lib/polyrun/cli/hooks_command.rb
|
|
@@ -206,6 +207,13 @@ files:
|
|
|
206
207
|
- lib/polyrun/coverage/merge.rb
|
|
207
208
|
- lib/polyrun/coverage/merge/formatters.rb
|
|
208
209
|
- lib/polyrun/coverage/merge/formatters_html.rb
|
|
210
|
+
- lib/polyrun/coverage/merge/html/_file_list.html.erb
|
|
211
|
+
- lib/polyrun/coverage/merge/html/_file_section.html.erb
|
|
212
|
+
- lib/polyrun/coverage/merge/html/_groups_table.html.erb
|
|
213
|
+
- lib/polyrun/coverage/merge/html/_overview.html.erb
|
|
214
|
+
- lib/polyrun/coverage/merge/html/report.css
|
|
215
|
+
- lib/polyrun/coverage/merge/html/report.js
|
|
216
|
+
- lib/polyrun/coverage/merge/html/template.html.erb
|
|
209
217
|
- lib/polyrun/coverage/merge_fragment_meta.rb
|
|
210
218
|
- lib/polyrun/coverage/merge_merge_two.rb
|
|
211
219
|
- lib/polyrun/coverage/rails.rb
|
|
@@ -258,8 +266,10 @@ files:
|
|
|
258
266
|
- lib/polyrun/quick/reporter.rb
|
|
259
267
|
- lib/polyrun/quick/runner.rb
|
|
260
268
|
- lib/polyrun/railtie.rb
|
|
269
|
+
- lib/polyrun/reporting/failure_merge.rb
|
|
261
270
|
- lib/polyrun/reporting/junit.rb
|
|
262
271
|
- lib/polyrun/reporting/junit_emit.rb
|
|
272
|
+
- lib/polyrun/reporting/rspec_failure_fragment_formatter.rb
|
|
263
273
|
- lib/polyrun/reporting/rspec_junit.rb
|
|
264
274
|
- lib/polyrun/rspec.rb
|
|
265
275
|
- lib/polyrun/templates/POLYRUN.md
|