Skip to content

Commit

Permalink
Django Test Compatibility (#23935)
Browse files Browse the repository at this point in the history
implements and thus closes
#22206
resolves #73!
  • Loading branch information
eleanorjboyd authored Aug 14, 2024
1 parent b872cb4 commit 59a8d03
Show file tree
Hide file tree
Showing 26 changed files with 702 additions and 26 deletions.
3 changes: 3 additions & 0 deletions build/test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ freezegun

# testing custom pytest plugin require the use of named pipes
namedpipe; platform_system == "Windows"

# typing for Django files
django-stubs
35 changes: 30 additions & 5 deletions python_files/tests/pytestadapter/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,17 +193,35 @@ def _run_test_code(proc_args: List[str], proc_env, proc_cwd: str, completed: thr


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


def runner_with_cwd(args: List[str], path: pathlib.Path) -> Optional[List[Dict[str, Any]]]:
"""Run the pytest discovery and return the JSON data from the server."""
process_args: List[str] = [sys.executable, "-m", "pytest", "-p", "vscode_pytest", "-s", *args]
"""Run a subprocess and a named-pipe to listen for messages at the same time with threading."""
return runner_with_cwd_env(args, path, {})


def runner_with_cwd_env(
args: List[str], path: pathlib.Path, env_add: Dict[str, str]
) -> Optional[List[Dict[str, Any]]]:
"""
Run a subprocess and a named-pipe to listen for messages at the same time with threading.
Includes environment variables to add to the test environment.
"""
process_args: List[str]
pipe_name: str
if "MANAGE_PY_PATH" in env_add:
# If we are running Django, generate a unittest-specific pipe name.
process_args = [sys.executable, *args]
pipe_name = generate_random_pipe_name("unittest-discovery-test")
else:
process_args = [sys.executable, "-m", "pytest", "-p", "vscode_pytest", "-s", *args]
pipe_name = generate_random_pipe_name("pytest-discovery-test")

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

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

completed = threading.Event()

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

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

t2 = threading.Thread(
t2: threading.Thread = threading.Thread(
target=_run_test_code,
args=(process_args, env, path, completed),
)

t2.start()

t1.join()
Expand Down
Binary file not shown.
23 changes: 23 additions & 0 deletions python_files/tests/unittestadapter/.data/simple_django/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)


if __name__ == '__main__':
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')

application = get_asgi_application()
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""
Django settings for mysite project.
Generated by 'django-admin startproject' using Django 3.2.22.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
"polls.apps.PollsConfig",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'mysite.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}




# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
path("polls/", include("polls.urls")),
path("admin/", admin.site.urls),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import os

from django.core.wsgi import get_wsgi_application

application = get_wsgi_application()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from django.apps import AppConfig
from django.utils.functional import cached_property


class PollsConfig(AppConfig):
@cached_property
def default_auto_field(self):
return "django.db.models.BigAutoField"

name = "polls"
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 5.0.8 on 2024-08-09 20:04

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Question",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("question_text", models.CharField(max_length=200, default="")),
("pub_date", models.DateTimeField(verbose_name="date published", auto_now_add=True)),
],
),
migrations.CreateModel(
name="Choice",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("choice_text", models.CharField(max_length=200)),
("votes", models.IntegerField(default=0)),
(
"question",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="polls.question"
),
),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from django.db import models
from django.utils import timezone
import datetime


class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField("date published")
def __str__(self):
return self.question_text
def was_published_recently(self):
if self.pub_date > timezone.now():
return False
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)


class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField()
def __str__(self):
return self.choice_text
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from django.utils import timezone
from django.test import TestCase
from .models import Question
import datetime

class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question: Question = Question.objects.create(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)

def test_was_published_recently_with_future_question_2(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question.objects.create(pub_date=time)
self.assertIs(future_question.was_published_recently(), True)

def test_question_creation_and_retrieval(self):
"""
Test that a Question can be created and retrieved from the database.
"""
time = timezone.now()
question = Question.objects.create(pub_date=time, question_text="What's new?")
retrieved_question = Question.objects.get(question_text=question.question_text)
self.assertEqual(question, retrieved_question)
self.assertEqual(retrieved_question.question_text, "What's new?")
self.assertEqual(retrieved_question.pub_date, time)

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from django.urls import path

from . import views

urlpatterns = [
# ex: /polls/
path("", views.index, name="index"),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
from django.http import HttpResponse
from .models import Question # noqa: F401

def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
Loading

0 comments on commit 59a8d03

Please sign in to comment.