tslaws 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators.js +2 -1
- package/dist/sequence.d.ts +30 -0
- package/dist/sequence.js +131 -0
- package/package.json +6 -6
package/dist/generators.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as fc from 'fast-check';
|
|
5
5
|
import ts from 'typescript';
|
|
6
6
|
import { arbitraryRegistry } from "./api.js";
|
|
7
|
+
import { sequence } from "./sequence.js";
|
|
7
8
|
export class UnsupportedType extends Error {
|
|
8
9
|
}
|
|
9
10
|
const numbers = fc.oneof({ weight: 4, arbitrary: fc.integer({ min: -8, max: 8 }) }, { weight: 3, arbitrary: fc.integer({ min: -1000, max: 1000 }) }, { weight: 1, arbitrary: fc.integer() }, { weight: 2, arbitrary: fc.double({ noNaN: true, noDefaultInfinity: true }) });
|
|
@@ -138,7 +139,7 @@ export class GeneratorFactory {
|
|
|
138
139
|
return fc.constant(/x/);
|
|
139
140
|
if (checker.isArrayType(type)) {
|
|
140
141
|
const element = checker.getTypeArguments(type)[0];
|
|
141
|
-
return
|
|
142
|
+
return sequence(this.of(element, depth + 1), { maxLength: 8 });
|
|
142
143
|
}
|
|
143
144
|
if (checker.isTupleType(type)) {
|
|
144
145
|
const target = type.target;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
/** Arrays of up to this many elements are shrunk by trying every subset of them: 2^12 - 1 = 4095 candidates at most. */
|
|
3
|
+
export declare const EXHAUSTIVE_LENGTH = 12;
|
|
4
|
+
/** The most candidates enumerated for one longer array, whatever its length. */
|
|
5
|
+
export declare const CANDIDATE_CAP = 50000;
|
|
6
|
+
/** The longest array whose one- and two-span removals all fit in `CANDIDATE_CAP`: C(34, 4) + 33 * 34 / 2 = 46937. */
|
|
7
|
+
export declare const TWO_SPAN_LENGTH = 33;
|
|
8
|
+
/**
|
|
9
|
+
* The smaller arrays to try in place of `value`, one at a time (nothing is built before it is asked for), without
|
|
10
|
+
* repeating one and never shorter than `minLength`. `fc.array` removes a run only when its length is one its integer
|
|
11
|
+
* shrinker proposes (it halves, so it skips lengths such as 2 of 3 elements), and never two separate runs, so on its own it
|
|
12
|
+
* can stop at a failing array that is longer than the shortest, such as `inc, inc, dec, reset` for `inc, reset`, and
|
|
13
|
+
* whether it does depends on the seed.
|
|
14
|
+
*
|
|
15
|
+
* - Up to `EXHAUSTIVE_LENGTH` elements: every subset of them, fewest remaining first. fast-check shrinks to a fixpoint, so
|
|
16
|
+
* the result is a shortest failing subsequence of the array it started from.
|
|
17
|
+
* - Longer: every run removed, then every pair of runs that are apart, at most `CANDIDATE_CAP` candidates. The result is
|
|
18
|
+
* locally minimal under removing up to two runs when all of them fit, which is the case up to `TWO_SPAN_LENGTH` elements.
|
|
19
|
+
* Beyond that the cap cuts the search short, and repeats are no longer filtered (a repeat costs one more property run).
|
|
20
|
+
*/
|
|
21
|
+
export declare function withoutElements<T>(value: readonly T[], minLength: number): Generator<T[]>;
|
|
22
|
+
/**
|
|
23
|
+
* `fc.array(item, { minLength, maxLength })` that also shrinks by removing elements (see `withoutElements` for what that
|
|
24
|
+
* guarantees). The values generated for a seed are those of `fc.array`; only the search for a smaller failing array differs,
|
|
25
|
+
* and it is deterministic.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sequence<T>(item: fc.Arbitrary<T>, options: {
|
|
28
|
+
minLength?: number;
|
|
29
|
+
maxLength: number;
|
|
30
|
+
}): fc.Arbitrary<T[]>;
|
package/dist/sequence.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
/** Arrays of up to this many elements are shrunk by trying every subset of them: 2^12 - 1 = 4095 candidates at most. */
|
|
3
|
+
export const EXHAUSTIVE_LENGTH = 12;
|
|
4
|
+
/** The most candidates enumerated for one longer array, whatever its length. */
|
|
5
|
+
export const CANDIDATE_CAP = 50_000;
|
|
6
|
+
/** The longest array whose one- and two-span removals all fit in `CANDIDATE_CAP`: C(34, 4) + 33 * 34 / 2 = 46937. */
|
|
7
|
+
export const TWO_SPAN_LENGTH = 33;
|
|
8
|
+
/** Combinations of `size` of the indexes `0..length - 1`, in lexicographic order, one at a time. */
|
|
9
|
+
function* combinations(length, size) {
|
|
10
|
+
const at = Array.from({ length: size }, (_, i) => i);
|
|
11
|
+
for (;;) {
|
|
12
|
+
yield at.slice();
|
|
13
|
+
let i = size - 1;
|
|
14
|
+
while (i >= 0 && at[i] === length - size + i)
|
|
15
|
+
i--;
|
|
16
|
+
if (i < 0)
|
|
17
|
+
return;
|
|
18
|
+
at[i]++;
|
|
19
|
+
for (let j = i + 1; j < size; j++)
|
|
20
|
+
at[j] = at[j - 1] + 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** A key that is equal for two arrays exactly when they hold the same elements (`Object.is`, objects by identity). */
|
|
24
|
+
function keyOf(elements, ids) {
|
|
25
|
+
return elements
|
|
26
|
+
.map((element) => {
|
|
27
|
+
if (Object.is(element, -0))
|
|
28
|
+
return 'z';
|
|
29
|
+
let id = ids.get(element);
|
|
30
|
+
if (id === undefined) {
|
|
31
|
+
id = ids.size;
|
|
32
|
+
ids.set(element, id);
|
|
33
|
+
}
|
|
34
|
+
return id;
|
|
35
|
+
})
|
|
36
|
+
.join(',');
|
|
37
|
+
}
|
|
38
|
+
function* spans(value, minLength) {
|
|
39
|
+
const n = value.length;
|
|
40
|
+
let enumerated = 0;
|
|
41
|
+
// Every run of consecutive elements, shortest runs first.
|
|
42
|
+
for (let size = 1; size <= n - minLength; size++)
|
|
43
|
+
for (let start = 0; start + size <= n; start++) {
|
|
44
|
+
if (enumerated++ >= CANDIDATE_CAP)
|
|
45
|
+
return;
|
|
46
|
+
yield [...value.slice(0, start), ...value.slice(start + size)];
|
|
47
|
+
}
|
|
48
|
+
// Every two runs that are apart from each other: [a, b) and [c, d) with b < c.
|
|
49
|
+
for (let a = 0; a < n; a++)
|
|
50
|
+
for (let b = a + 1; b < n; b++)
|
|
51
|
+
for (let c = b + 1; c < n; c++)
|
|
52
|
+
for (let d = c + 1; d <= n; d++) {
|
|
53
|
+
if (n - (b - a) - (d - c) < minLength)
|
|
54
|
+
continue;
|
|
55
|
+
if (enumerated++ >= CANDIDATE_CAP)
|
|
56
|
+
return;
|
|
57
|
+
yield [...value.slice(0, a), ...value.slice(b, c), ...value.slice(d)];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The smaller arrays to try in place of `value`, one at a time (nothing is built before it is asked for), without
|
|
62
|
+
* repeating one and never shorter than `minLength`. `fc.array` removes a run only when its length is one its integer
|
|
63
|
+
* shrinker proposes (it halves, so it skips lengths such as 2 of 3 elements), and never two separate runs, so on its own it
|
|
64
|
+
* can stop at a failing array that is longer than the shortest, such as `inc, inc, dec, reset` for `inc, reset`, and
|
|
65
|
+
* whether it does depends on the seed.
|
|
66
|
+
*
|
|
67
|
+
* - Up to `EXHAUSTIVE_LENGTH` elements: every subset of them, fewest remaining first. fast-check shrinks to a fixpoint, so
|
|
68
|
+
* the result is a shortest failing subsequence of the array it started from.
|
|
69
|
+
* - Longer: every run removed, then every pair of runs that are apart, at most `CANDIDATE_CAP` candidates. The result is
|
|
70
|
+
* locally minimal under removing up to two runs when all of them fit, which is the case up to `TWO_SPAN_LENGTH` elements.
|
|
71
|
+
* Beyond that the cap cuts the search short, and repeats are no longer filtered (a repeat costs one more property run).
|
|
72
|
+
*/
|
|
73
|
+
export function* withoutElements(value, minLength) {
|
|
74
|
+
const n = value.length;
|
|
75
|
+
const ids = new Map();
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
const fresh = (candidate) => {
|
|
78
|
+
if (n > TWO_SPAN_LENGTH)
|
|
79
|
+
return true;
|
|
80
|
+
const key = keyOf(candidate, ids);
|
|
81
|
+
if (seen.has(key))
|
|
82
|
+
return false;
|
|
83
|
+
seen.add(key);
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
if (n <= EXHAUSTIVE_LENGTH) {
|
|
87
|
+
for (let size = minLength; size < n; size++)
|
|
88
|
+
for (const kept of combinations(n, size)) {
|
|
89
|
+
const candidate = kept.map((i) => value[i]);
|
|
90
|
+
if (fresh(candidate))
|
|
91
|
+
yield candidate;
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const candidate of spans(value, minLength))
|
|
96
|
+
if (fresh(candidate))
|
|
97
|
+
yield candidate;
|
|
98
|
+
}
|
|
99
|
+
class SequenceArbitrary extends fc.Arbitrary {
|
|
100
|
+
inner;
|
|
101
|
+
minLength;
|
|
102
|
+
constructor(inner, minLength) {
|
|
103
|
+
super();
|
|
104
|
+
this.inner = inner;
|
|
105
|
+
this.minLength = minLength;
|
|
106
|
+
}
|
|
107
|
+
generate(mrng, biasFactor) {
|
|
108
|
+
const made = this.inner.generate(mrng, biasFactor);
|
|
109
|
+
return new fc.Value(made.value, { inner: made.context });
|
|
110
|
+
}
|
|
111
|
+
canShrinkWithoutContext(value) {
|
|
112
|
+
return this.inner.canShrinkWithoutContext(value);
|
|
113
|
+
}
|
|
114
|
+
shrink(value, context) {
|
|
115
|
+
const origin = context;
|
|
116
|
+
const own = this.inner
|
|
117
|
+
.shrink(value, origin?.inner)
|
|
118
|
+
.map((shrunk) => new fc.Value(shrunk.value, { inner: shrunk.context }));
|
|
119
|
+
const fewer = new fc.Stream(withoutElements(value, this.minLength)).map((shorter) => new fc.Value(shorter, { inner: undefined }));
|
|
120
|
+
return own.join(fewer);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* `fc.array(item, { minLength, maxLength })` that also shrinks by removing elements (see `withoutElements` for what that
|
|
125
|
+
* guarantees). The values generated for a seed are those of `fc.array`; only the search for a smaller failing array differs,
|
|
126
|
+
* and it is deterministic.
|
|
127
|
+
*/
|
|
128
|
+
export function sequence(item, options) {
|
|
129
|
+
const minLength = options.minLength ?? 0;
|
|
130
|
+
return new SequenceArbitrary(fc.array(item, { minLength, maxLength: options.maxLength }), minLength);
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tslaws",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Runs and checks `law` declarations compiled by tsc-laws: property tests, SMT proofs, tamper lock.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tslaws": "bin/tslaws.js"
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
"optionalDependencies": {
|
|
74
|
-
"tslaws-compiler-darwin-arm64": "0.6.
|
|
75
|
-
"tslaws-compiler-darwin-x64": "0.6.
|
|
76
|
-
"tslaws-compiler-linux-arm64-gnu": "0.6.
|
|
77
|
-
"tslaws-compiler-linux-x64-gnu": "0.6.
|
|
78
|
-
"tslaws-compiler-win32-x64-msvc": "0.6.
|
|
74
|
+
"tslaws-compiler-darwin-arm64": "0.6.3",
|
|
75
|
+
"tslaws-compiler-darwin-x64": "0.6.3",
|
|
76
|
+
"tslaws-compiler-linux-arm64-gnu": "0.6.3",
|
|
77
|
+
"tslaws-compiler-linux-x64-gnu": "0.6.3",
|
|
78
|
+
"tslaws-compiler-win32-x64-msvc": "0.6.3"
|
|
79
79
|
},
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|