Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django Test Compatibility #23935

Merged
merged 17 commits into from
Aug 14, 2024
Merged
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
Loading