From 4e1890400288bc4dce7b5c0990a837364d746723 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 18 Feb 2025 10:41:42 -0600 Subject: [PATCH] fixup! test: use fine-grained expected unhandled rejections --- .../vow/test/unhandled-rejections.test.js | 248 ++++++++++++++++++ packages/vow/test/watch-upgrade.test.js | 3 +- 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 packages/vow/test/unhandled-rejections.test.js diff --git a/packages/vow/test/unhandled-rejections.test.js b/packages/vow/test/unhandled-rejections.test.js new file mode 100644 index 00000000000..375030f1c4b --- /dev/null +++ b/packages/vow/test/unhandled-rejections.test.js @@ -0,0 +1,248 @@ +import { + annihilate, + startLife, + test, +} from '@agoric/swingset-vat/tools/prepare-strict-test-env-ava.js'; + +import { Far } from '@endo/far'; +import { makeDurableZone } from '@agoric/zone/durable.js'; + +import { makeExpectUnhandledRejectionMacro } from '@agoric/internal/src/lib-nodejs/ava-unhandled-rejection.js'; +import { makeGcAndFinalize } from '@agoric/internal/src/lib-nodejs/gc-and-finalize.js'; +import engineGC from '@agoric/internal/src/lib-nodejs/engine-gc.js'; +import { prepareVowTools } from '../vat.js'; + +const expectUnhandled = test.macro( + makeExpectUnhandledRejectionMacro(import.meta.url), +); + +const gcAndFinalize = makeGcAndFinalize(engineGC); +test.afterEach.always(gcAndFinalize); + +const makeRejectedVow = ( + { makeVowKit }, + reason = Error('caught the rejection'), +) => { + const { resolver, vow } = makeVowKit(); + resolver.reject(reason); + return vow; +}; + +const makeAndShortenRejectedVow = async ({ makeVowKit }) => { + const vow = makeRejectedVow({ makeVowKit }); + await gcAndFinalize(); + return vow.payload.vowV0.shorten(); +}; + +test.serial('vow shortened, then rejected', async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + const testVowKit = vowTools.makeVowKit(); + + void testVowKit.vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { message: 'caught the rejection' }, + ), + ); + testVowKit.resolver.reject(Error('caught the rejection')); + }); +}); + +test.serial('vow resolved to rejected promise, then shortened', async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + const testVowKit = vowTools.makeVowKit(); + + const rejection = Promise.reject(Error('caught the rejection')); + testVowKit.resolver.resolve(rejection); + testVowKit.vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { message: 'caught the rejection' }, + ), + ); + }); +}); + +test.serial.failing( + expectUnhandled, + 1, + 'vow rejected, then shorten after status update', + async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + const shorter = makeAndShortenRejectedVow(vowTools); + await gcAndFinalize(); + shorter.then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { message: 'caught the rejection' }, + ), + ); + }); + }, +); + +test.serial.failing( + expectUnhandled, + 1, + 'vow shortened, upgraded, then shortened again', + async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + const testVowKit = zone.makeOnce('testVowKit', () => + vowTools.makeVowKit(), + ); + + testVowKit.vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => t.fail(reason), + ); + }); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + prepareVowTools(zone); + + const testVowKit = zone.makeOnce('testVowKit', () => + t.fail('need testVowKit in baggage'), + ); + + testVowKit.resolver.reject(Error('caught the rejection')); + + testVowKit.vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { message: 'caught the rejection' }, + ), + ); + }); + }, +); + +test.serial(expectUnhandled, 1, 'vow rejected, then dropped', async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + makeRejectedVow(vowTools); + await gcAndFinalize(); + }); + + await gcAndFinalize(); + t.pass(); +}); + +test.serial( + expectUnhandled, + 2, + 'vow rejected with storable reason, upgraded, then handled', + async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + zone.makeOnce('testVow', () => makeRejectedVow(vowTools)); + }); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + prepareVowTools(zone); + + const vow = zone.makeOnce('testVow', () => + t.fail('need testVow in baggage'), + ); + + vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { message: 'caught the rejection' }, + ), + ); + }); + }, +); + +test.serial( + expectUnhandled, + 2, + 'vow rejected with non-storable reason, upgraded, then handled', + async t => { + annihilate(); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + const vowTools = prepareVowTools(zone); + + const nonStorable = Far('non-storable', { + toString() { + return 'is not storable'; + }, + }); + zone.makeOnce('testVow', () => makeRejectedVow(vowTools, nonStorable)); + }); + + await startLife(async baggage => { + const zone = makeDurableZone(baggage, 'durableRoot'); + prepareVowTools(zone); + + const vow = zone.makeOnce('testVow', () => + t.fail('need testVow in baggage'), + ); + + vow.payload.vowV0.shorten().then( + value => t.fail(value), + reason => + t.throws( + () => { + throw reason; + }, + { + message: `Vow rejection reason was not stored: "[Alleged: non-storable]"`, + }, + ), + ); + }); + }, +); diff --git a/packages/vow/test/watch-upgrade.test.js b/packages/vow/test/watch-upgrade.test.js index e9b12565ca6..757043e19af 100644 --- a/packages/vow/test/watch-upgrade.test.js +++ b/packages/vow/test/watch-upgrade.test.js @@ -60,7 +60,8 @@ test(expectUnhandled, 5, 'vow resolve across upgrade', async t => { vowTools.watch(testVowKit3.vow, watcher2); testVowKit3.resolver.resolve(42); - testVowKit4.resolver.reject(() => 'is not storable'); + const nonStorable = () => 'is not storable'; + testVowKit4.resolver.reject(nonStorable); }); await startLife(