Skip to content

Commit

Permalink
Fix bug with symlink for pytest execution (#22952)
Browse files Browse the repository at this point in the history
Fixes #22938
  • Loading branch information
eleanorjboyd authored Feb 21, 2024
1 parent bae7d40 commit 178a0b2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 14 deletions.
12 changes: 12 additions & 0 deletions pythonFiles/tests/pytestadapter/expected_execution_test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,3 +684,15 @@
"subtest": None,
},
}

# Constant for the symlink execution test where TEST_DATA_PATH / "root" the target and TEST_DATA_PATH / "symlink_folder" the symlink
test_a_symlink_path = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_a.py"
symlink_run_expected_execution_output = {
get_absolute_test_id("test_a.py::test_a_function", test_a_symlink_path): {
"test": get_absolute_test_id("test_a.py::test_a_function", test_a_symlink_path),
"outcome": "success",
"message": None,
"traceback": None,
"subtest": None,
}
}
56 changes: 55 additions & 1 deletion pythonFiles/tests/pytestadapter/test_execution.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json
import os
import shutil
from typing import Any, Dict, List
Expand All @@ -8,7 +9,13 @@

from tests.pytestadapter import expected_execution_test_output

from .helpers import TEST_DATA_PATH, runner, runner_with_cwd
from .helpers import (
TEST_DATA_PATH,
create_symlink,
get_absolute_test_id,
runner,
runner_with_cwd,
)


def test_config_file():
Expand Down Expand Up @@ -276,3 +283,50 @@ def test_pytest_execution(test_ids, expected_const):
if actual_result_dict[key]["traceback"] is not None:
actual_result_dict[key]["traceback"] = "TRACEBACK"
assert actual_result_dict == expected_const


def test_symlink_run():
"""
Test to test pytest discovery with the command line arg --rootdir specified as a symlink path.
Discovery should succeed and testids should be relative to the symlinked root directory.
"""
with create_symlink(TEST_DATA_PATH, "root", "symlink_folder") as (
source,
destination,
):
assert destination.is_symlink()
test_a_path = TEST_DATA_PATH / "symlink_folder" / "tests" / "test_a.py"
test_a_id = get_absolute_test_id(
"tests/test_a.py::test_a_function",
test_a_path,
)

# Run pytest with the cwd being the resolved symlink path (as it will be when we run the subprocess from node).
actual = runner_with_cwd(
[f"--rootdir={os.fspath(destination)}", test_a_id], source
)

expected_const = (
expected_execution_test_output.symlink_run_expected_execution_output
)
assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
assert actual_list.pop(-1).get("eot")
actual_item = actual_list.pop(0)
try:
# Check if all requirements
assert all(
item in actual_item.keys() for item in ("status", "cwd", "result")
), "Required keys are missing"
assert actual_item.get("status") == "success", "Status is not 'success'"
assert actual_item.get("cwd") == os.fspath(
destination
), f"CWD does not match: {os.fspath(destination)}"
actual_result_dict = dict()
actual_result_dict.update(actual_item["result"])
assert actual_result_dict == expected_const
except AssertionError as e:
# Print the actual_item in JSON format if an assertion fails
print(json.dumps(actual_item, indent=4))
pytest.fail(str(e))
32 changes: 19 additions & 13 deletions pythonFiles/vscode_pytest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ def pytest_report_teststatus(report, config):
config -- configuration object.
"""
cwd = pathlib.Path.cwd()
if SYMLINK_PATH:
cwd = SYMLINK_PATH

if report.when == "call":
traceback = None
Expand Down Expand Up @@ -348,10 +350,7 @@ def pytest_sessionfinish(session, exitstatus):
cwd = pathlib.Path.cwd()
if SYMLINK_PATH:
print("Plugin warning[vscode-pytest]: SYMLINK set, adjusting cwd.")
# Get relative between the cwd (resolved path) and the node path.
rel_path = os.path.relpath(cwd, pathlib.Path.cwd())
# Calculate the new node path by making it relative to the symlink path.
cwd = pathlib.Path(os.path.join(SYMLINK_PATH, rel_path))
cwd = pathlib.Path(SYMLINK_PATH)

if IS_DISCOVERY:
if not (exitstatus == 0 or exitstatus == 1 or exitstatus == 5):
Expand Down Expand Up @@ -681,9 +680,9 @@ def get_node_path(node: Any) -> pathlib.Path:
A function that returns the path of a node given the switch to pathlib.Path.
It also evaluates if the node is a symlink and returns the equivalent path.
"""
path = getattr(node, "path", None) or pathlib.Path(node.fspath)
node_path = getattr(node, "path", None) or pathlib.Path(node.fspath)

if not path:
if not node_path:
raise VSCodePytestError(
f"Unable to find path for node: {node}, node.path: {node.path}, node.fspath: {node.fspath}"
)
Expand All @@ -692,17 +691,24 @@ def get_node_path(node: Any) -> pathlib.Path:
if SYMLINK_PATH and not isinstance(node, pytest.Session):
# Get relative between the cwd (resolved path) and the node path.
try:
rel_path = path.relative_to(pathlib.Path.cwd())

# Calculate the new node path by making it relative to the symlink path.
sym_path = pathlib.Path(os.path.join(SYMLINK_PATH, rel_path))
return sym_path
# check to see if the node path contains the symlink root already
common_path = os.path.commonpath([SYMLINK_PATH, node_path])
if common_path == os.fsdecode(SYMLINK_PATH):
# node path is already relative to the SYMLINK_PATH root therefore return
return node_path
else:
# if the node path is not a symlink, then we need to calculate the equivalent symlink path
# get the relative path between the cwd and the node path (as the node path is not a symlink)
rel_path = node_path.relative_to(pathlib.Path.cwd())
# combine the difference between the cwd and the node path with the symlink path
sym_path = pathlib.Path(os.path.join(SYMLINK_PATH, rel_path))
return sym_path
except Exception as e:
raise VSCodePytestError(
f"Error occurred while calculating symlink equivalent from node path: {e}"
"\n SYMLINK_PATH: {SYMLINK_PATH}, \n node path: {path}, \n cwd: {{pathlib.Path.cwd()}}"
f"\n SYMLINK_PATH: {SYMLINK_PATH}, \n node path: {node_path}, \n cwd: {pathlib.Path.cwd()}"
)
return path
return node_path


__socket = None
Expand Down

0 comments on commit 178a0b2

Please sign in to comment.