From 03ebcf1ecfbfd918a5baaeaf5b2f0964976187e7 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 18 Nov 2024 21:26:42 -0500 Subject: [PATCH] fix(synthetic-chain): Read proposals in sorted order Fixes #197 --- .../synthetic-chain/src/cli/dockerfileGen.ts | 2 +- packages/synthetic-chain/src/cli/proposals.ts | 25 ++++++++++++- packages/synthetic-chain/test/test-cli.ts | 35 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/synthetic-chain/src/cli/dockerfileGen.ts b/packages/synthetic-chain/src/cli/dockerfileGen.ts index 7c24390c..b3df55fc 100755 --- a/packages/synthetic-chain/src/cli/dockerfileGen.ts +++ b/packages/synthetic-chain/src/cli/dockerfileGen.ts @@ -223,7 +223,7 @@ export function writeDockerfile( proposalName: fromTag, proposalIdentifier: fromTag, // XXX these are bogus - path: 'VIRTUAL', + path: '.', type: '/agoric.swingset.CoreEvalProposal', source: 'subdir', }; diff --git a/packages/synthetic-chain/src/cli/proposals.ts b/packages/synthetic-chain/src/cli/proposals.ts index 25f10c31..67ebe8bb 100644 --- a/packages/synthetic-chain/src/cli/proposals.ts +++ b/packages/synthetic-chain/src/cli/proposals.ts @@ -65,6 +65,28 @@ export function encodeUpgradeInfo(upgradeInfo: unknown): string { return upgradeInfo != null ? JSON.stringify(upgradeInfo) : ''; } +export function compareProposalDirNames(a: string, b: string): -1 | 0 | 1 { + // Proposal directories should be named like "$position:$name", and we expect + // $position to be numeric but this logic tolerates deviation. + // Compare by position numerically, then by position lexicographically (by + // code unit for simplicity), then by the full name. + const [_a, aPos, aNumericPos] = a.match(/^(([0-9]+)|[^:]*):.*/) || []; + const [_b, bPos, bNumericPos] = b.match(/^(([0-9]+)|[^:]*):.*/) || []; + if (aNumericPos && !bNumericPos) return -1; + if (!aNumericPos && bNumericPos) return 1; + if (aNumericPos && bNumericPos) { + if (Number(aNumericPos) < Number(bNumericPos)) return -1; + if (Number(aNumericPos) > Number(bNumericPos)) return 1; + } + if (aPos !== undefined && bPos === undefined) return -1; + if (aPos === undefined && bPos !== undefined) return 1; + if (aPos !== undefined && bPos !== undefined) { + if (aPos < bPos) return -1; + if (aPos > bPos) return 1; + } + return a < b ? -1 : a > b ? 1 : 0; +} + export function readProposals(proposalsParent: string): ProposalInfo[] { const proposalsDir = path.join(proposalsParent, 'proposals'); const proposalPaths = fs @@ -82,7 +104,8 @@ export function readProposals(proposalsParent: string): ProposalInfo[] { } return hasPackageJson; }) - .map(dirent => dirent.name); + .map(dirent => dirent.name) + .sort(compareProposalDirNames); return proposalPaths.map(readInfo); } diff --git a/packages/synthetic-chain/test/test-cli.ts b/packages/synthetic-chain/test/test-cli.ts index e5c8f143..645b81de 100644 --- a/packages/synthetic-chain/test/test-cli.ts +++ b/packages/synthetic-chain/test/test-cli.ts @@ -1,9 +1,44 @@ import test from 'ava'; import { type ProposalInfo, + compareProposalDirNames, imageNameForProposal, } from '../src/cli/proposals.js'; +test('compareProposalDirNames', t => { + const inputs = [ + '1:first', + '9:second', + '10:third', + '99:fourth', + '100:fifth', + 'foo:a', + 'bar:b', + 'baz:c', + '.dot:d', + 'Z:e', + 'qux', + 'quux', + ].sort(); + t.deepEqual(inputs.slice().sort(compareProposalDirNames), [ + // by numeric position + '1:first', + '9:second', + '10:third', + '99:fourth', + '100:fifth', + // lexicographically by whatever precedes the first colon + '.dot:d', + 'Z:e', + 'bar:b', + 'baz:c', + 'foo:a', + // lexicographically by full name + 'quux', + 'qux', + ]); +}); + test('imageNameForProposal', t => { const proposal: ProposalInfo = { type: '/agoric.swingset.CoreEvalProposal',