From 9c5c449423776d8350ed8f5bcfd8751ae4af78c8 Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Tue, 2 Apr 2019 18:45:34 +0200 Subject: [PATCH] Add fuzzing based tests in Jest (retry PR8012) (#8164) --- jest.config.js | 1 + package.json | 1 + .../__arbitraries__/sharedSettings.ts | 24 ++++++++ .../matchers-toContain.property.test.ts | 48 +++++++++++++++ .../matchers-toContainEqual.property.test.ts | 46 +++++++++++++++ .../matchers-toEqual.property.test.ts | 58 +++++++++++++++++++ .../matchers-toStrictEqual.property.test.ts | 49 ++++++++++++++++ yarn.lock | 12 ++++ 8 files changed, 239 insertions(+) create mode 100644 packages/expect/src/__tests__/__arbitraries__/sharedSettings.ts create mode 100644 packages/expect/src/__tests__/matchers-toContain.property.test.ts create mode 100644 packages/expect/src/__tests__/matchers-toContainEqual.property.test.ts create mode 100644 packages/expect/src/__tests__/matchers-toEqual.property.test.ts create mode 100644 packages/expect/src/__tests__/matchers-toStrictEqual.property.test.ts diff --git a/jest.config.js b/jest.config.js index 603dd266b03a..fb70f4d04d3b 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,6 +31,7 @@ module.exports = { ], testEnvironment: './packages/jest-environment-node', testPathIgnorePatterns: [ + '/__arbitraries__/', '/node_modules/', '/examples/', '/e2e/.*/__tests__', diff --git a/package.json b/package.json index 97c1225bcd17..6718b26efbf3 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "eslint-plugin-react": "^7.1.0", "eslint-plugin-relay": "~0.0.19", "execa": "^1.0.0", + "fast-check": "^1.13.0", "glob": "^7.1.1", "graceful-fs": "^4.1.15", "isbinaryfile": "^4.0.0", diff --git a/packages/expect/src/__tests__/__arbitraries__/sharedSettings.ts b/packages/expect/src/__tests__/__arbitraries__/sharedSettings.ts new file mode 100644 index 000000000000..ed8092112bf5 --- /dev/null +++ b/packages/expect/src/__tests__/__arbitraries__/sharedSettings.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import fc from 'fast-check'; + +// settings for anything arbitrary +export const anythingSettings = { + key: fc.oneof(fc.string(), fc.constantFrom('k1', 'k2', 'k3')), + maxDepth: 2, // Limit object depth (default: 2) + maxKeys: 5, // Limit number of keys per object (default: 5) + withBoxedValues: true, + // Issue #7975 have to be fixed before enabling the generation of Map + withMap: false, + // Issue #7975 have to be fixed before enabling the generation of Set + withSet: false, +}; + +// assertion settings +export const assertSettings = {}; // eg.: {numRuns: 10000} diff --git a/packages/expect/src/__tests__/matchers-toContain.property.test.ts b/packages/expect/src/__tests__/matchers-toContain.property.test.ts new file mode 100644 index 000000000000..29e9b4c1d4e5 --- /dev/null +++ b/packages/expect/src/__tests__/matchers-toContain.property.test.ts @@ -0,0 +1,48 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import fc from 'fast-check'; +import { + anythingSettings, + assertSettings, +} from './__arbitraries__/sharedSettings'; + +describe('toContain', () => { + it('should always find the value when inside the array', () => { + fc.assert( + fc.property( + fc.array(fc.anything(anythingSettings)), + fc.array(fc.anything(anythingSettings)), + fc.anything(anythingSettings).filter(v => !Number.isNaN(v)), + (startValues, endValues, v) => { + // Given: startValues, endValues arrays and v value (not NaN) + expect([...startValues, v, ...endValues]).toContain(v); + }, + ), + assertSettings, + ); + }); + + it('should not find the value if it has been cloned into the array', () => { + fc.assert( + fc.property( + fc.array(fc.anything(anythingSettings)), + fc.array(fc.anything(anythingSettings)), + fc.dedup(fc.anything(anythingSettings), 2), + (startValues, endValues, [a, b]) => { + // Given: startValues, endValues arrays + // and [a, b] equal, but not the same values + // with `typeof a === 'object && a !== null` + fc.pre(typeof a === 'object' && a !== null); + expect([...startValues, a, ...endValues]).not.toContain(b); + }, + ), + assertSettings, + ); + }); +}); diff --git a/packages/expect/src/__tests__/matchers-toContainEqual.property.test.ts b/packages/expect/src/__tests__/matchers-toContainEqual.property.test.ts new file mode 100644 index 000000000000..ab6145a645f7 --- /dev/null +++ b/packages/expect/src/__tests__/matchers-toContainEqual.property.test.ts @@ -0,0 +1,46 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import fc from 'fast-check'; +import { + anythingSettings, + assertSettings, +} from './__arbitraries__/sharedSettings'; + +describe('toContainEqual', () => { + it('should always find the value when inside the array', () => { + fc.assert( + fc.property( + fc.array(fc.anything(anythingSettings)), + fc.array(fc.anything(anythingSettings)), + fc.anything(anythingSettings), + (startValues, endValues, v) => { + // Given: startValues, endValues arrays and v any value + expect([...startValues, v, ...endValues]).toContainEqual(v); + }, + ), + assertSettings, + ); + }); + + it('should always find the value when cloned inside the array', () => { + fc.assert( + fc.property( + fc.array(fc.anything(anythingSettings)), + fc.array(fc.anything(anythingSettings)), + fc.dedup(fc.anything(anythingSettings), 2), + (startValues, endValues, [a, b]) => { + // Given: startValues, endValues arrays + // and [a, b] identical values + expect([...startValues, a, ...endValues]).toContainEqual(b); + }, + ), + assertSettings, + ); + }); +}); diff --git a/packages/expect/src/__tests__/matchers-toEqual.property.test.ts b/packages/expect/src/__tests__/matchers-toEqual.property.test.ts new file mode 100644 index 000000000000..ca7d88ef7390 --- /dev/null +++ b/packages/expect/src/__tests__/matchers-toEqual.property.test.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import fc from 'fast-check'; +import { + anythingSettings, + assertSettings, +} from './__arbitraries__/sharedSettings'; + +describe('toEqual', () => { + it('should be reflexive', () => { + fc.assert( + fc.property(fc.dedup(fc.anything(anythingSettings), 2), ([a, b]) => { + // Given: a and b identical values + expect(a).toEqual(b); + }), + assertSettings, + ); + }); + + it('should be symmetric', () => { + const safeExpectEqual = (a, b) => { + try { + expect(a).toEqual(b); + return true; + } catch (err) { + return false; + } + }; + fc.assert( + fc.property( + fc.anything(anythingSettings), + fc.anything(anythingSettings), + (a, b) => { + // Given: a and b values + // Assert: We expect `expect(a).toEqual(b)` + // to be equivalent to `expect(b).toEqual(a)` + expect(safeExpectEqual(a, b)).toBe(safeExpectEqual(b, a)); + }, + ), + { + ...assertSettings, + examples: [ + [0, 5e-324], // Issue #7941 + // [ + // new Set([false, true]), + // new Set([new Boolean(true), new Boolean(true)]), + // ], // Issue #7975 + ], + }, + ); + }); +}); diff --git a/packages/expect/src/__tests__/matchers-toStrictEqual.property.test.ts b/packages/expect/src/__tests__/matchers-toStrictEqual.property.test.ts new file mode 100644 index 000000000000..70dcd4e5cd1b --- /dev/null +++ b/packages/expect/src/__tests__/matchers-toStrictEqual.property.test.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import fc from 'fast-check'; +import { + anythingSettings, + assertSettings, +} from './__arbitraries__/sharedSettings'; + +describe('toStrictEqual', () => { + it('should be reflexive', () => { + fc.assert( + fc.property(fc.dedup(fc.anything(anythingSettings), 2), ([a, b]) => { + // Given: a and b identical values + expect(a).toStrictEqual(b); + }), + assertSettings, + ); + }); + + it('should be symmetric', () => { + const safeExpectStrictEqual = (a, b) => { + try { + expect(a).toStrictEqual(b); + return true; + } catch (err) { + return false; + } + }; + fc.assert( + fc.property( + fc.anything(anythingSettings), + fc.anything(anythingSettings), + (a, b) => { + // Given: a and b values + // Assert: We expect `expect(a).toStrictEqual(b)` + // to be equivalent to `expect(b).toStrictEqual(a)` + expect(safeExpectStrictEqual(a, b)).toBe(safeExpectStrictEqual(b, a)); + }, + ), + assertSettings, + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 693c7dbd5c4b..4a4fd3a84fd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5641,6 +5641,13 @@ fancy-log@^1.3.2: parse-node-version "^1.0.0" time-stamp "^1.0.0" +fast-check@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fast-check/-/fast-check-1.13.0.tgz#0f418efb7a8a1fc3f2371f9113b00c2605711e60" + integrity sha512-GlpwnO0mgNV5L5WqU6xGAURw9MMhePLCNfPXgG7hDyABl8obqCK03auKZArBPbybzuYFSKQ2wBMonnf1tbelkw== + dependencies: + pure-rand "^1.6.2" + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -10694,6 +10701,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pure-rand@^1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-1.6.2.tgz#90b3ae78efe36f7e6e27bfffedf934f77382e6e6" + integrity sha512-HNwHOH63m7kCxe0kWEe5jSLwJiL2N83RUUN8POniFuZS+OsbFcMWlvXgxIU2nwKy2zYG2bQan40WBNK4biYPRg== + q@0.9.7: version "0.9.7" resolved "https://registry.yarnpkg.com/q/-/q-0.9.7.tgz#4de2e6cb3b29088c9e4cbc03bf9d42fb96ce2f75"