mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
426 lines
12 KiB
TypeScript
426 lines
12 KiB
TypeScript
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
|
|
/**
|
|
* Tests that run the fast-check property-based testing library in the Deno
|
|
* runtime.
|
|
*
|
|
* See: https://github.com/dubzzz/fast-check
|
|
*
|
|
* This file contains all the 'simple' examples from the fast-check
|
|
* repo (001-simple folder) using Deno.test for the test functions and
|
|
* assertions from the Deno standard library. Missing type annotations
|
|
* were also added to the original fast-check examples.
|
|
*
|
|
* Since the nested testing API is used, the tests need to be run with the
|
|
* unstable flag as indicated in the command:
|
|
*
|
|
* ```ignore
|
|
* $ deno test --unstable ./testing/fast_check_example.ts
|
|
* ```
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import fc from "https://cdn.skypack.dev/fast-check";
|
|
import { groupBy } from "../collections/group_by.ts";
|
|
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;
|
|
|
|
Deno.test("Can use fast-check to property test contains function", async (t) => {
|
|
// string text always contains itself
|
|
await t.step("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
|
|
await t.step("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);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
/*********************** decomposePrime() ************************************/
|
|
export const decomposePrime = (n: number): number[] => {
|
|
// Quick implementation: the maximal number supported is 2**31-1
|
|
let done = false;
|
|
const factors: number[] = [];
|
|
while (!done) {
|
|
done = true;
|
|
const stop = Math.sqrt(n);
|
|
for (let i = 2; i <= stop; ++i) {
|
|
if (n % i === 0) {
|
|
factors.push(i);
|
|
n = Math.floor(n / i);
|
|
done = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return [...factors, n];
|
|
};
|
|
|
|
// Above this number a*b can be over 2**31-1
|
|
const MAX_INPUT = 65536;
|
|
|
|
Deno.test("Can use fast-check to property test decomposePrime function", async (t) => {
|
|
await t.step(
|
|
"should produce an array such that the product equals the input",
|
|
() => {
|
|
fc.assert(
|
|
fc.property(fc.nat(MAX_INPUT), (n: number) => {
|
|
const factors = decomposePrime(n);
|
|
const productOfFactors = factors.reduce((a, b) => a * b, 1);
|
|
return productOfFactors === n;
|
|
}),
|
|
);
|
|
},
|
|
);
|
|
await t.step("should be able to decompose a product of two numbers", () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer(2, MAX_INPUT),
|
|
fc.integer(2, MAX_INPUT),
|
|
(a: number, b: number) => {
|
|
const n = a * b;
|
|
const factors = decomposePrime(n);
|
|
return factors.length >= 2;
|
|
},
|
|
),
|
|
);
|
|
});
|
|
await t.step(
|
|
"should compute the same factors as to the concatenation of the one of a and b for a times b",
|
|
() => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer(2, MAX_INPUT),
|
|
fc.integer(2, MAX_INPUT),
|
|
(a: number, b: number) => {
|
|
const factorsA = decomposePrime(a);
|
|
const factorsB = decomposePrime(b);
|
|
const factorsAB = decomposePrime(a * b);
|
|
const reorder = (arr: number[]) => [...arr].sort((a, b) => a - b);
|
|
assertEquals(
|
|
reorder(factorsAB),
|
|
reorder([...factorsA, ...factorsB]),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
/*********************** indexOf() ************************************/
|
|
const indexOf = (text: string, pattern: string): number => {
|
|
return text.indexOf(pattern);
|
|
};
|
|
|
|
Deno.test("Can use fast-check to property test indexOf function", async (t) => {
|
|
await t.step("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;
|
|
},
|
|
),
|
|
);
|
|
});
|
|
await t.step(
|
|
"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): bigint {
|
|
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("Can use fast-check to property test fibronacci function", async (t) => {
|
|
await t.step(
|
|
"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
|
|
await t.step(
|
|
"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),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
await t.step("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),
|
|
);
|
|
}),
|
|
);
|
|
});
|
|
await t.step("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),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
await t.step("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,
|
|
);
|
|
}),
|
|
);
|
|
});
|
|
await t.step("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);
|
|
},
|
|
),
|
|
);
|
|
});
|
|
await t.step(
|
|
"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;
|
|
}
|
|
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,
|
|
// cmp: (a: T, b: T) => string,
|
|
): T[] => {
|
|
if (end - start < 2) return tab;
|
|
|
|
let pivot = start;
|
|
for (let idx = start + 1; idx < end; ++idx) {
|
|
if (!cmp(tab[start], tab[idx])) {
|
|
const prev = tab[++pivot];
|
|
tab[pivot] = tab[idx];
|
|
tab[idx] = prev;
|
|
}
|
|
}
|
|
const prev = tab[pivot];
|
|
tab[pivot] = tab[start];
|
|
tab[start] = prev;
|
|
|
|
sortInternal(tab, start, pivot, cmp);
|
|
sortInternal(tab, pivot + 1, end, cmp);
|
|
return tab;
|
|
};
|
|
|
|
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("Can use fast-check to property test sort function", async (t) => {
|
|
await t.step("should have the same length as source", () => {
|
|
fc.assert(
|
|
fc.property(fc.array(fc.integer()), (data: Array<number>) => {
|
|
assert(sort(data).length === data.length);
|
|
}),
|
|
);
|
|
});
|
|
await t.step(
|
|
"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);
|
|
const keyCreatorFn = (item: number) => item.toString();
|
|
assertEquals(
|
|
groupBy(data, keyCreatorFn),
|
|
groupBy(sorted, keyCreatorFn),
|
|
);
|
|
}),
|
|
);
|
|
},
|
|
);
|
|
await t.step("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]);
|
|
}
|
|
}),
|
|
);
|
|
});
|
|
await t.step(
|
|
"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
|
|
}
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
/**** Additional tests not in the fast-check github examples ****/
|
|
function lowercaseString(text: string): string {
|
|
return text.toLowerCase();
|
|
}
|
|
|
|
Deno.test("Can use fast-check to property test lowercaseString function", async (t) => {
|
|
await t.step("string after lower case should be of same length", () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.string(),
|
|
(text: string) => text.length === lowercaseString(text).length,
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
function add(a: number, b: number): number {
|
|
return a + b;
|
|
}
|
|
|
|
function absoluteAddition(a: number, b: number): number {
|
|
return Math.abs(a) + Math.abs(b);
|
|
}
|
|
|
|
Deno.test("Can use fast-check to property test add and absoluteAddition functions", async (t) => {
|
|
await t.step(
|
|
"absolute addition should always be greater or equal to regular addition",
|
|
() => {
|
|
fc.assert(
|
|
fc.property(
|
|
fc.integer(-99, 99),
|
|
fc.integer(-99, 99),
|
|
(a: number, b: number) => add(a, b) <= absoluteAddition(a, b),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
});
|