Files
wehub-resource-sync 85742ab165
CPU Test / Lint - next (push) Waiting to run
Dashboard / Chromatic (push) Waiting to run
CPU Test / Lint - fast (push) Waiting to run
CPU Test / Build documentation (push) Waiting to run
CPU Test / Test (Store, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Weave, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.11) (push) Waiting to run
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Others, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Store, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Utilities, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (Weave, stable, Python 3.12) (push) Waiting to run
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Others, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Store, latest, Python 3.13) (push) Waiting to run
CPU Test / Lint - slow (push) Waiting to run
CPU Test / Lint - JavaScript (push) Waiting to run
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Others, legacy, Python 3.10) (push) Waiting to run
CPU Test / Test (Utilities, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (Weave, latest, Python 3.13) (push) Waiting to run
CPU Test / Test (JavaScript) (push) Waiting to run
Deploy Documentation / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:17 +08:00

222 lines
6.1 KiB
JavaScript

// Copyright (c) Microsoft. All rights reserved.
import { spawn, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { setTimeout as sleep } from 'node:timers/promises';
import { fileURLToPath } from 'node:url';
const serverHost = process.env.VITEST_PYTHON_HOST ?? '127.0.0.1';
const serverPort = Number.parseInt(process.env.VITEST_PYTHON_PORT ?? '8765', 10);
const serverUrl =
(process.env.VITEST_PYTHON_URL && process.env.VITEST_PYTHON_URL.trim().replace(/\/$/, '')) ??
`http://${serverHost}:${Number.isFinite(serverPort) ? serverPort : 8765}`;
const serverHealthUrl = `${serverUrl}/v1/agl/health`;
let pythonServerProcess = null;
let startedByGlobalSetup = false;
const locatePythonServerPaths = () => {
const candidates = [];
const cwd = process.cwd();
candidates.push({ workspace: cwd, repo: path.resolve(cwd, '..') });
if (
typeof import.meta !== 'undefined' &&
typeof import.meta.url === 'string' &&
import.meta.url.startsWith('file:')
) {
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
candidates.push({ workspace: moduleDir, repo: path.resolve(moduleDir, '..') });
}
for (const candidate of candidates) {
const serverScript = path.resolve(candidate.workspace, 'test-utils/python-server.py');
if (fs.existsSync(serverScript)) {
return { repoRoot: candidate.repo, serverScript };
}
}
throw new Error('Unable to locate dashboard/test-utils/python-server.py from Vitest global setup.');
};
const waitForHealth = async (timeoutMs) => {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
try {
const controller = new AbortController();
const abortTimer = setTimeout(() => controller.abort(), 1000);
const response = await fetch(serverHealthUrl, { signal: controller.signal });
clearTimeout(abortTimer);
if (response.ok) {
return true;
}
} catch {
// Retry until timeout.
}
await sleep(200);
}
return false;
};
const resolvePythonRunners = (repoRoot) => {
const runners = [];
const seenCommands = new Set();
const pushRunner = (command, args, description, extraEnv = {}) => {
if (seenCommands.has(command)) {
return;
}
seenCommands.add(command);
runners.push({ command, args, description, env: extraEnv });
};
const explicit = process.env.VITEST_PYTHON_BIN?.trim();
if (explicit) {
pushRunner(explicit, [], explicit);
}
const hasUv = (() => {
try {
const result = spawnSync('uv', ['--version'], { stdio: 'ignore' });
return result.status === 0;
} catch {
return false;
}
})();
if (hasUv) {
const uvCacheDir = path.resolve(repoRoot, '.uv-cache');
pushRunner('uv', ['run', '--no-sync'], 'uv run --no-sync', { UV_CACHE_DIR: uvCacheDir });
}
const candidates = [process.env.PYTHON, process.env.PYTHON3, 'python3', 'python'].filter(
(candidate) => typeof candidate === 'string' && candidate.length > 0,
);
for (const candidate of candidates) {
if (seenCommands.has(candidate)) {
continue;
}
try {
const result = spawnSync(candidate, ['--version'], { stdio: 'ignore' });
if (result.status === 0) {
pushRunner(candidate, [], candidate);
}
} catch {
// try next candidate
}
}
if (runners.length === 0) {
throw new Error('Unable to locate a Python interpreter or uv CLI for launching the LightningStore server.');
}
return runners;
};
const ensurePythonServer = async () => {
if (await waitForHealth(1000)) {
startedByGlobalSetup = false;
return;
}
const { repoRoot, serverScript } = locatePythonServerPaths();
const runners = resolvePythonRunners(repoRoot);
const attemptErrors = [];
const collectOutput = (stream, buffer) => {
if (!stream) {
return;
}
stream.on('data', (chunk) => {
buffer.push(chunk.toString());
if (process.env.VITEST_DEBUG_PYTHON_SERVER) {
process.stderr.write(chunk);
}
});
};
for (const runner of runners) {
const child = spawn(runner.command, [...runner.args, serverScript], {
cwd: repoRoot,
stdio: ['ignore', 'pipe', 'pipe'],
env: {
...process.env,
PYTHONUNBUFFERED: '1',
...runner.env,
},
});
pythonServerProcess = child;
const recordedStdout = [];
const recordedStderr = [];
collectOutput(child.stdout, recordedStdout);
collectOutput(child.stderr, recordedStderr);
let exitPayload = null;
const exitPromise = new Promise((resolve) => {
child.on('exit', (code, signal) => {
exitPayload = { code, signal };
resolve();
});
});
const ready = await waitForHealth(10000);
if (ready) {
startedByGlobalSetup = true;
return;
}
const reused = await waitForHealth(3000);
if (reused) {
startedByGlobalSetup = false;
if (child.exitCode == null) {
child.kill('SIGTERM');
await exitPromise;
}
pythonServerProcess = null;
return;
}
if (child.exitCode == null) {
child.kill('SIGTERM');
await exitPromise;
} else {
await exitPromise;
}
const extra = exitPayload ? ` (code: ${exitPayload.code ?? 'null'}, signal: ${exitPayload.signal ?? 'null'})` : '';
const stderrLog = recordedStderr.join('').trim();
const stdoutLog = recordedStdout.join('').trim();
const message = [
`[${runner.description}] Failed to start LightningStore server at ${serverUrl}${extra}.`,
stderrLog.length > 0 ? `stderr:\n${stderrLog}` : '',
stdoutLog.length > 0 ? `stdout:\n${stdoutLog}` : '',
]
.filter(Boolean)
.join('\n\n');
attemptErrors.push(message);
}
throw new Error(attemptErrors.join('\n\n'));
};
export default async function setup() {
await ensurePythonServer();
return async () => {
if (!startedByGlobalSetup || !pythonServerProcess) {
return;
}
if (pythonServerProcess.exitCode == null) {
pythonServerProcess.kill('SIGTERM');
await new Promise((resolve) => {
pythonServerProcess?.once('exit', resolve);
});
}
};
}