├── .babelrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── spec └── support │ └── jasmine.json ├── test-ava ├── example.raw.spec.js └── example.spec.js ├── test-jasmine └── example.spec.js ├── test-jest └── example.spec.js ├── test-tape └── example.spec.js └── test ├── longest common substr ├── README.md ├── implem.js └── test.js ├── packages ├── crypto-js │ ├── README.md │ └── test.js ├── fast-check │ └── README.md ├── io-ts │ └── test.js ├── is-thirteen │ ├── README.md │ └── test.js ├── js-yaml │ ├── README.md │ └── test.js ├── left-pad │ ├── README.md │ └── test.js └── query-string │ ├── README.md │ └── test.js └── stable sort ├── README.md ├── implem.js └── test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["transform-es2015-modules-commonjs"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nicolas DUBIEN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Property based testing with [fast-check](https://fast-check.dev/) 2 | 3 | This repository provides examples of property based tests you might write. It makes use of **fast-check** - framework written in TypeScript - but can easily be transposed to other frameworks or languages. 4 | 5 | In order to add **fast-check** to you project, you have to run: 6 | 7 | ```bash 8 | npm install fast-check --save-dev 9 | ``` 10 | 11 | If you want to run the properties of this repository locally: 12 | 13 | ```bash 14 | git clone https://github.com/dubzzz/fast-check-examples.git 15 | cd fast-check-examples 16 | npm install 17 | npm run test 18 | ``` 19 | 20 | Please note that tests of failing implementations have been disabled. 21 | 22 | ## Property based testing in a nutshell 23 | 24 | The motto of property based testing: properties instead of isolated examples - *or in addition to*. 25 | 26 | It makes it possible to cover a wider range of inputs and hence find yet undiscovered bugs (see bugs discovered by fast-check: [js-yaml](https://github.com/nodeca/js-yaml/pull/398), [query-string](https://github.com/sindresorhus/query-string/pull/138), [left-pad](https://github.com/stevemao/left-pad/issues/58)). 27 | 28 | A property can be summurized by: *for any (a, b, ...) such as precondition(a, b, ...) holds, property(a, b, ...) is true* 29 | 30 | Property based testing frameworks try to discover inputs `(a, b, ...)` causing `property(a, b, ...)` to be false. If they find such, they have to reduce the counterexample to a minimal counterexample. 31 | 32 | ## Tips to find useful properties 33 | 34 | Traps to avoid: *testing your code against itself*. 35 | 36 | Keep in mind that *properties might not be able to assess the exact value of the output*. But they should be able to test some traits of the output. 37 | 38 | Here are some tips that can help you to find your properties. 39 | 40 | ### Independant of your inputs 41 | 42 | **When?** Some characteristics of your output are independant from your input 43 | 44 | For instance: 45 | - for any floating point number `d`, `Math.floor(d)` is an integer 46 | - for any integer `n`, `Math.abs(n)` is superior to `0` 47 | 48 | ### Characteristics derived from the input 49 | 50 | **When?** Output has an easy relationship with the input 51 | 52 | For instance: if the algorithm computes the average of two numbers 53 | ```js 54 | fc.assert( 55 | fc.property( 56 | fc.integer(), fc.integer(), 57 | (a, b) => a <= b 58 | ? a <= average(a, b) && average(a, b) <= b 59 | : b <= average(a, b) && average(a, b) <= a)); 60 | ``` 61 | 62 | *In other words: the average of a and b must be between a and b* 63 | 64 | For instance: if the algorithm factorizes a number 65 | ```js 66 | fc.assert( 67 | fc.property( 68 | fc.nat(), 69 | n => factorize(n).reduce((acc, cur) => acc*cur, 1) === n)); 70 | ``` 71 | 72 | *In other words: the product of all numbers in the output must be equal to the input* 73 | 74 | ### Subset of inputs 75 | 76 | **When?** Some inputs might have easy outputs 77 | 78 | For instance: if the algorithm removes duplicates from an array 79 | ```js 80 | fc.assert( 81 | fc.property( 82 | fc.set(fc.char()), 83 | data => assert.deepEqual(removeDuplicates(data), data))); 84 | ``` 85 | 86 | *In other words: removing duplicates of an array of unique values returns the array itself* 87 | 88 | For instance: if the algorithm checks if a string contains another one 89 | ```js 90 | fc.assert( 91 | fc.property( 92 | fc.string(), fc.string(), fc.string(), 93 | (a, b, c) => contains(b, a + b + c))); 94 | ``` 95 | 96 | *In other words: the concatenation of a, b and c always contains string b* 97 | 98 | ### Combination of functions 99 | 100 | **When?** Two or more functions in your API can be combined to have something simple to assess 101 | 102 | #### Round trip 103 | 104 | The API provides some kind of encode/decode functions. In this case the round trip is: `decode(encode(value)) === value`. 105 | 106 | For instance: if you have two methods zip/unzip 107 | ```js 108 | fc.assert( 109 | fc.property( 110 | fc.string(), 111 | v => unzip(zip(v)) === v)); 112 | ``` 113 | 114 | *In other words: unzip is the reverse of zip* 115 | 116 | #### More general combination 117 | 118 | For instance: if you provide lcm and gcd 119 | ```js 120 | fc.assert( 121 | fc.property( 122 | fc.nat(), fc.nat(), 123 | (a, b) => lcm(a, b) * gcd(a, b) === a * b)); 124 | ``` 125 | 126 | ### Compare to a simpler version 127 | 128 | **When?** There is a slower but simpler implementation or you are rewriting the code 129 | 130 | For instance: if the algorithm checks that a sorted array contains a given value in a binary way 131 | ```js 132 | fc.assert( 133 | fc.property( 134 | fc.char(), fc.array(fc.char()).map(d => d.sort()), 135 | (c, data) => binaryContains(c, data) === linearContains(c, data))); 136 | ``` 137 | 138 | *In other words: binaryContains and linearContains must always agree, only the complexity differs* 139 | 140 | ## More on Property Based Testing 141 | 142 | More details on Property based testing at: 143 | - [John Hughes — Don’t Write Tests](https://www.youtube.com/watch?v=hXnS_Xjwk2Y) 144 | - [Generating test cases so you don’t have to (Spotify)](https://labs.spotify.com/2015/06/25/rapid-check/) 145 | 146 | Remember that property based does not replace example based testing. 147 | Nonetheless it can cover a larger range of inputs and potentially catch problems not seen with examples. 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-check-examples", 3 | "version": "1.0.0", 4 | "description": "Property based testing examples based on fast-check", 5 | "scripts": { 6 | "format:check": "prettier --list-different \"**/*.js\"", 7 | "format:fix": "prettier --write \"**/*.js\"", 8 | "test:ava": "ava \"test-ava\"", 9 | "test:jasmine": "jasmine", 10 | "test:jest": "jest \"test-jest\"", 11 | "test:tape": "node \"test-tape/example.spec.js\"", 12 | "test": "mocha --require babel-polyfill --require babel-register \"test/**/test.js\"" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/dubzzz/fast-check-examples.git" 17 | }, 18 | "author": "Nicolas DUBIEN ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/dubzzz/fast-check-examples/issues" 22 | }, 23 | "homepage": "https://github.com/dubzzz/fast-check-examples#readme", 24 | "dependencies": { 25 | "babel-polyfill": "^6.26.0", 26 | "crypto-js": "^3.1.9-1", 27 | "io-ts": "^1.3.0", 28 | "is-thirteen": "^2.0.0", 29 | "js-yaml": "^3.11.0", 30 | "left-pad": "^1.3.0", 31 | "query-string": "^6.1.0" 32 | }, 33 | "devDependencies": { 34 | "ava": "^0.25.0", 35 | "ava-fast-check": "^1.0.1", 36 | "babel-cli": "^6.26.0", 37 | "babel-jest": "^22.4.3", 38 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 39 | "babel-preset-env": "^1.6.0", 40 | "chai": "^4.1.2", 41 | "fast-check": "^1.6.0", 42 | "jasmine": "^3.1.0", 43 | "jest": "^22.4.3", 44 | "mocha": "^5.0.5", 45 | "prettier": "^1.11.1", 46 | "regenerator-runtime": "^0.11.1", 47 | "tape": "^4.9.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "test-jasmine", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "stopSpecOnExpectationFailure": true, 7 | "random": true 8 | } 9 | -------------------------------------------------------------------------------- /test-ava/example.raw.spec.js: -------------------------------------------------------------------------------- 1 | // Example of fast-check integration without using ava-fast-check 2 | // See https://github.com/dubzzz/ava-fast-check 3 | 4 | import test from 'ava'; 5 | import * as fc from 'fast-check'; 6 | 7 | ///*bug*/ const contains = (pattern, text) => text.substr(1).indexOf(pattern) !== -1; 8 | const contains = (pattern, text) => text.indexOf(pattern) !== -1; 9 | 10 | test('The concatenation of a, b and c always contains b', t => 11 | t.notThrows(() => { 12 | fc.assert( 13 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 14 | return contains(b, a + b + c); 15 | }) 16 | ); 17 | })); 18 | -------------------------------------------------------------------------------- /test-ava/example.spec.js: -------------------------------------------------------------------------------- 1 | import { testProp, fc } from '@fast-check/ava'; 2 | import { expect } from 'chai'; 3 | 4 | ///*bug*/ const contains = (pattern, text) => text.substr(1).indexOf(pattern) !== -1; 5 | const contains = (pattern, text) => text.indexOf(pattern) !== -1; 6 | 7 | testProp('The concatenation of a, b and c always contains b', [fc.string(), fc.string(), fc.string()], (t, a, b, c) => { 8 | t.true(contains(b, a + b + c)); 9 | }); 10 | 11 | testProp( 12 | 'The concatenation of a, b and c always contains b (with expect)', 13 | [fc.string(), fc.string(), fc.string()], 14 | (t, a, b, c) => { 15 | expect(contains(b, a + b + c)).to.be.true; 16 | } 17 | ); 18 | -------------------------------------------------------------------------------- /test-jasmine/example.spec.js: -------------------------------------------------------------------------------- 1 | const fc = require('fast-check'); 2 | 3 | ///*bug*/ const contains = (pattern, text) => text.substr(1).indexOf(pattern) !== -1; 4 | const contains = (pattern, text) => text.indexOf(pattern) !== -1; 5 | 6 | describe('Jasmine Example', () => { 7 | it('The concatenation of a, b and c always contains b', () => { 8 | fc.assert( 9 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 10 | return contains(b, a + b + c); 11 | }) 12 | ); 13 | }); 14 | it('Also works with expect', () => { 15 | // requires jasmine.json to set: 16 | // "stopSpecOnExpectationFailure": true 17 | fc.assert( 18 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 19 | expect(contains(b, a + b + c)).toBe(true); 20 | }) 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test-jest/example.spec.js: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | 3 | ///*bug*/ const contains = (pattern, text) => text.substr(1).indexOf(pattern) !== -1; 4 | const contains = (pattern, text) => text.indexOf(pattern) !== -1; 5 | 6 | test('The concatenation of a, b and c always contains b', () => { 7 | fc.assert( 8 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 9 | return contains(b, a + b + c); 10 | }) 11 | ); 12 | }); 13 | test('Also works with expect', () => { 14 | fc.assert( 15 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 16 | expect(contains(b, a + b + c)).toBe(true); 17 | }) 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /test-tape/example.spec.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const fc = require('fast-check'); 3 | 4 | ///*bug*/ const contains = (pattern, text) => text.substr(1).indexOf(pattern) !== -1; 5 | const contains = (pattern, text) => text.indexOf(pattern) !== -1; 6 | 7 | test('The concatenation of a, b and c always contains b', assert => { 8 | assert.plan(1); 9 | assert.doesNotThrow(() => { 10 | fc.assert( 11 | fc.property(fc.string(), fc.string(), fc.string(), (a, b, c) => { 12 | return contains(b, a + b + c); 13 | }) 14 | ); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/longest common substr/README.md: -------------------------------------------------------------------------------- 1 | # Longest common substring 2 | 3 | ## Algorithm 4 | 5 | Given two strings **s1** and **s2** the algorithm should return the longest common substring. 6 | 7 | ## Properties 8 | 9 | ### [A] should find the same substring length whatever the order 10 | 11 | for any strings `s1` and `s2` 12 | longestCommonSubstr(`s1`, `s2`).length should be identical to longestCommonSubstr(`s2`, `s1`).length 13 | 14 | ### [B] should include the substr in both strings 15 | 16 | for any strings `s1` and `s2` 17 | longestCommonSubstr(`s1`, `s2`) should be a substring of `s1` 18 | longestCommonSubstr(`s1`, `s2`) should be a substring of `s2` 19 | 20 | ### [C] should detect the longest common 21 | 22 | for any strings `s`, `prefix` and `suffix` 23 | longestCommonSubstr(`s`, `prefix` + `s` + `suffix`) should be `s` 24 | -------------------------------------------------------------------------------- /test/longest common substr/implem.js: -------------------------------------------------------------------------------- 1 | const longestCommonSubstr = (s1, s2) => { 2 | const helper = [...s1].map(_ => [...s2].map(_ => 0)); 3 | 4 | let maxStart = 0; 5 | let maxLength = 0; 6 | for (let idx1 = 0; idx1 !== s1.length; ++idx1) { 7 | for (let idx2 = 0; idx2 !== s2.length; ++idx2) { 8 | if (s1[idx1] !== s2[idx2]) continue; 9 | const length = idx1 > 0 && idx2 > 0 ? helper[idx1 - 1][idx2 - 1] + 1 : 1; 10 | helper[idx1][idx2] = length; 11 | if (length > maxLength) { 12 | maxStart = idx1 - length + 1; 13 | maxLength = length; 14 | } 15 | } 16 | } 17 | return s1.substr(maxStart, maxLength); 18 | }; 19 | 20 | export { longestCommonSubstr }; 21 | -------------------------------------------------------------------------------- /test/longest common substr/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | import { longestCommonSubstr } from './implem'; 4 | 5 | describe('longest common substr', () => { 6 | it('should find the same substring lengths whatever the order of the inputs', () => 7 | fc.assert( 8 | fc.property(fc.string(), fc.string(), (s1, s2) => { 9 | assert.equal(longestCommonSubstr(s1, s2).length, longestCommonSubstr(s2, s1).length); 10 | }) 11 | )); 12 | it('should include the substr in both strings', () => 13 | fc.assert( 14 | fc.property(fc.string(), fc.string(), (s1, s2) => { 15 | const longest = longestCommonSubstr(s1, s2); 16 | assert.ok(s1.includes(longest)); 17 | assert.ok(s2.includes(longest)); 18 | }) 19 | )); 20 | it('should detect the longest common', () => 21 | fc.assert( 22 | fc.property(fc.string(), fc.string(), fc.string(), (s, prefix, suffix) => { 23 | assert.equal(longestCommonSubstr(prefix + s + suffix, s), s); 24 | }) 25 | )); 26 | }); 27 | -------------------------------------------------------------------------------- /test/packages/crypto-js/README.md: -------------------------------------------------------------------------------- 1 | # crypto-js 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/crypto-js.svg) 4 | 5 | ## Properties 6 | 7 | ### [A] should be able to decrypt itself 8 | 9 | for any unicode string `s` 10 | encrypting `s` then decryting it should be equal to `s` 11 | -------------------------------------------------------------------------------- /test/packages/crypto-js/test.js: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | const CryptoJS = require('crypto-js'); 3 | 4 | const propertyDecryptItSelf = algorithmName => { 5 | it(`[${algorithmName}] should decrypt to original message`, () => { 6 | fc.assert( 7 | fc.property(fc.fullUnicodeString(), fc.fullUnicodeString(), (message, secret) => { 8 | const ciphertext = CryptoJS[algorithmName].encrypt(message, secret); 9 | const bytes = CryptoJS[algorithmName].decrypt(ciphertext.toString(), secret); 10 | const plaintext = bytes.toString(CryptoJS.enc.Utf8); 11 | return message === plaintext; 12 | }) 13 | ); 14 | }); 15 | }; 16 | 17 | describe('crypto-js', () => { 18 | propertyDecryptItSelf('AES'); 19 | propertyDecryptItSelf('DES'); 20 | propertyDecryptItSelf('TripleDES'); 21 | propertyDecryptItSelf('RC4'); 22 | propertyDecryptItSelf('RC4Drop'); 23 | propertyDecryptItSelf('Rabbit'); 24 | propertyDecryptItSelf('RabbitLegacy'); 25 | }); 26 | -------------------------------------------------------------------------------- /test/packages/fast-check/README.md: -------------------------------------------------------------------------------- 1 | # fast-check 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/fast-check.svg) 4 | 5 | And what if fast-check was testing itself? Here is an extract of properties that can be defined for the arbitraries of fast-check. 6 | 7 | Tests of fast-check are available on the official repository: [here](https://github.com/dubzzz/fast-check/tree/master/test/unit). 8 | 9 | ## Properties 10 | 11 | ### [A] should produce the same value given the same seed 12 | 13 | for any seed `s` 14 | `arbitrary().generate(Random(s)) === arbitrary().generate(Random(s))` 15 | 16 | ### [B] should produce the same shrunk values given the same seed 17 | 18 | for any seed `s`, shrink path `path` 19 | `generateShrinks(s, path) === generateShrinks(s, path)` 20 | 21 | ### [C] should produce strictly smaller values along the shrink path 22 | 23 | for any seed `s`, shrink path `path` 24 | `generateShrinks(s, path)[idx] < generateShrinks(s, path)[idx-1]`, for all `idx` 25 | 26 | ### [D] should always generate correct values 27 | 28 | for any seed `s` 29 | `arbitrary().generate(Random(s))` is a valid value (right type, right range...) 30 | 31 | ### [E] should always shrink to correct values 32 | 33 | for any seed `s`, shrink path `path` 34 | `generateShrinks(s, path)[idx]` is a valid value, for all `idx` 35 | -------------------------------------------------------------------------------- /test/packages/io-ts/test.js: -------------------------------------------------------------------------------- 1 | import * as fc from 'fast-check'; 2 | import * as t from 'io-ts'; 3 | import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; 4 | 5 | const expandToIoAndInstance = rawValues => { 6 | const io = rawValues.reduce((p, e) => { 7 | p[e[0]] = e[1][0]; 8 | return p; 9 | }, {}); 10 | const instance = rawValues.reduce((p, e) => { 11 | p[e[0]] = e[1][1]; 12 | return p; 13 | }, {}); 14 | return { io, instance }; 15 | }; 16 | 17 | const ValidTypeDefValueArb = fc 18 | .set( 19 | fc.tuple( 20 | fc.fullUnicodeString(), 21 | fc.oneof( 22 | fc.tuple(fc.constant(t.null), fc.constant(null)), 23 | fc.tuple(fc.constant(t.undefined), fc.constant(undefined)), 24 | fc.tuple(fc.constant(t.string), fc.fullUnicodeString()), 25 | fc.tuple(fc.constant(t.number), fc.double()), 26 | fc.tuple(fc.constant(t.boolean), fc.boolean()), 27 | fc.tuple(fc.constant(t.any), fc.anything()), 28 | fc.tuple(fc.constant(t.object), fc.object()), 29 | fc.tuple(fc.constant(t.Integer), fc.integer(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)), 30 | fc.tuple(fc.constant(t.Array), fc.array(fc.anything())), 31 | fc.tuple(fc.constant(t.array(t.number)), fc.array(fc.integer())), 32 | fc.tuple(fc.constant(t.Dictionary), fc.dictionary(fc.fullUnicodeString(), fc.anything())), 33 | fc.tuple(fc.constant(t.Function), fc.func(fc.integer())) 34 | ) 35 | ), 36 | (a, b) => a[0] === b[0] 37 | ) 38 | .map(expandToIoAndInstance); 39 | 40 | const generationChoices = { 41 | null: fc.constant(null), 42 | undefined: fc.constant(undefined), 43 | string: fc.fullUnicodeString(), 44 | number: fc.double(), 45 | boolean: fc.boolean(), 46 | object: fc.object(), 47 | integer: fc.integer(Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER), 48 | array: fc.array(fc.integer()), 49 | dict: fc.dictionary(fc.fullUnicodeString(), fc.anything()), 50 | func: fc.func(fc.integer()) 51 | }; 52 | const allBut = (...forbidden) => { 53 | return fc.oneof( 54 | ...Object.keys(generationChoices) 55 | .filter(k => forbidden.indexOf(k) === -1) 56 | .map(k => generationChoices[k]) 57 | ); 58 | }; 59 | const InvalidTypeDefValueArb = fc 60 | .set( 61 | fc.tuple( 62 | fc.fullUnicodeString(), 63 | fc.oneof( 64 | fc.tuple(fc.constant(t.null), allBut('null')), 65 | fc.tuple(fc.constant(t.undefined), allBut('undefined')), 66 | fc.tuple(fc.constant(t.string), allBut('string')), 67 | fc.tuple(fc.constant(t.number), allBut('number', 'integer')), 68 | fc.tuple(fc.constant(t.boolean), allBut('boolean')), 69 | fc.tuple(fc.constant(t.object), allBut('object', 'dict', 'array')), //array?? 70 | fc.tuple(fc.constant(t.Integer), allBut('integer')), 71 | fc.tuple(fc.constant(t.Array), allBut('array')), 72 | fc.tuple(fc.constant(t.array(t.number)), allBut('array')), 73 | fc.tuple(fc.constant(t.Dictionary), allBut('object', 'dict', 'array')), //array?? 74 | fc.tuple(fc.constant(t.Function), allBut('func')) 75 | ) 76 | ), 77 | 1, 78 | 10, 79 | (a, b) => a[0] === b[0] 80 | ) 81 | .map(expandToIoAndInstance); 82 | 83 | describe('io-ts', () => { 84 | describe('t.type', () => { 85 | it('should fail to decode instance with missing keys', () => { 86 | fc.assert( 87 | fc.property(ValidTypeDefValueArb, ValidTypeDefValueArb, (v1, v2) => { 88 | const foundExtra = Object.keys(v2.instance).find( 89 | k => !v1.instance.hasOwnProperty(k) && v2.io[k] !== t.any && v2.io[k] !== t.undefined 90 | ); 91 | fc.pre(foundExtra); 92 | const io = Object.assign(Object.assign({}, v2.io), v1.io); 93 | const Schema = t.type(io); 94 | try { 95 | ThrowReporter.report(Schema.decode(v1.instance)); 96 | return false; 97 | } catch (err) { 98 | return true; 99 | } 100 | }) 101 | ); 102 | }); 103 | it('should fail to decode instance with invalid values', () => { 104 | fc.assert( 105 | fc.property(ValidTypeDefValueArb, InvalidTypeDefValueArb, (v1, v2) => { 106 | const io = Object.assign(Object.assign({}, v1.io), v2.io); 107 | const instance = Object.assign(Object.assign({}, v1.instance), v2.instance); 108 | const Schema = t.type(io); 109 | try { 110 | ThrowReporter.report(Schema.decode(instance)); 111 | return false; 112 | } catch (err) { 113 | return true; 114 | } 115 | }), 116 | { verbose: true } 117 | ); 118 | }); 119 | it('should successfully decode exact instance', () => { 120 | fc.assert( 121 | fc.property(ValidTypeDefValueArb, ({ io, instance }) => { 122 | const Schema = t.type(io); 123 | ThrowReporter.report(Schema.decode(instance)); 124 | }) 125 | ); 126 | }); 127 | it('should successfully decode valid instance', () => { 128 | fc.assert( 129 | fc.property(ValidTypeDefValueArb, fc.object(), ({ io, instance }, instance2) => { 130 | const Schema = t.type(io); 131 | const obj = Object.assign(Object.assign({}, instance2), instance); 132 | ThrowReporter.report(Schema.decode(obj)); 133 | }) 134 | ); 135 | }); 136 | }); 137 | describe('t.exact', () => { 138 | it('should fail to decode instance with missing keys', () => { 139 | fc.assert( 140 | fc.property(ValidTypeDefValueArb, ValidTypeDefValueArb, (v1, v2) => { 141 | const foundExtra = Object.keys(v2.instance).find( 142 | k => !v1.instance.hasOwnProperty(k) && v2.io[k] !== t.any && v2.io[k] !== t.undefined 143 | ); 144 | fc.pre(foundExtra); 145 | const io = Object.assign(Object.assign({}, v2.io), v1.io); 146 | const Schema = t.exact(t.type(io)); 147 | try { 148 | ThrowReporter.report(Schema.decode(v1.instance)); 149 | return false; 150 | } catch (err) { 151 | return true; 152 | } 153 | }) 154 | ); 155 | }); 156 | it('should fail to decode instance with invalid values', () => { 157 | fc.assert( 158 | fc.property(ValidTypeDefValueArb, InvalidTypeDefValueArb, (v1, v2) => { 159 | const io = Object.assign(Object.assign({}, v1.io), v2.io); 160 | const instance = Object.assign(Object.assign({}, v1.instance), v2.instance); 161 | const Schema = t.exact(t.type(io)); 162 | try { 163 | ThrowReporter.report(Schema.decode(instance)); 164 | return false; 165 | } catch (err) { 166 | return true; 167 | } 168 | }), 169 | { verbose: true } 170 | ); 171 | }); 172 | it('should fail to decode instance with more keys', () => { 173 | fc.assert( 174 | fc.property(ValidTypeDefValueArb, fc.object(), ({ io, instance }, instance2) => { 175 | const Schema = t.exact(t.type(io)); 176 | const obj = Object.assign(Object.assign({}, instance2), instance); 177 | const foundExtra = Object.keys(instance2).find(k => !instance.hasOwnProperty(k)); 178 | fc.pre(foundExtra); 179 | try { 180 | ThrowReporter.report(Schema.decode(obj)); 181 | return false; 182 | } catch (err) { 183 | return true; 184 | } 185 | }) 186 | ); 187 | }); 188 | it('should successfully decode exact instance', () => { 189 | fc.assert( 190 | fc.property(ValidTypeDefValueArb, ({ io, instance }) => { 191 | const Schema = t.exact(t.type(io)); 192 | ThrowReporter.report(Schema.decode(instance)); 193 | }) 194 | ); 195 | }); 196 | }); 197 | describe('t.union', () => { 198 | it('should successfully decode exact instance', () => { 199 | fc.assert( 200 | fc.property(fc.array(ValidTypeDefValueArb, 1, 10), vs => { 201 | const Schema = t.union(vs.map(v => t.type(v.io))); 202 | ThrowReporter.report(Schema.decode(vs[0].instance)); 203 | }) 204 | ); 205 | }); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /test/packages/is-thirteen/README.md: -------------------------------------------------------------------------------- 1 | # is-thirteen 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/is-thirteen.svg) 4 | 5 | ## Bug discovered using property based testing (fast-check) 6 | 7 | **Issue detected:** theoretically any string containing exactly 13 times the same character should be a valid isThirteen case, unfortunately it fails when the code-point is above 0xffff \[[more](https://github.com/jezen/is-thirteen/pull/557/)\] 8 | 9 | **Code example:** `is("🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱🐱").thirteen()` returns false while `is("!!!!!!!!!!!!!").thirteen()` returns true. 10 | 11 | **Fixed at:** N.A 12 | 13 | ## Properties 14 | 15 | ### [A] should validate a string being 13 times the same character 16 | 17 | for any character `c` 18 | the string containing 13 times `c` must be valid for isThirteen 19 | -------------------------------------------------------------------------------- /test/packages/is-thirteen/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | const is = require('is-thirteen'); 4 | 5 | describe('is-thirteen', () => { 6 | xit('should validate a string being 13 times the same character', () => { 7 | fc.assert(fc.property(fc.fullUnicode(), c => is(c.repeat(13)).thirteen())); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/packages/js-yaml/README.md: -------------------------------------------------------------------------------- 1 | # js-yaml 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/js-yaml.svg) 4 | 5 | ## Bug discovered using property based testing (fast-check) 6 | 7 | **Issue detected:** enabling `!!int: binary` style when dumping negative integers produces invalid content \[[more](https://github.com/nodeca/js-yaml/pull/398)\] 8 | 9 | **Code example:** `yaml.dump({toto: -10}, {styles:{'!!int':'binary'}})` produces `toto: 0b-1010` not `toto: -0b1010` 10 | 11 | **Fixed at:** 3.11.0 12 | 13 | ## Properties 14 | 15 | ### [A] should be able to read itself 16 | 17 | for any yaml object `obj` and any options `options` 18 | loading back the yaml object from its dump should rebuild the yaml object 19 | 20 | in other words: 21 | 22 | for any yaml object `obj` and any dump options `options` 23 | fromYAML(toYAML(`obj`, `options`)) === `obj` 24 | -------------------------------------------------------------------------------- /test/packages/js-yaml/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | import * as yaml from 'js-yaml'; 4 | 5 | // Generate valid YAML instances for yaml.safeDump 6 | const yamlArbitrary = fc.object({ 7 | key: fc.fullUnicodeString(), 8 | values: [ 9 | fc.fullUnicodeString(), 10 | fc.boolean(), 11 | fc.integer(), 12 | fc.double(), 13 | fc.constantFrom(null, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY) 14 | ] 15 | }); 16 | 17 | // Generate valid options for yaml.safeDump configuration 18 | const dumpOptionsArbitrary = fc 19 | .record( 20 | { 21 | skipInvalid: fc.boolean(), 22 | sortKeys: fc.boolean(), 23 | noRefs: fc.boolean(), 24 | noCompatMode: fc.boolean(), 25 | condenseFlow: fc.boolean(), 26 | indent: fc.integer(1, 80), 27 | flowLevel: fc.integer(-1, 10), 28 | styles: fc.record( 29 | { 30 | '!!null': fc.constantFrom('lowercase', 'canonical', 'uppercase', 'camelcase'), 31 | '!!int': fc.constantFrom('decimal', 'binary', 'octal', 'hexadecimal'), 32 | '!!bool': fc.constantFrom('lowercase', 'uppercase', 'camelcase'), 33 | '!!float': fc.constantFrom('lowercase', 'uppercase', 'camelcase') 34 | }, 35 | { withDeletedKeys: true } 36 | ) 37 | }, 38 | { withDeletedKeys: true } 39 | ) 40 | .map(instance => { 41 | if (instance.condenseFlow === true && instance.flowLevel !== undefined) { 42 | instance.flowLevel = -1; 43 | } 44 | return instance; 45 | }); 46 | 47 | describe('js-yaml', () => { 48 | it('should be able to read itself', () => { 49 | fc.assert( 50 | fc.property(yamlArbitrary, dumpOptionsArbitrary, (obj, dumpOptions) => { 51 | var yamlContent = yaml.safeDump(obj, dumpOptions); 52 | assert.ok(typeof yamlContent === 'string'); 53 | assert.deepStrictEqual(yaml.safeLoad(yamlContent), obj); 54 | }) 55 | ); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/packages/left-pad/README.md: -------------------------------------------------------------------------------- 1 | # left-pad 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/left-pad.svg) 4 | 5 | ## Bug discovered using property based testing (fast-check) 6 | 7 | **Issue detected:** unicode characters outside of the BMP plan are not handled consistently \[[more](https://github.com/stevemao/left-pad/issues/58)\] 8 | 9 | **Code example:** 10 | ```js 11 | leftPad('a\u{1f431}b', 4, 'x') //=> 'a\u{1f431}b' -- in: 3 code points, out: 3 code points 12 | leftPad('abc', 4, '\u{1f431}') //=> '\u{1f431}abc' -- in: 3 code points, out: 4 code points 13 | ``` 14 | 15 | **Fixed at:** N.A 16 | 17 | ## Properties 18 | 19 | ### [A] should be able to pad simple strings with utf-16 characters 20 | 21 | for any (string `s`, positive number `additionalPad`, unicode character `c`) 22 | leftPad(`s`, lengthUnicode(`s`) + `additionalPad`, `c`)) should have been padded by `additionalPad` times `c` 23 | 24 | ### [B] should be able to pad utf-16 strings 25 | 26 | for any (string `s`, positive number `additionalPad`) 27 | the unicode length of leftPad(`s`, lengthUnicode(`s`) + `additionalPad`)) should be lengthUnicode(`s`) + `additionalPad` 28 | -------------------------------------------------------------------------------- /test/packages/left-pad/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | const leftPad = require('left-pad'); 4 | 5 | const lengthUnicode = s => [...s].length; 6 | 7 | describe('left-pad', () => { 8 | it('should be able to pad simple strings with utf-16 characters', () => { 9 | fc.assert( 10 | fc.property( 11 | fc.unicodeString(), 12 | fc.nat(100), 13 | fc.fullUnicode(), 14 | (s, additionalPad, c) => 15 | lengthUnicode(leftPad(s, lengthUnicode(s) + additionalPad, c)) === lengthUnicode(s) + additionalPad 16 | ) 17 | ); 18 | }); 19 | xit('should be able to pad utf-16 strings', () => { 20 | fc.assert( 21 | fc.property( 22 | fc.fullUnicodeString(), 23 | fc.nat(100), 24 | (s, additionalPad) => 25 | lengthUnicode(leftPad(s, lengthUnicode(s) + additionalPad)) === lengthUnicode(s) + additionalPad 26 | ) 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/packages/query-string/README.md: -------------------------------------------------------------------------------- 1 | # query-string 2 | 3 | Total downloads: ![total downloads](https://img.shields.io/npm/dt/query-string.svg) 4 | 5 | ## Bug discovered using property based testing (fast-check) 6 | 7 | **Issue detected:** enabling the `bracket` setting when exporting arrays containing null values produces an invalid output for the parser \[[more](https://github.com/sindresorhus/query-string/pull/138)\] 8 | 9 | **Code example:** 10 | ```js 11 | m.stringify({bar: ['a', null, 'b']}, {arrayFormat: 'bracket'}) //=> "bar[]=a&bar&bar[]=b" 12 | m.parse('bar[]=a&bar&bar[]=b', {arrayFormat: 'bracket'}) //=> {bar: [null, 'b']} 13 | ``` 14 | 15 | **Fixed at:** 6.1.0 16 | 17 | ## Properties 18 | 19 | ### [A] should read correctly from stringified query params 20 | 21 | for any query parameters `params` and any options `options` 22 | reading back what has been stringified should rebuild the original query parameters 23 | 24 | in other words: 25 | 26 | for any query parameters `params` and any options `options` 27 | parse(stringify(`params`, `options`)) === `params` 28 | -------------------------------------------------------------------------------- /test/packages/query-string/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | import * as m from 'query-string'; 4 | 5 | // Valid query parameters must follow: 6 | // - key can be any unicode string (not empty) 7 | // - value must be one of: 8 | // --> any unicode string 9 | // --> null 10 | // --> array containing values defined above (at least two items) 11 | const queryParamsArbitrary = fc.object({ 12 | key: fc.fullUnicodeString(1, 10), 13 | values: [ 14 | fc.fullUnicodeString(), 15 | fc.constant(null), 16 | fc.array(fc.oneof(fc.fullUnicodeString(), fc.constant(null))).filter(a => a.length >= 2) 17 | ], 18 | maxDepth: 0 19 | }); 20 | 21 | const optionsArbitrary = fc.record({ 22 | arrayFormat: fc.constantFrom('bracket', 'index', 'none'), 23 | strict: fc.boolean(), 24 | sort: fc.constant(false) 25 | }); 26 | 27 | describe('query-string', () => { 28 | it('should read correctly from stringified query params', () => { 29 | fc.assert( 30 | fc.property(queryParamsArbitrary, optionsArbitrary, (obj, opts) => 31 | assert.deepEqual(m.parse(m.stringify(obj, opts), opts), obj) 32 | ) 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/stable sort/README.md: -------------------------------------------------------------------------------- 1 | # Stable sort 2 | 3 | ## Algorithm 4 | 5 | A stable sort is a **sorting** algorithm **keeping the relative order** of two equivalent elements. If records A and B are equivalents - *according to the comparison function* - then if A appears before B in the input it should appear before in the output too. 6 | 7 | The main consequence is that stable sorts can be applied consecutively: `data.sort((a,b) => a.key1-b.key1).sort((a,b) => a.key2-b.key2)` is equivalent to `data.sort((a,b) => a.key2===b.key2 ? a.key1-b.key1 : a.key2-b.key2)`. 8 | 9 | ## Properties 10 | 11 | ### [A] should contain the same items 12 | 13 | for any array `data` 14 | `data` and sort(`data`) should contain the same items (same number of each too) 15 | 16 | ### [B] should produce ordered array 17 | 18 | for any array `data` 19 | two consecutive items of sort(`data`) should be ordered 20 | 21 | in other words: 22 | 23 | for all i 24 | sort(`data`)[i] should be inferior to sort(`data`)[i+1] 25 | 26 | ### [C] should be stable 27 | 28 | for any array `data` 29 | applying comparators consecutively should be equivalent to apply a single combined comparator -------------------------------------------------------------------------------- /test/stable sort/implem.js: -------------------------------------------------------------------------------- 1 | const mergeSort = (tab, start, end, cmp) => { 2 | if (end - start < 2) return tab; 3 | 4 | const buffer = [...tab]; 5 | const mid = Math.floor((start + end) / 2); 6 | mergeSort(buffer, start, mid, cmp); 7 | mergeSort(buffer, mid, end, cmp); 8 | 9 | let i = start; 10 | let j = mid; 11 | for (let k = start; k !== end; ++k) { 12 | if (i === mid) tab[k] = buffer[j++]; 13 | else if (j === end) tab[k] = buffer[i++]; 14 | else if (cmp(buffer[i], buffer[j]) <= 0) tab[k] = buffer[i++]; 15 | else tab[k] = buffer[j++]; 16 | } 17 | return tab; 18 | }; 19 | 20 | const stableSort = (tab, cmp) => { 21 | return mergeSort([...tab], 0, tab.length, cmp); 22 | }; 23 | 24 | const quickSort = (tab, start, end, cmp) => { 25 | if (end - start < 2) return tab; 26 | 27 | let pivot = start; 28 | for (let idx = start + 1; idx < end; ++idx) { 29 | if (cmp(tab[start], tab[idx]) > 0) { 30 | let prev = tab[++pivot]; 31 | tab[pivot] = tab[idx]; 32 | tab[idx] = prev; 33 | } 34 | } 35 | let prev = tab[pivot]; 36 | tab[pivot] = tab[start]; 37 | tab[start] = prev; 38 | 39 | quickSort(tab, start, pivot, cmp); 40 | quickSort(tab, pivot + 1, end, cmp); 41 | return tab; 42 | }; 43 | 44 | const unstableSort = (tab, cmp) => { 45 | return quickSort([...tab], 0, tab.length, cmp); 46 | }; 47 | 48 | export { stableSort, unstableSort }; 49 | -------------------------------------------------------------------------------- /test/stable sort/test.js: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as fc from 'fast-check'; 3 | import { stableSort } from './implem'; 4 | 5 | const count = (tab, element) => tab.filter(v => v === element).length; 6 | 7 | describe('stable sort', () => { 8 | it('should contain the same items', () => 9 | fc.assert( 10 | fc.property(fc.array(fc.integer()), data => { 11 | const sorted = stableSort(data.slice(0), (a, b) => a - b); 12 | assert.equal(sorted.length, data.length); 13 | for (const item of data) assert.equal(count(sorted, item), count(data, item)); 14 | }) 15 | )); 16 | it('should produce ordered array', () => 17 | fc.assert( 18 | fc.property(fc.array(fc.integer()), data => { 19 | const sorted = stableSort(data, (a, b) => a - b); 20 | for (let idx = 1; idx < sorted.length; ++idx) assert.ok(sorted[idx - 1] <= sorted[idx]); 21 | }) 22 | )); 23 | it('should be stable', () => 24 | fc.assert( 25 | fc.property(fc.array(fc.record({ key1: fc.nat(5), key2: fc.nat(5) })), data => { 26 | const singleSort = stableSort(data, (a, b) => { 27 | if (a.key2 < b.key2) return -1; 28 | else if (a.key2 > b.key2) return 1; 29 | return a.key1 - b.key1; 30 | }); 31 | const sorted = stableSort(stableSort(data, (a, b) => a.key1 - b.key1), (a, b) => a.key2 - b.key2); 32 | assert.deepStrictEqual(sorted, singleSort); 33 | }) 34 | )); 35 | }); 36 | --------------------------------------------------------------------------------