Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Support compound stream matchers #1460

Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Enhancements:

* Improve the IO emulation in the output capture matchers (`output(...).to_stdout` et al)
by adding `as_tty` and `as_not_tty` to change the `tty?` flags. (Sergio Gil Pérez de la Manga, #1459)
* Add support for compound output matchers. (Eric Mueller, #1460)

### 3.13.1 / 2024-06-13
[Full Changelog](http://github.com/rspec/rspec-expectations/compare/v3.13.0...v3.13.1)
Expand Down
36 changes: 32 additions & 4 deletions lib/rspec/matchers/built_in/output.rb
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ def capture(block)
captured_stream.string
ensure
$stdout = original_stream
$stdout.write(captured_stream.string) unless $stdout == STDOUT # rubocop:disable Style/GlobalStdStream
end
end

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

Expand All @@ -223,21 +225,47 @@ 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there not a way to assert against one of our internal classes here? checking to see if we're already in a splitter etc

Copy link
Contributor Author

@nevinera nevinera Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only in some of the cases. We're interacting with whatever's in $stderr and $stdout, so I think we're reliably getting an internal class for the former but not the latter. Wrapping with an internal class for the latter as well did occur to me as a potential solution, but it was large and involved enough that I wasn't willing to attempt it for such a minor issue :-\ (also I'm scared)


# Careful here - the StdErrSplitter is what is being cloned; we're
# relying on the implemented clone method of that class (in
# rspec-support) to actually clone the File for ensure-reopen.
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
nevinera marked this conversation as resolved.
Show resolved Hide resolved

def clean_up_tempfile(tempfile)
tempfile.close
tempfile.unlink
end
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions spec/rspec/matchers/built_in/output_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ def invalid_block
}.to fail_including("expected block to not output a string starting with \"f\" to #{stream_name}, but output \"foo\"\nDiff")
end
end

context "expect { ... }.to output(matcher1).#{matcher_method}.and output(matcher2).#{matcher_method}" do
it "passes if the block outputs lines to #{stream_name} matching both matchers", :pending => RSpec::Support::Ruby.jruby? && matcher_method =~ /any_process/ do
expect { print_to_stream "foo_bar" }.to matcher(/foo/).and matcher(/bar/)
end
end
end

module RSpec
Expand Down
Loading