-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathdev.test.js
More file actions
140 lines (122 loc) · 4.65 KB
/
Copy pathdev.test.js
File metadata and controls
140 lines (122 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const { spawnMock, platformMock, existsSyncMock } = vi.hoisted(() => ({
spawnMock: vi.fn(() => ({ on: vi.fn() })),
platformMock: vi.fn(() => 'darwin'),
existsSyncMock: vi.fn(() => false),
}));
vi.mock('node:child_process', () => ({
spawn: spawnMock,
}));
vi.mock('node:os', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
platform: platformMock,
tmpdir: vi.fn(() => '/tmp'),
};
});
vi.mock('node:fs', () => ({
writeFileSync: vi.fn(),
mkdtempSync: vi.fn(() => '/tmp/qwen-dev-test'),
rmSync: vi.fn(),
existsSync: existsSyncMock,
symlinkSync: vi.fn(),
mkdirSync: vi.fn(),
readFileSync: vi.fn(() => JSON.stringify({ version: '0.0.0-test' })),
}));
const normalizePath = (path) => String(path).replaceAll('\\', '/');
describe('scripts/dev.js launcher', () => {
const originalArgv = process.argv;
const execPathDescriptor = Object.getOwnPropertyDescriptor(
process,
'execPath',
);
beforeEach(() => {
vi.resetModules();
vi.clearAllMocks();
process.argv = ['node', 'scripts/dev.js'];
});
afterEach(() => {
process.argv = originalArgv;
if (execPathDescriptor) {
Object.defineProperty(process, 'execPath', execPathDescriptor);
}
});
it('spawns Node without a shell on Windows when local tsx cli.mjs exists', async () => {
platformMock.mockReturnValue('win32');
existsSyncMock.mockImplementation((filePath) =>
normalizePath(filePath).endsWith('node_modules/tsx/dist/cli.mjs'),
);
Object.defineProperty(process, 'execPath', {
configurable: true,
value: 'C:\\Program Files\\nodejs\\node.exe',
});
process.argv = ['node', 'scripts/dev.js', '--help'];
await import('../dev.js?direct-node');
const [command, args, options] = spawnMock.mock.calls[0];
expect(command).toBe('C:\\Program Files\\nodejs\\node.exe');
expect(args.map(normalizePath)).toEqual([
expect.stringContaining('node_modules/tsx/dist/cli.mjs'),
expect.stringContaining('packages/cli/index.ts'),
'--help',
]);
expect(options).toEqual(expect.objectContaining({ shell: false }));
});
it('keeps shell fallback for Windows tsx.cmd resolution', async () => {
platformMock.mockReturnValue('win32');
existsSyncMock.mockImplementation((filePath) =>
normalizePath(filePath).endsWith('node_modules/.bin/tsx.cmd'),
);
await import('../dev.js?cmd-fallback');
const [command, args, options] = spawnMock.mock.calls[0];
expect(normalizePath(command)).toContain('tsx.cmd');
expect(args.map(normalizePath)).toEqual([
expect.stringContaining('packages/cli/index.ts'),
]);
expect(options).toEqual(expect.objectContaining({ shell: true }));
});
it('re-raises a child signal instead of exiting 0 — close(null, SIGKILL) is not success', async () => {
// `code ?? 0` read a signal-killed child as green. This launcher is a
// QWEN_CODE_CLI entry now: an OOM-killed review gate command must not come
// back as a passing exit.
const exitSpy = vi
.spyOn(process, 'exit')
.mockImplementation(() => undefined);
const killSpy = vi.spyOn(process, 'kill').mockImplementation(() => true);
try {
await import('../dev.js?signal-close');
const child = spawnMock.mock.results[0].value;
const close = child.on.mock.calls.find(([ev]) => ev === 'close')[1];
close(null, 'SIGKILL');
expect(killSpy).toHaveBeenCalledWith(process.pid, 'SIGKILL');
expect(exitSpy).not.toHaveBeenCalledWith(0);
} finally {
exitSpy.mockRestore();
killSpy.mockRestore();
}
});
it('stamps QWEN_CODE_CLI with its own path, overriding an inherited one', async () => {
// A dev CLI started from inside another qwen session's shell inherits that
// session's QWEN_CODE_CLI. Honouring it points every `qwen …` subprocess of
// THIS session at the OUTER session's build — the exact version skew the
// variable exists to prevent, one level up and silent. Each entry stamps
// itself; nested sessions each call their own build.
const inherited = process.env.QWEN_CODE_CLI;
process.env.QWEN_CODE_CLI = '/somewhere/else/entirely/qwen';
try {
await import('../dev.js?stamps-own-cli');
const [, , options] = spawnMock.mock.calls[0];
expect(normalizePath(options.env.QWEN_CODE_CLI)).toMatch(
/scripts\/dev\.js$/,
);
} finally {
if (inherited === undefined) delete process.env.QWEN_CODE_CLI;
else process.env.QWEN_CODE_CLI = inherited;
}
});
});