diff --git a/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php b/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php new file mode 100644 index 0000000000..32132653e8 --- /dev/null +++ b/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php @@ -0,0 +1,56 @@ +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(); + } + } +} diff --git a/webapp/src/Service/RejudgingService.php b/webapp/src/Service/RejudgingService.php index 12c098d369..427a91b96a 100644 --- a/webapp/src/Service/RejudgingService.php +++ b/webapp/src/Service/RejudgingService.php @@ -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. @@ -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 */ diff --git a/webapp/tests/Unit/Service/RejudgingServiceTest.php b/webapp/tests/Unit/Service/RejudgingServiceTest.php new file mode 100644 index 0000000000..1b77a27566 --- /dev/null +++ b/webapp/tests/Unit/Service/RejudgingServiceTest.php @@ -0,0 +1,81 @@ +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)); + } +}