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.
120 lines
4.8 KiB
JavaScript
120 lines
4.8 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Regression test for #315 — perf: repeated subrepo detection in loadConfig.
|
|
*
|
|
* Before the fix, loadConfig called detectSubRepos(cwd) up to 3 times per
|
|
* invocation (all with the same cwd):
|
|
* Site 1 (~line 305): root-config requiresFilesystem migration (workstream path)
|
|
* Site 2 (~line 353): workstream-config requiresFilesystem migration
|
|
* Site 3 (~line 367): planning.sub_repos filesystem re-sync
|
|
*
|
|
* After the fix, a per-call lazy memo ensures detectSubRepos(cwd) is called
|
|
* EXACTLY once regardless of how many sites trigger.
|
|
*
|
|
* Fixture triggers:
|
|
* - options.workstream set → loads root config (site 1 candidate)
|
|
* - root config has `multiRepo: true` → requiresFilesystem → site 1 fires
|
|
* - workstream config has `planning.sub_repos: [...]` → site 3 fires
|
|
* - workstream config also has `multiRepo: true` → requiresFilesystem → site 2 fires
|
|
* Combined: 3 distinct call sites attempted; memo should collapse to 1 scan.
|
|
*/
|
|
|
|
const { describe, test, afterEach } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const os = require('node:os');
|
|
const { cleanup } = require('./helpers.cjs');
|
|
|
|
// Import loadConfig directly (sync, no CLI subprocess needed)
|
|
const { loadConfig } = require('../msd-core/bin/lib/config-loader.cjs');
|
|
|
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
function createProjectWithSubRepo(prefix = 'msd-315-test-') {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
|
|
// Create a child dir that looks like a subrepo
|
|
const subRepoDir = path.join(tmpDir, 'sub-service');
|
|
fs.mkdirSync(path.join(subRepoDir, '.git'), { recursive: true });
|
|
|
|
// Create .planning root layout
|
|
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases'), { recursive: true });
|
|
|
|
return tmpDir;
|
|
}
|
|
|
|
function writeRootConfig(tmpDir, obj) {
|
|
const configPath = path.join(tmpDir, '.planning', 'config.json');
|
|
fs.writeFileSync(configPath, JSON.stringify(obj, null, 2), 'utf-8');
|
|
}
|
|
|
|
function writeWorkstreamConfig(tmpDir, wsName, obj) {
|
|
const wsDir = path.join(tmpDir, '.planning', 'workstreams', wsName, 'phases');
|
|
fs.mkdirSync(wsDir, { recursive: true });
|
|
const configPath = path.join(tmpDir, '.planning', 'workstreams', wsName, 'config.json');
|
|
fs.writeFileSync(configPath, JSON.stringify(obj, null, 2), 'utf-8');
|
|
}
|
|
|
|
// ─── test ─────────────────────────────────────────────────────────────────────
|
|
|
|
describe('perf-315 — loadConfig calls detectSubRepos at most once per invocation', () => {
|
|
let tmpDir;
|
|
|
|
afterEach(() => {
|
|
if (tmpDir) {
|
|
cleanup(tmpDir);
|
|
tmpDir = null;
|
|
}
|
|
});
|
|
|
|
test('detectSubRepos scan count is exactly 1 regardless of how many sites trigger', () => {
|
|
tmpDir = createProjectWithSubRepo();
|
|
|
|
const wsName = 'test-ws';
|
|
|
|
// Root config: multiRepo: true triggers requiresFilesystem at site 1
|
|
writeRootConfig(tmpDir, { multiRepo: true, model_profile: 'balanced' });
|
|
|
|
// Workstream config:
|
|
// - planning.sub_repos set → triggers site 3 (fs-resync)
|
|
// - multiRepo: true → triggers site 2 (requiresFilesystem normalization)
|
|
writeWorkstreamConfig(tmpDir, wsName, {
|
|
multiRepo: true,
|
|
planning: { sub_repos: ['sub-service'] },
|
|
model_profile: 'balanced',
|
|
});
|
|
|
|
// Spy on fs.readdirSync — count only calls that match detectSubRepos' signature
|
|
// (first arg === tmpDir, options object containing withFileTypes: true)
|
|
let scanCount = 0;
|
|
const originalReaddirSync = fs.readdirSync;
|
|
fs.readdirSync = function spyReaddirSync(dirPath, opts) {
|
|
if (dirPath === tmpDir && opts && opts.withFileTypes === true) {
|
|
scanCount += 1;
|
|
}
|
|
return originalReaddirSync.call(this, dirPath, opts);
|
|
};
|
|
|
|
try {
|
|
// Load config with workstream option so all three sites can be reached
|
|
const config = loadConfig(tmpDir, { workstream: wsName });
|
|
|
|
// Behavior lock: sub_repos is correctly resolved
|
|
assert.ok(
|
|
Array.isArray(config.sub_repos) || Array.isArray(config.planning?.sub_repos),
|
|
'sub_repos should be an array in the returned config'
|
|
);
|
|
|
|
// Performance assertion: only one scan regardless of how many sites triggered
|
|
assert.strictEqual(
|
|
scanCount, 1,
|
|
`Expected detectSubRepos to scan cwd exactly once, but fs.readdirSync was called ${scanCount} time(s) with the cwd + {withFileTypes:true} signature`
|
|
);
|
|
} finally {
|
|
fs.readdirSync = originalReaddirSync;
|
|
}
|
|
});
|
|
});
|