Giter Club home page Giter Club logo

super_diff's Introduction

SuperDiff Gem Version Build Status Downloads IssueHunt

SuperDiff is a Ruby gem which is designed to display the differences between two objects of any type in a familiar and intelligent fashion.

๐Ÿ“ข See what's changed in recent versions.

Introduction

The primary motivation behind this gem is to vastly improve upon RSpec's built-in diffing capabilities. RSpec has many nice features, and one of them is that whenever you use a matcher such as eq, match, include, or have_attributes, you will get a diff of the two data structures you are trying to match against. This is great if all you want to do is compare multi-line strings. But if you want to compare other, more "real world" kinds of values such as API or database data, then you are out of luck. Since RSpec merely runs your expected and actual values through Ruby's PrettyPrinter library and then performs a diff of these strings, the output it produces leaves much to be desired.

For instance, let's say you wanted to compare these two hashes:

actual = {
  customer: {
    person: SuperDiff::Test::Person.new(name: "Marty McFly, Jr.", age: 17),
    shipping_address: {
      line_1: "456 Ponderosa Ct.",
      city: "Hill Valley",
      state: "CA",
      zip: "90382"
    }
  },
  items: [
    { name: "Fender Stratocaster", cost: 100_000, options: %w[red blue green] },
    { name: "Mattel Hoverboard" }
  ]
}

expected = {
  customer: {
    person: SuperDiff::Test::Person.new(name: "Marty McFly", age: 17),
    shipping_address: {
      line_1: "123 Main St.",
      city: "Hill Valley",
      state: "CA",
      zip: "90382"
    }
  },
  items: [
    { name: "Fender Stratocaster", cost: 100_000, options: %w[red blue green] },
    { name: "Chevy 4x4" }
  ]
}

If, somewhere in a test, you were to say:

expect(actual).to eq(expected)

You would get output that looks like this:

Before super_diff

What this library does is to provide a diff engine that knows how to figure out the differences between any two data structures and display them in a sensible way. So, using the example above, you'd get this instead:

After super_diff

Installation & Usage

๐Ÿ“˜ For more on how to install and use SuperDiff, read the user documentation.

Support

My goal for this library is to improve your development experience. If this is not the case, and you encounter a bug or have a suggestion, feel free to create an issue. I'll try to respond to it as soon as I can!

Contributing

Any code contributions to improve this library are welcome! Please see the contributing document for more on how to do that.

Sponsoring

If there's a change you want implemented, you can choose to sponsor that change! super_diff is set up on IssueHunt, so feel free to search for an existing issue (or make your own) and add a bounty. I'll get notified right away!

Compatibility

super_diff is tested to work with Ruby >= 3.x, RSpec 3.x, and Rails >= 6.x.

Inspiration/Thanks

In developing this gem I made use of or was heavily inspired by these libraries:

Thank you to the authors of these libraries!

Author/License

SuperDiff was created and is maintained by Elliot Winkler. It is released under the MIT license.

super_diff's People

Contributors

aried3r avatar artofhuman avatar benk-gc avatar brunsa2 avatar dependabot[bot] avatar fizvlad avatar flash-gordon avatar groyoh avatar guiferrpereira avatar hlascelles avatar jas14 avatar jcoyne avatar knu avatar mange avatar mcmire avatar myronmarston avatar numbata avatar petergoldstein avatar ransombriggs avatar sidane avatar tradiff avatar wata727 avatar y-yagi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

super_diff's Issues

Extract CSI

No one needs another library to colorize text, but keeping it here bloats the codebase.

Unfortunately csi is already taken as a gem name. Something else?

Improve build time for JRuby

Right now the JRuby tests take 9 times as long to run as the MRI tests. This is probably because for each integration test, we spawn a sub-ruby, which is a pretty big performance hit. In fact, JRuby recommends not doing this. There are ways to get around this, but perhaps the simplest way is to stuff all of the integration tests into a single file? This means that only one sub-ruby would be spawned. The results of each test would then be collected and compared to expected outputs. This would likely create some kind of Frankenstein in terms of the integration tests, so maybe this isn't worth it for now?

Add custom differs

One example is the String differ that some dude wrote and posted to the RSpec issue tracker.

Another example is an OrderedHash differ that works very similar to the Hash differ.

In other words, the current differs need to be extracted into classes somehow.

Add Cucumber support

Importing super_diff/rspec technically works in Cucumber but diffs aren't showing for some reason.

Add pipelines instead of factory functions?

Currently, OperationSequence is the only class for which we do not have a factory function. In theory we shouldn't need a factory function for DiffFormatter, as operational sequences have a to_diff method and we should be able to use that. Can we extend this pattern to find the correct OperationSequence for an OperationalSequencer? And find the right OperationalSequencer for a Differ? That may obviate the need to repeat the applies_to? logic for each stratum of class in the hierarchy, as we have to do that right now (and it's a bit inconsistent).

List hash elements missing from hash A in order, not at end

When comparing the following

A: {:fiz => "gram", 1 => {2 => :sym}}
B: {42 => {:raz => "matazz"}, :fiz => "graeme", 1 => 3}

Output should be:

