polyrun 2.2.0 → 2.2.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: 8b7bedc5bda9c9ee341927dab2ba6aedafaebe4c26ad73750d8ed7ad4845f4e3
4
- data.tar.gz: bac9cd71c5ab3372c0ff5dda891dd354a0ceaac3555f5949870ce8611646debc
3
+ metadata.gz: a2d28bc9ddd04cf7beebdf3dc7588b61c32c5a17b9c2c97dc25a0135f84c0bef
4
+ data.tar.gz: 36dbe2a44021aeb42ef235fbddc994cfe823bac129bfe284f8945d60408f06fa
5
5
  SHA512:
6
- metadata.gz: fe09eaccf1eaf3fba5a998d49f2f2df9cfd6a1da6b1eef960e6cbe894f2269b05777be459fe394a67c3038d72750a8401ae8e224726901ac4bd437e3d01a93ed
7
- data.tar.gz: f8090b77fba080b7ca1996afeec0c2680ca26e0d0a4571453210f421f4921685bd8c5bcfeb79724556e377ff26968a914b52bf24b4a1a433a9500d656aa43f41
6
+ metadata.gz: 701f77a58aad54da7963043a54065cf5a11b4905fbfe6fdb20a25345fa0bc7f5b9fe62730e3828cce94b3203986b99d2f724848bfca56c67815d538723bf0602
7
+ data.tar.gz: 6591a3dae32970c56b8f329cb7caec8a36b53d25d20ad882dadf8a8d059dad4bcacd97015d8f5cfb822ed7cf0c7b49c03662ede103f3530e4f80f07a7a1f6213
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.2.2 (2026-07-25)
4
+
5
+ - BREAKING: Require Ruby 3.4 or newer
6
+ - Fix `Process.spawn` for string worker commands so the command is one argv element instead of character-splatted
7
+ - Drain remaining worker pipe output when the forwarder hits `IOError` or `Errno::EPIPE`
8
+
9
+ ## 2.2.1 (2026-07-13)
10
+
11
+ - Fix per-example timeout leaving ActiveRecord connection pools in a bad state after `Timeout` interrupts database I/O; disconnect all pools on timeout (adapter-agnostic).
12
+ - Fix `DEBUG_PROSOPITE=1`, `DEBUG_SQL` / `POLYRUN_DEBUG_SQL`, and `DEBUG_TRACE` / `POLYRUN_DEBUG_TRACE` requiring `POLYRUN_EXAMPLE_DEBUG=1`; each flag is standalone again.
13
+
3
14
  ## 2.2.0 (2026-07-13)
4
15
 
5
16
  - Fix `install_example_rails_logging!` raising `NameError` when example-debug Rails logging runs under `POLYRUN_EXAMPLE_DEBUG=1`.
data/CONTRIBUTING.md CHANGED
@@ -47,7 +47,6 @@ bundle exec rake bench_performance
47
47
  Run the suite under alternate Ruby constraints (see `Appraisals` and `gemfiles/`):
48
48
 
49
49
  ```bash
50
- bundle exec appraisal ruby32 rspec
51
50
  bundle exec appraisal ruby34 rspec
52
51
  bundle exec appraisal ruby40 rspec
53
52
  ```
