Skip to content

Commit

Permalink
Update score and rank of all teams of the contest after rejudging.
Browse files Browse the repository at this point in the history
This makes sure the rank and first to solve of all teams are correctly set.
Fixes #2105.
  • Loading branch information
nickygerritsen committed Aug 28, 2023
1 parent ad1398d commit 3bb5530
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 13 deletions.
56 changes: 56 additions & 0 deletions webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php declare(strict_types=1);

namespace App\DataFixtures\Test;

use App\Entity\Contest;
use App\Entity\ContestProblem;
use App\Entity\Judging;
use App\Entity\Language;
use App\Entity\Submission;
use App\Entity\Team;
use App\Utils\Utils;
use Doctrine\Persistence\ObjectManager;

class RejudgingFirstToSolveFixture extends AbstractTestDataFixture
{
public function load(ObjectManager $manager): void
{
$team1 = $manager->getRepository(Team::class)->findOneBy(['name' => 'Example teamname']);
$team2 = (new Team())
->setName('Another team')
->setCategory($team1->getCategory());

$manager->persist($team2);

$submissionData = [
// team, submittime, result]
[$team1, '2021-01-01 12:34:56', 'correct'],
[$team2, '2021-01-01 12:33:56', 'wrong-answer'],
];

$contest = $manager->getRepository(Contest::class)->findOneBy(['shortname' => 'demo']);
$language = $manager->getRepository(Language::class)->find('cpp');
$problem = $contest->getProblems()->filter(fn(ContestProblem $problem) => $problem->getShortname() === 'A')->first();

foreach ($submissionData as $index => $submissionItem) {
$submission = (new Submission())
->setContest($contest)
->setTeam($submissionItem[0])
->setContestProblem($problem)
->setLanguage($language)
->setValid(true)
->setSubmittime(Utils::toEpochFloat($submissionItem[1]));
$judging = (new Judging())
->setContest($contest)
->setStarttime(Utils::toEpochFloat($submissionItem[1]))
->setEndtime(Utils::toEpochFloat($submissionItem[1]) + 5)
->setValid(true)
->setResult($submissionItem[2]);
$judging->setSubmission($submission);
$submission->addJudging($judging);
$manager->persist($submission);
$manager->persist($judging);
$manager->flush();
}
}
}
42 changes: 29 additions & 13 deletions webapp/src/Service/RejudgingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,13 +251,10 @@ public function finishRejudging(Rejudging $rejudging, string $action, ?callable
);

