Merge pull request #693 from ae-utbm/faster-tests

faster tests
This commit is contained in:
thomas girod 2024-07-05 10:27:30 +02:00 committed by GitHub
commit 79a6d9e771
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 96 additions and 59 deletions

View File

@ -21,6 +21,10 @@ jobs:
tests:
name: Run tests and generate coverage report
runs-on: ubuntu-latest
strategy:
fail-fast: false # don't interrupt the other test processes
matrix:
pytest-mark: [slow, not slow]
steps:
- name: Check out repository
uses: actions/checkout@v4
@ -28,7 +32,7 @@ jobs:
- uses: ./.github/actions/setup_xapian
- uses: ./.github/actions/compile_messages
- name: Run tests
run: poetry run coverage run -m pytest
run: poetry run coverage run -m pytest -m "${{ matrix.pytest-mark }}"
- name: Generate coverage report
run: |
poetry run coverage report

View File

@ -6,7 +6,7 @@ from django.utils.translation import activate
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command("setup")
call_command("populate")
@pytest.fixture(scope="session", autouse=True)

View File

@ -1098,8 +1098,10 @@ Welcome to the wiki page!
n = News(
title="Repas barman",
summary="Enjoy la fin du semestre!",
content="Viens donc t'enjailler avec les autres barmans aux "
"frais du BdF! \o/",
content=(
"Viens donc t'enjailler avec les autres barmans aux "
"frais du BdF! \\o/"
),
type="EVENT",
club=bar_club,
author=subscriber,

View File

@ -19,7 +19,8 @@ import base64
import os
import random
import string
from datetime import date, datetime, timedelta, timezone
from datetime import date, datetime, timedelta
from datetime import timezone as tz
from typing import Tuple
from dict2xml import dict2xml
@ -549,7 +550,7 @@ class Counter(models.Model):
if since is None:
since = get_start_of_semester()
if isinstance(since, date):
since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc)
since = datetime(since.year, since.month, since.day, tzinfo=tz.utc)
return (
self.sellings.filter(date__gte=since)
.annotate(
@ -583,7 +584,7 @@ class Counter(models.Model):
if since is None:
since = get_start_of_semester()
if isinstance(since, date):
since = datetime(since.year, since.month, since.day, tzinfo=timezone.utc)
since = datetime(since.year, since.month, since.day, tzinfo=tz.utc)
total = self.sellings.filter(date__gte=since).aggregate(
total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField())
)["total"]

View File

@ -207,6 +207,29 @@ Pour lancer les tests il suffit d'utiliser la commande intégrée à django.
# Lancer une méthode en particulier de cette même classe
pytest core.tests.UserRegistrationTest.test_register_user_form_ok
.. note::
Certains tests sont un peu longs à tourner.
Pour ne faire tourner que les tests les plus rapides,
vous pouvez exécutez pytest ainsi :
.. code-block:: bash
pytest -m "not slow"
# vous pouvez toujours faire comme au-dessus
pytest core -m "not slow"
A l'inverse, vous pouvez ne faire tourner que les tests
lents en remplaçant `-m "not slow"` par `-m slow`.
De cette manière, votre processus de développement
devrait être un peu plus fluide.
Cependant, n'oubliez pas de bien faire tourner
tous les tests avant de push un commit.
Vérifier les dépendances Javascript
-----------------------------------

View File

@ -220,10 +220,11 @@ class EbouticTest(TestCase):
self.client.get(reverse("eboutic:command"))
response = self.client.get(et_answer_url)
assert response.status_code == 500
assert (
"Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'"
in response.content.decode("utf-8"),
msg = (
"Basket processing failed with error: "
"SuspiciousOperation('Basket total and amount do not match'"
)
assert msg in response.content.decode("utf-8")
def test_buy_simple_product_with_credit_card(self):
self.client.force_login(self.subscriber)

View File

@ -128,7 +128,7 @@ class Command(BaseCommand):
self.clubs.append(Club(unix_name=f"galaxy-club-{i}", name=f"club-{i}"))
# We don't need to create corresponding groups here, as the Galaxy doesn't care about them
Club.objects.bulk_create(self.clubs)
self.clubs = Club.objects.filter(unix_name__startswith="galaxy-").all()
self.clubs = list(Club.objects.filter(unix_name__startswith="galaxy-").all())
def make_users(self):
"""
@ -147,20 +147,20 @@ class Command(BaseCommand):
self.logger.info(f"Creating {u}")
self.users.append(u)
User.objects.bulk_create(self.users)
self.users = User.objects.filter(username__startswith="galaxy-").all()
self.users = list(User.objects.filter(username__startswith="galaxy-").all())
# now that users are created, create their subscription
subs = []
for i in range(self.NB_USERS):
u = self.users[i]
self.logger.info(f"Registering {u}")
end = Subscription.compute_end(duration=2)
for i, user in enumerate(self.users):
self.logger.info(f"Registering {user}")
subs.append(
Subscription(
member=u,
member=user,
subscription_start=Subscription.compute_start(
self.now - timedelta(days=self.NB_USERS - i)
),
subscription_end=Subscription.compute_end(duration=2),
subscription_end=end,
)
)
Subscription.objects.bulk_create(subs)
@ -175,20 +175,22 @@ class Command(BaseCommand):
Then it will take 14 other citizen among the previous 200
(godfathers are usually older), and apply another
heuristic to determine whether they should have a family link
It will result in approximately 40% of users having at least one godchild
and 70% having at least one godfather.
"""
if self.users is None:
raise RuntimeError(
"The `make_users()` method must be called before `make_families()`"
)
godfathers = []
for i in range(200, self.NB_USERS):
godfathers = []
for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196)
if (i / 10) % 10 == (i + j) % 10:
if (i // 10) % 10 == (i + j) % 10:
u1 = self.users[i]
u2 = self.users[j]
godfathers.append(User.godfathers.through(from_user=u1, to_user=u2))
self.logger.info(f"Making {u2} the godfather of {u1}")
godfathers.append(u2)
u1.godfathers.set(godfathers)
User.godfathers.through.objects.bulk_create(godfathers)
def make_club_memberships(self):
"""
@ -295,7 +297,7 @@ class Command(BaseCommand):
self.picts[i].compressed.name = self.picts[i].name
self.picts[i].thumbnail.name = self.picts[i].name
Picture.objects.bulk_create(self.picts)
self.picts = Picture.objects.filter(name__startswith="galaxy-").all()
self.picts = list(Picture.objects.filter(name__startswith="galaxy-").all())
def make_pictures_memberships(self):
"""
@ -377,32 +379,24 @@ class Command(BaseCommand):
u1 = self.users[uid]
u2 = self.users[uid - 100]
u3 = self.users[uid + 100]
u1.godfathers.add(u2)
u1.godchildren.add(u3)
User.godfathers.through.objects.bulk_create(
[
User.godfathers.through(from_user=u1, to_user=u2),
User.godfathers.through(from_user=u3, to_user=u2),
],
ignore_conflicts=True, # in case a relationship has already been created
)
self.logger.info(f"{u1} will be important and close to {u2} and {u3}")
pictures_tags = []
for p in range( # Mix them with other citizen for more chaos
uid - 400, uid - 200
):
# users may already be on the pictures
if not self.picts[p].people.filter(user=u1).exists():
pictures_tags.append(
PeoplePictureRelation(user=u1, picture=self.picts[p])
)
if not self.picts[p].people.filter(user=u2).exists():
pictures_tags.append(
PeoplePictureRelation(user=u2, picture=self.picts[p])
)
if not self.picts[p + self.NB_USERS].people.filter(user=u1).exists():
pictures_tags.append(
PeoplePictureRelation(
user=u1, picture=self.picts[p + self.NB_USERS]
)
)
if not self.picts[p + self.NB_USERS].people.filter(user=u2).exists():
pictures_tags.append(
PeoplePictureRelation(
user=u2, picture=self.picts[p + self.NB_USERS]
)
)
PeoplePictureRelation.objects.bulk_create(pictures_tags)
for p in range(uid - 400, uid - 200):
# Mix them with other citizen for more chaos
pictures_tags += [
PeoplePictureRelation(user=u1, picture=self.picts[p]),
PeoplePictureRelation(user=u2, picture=self.picts[p]),
PeoplePictureRelation(user=u1, picture=self.picts[p + self.NB_USERS]),
PeoplePictureRelation(user=u2, picture=self.picts[p + self.NB_USERS]),
]
# users may already be on the pictures.
# In this case the conflict will just be ignored
# and nothing will happen for this entry
PeoplePictureRelation.objects.bulk_create(pictures_tags, ignore_conflicts=True)

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@
import json
from pathlib import Path
import pytest
from django.core.management import call_command
from django.test import TestCase
from django.urls import reverse
@ -147,6 +148,7 @@ class GalaxyTestModel(TestCase):
galaxy.rule(0) # We want everybody here
@pytest.mark.slow
class GalaxyTestView(TestCase):
@classmethod
def setUpTestData(cls):
@ -196,6 +198,4 @@ class GalaxyTestView(TestCase):
# Dump computed state, either for easier debugging, or to copy as new reference if changes are legit
(galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state))
assert (
state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()),
)
assert state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text())

