Skip to content

Commit 7789834

Browse files
dckckriskowal
andauthored
feat(ava-xs): handle some zoe tests (#2573)
* feat(ava-xs): install as npm bin * chore(avaHandler): SES Compartment already has harden * feat(ava-xs): provide makeKind, weakStore to test compartment * feat(ava-xs): handle ava.files, ava.require from package.json - support -v / --verbose - xsnap: add glob, @agoric/assert dependencies. hm... having ava-xs in the xsnap package is increasingly awkward * chore(avaXS): log error before losing full message * feat(ava-xs): test.todo * feat(ava-xs): exclude takes a list of strings * test(zoe): XS test exclusions with reasons * test(zoe): add use --verbose on XS to know which script failed - add a separate test:xs-unit-debug script, since `ava-xs --verbose --debug` isn't yet supported * test(zoe): postpone _ in underscores until #2503 is done * test(zoe): be sure async tests wait for all promises * test(zoe): handle __dirname for bundling zcfTesterContract - clean up some lint * test(zoe): be flexible about error message from assertIssuerKeywords * test(zoe): support test.failing on XS foro test-zoeHelpersWZcf.js - elaborate diagnostics for checkExpectation * test(zoe): add test:xs-unit, test:xs-worker to test script * fix(ava-xs): handle missing ava-xs config section * feat(ava-xs): report count of tests, each in their own crank * test(ERTP): use ava-xs bin, ava-xs.exclude * fix(ava-xs): count where we can see crank boundaries The code running inside xsnap can't see crank boundaries, so don't bother to count planned assertions there. Count on the node parent side. * style(ava-xs): clarify type in avaHandler * fix(ava-xs): notThrows() success case * feat(ava-xs): summarize failed tests * test(zoe): update list of XS exclusions * test(zoe): postpone test:xs-worker * test(zoe): postpone test-scriptedOracle due to intermittent SEGV * refactor(ava-xs): export powerless main * test(zoe): try longer timeout * fix(ava-xs): use cjs rather than -r esm in #! * test(zoe): postpone zoeHelpers on XS * style(ava-xs): jsdoc idiom Co-authored-by: Kris Kowal <kris@agoric.com> * style(ava-xs): jsdoc idiom Co-authored-by: Kris Kowal <kris@agoric.com>
1 parent cf3ad2f commit 7789834

13 files changed

+407
-169
lines changed

.github/workflows/test-all-packages.yml

+1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ jobs:
398398
# END-RESTORE-BOILERPLATE
399399

400400
- name: yarn test (zoe)
401+
timeout-minutes: 20
401402
run: cd packages/zoe && yarn test
402403
env:
403404
ESM_DISABLE_CACHE: true

packages/ERTP/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"test": "yarn test:node && yarn test:xs",
1515
"test:node": "ava",
1616
"test:xs": "yarn test:xs-unit && yarn test:xs-worker",
17-
"test:xs-unit": "node -r esm ../xsnap/src/avaXS.js test/unitTests/test-*.js",
17+
"test:xs-unit": "ava-xs",
1818
"test:xs-worker": "WORKER_TYPE=xs-worker ava",
1919
"test:nyc": "nyc ava",
2020
"pretty-fix": "prettier --write '**/*.js'",
@@ -66,6 +66,9 @@
6666
"NEWS.md",
6767
"exported.js"
6868
],
69+
"ava-xs": {
70+
"exclude": "swingsetTests"
71+
},
6972
"ava": {
7073
"files": [
7174
"test/**/test-*.js"

packages/xsnap/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
},
1010
"main": "./src/xsnap.js",
1111
"bin": {
12+
"ava-xs": "./src/ava-xs.js",
1213
"xsrepl": "./src/xsrepl"
1314
},
1415
"scripts": {
@@ -25,10 +26,12 @@
2526
"test": "ava"
2627
},
2728
"dependencies": {
29+
"@agoric/assert": "^0.2.2-dev.0",
2830
"@agoric/bundle-source": "^1.2.1",
2931
"@agoric/eventual-send": "^0.13.2",
3032
"@agoric/install-ses": "^0.5.1",
3133
"esm": "^3.2.5",
34+
"glob": "^7.1.6",
3235
"ses": "^0.12.3"
3336
},
3437
"devDependencies": {

packages/xsnap/src/ava-xs.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env node
2+
/* global require, module */
3+
// @ts-check
4+
const esmRequire = require('esm')(module);
5+
const process = require('process');
6+
const { spawn } = require('child_process');
7+
const { type: osType } = require('os');
8+
const { promises } = require('fs');
9+
const path = require('path');
10+
const glob = require('glob');
11+
12+
esmRequire('@agoric/install-ses');
13+
const bundleSource = esmRequire('@agoric/bundle-source').default;
14+
15+
const { main, makeBundleResolve } = esmRequire('./avaXS');
16+
17+
Promise.resolve()
18+
.then(_ =>
19+
main(process.argv.slice(2), {
20+
bundleSource,
21+
spawn,
22+
osType,
23+
readFile: promises.readFile,
24+
resolve: makeBundleResolve(path),
25+
dirname: path.dirname,
26+
glob,
27+
}),
28+
)
29+
.then(status => {
30+
process.exit(status);
31+
})
32+
.catch(err => {
33+
console.error(err);
34+
process.exit(1);
35+
});

packages/xsnap/src/avaAssertXS.js

+58-56
Original file line numberDiff line numberDiff line change
@@ -105,25 +105,12 @@ let theHarness = null; // ISSUE: ambient
105105
*/
106106
function createHarness(send) {
107107
let testNum = 0;
108-
let passCount = 0;
109108
/** @type {((ot: { context: Object }) => Promise<void>)[]} */
110-
let beforeHooks = [];
111-
/** @type {(() => Promise<void>)[]} */
112-
let suitesToRun = [];
109+
const beforeHooks = [];
110+
/** @type {Record<string, () => Promise<void>>} */
111+
const suitesToRun = {};
113112
const context = {};
114113

115-
/**
116-
* @returns { Summary }
117-
* @typedef {import('./avaXS').Summary} Summary
118-
*/
119-
function summary() {
120-
return {
121-
pass: passCount,
122-
fail: testNum - passCount,
123-
total: testNum,
124-
};
125-
}
126-
127114
const it = freeze({
128115
send,
129116
get context() {
@@ -134,29 +121,31 @@ function createHarness(send) {
134121
beforeHooks.push(hook);
135122
},
136123
/** @type { (ok: boolean) => number } */
137-
finish(ok) {
124+
finish(_ok) {
138125
testNum += 1;
139-
if (ok) {
140-
passCount += 1;
141-
}
142126
return testNum;
143127
},
144-
/** @type { (thunk: () => Promise<void>) => Promise<void> } */
145-
async defer(thunk) {
146-
suitesToRun.push(thunk);
128+
/** @type { (name: string, thunk: () => Promise<void>) => void } */
129+
queue(name, thunk) {
130+
if (name in suitesToRun) {
131+
throw Error(`duplicate name ${name}`);
132+
}
133+
suitesToRun[name] = thunk;
134+
},
135+
testNames() {
136+
return keys(suitesToRun);
147137
},
148-
summary,
149-
async result() {
138+
/**
139+
* @param {string} name
140+
* @returns { Promise<void> }
141+
*/
142+
async run(name) {
150143
for await (const hook of beforeHooks) {
151144
await hook({ context });
152145
}
153-
beforeHooks = [];
154-
for await (const suite of suitesToRun) {
155-
await suite();
156-
}
157-
suitesToRun = [];
158146

159-
return summary();
147+
const suite = suitesToRun[name];
148+
await suite();
160149
},
161150
});
162151

@@ -169,17 +158,29 @@ function createHarness(send) {
169158
/**
170159
* @param {*} exc
171160
* @param {Expectation} expectation
172-
* @returns {boolean}
161+
* @returns {null | { expected: unknown, actual: unknown }}
173162
* @typedef {{ instanceOf: Function } | { message: string | RegExp }=} Expectation
174163
*/
175164
function checkExpectation(exc, expectation) {
176-
if (!expectation) return true;
177-
if ('instanceOf' in expectation) return exc instanceof expectation.instanceOf;
165+
if (!expectation) return null;
166+
if ('instanceOf' in expectation) {
167+
if (exc instanceof expectation.instanceOf) {
168+
return null;
169+
} else {
170+
return { expected: expectation.instanceOf, actual: exc };
171+
}
172+
}
178173
if ('message' in expectation) {
179174
const { message } = expectation;
180-
return typeof message === 'string'
181-
? message === exc.message
182-
: message.test(exc.message);
175+
const ok =
176+
typeof message === 'string'
177+
? message === exc.message
178+
: message.test(exc.message);
179+
if (ok) {
180+
return null;
181+
} else {
182+
return { actual: exc.message, expected: message };
183+
}
183184
}
184185
throw Error(`not implemented: ${JSON.stringify(expectation)}`);
185186
}
@@ -195,14 +196,8 @@ function checkExpectation(exc, expectation) {
195196
* @typedef {ReturnType<typeof makeTester>} Tester
196197
*/
197198
function makeTester(htest, out) {
198-
/** @type {number?} */
199-
let pending;
200-
201199
/** @type {(result: boolean, info?: string) => void} */
202200
function assert(result, info) {
203-
if (typeof pending === 'number') {
204-
pending -= 1;
205-
}
206201
const testNum = htest.finish(result);
207202
if (result) {
208203
out.ok(testNum, info);
@@ -222,10 +217,7 @@ function makeTester(htest, out) {
222217
const t = freeze({
223218
/** @param {number} count */
224219
plan(count) {
225-
pending = count;
226-
},
227-
get pending() {
228-
return pending;
220+
out.plan(count);
229221
},
230222
get context() {
231223
return htest.context;
@@ -289,7 +281,8 @@ function makeTester(htest, out) {
289281
fn();
290282
assert(false, message);
291283
} catch (ex) {
292-
assert(checkExpectation(ex, expectation), message);
284+
const delta = checkExpectation(ex, expectation);
285+
assert(!delta, `${message}: ${JSON.stringify(delta)}`);
293286
}
294287
},
295288
/** @type {(fn: () => unknown, message?: string) => void } */
@@ -298,7 +291,9 @@ function makeTester(htest, out) {
298291
fn();
299292
} catch (ex) {
300293
assert(false, message);
294+
return;
301295
}
296+
assert(true, message);
302297
},
303298
/** @type {(thrower: () => Promise<unknown>, expectation?: Expectation, message?: string) => Promise<void> } */
304299
async throwsAsync(
@@ -310,7 +305,8 @@ function makeTester(htest, out) {
310305
await (typeof thrower === 'function' ? thrower() : thrower);
311306
assert(false, message);
312307
} catch (ex) {
313-
assert(checkExpectation(ex, expectation), message);
308+
const delta = checkExpectation(ex, expectation);
309+
assert(!delta, `${message}: ${JSON.stringify(delta)}`);
314310
}
315311
},
316312
/** @type {(thrower: () => Promise<unknown>, message?: string) => Promise<void> } */
@@ -319,38 +315,44 @@ function makeTester(htest, out) {
319315
await (typeof nonThrower === 'function' ? nonThrower() : nonThrower);
320316
} catch (ex) {
321317
assert(false, message);
318+
return;
322319
}
320+
assert(true, message);
323321
},
324322
});
325323

326324
return t;
327325
}
328326

329-
/** @type {(label: string, run: (t: Tester) => Promise<void>, htestOpt: Harness?) => void } */
327+
/**
328+
* @param {string} label
329+
* @param {(t: Tester) => Promise<void>} run
330+
* @param {Harness?} htestOpt
331+
*/
330332
function test(label, run, htestOpt) {
331333
const htest = htestOpt || theHarness;
332334
if (!htest) throw Error('no harness');
333335

334-
htest.defer(async () => {
336+
htest.queue(label, async () => {
335337
const out = tapFormat(htest.send);
336338
const t = makeTester(htest, out);
337339
try {
338340
out.diagnostic('start', label);
339341
await run(t);
340342
out.diagnostic('end', label);
341343
} catch (ex) {
344+
console.log('FAIL (todo route console)', ex);
342345
t.fail(`${label} threw: ${ex.message}`);
343346
}
344-
const pending = t.pending;
345-
if (typeof pending === 'number' && pending !== 0) {
346-
t.fail(`bad plan: ${t.pending} still to go`);
347-
}
348347
});
349348
}
350349

350+
test.createHarness = createHarness;
351+
351352
// TODO: test.skip, test.failing
352353

353-
test.createHarness = createHarness;
354+
test.todo = _title => {};
355+
test.failing = (_title, _implementation) => {};
354356

355357
/** @type {(label: string, fn: () => Promise<void>) => void } */
356358
test.before = (label, fn) => {

0 commit comments

Comments
 (0)