vimonkey 0.2.0 → 0.2.1
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/package.json +6 -4
- package/dist/chaos/index.d.ts +0 -81
- package/dist/chaos/index.d.ts.map +0 -1
- package/dist/chaos/index.js +0 -148
- package/dist/env.d.ts +0 -38
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -52
- package/dist/fuzz/context.d.ts +0 -36
- package/dist/fuzz/context.d.ts.map +0 -1
- package/dist/fuzz/context.js +0 -43
- package/dist/fuzz/gen.d.ts +0 -70
- package/dist/fuzz/gen.d.ts.map +0 -1
- package/dist/fuzz/gen.js +0 -113
- package/dist/fuzz/index.d.ts +0 -57
- package/dist/fuzz/index.d.ts.map +0 -1
- package/dist/fuzz/index.js +0 -62
- package/dist/fuzz/regression.d.ts +0 -47
- package/dist/fuzz/regression.d.ts.map +0 -1
- package/dist/fuzz/regression.js +0 -91
- package/dist/fuzz/shrink.d.ts +0 -41
- package/dist/fuzz/shrink.d.ts.map +0 -1
- package/dist/fuzz/shrink.js +0 -80
- package/dist/fuzz/test-fuzz.d.ts +0 -55
- package/dist/fuzz/test-fuzz.d.ts.map +0 -1
- package/dist/fuzz/test-fuzz.js +0 -178
- package/dist/index.d.ts +0 -12
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -13
- package/dist/plugin.d.ts +0 -74
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -41
- package/dist/random.d.ts +0 -66
- package/dist/random.d.ts.map +0 -1
- package/dist/random.js +0 -137
package/dist/fuzz/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fuzz/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAGH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AAGrE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACtE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAGjG,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,WAAW,GACjB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAGvG,OAAO,EACL,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,eAAe,EACf,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,YAAY,GAClB,MAAM,cAAc,CAAA"}
|
package/dist/fuzz/index.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fuzz testing API
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { test, gen, take, createSeededRandom } from 'vimonkey/fuzz'
|
|
7
|
-
*
|
|
8
|
-
* // Simple random from array
|
|
9
|
-
* test('navigation', async () => {
|
|
10
|
-
* const handle = await run(<Board />, { cols: 80, rows: 24 })
|
|
11
|
-
* for await (const key of take(gen(['j','k','h','l']), 100)) {
|
|
12
|
-
* await handle.press(key)
|
|
13
|
-
* expect(handle.locator('[data-cursor]').count()).toBe(1)
|
|
14
|
-
* }
|
|
15
|
-
* })
|
|
16
|
-
*
|
|
17
|
-
* // Weighted random
|
|
18
|
-
* test('weighted', async () => {
|
|
19
|
-
* for await (const key of take(gen([[40,'j'], [40,'k'], [20,'Enter']]), 100)) {
|
|
20
|
-
* await handle.press(key)
|
|
21
|
-
* }
|
|
22
|
-
* })
|
|
23
|
-
*
|
|
24
|
-
* // Stateful with closure
|
|
25
|
-
* test('stateful', async () => {
|
|
26
|
-
* const handle = await app.run(<Board />)
|
|
27
|
-
* const random = createSeededRandom(Date.now())
|
|
28
|
-
*
|
|
29
|
-
* const keys = async function*() {
|
|
30
|
-
* while (true) {
|
|
31
|
-
* const state = handle.store.getState()
|
|
32
|
-
* yield state.cursor === 0 ? random.pick(['j','l']) : random.pick(['j','k','h','l'])
|
|
33
|
-
* }
|
|
34
|
-
* }
|
|
35
|
-
*
|
|
36
|
-
* for await (const key of take(keys(), 100)) {
|
|
37
|
-
* await handle.press(key)
|
|
38
|
-
* }
|
|
39
|
-
* })
|
|
40
|
-
*
|
|
41
|
-
* // With auto-tracking and shrinking
|
|
42
|
-
* test.fuzz('cursor invariants', async () => {
|
|
43
|
-
* for await (const key of take(gen(['j','k']), 100)) {
|
|
44
|
-
* await handle.press(key)
|
|
45
|
-
* expect(...) // On failure: shrinks, saves to __fuzz_cases__/
|
|
46
|
-
* }
|
|
47
|
-
* })
|
|
48
|
-
* ```
|
|
49
|
-
*/
|
|
50
|
-
// Core API
|
|
51
|
-
export { gen, take } from "./gen.js";
|
|
52
|
-
// test.fuzz wrapper
|
|
53
|
-
export { test, FuzzError } from "./test-fuzz.js";
|
|
54
|
-
export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "./test-fuzz.js";
|
|
55
|
-
// Context (for advanced use)
|
|
56
|
-
export { fuzzContext, getFuzzContext, isInFuzzContext, createFuzzContext, createReplayContext, } from "./context.js";
|
|
57
|
-
// Shrinking (for advanced use)
|
|
58
|
-
export { shrinkSequence, formatShrinkResult } from "./shrink.js";
|
|
59
|
-
// Regression (for advanced use)
|
|
60
|
-
export { saveCase, loadCases, loadCasesForTest, deleteCase, clearCases, getFuzzCasesDir, } from "./regression.js";
|
|
61
|
-
// Re-export random utilities
|
|
62
|
-
export { createSeededRandom, weightedPickFromTuples, parseSeed, parseRepeats, deriveSeeds, } from "../random.js";
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression testing - save and load failing sequences
|
|
3
|
-
*
|
|
4
|
-
* Failing fuzz test sequences are saved to __fuzz_cases__/ directory
|
|
5
|
-
* like Jest/Vitest snapshots. On subsequent runs, saved sequences
|
|
6
|
-
* are replayed first to ensure bugs don't regress.
|
|
7
|
-
*/
|
|
8
|
-
/** Failure case saved to disk */
|
|
9
|
-
export interface SavedCase {
|
|
10
|
-
/** Test name */
|
|
11
|
-
test: string;
|
|
12
|
-
/** Seed used for generation */
|
|
13
|
-
seed: number;
|
|
14
|
-
/** Minimal failing sequence */
|
|
15
|
-
sequence: unknown[];
|
|
16
|
-
/** Error message */
|
|
17
|
-
error: string;
|
|
18
|
-
/** When the failure was recorded */
|
|
19
|
-
timestamp: string;
|
|
20
|
-
/** Original sequence length before shrinking */
|
|
21
|
-
originalLength?: number;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Get the __fuzz_cases__ directory path for a test file
|
|
25
|
-
*/
|
|
26
|
-
export declare function getFuzzCasesDir(testFilePath: string): string;
|
|
27
|
-
/**
|
|
28
|
-
* Save a failing case to __fuzz_cases__/
|
|
29
|
-
*/
|
|
30
|
-
export declare function saveCase(testFilePath: string, testName: string, failure: SavedCase): string;
|
|
31
|
-
/**
|
|
32
|
-
* Load all saved cases for a test file
|
|
33
|
-
*/
|
|
34
|
-
export declare function loadCases(testFilePath: string): SavedCase[];
|
|
35
|
-
/**
|
|
36
|
-
* Load saved cases for a specific test name
|
|
37
|
-
*/
|
|
38
|
-
export declare function loadCasesForTest(testFilePath: string, testName: string): SavedCase[];
|
|
39
|
-
/**
|
|
40
|
-
* Delete a saved case (when bug is fixed)
|
|
41
|
-
*/
|
|
42
|
-
export declare function deleteCase(testFilePath: string, filename: string): void;
|
|
43
|
-
/**
|
|
44
|
-
* Clear all saved cases for a test file
|
|
45
|
-
*/
|
|
46
|
-
export declare function clearCases(testFilePath: string): void;
|
|
47
|
-
//# sourceMappingURL=regression.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"regression.d.ts","sourceRoot":"","sources":["../../src/fuzz/regression.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,iCAAiC;AACjC,MAAM,WAAW,SAAS;IACxB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,oBAAoB;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,gDAAgD;IAChD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAI5D;AAgBD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,MAAM,CAS3F;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,CAe3D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAEpF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMvE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CASrD"}
|
package/dist/fuzz/regression.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression testing - save and load failing sequences
|
|
3
|
-
*
|
|
4
|
-
* Failing fuzz test sequences are saved to __fuzz_cases__/ directory
|
|
5
|
-
* like Jest/Vitest snapshots. On subsequent runs, saved sequences
|
|
6
|
-
* are replayed first to ensure bugs don't regress.
|
|
7
|
-
*/
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync } from "node:fs";
|
|
9
|
-
import { dirname, join, basename } from "node:path";
|
|
10
|
-
/**
|
|
11
|
-
* Get the __fuzz_cases__ directory path for a test file
|
|
12
|
-
*/
|
|
13
|
-
export function getFuzzCasesDir(testFilePath) {
|
|
14
|
-
const dir = dirname(testFilePath);
|
|
15
|
-
const file = basename(testFilePath);
|
|
16
|
-
return join(dir, "__fuzz_cases__", file);
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Generate a filename for a saved case
|
|
20
|
-
*/
|
|
21
|
-
function getCaseFilename(testName) {
|
|
22
|
-
// Sanitize test name for filesystem
|
|
23
|
-
const safe = testName
|
|
24
|
-
.toLowerCase()
|
|
25
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
26
|
-
.replace(/^-|-$/g, "")
|
|
27
|
-
.slice(0, 50);
|
|
28
|
-
const timestamp = Date.now();
|
|
29
|
-
return `${safe}-${timestamp}.json`;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Save a failing case to __fuzz_cases__/
|
|
33
|
-
*/
|
|
34
|
-
export function saveCase(testFilePath, testName, failure) {
|
|
35
|
-
const dir = getFuzzCasesDir(testFilePath);
|
|
36
|
-
mkdirSync(dir, { recursive: true });
|
|
37
|
-
const filename = getCaseFilename(testName);
|
|
38
|
-
const filepath = join(dir, filename);
|
|
39
|
-
writeFileSync(filepath, JSON.stringify(failure, null, 2));
|
|
40
|
-
return filepath;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Load all saved cases for a test file
|
|
44
|
-
*/
|
|
45
|
-
export function loadCases(testFilePath) {
|
|
46
|
-
const dir = getFuzzCasesDir(testFilePath);
|
|
47
|
-
if (!existsSync(dir))
|
|
48
|
-
return [];
|
|
49
|
-
const cases = [];
|
|
50
|
-
for (const file of readdirSync(dir)) {
|
|
51
|
-
if (!file.endsWith(".json"))
|
|
52
|
-
continue;
|
|
53
|
-
try {
|
|
54
|
-
const content = readFileSync(join(dir, file), "utf-8");
|
|
55
|
-
cases.push(JSON.parse(content));
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Skip invalid files
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return cases;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Load saved cases for a specific test name
|
|
65
|
-
*/
|
|
66
|
-
export function loadCasesForTest(testFilePath, testName) {
|
|
67
|
-
return loadCases(testFilePath).filter((c) => c.test === testName);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Delete a saved case (when bug is fixed)
|
|
71
|
-
*/
|
|
72
|
-
export function deleteCase(testFilePath, filename) {
|
|
73
|
-
const dir = getFuzzCasesDir(testFilePath);
|
|
74
|
-
const filepath = join(dir, filename);
|
|
75
|
-
if (existsSync(filepath)) {
|
|
76
|
-
unlinkSync(filepath);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Clear all saved cases for a test file
|
|
81
|
-
*/
|
|
82
|
-
export function clearCases(testFilePath) {
|
|
83
|
-
const dir = getFuzzCasesDir(testFilePath);
|
|
84
|
-
if (!existsSync(dir))
|
|
85
|
-
return;
|
|
86
|
-
for (const file of readdirSync(dir)) {
|
|
87
|
-
if (file.endsWith(".json")) {
|
|
88
|
-
unlinkSync(join(dir, file));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
package/dist/fuzz/shrink.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shrinking - find minimal failing sequence via delta debugging
|
|
3
|
-
*
|
|
4
|
-
* Uses binary search to reduce a failing sequence to the smallest
|
|
5
|
-
* subsequence that still triggers the failure.
|
|
6
|
-
*/
|
|
7
|
-
export interface ShrinkOptions {
|
|
8
|
-
/** Maximum shrinking attempts (default: 100) */
|
|
9
|
-
maxAttempts?: number;
|
|
10
|
-
/** Minimum sequence length to try (default: 1) */
|
|
11
|
-
minLength?: number;
|
|
12
|
-
}
|
|
13
|
-
export interface ShrinkResult<T> {
|
|
14
|
-
/** Original sequence */
|
|
15
|
-
original: T[];
|
|
16
|
-
/** Shrunk (minimal) sequence */
|
|
17
|
-
shrunk: T[];
|
|
18
|
-
/** Number of shrinking attempts made */
|
|
19
|
-
attempts: number;
|
|
20
|
-
/** Whether shrinking found a smaller sequence */
|
|
21
|
-
reduced: boolean;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Shrink a failing sequence to its minimal form
|
|
25
|
-
*
|
|
26
|
-
* Uses delta debugging algorithm:
|
|
27
|
-
* 1. Try removing first half - if still fails, keep only first half
|
|
28
|
-
* 2. Try removing second half - if still fails, keep only second half
|
|
29
|
-
* 3. Try removing individual elements from start/end
|
|
30
|
-
* 4. Repeat until no reduction possible
|
|
31
|
-
*
|
|
32
|
-
* @param sequence - The original failing sequence
|
|
33
|
-
* @param runTest - Function that returns true if sequence passes, false if fails
|
|
34
|
-
* @param options - Shrinking options
|
|
35
|
-
*/
|
|
36
|
-
export declare function shrinkSequence<T>(sequence: T[], runTest: (seq: T[]) => Promise<boolean>, options?: ShrinkOptions): Promise<ShrinkResult<T>>;
|
|
37
|
-
/**
|
|
38
|
-
* Format shrink result for display
|
|
39
|
-
*/
|
|
40
|
-
export declare function formatShrinkResult<T>(result: ShrinkResult<T>): string;
|
|
41
|
-
//# sourceMappingURL=shrink.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"shrink.d.ts","sourceRoot":"","sources":["../../src/fuzz/shrink.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,wBAAwB;IACxB,QAAQ,EAAE,CAAC,EAAE,CAAA;IACb,gCAAgC;IAChC,MAAM,EAAE,CAAC,EAAE,CAAA;IACX,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAA;IAChB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,QAAQ,EAAE,CAAC,EAAE,EACb,OAAO,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EACvC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CA0D1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,CASrE"}
|
package/dist/fuzz/shrink.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shrinking - find minimal failing sequence via delta debugging
|
|
3
|
-
*
|
|
4
|
-
* Uses binary search to reduce a failing sequence to the smallest
|
|
5
|
-
* subsequence that still triggers the failure.
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Shrink a failing sequence to its minimal form
|
|
9
|
-
*
|
|
10
|
-
* Uses delta debugging algorithm:
|
|
11
|
-
* 1. Try removing first half - if still fails, keep only first half
|
|
12
|
-
* 2. Try removing second half - if still fails, keep only second half
|
|
13
|
-
* 3. Try removing individual elements from start/end
|
|
14
|
-
* 4. Repeat until no reduction possible
|
|
15
|
-
*
|
|
16
|
-
* @param sequence - The original failing sequence
|
|
17
|
-
* @param runTest - Function that returns true if sequence passes, false if fails
|
|
18
|
-
* @param options - Shrinking options
|
|
19
|
-
*/
|
|
20
|
-
export async function shrinkSequence(sequence, runTest, options = {}) {
|
|
21
|
-
const { maxAttempts = 100, minLength = 1 } = options;
|
|
22
|
-
let current = [...sequence];
|
|
23
|
-
let attempts = 0;
|
|
24
|
-
let changed = true;
|
|
25
|
-
while (changed && attempts < maxAttempts && current.length > minLength) {
|
|
26
|
-
changed = false;
|
|
27
|
-
// Try removing first half
|
|
28
|
-
if (current.length > 1) {
|
|
29
|
-
const half = Math.ceil(current.length / 2);
|
|
30
|
-
const secondHalf = current.slice(half);
|
|
31
|
-
attempts++;
|
|
32
|
-
if (secondHalf.length >= minLength && !(await runTest(secondHalf))) {
|
|
33
|
-
// Second half alone still fails
|
|
34
|
-
current = secondHalf;
|
|
35
|
-
changed = true;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Try removing second half
|
|
40
|
-
if (current.length > 1) {
|
|
41
|
-
const half = Math.floor(current.length / 2);
|
|
42
|
-
const firstHalf = current.slice(0, half);
|
|
43
|
-
attempts++;
|
|
44
|
-
if (firstHalf.length >= minLength && !(await runTest(firstHalf))) {
|
|
45
|
-
// First half alone still fails
|
|
46
|
-
current = firstHalf;
|
|
47
|
-
changed = true;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// Try removing individual elements
|
|
52
|
-
for (let i = 0; i < current.length && attempts < maxAttempts; i++) {
|
|
53
|
-
const without = [...current.slice(0, i), ...current.slice(i + 1)];
|
|
54
|
-
attempts++;
|
|
55
|
-
if (without.length >= minLength && !(await runTest(without))) {
|
|
56
|
-
// Removing element i still fails
|
|
57
|
-
current = without;
|
|
58
|
-
changed = true;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
original: sequence,
|
|
65
|
-
shrunk: current,
|
|
66
|
-
attempts,
|
|
67
|
-
reduced: current.length < sequence.length,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Format shrink result for display
|
|
72
|
-
*/
|
|
73
|
-
export function formatShrinkResult(result) {
|
|
74
|
-
const reduction = result.original.length - result.shrunk.length;
|
|
75
|
-
const percent = Math.round((reduction / result.original.length) * 100);
|
|
76
|
-
if (!result.reduced) {
|
|
77
|
-
return `Could not reduce sequence (${result.original.length} steps, ${result.attempts} attempts)`;
|
|
78
|
-
}
|
|
79
|
-
return `Shrunk from ${result.original.length} to ${result.shrunk.length} steps (${percent}% reduction, ${result.attempts} attempts)`;
|
|
80
|
-
}
|
package/dist/fuzz/test-fuzz.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* test.fuzz wrapper with auto-tracking, shrinking, and regression
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { test } from 'vimonkey/fuzz'
|
|
7
|
-
*
|
|
8
|
-
* test.fuzz('cursor invariants', async () => {
|
|
9
|
-
* const handle = await run(<Board />, { cols: 80, rows: 24 })
|
|
10
|
-
* for await (const key of take(gen(['j','k','h','l']), 100)) {
|
|
11
|
-
* await handle.press(key)
|
|
12
|
-
* expect(handle.locator('[data-cursor]').count()).toBe(1)
|
|
13
|
-
* }
|
|
14
|
-
* })
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
import { test as vitestTest, type TestOptions } from "vitest";
|
|
18
|
-
/** Options for test.fuzz */
|
|
19
|
-
export interface FuzzTestOptions extends TestOptions {
|
|
20
|
-
/** Seed for reproducibility (default: from FUZZ_SEED env or random) */
|
|
21
|
-
seed?: number;
|
|
22
|
-
/** Whether to shrink failing sequences (default: true) */
|
|
23
|
-
shrink?: boolean;
|
|
24
|
-
/** Whether to save failing sequences to __fuzz_cases__/ (default: true) */
|
|
25
|
-
save?: boolean;
|
|
26
|
-
/** Whether to replay saved failing sequences first (default: true) */
|
|
27
|
-
replay?: boolean;
|
|
28
|
-
/** Maximum shrinking attempts (default: 100) */
|
|
29
|
-
maxShrinkAttempts?: number;
|
|
30
|
-
}
|
|
31
|
-
/** Error with fuzz context attached */
|
|
32
|
-
export declare class FuzzError extends Error {
|
|
33
|
-
readonly sequence: unknown[];
|
|
34
|
-
readonly seed: number;
|
|
35
|
-
readonly originalError: Error;
|
|
36
|
-
constructor(originalError: Error, info: {
|
|
37
|
-
original: number;
|
|
38
|
-
shrunk: number;
|
|
39
|
-
sequence: unknown[];
|
|
40
|
-
seed: number;
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
type FuzzFn = {
|
|
44
|
-
(name: string, fn: () => Promise<void>, options?: FuzzTestOptions): void;
|
|
45
|
-
(name: string, options: FuzzTestOptions, fn: () => Promise<void>): void;
|
|
46
|
-
};
|
|
47
|
-
declare const fuzz: FuzzFn;
|
|
48
|
-
/**
|
|
49
|
-
* Extended test object with fuzz method
|
|
50
|
-
*/
|
|
51
|
-
export declare const test: typeof vitestTest & {
|
|
52
|
-
fuzz: typeof fuzz;
|
|
53
|
-
};
|
|
54
|
-
export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "vitest";
|
|
55
|
-
//# sourceMappingURL=test-fuzz.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test-fuzz.d.ts","sourceRoot":"","sources":["../../src/fuzz/test-fuzz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,IAAI,IAAI,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAA;AAM7D,4BAA4B;AAC5B,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,uCAAuC;AACvC,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAA;gBAG3B,aAAa,EAAE,KAAK,EACpB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,OAAO,EAAE,CAAA;QACnB,IAAI,EAAE,MAAM,CAAA;KACb;CAcJ;AAwJD,KAAK,MAAM,GAAG;IACZ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAA;IACxE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACxE,CAAA;AAGD,QAAA,MAAM,IAAI,EAAE,MAUX,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,IAAI,EAAE,OAAO,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,IAAI,CAAA;CAAwC,CAAA;AAGlG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
|
package/dist/fuzz/test-fuzz.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* test.fuzz wrapper with auto-tracking, shrinking, and regression
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import { test } from 'vimonkey/fuzz'
|
|
7
|
-
*
|
|
8
|
-
* test.fuzz('cursor invariants', async () => {
|
|
9
|
-
* const handle = await run(<Board />, { cols: 80, rows: 24 })
|
|
10
|
-
* for await (const key of take(gen(['j','k','h','l']), 100)) {
|
|
11
|
-
* await handle.press(key)
|
|
12
|
-
* expect(handle.locator('[data-cursor]').count()).toBe(1)
|
|
13
|
-
* }
|
|
14
|
-
* })
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
import { test as vitestTest } from "vitest";
|
|
18
|
-
import { fuzzContext, createFuzzContext, createReplayContext } from "./context.js";
|
|
19
|
-
import { shrinkSequence, formatShrinkResult } from "./shrink.js";
|
|
20
|
-
import { saveCase, loadCasesForTest } from "./regression.js";
|
|
21
|
-
import { parseSeed, parseRepeats, deriveSeeds } from "../random.js";
|
|
22
|
-
/** Error with fuzz context attached */
|
|
23
|
-
export class FuzzError extends Error {
|
|
24
|
-
sequence;
|
|
25
|
-
seed;
|
|
26
|
-
originalError;
|
|
27
|
-
constructor(originalError, info) {
|
|
28
|
-
const msg = `Fuzz test failed after ${info.original} steps (shrunk to ${info.shrunk})
|
|
29
|
-
Minimal failing sequence: ${JSON.stringify(info.sequence)}
|
|
30
|
-
Seed: ${info.seed} (reproduce with FUZZ_SEED=${info.seed})
|
|
31
|
-
|
|
32
|
-
Original error: ${originalError.message}`;
|
|
33
|
-
super(msg);
|
|
34
|
-
this.name = "FuzzError";
|
|
35
|
-
this.sequence = info.sequence;
|
|
36
|
-
this.seed = info.seed;
|
|
37
|
-
this.originalError = originalError;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Get the test file path from error stack
|
|
42
|
-
* This is a heuristic - it looks for .test.ts or .fuzz.ts files
|
|
43
|
-
*/
|
|
44
|
-
function getTestFilePath() {
|
|
45
|
-
const err = new Error();
|
|
46
|
-
const stack = err.stack?.split("\n") ?? [];
|
|
47
|
-
for (const line of stack) {
|
|
48
|
-
// Look for test file patterns
|
|
49
|
-
const match = line.match(/\(([^)]+\.(test|fuzz)\.(ts|tsx|js|jsx)):\d+:\d+\)/);
|
|
50
|
-
if (match)
|
|
51
|
-
return match[1];
|
|
52
|
-
// Also try without parentheses
|
|
53
|
-
const match2 = line.match(/at\s+([^\s]+\.(test|fuzz)\.(ts|tsx|js|jsx)):\d+:\d+/);
|
|
54
|
-
if (match2)
|
|
55
|
-
return match2[1];
|
|
56
|
-
}
|
|
57
|
-
// Fallback to current working directory
|
|
58
|
-
return process.cwd() + "/unknown.test.ts";
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Run a single fuzz test body with a specific seed.
|
|
62
|
-
* Handles replay, shrinking, and saving of failing cases.
|
|
63
|
-
*/
|
|
64
|
-
async function runFuzzBody(name, fn, seed, opts) {
|
|
65
|
-
const testFilePath = getTestFilePath();
|
|
66
|
-
// Replay saved failing sequences first
|
|
67
|
-
if (opts.replay) {
|
|
68
|
-
const savedCases = loadCasesForTest(testFilePath, name);
|
|
69
|
-
for (const savedCase of savedCases) {
|
|
70
|
-
const replayCtx = createReplayContext(savedCase.sequence, savedCase.seed);
|
|
71
|
-
try {
|
|
72
|
-
await fuzzContext.run(replayCtx, fn);
|
|
73
|
-
// If replay passes, the bug might be fixed - but we still run the main test
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
// Replay still fails - throw with saved context
|
|
77
|
-
throw new FuzzError(error, {
|
|
78
|
-
original: savedCase.originalLength ?? savedCase.sequence.length,
|
|
79
|
-
shrunk: savedCase.sequence.length,
|
|
80
|
-
sequence: savedCase.sequence,
|
|
81
|
-
seed: savedCase.seed,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Run the main fuzz test
|
|
87
|
-
const ctx = createFuzzContext(seed);
|
|
88
|
-
try {
|
|
89
|
-
await fuzzContext.run(ctx, fn);
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
// Test failed - attempt shrinking
|
|
93
|
-
if (ctx.history.length > 0) {
|
|
94
|
-
let minimalSequence = ctx.history;
|
|
95
|
-
let shrinkResult;
|
|
96
|
-
if (opts.shrink) {
|
|
97
|
-
// Define the test runner for shrinking
|
|
98
|
-
const runWithSequence = async (seq) => {
|
|
99
|
-
const replayCtx = createReplayContext(seq, seed);
|
|
100
|
-
try {
|
|
101
|
-
await fuzzContext.run(replayCtx, fn);
|
|
102
|
-
return true; // passed
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return false; // still fails
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
shrinkResult = await shrinkSequence(ctx.history, runWithSequence, {
|
|
109
|
-
maxAttempts: opts.maxShrinkAttempts,
|
|
110
|
-
});
|
|
111
|
-
minimalSequence = shrinkResult.shrunk;
|
|
112
|
-
// Log shrink result
|
|
113
|
-
console.log(formatShrinkResult(shrinkResult));
|
|
114
|
-
}
|
|
115
|
-
// Save failing case
|
|
116
|
-
if (opts.save) {
|
|
117
|
-
const savedCase = {
|
|
118
|
-
test: name,
|
|
119
|
-
seed,
|
|
120
|
-
sequence: minimalSequence,
|
|
121
|
-
error: String(error),
|
|
122
|
-
timestamp: new Date().toISOString(),
|
|
123
|
-
originalLength: ctx.history.length,
|
|
124
|
-
};
|
|
125
|
-
const filepath = saveCase(testFilePath, name, savedCase);
|
|
126
|
-
console.log(`Saved failing case to: ${filepath}`);
|
|
127
|
-
}
|
|
128
|
-
throw new FuzzError(error, {
|
|
129
|
-
original: ctx.history.length,
|
|
130
|
-
shrunk: minimalSequence.length,
|
|
131
|
-
sequence: minimalSequence,
|
|
132
|
-
seed,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Create the test.fuzz wrapper
|
|
140
|
-
*
|
|
141
|
-
* When FUZZ_REPEATS > 1, registers multiple vitest tests — one per seed —
|
|
142
|
-
* so each gets its own result in the reporter and failures are independently
|
|
143
|
-
* visible. Seeds are deterministically derived from the base seed.
|
|
144
|
-
*/
|
|
145
|
-
function createFuzzTest(name, fn, options = {}) {
|
|
146
|
-
const { seed = parseSeed("env"), shrink = true, save = true, replay = true, maxShrinkAttempts = 100, ...testOptions } = options;
|
|
147
|
-
const repeats = parseRepeats();
|
|
148
|
-
const bodyOpts = { shrink, save, replay, maxShrinkAttempts };
|
|
149
|
-
if (repeats <= 1) {
|
|
150
|
-
// Single run (default) — original behavior
|
|
151
|
-
return vitestTest(name, testOptions, async () => {
|
|
152
|
-
await runFuzzBody(name, fn, seed, bodyOpts);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// Multiple runs — register one test per seed
|
|
156
|
-
const seeds = deriveSeeds(seed, repeats);
|
|
157
|
-
for (let i = 0; i < seeds.length; i++) {
|
|
158
|
-
const s = seeds[i];
|
|
159
|
-
vitestTest(`${name} [seed=${s}]`, testOptions, async () => {
|
|
160
|
-
await runFuzzBody(name, fn, s, bodyOpts);
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Create the fuzz function with overloads
|
|
165
|
-
const fuzz = (name, fnOrOptions, optionsOrFn) => {
|
|
166
|
-
if (typeof fnOrOptions === "function") {
|
|
167
|
-
return createFuzzTest(name, fnOrOptions, optionsOrFn);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
return createFuzzTest(name, optionsOrFn, fnOrOptions);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
/**
|
|
174
|
-
* Extended test object with fuzz method
|
|
175
|
-
*/
|
|
176
|
-
export const test = Object.assign(vitestTest, { fuzz });
|
|
177
|
-
// Re-export vitest test types
|
|
178
|
-
export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "vitest";
|
package/dist/index.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vimonkey — Fuzz testing and chaos streams for Vitest
|
|
3
|
-
*/
|
|
4
|
-
export { gen, take, type Picker, type PickerContext } from "./fuzz/gen.js";
|
|
5
|
-
export { test, FuzzError, type FuzzTestOptions } from "./fuzz/test-fuzz.js";
|
|
6
|
-
export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "./fuzz/test-fuzz.js";
|
|
7
|
-
export { fuzzContext, getFuzzContext, isInFuzzContext, createFuzzContext, createReplayContext, type FuzzContext, } from "./fuzz/context.js";
|
|
8
|
-
export { shrinkSequence, formatShrinkResult, type ShrinkOptions, type ShrinkResult } from "./fuzz/shrink.js";
|
|
9
|
-
export { saveCase, loadCases, loadCasesForTest, deleteCase, clearCases, getFuzzCasesDir, type SavedCase, } from "./fuzz/regression.js";
|
|
10
|
-
export { createSeededRandom, weightedPickFromTuples, parseSeed, parseRepeats, deriveSeeds, type SeededRandom, } from "./random.js";
|
|
11
|
-
export { getTestSys, type TestSys } from "./env.js";
|
|
12
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAA;AAC1E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AACtG,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,WAAW,GACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC5G,OAAO,EACL,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,UAAU,EACV,eAAe,EACf,KAAK,SAAS,GACf,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,WAAW,EACX,KAAK,YAAY,GAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* vimonkey — Fuzz testing and chaos streams for Vitest
|
|
3
|
-
*/
|
|
4
|
-
// Fuzz API (primary)
|
|
5
|
-
export { gen, take } from "./fuzz/gen.js";
|
|
6
|
-
export { test, FuzzError } from "./fuzz/test-fuzz.js";
|
|
7
|
-
export { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from "./fuzz/test-fuzz.js";
|
|
8
|
-
export { fuzzContext, getFuzzContext, isInFuzzContext, createFuzzContext, createReplayContext, } from "./fuzz/context.js";
|
|
9
|
-
export { shrinkSequence, formatShrinkResult } from "./fuzz/shrink.js";
|
|
10
|
-
export { saveCase, loadCases, loadCasesForTest, deleteCase, clearCases, getFuzzCasesDir, } from "./fuzz/regression.js";
|
|
11
|
-
// Utilities
|
|
12
|
-
export { createSeededRandom, weightedPickFromTuples, parseSeed, parseRepeats, deriveSeeds, } from "./random.js";
|
|
13
|
-
export { getTestSys } from "./env.js";
|