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

View File

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

View File

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

View File

@ -19,7 +19,8 @@ import base64
import os import os
import random import random
import string 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 typing import Tuple
from dict2xml import dict2xml from dict2xml import dict2xml
@ -549,7 +550,7 @@ class Counter(models.Model):
if since is None: if since is None:
since = get_start_of_semester() since = get_start_of_semester()
if isinstance(since, date): 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 ( return (
self.sellings.filter(date__gte=since) self.sellings.filter(date__gte=since)
.annotate( .annotate(
@ -583,7 +584,7 @@ class Counter(models.Model):
if since is None: if since is None:
since = get_start_of_semester() since = get_start_of_semester()
if isinstance(since, date): 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 = self.sellings.filter(date__gte=since).aggregate(
total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField()) total=Sum(F("quantity") * F("unit_price"), output_field=CurrencyField())
)["total"] )["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 # Lancer une méthode en particulier de cette même classe
pytest core.tests.UserRegistrationTest.test_register_user_form_ok 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 Vérifier les dépendances Javascript
----------------------------------- -----------------------------------

View File

@ -220,10 +220,11 @@ class EbouticTest(TestCase):
self.client.get(reverse("eboutic:command")) self.client.get(reverse("eboutic:command"))
response = self.client.get(et_answer_url) response = self.client.get(et_answer_url)
assert response.status_code == 500 assert response.status_code == 500
assert ( msg = (
"Basket processing failed with error: SuspiciousOperation('Basket total and amount do not match'" "Basket processing failed with error: "
in response.content.decode("utf-8"), "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): def test_buy_simple_product_with_credit_card(self):
self.client.force_login(self.subscriber) 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}")) 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 # We don't need to create corresponding groups here, as the Galaxy doesn't care about them
Club.objects.bulk_create(self.clubs) 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): def make_users(self):
""" """
@ -147,20 +147,20 @@ class Command(BaseCommand):
self.logger.info(f"Creating {u}") self.logger.info(f"Creating {u}")
self.users.append(u) self.users.append(u)
User.objects.bulk_create(self.users) 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 # now that users are created, create their subscription
subs = [] subs = []
for i in range(self.NB_USERS): end = Subscription.compute_end(duration=2)
u = self.users[i] for i, user in enumerate(self.users):
self.logger.info(f"Registering {u}") self.logger.info(f"Registering {user}")
subs.append( subs.append(
Subscription( Subscription(
member=u, member=user,
subscription_start=Subscription.compute_start( subscription_start=Subscription.compute_start(
self.now - timedelta(days=self.NB_USERS - i) self.now - timedelta(days=self.NB_USERS - i)
), ),
subscription_end=Subscription.compute_end(duration=2), subscription_end=end,
) )
) )
Subscription.objects.bulk_create(subs) Subscription.objects.bulk_create(subs)
@ -175,20 +175,22 @@ class Command(BaseCommand):
Then it will take 14 other citizen among the previous 200 Then it will take 14 other citizen among the previous 200
(godfathers are usually older), and apply another (godfathers are usually older), and apply another
heuristic to determine whether they should have a family link 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: if self.users is None:
raise RuntimeError( raise RuntimeError(
"The `make_users()` method must be called before `make_families()`" "The `make_users()` method must be called before `make_families()`"
) )
godfathers = []
for i in range(200, self.NB_USERS): for i in range(200, self.NB_USERS):
godfathers = []
for j in range(i - 200, i, 14): # this will loop 14 times (14² = 196) 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] u1 = self.users[i]
u2 = self.users[j] 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}") self.logger.info(f"Making {u2} the godfather of {u1}")
godfathers.append(u2) User.godfathers.through.objects.bulk_create(godfathers)
u1.godfathers.set(godfathers)
def make_club_memberships(self): def make_club_memberships(self):
""" """
@ -295,7 +297,7 @@ class Command(BaseCommand):
self.picts[i].compressed.name = self.picts[i].name self.picts[i].compressed.name = self.picts[i].name
self.picts[i].thumbnail.name = self.picts[i].name self.picts[i].thumbnail.name = self.picts[i].name
Picture.objects.bulk_create(self.picts) 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): def make_pictures_memberships(self):
""" """
@ -377,32 +379,24 @@ class Command(BaseCommand):
u1 = self.users[uid] u1 = self.users[uid]
u2 = self.users[uid - 100] u2 = self.users[uid - 100]
u3 = self.users[uid + 100] u3 = self.users[uid + 100]
u1.godfathers.add(u2) User.godfathers.through.objects.bulk_create(
u1.godchildren.add(u3) [
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}") self.logger.info(f"{u1} will be important and close to {u2} and {u3}")
pictures_tags = [] pictures_tags = []
for p in range( # Mix them with other citizen for more chaos for p in range(uid - 400, uid - 200):
uid - 400, uid - 200 # Mix them with other citizen for more chaos
): pictures_tags += [
# users may already be on the pictures PeoplePictureRelation(user=u1, picture=self.picts[p]),
if not self.picts[p].people.filter(user=u1).exists(): PeoplePictureRelation(user=u2, picture=self.picts[p]),
pictures_tags.append( PeoplePictureRelation(user=u1, picture=self.picts[p + self.NB_USERS]),
PeoplePictureRelation(user=u1, picture=self.picts[p]) PeoplePictureRelation(user=u2, picture=self.picts[p + self.NB_USERS]),
) ]
if not self.picts[p].people.filter(user=u2).exists(): # users may already be on the pictures.
pictures_tags.append( # In this case the conflict will just be ignored
PeoplePictureRelation(user=u2, picture=self.picts[p]) # and nothing will happen for this entry
) PeoplePictureRelation.objects.bulk_create(pictures_tags, ignore_conflicts=True)
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)

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,7 @@
import json import json
from pathlib import Path from pathlib import Path
import pytest
from django.core.management import call_command from django.core.management import call_command
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
@ -147,6 +148,7 @@ class GalaxyTestModel(TestCase):
galaxy.rule(0) # We want everybody here galaxy.rule(0) # We want everybody here
@pytest.mark.slow
class GalaxyTestView(TestCase): class GalaxyTestView(TestCase):
@classmethod @classmethod
def setUpTestData(cls): 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 # 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)) (galaxy_dir / "test_galaxy_state.json").write_text(json.dumps(state))
assert ( assert state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text())
state == json.loads((galaxy_dir / "ref_galaxy_state.json").read_text()),
)

10
poetry.lock generated
View File

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

View File

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

View File

@ -72,7 +72,7 @@ class MergeUserTest(TestCase):
assert "B'ian" == self.to_keep.nick_name assert "B'ian" == self.to_keep.nick_name
assert "Jerusalem" == self.to_keep.address assert "Jerusalem" == self.to_keep.address
assert "Rome" == self.to_keep.parent_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) groups = sorted(self.to_keep.groups.all(), key=lambda i: i.id)
expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id) expected = sorted([subscribers, mde_admin, sas_admin], key=lambda i: i.id)
assert groups == expected assert groups == expected

View File

@ -696,6 +696,17 @@ if DEBUG:
if TESTING: if TESTING:
CAPTCHA_TEST_MODE = True 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: if SENTRY_DSN:
# Connection to sentry # Connection to sentry