Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

output matcher: add as_tty method to make the simulated stream behave as a TTY #1459

Merged
merged 7 commits into from
May 30, 2024

Conversation

porras
Copy link
Contributor

@porras porras commented May 16, 2024

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 delegate and define the class on runtime only once output has been called. This is already done for the tempfile require, although it would be a bit trickier than that because the class needs to be defined to.
  • Not use SimpleDelegator and implement the behavior using method_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 😅

lib/rspec/matchers/built_in/output.rb Outdated Show resolved Hide resolved
spec/rspec/expectations_spec.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/output.rb Outdated Show resolved Hide resolved
@JonRowe
Copy link
Member

JonRowe commented May 16, 2024

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

@porras
Copy link
Contributor Author

porras commented May 16, 2024

Thanks for such a fast feedback 😄

@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 🤦 (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 StringIO instead of delegating. Much simpler implementation.
  • Hardcode tty? to true
    • 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_tty method but I'm sure I'll find good examples in the codebase
  • While at it, I'll remove the let mentioned by @pirj (see comment in the relevant thread)

Once again, thank you!

@porras
Copy link
Contributor Author

porras commented May 16, 2024

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.

@porras
Copy link
Contributor Author

porras commented May 16, 2024

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 😄 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.

@porras porras changed the title output matcher: keep "TTYness" of original stream output matcher: add as_tty method to make the simulated stream behave as a TTY May 16, 2024
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

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

Nice! Thank you.

Can you add a couple of examples covering those parts where it raises when as_tty is not compatible with “in any process”?

‘output’ calls can be chained, too? output('foo').to_stdout.and output('bar').to_stdout.as_tty. Will it work as expected? Should as_tty be specified for both?
I am sorry, Iam from my phone, does it even chain properly, or just my wishful thinking?

* Error out when using it with *_from_any_process
* Chain matchers
@pirj pirj requested a review from JonRowe May 16, 2024 15:46
Copy link
Member

@JonRowe JonRowe left a comment

Choose a reason for hiding this comment

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

Nice, a few suggestions

lib/rspec/matchers/built_in/output.rb Outdated Show resolved Hide resolved
lib/rspec/matchers/built_in/output.rb Outdated Show resolved Hide resolved
spec/rspec/matchers/built_in/output_spec.rb Outdated Show resolved Hide resolved
porras and others added 4 commits May 24, 2024 15:08
Co-authored-by: Jon Rowe <mail@jonrowe.co.uk>
Co-authored-by: Jon Rowe <mail@jonrowe.co.uk>
I had broken them by committing from the github UI 🙄
@porras porras requested a review from JonRowe May 28, 2024 10:31
@JonRowe JonRowe merged commit b38cf53 into rspec:main May 30, 2024
29 of 30 checks passed
@JonRowe
Copy link
Member

JonRowe commented May 30, 2024

Great! Thanks!

JonRowe added a commit that referenced this pull request May 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants