Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
230 lines
8.9 KiB
JavaScript
230 lines
8.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Property-based tests for context-composer.cjs
|
|
*
|
|
* Module: msd-core/bin/lib/context-composer.cjs
|
|
* Exported: composeWithinBudget({ fragments, budget, measure, options })
|
|
*
|
|
* Key invariants (issue #2929 criterion 4):
|
|
* - moreBudgetNeverYieldsLessContent: raising the budget never shrinks the
|
|
* total surviving content.
|
|
* - neverDroppableFragmentsAlwaysSurvive: `verbatim` and `isolate`
|
|
* fragments retain their exact original content in every non-hard-fail
|
|
* result.
|
|
* - flooredFragmentNeverFallsBelowItsFloor: a `flexReserve`d fragment's
|
|
* final measured size never drops below min(flexReserve, original).
|
|
* - omittedIsAlwaysASubsequenceOfDeclaredDroppableOrder: `metadata.omitted`
|
|
* never reorders the declared `drop`-strategy ids.
|
|
* - isolatePrefixIsByteStable: `metadata.isolatePrefix` is identical
|
|
* regardless of budget or measure function.
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fc = require('./helpers/fast-check-setup.cjs');
|
|
|
|
const { composeWithinBudget } = require('../msd-core/bin/lib/context-composer.cjs');
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
|
|
// Monotone measure functions: growing text never yields a smaller measure.
|
|
const measureChars4 = (text) => (text ? Math.ceil(text.length / 4) : 0);
|
|
const measureBytes = (text) => (text ? Buffer.byteLength(text, 'utf8') : 0);
|
|
|
|
const strategyArb = fc.oneof(
|
|
fc.constant({ kind: 'verbatim' }),
|
|
fc.record({ kind: fc.constant('head-shrink'), maxLines: fc.integer({ min: 0, max: 8 }) }),
|
|
fc.record({
|
|
kind: fc.constant('proportional-truncate'),
|
|
floorChars: fc.integer({ min: 0, max: 40 }),
|
|
}),
|
|
fc.constant({ kind: 'drop' })
|
|
);
|
|
|
|
/** A realistic mixed fragment set: varying content, all four strategies, unique ids. */
|
|
function fragmentsArb({ minLength = 0, maxLength = 8 } = {}) {
|
|
return fc
|
|
.array(
|
|
fc.record({
|
|
content: fc.string({ maxLength: 300 }),
|
|
strategy: strategyArb,
|
|
required: fc.boolean(),
|
|
flexReserve: fc.integer({ min: 0, max: 30 }),
|
|
}),
|
|
{ minLength, maxLength }
|
|
)
|
|
.map((items) =>
|
|
items.map((item, idx) => ({
|
|
id: `f${idx}`,
|
|
content: item.content,
|
|
strategy: item.strategy,
|
|
required: item.required,
|
|
flexReserve: item.flexReserve,
|
|
}))
|
|
);
|
|
}
|
|
|
|
/** Marks a random leading prefix of `fragments` as isolate (order-legal). */
|
|
function withIsolatePrefix(fragments, isolateCount) {
|
|
const n = Math.min(isolateCount, fragments.length);
|
|
return fragments.map((f, idx) => (idx < n ? { ...f, isolate: true } : f));
|
|
}
|
|
|
|
const totalContentLength = (result) => result.fragments.reduce((sum, f) => sum + f.content.length, 0);
|
|
|
|
// ─── Properties ──────────────────────────────────────────────────────────────
|
|
|
|
describe('context-composer: composeWithinBudget properties', () => {
|
|
test('property: moreBudgetNeverYieldsLessContent', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fragmentsArb(),
|
|
fc.integer({ min: 0, max: 2000 }),
|
|
fc.integer({ min: 0, max: 2000 }),
|
|
(fragments, budgetA, budgetB) => {
|
|
const lo = Math.min(budgetA, budgetB);
|
|
const hi = Math.max(budgetA, budgetB);
|
|
const resultLo = composeWithinBudget({ fragments, budget: lo, measure: measureChars4 });
|
|
const resultHi = composeWithinBudget({ fragments, budget: hi, measure: measureChars4 });
|
|
if (resultLo.metadata.hardFailed || resultHi.metadata.hardFailed) return;
|
|
|
|
const contentLo = totalContentLength(resultLo);
|
|
const contentHi = totalContentLength(resultHi);
|
|
assert.ok(
|
|
contentHi >= contentLo,
|
|
`budget ${hi} yielded less content (${contentHi}) than budget ${lo} (${contentLo})`
|
|
);
|
|
}
|
|
)
|
|
);
|
|
});
|
|
|
|
test('property: neverDroppableFragmentsAlwaysSurvive (verbatim + isolate)', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fragmentsArb({ minLength: 1 }),
|
|
fc.nat({ max: 8 }),
|
|
fc.integer({ min: 0, max: 50 }),
|
|
(fragments, isolateCount, budget) => {
|
|
const withIsolates = withIsolatePrefix(fragments, isolateCount);
|
|
const result = composeWithinBudget({ fragments: withIsolates, budget, measure: measureChars4 });
|
|
if (result.metadata.hardFailed) return;
|
|
|
|
for (const f of withIsolates) {
|
|
const isNeverDroppable = f.isolate === true || f.strategy.kind === 'verbatim';
|
|
if (!isNeverDroppable) continue;
|
|
const composed = result.fragments.find((c) => c.id === f.id);
|
|
assert.ok(composed, `fragment ${f.id} missing from result`);
|
|
assert.equal(
|
|
composed.content,
|
|
f.content,
|
|
`never-droppable fragment ${f.id} (isolate=${f.isolate === true}, strategy=${f.strategy.kind}) was altered`
|
|
);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
});
|
|
|
|
test('property: flooredFragmentNeverFallsBelowItsFloor', () => {
|
|
fc.assert(
|
|
fc.property(fragmentsArb({ minLength: 1 }), fc.integer({ min: 0, max: 2000 }), (fragments, budget) => {
|
|
const result = composeWithinBudget({ fragments, budget, measure: measureChars4 });
|
|
if (result.metadata.hardFailed) return;
|
|
|
|
for (const f of fragments) {
|
|
if (!f.flexReserve || f.flexReserve <= 0) continue;
|
|
const composed = result.fragments.find((c) => c.id === f.id);
|
|
assert.ok(composed, `fragment ${f.id} missing from result`);
|
|
const originalMeasured = measureChars4(f.content);
|
|
const finalMeasured = measureChars4(composed.content);
|
|
const expectedFloor = Math.min(f.flexReserve, originalMeasured);
|
|
assert.ok(
|
|
finalMeasured >= expectedFloor,
|
|
`fragment ${f.id}: final measured size ${finalMeasured} < floor ${expectedFloor} ` +
|
|
`(flexReserve=${f.flexReserve}, originalMeasured=${originalMeasured})`
|
|
);
|
|
}
|
|
})
|
|
);
|
|
});
|
|
|
|
test('property: omittedIsAlwaysASubsequenceOfDeclaredDroppableOrder', () => {
|
|
fc.assert(
|
|
fc.property(fragmentsArb(), fc.integer({ min: 0, max: 2000 }), (fragments, budget) => {
|
|
const result = composeWithinBudget({ fragments, budget, measure: measureChars4 });
|
|
if (result.metadata.hardFailed) return;
|
|
|
|
const declaredDroppableOrder = fragments
|
|
.filter((f) => f.strategy.kind === 'drop')
|
|
.map((f) => f.id);
|
|
|
|
// A subsequence: walk declaredDroppableOrder once, consuming
|
|
// omitted ids in order — every omitted id must be found, in order,
|
|
// without skipping backward.
|
|
let cursor = 0;
|
|
for (const omittedId of result.metadata.omitted) {
|
|
const foundAt = declaredDroppableOrder.indexOf(omittedId, cursor);
|
|
assert.ok(
|
|
foundAt !== -1,
|
|
`omitted id "${omittedId}" is not a subsequence of declared droppable order ` +
|
|
`[${declaredDroppableOrder.join(', ')}] (omitted: [${result.metadata.omitted.join(', ')}])`
|
|
);
|
|
cursor = foundAt + 1;
|
|
}
|
|
})
|
|
);
|
|
});
|
|
|
|
test('property: isolatePrefixIsByteStable', () => {
|
|
fc.assert(
|
|
fc.property(
|
|
fragmentsArb({ minLength: 1 }),
|
|
fc.nat({ max: 8 }),
|
|
fc.integer({ min: 1, max: 5 }),
|
|
fc.integer({ min: 500, max: 5000 }),
|
|
(fragments, isolateCount, tinyBudget, hugeBudget) => {
|
|
const withIsolates = withIsolatePrefix(fragments, isolateCount);
|
|
if (!withIsolates.some((f) => f.isolate)) return;
|
|
|
|
const resultTinyChars = composeWithinBudget({
|
|
fragments: withIsolates,
|
|
budget: tinyBudget,
|
|
measure: measureChars4,
|
|
});
|
|
const resultHugeChars = composeWithinBudget({
|
|
fragments: withIsolates,
|
|
budget: hugeBudget,
|
|
measure: measureChars4,
|
|
});
|
|
const resultTinyBytes = composeWithinBudget({
|
|
fragments: withIsolates,
|
|
budget: tinyBudget,
|
|
measure: measureBytes,
|
|
});
|
|
const resultHugeBytes = composeWithinBudget({
|
|
fragments: withIsolates,
|
|
budget: hugeBudget,
|
|
measure: measureBytes,
|
|
});
|
|
|
|
const prefixes = [
|
|
resultTinyChars.metadata.isolatePrefix,
|
|
resultHugeChars.metadata.isolatePrefix,
|
|
resultTinyBytes.metadata.isolatePrefix,
|
|
resultHugeBytes.metadata.isolatePrefix,
|
|
];
|
|
for (const prefix of prefixes) {
|
|
assert.equal(
|
|
prefix,
|
|
prefixes[0],
|
|
`isolatePrefix diverged across budget/measure combinations: ${JSON.stringify(prefixes)}`
|
|
);
|
|
}
|
|
}
|
|
)
|
|
);
|
|
});
|
|
});
|