rspec-expectations
rspec-expectations copied to clipboard
output matcher: add `as_tty` method to make the simulated stream behave as a TTY
The problem
I have an app which prints some output only if the standard output is a TTY. I'd say it's a relative standard practice:
puts "Whatever" if $stdout.tty?
Currently, this can't be tested with expect { ... }.to output(...).to_stdout because $stdout is replaced with a StringIO, which always returns false for .tty?, so the code never prints.
If this is a problem RSpec wants to fix (I guess you can argue that it's an antipattern and should be implemented in a different way), this PR does it.
The fix
~$stdout is still replaced with a StringIO and the matcher works the same, but some specific methods are called on the original stream. I only added .tty? because that's my usecase, but I guess there are other methods for which this could make sense (now or in the future).~
~I haven't contributed to RSpec before so I'm not familiar with coding standards, style, etc., so I appreciate any feedback, particularly on the following problem: I had to add an exception for the libraries allowed to be loaded for delegate (in fact there is another one for stringio for the same reason). Is this acceptable? If not, there are at least two alternatives I could implement:~
- ~Do some trick to require
delegateand define the class on runtime only onceoutputhas been called. This is already done for thetempfilerequire, although it would be a bit trickier than that because the class needs to be defined to.~ - ~Not use
SimpleDelegatorand implement the behavior usingmethod_missing.~
~Is any of these approaches preferred?~
Update: the description above no longer mathes the implementation, which I've changed according to the feedback received. It's simpler now and doesn't need such a long explanation :sweat_smile:
I think its reasonable to respect the original tty behaviour as we're just capturing the output, another option would be to explicitly define this with something like expect { ... }.to output(...).to_stdout.as_tty which sets it explicitly in the test
Thanks for such a fast feedback :smile:
@pirj: Will there be cases when it will be false? I guess, something like running or CI with a redirect to file? But won’t this undermine the whole fix?
Of course :facepalm: (see failed build). I had only ran the tests locally and hadn't even thought of that.
--
Putting it all together, I will:
- Inherit from
StringIOinstead of delegating. Much simpler implementation. - Hardcode
tty?totrue- I'll give a try to make it only when explicitly asked to, as @JonRowe suggested. I currently don't know how to implement such an
.as_ttymethod but I'm sure I'll find good examples in the codebase
- I'll give a try to make it only when explicitly asked to, as @JonRowe suggested. I currently don't know how to implement such an
- ~While at it, I'll remove the
letmentioned by @pirj~ (see comment in the relevant thread)
Once again, thank you!
What I just pushed is without implementing the .as_tty method, it just hardcodes it to true. I'll give it a try to it.
I just pushed an attempt at implementing as_tty. It works but it's admittedly ugly. Feedback on suggested refactoring or a different way to do it welcome :smile: I guess the ugliest thing is the duplication between CaptureStdout and CaptureStderr (which was already there, but it's more evident now), but it's difficult to refactor them together without resorting to some metaprogramming tricks, because they need to reassign the global variables.
Great! Thanks!