qwen-code/scripts/tests/dev.test.js
Shaojin Wen 4bb2099089
fix(ci): normalize dev launcher path assertions on Windows (#4915)
The two Windows-targeted dev.js launcher tests added in #4728 mock
existsSync with forward-slash suffix matching and assert spawn args via
forward-slash stringContaining. On a real windows-latest runner dev.js
builds these paths with path.join, which yields backslashes, so the
existsSync mock never matches, dev.js takes the bare tsx.cmd shell
fallback, and both tests fail. On macOS/Linux the platform() mock plus
real forward-slash joins keep them green, which is why main CI has been
red only on the Windows job since 423cac110.

Normalize separators in both the existsSync mocks and the received
spawn arguments before asserting, so the tests pass on every host OS.

Same content as the fix bundled into #4840's merge commit 0e104e179,
extracted into a standalone test-only change so main recovers without
waiting on a core-behavior PR; both merge cleanly in either order.

Co-authored-by: qqqys <qys177@gmail.com>
2026-06-10 10:20:29 +08:00

97 lines
2.8 KiB
JavaScript

/**
* @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),
}));
const normalizePath = (filePath) => String(filePath).replaceAll('\\', '/');
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(),
}));
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 }));
});
});