// Update caches.
$rowIndex = $submission['cid'] . '-' . $submission['teamid'] . '-' . $submission['probid'];
if (!isset($scoreboardRowsToUpdate[$rowIndex])) {
$scoreboardRowsToUpdate[$rowIndex] = [
'cid' => $submission['cid'],
'teamid' => $submission['teamid'],
'probid' => $submission['probid'],
];
$cid = $submission['cid'];
$probid = $submission['probid'];
if (!isset($scoreboardRowsToUpdate[$cid][$probid])) {
$scoreboardRowsToUpdate[$cid][$probid] = true;
}

// Update event log.
Expand Down Expand Up @@ -329,16 +326,35 @@ public function finishRejudging(Rejudging $rejudging, string $action, ?callable
}

// Now update the scoreboard
foreach ($scoreboardRowsToUpdate as $item) {
['cid' => $cid, 'teamid' => $teamid, 'probid' => $probid] = $item;
foreach ($scoreboardRowsToUpdate as $cid => $probids) {
$contest = $this->em->getRepository(Contest::class)->find($cid);
$team = $this->em->getRepository(Team::class)->find($teamid);
$problem = $this->em->getRepository(Problem::class)->find($probid);
$this->scoreboardService->calculateScoreRow($contest, $team, $problem);
$queryBuilder = $this->em->createQueryBuilder()
->from(Team::class, 't')
->select('t')
->orderBy('t.teamid');
if (!$contest->isOpenToAllTeams()) {
$queryBuilder
->leftJoin('t.contests', 'c')
->join('t.category', 'cat')
->leftJoin('cat.contests', 'cc')
->andWhere('c.cid = :cid OR cc.cid = :cid')
->setParameter('cid', $contest->getCid());
}
/** @var Team[] $teams */
$teams = $queryBuilder->getQuery()->getResult();
foreach ($teams as $team) {
foreach ($probids as $probid) {
$problem = $this->em->getRepository(Problem::class)->find($probid);
$this->scoreboardService->calculateScoreRow($contest, $team, $problem);
}
$this->scoreboardService->updateRankCache($contest, $team);
}
}
}

$progressReporter(100, $log);
if ($progressReporter !== null) {
$progressReporter(100, $log);
}

// Update the rejudging itself.
/** @var Rejudging $rejudging */
Expand Down
81 changes: 81 additions & 0 deletions webapp/tests/Unit/Service/RejudgingServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php declare(strict_types=1);

namespace App\Tests\Unit\Service;

use App\DataFixtures\Test\RejudgingFirstToSolveFixture;
use App\Entity\Contest;
use App\Entity\Judging;
use App\Entity\Problem;
use App\Entity\Rejudging;
use App\Entity\Team;
use App\Service\RejudgingService;
use App\Service\ScoreboardService;
use App\Tests\Unit\BaseTestCase;
use App\Utils\Utils;
use Doctrine\ORM\EntityManagerInterface;

class RejudgingServiceTest extends BaseTestCase
{
/**
* Test that the first to solve is correctly updated when a rejudging is finished
*/
public function testUpdateFirstToSolve(): void
{
$this->logIn();

/** @var RejudgingService $rejudgingService */
$rejudgingService = static::getContainer()->get(RejudgingService::class);
/** @var ScoreboardService $scoreboardService */
$scoreboardService = static::getContainer()->get(ScoreboardService::class);
/** @var EntityManagerInterface $entityManager */
$entityManager = static::getContainer()->get(EntityManagerInterface::class);

$this->loadFixture(RejudgingFirstToSolveFixture::class);

$contest = $entityManager->getRepository(Contest::class)->findOneBy(['shortname' => 'demo']);
$team1 = $entityManager->getRepository(Team::class)->findOneBy(['name' => 'Example teamname']);
$team2 = $entityManager->getRepository(Team::class)->findOneBy(['name' => 'Another team']);
$problem = $entityManager->getRepository(Problem::class)->findOneBy(['externalid' => 'hello']);
$contestProblem = $problem->getContestProblems()->first();

// Get the initial scoreboard. team 1 should have the FTS for the problem, team 2 shouldn't
foreach ([$team1, $team2] as $team) {
$scoreboardService->calculateScoreRow($contest, $team, $problem);
$scoreboardService->calculateTeamRank($contest, $team);
}

$scoreboard = $scoreboardService->getScoreboard($contest, true);

static::assertTrue($scoreboard->solvedFirst($team1, $contestProblem));
static::assertFalse($scoreboard->solvedFirst($team2, $contestProblem));

// Now create a rejudging: it will apply a new judging for the submission of $team2 that is correct
$rejudging = (new Rejudging())
->setStarttime(Utils::now())
->setReason(__METHOD__);
$submissionToToggle = $team2->getSubmissions()->first();
$existingJudging = $submissionToToggle->getJudgings()->first();
$newJudging = (new Judging())
->setContest($contest)
->setStarttime($existingJudging->getStarttime())
->setEndtime($existingJudging->getEndtime())
->setRejudging($rejudging)
->setValid(false)
->setResult('correct');
$newJudging->setSubmission($submissionToToggle);
$submissionToToggle->addJudging($newJudging);
$submissionToToggle->setRejudging($rejudging);
$entityManager->persist($rejudging);
$entityManager->persist($newJudging);
$entityManager->flush();

// Now apply the rejudging
$rejudgingService->finishRejudging($rejudging, RejudgingService::ACTION_APPLY);

// Finally, get the scoreboard again and test if the first to solve changed
$scoreboard = $scoreboardService->getScoreboard($contest, true);

static::assertFalse($scoreboard->solvedFirst($team1, $contestProblem));
static::assertTrue($scoreboard->solvedFirst($team2, $contestProblem));
}
}

0 comments on commit 3bb5530

Please sign in to comment.