polyrun 2.1.3 → 2.2.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -1
  3. data/CONTRIBUTING.md +22 -0
  4. data/docs/SETUP_PROFILE.md +3 -3
  5. data/ext/polyrun_coverage_merge/coverage_merge.c +492 -0
  6. data/ext/polyrun_coverage_merge/extconf.rb +3 -0
  7. data/lib/polyrun/benchmark/profile.rb +191 -0
  8. data/lib/polyrun/benchmark/report.rb +87 -0
  9. data/lib/polyrun/cli/benchmark_commands.rb +66 -0
  10. data/lib/polyrun/cli/coverage_commands.rb +4 -4
  11. data/lib/polyrun/cli/coverage_merge_io.rb +30 -4
  12. data/lib/polyrun/cli/failure_commands.rb +10 -3
  13. data/lib/polyrun/cli/help.rb +13 -9
  14. data/lib/polyrun/cli/report_commands.rb +25 -11
  15. data/lib/polyrun/cli/run_shards_parallel_children.rb +1 -1
  16. data/lib/polyrun/cli/run_shards_plan_options.rb +1 -1
  17. data/lib/polyrun/cli/spec_quality_commands.rb +16 -7
  18. data/lib/polyrun/cli.rb +8 -1
  19. data/lib/polyrun/coverage/example_diff.rb +127 -60
  20. data/lib/polyrun/coverage/example_diff_snapshot.rb +44 -0
  21. data/lib/polyrun/coverage/example_diff_track_filter.rb +51 -0
  22. data/lib/polyrun/coverage/file_stats_report.rb +36 -0
  23. data/lib/polyrun/coverage/formatter.rb +22 -1
  24. data/lib/polyrun/coverage/merge/formatters.rb +11 -3
  25. data/lib/polyrun/coverage/merge_merge_two.rb +112 -63
  26. data/lib/polyrun/coverage/merge_native.rb +37 -0
  27. data/lib/polyrun/coverage/reporting.rb +1 -1
  28. data/lib/polyrun/export/csv.rb +25 -0
  29. data/lib/polyrun/export/markdown.rb +47 -0
  30. data/lib/polyrun/hooks/shell_runner.rb +33 -0
  31. data/lib/polyrun/hooks.rb +4 -7
  32. data/lib/polyrun/reporting/failure_merge.rb +48 -12
  33. data/lib/polyrun/reporting/junit.rb +8 -7
  34. data/lib/polyrun/reporting/junit_report.rb +87 -0
  35. data/lib/polyrun/rspec/active_record_timeout_recovery.rb +51 -0
  36. data/lib/polyrun/rspec/example_debug.rb +20 -11
  37. data/lib/polyrun/rspec/sharded_formatter_compat.rb +30 -7
  38. data/lib/polyrun/spec_quality/profile.rb +24 -12
  39. data/lib/polyrun/spec_quality/report.rb +93 -0
  40. data/lib/polyrun/spec_quality.rb +19 -11
  41. data/lib/polyrun/timing/summary.rb +55 -5
  42. data/lib/polyrun/version.rb +1 -1
  43. data/polyrun.gemspec +8 -1
  44. metadata +59 -2
@@ -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?
@@ -82,7 +83,7 @@ module Polyrun
82
83
  group = example.metadata[:example_group]
83
84
  Polyrun::Log.puts "\n\nRunning #{group[:file_path]}:#{group[:line_number]}"
84
85
 
85
- level = log_level
86
+ level = ExampleDebug.log_level
86
87
  Rails.logger.level = level
87
88
  if defined?(ActiveRecord::Base)
88
89
  ar_logger = Logger.new(Polyrun::Log.stdout)
@@ -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,27 +1,50 @@
1
1
  module Polyrun
2
2
  module RSpec
3
- # Formatter tweaks for progress bars (e.g. Fuubar) under +POLYRUN_SHARD_*+ workers.
3
+ # Formatter tweaks for progress formatters under +POLYRUN_SHARD_*+ workers.
4
4
  module ShardedFormatterCompat