data/README.md CHANGED
@@ -197,6 +197,19 @@ See [`examples/README.md`](examples/README.md) for Rails apps (Capybara, Playwri
197
197
 
198
198
  You can replace SimpleCov and simplecov plugins, parallel_tests, and rspec_junit_formatter with Polyrun for those roles. Use `merge-timing`, `report-timing`, and `Data::FactoryCounts` (optionally with `Data::FactoryInstrumentation`) for slow-file and factory metrics. YAML fixture batches and bulk inserts can use `Data::Fixtures` and `ParallelProvisioning` for shard-aware seeding; wire your own `truncate` and `load_seed` in hooks.
199
199
 
200
+ ## Links
201
+
202
+ - [GitHub](https://github.com/amkisko/polyrun.rb)
203
+ - [GitLab](https://gitlab.com/amkisko/polyrun.rb)
204
+ - [RubyGems](https://rubygems.org/gems/polyrun)
205
+ - [Versions Atom](https://rubygems.org/gems/polyrun/versions.atom) (feed id `10936755433440`)
206
+ - [libraries.io](https://libraries.io/rubygems/polyrun)
207
+ - [Deps.dev](https://deps.dev/rubygems/polyrun)
208
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_polyrun.rb)
209
+ - [Snyk](https://snyk.io/test/github/amkisko/polyrun.rb)
210
+ - [Codecov](https://app.codecov.io/github/amkisko/polyrun.rb)
211
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/polyrun.rb)
212
+
200
213
  ## License
201
214
 
202
215
  Released under the [MIT License](LICENSE). Copyright (c) 2026 Andrei Makarov.
@@ -69,14 +69,14 @@ Orchestration tracing stays on `DEBUG=1` / `POLYRUN_DEBUG=1`. Per-example toolin
69
69
 
70
70
  | Env | Effect |
71
71
  |--------|--------|
72
- | `POLYRUN_EXAMPLE_DEBUG=1` | Enable example debug installers |
72
+ | `POLYRUN_EXAMPLE_DEBUG=1` | Enable print helpers, Rails logging; disable per-example timeouts |
73
73
  | `POLYRUN_DEBUG_SQL=1` or `DEBUG_SQL=1` | Log mutating SQL |
74
74
  | `POLYRUN_DEBUG_TRACE=1` or `DEBUG_TRACE=1` | TracePoint under app root |
75
75
  | `DEBUG_PROSOPITE=1` | Prosopite N+1 scan when gem is loaded |
76
76
  | `DEBUG_LOG_LEVEL` | Rails / ActiveRecord logger severity: `debug`, `info`, `warn`, `error`, `fatal`, or Ruby Logger integers (`0` debug … `4` fatal); default `debug` |
77
- | `RSPEC_EXAMPLE_TIMEOUT_SEC` | Per-example timeout (disabled while example debug is on) |
77
+ | `RSPEC_EXAMPLE_TIMEOUT_SEC` | Per-example timeout (disabled while example debug is on); on timeout, ActiveRecord connection pools are disconnected so the next example gets fresh connections (any adapter) |
78
78
 
79
- In `spec_helper` / `spec/support`: `require "polyrun/rspec"` then `Polyrun::RSpec.install_sharded_formatter_compat!` (silences per-worker seed/summary/pending under `POLYRUN_SHARD_*`), `install_example_debug!`, `install_example_rails_logging!`, `install_example_timeout!`. Pair with `install_worker_ping!` for idle timeouts.
79
+ In `spec_helper` / `spec/support`: `require "polyrun/rspec"` then `Polyrun::RSpec.install_sharded_formatter_compat!` (silences per-worker seed/summary/pending under `POLYRUN_SHARD_*`), `install_example_debug!`, `install_example_rails_logging!`, `install_example_prosopite!`, `install_example_timeout!`. Pair with `install_worker_ping!` for idle timeouts.
80
80
 
81
81
  ## 6. Coverage and CI reports
82
82
 
@@ -26,7 +26,7 @@ module Polyrun
26
26
  Per-worker wall timeout: --worker-timeout SEC or POLYRUN_WORKER_TIMEOUT_SEC. Exit 124; parent stops remaining workers.
27
27
  Per-worker idle timeout: --worker-idle-timeout SEC or POLYRUN_WORKER_IDLE_TIMEOUT_SEC after a progress ping (POLYRUN_WORKER_PING_FILE). Enable pings in test setup. Exit 125. Optional periodic pings: POLYRUN_WORKER_PING_THREAD=1 (POLYRUN_WORKER_PING_INTERVAL_SEC).
28
28
  Worker output routing (opt-in): POLYRUN_WORKER_OUTPUT_ROUTING=1 or POLYRUN_WORKER_LOG_DIR; per-shard logs under tmp/polyrun/workers (POLYRUN_WORKER_OUTPUT_PREFIX=0 for log-only)
29
- Example debug (RSpec, opt-in): POLYRUN_EXAMPLE_DEBUG=1; POLYRUN_DEBUG_SQL / POLYRUN_DEBUG_TRACE
29
+ Example debug (RSpec, opt-in): POLYRUN_EXAMPLE_DEBUG=1 (print/Rails logging/timeouts); POLYRUN_DEBUG_SQL / DEBUG_SQL; POLYRUN_DEBUG_TRACE / DEBUG_TRACE; DEBUG_PROSOPITE=1
30
30
  Sharded formatter compat: silences per-worker seed, summary, and pending lines under POLYRUN_SHARD_* (see docs/SETUP_PROFILE.md)
31
31
  Orchestration warnings on process stderr: POLYRUN_ORCHESTRATION_STDERR=1
32
32
  Spec quality (opt-in): POLYRUN_SPEC_QUALITY=1; run-shards --merge-spec-quality; merge-spec-quality / report-spec-quality
@@ -0,0 +1,51 @@
1
+ module Polyrun
2
+ module RSpec
3
+ # Drops pooled connections after per-example Timeout interrupts database I/O.
4
+ # Uses only ActiveRecord connection-pool APIs (PostgreSQL, MySQL, SQLite, SQL Server, etc.).
5
+ module ActiveRecordTimeoutRecovery
6
+ module_function
7
+
8
+ def disconnect_all_connection_pools!
9
+ return unless active_record_loaded?
10
+
11
+ if defined?(ActiveRecord) && ActiveRecord.respond_to?(:disconnect_all!)
12
+ ActiveRecord.disconnect_all!
13
+ return
14
+ end
15
+
16
+ each_connection_pool(&:disconnect!)
17
+ end
18
+
19
+ def active_record_loaded?
20
+ defined?(ActiveRecord::Base) &&
21
+ ActiveRecord::Base.respond_to?(:connection_handler)
22
+ end
23
+
24
+ def each_connection_pool
25
+ connection_handlers.each do |handler|
26
+ pools_for(handler).each { |pool| yield pool }
27
+ end
28
+ end
29
+
30
+ def connection_handlers
31
+ base = ActiveRecord::Base
32
+ if base.respond_to?(:connection_handlers)
33
+ base.connection_handlers.values
34
+ else
35
+ [base.connection_handler]
36
+ end
37
+ end
38
+
39
+ def pools_for(handler)
40
+ if handler.respond_to?(:all_connection_pools)
41
+ handler.all_connection_pools
42
+ elsif handler.respond_to?(:connection_pool_list)
43
+ handler.connection_pool_list
44
+ else
45
+ []
46
+ end
47
+ end
48
+ private_class_method :active_record_loaded?, :each_connection_pool, :connection_handlers, :pools_for
49
+ end
50
+ end
51
+ end
@@ -6,9 +6,10 @@ module Polyrun
6
6
  module RSpec
7
7
  # Per-example RSpec debugging for local investigation (SQL, TracePoint, timeouts).
8
8
  #
9
- # +POLYRUN_EXAMPLE_DEBUG=1+ enables installers; +DEBUG=1+ / +POLYRUN_DEBUG=1+ trace orchestration only.
9
+ # +POLYRUN_EXAMPLE_DEBUG=1+ enables print helpers, Rails logging, and disables per-example timeouts.
10
10
  # +POLYRUN_DEBUG_SQL=1+ or legacy +DEBUG_SQL=1+ logs mutating SQL.
11
11
  # +POLYRUN_DEBUG_TRACE=1+ or legacy +DEBUG_TRACE=1+ traces :call/:raise under the app root.
12
+ # +DEBUG_PROSOPITE=1+ runs Prosopite N+1 scans when the gem is loaded.
12
13
  # +DEBUG_LOG_LEVEL+ accepts Ruby Logger severities as integers (0 debug … 4 fatal) or names.
13
14
  module ExampleDebug
14
15
  LOG_LEVEL_BY_NAME = {
@@ -26,15 +27,15 @@ module Polyrun
26
27
  end
27
28
 
28
29
  def sql_enabled?
29
- enabled? && (truthy?(ENV["POLYRUN_DEBUG_SQL"]) || truthy?(ENV["DEBUG_SQL"]))
30
+ truthy?(ENV["POLYRUN_DEBUG_SQL"]) || truthy?(ENV["DEBUG_SQL"])
30
31
  end
31
32
 
32
33
  def trace_enabled?
33
- enabled? && (truthy?(ENV["POLYRUN_DEBUG_TRACE"]) || truthy?(ENV["DEBUG_TRACE"]))
34
+ truthy?(ENV["POLYRUN_DEBUG_TRACE"]) || truthy?(ENV["DEBUG_TRACE"])
34
35
  end
35
36
 
36
37
  def prosopite_enabled?
37
- enabled? && truthy?(ENV["DEBUG_PROSOPITE"])
38
+ truthy?(ENV["DEBUG_PROSOPITE"])
38
39
  end
39
40
 
40
41
  def print_spec_enabled?
@@ -99,14 +100,22 @@ module Polyrun
99
100
  return if seconds <= 0
100
101
  return if example_timeout_disabled?
101
102
 
103
+ require_relative "active_record_timeout_recovery"
104
+
102
105
  rspec_config.around(:each) do |example|
103
- if example.metadata[:benchmark] || example.metadata[:slow]
104
- example.run
105
- else
106
- Timeout.timeout(seconds) { example.run }
106
+ timed_out = false
107
+ begin
108
+ if example.metadata[:benchmark] || example.metadata[:slow]
109
+ example.run
110
+ else
111
+ Timeout.timeout(seconds) { example.run }
112
+ end
113
+ rescue Timeout::Error
114
+ timed_out = true
115
+ raise "Example timed out after #{seconds}s (#{example.location})"
116
+ ensure
117
+ ActiveRecordTimeoutRecovery.disconnect_all_connection_pools! if timed_out
107
118
  end
108
- rescue Timeout::Error
109
- raise "Example timed out after #{seconds}s (#{example.location})"
110
119
  end
111
120
  end
112
121
 
@@ -1,3 +1,3 @@
1
1
  module Polyrun
2
- VERSION = "2.2.0"
2
+ VERSION = "2.2.2"
3
3
  end
@@ -66,7 +66,8 @@ module Polyrun
66
66
  err: err_write
67
67
  )
68
68
  else
69
- Process.spawn(child_env, *cmd, *paths, out: out_write, err: err_write)
69
+ # Array(cmd) so a String command is one argv element (not character-splatted).
70
+ Process.spawn(child_env, *Array(cmd), *Array(paths), out: out_write, err: err_write)
70
71
  end
71
72
 
72
73
  out_write.close
@@ -137,7 +138,7 @@ module Polyrun
137
138
  forwarder.consume(stream, io.readpartial(4096))
138
139
  end
139
140
  rescue IOError, Errno::EPIPE
140
- # worker closed the pipe
141
+ drain_remaining_output(io, forwarder, stream)
141
142
  ensure
142
143
  io.close unless io.closed?
143
144
  end
@@ -145,6 +146,16 @@ module Polyrun
145
146
  end
146
147
  private_class_method :forwarder_thread
147
148
 
149
+ # EOFError subclasses IOError; keep drain rescues separate so a closed pipe
150
+ # during the final read does not escape the forwarder thread.
151
+ def drain_remaining_output(io, forwarder, stream)
152
+ remaining = io.read
153
+ forwarder.consume(stream, remaining) if remaining && !remaining.empty?
154
+ rescue IOError, Errno::EPIPE
155
+ nil
156
+ end
157
+ private_class_method :drain_remaining_output
158
+
148
159
  def drain_forwarders(entry)
149
160
  entry[:ios].each do |io|
150
161
  io.close unless io.closed?
data/polyrun.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.summary = "Parallel tests, coverage (SimpleCov-compatible) formatters, fixtures/snapshots, assets & DB provisioning—zero runtime deps"
9
9
  spec.homepage = "https://github.com/amkisko/polyrun.rb"
10
10
  spec.license = "MIT"
11
- spec.required_ruby_version = ">= 3.1.0"
11
+ spec.required_ruby_version = ">= 3.4"
12
12
 
13
13
  spec.files = (
14
14
  Dir["lib/**/*", "sig/**/*.rbs", "bin/polyrun", "README.md", "CHANGELOG.md", "docs/SETUP_PROFILE.md", "LICENSE", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md", "SECURITY.md", "polyrun.gemspec"] +
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: 2.2.0
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -338,6 +338,7 @@ files:
338
338
  - lib/polyrun/reporting/rspec_failure_fragment_formatter.rb
339
339
  - lib/polyrun/reporting/rspec_junit.rb
340
340
  - lib/polyrun/rspec.rb
341
+ - lib/polyrun/rspec/active_record_timeout_recovery.rb
341
342
  - lib/polyrun/rspec/example_debug.rb
342
343
  - lib/polyrun/rspec/example_debug_instrumentation.rb
343
344
  - lib/polyrun/rspec/sharded_formatter_compat.rb
@@ -392,14 +393,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
392
393
  requirements:
393
394
  - - ">="
394
395
  - !ruby/object:Gem::Version
395
- version: 3.1.0
396
+ version: '3.4'
396
397
  required_rubygems_version: !ruby/object:Gem::Requirement
397
398
  requirements:
398
399
  - - ">="
399
400
  - !ruby/object:Gem::Version
400
401
  version: '0'
401
402
  requirements: []
402
- rubygems_version: 4.0.3
403
+ rubygems_version: 4.0.6
403
404
  specification_version: 4
404
405
  summary: Parallel tests, coverage (SimpleCov-compatible) formatters, fixtures/snapshots,
405
406
  assets & DB provisioning—zero runtime deps