diff --git a/packages/ERTP/test/unitTests/test-inputValidation.js b/packages/ERTP/test/unitTests/test-inputValidation.js index ed7d37ff25e..8c62e5a2f9e 100644 --- a/packages/ERTP/test/unitTests/test-inputValidation.js +++ b/packages/ERTP/test/unitTests/test-inputValidation.js @@ -19,7 +19,7 @@ test('makeIssuerKit bad assetKind', async t => { // @ts-expect-error Intentional wrong type for testing t.throws(() => makeIssuerKit('myTokens', 'somethingWrong'), { message: - /The assetKind "somethingWrong" must be one of \["copyBag","copySet","nat","set"\]/, + 'The assetKind "somethingWrong" must be one of ["copyBag","copySet","nat","set"]', }); }); @@ -32,7 +32,10 @@ test('makeIssuerKit bad displayInfo.decimalPlaces', async t => { // @ts-expect-error Intentional wrong type for testing harden({ decimalPlaces: 'hello' }), ), - { message: /^displayInfo: "hello" - Must be >= -100$/ }, + { + message: + 'displayInfo: optional-parts: decimalPlaces: "hello" - Must be >= -100', + }, ); t.throws( @@ -58,13 +61,19 @@ test('makeIssuerKit bad displayInfo.decimalPlaces', async t => { t.throws( () => makeIssuerKit('myTokens', AssetKind.NAT, harden({ decimalPlaces: 101 })), - { message: /^displayInfo: 101 - Must be <= 100$/ }, + { + message: + 'displayInfo: optional-parts: decimalPlaces: 101 - Must be <= 100', + }, ); t.throws( () => makeIssuerKit('myTokens', AssetKind.NAT, harden({ decimalPlaces: -101 })), - { message: /^displayInfo: -101 - Must be >= -100$/ }, + { + message: + 'displayInfo: optional-parts: decimalPlaces: -101 - Must be >= -100', + }, ); }); @@ -81,7 +90,7 @@ test('makeIssuerKit bad displayInfo.assetKind', async t => { ), { message: - /^displayInfo: "something" - Must match one of \["nat","set","copySet","copyBag"\]$/, + 'displayInfo: optional-parts: assetKind: "something" - Must match one of ["nat","set","copySet","copyBag"]', }, ); }); @@ -99,7 +108,7 @@ test('makeIssuerKit bad displayInfo.whatever', async t => { ), { message: - /^displayInfo: Remainder \{"whatever":"something"\} - Must match \{\}$/, + 'displayInfo: rest-parts: {"whatever":"something"} - Must be: {}', }, ); }); @@ -114,8 +123,7 @@ test('makeIssuerKit malicious displayInfo', async t => { 'badness', ), { - message: - /^displayInfo: "badness" - Must have shape of base: "copyRecord"$/, + message: 'displayInfo: string "badness" - Must be a copyRecord', }, ); }); diff --git a/packages/ERTP/test/unitTests/test-issuerObj.js b/packages/ERTP/test/unitTests/test-issuerObj.js index 1f36cc9b7a2..9f3202a423f 100644 --- a/packages/ERTP/test/unitTests/test-issuerObj.js +++ b/packages/ERTP/test/unitTests/test-issuerObj.js @@ -44,8 +44,7 @@ test('bad display info', t => { const displayInfo = harden({ somethingUnexpected: 3 }); // @ts-expect-error deliberate invalid arguments for testing t.throws(() => makeIssuerKit('fungible', AssetKind.NAT, displayInfo), { - message: - /^displayInfo: Remainder \{"somethingUnexpected":3\} - Must match \{\}$/, + message: 'displayInfo: rest-parts: {"somethingUnexpected":3} - Must be: {}', }); }); @@ -434,7 +433,7 @@ test('issuer.combine bad payments', async t => { await t.throwsAsync( () => E(issuer).combine(payments), { - message: /"\[Alleged: other fungible payment\]"/, + message: /.* "\[Alleged: other fungible payment\]"/, }, 'payment from other mint is not found', ); diff --git a/packages/ERTP/test/unitTests/test-mintObj.js b/packages/ERTP/test/unitTests/test-mintObj.js index ff6325eb8b4..9f1d3342ef4 100644 --- a/packages/ERTP/test/unitTests/test-mintObj.js +++ b/packages/ERTP/test/unitTests/test-mintObj.js @@ -45,7 +45,8 @@ test('mint.mintPayment set w strings AssetKind', async t => { const badAmount = AmountMath.make(brand, harden([['badElement']])); t.throws(() => mint.mintPayment(badAmount), { - message: / - Must have passStyle or tag "string"/, + message: + 'minted amount: value: [0]: copyArray ["badElement"] - Must be a string', }); }); diff --git a/packages/inter-protocol/test/amm/vpool-xyk-amm/test-liquidity.js b/packages/inter-protocol/test/amm/vpool-xyk-amm/test-liquidity.js index 3427b518329..7c84ee9cc73 100644 --- a/packages/inter-protocol/test/amm/vpool-xyk-amm/test-liquidity.js +++ b/packages/inter-protocol/test/amm/vpool-xyk-amm/test-liquidity.js @@ -521,7 +521,7 @@ test('add wrong liquidity', async t => { ); await t.throwsAsync(() => E(addLiquiditySeatBreaking).getOfferResult(), { - message: /liquidity brand must be "\[Alleged: MoolaLiquidity brand\]"/, + message: 'liquidity brand must be "[Alleged: MoolaLiquidity brand]"', }); await E(addLiquiditySeatBreaking).getPayouts(); diff --git a/packages/inter-protocol/test/vaultFactory/test-vaultFactory.js b/packages/inter-protocol/test/vaultFactory/test-vaultFactory.js index cb6e3310af6..bad4c90e5a5 100644 --- a/packages/inter-protocol/test/vaultFactory/test-vaultFactory.js +++ b/packages/inter-protocol/test/vaultFactory/test-vaultFactory.js @@ -2510,7 +2510,10 @@ test('addVaultType: extra, unexpected params', async t => { await t.throwsAsync( // @ts-expect-error bad args E(vaultFactory).addVaultType(chit.issuer, 'Chit', missingParams), - { message: /Must have same property names/ }, + { + message: + /initialParamValues: required-parts: .* - Must have missing properties \["interestRate"\]/, + }, ); const actual = await E(vaultFactory).addVaultType( diff --git a/packages/store/src/patterns/match-helpers.js b/packages/store/src/patterns/match-helpers.js index 9750b54ed97..e03a19fd36e 100644 --- a/packages/store/src/patterns/match-helpers.js +++ b/packages/store/src/patterns/match-helpers.js @@ -5,11 +5,14 @@ const { details: X } = assert; /** * @param {Error} innerErr - * @param {string} label + * @param {string|number} label * @param {ErrorConstructor=} ErrorConstructor * @returns {never} */ export const throwLabeled = (innerErr, label, ErrorConstructor = undefined) => { + if (typeof label === 'number') { + label = `[${label}]`; + } const outerErr = assert.error( `${label}: ${innerErr.message}`, ErrorConstructor, @@ -23,14 +26,13 @@ harden(throwLabeled); * @template A,R * @param {(...args: A) => R} func * @param {A} args - * @param {string} [label] + * @param {string|number} [label] * @returns {R} */ export const applyLabelingError = (func, args, label = undefined) => { if (label === undefined) { return func(...args); } - assert.typeof(label, 'string'); let result; try { result = func(...args); diff --git a/packages/store/src/patterns/patternMatchers.js b/packages/store/src/patterns/patternMatchers.js index 5a674a60372..24794438113 100644 --- a/packages/store/src/patterns/patternMatchers.js +++ b/packages/store/src/patterns/patternMatchers.js @@ -255,15 +255,28 @@ const makePatternKit = () => { // /////////////////////// matches /////////////////////////////////////////// - /** @type {CheckMatches} */ - const checkMatches = (specimen, patt, check = x => x) => { + /** + * @param {Passable} specimen + * @param {Pattern} pattern + * @param {Checker=} check + * @param {string|number} [label] + * @returns {boolean} + */ + const checkMatches = (specimen, pattern, check = x => x, label = undefined) => + // eslint-disable-next-line no-use-before-define + applyLabelingError(checkMatchesInternal, [specimen, pattern, check], label); + + /** + * @param {Passable} specimen + * @param {Pattern} patt + * @param {Checker=} check + * @returns {boolean} + */ + const checkMatchesInternal = (specimen, patt, check = x => x) => { if (isKey(patt)) { // Takes care of all patterns which are keys, so the rest of this // logic can assume patterns that are not key. - return check( - keyEQ(specimen, patt), - X`${specimen} - Must be equivalent to: ${patt}`, - ); + return check(keyEQ(specimen, patt), X`${specimen} - Must be: ${patt}`); } assertPattern(patt); const specStyle = passStyleOf(specimen); @@ -283,7 +296,7 @@ const makePatternKit = () => { X`Array ${specimen} - Must be as long as copyArray pattern: ${patt}`, ); } - return patt.every((p, i) => checkMatches(specimen[i], p, check)); + return patt.every((p, i) => checkMatches(specimen[i], p, check, i)); } case 'copyRecord': { if (specStyle !== 'copyRecord') { @@ -295,12 +308,32 @@ const makePatternKit = () => { const [specNames, specValues] = recordParts(specimen); const [pattNames, pattValues] = recordParts(patt); if (!keyEQ(specNames, pattNames)) { + const specNameSet = new Set(specNames); + const missing = pattNames.filter(name => !specNameSet.has(name)); + if (missing.length >= 1) { + return check( + false, + X`${specimen} - Must have missing properties ${q(missing)}`, + ); + } + const passNameSet = new Set(pattNames); + const unexpected = specNames.filter(name => !passNameSet.has(name)); + assert( + unexpected.length >= 1, + X`Internal: must have either missing or extra: ${q( + specNames, + )} vs ${q(pattNames)}`, + ); return check( false, - X`Record ${specimen} - Must have same property names as record pattern: ${patt}`, + X`${specimen} - Must not have unexpected properties: ${q( + unexpected, + )}`, ); } - return checkMatches(specValues, pattValues, check); + return pattNames.every((label, i) => + checkMatches(specValues[i], pattValues[i], check, label), + ); } case 'tagged': { const pattTag = getTag(patt); @@ -338,7 +371,7 @@ const makePatternKit = () => { patt.payload.length === 1, X`Non-singleton copySets with matcher not yet implemented: ${patt}`, ); - return checkMatches(specPayload[0], pattPayload[0], check); + return checkMatches(specPayload[0], pattPayload[0], check, 0); } case 'copyMap': { if (!checkCopySet(specimen, check)) { @@ -376,11 +409,10 @@ const makePatternKit = () => { /** * @param {Passable} specimen * @param {Pattern} patt - * @param {string} [label] + * @param {string|number} [label] */ - const fit = (specimen, patt, label = undefined) => { - applyLabelingError(checkMatches, [specimen, patt, assertChecker], label); - }; + const fit = (specimen, patt, label = undefined) => + checkMatches(specimen, patt, assertChecker, label); // /////////////////////// getRankCover ////////////////////////////////////// @@ -494,6 +526,49 @@ const makePatternKit = () => { return getPassStyleCover(passStyle); }; + /** + * @typedef {string} Kind + * It is either a PassStyle other than 'tagged', or, if the underlying + * PassStyle is 'tagged', then the `getTag` value. + * + * We cannot further restrict this to only possible passStyles + * or known tags, because we wish to allow matching of tags that we + * don't know ahead of time. Do we need to separate the namespaces? + * TODO are we asking for trouble by lumping passStyles and tags + * together into kinds? + */ + + /** + * @param {Passable} specimen + * @returns {Kind} + */ + const kindOf = specimen => { + const style = passStyleOf(specimen); + if (style === 'tagged') { + return getTag(specimen); + } else { + return style; + } + }; + + /** + * @param {Passable} specimen + * @param {Kind} kind + * @param {Checker} [check] + */ + const checkKind = (specimen, kind, check = x => x) => { + const specimenKind = kindOf(specimen); + if (specimenKind === kind) { + return true; + } + const details = X( + // quoting without quotes + [`${specimenKind} `, ` - Must be a ${kind}`], + specimen, + ); + return check(false, details); + }; + // /////////////////////// Match Helpers ///////////////////////////////////// /** @type {MatchHelper} */ @@ -545,13 +620,13 @@ const makePatternKit = () => { if (length === 0) { return check( false, - X`${specimen} - no pattern disjuncts to match: ${q(patts)}`, + X`${specimen} - no pattern disjuncts to match: ${patts}`, ); } if (patts.some(patt => matches(specimen, patt))) { return true; } - return check(false, X`${specimen} - Must match one of ${q(patts)}`); + return check(false, X`${specimen} - Must match one of ${patts}`); }, checkIsMatcherPayload: matchAndHelper.checkIsMatcherPayload, @@ -625,20 +700,10 @@ const makePatternKit = () => { /** @type {MatchHelper} */ const matchKindHelper = Far('M.kind helper', { - checkMatches: (specimen, kind, check = x => x) => - check( - passStyleOf(specimen) === kind || - (passStyleOf(specimen) === 'tagged' && getTag(specimen) === kind), - X`${specimen} - Must have passStyle or tag ${q(kind)}`, - ), + checkMatches: checkKind, checkIsMatcherPayload: (allegedKeyKind, check = x => x) => check( - // We cannot further restrict this to only possible passStyles - // or tags, because we wish to allow matching of tags that we - // don't know ahead of time. Do we need to separate the namespaces? - // TODO are we asking for trouble by lumping passStyles and tags - // together into kinds? typeof allegedKeyKind === 'string', X`A kind name must be a string: ${allegedKeyKind}`, ), @@ -767,7 +832,7 @@ const makePatternKit = () => { check( passStyleOf(specimen) === 'copyArray', X`${specimen} - Must be an array`, - ) && specimen.every(el => checkMatches(el, subPatt, check)), + ) && specimen.every((el, i) => checkMatches(el, subPatt, check, i)), checkIsMatcherPayload: checkPattern, @@ -785,7 +850,7 @@ const makePatternKit = () => { X`${specimen} - Must be a record`, ) && Object.entries(specimen).every(el => - checkMatches(harden(el), entryPatt, check), + checkMatches(harden(el), entryPatt, check, el[0]), ), checkIsMatcherPayload: (entryPatt, check = x => x) => @@ -806,7 +871,8 @@ const makePatternKit = () => { check( passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copySet', X`${specimen} - Must be a a CopySet`, - ) && specimen.payload.every(el => checkMatches(el, keyPatt, check)), + ) && + specimen.payload.every((el, i) => checkMatches(el, keyPatt, check, i)), checkIsMatcherPayload: checkPattern, @@ -824,9 +890,9 @@ const makePatternKit = () => { X`${specimen} - Must be a a CopyBag`, ) && specimen.payload.every( - ([key, count]) => - checkMatches(key, keyPatt, check) && - checkMatches(count, countPatt, check), + ([key, count], i) => + checkMatches(key, keyPatt, check, `keys[${i}]`) && + checkMatches(count, countPatt, check, `counts[${i}]`), ), checkIsMatcherPayload: (entryPatt, check = x => x) => @@ -848,8 +914,12 @@ const makePatternKit = () => { passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyMap', X`${specimen} - Must be a CopyMap`, ) && - specimen.payload.keys.every(k => checkMatches(k, keyPatt, check)) && - specimen.payload.values.every(v => checkMatches(v, valuePatt, check)), + specimen.payload.keys.every((k, i) => + checkMatches(k, keyPatt, check, `keys[${i}]`), + ) && + specimen.payload.values.every((v, i) => + checkMatches(v, valuePatt, check, `values[${i}]`), + ), checkIsMatcherPayload: (entryPatt, check = x => x) => check( @@ -866,13 +936,9 @@ const makePatternKit = () => { /** @type {MatchHelper} */ const matchSplitHelper = Far('match:split helper', { checkMatches: (specimen, [base, rest = undefined], check = x => x) => { - const specimenStyle = passStyleOf(specimen); const baseStyle = passStyleOf(base); - if (specimenStyle !== baseStyle) { - return check( - false, - X`${specimen} - Must have shape of base: ${q(baseStyle)}`, - ); + if (!checkKind(specimen, baseStyle, check)) { + return false; } let specB; let specR; @@ -897,12 +963,8 @@ const makePatternKit = () => { harden(specB); harden(specR); return ( - checkMatches(specB, base, check) && - (rest === undefined || - check( - matches(specR, rest), - X`Remainder ${specR} - Must match ${rest}`, - )) + checkMatches(specB, base, check, 'required-parts') && + (rest === undefined || checkMatches(specR, rest, check, 'rest-parts')) ); }, @@ -937,13 +999,9 @@ const makePatternKit = () => { /** @type {MatchHelper} */ const matchPartialHelper = Far('match:partial helper', { checkMatches: (specimen, [base, rest = undefined], check = x => x) => { - const specimenStyle = passStyleOf(specimen); const baseStyle = passStyleOf(base); - if (specimenStyle !== baseStyle) { - return check( - false, - X`${specimen} - Must have shape of base: ${q(baseStyle)}`, - ); + if (!checkKind(specimen, baseStyle, check)) { + return false; } let specB; let specR; @@ -976,12 +1034,8 @@ const makePatternKit = () => { harden(specR); harden(newBase); return ( - checkMatches(specB, newBase, check) && - (rest === undefined || - check( - matches(specR, rest), - X`Remainder ${specR} - Must match ${rest}`, - )) + checkMatches(specB, newBase, check, 'optional-parts') && + (rest === undefined || checkMatches(specR, rest, check, 'rest-parts')) ); }, diff --git a/packages/store/src/types.js b/packages/store/src/types.js index bf56d845b52..92e0a861027 100644 --- a/packages/store/src/types.js +++ b/packages/store/src/types.js @@ -435,14 +435,6 @@ * @returns {Iterable<[number, Passable]>} */ -/** - * @callback CheckMatches - * @param {Passable} specimen - * @param {Pattern} pattern - * @param {Checker=} check - * @returns {boolean} - */ - /** * @callback CheckPattern * @param {Passable} allegedPattern @@ -568,7 +560,7 @@ /** * @typedef {object} PatternKit * @property {(specimen: Passable, patt: Pattern) => boolean} matches - * @property {(specimen: Passable, patt: Pattern, label?: string) => void} fit + * @property {(specimen: Passable, patt: Pattern, label?: string|number) => void} fit * @property {(patt: Pattern) => void} assertPattern * @property {(patt: Passable) => boolean} isPattern * @property {(patt: Pattern) => void} assertKeyPattern @@ -600,7 +592,7 @@ * * @property {(specimen: Passable, * matcherPayload: Passable, - * check?: Checker + * check?: Checker, * ) => boolean} checkMatches * Assuming a valid Matcher of this type with `matcherPayload` as its * payload, does this specimen match that Matcher? diff --git a/packages/store/test/test-patterns.js b/packages/store/test/test-patterns.js index 5384c874912..48f98765bf2 100644 --- a/packages/store/test/test-patterns.js +++ b/packages/store/test/test-patterns.js @@ -2,7 +2,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; -import { makeCopySet } from '../src/keys/checkKey.js'; +import { makeCopyBag, makeCopyMap, makeCopySet } from '../src/keys/checkKey.js'; import { fit, matches, M } from '../src/patterns/patternMatchers.js'; import '../src/types.js'; @@ -10,7 +10,7 @@ import '../src/types.js'; * @typedef MatchTest * @property {Passable} specimen * @property {Pattern[]} yesPatterns - * @property {[Pattern, RegExp][]} noPatterns + * @property {[Pattern, RegExp|string][]} noPatterns */ /** @type {MatchTest[]} */ @@ -34,19 +34,19 @@ const matchTests = harden([ M.pattern(), ], noPatterns: [ - [4, /3 - Must be equivalent to: 4/], - [M.not(3), /3 - must fail negated pattern: 3/], - [M.not(M.any()), /3 - must fail negated pattern: "\[match:any\]"/], - [M.string(), /3 - Must have passStyle or tag "string"/], - [[3, 4], /3 - Must be equivalent to: \[3,4\]/], - [M.gte(7), /3 - Must be >= 7/], - [M.lte(2), /3 - Must be <= 2/], + [4, '3 - Must be: 4'], + [M.not(3), '3 - must fail negated pattern: 3'], + [M.not(M.any()), '3 - must fail negated pattern: "[match:any]"'], + [M.string(), 'number 3 - Must be a string'], + [[3, 4], '3 - Must be: [3,4]'], + [M.gte(7), '3 - Must be >= 7'], + [M.lte(2), '3 - Must be <= 2'], // incommensurate comparisons are neither <= nor >= - [M.lte('x'), /3 - Must be <= "x"/], - [M.gte('x'), /3 - Must be >= "x"/], - [M.and(3, 4), /3 - Must be equivalent to: 4/], - [M.or(4, 4), /3 - Must match one of \[4,4\]/], - [M.or(), /3 - no pattern disjuncts to match: \[\]/], + [M.lte('x'), '3 - Must be <= "x"'], + [M.gte('x'), '3 - Must be >= "x"'], + [M.and(3, 4), '3 - Must be: 4'], + [M.or(4, 4), '3 - Must match one of [4,4]'], + [M.or(), '3 - no pattern disjuncts to match: []'], ], }, { @@ -74,24 +74,27 @@ const matchTests = harden([ M.array(), M.key(), M.pattern(), + M.arrayOf(M.number()), ], noPatterns: [ - [[4, 3], /\[3,4\] - Must be equivalent to: \[4,3\]/], - [[3], /\[3,4\] - Must be equivalent to: \[3\]/], - [[M.string(), M.any()], /3 - Must have passStyle or tag "string"/], - [M.lte([3, 3]), /\[3,4\] - Must be <= \[3,3\]/], - [M.gte([4, 4]), /\[3,4\] - Must be >= \[4,4\]/], - [M.lte([3]), /\[3,4\] - Must be <= \[3\]/], - [M.gte([3, 4, 1]), /\[3,4\] - Must be >= \[3,4,1\]/], - - [M.split([3, 4, 5, 6]), /\[3,4\] - Must be equivalent to: \[3,4,5,6\]/], - [M.split([5]), /\[3\] - Must be equivalent to: \[5\]/], - [M.split({}), /\[3,4\] - Must have shape of base: "copyRecord"/], - [M.split([3], 'x'), /Remainder \[4\] - Must match "x"/], - - [M.partial([5]), /\[3\] - Must be equivalent to: \[5\]/], - - [M.scalar(), /A "copyArray" cannot be a scalar key: \[3,4\]/], + [[4, 3], '[3,4] - Must be: [4,3]'], + [[3], '[3,4] - Must be: [3]'], + [[M.string(), M.any()], '[0]: number 3 - Must be a string'], + [M.lte([3, 3]), '[3,4] - Must be <= [3,3]'], + [M.gte([4, 4]), '[3,4] - Must be >= [4,4]'], + [M.lte([3]), '[3,4] - Must be <= [3]'], + [M.gte([3, 4, 1]), '[3,4] - Must be >= [3,4,1]'], + + [M.split([3, 4, 5, 6]), 'required-parts: [3,4] - Must be: [3,4,5,6]'], + [M.split([5]), 'required-parts: [3] - Must be: [5]'], + [M.split({}), 'copyArray [3,4] - Must be a copyRecord'], + [M.split([3], 'x'), 'rest-parts: [4] - Must be: "x"'], + + [M.partial([5]), 'optional-parts: [3] - Must be: [5]'], + + [M.scalar(), 'A "copyArray" cannot be a scalar key: [3,4]'], + [M.set(), 'copyArray [3,4] - Must be a copySet'], + [M.arrayOf(M.string()), '[0]: number 3 - Must be a string'], ], }, { @@ -118,54 +121,62 @@ const matchTests = harden([ M.record(), M.key(), M.pattern(), + M.recordOf(M.string(), M.number()), ], noPatterns: [ - [ - { foo: 4, bar: 3 }, - /{"foo":3,"bar":4} - Must be equivalent to: {"foo":4,"bar":3}/, - ], - [ - { foo: M.string(), bar: M.any() }, - /3 - Must have passStyle or tag "string"/, - ], + [{ foo: 4, bar: 3 }, '{"foo":3,"bar":4} - Must be: {"foo":4,"bar":3}'], + [{ foo: M.string(), bar: M.any() }, 'foo: number 3 - Must be a string'], [ M.lte({ foo: 3, bar: 3 }), - /{"foo":3,"bar":4} - Must be <= {"foo":3,"bar":3}/, + '{"foo":3,"bar":4} - Must be <= {"foo":3,"bar":3}', ], [ M.gte({ foo: 4, bar: 4 }), - /{"foo":3,"bar":4} - Must be >= {"foo":4,"bar":4}/, + '{"foo":3,"bar":4} - Must be >= {"foo":4,"bar":4}', ], // Incommensurates are neither greater nor less - [M.gte({ foo: 3 }), /{"foo":3,"bar":4} - Must be >= {"foo":3}/], - [M.lte({ foo: 3 }), /{"foo":3,"bar":4} - Must be <= {"foo":3}/], + [M.gte({ foo: 3 }), '{"foo":3,"bar":4} - Must be >= {"foo":3}'], + [M.lte({ foo: 3 }), '{"foo":3,"bar":4} - Must be <= {"foo":3}'], [ M.gte({ foo: 3, bar: 4, baz: 5 }), - /{"foo":3,"bar":4} - Must be >= {"foo":3,"bar":4,"baz":5}/, + '{"foo":3,"bar":4} - Must be >= {"foo":3,"bar":4,"baz":5}', ], [ M.lte({ foo: 3, bar: 4, baz: 5 }), - /{"foo":3,"bar":4} - Must be <= {"foo":3,"bar":4,"baz":5}/, + '{"foo":3,"bar":4} - Must be <= {"foo":3,"bar":4,"baz":5}', ], - [M.lte({ baz: 3 }), /{"foo":3,"bar":4} - Must be <= {"baz":3}/], - [M.gte({ baz: 3 }), /{"foo":3,"bar":4} - Must be >= {"baz":3}/], + [M.lte({ baz: 3 }), '{"foo":3,"bar":4} - Must be <= {"baz":3}'], + [M.gte({ baz: 3 }), '{"foo":3,"bar":4} - Must be >= {"baz":3}'], - [M.split([]), /{"foo":3,"bar":4} - Must have shape of base: "copyArray"/], + [M.split([]), 'copyRecord {"foo":3,"bar":4} - Must be a copyArray'], [ M.split({ foo: 3, z: 4 }), - /{"foo":3} - Must be equivalent to: {"foo":3,"z":4}/, + 'required-parts: {"foo":3} - Must be: {"foo":3,"z":4}', ], [ M.split({ foo: 3 }, { foo: 3, bar: 4 }), - /Remainder {"bar":4} - Must match {"foo":3,"bar":4}/, + 'rest-parts: {"bar":4} - Must be: {"foo":3,"bar":4}', + ], + [ + M.split({ foo: 3 }, { foo: M.any(), bar: 4 }), + 'rest-parts: {"bar":4} - Must have missing properties ["foo"]', ], [ M.partial({ foo: 7, zip: 5 }, { bar: 4 }), - /{"foo":3} - Must be equivalent to: {"foo":7}/, + 'optional-parts: {"foo":3} - Must be: {"foo":7}', ], - [M.scalar(), /A "copyRecord" cannot be a scalar key: {"foo":3,"bar":4}/], + [M.scalar(), 'A "copyRecord" cannot be a scalar key: {"foo":3,"bar":4}'], + [M.map(), 'copyRecord {"foo":3,"bar":4} - Must be a copyMap'], + [ + M.recordOf(M.number(), M.number()), + 'foo: [0]: string "foo" - Must be a number', + ], + [ + M.recordOf(M.string(), M.string()), + 'foo: [1]: number 3 - Must be a string', + ], ], }, { @@ -174,17 +185,81 @@ const matchTests = harden([ makeCopySet([4, 3]), M.gte(makeCopySet([])), M.lte(makeCopySet([3, 4, 5])), + M.set(), + M.setOf(M.number()), + ], + noPatterns: [ + [makeCopySet([]), '"[copySet]" - Must be: "[copySet]"'], + [makeCopySet([3, 4, 5]), '"[copySet]" - Must be: "[copySet]"'], + [M.lte(makeCopySet([])), '"[copySet]" - Must be <= "[copySet]"'], + [M.gte(makeCopySet([3, 4, 5])), '"[copySet]" - Must be >= "[copySet]"'], + [M.bag(), 'copySet "[copySet]" - Must be a copyBag'], + [M.setOf(M.string()), '[0]: number 4 - Must be a string'], + ], + }, + { + specimen: makeCopyBag([ + ['a', 2n], + ['b', 3n], + ]), + yesPatterns: [ + M.gt(makeCopyBag([])), + M.gt(makeCopyBag([['a', 2n]])), + M.gt( + makeCopyBag([ + ['a', 1n], + ['b', 3n], + ]), + ), + M.bag(), + M.bagOf(M.string()), + M.bagOf(M.string(), M.lt(5n)), + ], + noPatterns: [ + [ + M.gte( + makeCopyBag([ + ['b', 2n], + ['c', 1n], + ]), + ), + '"[copyBag]" - Must be >= "[copyBag]"', + ], + [ + M.lte( + makeCopyBag([ + ['b', 2n], + ['c', 1n], + ]), + ), + '"[copyBag]" - Must be <= "[copyBag]"', + ], + [M.bagOf(M.boolean()), 'keys[0]: string "b" - Must be a boolean'], + [M.bagOf('b'), 'keys[1]: "a" - Must be: "b"'], + [M.bagOf(M.any(), M.gt(5n)), 'counts[0]: "[3n]" - Must be > "[5n]"'], + [M.bagOf(M.any(), M.gt(2n)), 'counts[1]: "[2n]" - Must be > "[2n]"'], + ], + }, + { + specimen: makeCopyMap([ + [{}, 'a'], + [{ foo: 3 }, 'b'], + ]), + yesPatterns: [ + // M.gt(makeCopyMap([])), Map comparison not yet implemented + M.map(), + M.mapOf(M.record(), M.string()), ], noPatterns: [ - [makeCopySet([]), /"\[copySet\]" - Must be equivalent to: "\[copySet\]"/], + [M.bag(), 'copyMap "[copyMap]" - Must be a copyBag'], + [M.set(), 'copyMap "[copyMap]" - Must be a copySet'], [ - makeCopySet([3, 4, 5]), - /"\[copySet\]" - Must be equivalent to: "\[copySet\]"/, + M.mapOf(M.string(), M.string()), + 'keys[0]: copyRecord {"foo":3} - Must be a string', ], - [M.lte(makeCopySet([])), /"\[copySet\]" - Must be <= "\[copySet\]"/], [ - M.gte(makeCopySet([3, 4, 5])), - /\[copySet\]" - Must be >= "\[copySet\]"/, + M.mapOf(M.record(), M.number()), + 'values[0]: string "b" - Must be a number', ], ], }, diff --git a/packages/store/test/test-store.js b/packages/store/test/test-store.js index 92c0c9f5906..c8e9735d055 100644 --- a/packages/store/test/test-store.js +++ b/packages/store/test/test-store.js @@ -102,7 +102,7 @@ test('reject promise keys', t => { const k = harden(Promise.resolve()); const s = makeScalarMapStore('store1'); t.throws(() => s.init(k, 1), { - message: /A "promise" cannot be a scalar key: "\[Promise\]"/, + message: 'A "promise" cannot be a scalar key: "[Promise]"', }); t.is(s.has(k), false); t.throws(() => s.get(k), { message: /not found:/ }); diff --git a/packages/zoe/test/unitTests/test-cleanProposal.js b/packages/zoe/test/unitTests/test-cleanProposal.js index b024ccd9cf2..6ab8bbb2e5b 100644 --- a/packages/zoe/test/unitTests/test-cleanProposal.js +++ b/packages/zoe/test/unitTests/test-cleanProposal.js @@ -123,7 +123,7 @@ test('cleanProposal - want patterns', t => { exit: { afterDeadline: { timer, deadline: 100n } }, }, 'nat', - /"keywordRecord" "\[match:any\]" must be a pass-by-copy record, not "tagged"/, + '"keywordRecord" "[match:any]" must be a pass-by-copy record, not "tagged"', ); proposeBad( @@ -134,7 +134,7 @@ test('cleanProposal - want patterns', t => { exit: { afterDeadline: { timer, deadline: 100n } }, }, 'nat', - /A passable tagged "match:any" is not a key: "\[match:any\]"/, + 'A passable tagged "match:any" is not a key: "[match:any]"', ); proposeBad( @@ -145,7 +145,7 @@ test('cleanProposal - want patterns', t => { exit: { afterDeadline: { timer, deadline: M.any() } }, }, 'nat', - /A passable tagged "match:any" is not a key: "\[match:any\]"/, + 'A passable tagged "match:any" is not a key: "[match:any]"', ); }); @@ -222,13 +222,13 @@ test('cleanProposal - other wrong stuff', t => { t, { exit: 'foo' }, 'nat', - /"foo" - Must have shape of base: "copyRecord"/, + 'proposal: exit: string "foo" - Must be a copyRecord', ); proposeBad( t, { exit: { onDemand: 'foo' } }, 'nat', - /{"onDemand":"foo"} - Must be equivalent to: {"onDemand":null}/, + 'proposal: exit: optional-parts: {"onDemand":"foo"} - Must be: {"onDemand":null}', ); proposeBad( t, @@ -240,43 +240,43 @@ test('cleanProposal - other wrong stuff', t => { t, { exit: { afterDeadline: { timer: 'foo', deadline: 3n } } }, 'nat', - /"foo" - Must have passStyle or tag "remotable"/, + 'proposal: exit: optional-parts: afterDeadline: timer: string "foo" - Must be a remotable', ); proposeBad( t, { exit: { afterDeadline: { timer, deadline: 'foo' } } }, 'nat', - /"foo" - Must be >= "\[0n\]"/, + 'proposal: exit: optional-parts: afterDeadline: deadline: "foo" - Must be >= "[0n]"', ); proposeBad( t, { exit: { afterDeadline: { timer, deadline: 3n, extra: 'foo' } } }, 'nat', - /.* - Must have same property names as record pattern:.*/, + 'proposal: exit: optional-parts: afterDeadline: {"timer":"[Alleged: ManualTimer]","deadline":"[3n]","extra":"foo"} - Must not have unexpected properties: ["extra"]', ); proposeBad( t, { exit: { afterDeadline: { timer } } }, 'nat', - /.* - Must have same property names as record pattern:.*/, + 'proposal: exit: optional-parts: afterDeadline: {"timer":"[Alleged: ManualTimer]"} - Must have missing properties ["deadline"]', ); proposeBad( t, { exit: { afterDeadline: { deadline: 3n } } }, 'nat', - /.* - Must have same property names as record pattern:.*/, + 'proposal: exit: optional-parts: afterDeadline: {"deadline":"[3n]"} - Must have missing properties ["timer"]', ); proposeBad( t, { exit: { afterDeadline: { timer, deadline: 3 } } }, 'nat', - /3 - Must be >= "\[0n\]"/, + 'proposal: exit: optional-parts: afterDeadline: deadline: 3 - Must be >= "[0n]"', ); proposeBad( t, { exit: { afterDeadline: { timer, deadline: -3n } } }, 'nat', - /"\[-3n\]" - Must be >= "\[0n\]"/, + 'proposal: exit: optional-parts: afterDeadline: deadline: "[-3n]" - Must be >= "[0n]"', ); proposeBad(t, { exit: {} }, 'nat', /exit {} should only have one key/); proposeBad( diff --git a/packages/zoe/test/unitTests/test-zoe.js b/packages/zoe/test/unitTests/test-zoe.js index 325027b06b8..98f93000ea2 100644 --- a/packages/zoe/test/unitTests/test-zoe.js +++ b/packages/zoe/test/unitTests/test-zoe.js @@ -429,7 +429,7 @@ test(`zoe.getInstance - no invitation`, async t => { const { zoe } = await setupZCFTest(); // @ts-expect-error invalid arguments for testing await t.throwsAsync(() => E(zoe).getInstance(), { - message: /A Zoe invitation is required, not "\[undefined\]"/, + message: 'A Zoe invitation is required, not "[undefined]"', }); }); @@ -445,7 +445,7 @@ test(`zoe.getInstallation - no invitation`, async t => { const { zoe } = await setupZCFTest(); // @ts-expect-error invalid arguments for testing await t.throwsAsync(() => E(zoe).getInstallation(), { - message: /A Zoe invitation is required, not "\[undefined\]"/, + message: 'A Zoe invitation is required, not "[undefined]"', }); }); @@ -466,7 +466,7 @@ test(`zoe.getInvitationDetails - no invitation`, async t => { const { zoe } = await setupZCFTest(); // @ts-expect-error invalid arguments for testing await t.throwsAsync(() => E(zoe).getInvitationDetails(), { - message: /A Zoe invitation is required, not "\[undefined\]"/, + message: 'A Zoe invitation is required, not "[undefined]"', }); }); diff --git a/packages/zoe/test/unitTests/zcf/test-zcf.js b/packages/zoe/test/unitTests/zcf/test-zcf.js index fbf71e458f1..6a6c4841343 100644 --- a/packages/zoe/test/unitTests/zcf/test-zcf.js +++ b/packages/zoe/test/unitTests/zcf/test-zcf.js @@ -290,7 +290,7 @@ test(`zcf.makeInvitation - no description`, async t => { const { zcf } = await setupZCFTest(); // @ts-expect-error deliberate invalid arguments for testing t.throws(() => zcf.makeInvitation(() => {}), { - message: /invitations must have a description string: "\[undefined\]"/, + message: 'invitations must have a description string: "[undefined]"', }); }); @@ -391,7 +391,7 @@ test(`zcf.makeZCFMint - not a math kind`, async t => { // @ts-expect-error deliberate invalid arguments for testing await t.throwsAsync(() => zcf.makeZCFMint('A', 'whatever'), { message: - /The assetKind "whatever" must be one of \["copyBag","copySet","nat","set"\]/, + 'The assetKind "whatever" must be one of ["copyBag","copySet","nat","set"]', }); }); diff --git a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js index b271776c1c5..aaaa154ceca 100644 --- a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js +++ b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js @@ -299,13 +299,13 @@ test(`zoeHelper with zcf - fit proposal patterns`, async t => { }); t.throws(() => fit(proposal, harden([])), { - message: /.* - Must be equivalent to: \[\]/, + message: /.* - Must be: \[\]/, }); t.throws( () => fit(proposal, M.split({ want: { C: M.any() } })), { message: - /Must have same property names as record pattern: {"C":"\[match:any\]"}/, + 'required-parts: want: {"A":{"brand":"[Alleged: moola brand]","value":"[20n]"}} - Must have missing properties ["C"]', }, 'empty keywordRecord does not match', ); @@ -315,15 +315,14 @@ test(`zoeHelper with zcf - fit proposal patterns`, async t => { () => fit(proposal, M.split({ give: { c: M.any() } })), { message: - /Must have same property names as record pattern: {"c":"\[match:any\]"}/, + 'required-parts: give: {"B":{"brand":"[Alleged: simoleans brand]","value":"[3n]"}} - Must have missing properties ["c"]', }, 'wrong key in keywordRecord does not match', ); t.throws( () => fit(proposal, M.split({ exit: { onDemaind: M.any() } })), { - message: - /Must have same property names as record pattern: {"exit":{"onDemaind":"\[match:any\]"}}/, + message: 'required-parts: {} - Must have missing properties ["exit"]', }, 'missing exit rule', ); @@ -619,7 +618,7 @@ test(`zcf/zoeHelper - fit proposal pattern w/bad Expected`, async t => { }); t.throws(() => fit(proposal, M.split({ give: { B: moola(3n) } })), { - message: /.* - Must be equivalent to: .*/, + message: /.* - Must be: .*/, }); });