5
+ TextFormatterSilencer = Module.new do
6
+ def seed(_notification)
7
+ end
8
+
9
+ def dump_summary(_notification)
10
+ end
11
+
12
+ def dump_pending(_notification)
13
+ end
14
+ end
15
+
16
+ FuubarSeedSilencer = Module.new do
17
+ def seed(_notification)
18
+ end
19
+ end
20
+
5
21
  module_function
6
22
 
7
23
  def install!(rspec_config: fetch_rspec_configuration!)
8
24
  return unless sharded_worker?
9
25
 
10
26
  rspec_config.silence_filter_announcements = true
11
- noop_fuubar_seed! if defined?(Fuubar)
27
+ silence_text_formatter_noise!
28
+ silence_fuubar_seed! if defined?(Fuubar)
12
29
  end
13
30
 
14
31
  def sharded_worker?
15
32
  !ENV["POLYRUN_SHARD_TOTAL"].to_s.strip.empty?
16
33
  end
17
34
 
18
- def noop_fuubar_seed!
35
+ def silence_text_formatter_noise!
36
+ require "rspec/core/formatters/base_text_formatter"
37
+ klass = ::RSpec::Core::Formatters::BaseTextFormatter
38
+ return if klass.ancestors.include?(TextFormatterSilencer)
39
+
40
+ klass.prepend(TextFormatterSilencer)
41
+ end
42
+
43
+ def silence_fuubar_seed!
19
44
  return unless defined?(Fuubar)
45
+ return if Fuubar.ancestors.include?(FuubarSeedSilencer)
20
46
 
21
- Fuubar.class_eval do
22
- def seed(_notification)
23
- end
24
- end
47
+ Fuubar.prepend(FuubarSeedSilencer)
25
48
  end
26
49
 
27
50
  def fetch_rspec_configuration!
@@ -4,18 +4,30 @@ module Polyrun
4
4
  module Profile
5
5
  module_function
6
6
 
7
- def snapshot
8
- cpu = Process.times
9
- gc = GC.stat
10
- io = read_proc_io
11
- {
12
- "cpu_user" => cpu.utime,
13
- "cpu_system" => cpu.stime,
14
- "gc_allocated" => gc[:total_allocated_objects],
15
- "gc_heap_live" => gc[:heap_live_slots],
16
- "io_read_bytes" => io[:read_bytes],
17
- "io_write_bytes" => io[:write_bytes]
18
- }
7
+ def snapshot(dimensions: %w[cpu mem io wall])
8
+ dims = enabled_dimensions(dimensions)
9
+ capture_all = dims.empty?
10
+ out = {}
11
+
12
+ if capture_all || dims.include?("cpu") || dims.include?("wall")
13
+ cpu = Process.times
14
+ out["cpu_user"] = cpu.utime
15
+ out["cpu_system"] = cpu.stime
16
+ end
17
+
18
+ if capture_all || dims.include?("mem")
19
+ gc = GC.stat
20
+ out["gc_allocated"] = gc[:total_allocated_objects]
21
+ out["gc_heap_live"] = gc[:heap_live_slots]
22
+ end
23
+
24
+ if capture_all || dims.include?("io")
25
+ io = read_proc_io
26
+ out["io_read_bytes"] = io[:read_bytes]
27
+ out["io_write_bytes"] = io[:write_bytes]
28
+ end
29
+
30
+ out
19
31
  end
20
32
 
21
33
  def diff(before, after)
@@ -1,4 +1,7 @@
1
1
  # rubocop:disable Polyrun/FileLength, Metrics/ModuleLength -- report analysis + text formatting
2
+ require_relative "../export/csv"
3
+ require_relative "../export/markdown"
4
+
2
5
  module Polyrun
3
6
  module SpecQuality
4
7
  # Human-readable spec quality report from merged JSON.
@@ -55,6 +58,96 @@ module Polyrun
55
58
  lines.join("\n") + "\n"
56
59
  end
57
60
 
