Skip to content

Commit 7291dfd

Browse files
authored
Add new ParallelTests::RSpec::VerboseFormatter for enhanced debugging (grosser#926)
The new formatter allows users of parallel_rspec to more easily obtain detailed information about the examples running as they occur in real time. Background: In my project, it was observed that an RSpec example would hang intermittently on CI. At first, no team member was able to make this reliably fail enough on CI to facilitate debugging and no one was able to reproduce locally at all. The hang only occurred with parallel tests running and never with just the "rspec" command. In an attempt to gather more information, we began using the RSpec "documentation" formatter which outputs the "description", "context", and "it" strings. Unfortunately, when running in parallel, the output from different processes will interleave with one another making it extremely difficult to understand which example is responsible for the observed hang. Solution: Build a "documentation-like" formatter that plays nicely with the parallel_tests framework. The new formatter always ouputs all information on a single line to avoid important details from different processes interleaving with one another. This means the "describe" and "context" strings will be repeated across multiple "it" blocks. To further assist, the formatter include the PID and parallel process number to help identify which subprocess hangs and which one continues. The output will look something like: ``` [14403] [2] [STARTED] Foo foo [14402] [1] [STARTED] Bar bar [14402] [1] [PASSED] Bar bar ``` In the output above, it is clear the "Foo foo" example is still running. Using this formatter, our project's team was able to successfully identify the example causing the intermittent hang and then address it. As such, I'm hoping that this may be useful for a larger audience.
1 parent 6e9be6f commit 7291dfd

File tree

5 files changed

+133
-0
lines changed

5 files changed

+133
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44

55
- Sort the output of `runtime_logger` for RSpec to show slowest tests first
6+
- Add new `ParallelTests::RSpec::VerboseFormatter` to output detailed
7+
information about each example as it starts and finishes.
68

79
### Breaking Changes
810

Readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,22 @@ To rerun failures:
183183

184184
cucumber @tmp/cucumber_failures.log
185185

186+
Formatters
187+
==========
188+
189+
RSpec: VerboseFormatter
190+
191+
Output the RSpec PID, parallel process number, spec status, and the full
192+
example description. The formatter outputs one line when starting an example
193+
and then again when finishing. Use like the RSpec documentation but plays
194+
nicely with parallel processes.
195+
196+
Usage:
197+
198+
```console
199+
$ parallel_rspec -- --format ParallelTests::RSpec::VerboseFormatter --
200+
```
201+
186202
Setup for non-rails
187203
===================
188204

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
require 'rspec/core/formatters/base_text_formatter'
4+
require 'parallel_tests/rspec/runner'
5+
6+
class ParallelTests::RSpec::VerboseFormatter < RSpec::Core::Formatters::BaseTextFormatter
7+
RSpec::Core::Formatters.register(
8+
self,
9+
:example_group_started,
10+
:example_group_finished,
11+
:example_started,
12+
:example_passed,
13+
:example_pending,
14+
:example_failed
15+
)
16+
17+
def initialize(output)
18+
super
19+
@line = []
20+
end
21+
22+
def example_group_started(notification)
23+
@line.push(notification.group.description)
24+
end
25+
26+
def example_group_finished(_notification)
27+
@line.pop
28+
end
29+
30+
def example_started(notification)
31+
@line.push(notification.example.description)
32+
output_formatted_line('STARTED', :yellow)
33+
end
34+
35+
def example_passed(_passed)
36+
output_formatted_line('PASSED', :success)
37+
@line.pop
38+
end
39+
40+
def example_pending(_pending)
41+
output_formatted_line('PENDING', :pending)
42+
@line.pop
43+
end
44+
45+
def example_failed(_failure)
46+
output_formatted_line('FAILED', :failure)
47+
@line.pop
48+
end
49+
50+
private
51+
52+
def output_formatted_line(status, console_code)
53+
prefix = ["[#{Process.pid}]"]
54+
if ENV.include?('TEST_ENV_NUMBER')
55+
test_env_number = ENV['TEST_ENV_NUMBER'] == '' ? 1 : Integer(ENV['TEST_ENV_NUMBER'])
56+
prefix << "[#{test_env_number}]"
57+
end
58+
prefix << RSpec::Core::Formatters::ConsoleCodes.wrap("[#{status}]", console_code)
59+
60+
output.puts [*prefix, *@line].join(' ')
61+
end
62+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe ParallelTests::RSpec::VerboseFormatter do
6+
def run(command)
7+
result = IO.popen(command, err: [:child, :out], &:read)
8+
raise "FAILED: #{result}" unless $?.success?
9+
result
10+
end
11+
12+
it 'outputs verbose information' do
13+
repo_root = Dir.pwd
14+
15+
use_temporary_directory do
16+
# setup simple structure
17+
FileUtils.mkdir "spec"
18+
19+
File.write "spec/foo_spec.rb", <<-RUBY
20+
describe "Foo" do
21+
it "foo" do
22+
sleep 0.5
23+
expect(true).to be(true)
24+
end
25+
end
26+
RUBY
27+
28+
File.write "spec/bar_spec.rb", <<-RUBY
29+
describe "Bar" do
30+
it "bar" do
31+
sleep 0.25111
32+
expect(true).to be(true)
33+
end
34+
end
35+
RUBY
36+
37+
result = run [
38+
"ruby",
39+
"#{repo_root}/bin/parallel_rspec",
40+
"-n", "2",
41+
"--",
42+
"--format", "ParallelTests::RSpec::VerboseFormatter",
43+
"--"
44+
]
45+
46+
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Foo foo$/)
47+
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Foo foo$/)
48+
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Bar bar$/)
49+
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Bar bar$/)
50+
end
51+
end
52+
end

spec/spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
require 'parallel_tests/test/runtime_logger'
99
require 'parallel_tests/rspec/runtime_logger'
1010
require 'parallel_tests/rspec/summary_logger'
11+
require 'parallel_tests/rspec/verbose_formatter'
1112

1213
String.class_eval do
1314
def strip_heredoc

0 commit comments

Comments
 (0)