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

Add fuzzing based tests in Jest #8012

Merged
merged 11 commits into from Mar 17, 2019
Merged

Add fuzzing based tests in Jest #8012

merged 11 commits into from Mar 17, 2019

Conversation

dubzzz
Copy link
Contributor

@dubzzz dubzzz commented Mar 1, 2019

Summary

Following discussions with @jeysal and @SimenB in the PR #7938, I open a PR to add property based tests into the CI of Jest.

In a nutshell what it property based testing:

Property based can be classified as being some kind of fuzzing technic. The need for such technic comes from the fact that it is difficult and barely impossible to cover all the edge cases a code can have with classical unit-tests (so called example based tests).

Property based framework relies on assessing whether a property stays true for any valid inputs. It can be summarized by the following mathematical statement:

for all (x, y, ...)
such as precondition(x, y, ...) holds
property(x, y, ...) is true

In case of failure, it is able to reduce the randomly generated x, y to smaller entries to be more human readable.

@SimenB I will try to work on the RFC next week

Copy link
Member

@SimenB SimenB left a comment

Choose a reason for hiding this comment

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

To my eyes this looks great. Thank you so much for putting it together!

Could you show an example (screenshot) of how a failure looks?

packages/expect/src/__tests__/matchers.property.test.js Outdated Show resolved Hide resolved
packages/expect/src/__tests__/matchers.property.test.js Outdated Show resolved Hide resolved
Copy link
Contributor Author

@dubzzz dubzzz left a comment

Choose a reason for hiding this comment

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

Here is an example of an error output (non verbose mode):
image

packages/expect/src/__tests__/matchers.property.test.js Outdated Show resolved Hide resolved
packages/expect/src/__tests__/matchers.property.test.js Outdated Show resolved Hide resolved
Copy link
Contributor

@jeysal jeysal left a comment

Choose a reason for hiding this comment

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

This is great, thanks @dubzzz! I found some errors in the comments. Please go over them to make sure they accurately describe what the test cases do so people unfamiliar with property-based testing do not get confused 😄

packages/expect/src/__tests__/matchers.property.test.ts Outdated Show resolved Hide resolved
packages/expect/src/__tests__/matchers.property.test.ts Outdated Show resolved Hide resolved
packages/expect/src/__tests__/matchers.property.test.ts Outdated Show resolved Hide resolved
@pedrottimark
Copy link
Contributor

pedrottimark commented Mar 2, 2019

Thank you for bringing explicit property-based assertions to improve the quality of matchers.

/cc @grosto your thoughts are welcome in this discussion

I will let Simen and Tim brainstorm options (like seed and search depth) for CI versus local run.

To learn fast-check package, I will follow your example for another matcher.

  • Suggestion: despite current pattern in matchers.test.js file, let’s simplify matcher names to describe('toEqual', …) because I found recently regexp characters especially () are trouble for --testNamePattern option (but see next question)
  • Question: what do y’all think are strength and weakness of splitting tests from huge file to matchers-toEqual.test.ts and so on?
  • Question: Can we organize existing example-based tests as specifics of property-based tests?
  • First impression about Jest integration: is fc.assert what needs to be integrated? in a way that tester can still explicitly import/require whichever version offast-check package they want?

@dubzzz Your thoughts also welcome about tests for diff-sequences package in Jest repo.

P.S. The effort to find broken edge cases in expect package has code name “expectorant” ;)

@jeysal
Copy link
Contributor

jeysal commented Mar 2, 2019

  • Question: Can we organize existing-based tests as specific examples of property-based tests?

This sounds really interesting, @dubzzz seen something like that been done before?

@pedrottimark
Copy link
Contributor

Forgot to say that fast-check will also be local research tool to confirm understanding about differences between assertion libraries when we fix subtle problems (for example, sets and maps).

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 3, 2019

@pedrottimark, @jeysal here are some of my answers:

Suggestion: despite current pattern in matchers.test.js file, let’s simplify matcher names to describe('toEqual', …) because I found recently regexp characters especially () are trouble for --testNamePattern option (but see next question)

✅ I am updating the review accordingly. Indeed it seems easier to write a regex for toEqual than for .toEqual() (escaping needed for this one).

Question: what do y’all think are strength and weakness of splitting tests from huge file to matchers-toEqual.test.ts and so on?

✅ IMO it would be a great idea. Today the file gathering tests for matchers is quite huge. Splitting it into smaller entities would make it more readable and easier to find - just need to look for a file whose name is toEqual for instance.

Question: Can we organize existing example-based tests as specifics of property-based tests?

fast-check makes it possible for users to specify special values they always want to test against a given property. If you add custom examples, the first random values would be replaced by the custom values you wanted to assess.

It's important to see that sometimes, the property would not be as accurate as a real unit test. For instance, in the test Given a, b random values, expect(a).toEqual(b) should be equivalent to expect(b).toEqual(a) does not check if the expect if successful or failing. Ii only checks for symmetry. In a way properties should be seen as a complement to classical unit-tests.

