Skip to content

Commit

Permalink
Nearly support compound matchers on CaptureStreamToTempfile
Browse files Browse the repository at this point in the history
Sadly, it doesn't quite work on StdErrSplitter, because of the existing
(but possibly non-impactful?) bug around restoring the original stream
during the ensure block.
  • Loading branch information
nevinera committed May 20, 2024
1 parent 5d9ee8b commit b07ee62
Showing 1 changed file with 33 additions and 6 deletions.
39 changes: 33 additions & 6 deletions lib/rspec/matchers/built_in/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def self.capture(block)
captured_stream.string
ensure
$stdout = original_stream
$stdout.write(captured_stream.string) unless $stdout == STDOUT
$stdout.write(captured_stream.string) unless $stdout == STDOUT # rubocop:disable Style/GlobalStdStream
end
end

Expand All @@ -173,7 +173,7 @@ def self.capture(block)
captured_stream.string
ensure
$stderr = original_stream
$stderr.write(captured_stream.string) unless $stderr == STDERR
$stderr.write(captured_stream.string) unless $stderr == STDERR # rubocop:disable Style/GlobalStdStream
end
end

Expand All @@ -188,21 +188,48 @@ def capture(block)
# thread, fileutils, etc), so it's worth delaying it until this point.
require 'tempfile'

# This is.. awkward-looking. But it's written this way because of how
# compound matchers work - we essentially need to be able to tell if
# we're in an _inner_ matcher, so we can pass the stream-output along
# to the outer matcher for further evaluation in that case. Added to
# that, it's fairly difficult to _tell_, because the only actual state
# we have access to is the stream itself, and in the case of stderr,
# that stream is really a RSpec::Support::StdErrSplitter (which is why
# we're testing `is_a?(File)` in such an obnoxious way).
inner_matcher = stream.to_io.is_a?(File)

# FIXME: stream.clone isn't sufficient right now - the StdErrSplitter
# clones with the same stream, and reopening the stream on either of
# them effectively updates both. Which means that the ensure-reopen
# further down doesn't really do anything currently.
original_stream = stream.clone

captured_stream = Tempfile.new(name)

begin
captured_stream.sync = true
stream.reopen(captured_stream)
block.call
captured_stream.rewind
captured_stream.read
read_contents(captured_stream)
ensure
captured_content = inner_matcher ? read_contents(captured_stream) : nil
stream.reopen(original_stream)
captured_stream.close
captured_stream.unlink
stream.write(captured_content) if captured_content
clean_up_tempfile(captured_stream)
end
end

private

def read_contents(strm)
strm.rewind
strm.read
end

def clean_up_tempfile(tempfile)
tempfile.close
tempfile.unlink
end
end
end
end
Expand Down

0 comments on commit b07ee62

Please sign in to comment.