-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Improve error message when passing a proc to assert_difference
or assert_changes
#52036
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
Conversation
I wonder if it's that valuable to print the expression inside the proc. Maybe the error message could be just |
ast = RubyVM::AbstractSyntaxTree.of(callable, keep_script_lines: true) | ||
"-> #{ast.source.strip}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use Prism here? I wonder if that would help with other Ruby implementations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm I think Prism does not have an API to get the source of a method. The only thing I can think of is parsing the file with Prism and trying to find the location of the assertion (probably by using the line number from caller
?).
Maybe there is another way I do not know of, but right know I feel like it would be way too complicated to achieve this using Prism.
I think that might be valuable as well. By not printing My goal here was to have the same information that I would get when using a code string instead of a proc. In this case we get the whole code string printed, so I thought why shouldn't we do this for procs as well. |
I like this change, my only worry is that |
activesupport/test/test_case_test.rb
Outdated
end | ||
end | ||
|
||
assert_equal "\"-> { @object.num }\" didn't change. It was already 0.\nExpected 0 to not be equal to 0.", error.message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd prefer something like:
assert_equal "\"-> { @object.num }\" didn't change. It was already 0.\nExpected 0 to not be equal to 0.", error.message | |
assert_equal "`@object.num` didn't change. It was already 0.\nExpected 0 to not be equal to 0.", error.message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I like that better as well, it's just a bit harder to implement since we do not get location information about the actual content of the proc. I tried implementing it in the second commit (I'll squash those before merging) using a regex, but I'm not sure how robust this solution is. Let me know what you think!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a pass on it. Using regexp to parse Ruby code is tricky, so I instead went to a route were we only include the source if we match the happy path (single line, defined with {}
, no arguments).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea being that the overwhelming majority of procs used for assert_changes
match these criteria, and those which don't would be a pain to render properly, so we just fallback to the old behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds like a good compromise to me. Thanks for merging!
36927a3
to
08874a7
Compare
Yeah it's definitely a risk to evaluate. From my personal point of view I think the improvement it brings is worth it. |
2fba0f7
to
bd97b56
Compare
Previously if `assert_difference` called with a proc fails, the inspect output of the proc object was shown. This is not helpful to identify what went wrong. With this commit we leverage the experimental `RubyVM::AbstractSyntaxTree` api of MRI to print the source code of the proc that was passed to `assert_difference`. On all other platforms the behavior stays the same. The same applies to `assert_changes`.
bd97b56
to
38e9695
Compare
rescue SystemCallError | ||
# Failed to get the source somehow | ||
return callable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also had to harden it a bit, as evidenced by railties test, it can happen that the source is not accessible.
…erence-output" This reverts commit e3ea4c7, reversing changes made to fdbe363. With prism as the default parser in Ruby 3.4, the method of getting a procs source does not work anymore. Also see https://bugs.ruby-lang.org/issues/20761
…erence-output" This reverts commit e3ea4c7, reversing changes made to fdbe363. With prism as the default parser in Ruby 3.4, the method of getting a procs source does not work anymore. Also see https://bugs.ruby-lang.org/issues/20761
Motivation / Background
In our team we often discuss if it's better to pass a string or a proc to
assert_difference
. In my opinion it's better to pass a proc because linters and IDEs can analyze the ruby code in the proc. However one disadvantage is that the error message is less clear when using a proc, because the message just prints the inspect-output of theProc
object.To fix this, I propose this PR which prints the source of the proc instead.
Detail
This Pull Request changes the error message of
assert_difference
,assert_no_difference
,assert_changes
andassert_no_changes
when using MRI and when passing a proc to those assertions. It leverages the experimentalRubyVM::AbstractSyntaxTree
api to print the source code of the proc which makes it easier to spot why the test failed.For example consider the following test:
Previous output:
Output with this change:
Note that this only works in MRI because other ruby implementations do not implement the
RubyVM::AbstractSyntaxTree
api.Additional information
I'm not sure if Rails wants to "depend" on an experimental api like
RubyVM::AbstractSyntaxTree
because it might not be stable and change in the future. I think this change brings value to Rails but I understand if there is a policy about not using experimental ruby apis. In this case we can close this PR.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]