Skip to content
Open
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
41 changes: 41 additions & 0 deletions python_files/tests/unittestadapter/test_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,44 @@ def test_symlink_with_project_root_path(mock_send_run_data) -> None: # noqa: AR
assert actual["cwd"] == os.fsdecode(destination), (
f"CWD does not match symlink path: expected {os.fsdecode(destination)}, got {actual['cwd']}"
)


def test_project_root_path_defaults_top_level_dir(mock_send_run_data) -> None: # noqa: ARG001
"""Test that project_root_path acts as top_level_dir during execution when start_dir is a subdir.

When tests are in a subdirectory (e.g. ./test_outer_folder/test_inner_folder) and top_level_dir
is None, passing project_root_path as top_level_dir ensures the discovered test IDs match
those discovered during the discovery phase.
"""
project_path = TEST_DATA_PATH / "utils_complex_tree"
start_dir = os.fsdecode(
pathlib.PurePath(
TEST_DATA_PATH,
"utils_complex_tree",
"test_outer_folder",
"test_inner_folder",
)
)
pattern = "test_*.py"
top_level_dir = os.fsdecode(project_path)
test_ids = [
"test_outer_folder.test_inner_folder.test_utils_complex_tree.TreeOne.test_one",
]

os.environ["TEST_RUN_PIPE"] = "fake"

actual = run_tests(
start_dir,
test_ids,
pattern,
None,
1,
None,
project_root_path=top_level_dir,
)

assert actual["status"] == "success"
assert actual["cwd"] == os.fsdecode(project_path)
assert actual["result"] is not None
assert test_ids[0] in actual["result"]
assert actual["result"][test_ids[0]]["outcome"] == "success"
5 changes: 3 additions & 2 deletions python_files/unittestadapter/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,10 @@ def discover_tests(
raise VSCodeUnittestError(error_msg) # noqa: B904
else:
# Check for PROJECT_ROOT_PATH environment variable (project-based testing).
# When set, this overrides top_level_dir to root the test tree at the project directory.
# When set, this overrides top_level_dir to root the test tree at the project directory
# if an explicit top-level directory was not provided.
project_root_path = os.environ.get("PROJECT_ROOT_PATH")
if project_root_path:
if project_root_path and not top_level_dir:
top_level_dir = project_root_path

# Perform regular unittest test discovery.
Expand Down
4 changes: 4 additions & 0 deletions python_files/unittestadapter/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ def run_tests(
(used for project-based testing to root test tree at project)
"""
cwd = os.path.abspath(project_root_path or start_dir) # noqa: PTH100
if top_level_dir is None and project_root_path:
top_level_dir = project_root_path
if "/" in start_dir: # is a subdir
parent_dir = os.path.dirname(start_dir) # noqa: PTH120
sys.path.insert(0, parent_dir)
Expand Down Expand Up @@ -373,6 +375,8 @@ def send_run_data(raw_data, test_run_pipe):
# Update the module-level variable for send_run_data to use
# pylint: disable=global-statement
globals()["PROJECT_ROOT_PATH"] = project_root_path
if not top_level_dir:
top_level_dir = project_root_path

# Perform regular unittest execution.
# Pass project_root_path so the payload's cwd matches the project root.
Expand Down
6 changes: 5 additions & 1 deletion src/client/testing/common/debugLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ export class DebugLauncher implements ITestDebugLauncher {
);
}

if (options.project) {
launchArgs.env.PROJECT_ROOT_PATH = options.project.uri.fsPath;
}

const pluginPath = path.join(EXTENSION_ROOT_DIR, 'python_files');
// check if PYTHONPATH is already set in the environment variables
if (launchArgs.env) {
Expand All @@ -322,7 +326,7 @@ export class DebugLauncher implements ITestDebugLauncher {
}
// add the plugin path or cwd to PYTHONPATH if it is not already there using the following function
// this function will handle if PYTHONPATH is undefined
addPathToPythonpath(additionalPythonPath, launchArgs.env.PYTHONPATH);
launchArgs.env.PYTHONPATH = addPathToPythonpath(additionalPythonPath, launchArgs.env.PYTHONPATH);
}

// Clear out purpose so we can detect if the configuration was used to
Expand Down
4 changes: 4 additions & 0 deletions src/test/testing/common/debugLauncher.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,8 @@ suite('Unit Tests - Debug Launcher', () => {

expect(capturedConfigs).to.have.length(1);
expect(capturedConfigs[0].name).to.equal('Debug Tests: myproject (Python 3.11)');
expect(capturedConfigs[0].env?.PROJECT_ROOT_PATH).to.equal(Uri.file('one/two/three').fsPath);
expect(capturedConfigs[0].env?.PYTHONPATH).to.include(path.join(EXTENSION_ROOT_DIR, 'python_files'));
});

test('should use default python when no project provided', async () => {
Expand All @@ -812,6 +814,8 @@ suite('Unit Tests - Debug Launcher', () => {
expect(capturedConfigs).to.have.length(1);
// Should use the default 'python' from interpreterService mock
expect(capturedConfigs[0].python).to.equal('python');
expect(capturedConfigs[0].env?.PROJECT_ROOT_PATH).to.be.undefined;
expect(capturedConfigs[0].env?.PYTHONPATH).to.include(path.join(EXTENSION_ROOT_DIR, 'python_files'));
});

test('should add unique session marker to launch config', async () => {
Expand Down