Skip to content

Commit

Permalink
fix: tools arb passable
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Dec 2, 2022
1 parent 735e94f commit f648918
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 149 deletions.
4 changes: 2 additions & 2 deletions packages/marshal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@
"dependencies": {
"@endo/eventual-send": "^0.16.8",
"@endo/nat": "^4.1.23",
"@endo/promise-kit": "^0.2.52"
"@endo/promise-kit": "^0.2.52",
"@fast-check/ava": "^1.0.1"
},
"devDependencies": {
"@endo/init": "^0.5.52",
"@endo/lockdown": "^0.1.24",
"@endo/ses-ava": "^0.2.36",
"@fast-check/ava": "^1.0.1",
"ava": "^5.1.0",
"c8": "^7.7.3"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/marshal/src/encodePassable.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export const makeEncodePassable = ({
encodeError = (err, _) => assert.fail(X`error unexpected: ${err}`),
} = {}) => {
const encodePassable = passable => {
if (ErrorHelper.canBeValid(passable, x => x)) {
if (ErrorHelper.canBeValid(passable)) {
return encodeError(passable, encodePassable);
}
const passStyle = passStyleOf(passable);
Expand Down Expand Up @@ -485,7 +485,7 @@ harden(isEncodedRemotable);
* individually is a valid bigint prefix. `n` for "negative" and `p` for
* "positive". The ordering of these prefixes is the same as the
* rankOrdering of their respective PassStyles. This table is imported by
* randOrder.js for this purpose.
* rankOrder.js for this purpose.
*
* In addition, `|` is the remotable->ordinal mapping prefix:
* This is not used in covers but it is
Expand Down
6 changes: 4 additions & 2 deletions packages/marshal/src/rankOrder.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ const passStyleRanks = /** @type {PassStyleRanksRecord} */ (fromEntries(
return trivialComparator(leftPrefixes, rightPrefixes);
})
.map(([passStyle, prefixes], index) => {
// Cover all strings that start with any character in `prefixes`.
// `prefixes` is already sorted, so that's
// Cover all strings that start with any character in `prefixes`,
// verifying that it is sorted so that is
// all s such that prefixes.at(0) ≤ s < successor(prefixes.at(-1)).
prefixes === [...prefixes].sort().join('') ||
Fail`unsorted prefixes for passStyle ${q(passStyle)}: ${q(prefixes)}`;
const cover = [
prefixes.charAt(0),
String.fromCharCode(prefixes.charCodeAt(prefixes.length - 1) + 1),
Expand Down
118 changes: 88 additions & 30 deletions packages/marshal/test/test-encodePassable.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,75 @@ import {
} from '../src/encodePassable.js';
import { compareRank, makeComparatorKit } from '../src/rankOrder.js';
import { sample } from './test-rankOrder.js';
import { arbPassable } from '../tools/arb-passable.js';

const { details: X } = assert;
const { Fail, quote: q } = assert;

const r2e = new Map();
const e2r = [];
const buffers = {
__proto__: null,
r: [],
'?': [],
'!': [],
};
const resetBuffers = () => {
buffers.r = [];
buffers['?'] = [];
buffers['!'] = [];
};
const cursors = {
__proto__: null,
r: 0,
'?': 0,
'!': 0,
};
const resetCursors = () => {
cursors.r = 0;
cursors['?'] = 0;
cursors['!'] = 0;
};

const encodeRemotable = r => {
if (r2e.has(r)) {
return r2e.get(r);
}
const result = `r${e2r.length}`;
r2e.set(r, result);
e2r.push(r);
return result;
const encodeThing = (prefix, r) => {
buffers[prefix].push(r);
// With this encoding, all things with the same prefix have the same rank
return prefix;
};

const decodeRemotable = e => {
assert(e.startsWith('r'), X`unexpected encoding ${e}`);
const i = Number(BigInt(e.substring(1)));
assert(i >= 0 && i < e2r.length);
return e2r[i];
const decodeThing = (prefix, e) => {
prefix === e ||
Fail`expected encoding ${q(e)} to simply be the prefix ${q(prefix)}`;
(cursors[prefix] >= 0 && cursors[prefix] < buffers[prefix].length) ||
Fail`while decoding ${q(e)}, expected cursors[${q(prefix)}], i.e., ${q(
cursors[prefix],
)} <= ${q(buffers[prefix].length)}`;
const thing = buffers[prefix][cursors[prefix]];
cursors[prefix] += 1;
return thing;
};

const compareRemotables = (x, y) =>
compareRank(encodeRemotable(x), encodeRemotable(y));
compareRank(encodeThing('r', x), encodeThing('r', y));

const encodeKey = makeEncodePassable({ encodeRemotable });
const encodePassableInternal = makeEncodePassable({
encodeRemotable: r => encodeThing('r', r),
encodePromise: p => encodeThing('?', p),
encodeError: er => encodeThing('!', er),
});

const encodePassable = passable => {
resetBuffers();
return encodePassableInternal(passable);
};

const decodeKey = makeDecodePassable({ decodeRemotable });
const decodePassableInternal = makeDecodePassable({
decodeRemotable: e => decodeThing('r', e),
decodePromise: e => decodeThing('?', e),
decodeError: e => decodeThing('!', e),
});

const decodePassable = encoded => {
resetCursors();
return decodePassableInternal(encoded);
};

const { comparator: compareFull } = makeComparatorKit(compareRemotables);

Expand Down Expand Up @@ -80,12 +120,12 @@ const goldenPairs = harden([

test('golden round trips', t => {
for (const [k, e] of goldenPairs) {
t.is(encodeKey(k), e, 'does k encode as expected');
t.is(decodeKey(e), k, 'does the key round trip through the encoding');
t.is(encodePassable(k), e, 'does k encode as expected');
t.is(decodePassable(e), k, 'does the key round trip through the encoding');
}
// Not round trips
t.is(encodeKey(-0), 'f8000000000000000');
t.is(decodeKey('f0000000000000000'), NaN);
t.is(encodePassable(-0), 'f8000000000000000');
t.is(decodePassable('f0000000000000000'), NaN);
});

const orderInvariants = (t, x, y) => {
Expand All @@ -99,6 +139,12 @@ const orderInvariants = (t, x, y) => {
} else {
t.assert(rankComp === 0 || rankComp === fullComp);
}
const ex = encodePassable(x);
const ey = encodePassable(y);
const encComp = compareRank(ex, ey);
if (fullComp !== 0) {
t.is(encComp, fullComp);
}
};

test('order invariants', t => {
Expand All @@ -109,21 +155,33 @@ test('order invariants', t => {
}
});

test('BigInt values round-trip', async t => {
test('Passables round-trip', async t => {
await fc.assert(
fc.property(fc.bigInt(), n => {
const rt = decodeKey(encodeKey(n));
return t.is(rt, n);
fc.property(arbPassable, n => {
const en = encodePassable(n);
const rt = decodePassable(en);
const er = encodePassable(rt);
t.is(en, er);
t.is(compareFull(n, rt), 0);
}),
);
});

test('BigInt encoding comparison corresponds with numeric comparison', async t => {
await fc.assert(
fc.property(fc.bigInt(), fc.bigInt(), (a, b) => {
const ea = encodeKey(a);
const eb = encodeKey(b);
return t.is(a < b, ea < eb) && t.is(a > b, ea > eb);
const ea = encodePassable(a);
const eb = encodePassable(b);
t.is(a < b, ea < eb);
t.is(a > b, ea > eb);
}),
);
});

test('Passable encoding corresponds to rankOrder', async t => {
await fc.assert(
fc.property(arbPassable, arbPassable, (a, b) => {
orderInvariants(t, a, b);
}),
);
});
Loading

0 comments on commit f648918

Please sign in to comment.