eyeprolog 1.5.93 → 1.5.94
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 +1 -1
- package/test/README.md +18 -11
- package/test/run-properties.mjs +65 -3
package/package.json
CHANGED
package/test/README.md
CHANGED
|
@@ -29,19 +29,26 @@ each one. Focused checks do not replace the full release gate.
|
|
|
29
29
|
|
|
30
30
|
`run-properties.mjs` is different in kind from the rest of the suite: instead
|
|
31
31
|
of hand-picked inputs, it generates many random ground terms from a seeded
|
|
32
|
-
PRNG and checks invariants that must hold for any input
|
|
33
|
-
round-tripping, `sort/2`
|
|
34
|
-
|
|
35
|
-
`
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
PRNG and checks invariants that must hold for any input. It covers term/list/
|
|
33
|
+
arithmetic/string library predicates (write/read round-tripping, `sort/2`
|
|
34
|
+
ordering and idempotence, `append/3`/`length/2` agreement, `compare/3`
|
|
35
|
+
symmetry, `keysort/2` stability, `=..` round-tripping, `copy_term/2` sharing,
|
|
36
|
+
`reverse/2` involution, `atom_codes/2`/`atom_chars/2`/`char_code/2`/
|
|
37
|
+
`number_codes/2` round-tripping, `succ/2` and `abs/1`/`sign/1` arithmetic
|
|
38
|
+
identities, `nth0/3`/`nth1/3` agreement, `last/2`, `min_list/2`/`max_list/2`
|
|
39
|
+
bounds, `sum_list/2` additivity, `atom_concat/3`/`string_concat/3` regrouping,
|
|
40
|
+
`atom_string/2`/`number_string/2` round-tripping, `list_to_set/2` idempotence,
|
|
41
|
+
`permutation/2`, `between/3` range generation, `split/3`/`join/3` inversion)
|
|
42
|
+
and, more broadly, control constructs, exceptions, the database, and grammar
|
|
43
|
+
rules (`catch/3` ball delivery, `findall/3` order/duplicate/no-match
|
|
44
|
+
behavior, double negation, the exact `existence_error` indicator for an
|
|
45
|
+
undefined predicate, `assertz/1`+`retract/1` leaving no trace, a DCG list
|
|
46
|
+
rule accepting exactly the list it was built from, `bagof/3` agreeing with
|
|
47
|
+
`findall/3`, and `numbervars/3` counting distinct variables) -- around 35
|
|
48
|
+
properties, each reported as its own trial (currently ~530 individual test
|
|
42
49
|
lines) so a failure names exactly which trial of which property broke rather
|
|
43
50
|
than leaving a reader to dig through one aggregate error. The whole file
|
|
44
|
-
still runs in
|
|
51
|
+
still runs in about two seconds. The seed (`SEED` in that file) is fixed so a
|
|
45
52
|
failure is exactly reproducible by rerunning it -- change it only
|
|
46
53
|
deliberately, and say why, never to make a transient failure disappear.
|
|
47
54
|
|
package/test/run-properties.mjs
CHANGED
|
@@ -100,7 +100,9 @@ function genCompound() {
|
|
|
100
100
|
return `${pick(FUNCTOR_POOL)}(${args.join(', ')})`;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function genList(depth = 1
|
|
103
|
+
function genList(depth = 1, minLen = 0, maxLen = 6) {
|
|
104
|
+
return `[${Array.from({ length: int(minLen, maxLen) }, () => genTerm(depth)).join(', ')}]`;
|
|
105
|
+
}
|
|
104
106
|
function genWord() { return pick(WORD_POOL); }
|
|
105
107
|
function genSmallInt() { return int(0, 1000); }
|
|
106
108
|
function genNonZeroInt() { const n = int(1, 1000); return rng() < 0.5 ? -n : n; }
|
|
@@ -114,8 +116,8 @@ function genNonZeroInt() { const n = int(1, 1000); return rng() < 0.5 ? -n : n;
|
|
|
114
116
|
// succeeding, and a ground goal that succeeds is echoed back in full,
|
|
115
117
|
// untaken branches included -- so a literal marker atom anywhere in the
|
|
116
118
|
// goal text can appear in that echo without ever having been reached.
|
|
117
|
-
function solve(goal, { use = [] } = {}) {
|
|
118
|
-
const prelude = use.map((lib) => `:- use_module(library(${lib})).\n`).join('');
|
|
119
|
+
function solve(goal, { use = [], preamble = '' } = {}) {
|
|
120
|
+
const prelude = use.map((lib) => `:- use_module(library(${lib})).\n`).join('') + preamble;
|
|
119
121
|
const result = run(prelude, { goal });
|
|
120
122
|
if (result.stats.completed_goal_lists < 1) {
|
|
121
123
|
throw new Error(`property goal did not hold (failed or errored): ${goal}`);
|
|
@@ -289,6 +291,66 @@ export function runProperties(reporter = new TestReporter()) {
|
|
|
289
291
|
`nth0(0, L, F), F =:= ${lo}, last(L, La), La =:= ${hi}`;
|
|
290
292
|
});
|
|
291
293
|
|
|
294
|
+
// --- Broader coverage: control constructs, exceptions, the database, and
|
|
295
|
+
// grammar rules, not just term/list/arithmetic/string library predicates.
|
|
296
|
+
|
|
297
|
+
property(reporter, 'catch/3 delivers the thrown ball unchanged', 15, () =>
|
|
298
|
+
`B = (${genTerm()}), catch(throw(B), Ball, true), Ball == B`);
|
|
299
|
+
|
|
300
|
+
property(reporter, 'findall/3 over member/2 reproduces the list exactly, in order, duplicates included', 15, () => {
|
|
301
|
+
const list = genList();
|
|
302
|
+
return `L = ${list}, findall(X, member(X, L), L2), L2 == L`;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
property(reporter, 'findall/3 leaves its template variable unbound when nothing matches', 6, () =>
|
|
306
|
+
`findall(X, (member(_, ${genList()}), fail), Bag), Bag == [], var(X)`);
|
|
307
|
+
|
|
308
|
+
property(reporter, 'double negation reports success without ever binding anything', 20, () => {
|
|
309
|
+
const list = genList(1);
|
|
310
|
+
// Half the time probe an element genuinely in the list; half the time
|
|
311
|
+
// probe one that (almost certainly) is not, so both the success and
|
|
312
|
+
// the failure side of \+ \+ get exercised across the run.
|
|
313
|
+
const probe = rng() < 0.5 ? pick(FUNCTOR_POOL) : genTerm(1);
|
|
314
|
+
return `L = ${list}, X = (${probe}), ` +
|
|
315
|
+
`( member(X, L) -> \\+ \\+ member(X, L) ; \\+ member(X, L) )`;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
property(reporter, 'calling an undefined predicate names its own exact indicator', 15, () => {
|
|
319
|
+
const name = `zz_property_undefined_${int(0, 1000000)}`;
|
|
320
|
+
const arity = int(0, 4);
|
|
321
|
+
const args = Array.from({ length: arity }, () => '_').join(', ');
|
|
322
|
+
const call = arity === 0 ? name : `${name}(${args})`;
|
|
323
|
+
return `catch(${call}, error(existence_error(procedure, PI), _), true), PI == ${name}/${arity}`;
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
property(reporter, 'assertz/1 then retract/1 leaves no trace behind', 15, () => {
|
|
327
|
+
const term = genTerm();
|
|
328
|
+
return `assertz(zz_property_fact(${term})), zz_property_fact(X), X == (${term}), ` +
|
|
329
|
+
`retract(zz_property_fact(${term})), \\+ zz_property_fact(_)`;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
property(reporter, 'a DCG list rule accepts exactly the list it was built from', 15, () => {
|
|
333
|
+
const list = genList(1);
|
|
334
|
+
return `L = ${list}, phrase(zz_property_seq(L), L)`;
|
|
335
|
+
}, { preamble: 'zz_property_seq([]) --> [].\nzz_property_seq([H|T]) --> [H], zz_property_seq(T).\n' });
|
|
336
|
+
|
|
337
|
+
property(reporter, 'bagof/3 over member/2 agrees with findall/3 on a non-empty list', 15, () => {
|
|
338
|
+
const list = genList(1, 1, 6); // at least one element: bagof/3 has no "no solutions" answer
|
|
339
|
+
return `L = ${list}, bagof(X, member(X, L), Bag), Bag == L`;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
property(reporter, 'numbervars/3 assigns exactly as many numbers as distinct variables', 15, () => {
|
|
343
|
+
const arity = int(2, 5);
|
|
344
|
+
const slots = int(1, arity);
|
|
345
|
+
const args = Array.from({ length: arity }, () => `V${int(1, slots)}`);
|
|
346
|
+
return `T = f(${args.join(', ')}), numbervars(T, 0, End), End =:= ${slots}`;
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
property(reporter, 'split/3 and join/3 (library(strings)) invert each other', 12, () => {
|
|
350
|
+
const words = Array.from({ length: int(2, 5) }, () => genWord());
|
|
351
|
+
return `split('${words.join(',')}', ',', Parts), join(Parts, ',', S), S == '${words.join(',')}'`;
|
|
352
|
+
}, { use: ['strings'] });
|
|
353
|
+
|
|
292
354
|
reporter.sectionTotal('property-based round-trip checks');
|
|
293
355
|
}
|
|
294
356
|
|