61
+ EXAMPLE_CSV_HEADERS = %w[example unique_lines line_churn max_line_churn shard_index].freeze
62
+
63
+ def format_csv(merged, cfg: {}, top: 30, profile: nil, plan_shards: nil)
64
+ rows = merged.fetch("examples", {}).map do |location, row|
65
+ [
66
+ location,
67
+ row["unique_lines"],
68
+ row["line_churn"],
69
+ row["max_line_churn"],
70
+ row["polyrun_shard_index"]
71
+ ]
72
+ end.sort_by { |row| [-row[2].to_i, row[0].to_s] }
73
+ rows = rows.first(top) if top
74
+ Export::Csv.generate(EXAMPLE_CSV_HEADERS, rows)
75
+ end
76
+
77
+ def format_markdown(merged, cfg: {}, top: 30, profile: nil, plan_shards: nil)
78
+ analysis = analyze(merged, cfg, plan_shards: plan_shards)
79
+ sections = markdown_sections(analysis, top).compact
80
+ Export::Markdown.document("Polyrun spec quality report", sections)
81
+ end
82
+
83
+ def markdown_sections(analysis, top)
84
+ [
85
+ markdown_shard_section(analysis[:shard_summary]),
86
+ markdown_zero_hit_section(analysis[:zero_hit], top),
87
+ markdown_hot_lines_section(analysis[:hot_lines], top),
88
+ markdown_churn_section(analysis[:line_churn], top)
89
+ ]
90
+ end
91
+
92
+ def markdown_shard_section(shard_summary)
93
+ shard_rows = (shard_summary || {}).sort_by { |shard, _| shard.to_s }.map do |shard, stats|
94
+ [shard, stats["examples"], stats["zero_hit"], stats["line_churn"]]
95
+ end
96
+ return if shard_rows.empty?
97
+
98
+ {
99
+ heading: "Shard attribution",
100
+ headers: %w[shard examples zero_hit line_churn],
101
+ rows: shard_rows
102
+ }
103
+ end
104
+
105
+ def markdown_zero_hit_section(zero_hit, top)
106
+ zero_rows = zero_hit.keys.sort.first(top).zip
107
+ {
108
+ heading: "Zero production lines",
109
+ headers: %w[example],
110
+ rows: zero_rows.empty? ? [["(none)"]] : zero_rows
111
+ }
112
+ end
113
+
114
+ def markdown_hot_lines_section(hot_lines, top)
115
+ hot_rows = hot_lines.first(top).map do |line, stats|
116
+ [line, stats["example_count"], stats["total_hits"]]
117
+ end
118
+ {
119
+ heading: "Hot lines",
120
+ headers: %w[line example_count total_hits],
121
+ rows: hot_rows.empty? ? [["(none)", 0, 0]] : hot_rows
122
+ }
123
+ end
124
+
125
+ def markdown_churn_section(churn_rows, top)
126
+ rows = churn_rows.first(top).map do |location, row|
127
+ [location, row["line_churn"], row["max_line_churn"]]
128
+ end
129
+ {
130
+ heading: "Per-example line churn",
131
+ headers: %w[example line_churn max_line_churn],
132
+ rows: rows.empty? ? [["(none)", 0, 0]] : rows
133
+ }
134
+ end
135
+
136
+ def render(merged, format: "text", **kwargs)
137
+ case format.to_s.downcase
138
+ when "text", "console", "txt"
139
+ format_report(merged, **kwargs)
140
+ when "json"
141
+ JSON.pretty_generate(analyze(merged, kwargs[:cfg] || {}, plan_shards: kwargs[:plan_shards]))
142
+ when "csv"
143
+ format_csv(merged, **kwargs)
144
+ when "markdown", "md"
145
+ format_markdown(merged, **kwargs)
146
+ else
147
+ raise Polyrun::Error, "report-spec-quality: unknown format #{format.inspect} (use text, json, csv, or markdown)"
148
+ end
149
+ end
150
+
58
151
  def gate_violations(merged, cfg = {})
59
152
  cfg = default_cfg(cfg)
60
153
  analysis = analyze(merged, cfg)