Documentation of the feature: https://github.com/dubzzz/fast-check/blob/master/documentation/Tips.md#add-custom-examples-next-to-generated-ones.

See the initial feature request: dubzzz/fast-check#141.

✅ I added examples for the issues I recently opened in Jest.

First impression about Jest integration: is fc.assert what needs to be integrated? in a way that tester can still explicitly import/require whichever version offast-check package they want?

In my wrapper for ava [here], fast-check is a peer dependency of the package so that users can choose which version of fast-check they want to use. I don't known exactly which would be the best thing for an integration with Jest ;)

Choice A: peer dependency

Users would need to explicitely install fast-check to have it available.

Choice B: dependency on 1.x.x

Users would directly benefit from fast-check. I think they might still be able to bump to the latest version just by referencing it as a dev dependency of their own project - has to be confirmed.

Your thoughts also welcome about tests for diff-sequences package in Jest repo.

I will have a look to this one to see how I would have tested it with property based.

@SimenB
Copy link
Member

SimenB commented Mar 3, 2019

Side note (better discussed in the RFC), but if we include fast-check in Jest, it will not be as a peer dep - we don't want people to have to install extra packages to use built-in features. The prettier requirement for inline snapshots is unfortunate and not something I think we should repeat.

I think they might still be able to bump to the latest version just by referencing it as a dev dependency of their own project - has to be confirmed.

Yeah, that should be fine. We'll have a semver range, so unless you release a new major it'll be within reach

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 3, 2019

@pedrottimark If I understand well what is the purpose of diff-sequences, it is pretty similar to: find the longest common substring except we are dealing with arrays of objects instead of strings.

I think we can adapt tests I did on commonLongestSubstr to diff-sequences:

  • symmetry: diff(a, b) has the same length as diff(b, a) -- there is not a real symmetry but lengths are equal
  • sub-part of the input: diff(a, b) is a subset of a and a subset of b
  • other cases described in the review below

Full code: dubzzz/javascript-algorithms@67102e4

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 3, 2019

@SimenB I will try to open the RFC later this week to discuss those points ;)
Or maybe now

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 3, 2019

@pedrottimark @SimenB Let's discuss direct integration within Jest here: #8035

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 5, 2019

For the moment I will remove the property called should distinguish distinct values.

  1. The precondition relying on a tweaked version of JSON.stringify is too fragile. For instance it would consider that {a:0,b:1} and {b:1,a:0} are distinct and move to the expect(a).not.toStrictEqual(b). -- was not detected so far because the probability of such entry is barely null given the scope of valid inputs
  2. Generated objects are far from each others, I need to find a better solution for generating objects closer to each others.

Meanwhile those properties are still a good starting point :)

@jeysal
Copy link
Contributor

jeysal commented Mar 5, 2019

This mostly LGTM in its current form and I'm excited to see what other bugs we may find with further property tests for other matchers.
Just one request: Given that we apparently agree something like matchers.test.ts shouldn't exist but rather be split into tests for individual matchers, I think we should set a good example here and not make the "everything into one file" mistake for property-based tests.
@dubzzz Could you split the test (essentially converting the current describe blocks into individual files)?

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 5, 2019

@jeysal I just updated the review based on your last comment. I divided the spec into four distinct specs. Common settings have been moved into a file shared accross those files called sharedSettings.ts.

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 6, 2019

Some checks failed, but I don't if they are related to the review. Can you help me?

#!/bin/bash -eo pipefail
yarn test-ci

yarn run v1.13.0
$ yarn jest-coverage -i --config jest.config.ci.js && yarn test-leak && node scripts/mapCoverage.js && codecov
$ yarn jest --coverage -i --config jest.config.ci.js
$ node ./packages/jest-cli/bin/jest.js --coverage -i --config jest.config.ci.js
........(node:335) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 exit listeners added. Use emitter.setMaxListeners() to increase limit
......................................................................................................................................................................................................................................
  ● Test suite failed to run

    Your test suite must contain at least one test.

      at packages/jest-core/build/TestScheduler.js:221:24

..............................................................................

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Exited with code 1

See https://circleci.com/gh/facebook/jest/56322?utm_campaign=vcs-integration-link&utm_medium=referral&utm_source=github-build-link

Copy link
Contributor

@jeysal jeysal left a comment

Choose a reason for hiding this comment

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

I divided the spec into four distinct specs.

Great, thanks! The set issue is not close to being fixed, so I guess we can merge this now and enable the Set tests / add further tests later.

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 12, 2019

@thymikee @pedrottimark Do you approve the review on your side too?

@pedrottimark
Copy link
Contributor

@dubzzz I apologize for delay to finish the review. I will look with fresh eyes tomorrow.

Copy link
Collaborator

@thymikee thymikee left a comment

Choose a reason for hiding this comment

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

👍

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 14, 2019

Just rebased all the commits due to a conflict in package.json. Test should be green again following this rebase

Copy link
Contributor

@pedrottimark pedrottimark left a comment

Choose a reason for hiding this comment

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

