Skip to content

Commit

Permalink
Implement parallel tests
Browse files Browse the repository at this point in the history
  • Loading branch information
evroon committed May 2, 2023
1 parent 352f461 commit 43b8a58
Show file tree
Hide file tree
Showing 16 changed files with 235 additions and 98 deletions.
22 changes: 9 additions & 13 deletions backend/bracket/routes/matches.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
from bracket.models.db.match import Match, MatchBody, MatchCreateBody, MatchFilter, MatchToInsert
from bracket.models.db.user import UserPublic
from bracket.routes.auth import user_authenticated_for_tournament
from bracket.routes.models import SuccessResponse, UpcomingMatchesResponse
from bracket.routes.models import SingleMatchResponse, SuccessResponse, UpcomingMatchesResponse
from bracket.routes.util import match_dependency
from bracket.schema import matches
from bracket.sql.matches import sql_create_match, sql_delete_match
from bracket.utils.types import assert_some

router = APIRouter()

Expand Down Expand Up @@ -43,28 +45,22 @@ async def delete_match(
_: UserPublic = Depends(user_authenticated_for_tournament),
match: Match = Depends(match_dependency),
) -> SuccessResponse:
await database.execute(
query=matches.delete().where(
matches.c.id == match.id and matches.c.tournament_id == tournament_id
),
)
await sql_delete_match(assert_some(match.id))
await recalculate_elo_for_tournament_id(tournament_id)
return SuccessResponse()


@router.post("/tournaments/{tournament_id}/matches", response_model=SuccessResponse)
@router.post("/tournaments/{tournament_id}/matches", response_model=SingleMatchResponse)
async def create_match(
tournament_id: int,
match_body: MatchCreateBody,
_: UserPublic = Depends(user_authenticated_for_tournament),
) -> SuccessResponse:
) -> SingleMatchResponse:
await database.execute(
query=matches.insert(),
values=MatchToInsert(
created=datetime_utc.now(),
**match_body.dict(),
).dict(),
values=MatchToInsert(created=datetime_utc.now(), **match_body.dict()).dict(),
)
return SuccessResponse()
return SingleMatchResponse(data=await sql_create_match(match_body))


@router.patch("/tournaments/{tournament_id}/matches/{match_id}", response_model=SuccessResponse)
Expand Down
6 changes: 5 additions & 1 deletion backend/bracket/routes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pydantic.generics import GenericModel

from bracket.models.db.club import Club
from bracket.models.db.match import SuggestedMatch
from bracket.models.db.match import Match, SuggestedMatch
from bracket.models.db.player import Player
from bracket.models.db.round import Round, StageWithRounds
from bracket.models.db.team import FullTeamWithPlayers, Team
Expand Down Expand Up @@ -59,6 +59,10 @@ class UpcomingMatchesResponse(DataResponse[list[SuggestedMatch]]):
pass


class SingleMatchResponse(DataResponse[Match]):
pass


class TeamsResponse(DataResponse[list[Team]]):
pass

Expand Down
25 changes: 25 additions & 0 deletions backend/bracket/sql/matches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from bracket.database import database
from bracket.models.db.match import Match, MatchCreateBody


async def sql_delete_match(match_id: int) -> None:
query = '''
DELETE FROM matches
WHERE matches.id = :match_id
'''
await database.execute(query=query, values={'match_id': match_id})


async def sql_create_match(match: MatchCreateBody) -> Match:
async with database.transaction():
query = '''
INSERT INTO matches (round_id, team1_id, team2_id, team1_score, team2_score, label, created)
VALUES (:round_id, :team1_id, :team2_id, 0, 0, :label, NOW())
RETURNING *
'''
result = await database.fetch_one(query=query, values=match.dict())

if result is None:
raise ValueError('Could not create stage')

return Match.parse_obj(result._mapping)
51 changes: 27 additions & 24 deletions backend/bracket/utils/dummy_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@

DUMMY_MOCK_TIME = datetime_utc(2022, 1, 11, 4, 32, 11, tzinfo=ZoneInfo('UTC'))

# We don't know any db IDs here, so we use a placeholder for foreign keys.
DB_PLACEHOLDER_ID = -42

DUMMY_CLUB = Club(
name='Some Cool Club',
created=DUMMY_MOCK_TIME,
)

DUMMY_TOURNAMENT = Tournament(
club_id=1,
club_id=DB_PLACEHOLDER_ID,
name='Some Cool Tournament',
created=DUMMY_MOCK_TIME,
dashboard_public=True,
Expand All @@ -30,28 +33,28 @@
)

DUMMY_STAGE1 = Stage(
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
created=DUMMY_MOCK_TIME,
is_active=False,
type=StageType.ROUND_ROBIN,
)

DUMMY_STAGE2 = Stage(
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
created=DUMMY_MOCK_TIME,
is_active=True,
type=StageType.SWISS,
)

DUMMY_ROUND1 = Round(
stage_id=1,
stage_id=DB_PLACEHOLDER_ID,
created=DUMMY_MOCK_TIME,
is_draft=False,
name='Round 1',
)

DUMMY_ROUND2 = Round(
stage_id=1,
stage_id=DB_PLACEHOLDER_ID,
created=DUMMY_MOCK_TIME,
is_active=True,
is_draft=False,
Expand All @@ -67,8 +70,8 @@

DUMMY_MATCH1 = Match(
created=DUMMY_MOCK_TIME,
round_id=1,
team1_id=1,
round_id=DB_PLACEHOLDER_ID,
team1_id=DB_PLACEHOLDER_ID,
team2_id=2,
team1_score=11,
team2_score=22,
Expand All @@ -77,7 +80,7 @@

DUMMY_MATCH2 = Match(
created=DUMMY_MOCK_TIME,
round_id=1,
round_id=DB_PLACEHOLDER_ID,
team1_id=3,
team2_id=4,
team1_score=9,
Expand All @@ -88,7 +91,7 @@
DUMMY_MATCH3 = Match(
created=DUMMY_MOCK_TIME,
round_id=2,
team1_id=1,
team1_id=DB_PLACEHOLDER_ID,
team2_id=4,
team1_score=23,
team2_score=26,
Expand All @@ -115,28 +118,28 @@
DUMMY_TEAM1 = Team(
created=DUMMY_MOCK_TIME,
name='Team 1',
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
active=True,
)

DUMMY_TEAM2 = Team(
created=DUMMY_MOCK_TIME,
name='Team 2',
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
active=True,
)

DUMMY_TEAM3 = Team(
created=DUMMY_MOCK_TIME,
name='Team 3',
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
active=True,
)

DUMMY_TEAM4 = Team(
created=DUMMY_MOCK_TIME,
name='Team 4',
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
active=True,
)

Expand All @@ -145,68 +148,68 @@
name='Luke',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER2 = Player(
name='Anakin',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER3 = Player(
name='Leia',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER4 = Player(
name='Yoda',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER5 = Player(
name='Boba',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER6 = Player(
name='General',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER7 = Player(
name='Han',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER8 = Player(
name='Emperor',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_PLAYER9 = Player(
name='R2D2',
active=True,
created=DUMMY_MOCK_TIME,
tournament_id=1,
tournament_id=DB_PLACEHOLDER_ID,
)

DUMMY_USER_X_CLUB = UserXClub(
user_id=1,
club_id=1,
user_id=DB_PLACEHOLDER_ID,
club_id=DB_PLACEHOLDER_ID,
)


Expand Down
1 change: 1 addition & 0 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ disable = [
'protected-access',
'logging-fstring-interpolation',
'too-many-arguments',
'unspecified-encoding',
]

[tool.bandit]
Expand Down
1 change: 1 addition & 0 deletions backend/tests/integration_tests/api/auth_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ async def test_auth_on_protected_endpoint(startup_and_shutdown_uvicorn_server: N
headers = {'Authorization': f'Bearer {get_mock_token()}'}

async with inserted_user(MOCK_USER) as user_inserted:
print(user_inserted.dict())
response = JsonDict(
await send_request(HTTPMethod.GET, f'users/{user_inserted.id}', {}, None, headers)
)
Expand Down
44 changes: 40 additions & 4 deletions backend/tests/integration_tests/api/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# pylint: disable=redefined-outer-name
import asyncio
import os
from asyncio import AbstractEventLoop
from time import sleep
from typing import AsyncIterator

import pytest
Expand Down Expand Up @@ -37,11 +39,45 @@ def event_loop() -> AsyncIterator[AbstractEventLoop]: # type: ignore[misc]
loop.close()


@pytest.fixture(scope="session", autouse=True)
async def reinit_database(event_loop: AbstractEventLoop) -> AsyncIterator[Database]:
@pytest.fixture(scope='session', autouse=True)
async def reinit_database(event_loop: AbstractEventLoop, worker_id: str) -> AsyncIterator[Database]:
"""
Creates the test database on the first test run in the session.
When running in parallel, the first test runner (gw0) creates a "lock" file and initializes the
database. The other runners poll this file and wait until it has been removed by gw0.
When running tests sequentially, the master worker just creates the test database and that's it.
"""
await database.connect()
metadata.drop_all(engine)
metadata.create_all(engine)

if worker_id == 'master':
metadata.drop_all(engine)
metadata.create_all(engine)

try:
yield database
finally:
await database.disconnect()

return

lock_path = '/tmp/tm_test_lock'

if worker_id == 'gw0':
try:
with open(lock_path, mode='w') as file:
file.write('')

metadata.drop_all(engine)
metadata.create_all(engine)
finally:
os.remove(lock_path)
else:
for _ in range(50):
sleep(0.1)
if not os.path.exists(lock_path):
break

try:
yield database
finally:
Expand Down
Loading

0 comments on commit 43b8a58

Please sign in to comment.