Files
qwen-code/scripts/tests/startup-benchmark.test.js
tanzhenxin 9534a528a6 perf(cli): keep running in place when env files only hold model credentials (#12680)
* perf(cli): keep running in place when env files only hold model credentials

The env-file relaunch rule from #12602 fires for any value that `.env` files
or `settings.env` inject, including the API key that `/auth` stores and the
keys the docs recommend for `~/.qwen/.env`. Those users always paid the
second boot. Providers read credentials and endpoints per request, never
while a module loads, so `hasLoadedEnvironmentValues()` now ignores the
documented per-auth-type variables and the `envKey` of every configured
model provider. Any other injected value still forces a fresh image, and a
provider entry can never exempt a loader or boot-time key.

The DashScope proxy base URL was the one such variable read at module load;
it is now read at call time.

Reported by @wenshao in the #12622 review (F2).

Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs

* feat(scripts): let the startup benchmark load credentials from a file

`--credentials settings|dotenv` moves the stub's key and URL from the shell
into the settings file's `env` block or `~/.qwen/.env`, so the env-file
relaunch rule can be measured. The default stays `shell`.

Claude-Session: https://claude.ai/code/session_01163b7X4Ntw1S2pr9VrtRrs
2026-09-25 10:13:38 +00:00

118 lines
4.2 KiB
JavaScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { makeRunEnvironment, parseStrace } from '../startup-benchmark/lib.mjs';
const sizes = {
'/q/cli-entry.js': 10,
'/q/cli.js': 100,
'/q/chunk.js': 1000,
'/npm/npm-cli.js': 5,
};
const fileSize = (file) => sizes[file];
describe('parseStrace', () => {
const trace = [
'100 1000.000 execve("/usr/bin/node", ["node", "/q/cli-entry.js"], 0x1 /* 6 vars */) = 0',
'100 1000.010 openat(AT_FDCWD, "/q/cli-entry.js", O_RDONLY|O_CLOEXEC) = 17',
'100 1000.020 openat(AT_FDCWD, "/q/cli.js", O_RDONLY|O_CLOEXEC) = 18',
'100 1000.030 openat(AT_FDCWD, "/q/cli.js", O_RDONLY|O_CLOEXEC) = 18',
'100 1000.040 openat(AT_FDCWD, "/q/missing.js", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)',
'100 1000.050 openat(AT_FDCWD, "/q/settings.json", O_RDONLY|O_CLOEXEC) = 19',
'101 1000.100 execve("/usr/local/bin/node", ["node", "/q/cli.js"], 0x2 /* 7 vars */) = -1 ENOENT (No such file or directory)',
'101 1000.110 execve("/usr/bin/node", ["node", "/q/cli.js"], 0x2 /* 7 vars */) = 0',
'101 1000.120 openat(AT_FDCWD, "/q/cli.js", O_RDONLY|O_CLOEXEC) = 17',
'101 1000.130 openat(AT_FDCWD, "/q/chunk.js", O_RDONLY|O_CLOEXEC) = 18',
'102 1000.200 execve("/usr/bin/git", ["git", "status"], 0x3 /* 7 vars */) = 0',
'103 1005.000 execve("/usr/bin/node", ["node", "/npm/npm-cli.js"], 0x4 /* 7 vars */) = 0',
'103 1005.010 openat(AT_FDCWD, "/npm/npm-cli.js", O_RDONLY|O_CLOEXEC) = 17',
].join('\n');
it('counts Node images and the JavaScript each one opened, once per file', () => {
expect(parseStrace(trace, Infinity, fileSize)).toEqual({
nodeProcesses: 3,
jsBytes: 10 + 100 + 100 + 1000 + 5,
});
});
it('stops at the cutoff', () => {
expect(parseStrace(trace, 1001_000, fileSize)).toEqual({
nodeProcesses: 2,
jsBytes: 10 + 100 + 100 + 1000,
});
});
});
describe('makeRunEnvironment', () => {
const modelServer = { baseUrl: (runId) => `http://127.0.0.1:1/${runId}` };
let root;
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-bench-test-'));
});
afterEach(() => {
fs.rmSync(root, { recursive: true, force: true });
});
const layout = (run) => {
const qwenDir = path.join(run.env.HOME, '.qwen');
const dotenv = path.join(qwenDir, '.env');
return {
settings: JSON.parse(
fs.readFileSync(path.join(qwenDir, 'settings.json'), 'utf8'),
),
dotenv: fs.existsSync(dotenv) ? fs.readFileSync(dotenv, 'utf8') : null,
};
};
it('exports the model credentials in the shell by default', () => {
const run = makeRunEnvironment({ root, modelServer });
expect(run.env.OPENAI_API_KEY).toBe('bench-key');
expect(run.env.OPENAI_BASE_URL).toBe(`http://127.0.0.1:1/${run.runId}`);
const { settings, dotenv } = layout(run);
expect(settings.env).toBeUndefined();
expect(dotenv).toBeNull();
});
it('puts them in the settings env block for credentials: settings', () => {
const run = makeRunEnvironment({
root,
modelServer,
credentials: 'settings',
});
expect(run.env.OPENAI_API_KEY).toBeUndefined();
expect(run.env.OPENAI_BASE_URL).toBeUndefined();
const { settings, dotenv } = layout(run);
expect(settings.env).toEqual({
OPENAI_API_KEY: 'bench-key',
OPENAI_BASE_URL: `http://127.0.0.1:1/${run.runId}`,
});
expect(dotenv).toBeNull();
});
it('puts them in ~/.qwen/.env for credentials: dotenv', () => {
const run = makeRunEnvironment({
root,
modelServer,
credentials: 'dotenv',
});
expect(run.env.OPENAI_API_KEY).toBeUndefined();
const { settings, dotenv } = layout(run);
expect(settings.env).toBeUndefined();
expect(dotenv).toBe(
`OPENAI_API_KEY=bench-key\nOPENAI_BASE_URL=http://127.0.0.1:1/${run.runId}\n`,
);
});
it('rejects an unknown credentials source', () => {
expect(() =>
makeRunEnvironment({ root, modelServer, credentials: 'vault' }),
).toThrow(/credentials must be one of/);
});
});