From fcb35d63a14fa9f07c5b4583d561ab14cc5ec75f Mon Sep 17 00:00:00 2001 From: shape-warrior-t Date: Tue, 9 Jul 2024 22:23:30 -0600 Subject: [PATCH] Improve tests for Subsequence Indices Original tests: - When `arr` and `sub` are arbitrary, check that if `subsequenceIndices(arr, sub)` returns non-null, then the returned indices are correct. - When `sub` is guaranteed to be a subsequence of `arr`, check that `subsequenceIndices(arr, sub)` never returns null. New tests: - When `sub` is guaranteed to be a subsequence of `arr`, check `subsequenceIndices(arr, sub)` returns non-null, correct indices. - When `arr` and `sub` are arbitrary, check that if `subsequenceIndices(arr, sub)` returns non-null, then the returned indices are correct. Adding the indices correctness check to both tests allows us to use the guaranteed-subsequence test as the primary test for the success case (non-null return), with the arbitrary-inputs test primarily accounting for the failure case now. This allows us to vary the inputs to the arbitrary-inputs test more, since we are no longer depending on it for coverage of the success case. Also, the inputs to the guaranteed-subsequence test are now decoupled from the inputs to the arbitrary-inputs test, and are even more varied. --- .../code/subsequence-indices.test.ts | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/typescript-challenges/code/subsequence-indices.test.ts b/typescript-challenges/code/subsequence-indices.test.ts index 0135abd..a2c9a5c 100644 --- a/typescript-challenges/code/subsequence-indices.test.ts +++ b/typescript-challenges/code/subsequence-indices.test.ts @@ -2,13 +2,14 @@ * Tests for Subsequence Indices, ensuring that the output of * `subsequenceIndices(arr, sub)` is always correct. * - * Properties tested: - * a. If `subsequenceIndices(arr, sub)` is a list of indices `[i_0, i_1, ...]`, - * then the list is strictly ascending - * and `[arr[i_0], arr[i_1], ...]` represents the same list as `sub`. - * b. If `subsequenceIndices(arr, sub)` is null, then `sub` is not a subsequence of `arr` -- - * equivalently, if `sub` is a subsequence of `arr`, - * then `subsequenceIndices(arr, sub)` is not null. + * If `subsequenceIndices(arr, sub)` returns a list of indices `[i_0, i_1, ...]`, + * we can always verify correctness by checking that the list is strictly ascending + * and that `[arr[i_0], arr[i_1], ...]` represents the same list as `sub`. + * Based on that, we have the following tests: + * - When `sub` is guaranteed to be a subsequence of `arr`, + * `subsequenceIndices(arr, sub)` returns the correct list of indices and never returns null. + * - When `arr` and `sub` are arbitrary, + * `subsequenceIndices(arr, sub)` either returns the correct list of indices or returns null. * * NOTE: generating indices `[i_0, i_1, ...]` and testing that * `subsequenceIndices(arr, [arr[i_0], arr[i_1], ...])` is the same as `[i_0, i_1, ...]` @@ -23,29 +24,15 @@ import fc from 'fast-check'; import { pairwise } from 'itertools'; import subsequenceIndices from './subsequence-indices'; -/** - * Arbitrary for list inputs to `subsequenceIndices`. - * - * Elements are taken from a set of size 2 in order to maximize the chances that, - * given randomly generated `sub` and `arr`, - * `sub` is a subsequence of `arr` and also has a reasonably high length. - */ -function list(maxLength: number): fc.Arbitrary { - return fc.array(fc.constantFrom('a', 'b'), { maxLength }); +function expectIndicesAreCorrect( + indices: number[], + arr: T[], + sub: T[], +): void { + expectStrictlyAscending(indices); + expect(indices.map((i) => arr[i])).toStrictEqual(sub); } -test('a. list returns are valid', () => { - fc.assert( - fc.property(list(10), list(5), (arr, sub) => { - const indices = subsequenceIndices(arr, sub); - if (indices === null) return; - expectStrictlyAscending(indices); - const subFromIndices = indices.map((i) => arr[i]); - expect(subFromIndices).toStrictEqual(sub); - }), - ); -}); - function expectStrictlyAscending(arr: number[]): void { for (const [a, b] of pairwise(arr)) { expect(a).toBeLessThan(b); @@ -53,14 +40,37 @@ function expectStrictlyAscending(arr: number[]): void { } /** Arbitrary for a list together with a random subsequence of that list. */ -const listAndSubsequence: fc.Arbitrary<[string[], string[]]> = list(10).chain( - (arr) => fc.subarray(arr).map((sub) => [arr, sub]), -); +const listAndSubsequence: fc.Arbitrary<[number[], number[]]> = fc + .array(fc.integer({ min: 0, max: 9 })) + .chain((arr) => fc.subarray(arr).map((sub) => [arr, sub])); -test('b. returns non-null for subsequences', () => { +test('`sub` subsequence of `arr`', () => { fc.assert( fc.property(listAndSubsequence, ([arr, sub]) => { - expect(subsequenceIndices(arr, sub)).not.toBeNull(); + const indices = subsequenceIndices(arr, sub); + expect(indices).not.toBeNull(); + expectIndicesAreCorrect(indices!, arr, sub); + }), + ); +}); + +/** + * Arbitrary for list inputs to `subsequenceIndices`. + * + * Elements are taken from a set of size 3 in order to raise the chances of + * `sub` being a subsequence or near-subsequence of `arr`. + */ +function list(maxLength: number): fc.Arbitrary { + return fc.array(fc.constantFrom('a', 'b', 'c'), { maxLength }); +} + +test('`arr` and `sub` arbitrary', () => { + fc.assert( + fc.property(list(10), list(5), (arr, sub) => { + const indices = subsequenceIndices(arr, sub); + if (indices !== null) { + expectIndicesAreCorrect(indices, arr, sub); + } }), ); });