@mettascript/fuzz 3.1.2 → 3.3.0
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/README.md +20 -127
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# @mettascript/fuzz
|
|
2
2
|
|
|
3
|
-
Property testing for [MeTTaScript](https://github.com/MesTTo/MeTTaScript), written in MeTTa. You declare
|
|
3
|
+
Property testing for [MeTTaScript](https://github.com/MesTTo/MeTTaScript), written in MeTTa. You declare
|
|
4
|
+
a generator and a property, and the library generates cases, shrinks a failure to its smallest form, and
|
|
5
|
+
hands back a result you can replay. It also enumerates small domains exhaustively, checks a real system
|
|
6
|
+
against a model over command sequences, and searches a transition relation for a reachable state.
|
|
4
7
|
|
|
5
|
-
The policy lives in MeTTa: generation, shrinking, the run loop, the state machines
|
|
8
|
+
The policy lives in MeTTa: generation, shrinking, the run loop, the state machines and the search are all
|
|
9
|
+
rewrite rules you can read in `src/metta`. TypeScript supplies only what a representation needs, in one
|
|
10
|
+
place: a splitmix/xoroshiro random source, a structural key over atoms, and a versioned atom codec.
|
|
6
11
|
|
|
7
12
|
## Install
|
|
8
13
|
|
|
@@ -12,7 +17,7 @@ npm install @mettascript/fuzz
|
|
|
12
17
|
|
|
13
18
|
Importing the package registers the `fuzz` module, so MeTTa code reaches it with `import!`.
|
|
14
19
|
|
|
15
|
-
##
|
|
20
|
+
## In one example
|
|
16
21
|
|
|
17
22
|
```metta
|
|
18
23
|
!(import! &self fuzz)
|
|
@@ -38,138 +43,26 @@ A passing run reports what it did:
|
|
|
38
43
|
`Runs` counts the random cases. Edge cases are drawn on top of them, which is why 200 runs report 213
|
|
39
44
|
passes here: the generator's boundary values are tried first, then the random ones.
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- `(fuzz-pass)` and `(fuzz-fail <tag> <details>)` are the two results everything else builds on.
|
|
44
|
-
- `(expect-true <bool> <details>)`, `(expect-false ...)`.
|
|
45
|
-
- `(expect-atom-equal <a> <b>)` compares structurally; `(expect-alpha-equal ...)` ignores variable names.
|
|
46
|
-
- `(expect-results-exact <a> <b>)` compares result bags in order; `-alpha`, `-multiset` and `-set` relax that to variable renaming, order, and multiplicity.
|
|
47
|
-
|
|
48
|
-
## Generators
|
|
49
|
-
|
|
50
|
-
Generators are data, not functions: `(gen-int 0 9)` is an atom the runner interprets, which is what lets one declaration be replayed, shrunk, and enumerated.
|
|
51
|
-
|
|
52
|
-
- Scalars: `gen-bool`, `gen-int`, `gen-int-origin`, `gen-sized-int`, `gen-float`, `gen-float-range`, `gen-float-bits`, `gen-char`, `gen-char-ascii`, `gen-char-unicode`, `gen-symbol`, `gen-syntax-token`, `gen-const`.
|
|
53
|
-
- Text: `gen-string`, `gen-ascii-string`, `gen-unicode-string`.
|
|
54
|
-
- Structure: `gen-tuple`, `gen-list`, `gen-option`, `gen-element`, `gen-one-of`, `gen-frequency`.
|
|
55
|
-
- Combinators: `gen-map`, `gen-bind`, `gen-filter`, `gen-sized`, `gen-resize`, `gen-recursive`, `gen-custom`.
|
|
56
|
-
- Grammars and types: `gen-grammar`, `gen-grammar-root`, `gen-well-typed` generate from a declared grammar or from type constructors, so you can generate well-formed terms of a language rather than raw trees.
|
|
57
|
-
|
|
58
|
-
## Shrinking
|
|
59
|
-
|
|
60
|
-
A failure is shrunk before it is reported, under a named order (`mettascript-shrink-v1`) so the smallest form is stable across runs rather than a function of the seed. The result says whether it reached a local minimum or stopped early, and why. A custom generator can supply its own shrinker; anything else shrinks through its decision tree.
|
|
61
|
-
|
|
62
|
-
## Exhaustive checking
|
|
63
|
-
|
|
64
|
-
For a small domain, enumerate it instead of sampling:
|
|
65
|
-
|
|
66
|
-
```metta
|
|
67
|
-
(: always (-> Atom FuzzProperty))
|
|
68
|
-
(= (always $value) (fuzz-pass))
|
|
69
|
-
|
|
70
|
-
!(fuzz-check-exhaustive small (gen-bool) always (fuzz-config (MaxEnumerated 10)))
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
`(FuzzExhaustivelyVerified (Property small) (DomainCount 2) ...)` means the whole domain was covered, which is a proof over that domain rather than evidence. A domain that does not fit the bound is reported as an incomplete run, never as verified.
|
|
74
|
-
|
|
75
|
-
## State machines
|
|
76
|
-
|
|
77
|
-
Check a real system against a model over generated command sequences. Generation walks the model only, so a command sequence is chosen without touching the real system; execution then runs both and compares.
|
|
78
|
-
|
|
79
|
-
```metta
|
|
80
|
-
(= (counter-initialize) (new-state 0))
|
|
81
|
-
(: counter-command-generator (-> Atom %Undefined%))
|
|
82
|
-
(= (counter-command-generator $model) (gen-element (increment reset)))
|
|
83
|
-
(= (counter-precondition $model $command) True)
|
|
84
|
-
(= (counter-execute $real increment)
|
|
85
|
-
(let $seen (get-state $real)
|
|
86
|
-
(let $changed (change-state! $real (+ $seen 1))
|
|
87
|
-
(+ $seen 1))))
|
|
88
|
-
(= (counter-execute $real reset) (let $changed (change-state! $real 0) 0))
|
|
89
|
-
(= (counter-next-model (Count $n) increment) (Count (+ $n 1)))
|
|
90
|
-
(= (counter-next-model (Count $n) reset) (Count 0))
|
|
91
|
-
(= (counter-postcondition (Count $n) increment $result) (== $result (+ $n 1)))
|
|
92
|
-
(= (counter-postcondition (Count $n) reset $result) (== $result 0))
|
|
93
|
-
(= (counter-invariant (Count $n)) (>= $n 0))
|
|
94
|
-
(= (counter-cleanup $real) Done)
|
|
95
|
-
|
|
96
|
-
(FuzzMachine Counter
|
|
97
|
-
(InitialModel (Count 0))
|
|
98
|
-
(InitializeReal counter-initialize)
|
|
99
|
-
(CommandGenerator counter-command-generator)
|
|
100
|
-
(Precondition counter-precondition)
|
|
101
|
-
(Execute counter-execute)
|
|
102
|
-
(NextModel counter-next-model)
|
|
103
|
-
(Postcondition counter-postcondition)
|
|
104
|
-
(Invariant counter-invariant)
|
|
105
|
-
(Cleanup counter-cleanup))
|
|
106
|
-
|
|
107
|
-
!(fuzz-check-machine Counter (fuzz-config (Runs 20) (MaxSize 6)))
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
A divergence shrinks to a shorter command sequence that still diverges, so you get the shortest sequence that separates the real system from the model rather than the one that happened to be generated.
|
|
111
|
-
|
|
112
|
-
## Bounded reachability
|
|
113
|
-
|
|
114
|
-
Search a transition relation, breadth first, for a state that satisfies a target:
|
|
115
|
-
|
|
116
|
-
```metta
|
|
117
|
-
(= (counter-enumerate (Count $n))
|
|
118
|
-
(if (< $n 4) (FiniteCommands up split) (FiniteCommands)))
|
|
119
|
-
(= (counter-transition (Count $n) up) (Count (+ $n 1)))
|
|
120
|
-
(= (counter-transition (Count $n) split) (superpose ((Count (+ $n 1)) (Count (+ $n 2)))))
|
|
121
|
-
(= (counter-target (Count $n)) (== $n 3))
|
|
122
|
-
|
|
123
|
-
!(fuzz-reachable Counter (Count 0)
|
|
124
|
-
counter-enumerate counter-transition counter-target
|
|
125
|
-
(reach-config (MaxDepth 20)))
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
A transition may return several next states, and the whole ordered result bag becomes outgoing edges, so a witness records which branch it took and can be replayed through a nondeterministic model.
|
|
129
|
-
|
|
130
|
-
The outcomes are deliberately distinct. `FuzzReachable` carries a witness that was replayed from the initial state before being reported. `FuzzReachabilityExhausted` means unreachable in the declared finite model. `FuzzUnreachableWithinDepth` means only that nothing was found at or below `MaxDepth`. Every limit, incomplete enumeration, or replay mismatch is a `FuzzReachabilityCutoff` and never becomes exhaustion.
|
|
131
|
-
|
|
132
|
-
## Running a suite from the command line
|
|
133
|
-
|
|
134
|
-
Declare tests as data and run the file with [`@mettascript/node`](https://github.com/MesTTo/MeTTaScript/tree/main/packages/node):
|
|
135
|
-
|
|
136
|
-
```metta
|
|
137
|
-
(FuzzTest involution (gen-list (gen-int -20 20) 0 6) reverse-involution
|
|
138
|
-
(fuzz-config (Runs 200)))
|
|
139
|
-
```
|
|
46
|
+
Run a whole file of declared properties from the command line with `metta fuzz`:
|
|
140
47
|
|
|
141
48
|
```bash
|
|
142
|
-
metta fuzz
|
|
143
|
-
metta fuzz --seed 7 --runs 50 suite.metta # override each declaration's config
|
|
144
|
-
metta fuzz --exhaustive suite.metta # enumerate each domain instead
|
|
145
|
-
metta fuzz --corpus regressions suite.metta # replay stored counterexamples, record new ones
|
|
146
|
-
metta reach suite.metta # run every (FuzzReachTest ...)
|
|
49
|
+
metta fuzz properties.metta
|
|
147
50
|
```
|
|
148
51
|
|
|
149
|
-
|
|
52
|
+
## Where the documentation lives
|
|
150
53
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
import { decodeFuzzOutcome, exitCodeForOutcomes, renderOutcomeLine } from "@mettascript/fuzz";
|
|
159
|
-
|
|
160
|
-
const outcome = decodeFuzzOutcome(resultAtom);
|
|
161
|
-
if (outcome.kind === "failed") console.log(renderOutcomeLine(outcome));
|
|
162
|
-
process.exit(exitCodeForOutcomes([outcome]));
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
The decoder is strict: an atom it does not recognize becomes an `undecodable` outcome rather than a pass.
|
|
166
|
-
|
|
167
|
-
## Determinism
|
|
168
|
-
|
|
169
|
-
A run is a function of its seed. The random source, the shrink order, the replay keys, and the exhaustive enumeration order are all named and versioned, so a reported failure reproduces, and `metta fuzz --seed <n>` twice gives the same cases.
|
|
54
|
+
- [Property testing](https://mestto.github.io/MeTTaScript/fuzz/overview) is the guide: writing a
|
|
55
|
+
property, the generators, shrinking, exhaustive checking, state machines, and bounded reachability.
|
|
56
|
+
- [API reference](https://mestto.github.io/MeTTaScript/reference/fuzz) lists the full surface, including
|
|
57
|
+
reading a result back from TypeScript.
|
|
58
|
+
- [The CLI](https://mestto.github.io/MeTTaScript/tools/cli) covers `metta fuzz` and `metta reach`.
|
|
170
59
|
|
|
171
60
|
## For language models
|
|
172
61
|
|
|
173
62
|
[`LLMS.md`](./LLMS.md) is a one-page, high-density reference for this package: API surface, working
|
|
174
63
|
examples, and the mistakes that produce wrong code. The repository root carries an
|
|
175
64
|
[`llms.txt`](../../llms.txt) index of all of them.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
[MIT](https://github.com/MesTTo/MeTTaScript/blob/main/LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mettascript/fuzz",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"pure-rand": "8.4.2",
|
|
22
|
-
"@mettascript/core": "3.
|
|
22
|
+
"@mettascript/core": "3.3.0"
|
|
23
23
|
},
|
|
24
24
|
"author": "MesTTo",
|
|
25
25
|
"engines": {
|