Skip to content

Commit 59a8d03

Browse files
authored
Django Test Compatibility (#23935)
implements and thus closes #22206 resolves #73!
1 parent b872cb4 commit 59a8d03

26 files changed

Lines changed: 702 additions & 26 deletions

build/test-requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ freezegun
2424

2525
# testing custom pytest plugin require the use of named pipes
2626
namedpipe; platform_system == "Windows"
27+
28+
# typing for Django files
29+
django-stubs

python_files/tests/pytestadapter/helpers.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,35 @@ def _run_test_code(proc_args: List[str], proc_env, proc_cwd: str, completed: thr
193193

194194

195195
def runner(args: List[str]) -> Optional[List[Dict[str, Any]]]:
196-
"""Run the pytest discovery and return the JSON data from the server."""
196+
"""Run a subprocess and a named-pipe to listen for messages at the same time with threading."""
197197
print("\n Running python test subprocess with cwd set to: ", TEST_DATA_PATH)
198198
return runner_with_cwd(args, TEST_DATA_PATH)
199199

200200

201201
def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[str, Any]]]:
202-
"""Run the pytest discovery and return the JSON data from the server."""
203-
process_args: List[str] = [sys.executable, "-m", "pytest", "-p", "vscode_pytest", "-s", *args]
202+
"""Run a subprocess and a named-pipe to listen for messages at the same time with threading."""
203+
return runner_with_cwd_env(args, path, {})
204+
205+
206+
def runner_with_cwd_env(
207+
args: List[str], path: pathlib.Path, env_add: Dict[str, str]
208+
) -> Optional[List[Dict[str, Any]]]:
209+
"""
210+
Run a subprocess and a named-pipe to listen for messages at the same time with threading.
211+
212+
Includes environment variables to add to the test environment.
213+
"""
214+
process_args: List[str]
215+
pipe_name: str
216+
if "MANAGE_PY_PATH" in env_add:
217+
# If we are running Django, generate a unittest-specific pipe name.
218+
process_args = [sys.executable, *args]
219+
pipe_name = generate_random_pipe_name("unittest-discovery-test")
220+
else:
221+
process_args = [sys.executable, "-m", "pytest", "-p", "vscode_pytest", "-s", *args]
222+
pipe_name = generate_random_pipe_name("pytest-discovery-test")
204223

205224
# Generate pipe name, pipe name specific per OS type.
206-
pipe_name = generate_random_pipe_name("pytest-discovery-test")
207225

208226
# Windows design
209227
if sys.platform == "win32":
@@ -216,6 +234,9 @@ def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[s
216234
"PYTHONPATH": os.fspath(pathlib.Path(__file__).parent.parent.parent),
217235
}
218236
)
237+
# if additional environment variables are passed, add them to the environment
238+
if env_add:
239+
env.update(env_add)
219240

220241
completed = threading.Event()
221242

@@ -244,6 +265,9 @@ def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[s
244265
"PYTHONPATH": os.fspath(pathlib.Path(__file__).parent.parent.parent),
245266
}
246267
)
268+
# if additional environment variables are passed, add them to the environment
269+
if env_add:
270+
env.update(env_add)
247271
server = UnixPipeServer(pipe_name)
248272
server.start()
249273

@@ -255,10 +279,11 @@ def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[s
255279
)
256280
t1.start()
257281

258-
t2 = threading.Thread(
282+
t2: threading.Thread = threading.Thread(
259283
target=_run_test_code,
260284
args=(process_args, env, path, completed),
261285
)
286+
262287
t2.start()
263288

264289
t1.join()
Binary file not shown.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
"""Django's command-line utility for administrative tasks."""
4+
import os
5+
import sys
6+
7+
8+
def main():
9+
"""Run administrative tasks."""
10+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
11+
try:
12+
from django.core.management import execute_from_command_line
13+
except ImportError as exc:
14+
raise ImportError(
15+
"Couldn't import Django. Are you sure it's installed and "
16+
"available on your PYTHONPATH environment variable? Did you "
17+
"forget to activate a virtual environment?"
18+
) from exc
19+
execute_from_command_line(sys.argv)
20+
21+
22+
if __name__ == '__main__':
23+
main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import os
4+
5+
from django.core.asgi import get_asgi_application
6+
7+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
8+
9+
application = get_asgi_application()
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
"""
4+
Django settings for mysite project.
5+
6+
Generated by 'django-admin startproject' using Django 3.2.22.
7+
8+
For more information on this file, see
9+
https://docs.djangoproject.com/en/3.2/topics/settings/
10+
11+
For the full list of settings and their values, see
12+
https://docs.djangoproject.com/en/3.2/ref/settings/
13+
"""
14+
15+
from pathlib import Path
16+
17+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
18+
BASE_DIR = Path(__file__).resolve().parent.parent
19+
20+
21+
ALLOWED_HOSTS = []
22+
23+
24+
# Application definition
25+
26+
INSTALLED_APPS = [
27+
"polls.apps.PollsConfig",
28+
"django.contrib.admin",
29+
"django.contrib.auth",
30+
"django.contrib.contenttypes",
31+
"django.contrib.sessions",
32+
"django.contrib.messages",
33+
"django.contrib.staticfiles",
34+
]
35+
36+
MIDDLEWARE = [
37+
'django.middleware.security.SecurityMiddleware',
38+
'django.contrib.sessions.middleware.SessionMiddleware',
39+
'django.middleware.common.CommonMiddleware',
40+
'django.middleware.csrf.CsrfViewMiddleware',
41+
'django.contrib.auth.middleware.AuthenticationMiddleware',
42+
'django.contrib.messages.middleware.MessageMiddleware',
43+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
44+
]
45+
46+
ROOT_URLCONF = 'mysite.urls'
47+
48+
TEMPLATES = [
49+
{
50+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
51+
'DIRS': [],
52+
'APP_DIRS': True,
53+
'OPTIONS': {
54+
'context_processors': [
55+
'django.template.context_processors.debug',
56+
'django.template.context_processors.request',
57+
'django.contrib.auth.context_processors.auth',
58+
'django.contrib.messages.context_processors.messages',
59+
],
60+
},
61+
},
62+
]
63+
64+
WSGI_APPLICATION = 'mysite.wsgi.application'
65+
66+
67+
# Database
68+
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
69+
70+
DATABASES = {
71+
'default': {
72+
'ENGINE': 'django.db.backends.sqlite3',
73+
'NAME': BASE_DIR / 'db.sqlite3',
74+
}
75+
}
76+
77+
78+
79+
80+
# Internationalization
81+
# https://docs.djangoproject.com/en/3.2/topics/i18n/
82+
83+
LANGUAGE_CODE = 'en-us'
84+
85+
TIME_ZONE = 'UTC'
86+
87+
USE_I18N = True
88+
89+
USE_L10N = True
90+
91+
USE_TZ = True
92+
93+
94+
# Static files (CSS, JavaScript, Images)
95+
# https://docs.djangoproject.com/en/3.2/howto/static-files/
96+
97+
STATIC_URL = '/static/'
98+
99+
# Default primary key field type
100+
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
101+
102+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
from django.contrib import admin
4+
from django.urls import include, path
5+
6+
urlpatterns = [
7+
path("polls/", include("polls.urls")),
8+
path("admin/", admin.site.urls),
9+
]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
import os
4+
5+
from django.core.wsgi import get_wsgi_application
6+
7+
application = get_wsgi_application()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.

0 commit comments

Comments
 (0)