From 5b37f00365db4ec298ce1dba7f323199cd59c818 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 9 Jun 2026 08:23:02 -0700 Subject: [PATCH] Refine compact discovery payload review feedback - Move isAbsolutePath helper to common/platform/fs-paths so it can be shared instead of duplicated inside testDiscoveryHandler. Uses path.posix.isAbsolute || path.win32.isAbsolute so paths produced on either OS (e.g. coming from a Python subprocess) are recognized. - Tighten create_class_node annotation in vscode_pytest from Any to pytest.Class | DescribeBlockType. DescribeBlockType is imported under TYPE_CHECKING so it remains optional at runtime. - Add unit tests for isAbsolutePath covering POSIX absolute, Windows drive-letter, UNC, and relative paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python_files/vscode_pytest/__init__.py | 3 ++- src/client/common/platform/fs-paths.ts | 13 +++++++++ .../common/testDiscoveryHandler.ts | 5 +--- .../common/platform/fs-paths.unit.test.ts | 27 ++++++++++++++++++- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index e86b6e155ec3..88f6327f6e8b 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: from pluggy import Result + from pytest_describe.plugin import DescribeBlock as DescribeBlockType from typing_extensions import NotRequired USES_PYTEST_DESCRIBE = False @@ -873,7 +874,7 @@ def create_session_node(session: pytest.Session) -> TestNode: } -def create_class_node(class_module: Any) -> TestNode: +def create_class_node(class_module: pytest.Class | DescribeBlockType) -> TestNode: """Creates a class node from a pytest class object. Keyword arguments: diff --git a/src/client/common/platform/fs-paths.ts b/src/client/common/platform/fs-paths.ts index fa809d31b0b9..7cdaef02f77d 100644 --- a/src/client/common/platform/fs-paths.ts +++ b/src/client/common/platform/fs-paths.ts @@ -148,6 +148,19 @@ export function normCasePath(filePath: string): string { return normCase(nodepath.normalize(filePath)); } +/** + * Returns true if the given path is absolute on either POSIX or Windows. + * + * Node's `path.isAbsolute` is platform-dependent, so a POSIX path like `/foo` + * is not recognized as absolute on Windows, and a Windows path like `C:\foo` + * is not recognized as absolute on POSIX. Use this helper when the path could + * have been produced on a different OS than the one currently running (e.g. + * paths received in a JSON payload from a Python subprocess). + */ +export function isAbsolutePath(value: string): boolean { + return nodepath.posix.isAbsolute(value) || nodepath.win32.isAbsolute(value); +} + export function normCase(s: string): string { return getOSType() === OSType.Windows ? s.toUpperCase() : s; } diff --git a/src/client/testing/testController/common/testDiscoveryHandler.ts b/src/client/testing/testController/common/testDiscoveryHandler.ts index 91e63b2534dc..2cd5e248e275 100644 --- a/src/client/testing/testController/common/testDiscoveryHandler.ts +++ b/src/client/testing/testController/common/testDiscoveryHandler.ts @@ -11,10 +11,7 @@ import { createErrorTestItem } from './testItemUtilities'; import { buildErrorNodeOptions, populateTestTree } from './utils'; import { TestItemIndex } from './testItemIndex'; import { PROJECT_ID_SEPARATOR } from './projectUtils'; - -function isAbsolutePath(value: string): boolean { - return /^([a-zA-Z]:[\\/]|\\\\)/.test(value) || value.startsWith('/'); -} +import { isAbsolutePath } from '../../../common/platform/fs-paths'; function joinWithBase(base: string, relativePath: string): string { if (!base || isAbsolutePath(relativePath)) { diff --git a/src/test/common/platform/fs-paths.unit.test.ts b/src/test/common/platform/fs-paths.unit.test.ts index b34b65d01e53..9518f061bc0f 100644 --- a/src/test/common/platform/fs-paths.unit.test.ts +++ b/src/test/common/platform/fs-paths.unit.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { FileSystemPathUtils } from '../../../client/common/platform/fs-paths'; +import { FileSystemPathUtils, isAbsolutePath } from '../../../client/common/platform/fs-paths'; import { getNamesAndValues } from '../../../client/common/utils/enum'; import { OSType } from '../../../client/common/utils/platform'; @@ -112,3 +112,28 @@ suite('FileSystem - Path Utils', () => { }); }); }); + +suite('FileSystem - isAbsolutePath', () => { + test('Recognizes POSIX absolute paths', () => { + expect(isAbsolutePath('/foo/bar')).to.equal(true); + expect(isAbsolutePath('/')).to.equal(true); + }); + + test('Recognizes Windows drive-letter absolute paths', () => { + expect(isAbsolutePath('C:\\foo\\bar')).to.equal(true); + expect(isAbsolutePath('c:/foo/bar')).to.equal(true); + expect(isAbsolutePath('Z:\\')).to.equal(true); + }); + + test('Recognizes Windows UNC absolute paths', () => { + expect(isAbsolutePath('\\\\server\\share\\foo')).to.equal(true); + }); + + test('Rejects relative paths', () => { + expect(isAbsolutePath('foo/bar')).to.equal(false); + expect(isAbsolutePath('./foo')).to.equal(false); + expect(isAbsolutePath('../foo')).to.equal(false); + expect(isAbsolutePath('foo\\bar')).to.equal(false); + expect(isAbsolutePath('')).to.equal(false); + }); +});