polyrun 1.4.1 → 1.5.0
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 +21 -0
- data/lib/polyrun/cli/ci_shard_hooks.rb +12 -4
- data/lib/polyrun/cli/ci_shard_run_command.rb +3 -1
- data/lib/polyrun/cli/help.rb +3 -0
- data/lib/polyrun/cli/helpers.rb +22 -0
- data/lib/polyrun/cli/run_shards_parallel_children.rb +26 -34
- data/lib/polyrun/cli/run_shards_parallel_wait.rb +267 -0
- data/lib/polyrun/cli/run_shards_plan_boot_phases.rb +34 -1
- data/lib/polyrun/cli/run_shards_plan_options.rb +6 -2
- data/lib/polyrun/cli/run_shards_run.rb +7 -33
- data/lib/polyrun/cli/run_shards_worker_interrupt.rb +75 -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 +191 -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/hooks.rb +9 -1
- data/lib/polyrun/log.rb +16 -0
- data/lib/polyrun/minitest.rb +34 -0
- data/lib/polyrun/quick/example_runner.rb +11 -0
- data/lib/polyrun/rspec.rb +18 -0
- data/lib/polyrun/version.rb +1 -1
- data/lib/polyrun/worker_ping.rb +74 -0
- data/sig/polyrun/minitest.rbs +2 -0
- data/sig/polyrun/rspec.rbs +4 -0
- data/sig/polyrun/worker_ping.rbs +10 -0
- metadata +12 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<title><%= title %></title>
|
|
6
|
+
<style>
|
|
7
|
+
<%= stylesheet %>
|
|
8
|
+
</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="wrapper">
|
|
12
|
+
<div class="topbar">
|
|
13
|
+
<h1><%= title %></h1>
|
|
14
|
+
<div class="timestamp">Generated <%= generated_label %></div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<%= overview_html %>
|
|
18
|
+
|
|
19
|
+
<%= file_list_html %>
|
|
20
|
+
|
|
21
|
+
<div id="source-files">
|
|
22
|
+
<%= file_sections_html %>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<script>
|
|
27
|
+
<%= javascript %>
|
|
28
|
+
</script>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
@@ -22,6 +22,15 @@ module Polyrun
|
|
|
22
22
|
end.map { |rel| File.expand_path(rel, root) }.uniq
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Keeps only loaded files that match configured +track_files+ globs.
|
|
26
|
+
def keep_tracked_files(blob, root, track_files)
|
|
27
|
+
tracked = expand_globs(root, track_files).each_with_object({}) { |abs, acc| acc[abs] = true }
|
|
28
|
+
blob.each_with_object({}) do |(path, entry), out|
|
|
29
|
+
abs = File.expand_path(path.to_s)
|
|
30
|
+
out[abs] = entry if tracked[abs]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
25
34
|
# Adds tracked files that were never required, with simulated line arrays (blank/comment => nil, else 0).
|
|
26
35
|
# Matches SimpleCov +add_not_loaded_files+ behavior for coverage completeness.
|
|
27
36
|
def merge_untracked_into_blob(blob, root, track_files)
|
data/lib/polyrun/hooks.rb
CHANGED
|
@@ -118,6 +118,9 @@ module Polyrun
|
|
|
118
118
|
if reg&.any?(phase)
|
|
119
119
|
begin
|
|
120
120
|
reg.run(phase, merged)
|
|
121
|
+
rescue Interrupt
|
|
122
|
+
Polyrun::Log.warn "polyrun hooks: #{phase} ruby hook interrupted"
|
|
123
|
+
return 130
|
|
121
124
|
rescue => e
|
|
122
125
|
Polyrun::Log.warn "polyrun hooks: #{phase} ruby hook failed: #{e.class}: #{e.message}"
|
|
123
126
|
return 1
|
|
@@ -125,7 +128,12 @@ module Polyrun
|
|
|
125
128
|
end
|
|
126
129
|
|
|
127
130
|
commands_for(phase).each do |cmd|
|
|
128
|
-
ok =
|
|
131
|
+
ok = begin
|
|
132
|
+
system(merged, "sh", "-c", cmd)
|
|
133
|
+
rescue Interrupt
|
|
134
|
+
Polyrun::Log.warn "polyrun hooks: #{phase} shell hook interrupted"
|
|
135
|
+
return 130
|
|
136
|
+
end
|
|
129
137
|
return $?.exitstatus unless ok
|
|
130
138
|
end
|
|
131
139
|
0
|
data/lib/polyrun/log.rb
CHANGED
|
@@ -6,6 +6,9 @@ module Polyrun
|
|
|
6
6
|
#
|
|
7
7
|
# Polyrun::Log.stderr = Logger.new($stderr)
|
|
8
8
|
# Polyrun::Log.stdout = StringIO.new
|
|
9
|
+
#
|
|
10
|
+
# Orchestration (+orchestration_warn+): worker timeout and SIGINT lines use the same sink as +warn+ unless
|
|
11
|
+
# +POLYRUN_ORCHESTRATION_STDERR=1+ and stderr is not process +$stderr+ (then the summary is copied to +$stderr+).
|
|
9
12
|
module Log
|
|
10
13
|
class << self
|
|
11
14
|
attr_writer :stderr
|
|
@@ -25,6 +28,19 @@ module Polyrun
|
|
|
25
28
|
emit_line(stderr, msg)
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
# Like {#warn}, and when +POLYRUN_ORCHESTRATION_STDERR=1+ and {#stderr} is not the process +$stderr+,
|
|
32
|
+
# also writes one line to +$stderr+ so timeout/interrupt attribution survives custom/null Log sinks.
|
|
33
|
+
def orchestration_warn(msg)
|
|
34
|
+
warn(msg)
|
|
35
|
+
return unless %w[1 true yes].include?(ENV["POLYRUN_ORCHESTRATION_STDERR"]&.downcase)
|
|
36
|
+
return if stderr.equal?($stderr)
|
|
37
|
+
|
|
38
|
+
# Intentionally the real stderr stream (+Kernel#warn+ routes through +Log.stderr+).
|
|
39
|
+
# rubocop:disable Style/StderrPuts
|
|
40
|
+
$stderr.puts(msg.to_s.chomp)
|
|
41
|
+
# rubocop:enable Style/StderrPuts
|
|
42
|
+
end
|
|
43
|
+
|
|
28
44
|
def puts(msg = "")
|
|
29
45
|
if msg.nil?
|
|
30
46
|
stdout.write("\n")
|
data/lib/polyrun/minitest.rb
CHANGED
|
@@ -7,11 +7,45 @@ module Polyrun
|
|
|
7
7
|
# after Rails / DB configuration (same timing as a direct call to
|
|
8
8
|
# {Data::ParallelProvisioning.run_suite_hooks!}).
|
|
9
9
|
module Minitest
|
|
10
|
+
module WorkerPingTestHook
|
|
11
|
+
def setup
|
|
12
|
+
Polyrun::WorkerPing.ping!(location: polyrun_minitest_location)
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def teardown
|
|
17
|
+
super
|
|
18
|
+
Polyrun::WorkerPing.ping!(location: polyrun_minitest_location)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def polyrun_minitest_location
|
|
24
|
+
file, line = method(name).source_location
|
|
25
|
+
(file && line) ? "#{file}:#{line}" : nil
|
|
26
|
+
rescue NameError
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
10
31
|
module_function
|
|
11
32
|
|
|
12
33
|
# Runs {Data::ParallelProvisioning.run_suite_hooks!} (serial vs shard worker hooks).
|
|
13
34
|
def install_parallel_provisioning!
|
|
14
35
|
Polyrun::Data::ParallelProvisioning.run_suite_hooks!
|
|
15
36
|
end
|
|
37
|
+
|
|
38
|
+
# Same ping semantics as {RSpec.install_worker_ping!}: +ping!+ at test +setup+ and +teardown+.
|
|
39
|
+
# Requires +minitest+ to be loaded first (+Minitest::Test+ defined).
|
|
40
|
+
def install_worker_ping!
|
|
41
|
+
require_relative "worker_ping"
|
|
42
|
+
unless defined?(::Minitest::Test)
|
|
43
|
+
Polyrun::Log.warn "polyrun minitest: install_worker_ping! skipped (load minitest/autorun or minitest/test first)"
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
::Minitest::Test.send(:prepend, WorkerPingTestHook)
|
|
48
|
+
Polyrun::WorkerPing.ensure_interval_ping_thread!
|
|
49
|
+
end
|
|
16
50
|
end
|
|
17
51
|
end
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
require_relative "../worker_ping"
|
|
1
2
|
require_relative "assertions"
|
|
2
3
|
require_relative "errors"
|
|
3
4
|
require_relative "matchers"
|
|
4
5
|
|
|
6
|
+
Polyrun::WorkerPing.ensure_interval_ping_thread!
|
|
7
|
+
|
|
5
8
|
module Polyrun
|
|
6
9
|
module Quick
|
|
7
10
|
# Per-example execution: merged lets, hooks, assertions, optional Capybara::DSL.
|
|
@@ -20,6 +23,8 @@ module Polyrun
|
|
|
20
23
|
define_let_methods!
|
|
21
24
|
run_let_bangs_from_chain
|
|
22
25
|
extend_capybara_if_enabled!
|
|
26
|
+
qloc = quick_example_location(block)
|
|
27
|
+
Polyrun::WorkerPing.ping!(location: qloc)
|
|
23
28
|
begin
|
|
24
29
|
run_before_hooks_from_chain(ancestor_chain)
|
|
25
30
|
instance_eval(&block)
|
|
@@ -32,11 +37,17 @@ module Polyrun
|
|
|
32
37
|
run_after_hooks_from_chain(ancestor_chain)
|
|
33
38
|
reset_capybara_if_enabled!
|
|
34
39
|
@_let_cache = {}
|
|
40
|
+
Polyrun::WorkerPing.ping!(location: qloc)
|
|
35
41
|
end
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
private
|
|
39
45
|
|
|
46
|
+
def quick_example_location(block)
|
|
47
|
+
loc = block&.source_location
|
|
48
|
+
loc ? "#{loc[0]}:#{loc[1]}" : nil
|
|
49
|
+
end
|
|
50
|
+
|
|
40
51
|
def merge_lets_from_chain(ancestor_chain)
|
|
41
52
|
@merged_lets = {}
|
|
42
53
|
ancestor_chain.each do |g|
|
data/lib/polyrun/rspec.rb
CHANGED
|
@@ -44,5 +44,23 @@ module Polyrun
|
|
|
44
44
|
config.add_formatter Polyrun::Reporting::RspecFailureFragmentFormatter
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
# Writes {WorkerPing} after suite start, before/after each example (+location+ is file:line from metadata).
|
|
49
|
+
# Keeps +--worker-idle-timeout+ sensitive to example progress (not only a background thread).
|
|
50
|
+
def install_worker_ping!
|
|
51
|
+
require "rspec/core"
|
|
52
|
+
require_relative "worker_ping"
|
|
53
|
+
::RSpec.configure do |config|
|
|
54
|
+
config.before(:suite) { Polyrun::WorkerPing.ping! }
|
|
55
|
+
config.before(:each) do |example|
|
|
56
|
+
Polyrun::WorkerPing.ping!(location: example.metadata[:location] || example.location)
|
|
57
|
+
end
|
|
58
|
+
config.after(:each) do |example|
|
|
59
|
+
Polyrun::WorkerPing.ping!(location: example.metadata[:location] || example.location)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Polyrun::WorkerPing.ensure_interval_ping_thread!
|
|
64
|
+
end
|
|
47
65
|
end
|
|
48
66
|
end
|
data/lib/polyrun/version.rb
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Polyrun
|
|
2
|
+
# Writes a monotonic timestamp to +POLYRUN_WORKER_PING_FILE+ when the test process advances
|
|
3
|
+
# (typically once per example). When +location:+ is passed (path:line of the example), the file
|
|
4
|
+
# is two lines: timestamp, then that string. Parents use +--worker-idle-timeout+ to detect a worker with no
|
|
5
|
+
# progress *inside* a single example—unlike a background thread, +ping!+ does not run while Ruby
|
|
6
|
+
# is busy on the main thread, so a tight CPU loop or stuck native code leaves the timestamp stale.
|
|
7
|
+
#
|
|
8
|
+
# Prefer framework installs (call from helpers *after* loading the runner):
|
|
9
|
+
#
|
|
10
|
+
# require "polyrun/rspec"
|
|
11
|
+
# Polyrun::RSpec.install_worker_ping!
|
|
12
|
+
#
|
|
13
|
+
# require "polyrun/minitest"
|
|
14
|
+
# Polyrun::Minitest.install_worker_ping!
|
|
15
|
+
#
|
|
16
|
+
# Polyrun Quick runs +ping!+ automatically when requiring the Quick stack.
|
|
17
|
+
#
|
|
18
|
+
# Optional interval thread (+POLYRUN_WORKER_PING_THREAD=1+, +POLYRUN_WORKER_PING_INTERVAL_SEC+): call
|
|
19
|
+
# {#ensure_interval_ping_thread!} once at worker startup if you rely on periodic pings without per-example {#ping!};
|
|
20
|
+
# installers call this so the env toggle works out of the box.
|
|
21
|
+
module WorkerPing
|
|
22
|
+
class << self
|
|
23
|
+
def ping!(location: nil)
|
|
24
|
+
path = ping_file_path
|
|
25
|
+
return if path.empty?
|
|
26
|
+
|
|
27
|
+
t = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_s
|
|
28
|
+
loc = location.to_s.strip
|
|
29
|
+
payload = loc.empty? ? t : "#{t}\n#{loc}"
|
|
30
|
+
File.binwrite(path, payload)
|
|
31
|
+
rescue SystemCallError
|
|
32
|
+
# best-effort
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ping_file_path
|
|
36
|
+
ENV["POLYRUN_WORKER_PING_FILE"].to_s.strip
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Starts a periodic +ping!+ thread when +POLYRUN_WORKER_PING_THREAD+ is truthy and +POLYRUN_WORKER_PING_FILE+ is set.
|
|
40
|
+
# Prefer per-example {#ping!}; safe to call more than once (idempotent).
|
|
41
|
+
# rubocop:disable ThreadSafety/ClassInstanceVariable -- idempotent once-per-process latch
|
|
42
|
+
def ensure_interval_ping_thread!
|
|
43
|
+
thread_flag = ENV["POLYRUN_WORKER_PING_THREAD"]
|
|
44
|
+
return unless %w[1 true yes].include?(thread_flag&.downcase)
|
|
45
|
+
|
|
46
|
+
path = ping_file_path
|
|
47
|
+
return if path.empty?
|
|
48
|
+
|
|
49
|
+
@interval_ping_mx ||= Mutex.new
|
|
50
|
+
@interval_ping_mx.synchronize do
|
|
51
|
+
return if @interval_ping_started
|
|
52
|
+
|
|
53
|
+
raw = ENV["POLYRUN_WORKER_PING_INTERVAL_SEC"].to_s.strip
|
|
54
|
+
interval = Float(raw.empty? ? "15" : raw, exception: false) || 15.0
|
|
55
|
+
interval = 1.0 if interval < 1.0
|
|
56
|
+
|
|
57
|
+
ping!
|
|
58
|
+
# rubocop:disable ThreadSafety/NewThread -- optional periodic ping alongside per-example ping!
|
|
59
|
+
Thread.new do
|
|
60
|
+
loop do
|
|
61
|
+
sleep(interval)
|
|
62
|
+
ping!
|
|
63
|
+
rescue SystemCallError, Interrupt
|
|
64
|
+
break
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
# rubocop:enable ThreadSafety/NewThread
|
|
68
|
+
@interval_ping_started = true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
# rubocop:enable ThreadSafety/ClassInstanceVariable
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/sig/polyrun/minitest.rbs
CHANGED
data/sig/polyrun/rspec.rbs
CHANGED
|
@@ -3,5 +3,9 @@ module Polyrun
|
|
|
3
3
|
def self.install_parallel_provisioning!: (untyped rspec_config) -> void
|
|
4
4
|
|
|
5
5
|
def self.install_example_timing!: (?output_path: String? ) -> void
|
|
6
|
+
|
|
7
|
+
def self.install_failure_fragments!: (?only_if: untyped?) -> void
|
|
8
|
+
|
|
9
|
+
def self.install_worker_ping!: () -> void
|
|
6
10
|
end
|
|
7
11
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Polyrun
|
|
2
|
+
module WorkerPing
|
|
3
|
+
def self.ping!: (?location: String?) -> void
|
|
4
|
+
|
|
5
|
+
def self.ping_file_path: () -> String
|
|
6
|
+
|
|
7
|
+
# Idempotent — installers call once; POLYRUN_WORKER_PING_THREAD gates the periodic thread.
|
|
8
|
+
def self.ensure_interval_ping_thread!: () -> void
|
|
9
|
+
end
|
|
10
|
+
end
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -188,10 +188,12 @@ files:
|
|
|
188
188
|
- lib/polyrun/cli/report_commands.rb
|
|
189
189
|
- lib/polyrun/cli/run_shards_command.rb
|
|
190
190
|
- lib/polyrun/cli/run_shards_parallel_children.rb
|
|
191
|
+
- lib/polyrun/cli/run_shards_parallel_wait.rb
|
|
191
192
|
- lib/polyrun/cli/run_shards_plan_boot_phases.rb
|
|
192
193
|
- lib/polyrun/cli/run_shards_plan_options.rb
|
|
193
194
|
- lib/polyrun/cli/run_shards_planning.rb
|
|
194
195
|
- lib/polyrun/cli/run_shards_run.rb
|
|
196
|
+
- lib/polyrun/cli/run_shards_worker_interrupt.rb
|
|
195
197
|
- lib/polyrun/cli/start_bootstrap.rb
|
|
196
198
|
- lib/polyrun/cli/timing_command.rb
|
|
197
199
|
- lib/polyrun/config.rb
|
|
@@ -207,6 +209,13 @@ files:
|
|
|
207
209
|
- lib/polyrun/coverage/merge.rb
|
|
208
210
|
- lib/polyrun/coverage/merge/formatters.rb
|
|
209
211
|
- lib/polyrun/coverage/merge/formatters_html.rb
|
|
212
|
+
- lib/polyrun/coverage/merge/html/_file_list.html.erb
|
|
213
|
+
- lib/polyrun/coverage/merge/html/_file_section.html.erb
|
|
214
|
+
- lib/polyrun/coverage/merge/html/_groups_table.html.erb
|
|
215
|
+
- lib/polyrun/coverage/merge/html/_overview.html.erb
|
|
216
|
+
- lib/polyrun/coverage/merge/html/report.css
|
|
217
|
+
- lib/polyrun/coverage/merge/html/report.js
|
|
218
|
+
- lib/polyrun/coverage/merge/html/template.html.erb
|
|
210
219
|
- lib/polyrun/coverage/merge_fragment_meta.rb
|
|
211
220
|
- lib/polyrun/coverage/merge_merge_two.rb
|
|
212
221
|
- lib/polyrun/coverage/rails.rb
|
|
@@ -273,6 +282,7 @@ files:
|
|
|
273
282
|
- lib/polyrun/timing/rspec_example_formatter.rb
|
|
274
283
|
- lib/polyrun/timing/summary.rb
|
|
275
284
|
- lib/polyrun/version.rb
|
|
285
|
+
- lib/polyrun/worker_ping.rb
|
|
276
286
|
- polyrun.gemspec
|
|
277
287
|
- sig/polyrun.rbs
|
|
278
288
|
- sig/polyrun/cli.rbs
|
|
@@ -282,6 +292,7 @@ files:
|
|
|
282
292
|
- sig/polyrun/minitest.rbs
|
|
283
293
|
- sig/polyrun/quick.rbs
|
|
284
294
|
- sig/polyrun/rspec.rbs
|
|
295
|
+
- sig/polyrun/worker_ping.rbs
|
|
285
296
|
homepage: https://github.com/amkisko/polyrun.rb
|
|
286
297
|
licenses:
|
|
287
298
|
- MIT
|