@@ -58,8 +58,12 @@ module Polyrun
58
58
  @current = {
59
59
  location: normalize_location(location),
60
60
  wall_start: wall_start || Process.clock_gettime(Process::CLOCK_MONOTONIC),
61
- coverage_before: Coverage::ExampleDiff.peek_blob,
62
- profile_before: Profile.snapshot,
61
+ coverage_before: Coverage::ExampleDiff.peek_blob(
62
+ root: @config["root"],
63
+ track_under: @config["track_under"],
64
+ ignore_paths: @config["ignore_paths"]
65
+ ),
66
+ profile_before: Profile.snapshot(dimensions: @config["profile"]),
63
67
  sql_count: 0,
64
68
  sql_fingerprints: Hash.new(0),
65
69
  factory_counts: {}
@@ -78,16 +82,9 @@ module Polyrun
78
82
  loc = location || cur[:location]
79
83
  return if loc.nil? || loc.to_s.empty?
80
84
 
81
- after_cov = Coverage::ExampleDiff.peek_blob
82
- delta = Coverage::ExampleDiff.diff(cur[:coverage_before], after_cov)
83
- delta = Coverage::ExampleDiff.apply_track_under(
84
- delta,
85
- root: @config["root"],
86
- track_under: @config["track_under"],
87
- ignore_paths: @config["ignore_paths"]
88
- )
85
+ delta = coverage_delta_for_example(cur)
89
86
 
90
- profile_after = Profile.snapshot
87
+ profile_after = Profile.snapshot(dimensions: @config["profile"])
91
88
  profile_delta = Profile.diff(cur[:profile_before], profile_after)
92
89
  wall = Process.clock_gettime(Process::CLOCK_MONOTONIC) - cur[:wall_start]
93
90
  profile_delta["wall"] = wall
@@ -169,6 +166,17 @@ module Polyrun
169
166
  Polyrun::Partition::TimingKeys.canonical_file_path(File.expand_path(s, root))
170
167
  end
171
168
 
169
+ def coverage_delta_for_example(cur)
170
+ after_source = Coverage::ExampleDiff.coverage_active? ? ::Coverage.peek_result : {}
171
+ Coverage::ExampleDiff.diff(
172
+ cur[:coverage_before],
173
+ after_source,
174
+ root: @config["root"],
175
+ track_under: @config["track_under"],
176
+ ignore_paths: @config["ignore_paths"]
177
+ )
178
+ end
179
+
172
180
  def build_row(cur, location, delta, profile_delta, factory_counts)
173
181
  profile = Profile.slice_profile(profile_delta, @config["profile"])
174
182
  repeated_sql = cur[:sql_fingerprints].select { |_sql, n| n >= min_query_count }.transform_keys(&:to_s)
@@ -1,26 +1,76 @@
1
1
  require_relative "stats"
2
+ require_relative "../export/csv"
3
+ require_relative "../export/markdown"
2
4
 
3
5
  module Polyrun
4
6
  module Timing
5
7
  # Human-readable slow-file list from merged timing JSON (per-file cost).
6
8
  module Summary
9
+ CSV_HEADERS = %w[rank path last_seconds min max mean p95 runs failures timeouts].freeze
10
+ MARKDOWN_HEADERS = %w[rank path seconds].freeze
11
+
7
12
  module_function
8
13
 
14
+ def ranked_entries(merged, top: 30)
15
+ return [] if merged.nil? || merged.empty?
16
+
17
+ merged.map { |path, entry| [path, Stats.normalize_entry(entry)] }
18
+ .sort_by { |(_, entry)| -Stats.binpack_weight(entry) }
19
+ .first(Integer(top))
20
+ end
21
+
9
22
  # +merged+ is path (String) => seconds (Float) or stats Hash, as produced by +Timing::Merge.merge_files+.
10
23
  def format_slow_files(merged, top: 30, title: "Polyrun slowest files (by wall time, seconds)")
11
24
  return "#{title}\n (no data)\n" if merged.nil? || merged.empty?
12
25
 
13
- pairs = merged.map { |path, sec| [path, Stats.binpack_weight(sec)] }
14
- .sort_by { |(_, sec)| -sec.to_f }.first(Integer(top))
15
26
  lines = [title, ""]
16
- pairs.each_with_index do |(path, sec), i|
17
- lines << format(" %2d. %s %.4f", i + 1, path, sec.to_f)
27
+ ranked_entries(merged, top: top).each_with_index do |(path, entry), index|
28
+ lines << format(" %2d. %s %.4f", index + 1, path, Stats.binpack_weight(entry).to_f)
18
29
  end
19
30
  lines.join("\n") + "\n"
20
31
  end
21
32
 
33
+ def format_csv(merged, top: 30)
34
+ rows = ranked_entries(merged, top: top).each_with_index.map do |(path, entry), index|
35
+ [
36
+ index + 1,
37
+ path,
38
+ entry["last_seconds"],
39
+ entry["min"],
40
+ entry["max"],
41
+ entry["mean"],
42
+ entry["p95"],
43
+ entry["runs"],
44
+ entry["failures"],
45
+ entry["timeouts"]
46
+ ]
47
+ end
48
+ Export::Csv.generate(CSV_HEADERS, rows)
49
+ end
50
+
51
+ def format_markdown(merged, top: 30, title: "Polyrun slowest files")
52
+ rows = ranked_entries(merged, top: top).each_with_index.map do |(path, entry), index|
53
+ [index + 1, path, format("%.4f", Stats.binpack_weight(entry).to_f)]
54
+ end
55
+ Export::Markdown.document(
56
+ title,
57
+ [{heading: "Slow files", headers: MARKDOWN_HEADERS, rows: rows}]
58
+ )
59
+ end
60
+
61
+ def render(merged, format: "text", **kwargs)
62
+ case format.to_s.downcase
63
+ when "text", "console", "txt" then format_slow_files(merged, **kwargs)
64
+ when "csv" then format_csv(merged, **kwargs)
65
+ when "markdown", "md" then format_markdown(merged, **kwargs)
66
+ else
67
+ raise Polyrun::Error, "report-timing: unknown format #{format.inspect} (use text, csv, or markdown)"
68
+ end
69
+ end
70
+
22
71
  def write_file(merged, path, **kwargs)
23
- File.write(path, format_slow_files(merged, **kwargs))
72
+ format = kwargs.delete(:format) || "text"
73
+ File.write(path, render(merged, format: format, **kwargs))
24
74
  path
25
75
  end
26
76
  end
@@ -1,3 +1,3 @@
1
1
  module Polyrun
2
- VERSION = "2.1.3"
2
+ VERSION = "2.2.1"
3
3
  end
data/polyrun.gemspec CHANGED
@@ -10,10 +10,14 @@ Gem::Specification.new do |spec|
10
10
  spec.license = "MIT"
11
11
  spec.required_ruby_version = ">= 3.1.0"
12
12
 
13
- spec.files = 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"].reject { |f| File.directory?(f) }
13
+ spec.files = (
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"] +
15
+ Dir["ext/**/extconf.rb", "ext/**/*.c", "ext/**/*.h"]
16
+ ).uniq.reject { |f| File.directory?(f) }
14
17
  spec.bindir = "bin"
15
18
  spec.executables = ["polyrun"]
16
19
  spec.require_paths = ["lib"]
20
+ spec.extensions = ["ext/polyrun_coverage_merge/extconf.rb"]
17
21
 
18
22
  spec.metadata = {
19
23
  "homepage_uri" => spec.homepage,
@@ -25,6 +29,9 @@ Gem::Specification.new do |spec|
25
29
  # Normative: zero runtime dependencies (stdlib + vendored/native code only).
26
30
  # Ruby 3.5+: `benchmark` is no longer a default gem; keep scripts and `rake bench_merge` warning-free.
27
31
  spec.add_development_dependency "benchmark", ">= 0.3"
32
+ spec.add_development_dependency "benchmark-ips", "~> 2"
33
+ spec.add_development_dependency "memory_profiler", "~> 1"
34
+ spec.add_development_dependency "stackprof", "~> 0.2"
28
35
  spec.add_development_dependency "rake", "~> 13.0"
29
36
  spec.add_development_dependency "rspec", "~> 3.12"
30
37
  spec.add_development_dependency "appraisal", "~> 2.5"
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.1.3
4
+ version: 2.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -23,6 +23,48 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.3'
26
+ - !ruby/object:Gem::Dependency
27
+ name: benchmark-ips
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: memory_profiler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: stackprof
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.2'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.2'
26
68
  - !ruby/object:Gem::Dependency
27
69
  name: rake
28
70
  requirement: !ruby/object:Gem::Requirement
@@ -153,7 +195,8 @@ email:
153
195
  - contact@kiskolabs.com
154
196
  executables:
155
197
  - polyrun
156
- extensions: []
198
+ extensions:
199
+ - ext/polyrun_coverage_merge/extconf.rb
157
200
  extra_rdoc_files: []
158
201
  files:
159
202
  - CHANGELOG.md
@@ -164,8 +207,13 @@ files:
164
207
  - SECURITY.md
165
208
  - bin/polyrun
166
209
  - docs/SETUP_PROFILE.md
210
+ - ext/polyrun_coverage_merge/coverage_merge.c
211
+ - ext/polyrun_coverage_merge/extconf.rb
167
212
  - lib/polyrun.rb
213
+ - lib/polyrun/benchmark/profile.rb
214
+ - lib/polyrun/benchmark/report.rb
168
215
  - lib/polyrun/cli.rb
216
+ - lib/polyrun/cli/benchmark_commands.rb
169
217
  - lib/polyrun/cli/ci_shard_hooks.rb
170
218
  - lib/polyrun/cli/ci_shard_run_command.rb
171
219
  - lib/polyrun/cli/ci_shard_run_parse.rb
@@ -208,6 +256,9 @@ files:
208
256
  - lib/polyrun/coverage/collector_finish.rb
209
257
  - lib/polyrun/coverage/collector_fragment_meta.rb
210
258
  - lib/polyrun/coverage/example_diff.rb
259
+ - lib/polyrun/coverage/example_diff_snapshot.rb
260
+ - lib/polyrun/coverage/example_diff_track_filter.rb
261
+ - lib/polyrun/coverage/file_stats_report.rb
211
262
  - lib/polyrun/coverage/filter.rb
212
263
  - lib/polyrun/coverage/formatter.rb
213
264
  - lib/polyrun/coverage/merge.rb
@@ -222,6 +273,7 @@ files:
222
273
  - lib/polyrun/coverage/merge/html/template.html.erb
223
274
  - lib/polyrun/coverage/merge_fragment_meta.rb
224
275
  - lib/polyrun/coverage/merge_merge_two.rb
276
+ - lib/polyrun/coverage/merge_native.rb
225
277
  - lib/polyrun/coverage/rails.rb
226
278
  - lib/polyrun/coverage/reporting.rb
227
279
  - lib/polyrun/coverage/result.rb
@@ -242,8 +294,11 @@ files:
242
294
  - lib/polyrun/database/url_builder/template_prepare.rb
243
295
  - lib/polyrun/debug.rb
244
296
  - lib/polyrun/env/ci.rb
297
+ - lib/polyrun/export/csv.rb
298
+ - lib/polyrun/export/markdown.rb
245
299
  - lib/polyrun/hooks.rb
246
300
  - lib/polyrun/hooks/dsl.rb
301
+ - lib/polyrun/hooks/shell_runner.rb
247
302
  - lib/polyrun/hooks/worker_runner.rb
248
303
  - lib/polyrun/hooks/worker_shell.rb
249
304
  - lib/polyrun/log.rb
@@ -279,9 +334,11 @@ files:
279
334
  - lib/polyrun/reporting/failure_merge.rb
280
335
  - lib/polyrun/reporting/junit.rb
281
336
  - lib/polyrun/reporting/junit_emit.rb
337
+ - lib/polyrun/reporting/junit_report.rb
282
338
  - lib/polyrun/reporting/rspec_failure_fragment_formatter.rb
283
339
  - lib/polyrun/reporting/rspec_junit.rb
284
340
  - lib/polyrun/rspec.rb
341
+ - lib/polyrun/rspec/active_record_timeout_recovery.rb
285
342
  - lib/polyrun/rspec/example_debug.rb
286
343
  - lib/polyrun/rspec/example_debug_instrumentation.rb
287
344
  - lib/polyrun/rspec/sharded_formatter_compat.rb