Skip to content

Commit 2c6c078

Browse files
committed
Add helpers for collecting simplecov data during a prspec run
Closes !9
1 parent 22adf1c commit 2c6c078

4 files changed

Lines changed: 118 additions & 0 deletions

File tree

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* Add support for simplecov reporting and collation
6+
37
## 3.0.0
48

59
* Fix reporting of `aggregate_failures` errors.

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,36 @@ You might also want to detect whether your spec suite is running under ParallelR
8282
do_something unless ParallelRSpec.running?
8383
```
8484

85+
## SimpleCov
86+
87+
SimpleCov doesn't track coverage across forked processes by default. ParallelRSpec ships a helper that handles the wiring. For example, in `spec_helper.rb` add something like:
88+
89+
```ruby
90+
require 'simplecov'
91+
require 'parallel_rspec/simplecov'
92+
93+
ParallelRSpec.simplecov('rails') do
94+
enable_coverage :branch
95+
add_filter '/spec/'
96+
end
97+
```
98+
99+
Per-worker output is intentionally always written as a `.resultset.json` so you can use any SimpleCov formatter without it correctly supporting parallel processes etc. Pass a `formatters:` array to control the final format.
100+
101+
```ruby
102+
require 'simplecov-cobertura'
103+
104+
ParallelRSpec.simplecov('rails',
105+
formatters: [
106+
SimpleCov::Formatter::HTMLFormatter,
107+
SimpleCov::Formatter::CoberturaFormatter,
108+
]
109+
) do
110+
enable_coverage :branch
111+
add_filter '/spec/'
112+
end
113+
```
114+
85115
## Limitations
86116

87117
ParallelRSpec can't parallelize the specs in groups that use `before(:context)` or `after(:context)` (AKA `before(:all)` or `after(:all)`) hooks, since these may mean that there is state shared between examples.

lib/parallel_rspec/runner.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def run_specs(example_groups)
6666

6767
persist_example_statuses
6868

69+
if defined?(ParallelRSpec) && ParallelRSpec.respond_to?(:simplecov_enabled?) && ParallelRSpec.simplecov_enabled?
70+
ParallelRSpec.collate_simplecov!
71+
end
72+
6973
success ? 0 : @configuration.failure_exit_code
7074
end
7175
end

lib/parallel_rspec/simplecov.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require "simplecov"
2+
require "parallel_rspec/config"
3+
4+
module ParallelRSpec
5+
@simplecov_enabled = false
6+
@simplecov_base_dir = nil
7+
@simplecov_formatters = nil
8+
9+
def self.simplecov_enabled?
10+
@simplecov_enabled
11+
end
12+
13+
def self.simplecov(profile = nil, formatters: nil, &block)
14+
::SimpleCov.enable_for_subprocesses true
15+
16+
base_dir = ::SimpleCov.coverage_dir
17+
18+
force_worker_settings = lambda do
19+
# Prevent forked processes races generating the HTML report; each
20+
# process just dumps a .resultset.json that the runner collates into
21+
# the user's chosen formatter(s) at the end of the run.
22+
::SimpleCov.print_error_status = false
23+
::SimpleCov.formatter ::SimpleCov::Formatter::SimpleFormatter
24+
::SimpleCov.minimum_coverage 0
25+
end
26+
27+
start_simplecov = lambda do |command_name, dir|
28+
::SimpleCov.command_name command_name
29+
::SimpleCov.coverage_dir dir
30+
::SimpleCov.start(profile, &block)
31+
end
32+
33+
ParallelRSpec::Config.after_fork do |worker_number|
34+
force_worker_settings.call
35+
start_simplecov.call(
36+
"RSpec Worker #{worker_number}",
37+
File.join(base_dir, "rspec_#{worker_number}"),
38+
)
39+
end
40+
41+
force_worker_settings.call
42+
start_simplecov.call("RSpec Server", File.join(base_dir, "rspec_server"))
43+
44+
@simplecov_base_dir = base_dir
45+
@simplecov_formatters = formatters
46+
@simplecov_enabled = true
47+
end
48+
49+
def self.collate_simplecov!
50+
base_dir = @simplecov_base_dir || ::SimpleCov.coverage_dir
51+
52+
# Force the server process's resultset to disk before we glob. SimpleCov's
53+
# own at_exit handler will harmlessly re-dump it after collation finishes.
54+
::SimpleCov.result.format!
55+
56+
pattern = File.join(base_dir, "rspec_*", ".resultset.json")
57+
resultsets = Dir.glob(pattern)
58+
59+
if resultsets.empty?
60+
warn "[parallel_rspec] no SimpleCov resultsets found under #{pattern}; skipping collation"
61+
return
62+
end
63+
64+
collate_formatter =
65+
case (@simplecov_formatters || []).length
66+
when 0 then nil
67+
when 1 then @simplecov_formatters.first
68+
else ::SimpleCov::Formatter::MultiFormatter.new(@simplecov_formatters)
69+
end
70+
71+
puts "Collating #{resultsets.size} SimpleCov resultset(s)..."
72+
73+
::SimpleCov.collate(resultsets) do
74+
coverage_dir base_dir
75+
formatter collate_formatter if collate_formatter
76+
end
77+
rescue => e
78+
warn "[parallel_rspec] simplecov collation failed: #{e.class}: #{e.message}"
79+
end
80+
end

0 commit comments

Comments
 (0)