10
poetry.lock generated
View File

@ -573,17 +573,17 @@ test = ["testfixtures"]
[[package]]
name = "djangorestframework"
version = "3.15.1"
version = "3.15.2"
description = "Web APIs for Django, made easy."
optional = false
python-versions = ">=3.6"
python-versions = ">=3.8"
files = [
{file = "djangorestframework-3.15.1-py3-none-any.whl", hash = "sha256:3ccc0475bce968608cf30d07fb17d8e52d1d7fc8bfe779c905463200750cbca6"},
{file = "djangorestframework-3.15.1.tar.gz", hash = "sha256:f88fad74183dfc7144b2756d0d2ac716ea5b4c7c9840995ac3bfd8ec034333c1"},
{file = "djangorestframework-3.15.2-py3-none-any.whl", hash = "sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20"},
{file = "djangorestframework-3.15.2.tar.gz", hash = "sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad"},
]
[package.dependencies]
django = ">=3.0"
django = ">=4.2"
[[package]]
name = "docutils"

View File

@ -76,6 +76,7 @@ select = ["I", "F401"]
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "sith.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"]
markers = ["slow"]
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@ -72,7 +72,7 @@ class MergeUserTest(TestCase):
assert "B'ian" == self.to_keep.nick_name
assert "Jerusalem" == self.to_keep.address
assert "Rome" == self.to_keep.parent_address
assert (3, self.to_keep.groups.count())
assert self.to_keep.groups.count() == 3
groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
assert groups == expected

View File

@ -696,6 +696,17 @@ if DEBUG:
if TESTING:
CAPTCHA_TEST_MODE = True
PASSWORD_HASHERS = [ # not secure, but faster password hasher
"django.contrib.auth.hashers.MD5PasswordHasher",
]
STORAGES = { # store files in memory rather than using the hard drive
"default": {
"BACKEND": "django.core.files.storage.InMemoryStorage",
},
"staticfiles": {
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
},
}
if SENTRY_DSN:
# Connection to sentry