Skip to content

Spec: show diff between expected and actual values when should eq fails#10006

Open
makenowjust wants to merge 7 commits intocrystal-lang:masterfrom
makenowjust:fix-9948
Open

Spec: show diff between expected and actual values when should eq fails#10006
makenowjust wants to merge 7 commits intocrystal-lang:masterfrom
makenowjust:fix-9948

Conversation

@makenowjust
Copy link
Contributor

Fixed #9948

Screenshot:

スクリーンショット 2020-12-01 20 01 43

This PR adds only diff output to the AssertionFailed message.

We can consider the following enhancements:

  • Make #pretty_inspect output stable on Hash keys and Set values for diff.
    (by adding stable option to #pretty_inspect?)
  • Highlight diff output for human readability.
  • Add word diff by using wdiff command.

Thank you.


begin
# Invoke `diff` command and fix up its output.
result = `#{diff_command} -u #{expected_file.path} #{actual_file.path}`
Copy link
Member

Choose a reason for hiding this comment

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

Please use Process.run/run? for better error and stream handling, path escapes etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed.

@straight-shoota
Copy link
Member

This looks great! I'm not sure how it behaves with long values and huge differences. Maybe the diff should be hidden when it's too long? I don't think it's particularly useful when it spans over a page or so.
I'd also be very interested in shortening the diff output. For example, are ---expected/---actual labels really necessary? The line numbers probably only make sense when there's exactly one item per line (like in a multiline string). But when you have an array of complex data structures where each can print more than one line, you the line number of the diff provides only limited information.

@oprypin It would be really helpful if you could share your concerns instead of just giving a thumbs down.

@makenowjust
Copy link
Contributor Author

--- expected and +++ actual lines are removed. In fact, AVA has no such lines.

@oprypin
Copy link
Member

oprypin commented Dec 5, 2020

I just think that this is a maintenance liability. Running specs will now require a diff executable and with particular expectations towards how it's implemented.
GitHub Actions just happens to have the executable, but that won't always be so -- on Windows almost never.

diff_command = Spec.diff_command
return unless diff_command && (expected.includes?('\n') || actual.includes?('\n'))

expected_file = File.tempfile("expected") { |f| f.puts expected }
Copy link
Member

Choose a reason for hiding this comment

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

If this will be merged, there should be some investigation towards limiting Crystal's tmp file footprint to a particular subdirectory. Maybe there is already a spec-global top-level directory being used somewhere? I know in Crystal's own specs there is but that's not it.

@straight-shoota
Copy link
Member

@oprypin This does not require diff. It serves as an optional enhancement.

@oprypin
Copy link
Member

oprypin commented Dec 5, 2020

The spec for this requires diff.

@makenowjust
Copy link
Contributor Author

makenowjust commented Dec 5, 2020

Added git diff to candidates of diff command.

I think we can drop the support of other diff candidates because it is rare that a Crystal developer's PC lacks git command.

Hmm, container env may lack git...?

@jhass
Copy link
Member

jhass commented Dec 5, 2020

We can use pending! from #9566 to mark the specs as pending when there's no diff tool available.

@makenowjust
Copy link
Contributor Author

What should I do?

@asterite
Copy link
Member

I think it would be nice to have a diff library in the standard library. It could start by just being text-oriented, and eventually we could make it more complex. But I think relying on the system diff might be a bit too much.

@makenowjust
Copy link
Contributor Author

diff library in stdlib sounds not so bad. We have levenstein in stdlib for a specific purpose (Did you mean?). I think we can go in the same way.

@oprypin
Copy link
Member

oprypin commented Feb 11, 2021

@asterite That's the opposite of what you said here #9948 (comment)
which itself was the opposite of what the discussion was tending towards at that time.

@makenowjust
Copy link
Contributor Author

@asterite

It could start by just being text-oriented, and eventually we could make it more complex.

I guess and believe the latter part is impossible... (#9948 (comment))
However, the first part can implement in less than 200 LOC, so I said "not so bad".

Repeatedly, my opinion is the best idea is to use system diff because it works in 99% of environments.

@asterite
Copy link
Member

@oprypin Yes, I changed my mind :-)

In any case, if others think that relying on diff is fine, then merge this.

makenowjust added a commit to makenowjust/crystal that referenced this pull request Feb 11, 2021
Fixed crystal-lang#9948
Close crystal-lang#10006

This commit adds `Diff` module.
It has three public methods:

  - `Diff.diff` computes a diff between two collections.
  - `Diff.unified_diff` computes a diff between two strings on lines,
    and returns the patch string as unified diff format.
  - `Diff.show_unified_diff` shows the given patch and original data
    as unified diff format.

This implementation is less than 150 LOC 🎉

And, this commit also updates `should eq` failure message by using diff.
@dsisnero
Copy link

dsisnero commented Mar 5, 2026

I have a diffing library that can be used. https://github.com/dsisnero/similar.cr/tree/main, ir is a port of a rust library

@ysbaddaden
Copy link
Collaborator

Nice!

I recall I added one to minitest.cr assertions, adapted from crystal-diff by @makenowjust. It's incredibly tiny.

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.

[RFC] Add diff algorithm to spec output

8 participants