Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e9fb2bf

Browse filesBrowse files
Django Test Runs with Coverage (#24927)
#24199 Co-authored-by: Danila Grobov (s4642g) <danila.grobov@seb.se>
1 parent 70a36f5 commit e9fb2bf
Copy full SHA for e9fb2bf

File tree

Expand file treeCollapse file tree

4 files changed

+79
-24
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+79
-24
lines changed

‎build/test-requirements.txt

Copy file name to clipboardExpand all lines: build/test-requirements.txt
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ django-stubs
3232
coverage
3333
pytest-cov
3434
pytest-json
35+
pytest-timeout
3536

3637

3738
# for pytest-describe related tests

‎python_files/tests/pytestadapter/helpers.py

Copy file name to clipboardExpand all lines: python_files/tests/pytestadapter/helpers.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def runner_with_cwd_env(
244244
"""
245245
process_args: List[str]
246246
pipe_name: str
247-
if "MANAGE_PY_PATH" in env_add:
247+
if "MANAGE_PY_PATH" in env_add and "COVERAGE_ENABLED" not in env_add:
248248
# If we are running Django, generate a unittest-specific pipe name.
249249
process_args = [sys.executable, *args]
250250
pipe_name = generate_random_pipe_name("unittest-discovery-test")

‎python_files/tests/unittestadapter/test_coverage.py

Copy file name to clipboardExpand all lines: python_files/tests/unittestadapter/test_coverage.py
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import pathlib
99
import sys
1010

11+
import pytest
12+
1113
sys.path.append(os.fspath(pathlib.Path(__file__).parent))
1214

1315
python_files_path = pathlib.Path(__file__).parent.parent.parent
@@ -49,3 +51,41 @@ def test_basic_coverage():
4951
assert focal_function_coverage.get("lines_missed") is not None
5052
assert set(focal_function_coverage.get("lines_covered")) == {4, 5, 7, 9, 10, 11, 12, 13, 14}
5153
assert set(focal_function_coverage.get("lines_missed")) == {6}
54+
55+
56+
@pytest.mark.timeout(30)
57+
def test_basic_django_coverage():
58+
"""This test validates that the coverage is correctly calculated for a Django project."""
59+
data_path: pathlib.Path = TEST_DATA_PATH / "simple_django"
60+
manage_py_path: str = os.fsdecode(data_path / "manage.py")
61+
execution_script: pathlib.Path = python_files_path / "unittestadapter" / "execution.py"
62+
63+
test_ids = [
64+
"polls.tests.QuestionModelTests.test_was_published_recently_with_future_question",
65+
"polls.tests.QuestionModelTests.test_was_published_recently_with_future_question_2",
66+
"polls.tests.QuestionModelTests.test_question_creation_and_retrieval",
67+
]
68+
69+
script_str = os.fsdecode(execution_script)
70+
actual = helpers.runner_with_cwd_env(
71+
[script_str, "--udiscovery", "-p", "*test*.py", *test_ids],
72+
data_path,
73+
{
74+
"MANAGE_PY_PATH": manage_py_path,
75+
"_TEST_VAR_UNITTEST": "True",
76+
"COVERAGE_ENABLED": os.fspath(data_path),
77+
},
78+
)
79+
80+
assert actual
81+
coverage = actual[-1]
82+
assert coverage
83+
results = coverage["result"]
84+
assert results
85+
assert len(results) == 15
86+
polls_views_coverage = results.get(str(data_path / "polls" / "views.py"))
87+
assert polls_views_coverage
88+
assert polls_views_coverage.get("lines_covered") is not None
89+
assert polls_views_coverage.get("lines_missed") is not None
90+
assert set(polls_views_coverage.get("lines_covered")) == {3, 4, 6}
91+
assert set(polls_views_coverage.get("lines_missed")) == {7}

‎python_files/unittestadapter/django_handler.py

Copy file name to clipboardExpand all lines: python_files/unittestadapter/django_handler.py
+37-23Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4+
import importlib.util
45
import os
56
import pathlib
67
import subprocess
78
import sys
8-
from typing import List
9+
from contextlib import contextmanager, suppress
10+
from typing import TYPE_CHECKING, Generator, List
11+
12+
if TYPE_CHECKING:
13+
from importlib.machinery import ModuleSpec
14+
915

1016
script_dir = pathlib.Path(__file__).parent
1117
sys.path.append(os.fspath(script_dir))
@@ -16,6 +22,17 @@
1622
)
1723

1824

25+
@contextmanager
26+
def override_argv(argv: List[str]) -> Generator:
27+
"""Context manager to temporarily override sys.argv with the provided arguments."""
28+
original_argv = sys.argv
29+
sys.argv = argv
30+
try:
31+
yield
32+
finally:
33+
sys.argv = original_argv
34+
35+
1936
def django_discovery_runner(manage_py_path: str, args: List[str]) -> None:
2037
# Attempt a small amount of validation on the manage.py path.
2138
if not pathlib.Path(manage_py_path).exists():
@@ -72,31 +89,28 @@ def django_execution_runner(manage_py_path: str, test_ids: List[str], args: List
7289
else:
7390
env["PYTHONPATH"] = os.fspath(custom_test_runner_dir)
7491

75-
# Build command to run 'python manage.py test'.
76-
command: List[str] = [
77-
sys.executable,
92+
django_project_dir: pathlib.Path = pathlib.Path(manage_py_path).parent
93+
sys.path.insert(0, os.fspath(django_project_dir))
94+
print(f"Django project directory: {django_project_dir}")
95+
96+
manage_spec: ModuleSpec | None = importlib.util.spec_from_file_location(
97+
"manage", manage_py_path
98+
)
99+
if manage_spec is None or manage_spec.loader is None:
100+
raise VSCodeUnittestError("Error importing manage.py when running Django testing.")
101+
manage_module = importlib.util.module_from_spec(manage_spec)
102+
manage_spec.loader.exec_module(manage_module)
103+
104+
manage_argv: List[str] = [
78105
manage_py_path,
79106
"test",
80107
"--testrunner=django_test_runner.CustomExecutionTestRunner",
108+
*args,
109+
*test_ids,
81110
]
82-
# Add any additional arguments to the command provided by the user.
83-
command.extend(args)
84-
# Add the test_ids to the command.
85-
print("Test IDs: ", test_ids)
86-
print("args: ", args)
87-
command.extend(test_ids)
88-
print("Running Django run tests with command: ", command)
89-
subprocess_execution = subprocess.run(
90-
command,
91-
capture_output=True,
92-
text=True,
93-
env=env,
94-
)
95-
print(subprocess_execution.stderr, file=sys.stderr)
96-
print(subprocess_execution.stdout, file=sys.stdout)
97-
# Zero return code indicates success, 1 indicates test failures, so both are considered successful.
98-
if subprocess_execution.returncode not in (0, 1):
99-
error_msg = "Django test execution process exited with non-zero error code See stderr above for more details."
100-
print(error_msg, file=sys.stderr)
111+
print(f"Django manage.py arguments: {manage_argv}")
112+
113+
with override_argv(manage_argv), suppress(SystemExit):
114+
manage_module.main()
101115
except Exception as e:
102116
print(f"Error during Django test execution: {e}", file=sys.stderr)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.