Skip to content

Commit

Permalink
Merge pull request #53 from threez/capture_example_output
Browse files Browse the repository at this point in the history
implements system-out and system-err output formating using example-metadata
  • Loading branch information
sj26 committed May 26, 2018
2 parents 468c54b + 49195d8 commit 9858785
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 5 deletions.
24 changes: 24 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,30 @@ For use with `parallel_tests`, add `$TEST_ENV_NUMBER` in the output file option

The formatter includes `$TEST_ENV_NUMBER` in the test suite name within the XML, too.

### Capturing output

If you like, you can capture the standard output and error streams of each test into the `:stdout` and `:stderr` example metadata which will be added to the junit report, e.g.:

```ruby
# spec_helper.rb

RSpec.configure do |config|
# register around filter that captures stdout and stderr
config.around(:each) do |example|
$stdout = StringIO.new
$stderr = StringIO.new

example.run

example.metadata[:stdout] = $stdout.string
example.metadata[:stderr] = $stderr.string

$stdout = STDOUT
$stderr = STDERR
end
end
```

## Caveats

* XML can only represent a [limited subset of characters][xml-charsets] which excludes null bytes and most control characters. This gem will use character entities where possible and fall back to replacing invalid characters with Ruby-like escape codes otherwise. For example, the null byte becomes `\0`.
Expand Down
6 changes: 6 additions & 0 deletions example/spec/example_spec.rb
@@ -1,3 +1,4 @@
require "spec_helper"
require_relative "shared_examples"

describe "some example specs" do
Expand Down Expand Up @@ -42,4 +43,9 @@
end

it_should_behave_like "shared examples"

it "can capture stdout and stderr" do
$stdout.puts "Test"
$stderr.puts "Bar"
end
end
15 changes: 15 additions & 0 deletions example/spec/spec_helper.rb
@@ -0,0 +1,15 @@
RSpec.configure do |config|
# register around filter that captures stderr and stdout
config.around(:each) do |example|
$stdout = StringIO.new
$stderr = StringIO.new

example.run

example.metadata[:stdout] = $stdout.string
example.metadata[:stderr] = $stderr.string

$stdout = STDOUT
$stderr = STDERR
end
end
15 changes: 15 additions & 0 deletions lib/rspec_junit_formatter.rb
Expand Up @@ -71,9 +71,24 @@ def xml_dump_example(example)
output << %{ time="#{escape("%.6f" % duration_for(example))}"}
output << %{>}
yield if block_given?
xml_dump_output(example)
output << %{</testcase>\n}
end

def xml_dump_output(example)
if (stdout = stdout_for(example)) && !stdout.empty?
output << %{<system-out>}
output << escape(stdout)
output << %{</system-out>}
end

if (stderr = stderr_for(example)) && !stderr.empty?
output << %{<system-err>}
output << escape(stderr)
output << %{</system-err>}
end
end

# Inversion of character range from https://www.w3.org/TR/xml/#charsets
ILLEGAL_REGEXP = Regexp.new(
"[^" <<
Expand Down
8 changes: 8 additions & 0 deletions lib/rspec_junit_formatter/rspec2.rb
Expand Up @@ -69,4 +69,12 @@ def find_shared_group(example)
def group_and_parent_groups(example)
example.example_group.parent_groups + [example.example_group]
end

def stdout_for(example)
example.metadata[:stdout]
end

def stderr_for(example)
example.metadata[:stderr]
end
end
8 changes: 8 additions & 0 deletions lib/rspec_junit_formatter/rspec3.rb
Expand Up @@ -122,6 +122,14 @@ def without_color
yield
end
end

def stdout_for(example_notification)
example_notification.example.metadata[:stdout]
end

def stderr_for(example_notification)
example_notification.example.metadata[:stderr]
end
end

# rspec-core 3.0.x forgot to mark this as a module function which causes:
Expand Down
17 changes: 12 additions & 5 deletions spec/rspec_junit_formatter_spec.rb
Expand Up @@ -41,7 +41,7 @@ def execute_example_spec

let(:testsuite) { doc.xpath("/testsuite").first }
let(:testcases) { doc.xpath("/testsuite/testcase") }
let(:successful_testcases) { doc.xpath("/testsuite/testcase[count(*)=0]") }
let(:successful_testcases) { doc.xpath("/testsuite/testcase[not(failure) and not(skipped)]") }
let(:pending_testcases) { doc.xpath("/testsuite/testcase[skipped]") }
let(:failed_testcases) { doc.xpath("/testsuite/testcase[failure]") }
let(:shared_testcases) { doc.xpath("/testsuite/testcase[contains(@name, 'shared example')]") }
Expand All @@ -57,7 +57,7 @@ def execute_example_spec
expect(testsuite).not_to be(nil)

expect(testsuite["name"]).to eql("rspec")
expect(testsuite["tests"]).to eql("11")
expect(testsuite["tests"]).to eql("12")
expect(testsuite["skipped"]).to eql("1")
expect(testsuite["failures"]).to eql("8")
expect(testsuite["errors"]).to eql("0")
Expand All @@ -67,7 +67,7 @@ def execute_example_spec

# it has some test cases

expect(testcases.size).to eql(11)
expect(testcases.size).to eql(12)

testcases.each do |testcase|
expect(testcase["classname"]).to eql("spec.example_spec")
Expand All @@ -77,11 +77,14 @@ def execute_example_spec

# it has successful test cases

expect(successful_testcases.size).to eql(2)
expect(successful_testcases.size).to eql(3)

successful_testcases.each do |testcase|
expect(testcase).not_to be(nil)
expect(testcase.children).to be_empty
# test results that capture stdout / stderr are not 'empty'
unless (testcase["name"]) =~ /capture stdout and stderr/
expect(testcase.children).to be_empty
end
end

# it has pending test cases
Expand Down Expand Up @@ -146,6 +149,10 @@ def execute_example_spec
# it correctly escapes reserved xml characters

expect(doc.xpath("//testcase[contains(@name, 'html')]").first[:name]).to eql(%{some example specs escapes <html tags='correctly' and="such &amp; such">})

# it correctly captures stdout / stderr output
expect(doc.xpath("//testcase/system-out").text).to eql("Test\n")
expect(doc.xpath("//testcase/system-err").text).to eql("Bar\n")
end

context "when $TEST_ENV_NUMBER is set" do
Expand Down

0 comments on commit 9858785

Please sign in to comment.