Last active
September 28, 2024 09:03
-
-
Save cdoremus/52eb1b617ba3113ddf251904d2b05ddc to your computer and use it in GitHub Desktop.
How to use the fast-check property-based testing library with Deno
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Running the fast-check property-based testing library in the Deno | |
* JavaScript/TYpeScript runtime. | |
* See: https://github.com/dubzzz/fast-check | |
* | |
* This file contains all the 'simple' examples from the fast-check | |
* repo using Deno.test for the test functions and assertions | |
* from the Deno standard library. I also added some missing type | |
* annotations. | |
* | |
* Deno docs: https://deno.land/ | |
* | |
* To run this test file: | |
* deno test fast-check.test.ts | |
*/ | |
import fc from 'https://cdn.skypack.dev/fast-check'; | |
import * as _ from 'https://cdn.skypack.dev/lodash'; | |
import { | |
assert, | |
assertEquals, | |
} from "https://deno.land/[email protected]/testing/asserts.ts"; | |
/********************** contains() *************************************/ | |
const contains = (text: string, pattern: string): boolean => text.indexOf(pattern) >= 0; | |
// string text always contains itself | |
Deno.test('should always contain itself', () => { | |
fc.assert(fc.property(fc.string(), (text: string) => contains(text, text))); | |
}) | |
// string a + b + c always contains b, whatever the values of a, b and c | |
Deno.test('should always contain its substrings', () => { | |
fc.assert(fc.property(fc.string(), fc.string(), fc.string(), | |
(a: string, b: string, c: string) => { | |
// Alternatively: no return statement and direct usage of expect or assert | |
return contains(a+b+c, b); | |
})); | |
}); | |
/*********************** indexOf() ************************************/ | |
const indexOf = (text: string, pattern: string): number => { | |
return text.indexOf(pattern); | |
}; | |
Deno.test('should confirm b is a substring of a + b + c', () => { | |
fc.assert( | |
fc.property(fc.string(), fc.string(), fc.string(), | |
(a: string, b: string, c: string) => { | |
return indexOf(a + b + c, b) !== -1; | |
}) | |
); | |
}); | |
Deno.test('should return the starting position of the pattern within text if any', () => { | |
fc.assert( | |
fc.property(fc.string(), fc.string(), fc.string(), | |
(a: string, b: string, c: string) => { | |
const text = a + b + c; | |
const pattern = b; | |
const index = indexOf(text, pattern); | |
return index === -1 || text.substr(index, pattern.length) === pattern; | |
}) | |
); | |
}); | |
/************************** fibronacci() *********************************/ | |
function fibonacci(n: number) { | |
let a = 0n; | |
let b = 1n; | |
for (let i = 0; i !== n; ++i) { | |
const previousA = a; | |
a = b; | |
b = previousA + b; | |
} | |
return a; | |
} | |
// The complexity of the algorithm is O(n) | |
// As a consequence we limit the value of n to 1000 | |
const MaxN = 1000; | |
Deno.test('should be equal to the sum of fibonacci(n-1) and fibonacci(n-2)', () => { | |
fc.assert( | |
fc.property(fc.integer(2, MaxN), (n: number) => { | |
assert(fibonacci(n) === (fibonacci(n - 1) + fibonacci(n - 2))); | |
}) | |
); | |
}); | |
// The following properties are listed on the Wikipedia page: | |
// https://fr.wikipedia.org/wiki/Suite_de_Fibonacci#Divisibilit%C3%A9_des_nombres_de_Fibonacci | |
Deno.test('should fulfill fibonacci(p)*fibonacci(q+1)+fibonacci(p-1)*fibonacci(q) = fibonacci(p+q)', () => { | |
fc.assert( | |
fc.property(fc.integer(1, MaxN), fc.integer(0, MaxN), (p: number, q: number) => { | |
assertEquals(fibonacci(p + q), fibonacci(p) * fibonacci(q + 1) + fibonacci(p - 1) * fibonacci(q)); | |
}) | |
); | |
}); | |
Deno.test('should fulfill fibonacci(2p-1) = fibo²(p-1)+fibo²(p)', () => { | |
// Special case of the property above | |
fc.assert( | |
fc.property(fc.integer(1, MaxN), (p: number) => { | |
assertEquals(fibonacci(2 * p - 1), fibonacci(p - 1) * fibonacci(p - 1) + fibonacci(p) * fibonacci(p)); | |
}) | |
); | |
}); | |
Deno.test('should fulfill Catalan identity', () => { | |
fc.assert( | |
fc.property(fc.integer(0, MaxN), fc.integer(0, MaxN), (a: number, b: number) => { | |
const [p, q] = a < b ? [b, a] : [a, b]; | |
const sign = (p - q) % 2 === 0 ? 1n : -1n; // (-1)^(p-q) | |
assertEquals(fibonacci(p) * fibonacci(p) - fibonacci(p - q) * fibonacci(p + q), sign * fibonacci(q) * fibonacci(q)); | |
}) | |
); | |
}); | |
Deno.test('should fulfill Cassini identity', () => { | |
fc.assert( | |
fc.property(fc.integer(1, MaxN), fc.integer(0, MaxN), (p: number) => { | |
const sign = p % 2 === 0 ? 1n : -1n; // (-1)^p | |
assert(fibonacci(p + 1) * fibonacci(p - 1) - fibonacci(p) * fibonacci(p) === sign); | |
}) | |
); | |
}); | |
Deno.test('should fibonacci(nk) divisible by fibonacci(n)', () => { | |
fc.assert( | |
fc.property(fc.integer(1, MaxN), fc.integer(0, 100), (n: number, k: number) => { | |
assert(fibonacci(n * k) % fibonacci(n) === 0n); | |
}) | |
); | |
}); | |
Deno.test('should fulfill gcd(fibonacci(a), fibonacci(b)) = fibonacci(gcd(a,b))', () => { | |
fc.assert( | |
fc.property(fc.integer(1, MaxN), fc.integer(1, MaxN), (a: number, b: number) => { | |
const gcd = <T extends bigint | number>(a: T, b: T, zero: T): T => { | |
a = a < zero ? (-a as T) : a; | |
b = b < zero ? (-b as T) : b; | |
if (b > a) { | |
const temp = a; | |
a = b; | |
b = temp; | |
} | |
// eslint-disable-next-line no-constant-condition | |
while (true) { | |
if (b == zero) return a; | |
a = (a % b) as T; | |
if (a == zero) return b; | |
b = (b % a) as T; | |
} | |
}; | |
assert(gcd(fibonacci(a), fibonacci(b), 0n) === fibonacci(gcd(a, b, 0))); | |
}) | |
); | |
}); | |
/********************* sort() **************************************/ | |
const sortInternal = <T>(tab: T[], start: number, end: number, cmp: (a: T, b: T) => boolean): T[] => { | |
if (end - start < 2) return tab; | |
let pivot = start; | |
for (let idx = start + 1; idx < end; ++idx) { | |
if (!cmp(tab[start], tab[idx])) { | |
let prev = tab[++pivot]; | |
tab[pivot] = tab[idx]; | |
tab[idx] = prev; | |
} | |
} | |
let prev = tab[pivot]; | |
tab[pivot] = tab[start]; | |
tab[start] = prev; | |
sortInternal(tab, start, pivot, cmp); | |
sortInternal(tab, pivot + 1, end, cmp); | |
return tab; | |
}; | |
export const sort = <T>(tab: T[], compare?: (a: T, b: T) => boolean): T[] => { | |
return sortInternal([...tab], 0, tab.length, compare || ((a, b) => a < b)); | |
}; | |
Deno.test('should have the same length as source', () => { | |
fc.assert( | |
fc.property(fc.array(fc.integer()), (data: Array<number>) => { | |
assert(sort(data).length === data.length); | |
}) | |
); | |
}); | |
Deno.test('should have exactly the same number of occurences as source for each item', () => { | |
fc.assert( | |
fc.property(fc.array(fc.integer()), (data: Array<number>) => { | |
const sorted = sort(data); | |
assertEquals(_.groupBy(sorted), _.groupBy(data)); | |
}) | |
); | |
}); | |
Deno.test('should produce an ordered array', () => { | |
fc.assert( | |
fc.property(fc.array(fc.integer()), (data: Array<number>) => { | |
const sorted = sort(data); | |
for (let idx = 1; idx < sorted.length; ++idx) { | |
assert(sorted[idx - 1] <= sorted[idx]); | |
} | |
}) | |
); | |
}); | |
Deno.test('should produce an ordered array with respect to a custom compare function', () => { | |
fc.assert( | |
fc.property(fc.array(fc.integer()), fc.compareBooleanFunc(), (data: Array<number>, | |
compare: (n1: number, n2: number) => boolean) => { | |
const sorted = sort(data, compare); | |
for (let idx = 1; idx < sorted.length; ++idx) { | |
// compare(sorted[idx], sorted[idx - 1]): | |
// = true : sorted[idx - 1] > sorted[idx] | |
// = false: sorted[idx - 1] <= sorted[idx] | |
assert(compare(sorted[idx], sorted[idx - 1]) === false); | |
// Meaning of compare: | |
// a = b means in terms of ordering a and b are equivalent | |
// a < b means in terms of ordering a comes before b | |
// One important property is: a < b and b < c implies a < c | |
} | |
}) | |
); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment