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
17 changes: 9 additions & 8 deletions packages/adapter-dotnet/src/utils/dotnet-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,10 @@ export async function findDotnetBackend(
* @returns Array of process info objects
*/
export async function listDotnetProcesses(
logger: Logger = noopLogger
logger: Logger = noopLogger,
platform: NodeJS.Platform = process.platform
): Promise<Array<{ name: string; pid: number }>> {
if (process.platform !== 'win32') {
if (platform !== 'win32') {
logger.debug?.('[Dotnet Detection] Process listing is currently Windows-only');
return [];
}
Expand Down Expand Up @@ -269,8 +270,8 @@ export async function listDotnetProcesses(
* @param pid Process ID
* @returns Full path to the process executable, or null if not found
*/
export function getProcessExecutablePath(pid: number | string): string | null {
if (process.platform !== 'win32') {
export function getProcessExecutablePath(pid: number | string, platform: NodeJS.Platform = process.platform): string | null {
if (platform !== 'win32') {
return null;
}

Expand Down Expand Up @@ -304,8 +305,8 @@ export function getProcessExecutablePath(pid: number | string): string | null {
* @param pid Process ID
* @returns Directory containing the process executable, or null if not found
*/
export function getProcessExecutableDir(pid: number | string): string | null {
const exePath = getProcessExecutablePath(pid);
export function getProcessExecutableDir(pid: number | string, platform: NodeJS.Platform = process.platform): string | null {
const exePath = getProcessExecutablePath(pid, platform);
return exePath ? path.dirname(exePath) : null;
}

Expand Down Expand Up @@ -352,8 +353,8 @@ export function getExeArchitecture(exePath: string): 'x86' | 'x64' | null {
* @param pid Process ID
* @returns 'x86' or 'x64', or null if detection fails
*/
export function getProcessArchitecture(pid: number | string): 'x86' | 'x64' | null {
const exePath = getProcessExecutablePath(pid);
export function getProcessArchitecture(pid: number | string, platform: NodeJS.Platform = process.platform): 'x86' | 'x64' | null {
const exePath = getProcessExecutablePath(pid, platform);
if (!exePath) return null;
return getExeArchitecture(exePath);
}
Expand Down
78 changes: 14 additions & 64 deletions packages/adapter-dotnet/tests/unit/dotnet-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,16 @@ describe('findDotnetBackend', () => {
});

describe('listDotnetProcesses', () => {
const originalPlatform = process.platform;

beforeEach(() => {
vi.clearAllMocks();
});

it('returns empty array on non-Windows platforms', async () => {
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'linux');
expect(result).toEqual([]);

Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('parses tasklist CSV output on Windows', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({
exitCode: 0,
stdout: [
Expand All @@ -203,35 +195,25 @@ describe('listDotnetProcesses', () => {
].join('\n')
}));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');

expect(result).toHaveLength(2);
expect(result).toContainEqual({ name: 'NinjaTrader.exe', pid: 12345 });
expect(result).toContainEqual({ name: 'devenv.exe', pid: 67890 });

Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('returns empty array when tasklist fails', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({ exitCode: 1 }));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');
expect(result).toEqual([]);

Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('returns empty array when tasklist spawn errors', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({ exitCode: 0, error: new Error('spawn failed') }));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');
expect(result).toEqual([]);

Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});
});

Expand Down Expand Up @@ -366,19 +348,11 @@ describe('findNetcoredbgExecutable (additional coverage)', () => {
});

describe('listDotnetProcesses (additional coverage)', () => {
const originalPlatform = process.platform;

beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('skips malformed CSV lines', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({
exitCode: 0,
stdout: [
Expand All @@ -389,23 +363,19 @@ describe('listDotnetProcesses (additional coverage)', () => {
].join('\n')
}));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');
expect(result).toHaveLength(1);
expect(result[0]).toEqual({ name: 'NinjaTrader.exe', pid: 12345 });
});

it('returns empty array for empty output', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({ exitCode: 0, stdout: '' }));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');
expect(result).toEqual([]);
});

it('recognizes all known .NET processes', async () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

spawnMock.mockReturnValue(createSpawn({
exitCode: 0,
stdout: [
Expand All @@ -418,7 +388,7 @@ describe('listDotnetProcesses (additional coverage)', () => {
].join('\n')
}));

const result = await listDotnetProcesses();
const result = await listDotnetProcesses(undefined, 'win32');
expect(result).toHaveLength(5);
});
});
Expand Down Expand Up @@ -777,70 +747,51 @@ describe('getExeArchitecture', () => {
});

describe('getProcessExecutablePath', () => {
const originalPlatform = process.platform;

beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('returns null on non-Windows platforms', () => {
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
expect(getProcessExecutablePath(1234)).toBeNull();
expect(getProcessExecutablePath(1234, 'linux')).toBeNull();
});

it('returns executable path via WMIC on Windows', () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
spawnSyncMock.mockReturnValue({
status: 0,
stdout: Buffer.from('\r\nExecutablePath=C:\\Program Files\\App\\app.exe\r\n\r\n'),
stderr: Buffer.from('')
});

expect(getProcessExecutablePath(1234)).toBe('C:\\Program Files\\App\\app.exe');
expect(getProcessExecutablePath(1234, 'win32')).toBe('C:\\Program Files\\App\\app.exe');
});

it('returns null when WMIC fails', () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
spawnSyncMock.mockReturnValue({ status: 1, stdout: Buffer.from(''), stderr: Buffer.from('') });

expect(getProcessExecutablePath(1234)).toBeNull();
expect(getProcessExecutablePath(1234, 'win32')).toBeNull();
});

it('returns null when WMIC output has no ExecutablePath', () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
spawnSyncMock.mockReturnValue({
status: 0,
stdout: Buffer.from('No Instance(s) Available.\r\n'),
stderr: Buffer.from('')
});

expect(getProcessExecutablePath(1234)).toBeNull();
expect(getProcessExecutablePath(1234, 'win32')).toBeNull();
});
});

describe('getProcessArchitecture', () => {
const originalPlatform = process.platform;

beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
});

it('returns null on non-Windows platforms', () => {
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
expect(getProcessArchitecture(1234)).toBeNull();
expect(getProcessArchitecture(1234, 'linux')).toBeNull();
});

it('returns x86 for a 32-bit process', () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });

// WMIC returns exe path
spawnSyncMock.mockReturnValue({
status: 0,
Expand All @@ -864,14 +815,13 @@ describe('getProcessArchitecture', () => {
});
closeSyncMock.mockReturnValue(undefined);

expect(getProcessArchitecture(1234)).toBe('x86');
expect(getProcessArchitecture(1234, 'win32')).toBe('x86');
});

it('returns null when exe path cannot be determined', () => {
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
spawnSyncMock.mockReturnValue({ status: 1, stdout: Buffer.from(''), stderr: Buffer.from('') });

expect(getProcessArchitecture(1234)).toBeNull();
expect(getProcessArchitecture(1234, 'win32')).toBeNull();
});
});

Expand Down
7 changes: 4 additions & 3 deletions packages/adapter-javascript/src/javascript-adapter-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ export class JavascriptAdapterFactory extends BaseAdapterFactory {
* - Node.js version >= 14
* - Bundled js-debug adapter present
* - TypeScript runner availability (warnings only)
*
* @param nodeVersion Node version override for tests (issue #186); defaults to process.version
*/
async validate(): Promise<FactoryValidationResult> {
async validate(nodeVersion: string = process.version): Promise<FactoryValidationResult> {
const errors: string[] = [];
const warnings: string[] = [];

// Node.js version check
const nodeVersion = process.version; // e.g., v20.11.1
// Node.js version check (nodeVersion e.g. v20.11.1)
let major = 0;
const m = /^v?(\d+)\./.exec(nodeVersion);
if (m) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, vi, afterEach } from 'vitest';
import fs from 'fs';
import path from 'path';
import { JavascriptAdapterFactory } from '../../src/index.js';
Expand All @@ -12,18 +12,7 @@ function isVendorPath(p: unknown): boolean {
}

describe('JavascriptAdapterFactory.validate', () => {
let versionDescriptor: PropertyDescriptor | undefined;

beforeEach(() => {
// Save original process.version descriptor so we can restore it
versionDescriptor = Object.getOwnPropertyDescriptor(process, 'version');
});

afterEach(() => {
// Restore process.version
if (versionDescriptor) {
Object.defineProperty(process, 'version', versionDescriptor);
}
vi.restoreAllMocks();
vi.clearAllMocks();
});
Expand All @@ -46,20 +35,14 @@ describe('JavascriptAdapterFactory.validate', () => {
});

it('flags error when Node version is too old (< 14)', async () => {
// Force Node v12
Object.defineProperty(process, 'version', {
configurable: true,
get: () => 'v12.22.0'
});

// Vendor present
vi.spyOn(fs, 'existsSync').mockImplementation((p: unknown) => {
return isVendorPath(p);
});
vi.stubEnv('PATH', '');

const factory = new JavascriptAdapterFactory();
const res = await factory.validate();
const res = await factory.validate('v12.22.0');

expect(res.valid).toBe(false);
expect(res.errors.some(e => e.includes('Node.js 14+ required') && e.includes('v12.22.0'))).toBe(true);
Expand Down
Loading
Loading