Skip to content
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

have Base generate the strings for error messages immediately #3075

Merged
merged 3 commits into from
Dec 7, 2017

Conversation

abrady0
Copy link
Contributor

@abrady0 abrady0 commented Oct 20, 2017

instead of delaying until epilogue

Requirements

Description of the Change

An object referred to by a test failure can be mutated between the point of test failing and when the object is converted to a string and output. For instance if an afterEach clears the fields in an object pointed to by a chai.expect.eql AssertionError's expected or actual fields then the reporter's error message will be misleading

see https://codepen.io/abrady0/pen/pWGBxR?editors=1111 for running example.

Here is a snippet that assumes 'spec' reporter:

describe('example', function() {
  var foo = {
    a: 'before'
  };

  afterEach(function() {
    console.log('afterEach');
    foo.a = 'after';
  })
  it('should print the error properly', function() {
    var bar = { a: 'during' };
    console.log('foo2')
    // HERE: output *should* say 'before' instead of 'after' for the previous line
    //  + expected - actual
    //   {
    //  -  'a': 'after'
    //  +  'a': 'during'
    //   }
    // 
    //  + expected - actual
    //   {
    //  -  'a': 'before'
    //  +  'a': 'during'
    //   }
    expect(foo).to.deep.equal(bar);
  });  
});

Alternate Designs

To fix this in chai would require either cloning or stringifying the actual and expected objects at the time of error creation.

Cloning the objects is a potentially expensive, and not always possible operation that would put new constraints on what could be used in an eql comparison.

Stringifying is a possibility as that is an existing requirement for using the Base reporter, however mocha has its own stringifying function with its own configuration and behavior (such as sorting fields in an object) and would require that chai, and any other libraries similar to chai, import mocha and use mocha.utils.stringify (or perhaps another stringifier depending on the reporter), which feels like it breaks modularity pretty hard

Even if this was fixed in the chai module, other modules would have to take on the burden of making sure their actual and expected objects were in mocha compatible string form.

Why should this be in core?

This functionality directly affects the Base reporter.

Benefits

The benefit realized by this code change is correct error messages in situations where objects that can be mutated after changing are passed to the Base reporter

Possible Drawbacks

Code that would have executed later is now run when a test fails. This puts an extra burden on memory requirements at the time of test failure and could possibly result in running out of memory space in a tightly constrained test when it fails.

The cost of execution should be the same, but any time you change when things happen it risks having side effects. It is possible the configuration of the reporter is changed in some way that would make the output different before and after this change.

Possibly the string representation of an object is larger than its non-string representation and causes too much memory to be used over the lifetime of the test.

Unlikely but there might be side effects from having the expected and actual no longer referenced such that they get garbage collected at a different time (and maybe are referenced by a weakmap? I'm reaching here)

It is possible this bug will appear again in new reporters depending on if they build on the Base reporter or not.

Applicable issues

I didn't make an issue for the fix, just noticed it today in a test and thought I'd take a shot at fixing it.

This is just a bug fix

… of delaying until epilogue, the reason for this is that it is possible to reference objects in errors that could be mutated after the error occurs, causing the printed error message to be misleading
@jsf-clabot
Copy link

jsf-clabot commented Oct 20, 2017

CLA assistant check
All committers have signed the CLA.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 89.86% when pulling 760b4db on abrady0:master into da901da on mochajs:master.

Copy link
Contributor

@boneskull boneskull left a comment

Choose a reason for hiding this comment

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

@abrady0 Thanks for the detailed explanation. Hopefully you can entertain my questions...

  1. Do you have a non-contrived use case where this is a problem? It's unusual to do something like the example.
  2. Can you speak to Object.freeze() as a potential solution?

I may have more comments/questions later, but this is just top of mind.

@abrady0
Copy link
Contributor Author

abrady0 commented Nov 2, 2017

Hey @boneskull!

This popped up in one of our unit tests that was exercising some code that accumulated responses from several different async sources and then processed it. for the suite we initialized a single context to be used by multiple tests (as context initialization has some cost), and simulated the responses to test the handling, clearing the accumulator after each test.

Object.freeze is potentially problematic because it could break any existing tests that rely on mutating objects after a test fails, and maybe also because if we want the state of the object to match exactly it would have to be a recursive freeze which could be time consuming if the object was especially large. (I think the first reason is the more legit one, if you're already doing a huge compare the cost of freezing recursively on fail probably won't be a factor).

Agree that this is unusual, perhaps suggesting that the freeze approach is better? I'm open either way.

Sorry for the late reply!

@boneskull
Copy link
Contributor

I'm going to have to take a closer look at this. It seems OK, but in my experience, those are always the ones that bite me in the ass. 😉

@boneskull boneskull self-assigned this Dec 7, 2017
@boneskull boneskull added type: bug a defect, confirmed by a maintainer area: reporters involving a specific reporter semver-patch implementation requires increase of "patch" version number; "bug fixes" labels Dec 7, 2017
@boneskull
Copy link
Contributor

I can find no obvious slowdown or extra memory pressure when run against Babel (Mocha, Chai) nor TypeScript (just Mocha), or Chai itself.

LGTM

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 89.905% when pulling 24b65e8 on abrady0:master into 709a09e on mochajs:master.

@boneskull boneskull merged commit 4508386 into mochajs:master Dec 7, 2017
boneskull added a commit that referenced this pull request Dec 9, 2017
* master:
  refactor "forbid pending" tests for DRY
  add diff tool artifacts to .gitignore [ci skip]
  fix inaccurate diff output due to post-assertion object mutation  (#3075)
  remove unused code in ms module

# Conflicts:
#	.gitignore
@boneskull boneskull removed their assignment Dec 12, 2017
@boneskull boneskull added this to the v4.1.0 milestone Dec 29, 2017
@boneskull boneskull removed the area: reporters involving a specific reporter label Dec 29, 2017
sgilroy pushed a commit to TwineHealth/mocha that referenced this pull request Feb 27, 2019
…hajs#3075)

* have Base generate the strings for error messages immediately instead of delaying until epilogue, the reason for this is that it is possible to reference objects in errors that could be mutated after the error occurs, causing the printed error message to be misleading

* re-add stringify during list to appease tests, open for discussion if this is correct behavior
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
semver-patch implementation requires increase of "patch" version number; "bug fixes" type: bug a defect, confirmed by a maintainer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants