Skip to content

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

Merged
merged 1 commit into from
Jun 9, 2024

Conversation

richardboehme
Copy link
Contributor

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 the Proc 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 and assert_no_changes when using MRI and when passing a proc to those assertions. It leverages the experimental RubyVM::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:

test "see proc output" do
  assert_difference -> { 1 } do
  end
end

Previous output:

#<Proc:0x000074357846eae8 /home/richard/my-test-app/test/models/test.rb:21 (lambda)> didn't change by 1, but by 0.
Expected: 2
  Actual: 1

Output with this change:

"-> { 1 }" didn't change by 1, but by 0.
Expected: 2
  Actual: 1

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:

  • This Pull Request is related to one change. Unrelated changes should be opened in separate PRs.
  • Commit message has a detailed description of what changed and why. If this PR fixes a related issue include it in the commit message. Ex: [Fix #issue-number]
  • Tests are added or updated if you fix a bug or add a feature.
  • CHANGELOG files are updated for the changed libraries if there is a behavior change or additional feature. Minor bug fixes and documentation changes should not be included.

@MatheusRich
Copy link
Contributor

I wonder if it's that valuable to print the expression inside the proc. Maybe the error message could be just Expression didn't change by 1, but by 0. That would work for any Ruby implementation.

Comment on lines 285 to 286
ast = RubyVM::AbstractSyntaxTree.of(callable, keep_script_lines: true)
"-> #{ast.source.strip}"
Copy link
Contributor

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

Copy link
Contributor Author

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.

@richardboehme
Copy link
Contributor Author

I wonder if it's that valuable to print the expression inside the proc. Maybe the error message could be just Expression didn't change by 1, but by 0. That would work for any Ruby implementation.

I think that might be valuable as well. By not printing Proc#inspect it might be easier to understand for folks that are new to the framework or language. However, for experienced developers this would mean we print even less information than before.

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.

@byroot
Copy link
Member

byroot commented Jun 8, 2024

I like this change, my only worry is that RubyVM::AbstractSyntaxTree is not stable, so we might get bit hard if it changes in the future. But perhaps this ship has sailed because we already use it in Action View

end
end

assert_equal "\"-> { @object.num }\" didn't change. It was already 0.\nExpected 0 to not be equal to 0.", error.message
Copy link
Member

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:

Suggested change
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

Copy link
Contributor Author

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!

Copy link
Member

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

Copy link
Member

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.

Copy link
Contributor Author

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!

@richardboehme richardboehme force-pushed the assert-difference-output branch from 36927a3 to 08874a7 Compare June 8, 2024 20:37
@richardboehme
Copy link
Contributor Author

I like this change, my only worry is that RubyVM::AbstractSyntaxTree is not stable, so we might get bit hard if it changes in the future. But perhaps this ship has sailed because we already use it in Action View

Yeah it's definitely a risk to evaluate. From my personal point of view I think the improvement it brings is worth it.

@byroot byroot force-pushed the assert-difference-output branch 3 times, most recently from 2fba0f7 to bd97b56 Compare June 9, 2024 08:36
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`.
@byroot byroot force-pushed the assert-difference-output branch from bd97b56 to 38e9695 Compare June 9, 2024 08:46
Comment on lines +287 to +289
rescue SystemCallError
# Failed to get the source somehow
return callable
Copy link
Member

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.

@byroot byroot merged commit e3ea4c7 into rails:main Jun 9, 2024
2 of 3 checks passed
@richardboehme richardboehme deleted the assert-difference-output branch June 9, 2024 09:35
Earlopain added a commit to Earlopain/rails that referenced this pull request Sep 26, 2024
…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
Earlopain added a commit to Earlopain/rails that referenced this pull request Sep 26, 2024
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants