Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,18 @@ function runTrace(config, flowName, args, opts) {
LOOPFINDER_ALLOW_NETWORK: opts.allowNetwork ? '1' : '',
LOOPFINDER_ALLOW_SUBPROCESS: opts.allowSubprocess ? '1' : '',
};
// A record left by an earlier build must not pass for this run's: if the script fails to start,
// the old record would be used as if it were new, and declared loops would look broken.
fs.rmSync(cache, { force: true });
const r = spawnSync(py ? config.python : process.execPath,
py ? [TRACE_PY, script, ...args.slice(1)] : ['-r', TRACE_JS, script, ...args.slice(1)],
{ cwd: config.root, env, encoding: 'utf8', timeout: opts.timeout || 600000 });
// Windows ships a `python` that only points to the Microsoft Store (exit 9009), so a missing
// interpreter shows up either as a spawn error or as that message.
if (py && !fs.existsSync(cache) && (r.error?.code === 'ENOENT' || r.status === 9009 || /Python was not found/.test(r.stderr || ''))) {
throw new Error(`could not start Python to trace ${args[0]} ("python": ${JSON.stringify(config.python)} in the config).\n`
+ 'Install Python 3, or set "python" in loopfinder/config.json to the interpreter (for example "py" or a full path).');
}
if (!fs.existsSync(cache)) {
throw new Error(`trace did not finish: ${args.join(' ')} (exit ${r.status})\n${(r.stderr || '').slice(-2000)}`);
}
Expand Down
20 changes: 20 additions & 0 deletions test/core.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,23 @@ test('demo: builds the expected loops and leaves the workspace untouched', () =>
const feedNode = graph.nodes.find(n => n.id === 'web:Garden feed');
assert.ok(feedNode, 'the blocked fetch is still recorded and labelled');
});

test('build: a Python that cannot start is named, and an old record is not used in its place', () => {
const src = path.join(__dirname, '..', 'examples', 'demo');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'loopfinder-nopy-'));
fs.cpSync(src, dir, { recursive: true });
const cfgFile = path.join(dir, 'loopfinder', 'config.json');
const cfg = JSON.parse(fs.readFileSync(cfgFile, 'utf8'));
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'lf-nopy-out-'));
cfg.output = path.join(out, 'flow.json');
cfg.cache = path.join(out, 'traces');
cfg.python = 'loopfinder-no-such-python';
fs.writeFileSync(cfgFile, JSON.stringify(cfg));
// A record from an earlier build, as if Python had been there last time.
fs.mkdirSync(cfg.cache, { recursive: true });
const stale = path.join(cfg.cache, 'reading__weekly_summary.py.json');
fs.writeFileSync(stale, JSON.stringify({ reads: [], writes: [], network: [], subprocess: [] }));
assert.throws(() => build(loadConfig(cfgFile), { warn: () => {} }),
e => /could not start Python/.test(e.message) && e.message.includes('loopfinder-no-such-python'));
assert.ok(!fs.existsSync(stale), 'the old record was left to pass for this run');
});
Loading