-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Alternative for deepEqual, formatting, diffs and snapshots #1341
Conversation
bf57aa9
to
1d72023
Compare
I've added serialization support to Perhaps controversially, Kathryn serializes to a binary format. Consequently so would AVA with this implementation. I'm writing a second To keep things simple, snapshots are only updated when There are some to-do items left:
@vadimdemedes @sindresorhus what do you think? |
This looks very promising!
Can you elaborate on why this is needed? I can see the benefit of having two files, as we can make the readable one even more readable. I'm just curious why binary. |
Just some nitpick:
Since we control the output now. I don't like the trailing commas. And I think we should drop the type for I think the readable snapshot should be the main one, so |
Can you document the binary format? Why not use something like Protocol Buffers? My biggest concerns with a binary format is debuggability and it not being diffable in git, so each snapshot update will take the whole size of the snapshot. This can have big impact of projects with lots of large snapshots. |
lib/snapshot.js
Outdated
// Increment if encoding layout or Kathryn serialization versions change. Previous AVA versions will not be able to | ||
// decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself. | ||
// The version is encoded as an unsigned 8 bit integer. If it ever reaches 255 it *must* be encoded as a 16 bit integer | ||
// instead. |
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.
So let's go with 16 bit from the start then? Then we don't have to document such limitation and never have to worry about it.
lib/snapshot.js
Outdated
|
||
mkdirp.sync(this.dir); | ||
fs.writeFileSync(path.join(this.dir, this.name + '.snap'), buffer); | ||
fs.writeFileSync(path.join(this.dir, this.name + '.readable.snap'), readableBuffer); |
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.
Nitpick: Use template literals
It needs to serialize to some intermediate format. It can't really be JSON, because of dates, buffers, and certain number serializations.
Yes, eventually. I'm still proving out the feasibility of it all.
I can look into that. There isn't a lot to the current encoding though (at least in AVA).
At least the snapshot files correspond to test files. I don't know how outrageously large they would get in practice. I don't think there is much value in the serialization being readable. Even if it's JSON it wouldn't be pretty formatted, and a single line diff is just as useless as a binary diff. If we do pretty format it would tempt people to make changes, and that's likely to break the snapshot. With the binary format we discourage all that and we get to use compression. I think that strikes the right balance.
Maybe, yea. The readable snapshot isn't actually used though, it's just there so you can verify changes.
For the last property you mean? It'd be a bit more work to track which item / property / map entry is the last one, flag it, and then prevent the comma. Though it would be possible. Thing is, this output isn't necessarily JavaScript. It just looks a lot like it. I like the consistency of always ending a property.
Sure. concordancejs/concordance#15 I'm hoping to land theme support today, and I'm also doing a pass through this PR to revisit the assert integration and snapshot implementation. |
With Protocol Buffers we don't really have to care about the binary part. We just define the schema and automatically get a encoder/decoder. Instead of how we have lots of custom code to encode/decode now.
True Objects and Arrays are the most common output, and simplifying that simplifies the 95%. I think it's very much worth it.
Good point. I'm warming up to the idea.
Yes. That's how JS is usually presented, like with |
I've forced-pushed some updates:
Lastly there is a commit that switches to protocol buffers. It uses https://www.npmjs.com/package/protobufjs/. I'm vendoring a minimal implementation to avoid users having to install the full package, which seems to come to 13MB! |
1d72023
to
1695c44
Compare
lib/snapshot-index.proto
Outdated
@@ -0,0 +1,9 @@ | |||
syntax="proto3"; | |||
|
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 snapshot version should be defined in the schema too.
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.
Why?
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 was thinking so we could warn about different versions, but seems like the best practise is not to version them, so I guess having it in the Snapshot field makes more sense: http://stackoverflow.com/questions/8519381/how-does-protocol-buffer-handle-versioning
How do you like it? Do you think it's worth using or do you prefer the custom binary handling? If we go for this, I think we should just publish |
I'm not sure. On the one hand it's nice to not have to write the binary logic, on the other hand it's not that complicated. But it's quite likely that's just me 😉 There's overhead in managing the tooling too, and I don't know whether having the tooling makes it easier for others who end up having to deal with this code. I'm not convinced it'll help with What do you think, given the diff?
Yea. And then we can use Greenkeeper for updates too 😉 |
I wanted to come back to this. I'm not sure about the pruning behavior, either with the current AVA or with Jest itself. The more useful thing we have currently is that new snapshots are saved the first time they're asserted. This is nice with watch mode since you can just keep typing. On the other hand it seems strange that AVA would actually write files without being told to do so. The resulting snapshots aren't deterministic either. Files churn when run with I'm leaning towards only updating when |
I've pushed color support. Update dependencies and use |
I'm gonna say it's up to you. I'm slightly in favor of Protocol Buffers, but it does add some overhead in tooling and I'm not seeing as much use for it as I had hoped, and you're right that the manual binary handling is not that advanced. For example, I would have thought that Protocol Buffers would handle the whole binary thing, so I'm curious why you're adding a header and version manually: Lines 91 to 92 in 1695c44
|
It is being told so though, kinda. The user is explicitly writing a |
I tried latest now with my existing snapshot and got:
Ah never mind, probably because the protocol buffer change. Sidenote: I also think the error output could be better here. We're saying
|
I think |
You can't really see the snapshot in that file, so I would rather say:
Or something similar. |
The header is so that people can see what generated the file. The version so that eventually, older AVA versions can detect a newer snapshot and not even try to decode it. Currently it's compressed and then within that there's the encoded index. If we change the compression then older AVA versions would just crash. If we change how the version is encoded inside the decompressed binary blob (easy to accidentally do with protobufs) then again older AVA versions would crash. Hence leaving it outside, which makes it easier to guarantee we never (accidentally) change it.
Also, a big use case for protobufs is when you need to share data between different programs. You can write a definition once and then generate parsers / generators in different languages. Here it's just AVA reading its own output.
Yes because I'm changing formats without regard for versioning in this work-in-progress PR.
Oh good observation! We should be able to remove the
That's a good interpretation.
|
I like the idea of using Markdown for the readable version. This is how I would design the report:
Ignore the |
@sindresorhus yea I can update to that. I'm using the indented code blocks though since there is no way for the formatted value to accidentally escape it. |
This ensures the snapshot values are shown with `+` gutters, and the actual values with `-` gutters, despite the snapshot value being passed to Concordance as the actual, left-hand-side value.
The watcher needs to wait more than 10ms before snapshot related file changes are detected. Start with a 100ms delay, but progressively decrease the delay (50ms, 25ms, 13ms, 10ms) to avoid delaying for too long.
b598b1e
to
75b96d9
Compare
2ddf660
to
ef209b3
Compare
Workers can emit which files were touched during the test run. This is used to communicate to the watcher which snapshot file events to ignore from the current test run, stopping the watcher from running the same tests repeatedly. Note that this is only an issue when the `sources` glob has been customized by the user. The default glob excludes snapshot files. Files are ignored only once, so subsequent edits of snapshot files (e.g. by reverting a commit) will cause tests to be rerun.
This enables the watcher to rerun the correct test when a snapshot file is modified.
Tests inside a `__tests__` directory have their snapshots written to a `__snapshots__` directory. Tests inside a `test` or `tests` directory have their snapshots written to a `snapshots` directory. All other tests have their snapshots colocated.
Doesn't verify that the generated Markdown changes, but at least the code paths are exercised.
7b6a86f
to
1d9d915
Compare
🎉 |
Latest status: #1341 (comment)
This PR proposes we replace
lodash.isequal
,@ava/pretty-format
,diff
andjest-snapshot
with one single library.With
t.deepEqual()
, if actual and expected values are not equal then the diff we present should actually show a difference. This currently isn't always the case becauselodash.isequal
checks properties that may not be formatted by@ava/pretty-format
The diff resulting from
t.deepEqual()
is done usingdiff
, which compares formatted values. This diff may be too large, or it's unclear where the shown diff is located in a large tree structureAs discussed in Snapshot recap #1275
jest-snapshot
presents a diff between formatted values. This has the same issues as with our currentt.deepEqual()
implementation, as well as preventing us from showing colorsWhat counts as equality in snapshots is different from
t.deepEqual()
which could be surprisinglodash.isequal
has some quirks too:Object(1)
is equal to1
arguments
object can be compared to an actual object, where I'd expect to compare it to an arrayMap
andSet
objects are equal even if their entries are in a different order@ava/pretty-format
andjest-snapshot
have other issues, like not printing properties of most non-Object
objects.I've been working on
concordance
to tackle these issues in one library:testdouble
explanationsA lot more work needs to be done:
jest-snapshot
) Support snapshots concordancejs/concordance#9Regardless, I'm excited about the consistency and user experience this provides, and how it helps AVA be a more agnostic test runner.
If you're interested in contributing to this effort there are lots of opportunities to help out. Please come find me in our Gitter channel or consult the
concordance
issue list.