diff --git a/.github/workflows/test-all-packages.yml b/.github/workflows/test-all-packages.yml index e54419671be..b1d1251b77a 100644 --- a/.github/workflows/test-all-packages.yml +++ b/.github/workflows/test-all-packages.yml @@ -398,6 +398,7 @@ jobs: # END-RESTORE-BOILERPLATE - name: yarn test (zoe) + timeout-minutes: 20 run: cd packages/zoe && yarn test env: ESM_DISABLE_CACHE: true diff --git a/packages/ERTP/package.json b/packages/ERTP/package.json index a41c8fd9c19..b2589fbc4df 100644 --- a/packages/ERTP/package.json +++ b/packages/ERTP/package.json @@ -14,7 +14,7 @@ "test": "yarn test:node && yarn test:xs", "test:node": "ava", "test:xs": "yarn test:xs-unit && yarn test:xs-worker", - "test:xs-unit": "node -r esm ../xsnap/src/avaXS.js test/unitTests/test-*.js", + "test:xs-unit": "ava-xs", "test:xs-worker": "WORKER_TYPE=xs-worker ava", "test:nyc": "nyc ava", "pretty-fix": "prettier --write '**/*.js'", @@ -66,6 +66,9 @@ "NEWS.md", "exported.js" ], + "ava-xs": { + "exclude": "swingsetTests" + }, "ava": { "files": [ "test/**/test-*.js" diff --git a/packages/xsnap/package.json b/packages/xsnap/package.json index 6825324a54f..d3da1f17a95 100644 --- a/packages/xsnap/package.json +++ b/packages/xsnap/package.json @@ -9,6 +9,7 @@ }, "main": "./src/xsnap.js", "bin": { + "ava-xs": "./src/ava-xs.js", "xsrepl": "./src/xsrepl" }, "scripts": { @@ -25,10 +26,12 @@ "test": "ava" }, "dependencies": { + "@agoric/assert": "^0.2.2-dev.0", "@agoric/bundle-source": "^1.2.1", "@agoric/eventual-send": "^0.13.2", "@agoric/install-ses": "^0.5.1", "esm": "^3.2.5", + "glob": "^7.1.6", "ses": "^0.12.3" }, "devDependencies": { diff --git a/packages/xsnap/src/ava-xs.js b/packages/xsnap/src/ava-xs.js new file mode 100755 index 00000000000..38807063f38 --- /dev/null +++ b/packages/xsnap/src/ava-xs.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node +/* global require, module */ +// @ts-check +const esmRequire = require('esm')(module); +const process = require('process'); +const { spawn } = require('child_process'); +const { type: osType } = require('os'); +const { promises } = require('fs'); +const path = require('path'); +const glob = require('glob'); + +esmRequire('@agoric/install-ses'); +const bundleSource = esmRequire('@agoric/bundle-source').default; + +const { main, makeBundleResolve } = esmRequire('./avaXS'); + +Promise.resolve() + .then(_ => + main(process.argv.slice(2), { + bundleSource, + spawn, + osType, + readFile: promises.readFile, + resolve: makeBundleResolve(path), + dirname: path.dirname, + glob, + }), + ) + .then(status => { + process.exit(status); + }) + .catch(err => { + console.error(err); + process.exit(1); + }); diff --git a/packages/xsnap/src/avaAssertXS.js b/packages/xsnap/src/avaAssertXS.js index d44f0f645b3..2804874e39b 100644 --- a/packages/xsnap/src/avaAssertXS.js +++ b/packages/xsnap/src/avaAssertXS.js @@ -105,25 +105,12 @@ let theHarness = null; // ISSUE: ambient */ function createHarness(send) { let testNum = 0; - let passCount = 0; /** @type {((ot: { context: Object }) => Promise)[]} */ - let beforeHooks = []; - /** @type {(() => Promise)[]} */ - let suitesToRun = []; + const beforeHooks = []; + /** @type {Record Promise>} */ + const suitesToRun = {}; const context = {}; - /** - * @returns { Summary } - * @typedef {import('./avaXS').Summary} Summary - */ - function summary() { - return { - pass: passCount, - fail: testNum - passCount, - total: testNum, - }; - } - const it = freeze({ send, get context() { @@ -134,29 +121,31 @@ function createHarness(send) { beforeHooks.push(hook); }, /** @type { (ok: boolean) => number } */ - finish(ok) { + finish(_ok) { testNum += 1; - if (ok) { - passCount += 1; - } return testNum; }, - /** @type { (thunk: () => Promise) => Promise } */ - async defer(thunk) { - suitesToRun.push(thunk); + /** @type { (name: string, thunk: () => Promise) => void } */ + queue(name, thunk) { + if (name in suitesToRun) { + throw Error(`duplicate name ${name}`); + } + suitesToRun[name] = thunk; + }, + testNames() { + return keys(suitesToRun); }, - summary, - async result() { + /** + * @param {string} name + * @returns { Promise } + */ + async run(name) { for await (const hook of beforeHooks) { await hook({ context }); } - beforeHooks = []; - for await (const suite of suitesToRun) { - await suite(); - } - suitesToRun = []; - return summary(); + const suite = suitesToRun[name]; + await suite(); }, }); @@ -169,17 +158,29 @@ function createHarness(send) { /** * @param {*} exc * @param {Expectation} expectation - * @returns {boolean} + * @returns {null | { expected: unknown, actual: unknown }} * @typedef {{ instanceOf: Function } | { message: string | RegExp }=} Expectation */ function checkExpectation(exc, expectation) { - if (!expectation) return true; - if ('instanceOf' in expectation) return exc instanceof expectation.instanceOf; + if (!expectation) return null; + if ('instanceOf' in expectation) { + if (exc instanceof expectation.instanceOf) { + return null; + } else { + return { expected: expectation.instanceOf, actual: exc }; + } + } if ('message' in expectation) { const { message } = expectation; - return typeof message === 'string' - ? message === exc.message - : message.test(exc.message); + const ok = + typeof message === 'string' + ? message === exc.message + : message.test(exc.message); + if (ok) { + return null; + } else { + return { actual: exc.message, expected: message }; + } } throw Error(`not implemented: ${JSON.stringify(expectation)}`); } @@ -195,14 +196,8 @@ function checkExpectation(exc, expectation) { * @typedef {ReturnType} Tester */ function makeTester(htest, out) { - /** @type {number?} */ - let pending; - /** @type {(result: boolean, info?: string) => void} */ function assert(result, info) { - if (typeof pending === 'number') { - pending -= 1; - } const testNum = htest.finish(result); if (result) { out.ok(testNum, info); @@ -222,10 +217,7 @@ function makeTester(htest, out) { const t = freeze({ /** @param {number} count */ plan(count) { - pending = count; - }, - get pending() { - return pending; + out.plan(count); }, get context() { return htest.context; @@ -289,7 +281,8 @@ function makeTester(htest, out) { fn(); assert(false, message); } catch (ex) { - assert(checkExpectation(ex, expectation), message); + const delta = checkExpectation(ex, expectation); + assert(!delta, `${message}: ${JSON.stringify(delta)}`); } }, /** @type {(fn: () => unknown, message?: string) => void } */ @@ -298,7 +291,9 @@ function makeTester(htest, out) { fn(); } catch (ex) { assert(false, message); + return; } + assert(true, message); }, /** @type {(thrower: () => Promise, expectation?: Expectation, message?: string) => Promise } */ async throwsAsync( @@ -310,7 +305,8 @@ function makeTester(htest, out) { await (typeof thrower === 'function' ? thrower() : thrower); assert(false, message); } catch (ex) { - assert(checkExpectation(ex, expectation), message); + const delta = checkExpectation(ex, expectation); + assert(!delta, `${message}: ${JSON.stringify(delta)}`); } }, /** @type {(thrower: () => Promise, message?: string) => Promise } */ @@ -319,19 +315,25 @@ function makeTester(htest, out) { await (typeof nonThrower === 'function' ? nonThrower() : nonThrower); } catch (ex) { assert(false, message); + return; } + assert(true, message); }, }); return t; } -/** @type {(label: string, run: (t: Tester) => Promise, htestOpt: Harness?) => void } */ +/** + * @param {string} label + * @param {(t: Tester) => Promise} run + * @param {Harness?} htestOpt + */ function test(label, run, htestOpt) { const htest = htestOpt || theHarness; if (!htest) throw Error('no harness'); - htest.defer(async () => { + htest.queue(label, async () => { const out = tapFormat(htest.send); const t = makeTester(htest, out); try { @@ -339,18 +341,18 @@ function test(label, run, htestOpt) { await run(t); out.diagnostic('end', label); } catch (ex) { + console.log('FAIL (todo route console)', ex); t.fail(`${label} threw: ${ex.message}`); } - const pending = t.pending; - if (typeof pending === 'number' && pending !== 0) { - t.fail(`bad plan: ${t.pending} still to go`); - } }); } +test.createHarness = createHarness; + // TODO: test.skip, test.failing -test.createHarness = createHarness; +test.todo = _title => {}; +test.failing = (_title, _implementation) => {}; /** @type {(label: string, fn: () => Promise) => void } */ test.before = (label, fn) => { diff --git a/packages/xsnap/src/avaHandler.js b/packages/xsnap/src/avaHandler.js index 9479aebc855..cafbb9a0914 100644 --- a/packages/xsnap/src/avaHandler.js +++ b/packages/xsnap/src/avaHandler.js @@ -19,7 +19,9 @@ const encoder = new TextEncoder(); const decoder = new TextDecoder(); /** - * @param { TapMessage | { bundleSource: [string, ...unknown[]] } | Summary } item + * @param { { testNames: string[] } | + * { bundleSource: [string, ...unknown[]] } | + * TapMessage | Summary } item * @typedef {import('./avaXS').Summary} Summary */ function send(item) { @@ -36,7 +38,8 @@ const bundleSource = async (startFilename, ...args) => { return JSON.parse(decoder.decode(msg)); }; -const harness = test.createHarness(send); +const harness = test.createHarness(send); // ISSUE: global mutable state + const testRequire = function require(specifier) { switch (specifier) { case 'ava': @@ -53,36 +56,61 @@ const testRequire = function require(specifier) { } }; -function handler(msg) { - const src = decoder.decode(msg); - // @ts-ignore How do I get ses types in scope?!?!?! - const c = new Compartment({ - require: testRequire, - __dirname, - __filename, - console, - // @ts-ignore - assert, - harden, - // @ts-ignore - HandledPromise, - TextEncoder, - TextDecoder, - }); - try { - c.evaluate(`(${src}\n)()`); - } catch (ex) { - send({ status: 'not ok', message: `running test script: ${ex.message}` }); +/** @param {ArrayBuffer} rawMessage */ +function handler(rawMessage) { + /** + * @type {{ method: 'loadScript', source: string } | { method: 'runTest', name: string }} + */ + const msg = JSON.parse(decoder.decode(rawMessage)); + + switch (msg.method) { + case 'loadScript': { + const { source } = msg; + const virtualObjectGlobals = + // @ts-ignore + // eslint-disable-next-line no-undef + typeof makeKind !== 'undefined' ? { makeKind, makeWeakStore } : {}; + // @ts-ignore How do I get ses types in scope?!?!?! + const c = new Compartment({ + require: testRequire, + __dirname, + __filename, + console, + // @ts-ignore + assert, + // @ts-ignore + HandledPromise, + TextEncoder, + TextDecoder, + ...virtualObjectGlobals, + }); + try { + c.evaluate(`(${source}\n)()`); + send({ testNames: harness.testNames() }); + } catch (ex) { + send({ + status: 'not ok', + message: `running test script: ${ex.message}`, + }); + } + break; + } + + case 'runTest': { + const { name } = msg; + harness.run(name).catch(ex => + send({ + status: 'not ok', + message: `${name} threw: ${ex.message}`, + }), + ); + break; + } + + default: + console.log('bad method', msg); } - harness - .result() - .then(send) - .catch(ex => - send({ - status: 'not ok', - message: `getting test results: ${ex.message}`, - }), - ); + return undefined; } globalThis.handleCommand = harden(handler); diff --git a/packages/xsnap/src/avaXS.js b/packages/xsnap/src/avaXS.js index a7e4da56848..d940e4a4956 100644 --- a/packages/xsnap/src/avaXS.js +++ b/packages/xsnap/src/avaXS.js @@ -1,4 +1,3 @@ -/* global module require process __filename */ /* avaXS - ava style test runner for XS Usage: @@ -6,11 +5,13 @@ Usage: node -r esm avaXS.js [--debug] test-*.js */ +/* global __filename */ // @ts-check /* eslint-disable no-await-in-loop */ import '@agoric/install-ses'; +import { assert, details as X, q } from '@agoric/assert'; import { xsnap } from './xsnap'; // scripts for use in xsnap subprocesses @@ -39,6 +40,8 @@ const externals = [ const encoder = new TextEncoder(); const decoder = new TextDecoder(); +const { keys } = Object; + /** * Run one test script in an xsnap subprocess. * @@ -67,14 +70,12 @@ const decoder = new TextDecoder(); * @param {{ * spawnXSnap: (opts: object) => XSnap, * bundleSource: (...args: [string, ...unknown[]]) => Promise, - * resolve: typeof import('path').resolve, + * resolve: ResolveFn, * dirname: typeof import('path').dirname, * }} io - * @returns {Promise<{ - * qty: number, - * byStatus: Record - * }>} quantity of tests run and breakdown by status + * @returns {Promise} * + * @typedef {{ total: number, pass: number, fail: { filename: string, name: string }[] }} TestResults * @typedef { 'ok' | 'not ok' | 'SKIP' } Status * @typedef {ReturnType} XSnap */ @@ -85,10 +86,14 @@ async function runTestScript( { spawnXSnap, bundleSource, resolve, dirname }, ) { const testBundle = await bundleSource(filename, 'getExport', { externals }); - - let qty = 0; - const byStatus = { ok: 0, 'not ok': 0, SKIP: 0 }; + let assertionStatus = { ok: 0, 'not ok': 0, SKIP: 0 }; + /** @type { number | null } */ + let plan = null; + /** @type {TestResults} */ + const testStatus = { total: 0, pass: 0, fail: [] }; let label = ''; + /** @type { string[] } */ + let testNames = []; /** * Handle callback "command" from xsnap subprocess. @@ -99,14 +104,19 @@ async function runTestScript( /** * See also send() in avaHandler.js * - * @type { TapMessage | { bundleSource: [string, ...unknown[]] } | Summary } + * @type { TapMessage | { testNames: string[] } | { bundleSource: [string, ...unknown[]] } | Summary } */ const msg = JSON.parse(decoder.decode(message)); // console.log(input, msg, qty, byStatus); + if ('testNames' in msg) { + testNames = msg.testNames; + } + if ('bundleSource' in msg) { const [startFilename, ...rest] = msg.bundleSource; - const bundle = await bundleSource(startFilename, ...rest); + // see also makeBundleResolve() below + const bundle = await bundleSource(resolve(startFilename), ...rest); return encoder.encode(JSON.stringify(bundle)); } @@ -117,12 +127,14 @@ async function runTestScript( } } if ('status' in msg) { - byStatus[msg.status] += 1; - qty += 1; + assertionStatus[msg.status] += 1; if (msg.status === 'not ok') { console.warn({ ...msg, filename, label }); } } + if ('plan' in msg) { + plan = msg.plan; + } return encoder.encode('null'); } @@ -143,16 +155,106 @@ async function runTestScript( await worker.evaluate(pathGlobalsKludge); // Send the test script to avaHandler. - await worker.issueStringCommand(testBundle.source); + await worker.issueStringCommand( + JSON.stringify({ method: 'loadScript', source: testBundle.source }), + ); + + for (const name of testNames) { + assertionStatus = { ok: 0, 'not ok': 0, SKIP: 0 }; + plan = null; + await worker.issueStringCommand( + JSON.stringify({ method: 'runTest', name }), + ); + testStatus.total += 1; + + const pending = typeof plan === 'number' ? plan - assertionStatus.ok : 0; + + const pass = + pending === 0 && + assertionStatus.ok > 0 && + assertionStatus['not ok'] === 0; + if (pass) { + testStatus.pass += 1; + } else { + testStatus.fail.push({ filename, name }); + } + console.log(pass ? '.' : 'F', filename, name); + if (pending !== 0) { + console.warn(`bad plan: ${pending} still to go`); + } + } } finally { await worker.terminate(); } - return { qty, byStatus }; + return testStatus; } /** - * @param {string[]} argv + * Get ava / ava-xs config from package.json + * + * @param { string[] } args + * @param {Object} options + * @param {string} [options.packageFilename] + * @param {{ + * readFile: typeof import('fs').promises.readFile, + * glob: typeof import('glob') + * }} io + * @returns {Promise} + * + * @typedef {Object} AvaXSConfig + * @property {string[]} files - files from args or else ava.files + * @property {string[]} require - specifiers of modules to run before each test script + * @property {string[]=} exclude - files containing any of these should be skipped + */ +async function avaConfig(args, options, { glob, readFile }) { + const { packageFilename = 'package.json' } = options; + + const txt = await readFile(packageFilename, 'utf-8'); + const pkgMeta = JSON.parse(txt); + + if (!pkgMeta.ava) { + return { files: [], require: [] }; + } + const expected = ['files', 'require']; + const unsupported = keys(pkgMeta.ava).filter(k => !expected.includes(k)); + if (unsupported.length > 0) { + console.warn(X`ava-xs does not support ava options: ${q(unsupported)}`); + } + const { files: filePatterns, require } = pkgMeta.ava; + let { exclude } = pkgMeta['ava-xs'] || {}; + if (typeof exclude === 'string') { + exclude = [exclude]; + } + assert( + !exclude || Array.isArray(exclude), + X`ava-xs.exclude: expected array or string: ${q(exclude)}`, + ); + + /** + * @param { string } pattern + * @returns { Promise } + */ + const globFiles = pattern => + new Promise((res, rej) => + glob(pattern, {}, (err, matches) => (err ? rej(err) : res(matches))), + ); + assert( + Array.isArray(filePatterns), + X`ava.files: expected Array: ${q(filePatterns)}`, + ); + const files = (await Promise.all(filePatterns.map(globFiles))).flat(); + + assert( + Array.isArray(require), + X`ava.requires: expected Array: ${q(require)}`, + ); + const config = { files: args.length > 0 ? args : files, require, exclude }; + return config; +} + +/** + * @param {string[]} args - CLI args (excluding node interpreter, script name) * @param {{ * bundleSource: typeof import('@agoric/bundle-source').default, * spawn: typeof import('child_process')['spawn'], @@ -160,16 +262,23 @@ async function runTestScript( * readFile: typeof import('fs')['promises']['readFile'], * resolve: typeof import('path').resolve, * dirname: typeof import('path').dirname, + * glob: typeof import('glob'), * }} io */ -async function main( - argv, - { bundleSource, spawn, osType, readFile, resolve, dirname }, +export async function main( + args, + { bundleSource, spawn, osType, readFile, resolve, dirname, glob }, ) { - const args = argv.slice(2); const debug = args[0] === '--debug'; - const files = debug ? args.slice(1) : args; + const verbose = ['--verbose', '-v'].includes(args[0]) || debug; + const fileArgs = debug || verbose ? args.slice(1) : args; + const { files, require, exclude } = await avaConfig( + fileArgs, + {}, + { readFile, glob }, + ); + /** @param {Record} opts */ const spawnXSnap = opts => xsnap({ ...opts, @@ -182,56 +291,81 @@ async function main( }); /** - * we only use import() in type annotations + * SES objects to `import(...)` + * avaAssert and avaHandler only use import() in type comments * * @param { string } src */ const hideImport = src => src.replace(/import\(/g, ''); + const requiredBundles = await Promise.all( + require + .filter(specifier => !['esm', ...externals].includes(specifier)) + .map(specifier => bundleSource(specifier, 'getExport', { externals })), + ); + const requiredScripts = requiredBundles.map( + ({ source }) => `(${source}\n)()`, + ); + const preamble = [ await asset(SESboot, readFile), + ...requiredScripts, hideImport(await asset(avaAssert, readFile)), hideImport(await asset(avaHandler, readFile)), ]; - let totalTests = 0; - const stats = { ok: 0, 'not ok': 0, SKIP: 0 }; + /** @type { TestResults } */ + const stats = { total: 0, pass: 0, fail: [] }; for (const filename of files) { - console.log('# test script:', filename); + if (exclude && exclude.filter(s => filename.match(s)).length > 0) { + console.warn('# SKIP test excluded on XS', filename); + // eslint-disable-next-line no-continue + continue; + } else if (verbose) { + console.log('# test script:', filename); + } - const { qty, byStatus } = await runTestScript(filename, preamble, debug, { + const results = await runTestScript(filename, preamble, debug, { spawnXSnap, bundleSource, resolve, dirname, }); - totalTests += qty; - Object.entries(byStatus).forEach(([status, q]) => { - stats[status] += q; - }); + stats.total += results.total; + stats.pass += results.pass; + results.fail.forEach(info => stats.fail.push(info)); } - console.log({ totalTests, stats }); - return stats['not ok'] > 0 ? 1 : 0; + console.log(stats.pass, 'tests passed'); + if (stats.fail.length > 0) { + console.warn(stats.fail.length, 'tests failed'); + for (const { filename, name } of stats.fail) { + console.log('F', filename, name); + } + } + return stats.fail.length > 0 ? 1 : 0; } -/* eslint-disable global-require */ -if (require.main === module) { - main([...process.argv], { - bundleSource: require('@agoric/bundle-source').default, - spawn: require('child_process').spawn, - osType: require('os').type, - readFile: require('fs').promises.readFile, - resolve: require('path').resolve, - dirname: require('path').dirname, - }) - .then(status => { - process.exit(status); - }) - .catch(err => { - console.error(err); - process.exit(1); - }); +/** + * Fix path resolution for test contract bundles. + * + * @param {typeof import('path')} path + * @returns {ResolveFn} + * @typedef {typeof import('path').resolve } ResolveFn + */ +export function makeBundleResolve(path) { + const bundleRoots = [ + { basename: 'zcfTesterContract', pkg: 'zoe', dir: 'test/unitTests/zcf' }, + ]; + return function resolveWithFixes(seg0, ...pathSegments) { + for (const { basename, pkg, dir } of bundleRoots) { + if (seg0.indexOf(basename) >= 0) { + const [sdk] = seg0.split(`packages${path.sep}${pkg}${path.sep}`, 1); + seg0 = path.join(sdk, 'packages', pkg, ...dir.split('/'), basename); + } + } + return path.resolve(seg0, ...pathSegments); + }; } diff --git a/packages/zoe/package.json b/packages/zoe/package.json index b5c280ad7bc..70ff9023145 100644 --- a/packages/zoe/package.json +++ b/packages/zoe/package.json @@ -11,8 +11,12 @@ }, "scripts": { "build": "yarn build-zcfBundle", - "test": "ava --verbose", + "test": "yarn test:node && yarn test:xs-unit", + "test:node": "ava --verbose", "test:nyc": "nyc ava", + "test:xs-unit": "ava-xs --verbose", + "test:xs-unit-debug": "ava-xs --debug", + "test:xs-worker": "WORKER_TYPE=xs-worker ava", "build-zcfBundle": "node -r esm scripts/build-zcfBundle.js", "lint-fix": "yarn lint --fix", "lint-check": "yarn lint", @@ -68,6 +72,29 @@ "exported.js", "NEWS.md" ], + "ava-xs": { + "exclude": [ + "# eek! SIGSEGV in GC https://github.com/Moddable-OpenSource/moddable/issues/592", + "test-loan-e2e.js", + "# another SEGV, but not in --debug so I cannot get a stack trace", + "contracts/test-callSpread-calculation", + "# another SEGV; not investigated", + "loan/test-borrow.js", + "test-fakePriceAuthority.js", + "test-offerSafety", + "test-scriptedOracle", + "test-zoeHelpers", + "# multiple instances of automaticRefund for the same Zoe bad plan: 6 still to go", + "test-automaticRefund", + "test-secondPriceAuction.js", + "test-escrowToVote", + "test-coveredCall", + "# offerOk threw: \"instance\" not found: (an object)'", + "test-otcDesk.js", + "# We use WORKER_TYPE=xs ava to run these...", + "swingsetTests" + ] + }, "ava": { "files": [ "test/**/test-*.js" diff --git a/packages/zoe/test/unitTests/contractSupport/test-percentSupport.js b/packages/zoe/test/unitTests/contractSupport/test-percentSupport.js index b2996b1d107..f252098e5c8 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-percentSupport.js +++ b/packages/zoe/test/unitTests/contractSupport/test-percentSupport.js @@ -37,8 +37,8 @@ test('ratio - ALL', t => { amountsEqual( t, - multiplyBy(moe(100_000), make100Percent(brand)), - moe(100_000), + multiplyBy(moe(100000), make100Percent(brand)), + moe(100000), brand, ); }); diff --git a/packages/zoe/test/unitTests/contractSupport/test-ratio.js b/packages/zoe/test/unitTests/contractSupport/test-ratio.js index eee7741e44e..9e8bafbbf90 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-ratio.js +++ b/packages/zoe/test/unitTests/contractSupport/test-ratio.js @@ -77,7 +77,7 @@ test('ratio - different brands', t => { const ast = astAmountMath.make; const convertToMoe = makeRatioFromAmounts(moe(1), astAmountMath.make(3)); - amountsEqual(t, multiplyBy(ast(10_000), convertToMoe), moe(3333), moeBrand); + amountsEqual(t, multiplyBy(ast(10000), convertToMoe), moe(3333), moeBrand); }); test('ratio - brand mismatch', t => { @@ -87,10 +87,10 @@ test('ratio - brand mismatch', t => { const ast = astAmountMath.make; const convertToMoe = makeRatioFromAmounts(moe(1), astAmountMath.make(3)); - t.throws(() => divideBy(ast(10_000), convertToMoe), { + t.throws(() => divideBy(ast(10000), convertToMoe), { message: /amount's brand .* must match ratio's numerator .*/, }); - t.throws(() => multiplyBy(moe(10_000), convertToMoe), { + t.throws(() => multiplyBy(moe(10000), convertToMoe), { message: /amount's brand .* must match ratio's denominator .*/, }); }); @@ -102,10 +102,10 @@ test.failing('ratio - brand mismatch & details', t => { const ast = astAmountMath.make; const convertToMoe = makeRatioFromAmounts(moe(1), astAmountMath.make(3)); - t.throws(() => divideBy(ast(10_000), convertToMoe), { + t.throws(() => divideBy(ast(10000), convertToMoe), { message: `amount's brand "ast" must match ratio's numerator "moe"`, }); - t.throws(() => multiplyBy(moe(10_000), convertToMoe), { + t.throws(() => multiplyBy(moe(10000), convertToMoe), { message: `amount's brand "moe" must match ratio's denominator "ast"`, }); }); diff --git a/packages/zoe/test/unitTests/contracts/test-barter.js b/packages/zoe/test/unitTests/contracts/test-barter.js index ad2a6c0bd99..39543f43c3f 100644 --- a/packages/zoe/test/unitTests/contracts/test-barter.js +++ b/packages/zoe/test/unitTests/contracts/test-barter.js @@ -114,11 +114,13 @@ test('barter with valid offers', async t => { // Alice sold all of her moola t.deepEqual(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - // Alice had 0 moola and 4 simoleans. - assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)); - assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(4)); - - // Bob had 3 moola and 3 simoleans. - assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(3)); - assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(3)); + await Promise.all([ + // Alice had 0 moola and 4 simoleans. + assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)), + assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(4)), + + // Bob had 3 moola and 3 simoleans. + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(3)), + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(3)), + ]); }); diff --git a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js index 63c22e6fc43..09193f503d6 100644 --- a/packages/zoe/test/unitTests/contracts/test-simpleExchange.js +++ b/packages/zoe/test/unitTests/contracts/test-simpleExchange.js @@ -86,7 +86,7 @@ test('simpleExchange with valid offers', async t => { alicePayments, ); - E(aliceNotifier) + const part1 = E(aliceNotifier) .getUpdateSince() .then(({ value: afterAliceOrders, updateCount: afterAliceCount }) => { t.deepEqual( @@ -104,7 +104,7 @@ test('simpleExchange with valid offers', async t => { ); t.is(afterAliceCount, 4); - aliceNotifier.getUpdateSince(afterAliceCount).then(update => { + return aliceNotifier.getUpdateSince(afterAliceCount).then(update => { t.falsy(update.value.sells[0], 'accepted offer from Bob'); t.is(update.updateCount, 5); }); @@ -182,15 +182,18 @@ test('simpleExchange with valid offers', async t => { // Alice sold all of her moola t.deepEqual(await moolaIssuer.getAmountOf(aliceMoolaPayout), moola(0)); - // 6: Alice deposits her payout to ensure she can - // Alice had 0 moola and 4 simoleans. - assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)); - assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(4)); - - // 7: Bob deposits his original payments to ensure he can - // Bob had 3 moola and 3 simoleans. - assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(3)); - assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(3)); + await Promise.all([ + part1, + // 6: Alice deposits her payout to ensure she can + // Alice had 0 moola and 4 simoleans. + assertPayoutAmount(t, moolaIssuer, aliceMoolaPayout, moola(0)), + assertPayoutAmount(t, simoleanIssuer, aliceSimoleanPayout, simoleans(4)), + + // 7: Bob deposits his original payments to ensure he can + // Bob had 3 moola and 3 simoleans. + assertPayoutAmount(t, moolaIssuer, bobMoolaPayout, moola(3)), + assertPayoutAmount(t, simoleanIssuer, bobSimoleanPayout, simoleans(3)), + ]); }); test('simpleExchange with multiple sell offers', async t => { @@ -409,9 +412,9 @@ test('simpleExchange with non-fungible assets', async t => { // Bob has an empty CryptoCat purse, and the Spell of Binding he wanted. const noCats = amountMaths.get('cc').getEmpty(); const noRpgItems = amountMaths.get('rpg').getEmpty(); - assertPayoutAmount(t, rpgIssuer, aliceRpgPayout, noRpgItems); + await assertPayoutAmount(t, rpgIssuer, aliceRpgPayout, noRpgItems); const cheshireCatAmount = cryptoCats(harden(['Cheshire Cat'])); - assertPayoutAmount(t, ccIssuer, aliceCcPayout, cheshireCatAmount); - assertPayoutAmount(t, rpgIssuer, bobRpgPayout, rpgItems(spell)); - assertPayoutAmount(t, ccIssuer, bobCcPayout, noCats); + await assertPayoutAmount(t, ccIssuer, aliceCcPayout, cheshireCatAmount); + await assertPayoutAmount(t, rpgIssuer, bobRpgPayout, rpgItems(spell)); + await assertPayoutAmount(t, ccIssuer, bobCcPayout, noCats); }); diff --git a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js index 1c17ee42b72..94063ecc77a 100644 --- a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js +++ b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js @@ -256,8 +256,8 @@ test(`zoeHelper with zcf - assertIssuerKeywords`, async t => { t.throws( () => assertIssuerKeywords(zcf), { - message: - 'undefined is not iterable (cannot read property Symbol(Symbol.iterator))', + // host-defined error message differs between node and XS. (agoric-sdk/issues/1780) + message: /undefined/, }, 'no expected keywordRecord gets an error', );