From 2ee0b4f7edd32290683e98e231a18ded9595ba6c Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Fri, 10 Nov 2023 17:50:24 -0800 Subject: [PATCH] feat(eventual-send): breakpoint on delivery by env-options --- packages/eventual-send/src/local.js | 58 ++++++++++++++++++- packages/pass-style/package.json | 1 + .../pass-style/test/prepare-breakpoints.js | 6 ++ .../test/test-breakpoints-on-delivery.js | 28 +++++++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 packages/pass-style/test/prepare-breakpoints.js create mode 100644 packages/pass-style/test/test-breakpoints-on-delivery.js diff --git a/packages/eventual-send/src/local.js b/packages/eventual-send/src/local.js index f7610978ad..63ea6c3b90 100644 --- a/packages/eventual-send/src/local.js +++ b/packages/eventual-send/src/local.js @@ -1,6 +1,11 @@ +/* global globalThis */ +import { makeEnvironmentCaptor } from '@endo/env-options'; + +const { getEnvironmentOption } = makeEnvironmentCaptor(globalThis); + const { details: X, quote: q, Fail } = assert; -const { getOwnPropertyDescriptors, getPrototypeOf, freeze } = Object; +const { getOwnPropertyDescriptors, getPrototypeOf, freeze, hasOwn } = Object; const { apply, ownKeys } = Reflect; const ntypeof = specimen => (specimen === null ? 'null' : typeof specimen); @@ -13,6 +18,55 @@ const ntypeof = specimen => (specimen === null ? 'null' : typeof specimen); */ const isObject = val => Object(val) === val; +const BREAKPOINTS = getEnvironmentOption('BREAKPOINTS', ''); + +/** + * @type {Record} + */ +// @ts-expect-error doesn't like null __proto__ +const BREAKS = { __proto__: null }; + +const BP_REGEXP = /(.+?)\.(\w+?|\*)=(\d+?|\*)/; + +for (const bp of BREAKPOINTS.split(',')) { + if (bp !== '') { + const match = BP_REGEXP.exec(bp); + if (match === null) { + throw Fail`Expected "tagName.methodName=count" ${q(bp)}`; + } + const [_, tag, verb, countStr] = match; + const patts = hasOwn(BREAKS, verb) ? BREAKS[verb] : (BREAKS[verb] = []); + patts.push([tag, countStr === '*' ? countStr : +countStr]); + } +} + +const debugApply = + BREAKPOINTS === '' + ? (f, obj, _verb, args) => apply(f, obj, args) + : (f, obj, verb, args) => { + const patts = BREAKS[verb] || BREAKS['*']; + if (patts) { + for (const patt of patts) { + const [tag, count] = patt; + if (tag === '*' || tag === obj[Symbol.toStringTag]) { + if (count === '*') { + // eslint-disable-next-line no-debugger + debugger; + break; + } else { + if (count === 0) { + // eslint-disable-next-line no-debugger + debugger; + break; + } + patt[1] = count - 1; + } + } + } + } + return apply(f, obj, args); + }; + /** * Prioritize symbols as earlier than strings. * @@ -96,7 +150,7 @@ export const localApplyMethod = (t, method, args) => { const ftype = ntypeof(fn); typeof fn === 'function' || Fail`invoked method ${q(method)} is not a function; it is a ${q(ftype)}`; - return apply(fn, t, args); + return debugApply(fn, t, method, args); }; export const localGet = (t, key) => t[key]; diff --git a/packages/pass-style/package.json b/packages/pass-style/package.json index 60a39fe909..74bf3fff75 100644 --- a/packages/pass-style/package.json +++ b/packages/pass-style/package.json @@ -38,6 +38,7 @@ }, "devDependencies": { "@endo/init": "^0.5.60", + "@endo/pass-style": "^0.1.7", "@endo/ses-ava": "^0.2.44", "ava": "^5.3.0", "babel-eslint": "^10.0.3", diff --git a/packages/pass-style/test/prepare-breakpoints.js b/packages/pass-style/test/prepare-breakpoints.js new file mode 100644 index 0000000000..dfa1852276 --- /dev/null +++ b/packages/pass-style/test/prepare-breakpoints.js @@ -0,0 +1,6 @@ +/* global process */ + +process.env.BREAKPOINTS = [ + 'Alleged: Bob.foo=*', + '*.bar=0', +].join(','); diff --git a/packages/pass-style/test/test-breakpoints-on-delivery.js b/packages/pass-style/test/test-breakpoints-on-delivery.js new file mode 100644 index 0000000000..3fc8905331 --- /dev/null +++ b/packages/pass-style/test/test-breakpoints-on-delivery.js @@ -0,0 +1,28 @@ +import './prepare-breakpoints.js'; +import { test } from './prepare-test-env-ava.js'; + +// eslint-disable-next-line import/order +import { E } from '@endo/eventual-send'; +import { Far } from '../src/make-far.js'; + +// Example from test-deep-send.js in @endo/eventual-send + +const carol = Far('Carol', { + bar: () => console.log('Wut?'), +}); + +const bob = Far('Bob', { + foo: carolP => E(carolP).bar(), +}); + +const alice = Far('Alice', { + test: () => E(bob).foo(carol), +}); + +// This is not useful as an automated test. Its purpose is to run it under a +// debugger and see where it breakpoints. To play with it, adjust the +// settings in prepare-breakpoints.js and try again. +test('test breakpoints on delivery', async t => { + await alice.test(); + t.pass('introduced'); +});