-
Notifications
You must be signed in to change notification settings - Fork 18
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
Reporter: handle objects with cycles #130
Conversation
lib/reporters/TapReporter.js
Outdated
* it will be represented as "[Circular]". | ||
*/ | ||
function createCycleSafeReplacer() { | ||
const seen = new Set(); |
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.
const seen = new Set(); | |
const seen = new WeakSet(); |
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 made this change but then later didn't need it since I switched to using a shallow clone and the cloning is stack-based.
lib/reporters/TapReporter.js
Outdated
|
||
if (typeof value === 'object' && value !== null) { | ||
if (seen.has(value)) { | ||
return '[Circular]'; |
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.
node’s util.inspect tracks specific refs here, so you know what it’s circular of - it might be worth doing that here.
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.
This is indeed a neat feature of inspect
. One question is whether this would be confusing seeing it in the actual/expected output. To label the refs we need two parts:
- To label the object being referenced as a
<ref *n>
- To use the correct index when labeling cycles
[Circular *n]
Labeling the cycles should be pretty easy, but I'm a little worried about changing the structure of the stringified object too much.
There are some other enhancements to this stringification we need to consider, for example, BigInt
also throws when being fed to JSON.stringify
. I'd rather follow up on enhancements separately and landing this fix first.
Thoughts?
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.
Right - normal JSON.stringify isn't really an option, I think, at least not without a stringifier function also provided.
I need to crunch on this a little bit more. The existing approach can have false positives as long as the same object appears more than once (even when it's not cyclical). I also have to fix the linting errors. |
fc2860a
to
3258391
Compare
Circling back on this: I think there are many improvements we can make to the serialization outside of handling ref cycles. It would be worth having an issue dedicated to those improvements so that we can figure out what "ideal" serialization looks like. In the short term, it would help me (and others) to land this fix. I've squashed/rebased my changes, so it should be in a reviewable state. @Krinkle if you have any time would you mind taking a look? |
lib/reporters/TapReporter.js
Outdated
clone = object.map(function (element) { | ||
return decycledShallowClone(element, ancestors); | ||
}); | ||
ancestors.pop(object); |
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.
Array.pop()
does not take an argument.
lib/reporters/TapReporter.js
Outdated
* been replaced with "[Circular]". | ||
*/ | ||
function decycledShallowClone (object, ancestors = []) { | ||
if (ancestors.includes(object)) { |
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.
Use Array#indexOf() !== -1
here for browser compat as I'd rather not ship a polyfill with this library, and given its low-level use in test runners it's not feasible for end-users to ship a polyfil either as they generally can't or wouldn't be expected to load something before this.
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.
Fixed. Should I also avoid Array#map
?
lib/reporters/TapReporter.js
Outdated
Object.keys(object).forEach(function (key) { | ||
clone[key] = decycledShallowClone(object[key], ancestors); | ||
}); | ||
ancestors.pop(object); |
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.
(idem)
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.
TIL what idem means.
test/fixtures/unit.js
Outdated
* than once in an acyclical way. | ||
*/ | ||
function createDuplicateAcyclical () { | ||
const duplicate = {}; |
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.
To improve code coverage a little bit, and to weed out errors that result in objects rendering as empty without keys, perhaps include at least one key-value pair in here.
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.
Good point. Added keys to each of the objects so we can identify them in the output.
*/ | ||
function createCyclical () { | ||
const cyclical = {}; | ||
cyclical.cycle = cyclical; |
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.
In order to excercise the stack a bit bit, perhaps add one layer of indirection here, e.g. cyclical = { sub: {} }; cyclical.sub.cycle = cyclical;
.
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.
Added an additional structure with a subobject cycle.
3258391
to
1a009dd
Compare
Updating `TapReporter` with the ability to handle objects with circular references. This is needed for proper stringification of actual and expected values that contain cycles. Fixes qunitjs#104
1a009dd
to
8307cc4
Compare
Updating
TapReporter
with the ability to handle objects with circularreferences. This is needed for proper stringification of actual and
expected values that contain cycles.
Fixes #104