Thank you for building a beautiful foundation! Now we know who will test the testers :)

We can clean up existing tests while moving them into new files as examples, when possible.

@SimenB SimenB merged commit 609932e into jestjs:master Mar 17, 2019
@SimenB
Copy link
Member

SimenB commented Mar 17, 2019

Thank you so much @dubzzz!

@SimenB
Copy link
Member

SimenB commented Mar 17, 2019

Hmm, I merged this thinking the CI failure was transient. But it failed twice on master with the same error as well.

@dubzzz mind checking out why the node process aborts using circus? To run with circus locally, do JEST_CIRCUS=1 yarn jest. Might have to revert tomorrow (we plan on pushing quite a lot this coming week, and need green CI before that)

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 17, 2019

@SimenB Please revert for the moment :'(, if it breaks your CI. I will spend some time to investigate the issue next week (I'll not be able to make it today)

@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 17, 2019

What's strange is that it passed before my rebase. Like you I thought it was just transient :/

Last week, I started to investigate whether it can be related to the size of the instances generated by fc.anything(). But instances were not so huge :/

I'll re-run the circus test suite locally to have a better understanding of what is going wrong. Sorry for the inconvenience :(

@SimenB
Copy link
Member

SimenB commented Mar 17, 2019

Sorry for the inconvenience :(

No worries! If it hadn't been for the fact we're gonna be working on this quite a lot the coming week there'd have been no rush. So just fix it whenever you find the time!

SimenB added a commit to SimenB/jest that referenced this pull request Mar 17, 2019
@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 18, 2019

@SimenB I do not reproduce the crash on my machine. Did you reproduce it locally?
Did you encounter the failures on other test suites - not ci/circleci: test-jest-circus?

Investigation

On my side I started to investigate what could have led fast-check to consume so many ressources.

The issue seems to come from the fact, that fast-check will generate large objects when asked to build fc.array(fc.anything()). Basically you can imagine that:

  • fc.array generates arrays having between 0 to 10 items
  • fc.anything generates an object of maximal depth 2 having at most 10 keys (depth 0 is equivalent to a primitive)

In addition to the generated object, fast-check provides a shrinker. The shrinker will be used internally in case of failure to reduce an object to a smaller one (easier to debug). Unfortunately it takes lots of memory for large fc.anything() and might explain the out-of-memory in some extreme cases where multiple references are kept alive in memory.

In order to confirm my say and reduce the memory footprint of fc.array(fc.anything()), I wrote a code snippet to measure its memory consumption. I found a way to divide the memory consumption by a factor of 2 to 3 by reducing the number of keys of the objects generated by fc.anything().

Next

Maybe the best choice to check if my coming soon fix works properly will be to open a new PR to try it against the CI. We can confirm its stability by relaunching the CI multiple times on the next PR if needed.

Still confirming the explanation above on my side

@SimenB
Copy link
Member

SimenB commented Mar 19, 2019

I was unable to reproduce this locally as well...

Opening up a PR seems reasonable to me!

thymikee added a commit to Brantron/jest that referenced this pull request Mar 19, 2019
* upstream/master: (391 commits)
  more precise circus asyncError types (jestjs#8150)
  Add typeahead watch plugin (jestjs#6449)
  fix: getTimerCount not taking immediates and ticks into account (jestjs#8139)
  website: add an additional filter predicate to backers (jestjs#7286)
  [🔥] Revised README (jestjs#8076)
  [jest-each] Fix test function type (jestjs#8145)
  chore: improve bug template labels for easier maintenance (jestjs#8141)
  Add documentation related to auto-mocking (jestjs#8099)
  Add support for bigint to pretty-format (jestjs#8138)
  Revert "Add fuzzing based tests in Jest (jestjs#8012)"
  chore: remove console.log
  chore: Improve description of optional arguments in ExpectAPI.md (jestjs#8126)
  Add fuzzing based tests in Jest (jestjs#8012)
  Move @types/node to the root package.json (jestjs#8129)
  chore: use property initializer syntax (jestjs#8117)
  chore: delete flow types from the repo (jestjs#8061)
  Move changelog entry to the proper version (jestjs#8115)
  Release 24.5.0
  Expose throwOnModuleCollision (jestjs#8113)
  add matchers to expect type (jestjs#8093)
  ...
dubzzz added a commit to dubzzz/jest that referenced this pull request Mar 19, 2019
@dubzzz
Copy link
Contributor Author

dubzzz commented Mar 19, 2019

@SimenB Let's retry with PR #8164

Before merging it, we should definitely confirm we will not encounter the out-of-memory issue with it.

The fix: I basically decreased the size of the generated instances (and also the memory consumed to store the shrinker). Instances go from 10 (this PR) to 5 (the new PR) keys per object with a maximal depth of 2 (already the case before). When I tested it manually, it reduced by a factor of 2-3 the size required to store value+shrinker in memory.

dubzzz added a commit to dubzzz/jest that referenced this pull request Mar 21, 2019
@dubzzz dubzzz deleted the property-based branch October 14, 2019 20:29
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants