tslaws 0.6.1 → 0.6.4

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.
@@ -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 fc.array(this.of(element, depth + 1), { maxLength: 8 });
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[]>;
@@ -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.1",
3
+ "version": "0.6.4",
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.1",
75
- "tslaws-compiler-darwin-x64": "0.6.1",
76
- "tslaws-compiler-linux-arm64-gnu": "0.6.1",
77
- "tslaws-compiler-linux-x64-gnu": "0.6.1",
78
- "tslaws-compiler-win32-x64-msvc": "0.6.1"
74
+ "tslaws-compiler-darwin-arm64": "0.6.4",
75
+ "tslaws-compiler-darwin-x64": "0.6.4",
76
+ "tslaws-compiler-linux-arm64-gnu": "0.6.4",
77
+ "tslaws-compiler-linux-x64-gnu": "0.6.4",
78
+ "tslaws-compiler-win32-x64-msvc": "0.6.4"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public"