Skip to content

Add opt-in binary string diff support#301

Open
gschlager wants to merge 1 commit intosplitwise:mainfrom
gschlager:binary-diff
Open

Add opt-in binary string diff support#301
gschlager wants to merge 1 commit intosplitwise:mainfrom
gschlager:binary-diff

Conversation

@gschlager
Copy link
Copy Markdown
Contributor

Depends on #300

Add a SuperDiff::BinaryString extension that renders ASCII-8BIT strings as hex dumps. Users opt-in by requiring the module:

require 'super_diff/binary_string'

Includes a dedicated differ, inspection tree builder, operation tree builder/tree/flattener, and updates SuperDiff::RSpec::Differ to allow diffs for binary strings (skipping the single-line string short-
circuit).

Hex dumps use 16 bytes per line with offset, hex pairs, and ASCII representation (xxd-style output).

Add a `SuperDiff::BinaryString` extension that renders `ASCII-8BIT` strings as hex dumps. Users opt-in by requiring the module:

```
require 'super_diff/binary_string'
```

Includes a dedicated differ, inspection tree builder, operation tree builder/tree/flattener, and updates SuperDiff::RSpec::Differ to allow diffs for binary strings (skipping the single-line string short-
circuit).

Hex dumps use 16 bytes per line with offset, hex pairs, and ASCII representation (xxd-style output).
@gschlager
Copy link
Copy Markdown
Contributor Author

@jas14 I wanted to follow up on my PR to see if there’s anything I should address or if you haven’t had the chance to review it yet. No worries either way.

@jas14
Copy link
Copy Markdown
Collaborator

jas14 commented Mar 13, 2026

Hi @gschlager , I haven't forgotten about this, but haven't had the chance to review yet. Apologies for the delay!

@jas14 jas14 self-requested a review March 26, 2026 16:02
Copy link
Copy Markdown
Collaborator

@jas14 jas14 left a comment

Choose a reason for hiding this comment

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

Hey @gschlager , thank you for your contribution and for your patience! I finally had some time to give this PR the time it deserves.

In general, looks excellent. Back to you with a question about the OperationTree prepend and a request to remove the OperationTreeBuilder prepend.

P.S. don't forget to add an entry to the CHANGELOG! 😉

Comment on lines +29 to +31
def should_compare?(_operation, _next_operation)
false
end
Copy link
Copy Markdown
Collaborator

@jas14 jas14 Mar 26, 2026

Choose a reason for hiding this comment

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

Interesting. I ended up going down a rabbit hole here.

In practice, there are currently no plain string operation tree builders / differs, so this should never happen.

However, in theory, this is correct: if there were any operation tree builders that applied to two non-binary-encoded1 strings, the AbstractOperationTreeBuilder would produce a binary operation, and the flattener might blow up.

I'd normally ask for a test for this case, but since the same is true for the MultilineString operation tree builder, I'll separately look into 1) adding a failing test for the MultilineString superclass, and 2) probably hoisting this override up into that class, too.

EDIT: #304 is now in main, feel free to merge and get rid of this method.

Footnotes

  1. these are the formatted hex dump lines, not the original strings!

Comment on lines +20 to +22
config.prepend_extra_operation_tree_classes(
OperationTrees::BinaryString
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What effect will this have on the ObjectHavingAttributes matcher?

Can you add a test for this, or drop it if it has an adverse effect?

Comment on lines +23 to +25
config.prepend_extra_inspection_tree_builder_classes(
InspectionTreeBuilders::BinaryString
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

By the way, in contrast to the above, let's keep the binary string inspection tree builder – it makes the output a lot cleaner for this extension's use case:

puts SuperDiff.diff({x: Random.bytes(32)}, {x: Random.bytes(32)})
  {
-   x: "~\xC0\x88v\xFF9^[\x82p\x89%\xEE0\xEF\xDB\xC3`\xD8\xDF\xA0\xE0\xC6\x8F2\xEC\xC6\xBF\xA6\x8D\v\xC2"
+   x: "\x0F\x8A\e\xAE.J*\xE5X2l5\x92\x97\x03\xA2\xFF\xE0\xA8xQ\x8A\x97)\x88>}y@\xDDy\xD0"
  }

require 'super_diff/binary_string'

puts SuperDiff.diff({x: Random.bytes(32)}, {x: Random.bytes(32)})
  {
-   x: <binary string (32 bytes)>
+   x: <binary string (32 bytes)>
  }

Comment on lines +17 to +19
config.prepend_extra_operation_tree_builder_classes(
OperationTreeBuilders::BinaryString
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This call makes the operation tree builder available to the entire diffing system, which makes for some funky output when diffing binary strings in collections:

puts SuperDiff.diff({x: Random.bytes(32)}, {x: Random.bytes(32)})
  {
-   x: 00000000: afef a454 4376 72a6 729d 314d e1db 5bef  ...TCvr.r.1M..[.
-   00000010: 07a3 ab95 8424 7b62 fd2d 6d0a ed59 1dd8  .....${b.-m..Y..
+   00000000: 34e3 98e1 b17a 2855 4745 7583 584c 9123  4....z(UGEu.XL.#
+   00000010: 9d5a cacc 84a1 8275 1b42 3fa5 a81b d91f  .Z.....u.B?.....
  }

puts SuperDiff.diff([Random.bytes(32)], [Random.bytes(32)])
  [
-   00000000: bddb e405 5d1d c757 2922 162f 0c26 4c68  ....]..W)"./.&Lh
-   00000010: 1289 d606 03e0 69e1 a655 4b36 4054 ee7f  ......i..UK6@T..
+   00000000: df19 aa09 3357 65ed 6d8d 751a 9fda a81a  ....3We.m.u.....
+   00000010: b3cc 4dea 7708 e728 5907 5647 4c5c f81b  ..M.w..(Y.VGL\..
  ]

So, let's remove this. The Differs::BinaryString will still be available to diff top-level binary strings, and it will still use the BinaryString op tree builder.

Suggested change
config.prepend_extra_operation_tree_builder_classes(
OperationTreeBuilders::BinaryString
)

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.

2 participants