Skip to content

Instantly share code, notes, and snippets.

@cdoremus
Last active March 23, 2023 18:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cdoremus/52eb1b617ba3113ddf251904d2b05ddc to your computer and use it in GitHub Desktop.
Save cdoremus/52eb1b617ba3113ddf251904d2b05ddc to your computer and use it in GitHub Desktop.
How to use the fast-check property-based testing library with Deno
/**
* 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/std@0.90.0/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