Skip to content

Commit

Permalink
Merge pull request cucumber#259 from rtyler/feature/junit-with-sysout
Browse files Browse the repository at this point in the history
Include the output $stderr and $stdout in JUnit formatted XML
  • Loading branch information
mattwynne committed Apr 30, 2012
2 parents 6ed5f60 + b9ce6d6 commit ef187d3
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 33 deletions.
62 changes: 62 additions & 0 deletions lib/cucumber/formatter/interceptor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

module Cucumber
module Formatter
module Interceptor
class Pipe
attr_reader :pipe, :buffer
def initialize(pipe)
@pipe = pipe
@buffer = []
@wrapped = true
end

def write(str)
@buffer << str if @wrapped
return @pipe.write(str)
end

def unwrap!
@wrapped = false
@pipe
end

def method_missing(method, *args, &blk)
@pipe.send(method, *args, &blk)
end

def self.validate_pipe(pipe)
unless [:stdout, :stderr].include? pipe
raise ArgumentError, '#wrap only accepts :stderr or :stdout'
end
end

def self.unwrap!(pipe)
validate_pipe pipe
wrapped = nil
case pipe
when :stdout
wrapped = $stdout
$stdout = wrapped.unwrap!
when :stderr
wrapped = $stderr
$stderr = wrapped.unwrap!
end
wrapped
end

def self.wrap(pipe)
validate_pipe pipe

case pipe
when :stderr
$stderr = self.new($stderr)
return $stderr
when :stdout
$stdout = self.new($stdout)
return $stdout
end
end
end
end
end
end
42 changes: 29 additions & 13 deletions lib/cucumber/formatter/junit.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
require 'cucumber/formatter/ordered_xml_markup'
require 'cucumber/formatter/io'
require 'cucumber/formatter/interceptor'
require 'fileutils'

module Cucumber
module Formatter
# The formatter used for <tt>--format junit</tt>
class Junit
include Io

class UnNamedFeatureError < StandardError
def initialize(feature_file)
super("The feature in '#{feature_file}' does not have a name. The JUnit XML format requires a name for the testsuite element.")
end
end

def initialize(step_mother, io, options)
@reportdir = ensure_dir(io, "junit")
@options = options
Expand All @@ -24,13 +25,17 @@ def before_feature(feature)
@failures = @errors = @tests = @skipped = 0
@builder = OrderedXmlMarkup.new( :indent => 2 )
@time = 0
# In order to fill out <system-err/> and <system-out/>, we need to
# intercept the $stderr and $stdout
@interceptedout = Interceptor::Pipe.wrap(:stdout)
@interceptederr = Interceptor::Pipe.wrap(:stderr)
end

def before_feature_element(feature_element)
@in_examples = Ast::ScenarioOutline === feature_element
@steps_start = Time.now
end

def after_feature(feature)
@testsuite = OrderedXmlMarkup.new( :indent => 2 )
@testsuite.instruct!
Expand All @@ -42,15 +47,24 @@ def after_feature(feature)
:time => "%.6f" % @time,
:name => @feature_name ) do
@testsuite << @builder.target!
@testsuite.tag!('system-out') do
@testsuite.cdata! @interceptedout.buffer.join
end
@testsuite.tag!('system-err') do
@testsuite.cdata! @interceptederr.buffer.join
end
end

write_file(feature_result_filename(feature.file), @testsuite.target!)

Interceptor::Pipe.unwrap! :stdout
Interceptor::Pipe.unwrap! :stderr
end

def before_background(*args)
@in_background = true
end

def after_background(*args)
@in_background = false
end
Expand All @@ -68,23 +82,23 @@ def scenario_name(keyword, name, file_colon_line, source_indent)

def before_steps(steps)
end

def after_steps(steps)
return if @in_background || @in_examples

duration = Time.now - @steps_start
if steps.failed?
steps.each { |step| @output += "#{step.keyword}#{step.name}\n" }
@output += "\nMessage:\n"
end
build_testcase(duration, steps.status, steps.exception)
end

def before_examples(*args)
@header_row = true
@in_examples = true
end

def after_examples(*args)
@in_examples = false
end
Expand All @@ -106,7 +120,7 @@ def after_table_row(table_row)
end
build_testcase(duration, table_row.status, table_row.exception, name_suffix)
end

@header_row = false if @header_row
end

Expand All @@ -131,22 +145,24 @@ def build_testcase(duration, status, exception = nil, suffix = "")
@builder.skipped
@skipped += 1
end
@builder.tag!('system-out')
@builder.tag!('system-err')
end
@tests += 1
end

def format_exception(exception)
(["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n")
end

def feature_result_filename(feature_file)
File.join(@reportdir, "TEST-#{basename(feature_file)}.xml")
end

def basename(feature_file)
File.basename(feature_file.gsub(/[\\\/]/, '-'), '.feature')
end

def write_file(feature_filename, data)
File.open(feature_filename, 'w') { |file| file.write(data) }
end
Expand Down
111 changes: 111 additions & 0 deletions spec/cucumber/formatter/interceptor_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
require 'spec_helper'
require 'cucumber/formatter/interceptor'

module Cucumber::Formatter
describe Interceptor::Pipe do
let(:pipe) do
pipe = double('original pipe')
pipe.stub(:instance_of?).and_return(true)
pipe
end

describe '#wrap!' do
it 'should raise an ArgumentError if its not passed :stderr/:stdout' do
expect {
Interceptor::Pipe.wrap(:nonsense)
}.to raise_error(ArgumentError)

end

context 'when passed :stderr' do
before :each do
@stderr = $stdout
end

it 'should wrap $stderr' do
wrapped = Interceptor::Pipe.wrap(:stderr)
$stderr.should be_instance_of Interceptor::Pipe
$stderr.should be wrapped
end

after :each do
$stderr = @stderr
end
end

context 'when passed :stdout' do
before :each do
@stdout = $stdout
end

it 'should wrap $stdout' do
wrapped = Interceptor::Pipe.wrap(:stdout)
$stdout.should be_instance_of Interceptor::Pipe
$stdout.should be wrapped
end

after :each do
$stdout = @stdout
end
end
end

describe '#unwrap!' do
before :each do
@stdout = $stdout
@wrapped = Interceptor::Pipe.wrap(:stdout)
end

it 'should raise an ArgumentError if it wasn\'t passed :stderr/:stdout' do
expect {
Interceptor::Pipe.unwrap!(:nonsense)
}.to raise_error(ArgumentError)
end

it 'should reset $stdout when #unwrap! is called' do
interceptor = Interceptor::Pipe.unwrap! :stdout
interceptor.should be_instance_of Interceptor::Pipe
$stdout.should_not be interceptor
end

it 'should disable the pipe bypass' do
buffer = '(::)'
Interceptor::Pipe.unwrap! :stdout

@wrapped.should_receive(:write).with(buffer)
@wrapped.buffer.should_not_receive(:<<)
@wrapped.write(buffer)
end

after :each do
$stdout = @stdout
end
end

describe '#write' do
let(:buffer) { 'Some stupid buffer' }
let(:pi) { Interceptor::Pipe.new(pipe) }

it 'should write arguments to the original pipe' do
pipe.should_receive(:write).with(buffer).and_return(buffer.size)
pi.write(buffer).should == buffer.size
end

it 'should add the buffer to its stored output' do
pipe.stub(:write)
pi.write(buffer)
pi.buffer.should_not be_empty
pi.buffer.first.should == buffer
end
end

describe '#method_missing' do
let(:pi) { Interceptor::Pipe.new(pipe) }

it 'should pass #tty? to the original pipe' do
pipe.should_receive(:tty?).and_return(true)
pi.tty?.should be true
end
end
end
end
Loading

0 comments on commit ef187d3

Please sign in to comment.