- *[42]: Expected to not be present, but found (value: {:raz => "matazz"})
- *[:fiz]...

Currently keys not found in A are listed at the very end.

Use rubyfmt (when it is functional again)

Rubocop is getting super annoying. I end up having to tweak the rules a lot and they're just not working for me. I really like the idea of using an autoformatter. The best candidate seems like rubyfmt. I'm not sure when it'll be available, but I can't wait.

Flip magenta and yellow? Change colors back to red/green?

When printing:

Expected: ...
       to eq: ...

The "actual" value is in red and the "expected" value is in green. Shouldn't it be the other way around? (i.e. the actual value should be assumed to be the right one).

Note that this means we'd have to flip the colors in the diff as well, and update the legend.

When comparing two key/value pairs, and one of the values is a hash, expand the hash into multiple lines

For instance, instead of producing this diff:

  {
    "data" => {
-     "getCurrentAccount" => {
-       "key" => "49b471a566d7762da44778ff63526401",
-       "firstName" => "Elliot",
-       "lastName" => "Winkler"
      }
    },
+   "errors" => [{ "type" => "unknown", "message" => "Something bad happened", "details" => nil }]
  }

It would produce this instead:

  {
    "data" => {
-     "getCurrentAccount" => {
-       "key" => "49b471a566d7762da44778ff63526401",
-       "firstName" => "Elliot",
-       "lastName" => "Winkler"
      }
    },
+   "errors" => [
+     {
+       "type" => "unknown",
+       "message" => "Something bad happened",
+       "details" => nil
+     }
+   ]
  }

Extract CommandRunner

The CommandRunner is copied from my work with shoulda-matchers, and I imagine using it other places. Keeping it here bloats the codebase.

Clean up some duplication

There are a few different kinds of classes that have some duplication in them. Making a new strategy is a bit painful because you end up repeating yourself and only a small part of the class changes. Especially with DiffFormatters and using Collection.

Implement fallback in case of failure?

If there is a bug in the gem such that diffing, or even our custom overrides for matchers, don't work correctly, should we fall back to RSpec so as not to disrupt the developer?

Difference of arrays should be smarter

Currently if you have two arrays:

a = %w(a b c d)
b = %w(1 2 a b)

and you diff them, the differ will tell you this, basically:

0: "a" != "1"
1: "b" != "2"
2: "c" != "a"
3: "d" != "b"

Now, ideally, this is not very helpful. We should be able to figure out that "1" and "2" were inserted before "a" and that "c" and "d" were removed. So maybe the output would be exactly that:

- "1", "2" inserted before "a"
- "b", "c" deleted after "b"

Notice that we aren't listing the array indices anymore, since those are no longer meaningful.

This actually sounds like a proper use case for the LCS algorithm.

Add collapsed output option

What I mean by collapsed output is this:

Error: ...

Expected: ...
Got: ...

Breakdown:
- *["foo"][1]["bar"][:baz]: Differing strings.
  - Expected: ...
  - Got: ...

instead of this:

Error: ...

Expected: ...
Got: ...

Breakdown:
- *["foo"]: ...
  - *[1]: ...
    - *["bar"]: ...
      - *[:baz]: Differing strings.
        - Expected: ...
        - Got: ...

In other words, it would collapse everything until it gets to a node beyond which we can no longer recurse (a simple type, in other words).

Ensure that you can diff two completely different custom- and non-custom objects

Right now we have tests for comparing objects, but it uses the same class for both objects. We could have a case, however, where we're trying to compare totally different objects. We should make sure that the correct output is produced. (I don't think we should show a diff if you're comparing two objects, unless those objects are inside of another data structure.)

Consolidate logic between deep inspection and diffing

Currently we use a different algorithm to inspect two complex objects on a single line and then generate a diff of those objects. Here is a good example of this:

Differing objects.

Expected: #<SuperDiff::Test::Player:0x7fcdb0a77440 @handle="martymcfly" @character="mirage" @inventory=["flatline", "purple body shield"] @shields=0.6 @health=0.3 @ultimate=0.8>
  Actual: #<SuperDiff::Test::Player:0x7fcdb0a77300 @handle="docbrown" @character="lifeline" @inventory=["wingman", "mastiff"] @shields=0.6 @health=0.3 @ultimate=0.8>

Diff:

  #<SuperDiff::Test::Player {
-   handle: "martymcfly",
+   handle: "docbrown",
-   character: "mirage",
+   character: "lifeline",
    inventory: [
-     "flatline",
+     "wingman",
-     "purple body shield"
+     "mastiff"
    ],
    shields: 0.6,
    health: 0.3,
    ultimate: 0.8
  }>

In this case, "Expected" vs "Actual" uses deep inspection to generate an inspection of the two objects, whereas the diff uses an entirely different algorithm. Thus, you get SuperDiff::Test::Player:0x7fcdb0a77440 vs SuperDiff::Test::Player. Maybe there's a way we can make use of the pretty printing algorithm to generate a diff?

Add support for comparing POROs?

For instance, if a class merely accepts a bunch of values or keyword values and sets instance variables, and we have two instances of the same class, it might be nice if the gem automatically compared them using instance variables instead of having to write a custom differ for that class.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.