polyrun 1.3.0 → 1.4.1
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 +16 -0
- data/README.md +58 -0
- data/lib/polyrun/cli/ci_shard_hooks.rb +121 -0
- data/lib/polyrun/cli/ci_shard_run_command.rb +6 -22
- data/lib/polyrun/cli/failure_commands.rb +92 -0
- data/lib/polyrun/cli/help.rb +4 -1
- data/lib/polyrun/cli/hooks_command.rb +97 -0
- data/lib/polyrun/cli/run_shards_command.rb +4 -1
- data/lib/polyrun/cli/run_shards_parallel_children.rb +99 -0
- 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 +73 -64
- data/lib/polyrun/cli.rb +8 -2
- data/lib/polyrun/config.rb +10 -0
- data/lib/polyrun/hooks/dsl.rb +128 -0
- data/lib/polyrun/hooks/worker_runner.rb +27 -0
- data/lib/polyrun/hooks/worker_shell.rb +50 -0
- data/lib/polyrun/hooks.rb +185 -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
- data/lib/polyrun.rb +1 -0
- metadata +11 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
require_relative "log"
|
|
4
|
+
require_relative "hooks/dsl"
|
|
5
|
+
require_relative "hooks/worker_runner"
|
|
6
|
+
require_relative "hooks/worker_shell"
|
|
7
|
+
|
|
8
|
+
module Polyrun
|
|
9
|
+
# Shell and Ruby DSL hooks around parallel orchestration, named like RSpec lifecycle callbacks:
|
|
10
|
+
# +before_suite+ / +after_suite+ (+before(:suite)+ / +after(:suite)+),
|
|
11
|
+
# +before_shard+ / +after_shard+ (parent process, per partition index),
|
|
12
|
+
# +before_worker+ / +after_worker+ (inside each worker process, around the test command).
|
|
13
|
+
#
|
|
14
|
+
# Configure under +hooks:+ in +polyrun.yml+: shell strings, and/or +ruby:+ path to a Ruby DSL file.
|
|
15
|
+
# Run manually: +polyrun hook run <phase>+ (see {CLI}).
|
|
16
|
+
#
|
|
17
|
+
# Orchestration respects +POLYRUN_HOOKS_DISABLE=1+ (+run_phase_if_enabled+); +polyrun hook run+ always runs {#run_phase}.
|
|
18
|
+
class Hooks
|
|
19
|
+
include WorkerShell
|
|
20
|
+
|
|
21
|
+
PHASES = %i[
|
|
22
|
+
before_suite after_suite
|
|
23
|
+
before_shard after_shard
|
|
24
|
+
before_worker after_worker
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
attr_reader :ruby_file
|
|
28
|
+
|
|
29
|
+
def self.disabled?
|
|
30
|
+
v = ENV["POLYRUN_HOOKS_DISABLE"].to_s.downcase
|
|
31
|
+
%w[1 true yes].include?(v)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# When +POLYRUN_SHARD_TOTAL+ is greater than 1 (+ci-shard-run+ matrix), suite hooks are skipped by default; set
|
|
35
|
+
# +POLYRUN_HOOKS_SUITE_PER_MATRIX_JOB=1+ to run +before_suite+ / +after_suite+ on every matrix job (legacy).
|
|
36
|
+
def self.suite_per_matrix_job?
|
|
37
|
+
v = ENV["POLYRUN_HOOKS_SUITE_PER_MATRIX_JOB"].to_s.downcase
|
|
38
|
+
%w[1 true yes].include?(v)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.from_config(cfg)
|
|
42
|
+
raw = cfg.respond_to?(:hooks) ? cfg.hooks : {}
|
|
43
|
+
new(raw.is_a?(Hash) ? raw : {})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Maps CLI or YAML-style names (+before_suite+, +"before(:suite)"+) to a phase symbol or +nil+.
|
|
47
|
+
def self.parse_phase(str)
|
|
48
|
+
new({}).send(:canonical_key, str)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param raw [Hash] +hooks+ block from YAML
|
|
52
|
+
def initialize(raw)
|
|
53
|
+
@ruby_file = extract_ruby_file(raw)
|
|
54
|
+
h = {}
|
|
55
|
+
raw.each do |k, v|
|
|
56
|
+
next if %w[ruby ruby_file].include?(k.to_s)
|
|
57
|
+
|
|
58
|
+
ck = canonical_key(k)
|
|
59
|
+
next if ck.nil?
|
|
60
|
+
|
|
61
|
+
h[ck] = v
|
|
62
|
+
end
|
|
63
|
+
@raw = h.freeze
|
|
64
|
+
@ruby_registry_loaded = false
|
|
65
|
+
@ruby_registry = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def empty?
|
|
69
|
+
no_shell = PHASES.all? { |p| commands_for(p).empty? }
|
|
70
|
+
return false unless no_shell
|
|
71
|
+
|
|
72
|
+
return true if @ruby_file.nil? || @ruby_file.to_s.strip.empty?
|
|
73
|
+
return true unless File.file?(File.expand_path(@ruby_file, Dir.pwd))
|
|
74
|
+
|
|
75
|
+
reg = ruby_registry
|
|
76
|
+
reg.nil? || reg.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def worker_hooks?
|
|
80
|
+
return true if commands_for(:before_worker).any? || commands_for(:after_worker).any?
|
|
81
|
+
|
|
82
|
+
!!ruby_registry&.worker_hooks?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Merges +POLYRUN_HOOKS_RUBY_FILE+ when a DSL file is configured (for worker +ruby -e+).
|
|
86
|
+
def merge_worker_ruby_env(env)
|
|
87
|
+
return env unless @ruby_file
|
|
88
|
+
abs = File.expand_path(@ruby_file, Dir.pwd)
|
|
89
|
+
return env unless File.file?(abs)
|
|
90
|
+
|
|
91
|
+
env.merge("POLYRUN_HOOKS_RUBY_FILE" => abs)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param phase [Symbol]
|
|
95
|
+
# @return [Array<String>]
|
|
96
|
+
def commands_for(phase)
|
|
97
|
+
v = @raw[phase.to_sym]
|
|
98
|
+
case v
|
|
99
|
+
when nil then []
|
|
100
|
+
when Array then v.map(&:to_s).map(&:strip).reject(&:empty?)
|
|
101
|
+
else
|
|
102
|
+
s = v.to_s.strip
|
|
103
|
+
s.empty? ? [] : [s]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Runs Ruby DSL blocks (if any), then shell commands for +phase+.
|
|
108
|
+
# @return [Integer] exit code (0 if no commands)
|
|
109
|
+
def run_phase(phase, env)
|
|
110
|
+
return 0 unless PHASES.include?(phase.to_sym)
|
|
111
|
+
|
|
112
|
+
merged = stringify_env_for_hook(env).merge(
|
|
113
|
+
"POLYRUN_HOOK_PHASE" => phase.to_s,
|
|
114
|
+
"POLYRUN_HOOK" => "1"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
reg = ruby_registry
|
|
118
|
+
if reg&.any?(phase)
|
|
119
|
+
begin
|
|
120
|
+
reg.run(phase, merged)
|
|
121
|
+
rescue => e
|
|
122
|
+
Polyrun::Log.warn "polyrun hooks: #{phase} ruby hook failed: #{e.class}: #{e.message}"
|
|
123
|
+
return 1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
commands_for(phase).each do |cmd|
|
|
128
|
+
ok = system(merged, "sh", "-c", cmd)
|
|
129
|
+
return $?.exitstatus unless ok
|
|
130
|
+
end
|
|
131
|
+
0
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Like {#run_phase}, but no-ops when {disabled?} (+POLYRUN_HOOKS_DISABLE=1+). Used by run-shards / ci-shard orchestration.
|
|
135
|
+
def run_phase_if_enabled(phase, env)
|
|
136
|
+
return 0 if self.class.disabled?
|
|
137
|
+
|
|
138
|
+
run_phase(phase, env)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def extract_ruby_file(raw)
|
|
144
|
+
v = raw["ruby"] || raw[:ruby] || raw["ruby_file"] || raw[:ruby_file]
|
|
145
|
+
return nil if v.nil?
|
|
146
|
+
|
|
147
|
+
s = v.to_s.strip
|
|
148
|
+
s.empty? ? nil : s
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def ruby_registry
|
|
152
|
+
return @ruby_registry if @ruby_registry_loaded
|
|
153
|
+
|
|
154
|
+
@ruby_registry_loaded = true
|
|
155
|
+
@ruby_registry = Dsl.load_registry(@ruby_file)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def stringify_env_for_hook(env)
|
|
159
|
+
h = {}
|
|
160
|
+
env.each { |k, v| h[k.to_s] = v }
|
|
161
|
+
h
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Accept RSpec-style quoted keys from YAML, e.g. +"before(:suite)"+.
|
|
165
|
+
def canonical_key(k)
|
|
166
|
+
s = k.to_s.strip
|
|
167
|
+
sym = if s.match?(/\Abefore\(\s*:suite\s*\)\z/i)
|
|
168
|
+
:before_suite
|
|
169
|
+
elsif s.match?(/\Aafter\(\s*:suite\s*\)\z/i)
|
|
170
|
+
:after_suite
|
|
171
|
+
elsif s.match?(/\Abefore\(\s*:all\s*\)\z/i)
|
|
172
|
+
:before_shard
|
|
173
|
+
elsif s.match?(/\Aafter\(\s*:all\s*\)\z/i)
|
|
174
|
+
:after_shard
|
|
175
|
+
elsif s.match?(/\Abefore\(\s*:each\s*\)\z/i)
|
|
176
|
+
:before_worker
|
|
177
|
+
elsif s.match?(/\Aafter\(\s*:each\s*\)\z/i)
|
|
178
|
+
:after_worker
|
|
179
|
+
else
|
|
180
|
+
s.downcase.tr("-", "_").to_sym
|
|
181
|
+
end
|
|
182
|
+
PHASES.include?(sym) ? sym : nil
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -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
data/lib/polyrun.rb
CHANGED
|
@@ -25,6 +25,7 @@ require_relative "polyrun/database/shard"
|
|
|
25
25
|
require_relative "polyrun/database/url_builder"
|
|
26
26
|
require_relative "polyrun/database/provision"
|
|
27
27
|
require_relative "polyrun/database/clone_shards"
|
|
28
|
+
require_relative "polyrun/hooks"
|
|
28
29
|
require_relative "polyrun/env/ci"
|
|
29
30
|
require_relative "polyrun/timing/merge"
|
|
30
31
|
require_relative "polyrun/timing/summary"
|
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
|
+
version: 1.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -166,6 +166,7 @@ files:
|
|
|
166
166
|
- docs/SETUP_PROFILE.md
|
|
167
167
|
- lib/polyrun.rb
|
|
168
168
|
- lib/polyrun/cli.rb
|
|
169
|
+
- lib/polyrun/cli/ci_shard_hooks.rb
|
|
169
170
|
- lib/polyrun/cli/ci_shard_run_command.rb
|
|
170
171
|
- lib/polyrun/cli/ci_shard_run_parse.rb
|
|
171
172
|
- lib/polyrun/cli/config_command.rb
|
|
@@ -174,8 +175,10 @@ files:
|
|
|
174
175
|
- lib/polyrun/cli/database_commands.rb
|
|
175
176
|
- lib/polyrun/cli/default_run.rb
|
|
176
177
|
- lib/polyrun/cli/env_commands.rb
|
|
178
|
+
- lib/polyrun/cli/failure_commands.rb
|
|
177
179
|
- lib/polyrun/cli/help.rb
|
|
178
180
|
- lib/polyrun/cli/helpers.rb
|
|
181
|
+
- lib/polyrun/cli/hooks_command.rb
|
|
179
182
|
- lib/polyrun/cli/init_command.rb
|
|
180
183
|
- lib/polyrun/cli/plan_command.rb
|
|
181
184
|
- lib/polyrun/cli/prepare_command.rb
|
|
@@ -184,6 +187,7 @@ files:
|
|
|
184
187
|
- lib/polyrun/cli/quick_command.rb
|
|
185
188
|
- lib/polyrun/cli/report_commands.rb
|
|
186
189
|
- lib/polyrun/cli/run_shards_command.rb
|
|
190
|
+
- lib/polyrun/cli/run_shards_parallel_children.rb
|
|
187
191
|
- lib/polyrun/cli/run_shards_plan_boot_phases.rb
|
|
188
192
|
- lib/polyrun/cli/run_shards_plan_options.rb
|
|
189
193
|
- lib/polyrun/cli/run_shards_planning.rb
|
|
@@ -225,6 +229,10 @@ files:
|
|
|
225
229
|
- lib/polyrun/database/url_builder/template_prepare.rb
|
|
226
230
|
- lib/polyrun/debug.rb
|
|
227
231
|
- lib/polyrun/env/ci.rb
|
|
232
|
+
- lib/polyrun/hooks.rb
|
|
233
|
+
- lib/polyrun/hooks/dsl.rb
|
|
234
|
+
- lib/polyrun/hooks/worker_runner.rb
|
|
235
|
+
- lib/polyrun/hooks/worker_shell.rb
|
|
228
236
|
- lib/polyrun/log.rb
|
|
229
237
|
- lib/polyrun/minitest.rb
|
|
230
238
|
- lib/polyrun/partition/constraints.rb
|
|
@@ -251,8 +259,10 @@ files:
|
|
|
251
259
|
- lib/polyrun/quick/reporter.rb
|
|
252
260
|
- lib/polyrun/quick/runner.rb
|
|
253
261
|
- lib/polyrun/railtie.rb
|
|
262
|
+
- lib/polyrun/reporting/failure_merge.rb
|
|
254
263
|
- lib/polyrun/reporting/junit.rb
|
|
255
264
|
- lib/polyrun/reporting/junit_emit.rb
|
|
265
|
+
- lib/polyrun/reporting/rspec_failure_fragment_formatter.rb
|
|
256
266
|
- lib/polyrun/reporting/rspec_junit.rb
|
|
257
267
|
- lib/polyrun/rspec.rb
|
|
258
268
|
- lib/polyrun/templates/POLYRUN.md
|