-
Notifications
You must be signed in to change notification settings - Fork 500
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new ParallelTests::RSpec::VerboseFormatter for enhanced debugging (…
…#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.
- Loading branch information
Showing
5 changed files
with
133 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rspec/core/formatters/base_text_formatter' | ||
require 'parallel_tests/rspec/runner' | ||
|
||
class ParallelTests::RSpec::VerboseFormatter < RSpec::Core::Formatters::BaseTextFormatter | ||
RSpec::Core::Formatters.register( | ||
self, | ||
:example_group_started, | ||
:example_group_finished, | ||
:example_started, | ||
:example_passed, | ||
:example_pending, | ||
:example_failed | ||
) | ||
|
||
def initialize(output) | ||
super | ||
@line = [] | ||
end | ||
|
||
def example_group_started(notification) | ||
@line.push(notification.group.description) | ||
end | ||
|
||
def example_group_finished(_notification) | ||
@line.pop | ||
end | ||
|
||
def example_started(notification) | ||
@line.push(notification.example.description) | ||
output_formatted_line('STARTED', :yellow) | ||
end | ||
|
||
def example_passed(_passed) | ||
output_formatted_line('PASSED', :success) | ||
@line.pop | ||
end | ||
|
||
def example_pending(_pending) | ||
output_formatted_line('PENDING', :pending) | ||
@line.pop | ||
end | ||
|
||
def example_failed(_failure) | ||
output_formatted_line('FAILED', :failure) | ||
@line.pop | ||
end | ||
|
||
private | ||
|
||
def output_formatted_line(status, console_code) | ||
prefix = ["[#{Process.pid}]"] | ||
if ENV.include?('TEST_ENV_NUMBER') | ||
test_env_number = ENV['TEST_ENV_NUMBER'] == '' ? 1 : Integer(ENV['TEST_ENV_NUMBER']) | ||
prefix << "[#{test_env_number}]" | ||
end | ||
prefix << RSpec::Core::Formatters::ConsoleCodes.wrap("[#{status}]", console_code) | ||
|
||
output.puts [*prefix, *@line].join(' ') | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'spec_helper' | ||
|
||
describe ParallelTests::RSpec::VerboseFormatter do | ||
def run(command) | ||
result = IO.popen(command, err: [:child, :out], &:read) | ||
raise "FAILED: #{result}" unless $?.success? | ||
result | ||
end | ||
|
||
it 'outputs verbose information' do | ||
repo_root = Dir.pwd | ||
|
||
use_temporary_directory do | ||
# setup simple structure | ||
FileUtils.mkdir "spec" | ||
|
||
File.write "spec/foo_spec.rb", <<-RUBY | ||
describe "Foo" do | ||
it "foo" do | ||
sleep 0.5 | ||
expect(true).to be(true) | ||
end | ||
end | ||
RUBY | ||
|
||
File.write "spec/bar_spec.rb", <<-RUBY | ||
describe "Bar" do | ||
it "bar" do | ||
sleep 0.25111 | ||
expect(true).to be(true) | ||
end | ||
end | ||
RUBY | ||
|
||
result = run [ | ||
"ruby", | ||
"#{repo_root}/bin/parallel_rspec", | ||
"-n", "2", | ||
"--", | ||
"--format", "ParallelTests::RSpec::VerboseFormatter", | ||
"--" | ||
] | ||
|
||
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Foo foo$/) | ||
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Foo foo$/) | ||
expect(result).to match(/^\[\d+\] \[(1|2)\] \[STARTED\] Bar bar$/) | ||
expect(result).to match(/^\[\d+\] \[(1|2)\] \[PASSED\] Bar bar$/) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters