From ebb0b43bf10835d1974c05328be2b6223fb60a9f Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:21:27 +0200 Subject: [PATCH 01/54] Fix judgehost check if its enabled array_filter would only filter out the other judgehosts but still return an array of judgehost objects. By selecting the first (and only item) we can now get the `enabled` property and properly check. ``` array(1) { [1]=> array(5) { ["id"]=> string(1) "2" ["hostname"]=> string(8) "judgehost" ["enabled"]=> bool(true) ["polltime"]=> string(20) "1728821560.017400000" ["hidden"]=> bool(false) } } ``` --- judge/judgedaemon.main.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index b2ab57385b..8f4a44c068 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -768,7 +768,7 @@ function fetch_executable_internal( $judgehosts = request('judgehosts', 'GET'); if ($judgehosts !== null) { $judgehosts = dj_json_decode($judgehosts); - $judgehost = array_filter($judgehosts, fn($j) => $j['hostname'] === $myhost); + $judgehost = array_values(array_filter($judgehosts, fn($j) => $j['hostname'] === $myhost))[0]; if (!isset($judgehost['enabled']) || !$judgehost['enabled']) { logmsg(LOG_WARNING, "Judgehost needs to be enabled in web interface."); } From a43209a65c3490ec48cbf6145c0c469e762f2d12 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 20:10:54 +0200 Subject: [PATCH 02/54] Future work --- .../Controller/Jury/SubmissionController.php | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index d0f9c473e5..4470ce3a36 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1010,6 +1010,23 @@ public function requestRemainingRuns(Request $request, int $judgingId): Redirect ); } + /** + * @throws DBALException + */ + #[Route(path: '/{judgingId<\d+>}/request-visualization', name: 'jury_submission_request_visualization', methods: ['POST'])] + public function requestVisualizationRuns(Request $request, int $judgingId): RedirectResponse + { + $judging = $this->em->getRepository(Judging::class)->find($judgingId); + if ($judging === null) { + throw new BadRequestHttpException("Unknown judging with '$judgingId' requested."); + } + $this->createVisualization([$judging]); + + return $this->redirectToLocalReferrer($this->router, $request, + $this->generateUrl('jury_submission_by_judging', ['jid' => $judgingId]) + ); + } + /** * @throws DBALException */ @@ -1282,4 +1299,70 @@ private function maybeGetErrors(string $type, string $expectedConfigString, stri array_push($allErrors, ...$errors); } } + + /** + * @param Judging[] $judgings + */ + protected function createVisualization(array $judgings): void + { + throw new BadRequestHttpException("Not yet implemented."); + $inProgress = []; + $alreadyRequested = []; + $invalidJudgings = []; + $numRequested = 0; + foreach ($judgings as $judging) { + $judgingId = $judging->getJudgingid(); + if ($judging->getResult() === null) { + $inProgress[] = $judgingId; + } elseif ($judging->getJudgeCompletely()) { + $alreadyRequested[] = $judgingId; + } elseif (!$judging->getValid()) { + $invalidJudgings[] = $judgingId; + } else { + $numRequested = $this->em->getConnection()->executeStatement( + 'UPDATE judgetask SET valid=1' + . ' WHERE jobid=:jobid' + . ' AND judgehostid IS NULL', + [ + 'jobid' => $judgingId, + ] + ); + $judging->setJudgeCompletely(true); + + $submission = $judging->getSubmission(); + + $queueTask = new QueueTask(); + $queueTask->setJudging($judging) + ->setPriority(JudgeTask::PRIORITY_LOW) + ->setTeam($submission->getTeam()) + ->setTeamPriority((int)$submission->getSubmittime()) + ->setStartTime(null); + $this->em->persist($queueTask); + } + } + $this->em->flush(); + if (count($judgings) === 1) { + if ($inProgress !== []) { + $this->addFlash('warning', 'Please be patient, this judging is still in progress.'); + } + if ($alreadyRequested !== []) { + $this->addFlash('warning', 'This judging was already requested to be judged completely.'); + } + } else { + if ($inProgress !== []) { + $this->addFlash('warning', sprintf('Please be patient, these judgings are still in progress: %s', implode(', ', $inProgress))); + } + if ($alreadyRequested !== []) { + $this->addFlash('warning', sprintf('These judgings were already requested to be judged completely: %s', implode(', ', $alreadyRequested))); + } + if ($invalidJudgings !== []) { + $this->addFlash('warning', sprintf('These judgings were skipped as they were superseded by other judgings: %s', implode(', ', $invalidJudgings))); + } + } + if ($numRequested === 0) { + $this->addFlash('warning', 'No more remaining runs to be judged.'); + } else { + $this->addFlash('info', "Requested $numRequested remaining runs to be judged."); + } + } } From d3eec0c7d2e616a2edf6960842cb0b7e0e3d186a Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 19:40:27 +0200 Subject: [PATCH 03/54] Store new visualizer type --- webapp/migrations/Version20241013101907.php | 42 +++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 webapp/migrations/Version20241013101907.php diff --git a/webapp/migrations/Version20241013101907.php b/webapp/migrations/Version20241013101907.php new file mode 100644 index 0000000000..074d4bba91 --- /dev/null +++ b/webapp/migrations/Version20241013101907.php @@ -0,0 +1,42 @@ +addSql('ALTER TABLE executable DROP zipfile'); + $this->addSql('ALTER TABLE problem ADD special_output_visualizer VARCHAR(32) DEFAULT NULL COMMENT \'Executable ID (string)\', CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds; defaults to 1 for traditional problems, 2 for multi-pass problems if not specified.\''); + $this->addSql('ALTER TABLE problem ADD CONSTRAINT FK_D7E7CCC819F5352E FOREIGN KEY (special_output_visualizer) REFERENCES executable (execid) ON DELETE SET NULL'); + $this->addSql('CREATE INDEX special_output_visualizer ON problem (special_output_visualizer)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); + $this->addSql('ALTER TABLE problem DROP FOREIGN KEY FK_D7E7CCC819F5352E'); + $this->addSql('DROP INDEX special_output_visualizer ON problem'); + $this->addSql('ALTER TABLE problem DROP special_output_visualizer, CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\''); + } + + public function isTransactional(): bool + { + return false; + } +} From cdc1692efedd286f67936b90901d71432a13d9b4 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 20:11:51 +0200 Subject: [PATCH 04/54] Make the doctrine links --- webapp/src/Entity/Executable.php | 35 +++++++++++++++---- webapp/src/Entity/Problem.php | 17 +++++++++ webapp/src/Form/Type/ExecutableUploadType.php | 1 + 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/webapp/src/Entity/Executable.php b/webapp/src/Entity/Executable.php index 2beff3a635..bb8e8868d1 100644 --- a/webapp/src/Entity/Executable.php +++ b/webapp/src/Entity/Executable.php @@ -10,13 +10,13 @@ use ZipArchive; /** - * Compile, compare, and run script executable bundles. + * Compile, compare, run, debug and output visualizer script executable bundles. */ #[ORM\Entity] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', - 'comment' => 'Compile, compare, and run script executable bundles', + 'comment' => 'Compile, compare, debug, output visualizer and run script executable bundles', ])] class Executable { @@ -31,7 +31,7 @@ class Executable private ?string $description = null; #[ORM\Column(length: 32, options: ['comment' => 'Type of executable'])] - #[Assert\Choice(['compare', 'compile', 'debug', 'run'])] + #[Assert\Choice(['compare', 'compile', 'debug', 'output_visualizer', 'run'])] private string $type; #[ORM\OneToOne(targetEntity: ImmutableExecutable::class)] @@ -56,11 +56,18 @@ class Executable #[ORM\OneToMany(mappedBy: 'run_executable', targetEntity: Problem::class)] private Collection $problems_run; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'output_visualizer_executable', targetEntity: Problem::class)] + private Collection $problems_output_visualizer; + public function __construct() { - $this->languages = new ArrayCollection(); - $this->problems_compare = new ArrayCollection(); - $this->problems_run = new ArrayCollection(); + $this->languages = new ArrayCollection(); + $this->problems_compare = new ArrayCollection(); + $this->problems_run = new ArrayCollection(); + $this->problems_output_visualizer = new ArrayCollection(); } public function setExecid(string $execid): Executable @@ -143,6 +150,20 @@ public function getProblemsRun(): Collection return $this->problems_run; } + public function addProblemsOutputVisualizer(Problem $problemsOutputVisualizer): Executable + { + $this->problems_output_visualizer[] = $problemsOutputVisualizer; + return $this; + } + + /** + * @return Collection + */ + public function getProblemsOutputVisualizer(): Collection + { + return $this->problems_output_visualizer; + } + public function setImmutableExecutable(ImmutableExecutable $immutableExecutable): Executable { $this->immutableExecutable = $immutableExecutable; @@ -190,7 +211,7 @@ public function checkUsed(array $configScripts): bool if (in_array($this->execid, $configScripts, true)) { return true; } - if (count($this->problems_compare) || count($this->problems_run)) { + if (count($this->problems_compare) || count($this->problems_run) || count($this->problems_output_visualizer)) { return true; } foreach ($this->languages as $lang) { diff --git a/webapp/src/Entity/Problem.php b/webapp/src/Entity/Problem.php index 0d58aa6679..cd413ce37b 100644 --- a/webapp/src/Entity/Problem.php +++ b/webapp/src/Entity/Problem.php @@ -27,6 +27,7 @@ #[ORM\UniqueConstraint(columns: ['externalid'], name: 'externalid', options: ['lengths' => [190]])] #[ORM\Index(columns: ['special_run'], name: 'special_run')] #[ORM\Index(columns: ['special_compare'], name: 'special_compare')] +#[ORM\Index(columns: ['special_output_visualizer'], name: 'special_output_visualizer')] #[ORM\HasLifecycleCallbacks] #[UniqueEntity(fields: 'externalid')] class Problem extends BaseApiEntity implements @@ -154,6 +155,11 @@ class Problem extends BaseApiEntity implements #[Serializer\Exclude] private ?Executable $run_executable = null; + #[ORM\ManyToOne(inversedBy: 'problems_output_visualizer')] + #[ORM\JoinColumn(name: 'special_output_visualizer', referencedColumnName: 'execid', onDelete: 'SET NULL')] + #[Serializer\Exclude] + private ?Executable $output_visualizer_executable = null; + /** * @var Collection */ @@ -378,6 +384,17 @@ public function getRunExecutable(): ?Executable return $this->run_executable; } + public function setOutputVisualizerExecutable(?Executable $outputVisualizerExecutable = null): Problem + { + $this->output_visualizer_executable = $outputVisualizerExecutable; + return $this; + } + + public function getOutputVisualizerExecutable(): ?Executable + { + return $this->output_visualizer_executable; + } + public function __construct() { $this->testcases = new ArrayCollection(); diff --git a/webapp/src/Form/Type/ExecutableUploadType.php b/webapp/src/Form/Type/ExecutableUploadType.php index 80ac3f9cf1..2dce031fa3 100644 --- a/webapp/src/Form/Type/ExecutableUploadType.php +++ b/webapp/src/Form/Type/ExecutableUploadType.php @@ -17,6 +17,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'compare' => 'compare', 'compile' => 'compile', 'run' => 'run', + 'output_visualizer' => 'output_visualizer', ], ]); $builder->add('archives', FileType::class, [ From 88c0e887fe04e4a586455bcbb609d9aa47cdaed2 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 20:55:07 +0200 Subject: [PATCH 05/54] This should be part of the ProblemZip Such a script will most likely never be a generic script. --- webapp/src/Form/Type/ExecutableUploadType.php | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/src/Form/Type/ExecutableUploadType.php b/webapp/src/Form/Type/ExecutableUploadType.php index 2dce031fa3..80ac3f9cf1 100644 --- a/webapp/src/Form/Type/ExecutableUploadType.php +++ b/webapp/src/Form/Type/ExecutableUploadType.php @@ -17,7 +17,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'compare' => 'compare', 'compile' => 'compile', 'run' => 'run', - 'output_visualizer' => 'output_visualizer', ], ]); $builder->add('archives', FileType::class, [ From 131abb5888ccbdde241488a9129d4b537720ffa3 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 19:23:54 +0200 Subject: [PATCH 06/54] Add simple output_validator This is untested yet as the syntax should be further discussed. --- .../boolfind_visual/visualize.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py diff --git a/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py new file mode 100644 index 0000000000..6500632ee8 --- /dev/null +++ b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# +# Invoke as: +# input answer_file feedback_dir [additional_arguments] < team_output [ > team_input ] +import matplotlib.pyplot as plt +import sys + +my_name = sys.argv[0] +my_input = sys.argv[1] +#real_answer = sys.argv[2] +my_feedback = sys.argv[3] + + +plt.plot([map(int, x) for x in sys.stdin.readlines()]) +plt.ylabel('Guesses') +plt.savefig(f"{feedback_dir}visual.png") From 5e3406f78cd7b3b794be1efe5ca8aed493a0e929 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 19:53:20 +0200 Subject: [PATCH 07/54] Assume this will be put in the spec This assumes a similar invocation as for the output_validator, the other alternative is to add this to the domjudge-problem.ini file if this doesn't end up in the ICPC problem spec. --- example_problems/boolfind/problem.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/example_problems/boolfind/problem.yaml b/example_problems/boolfind/problem.yaml index 2a0e38b965..253a29c138 100644 --- a/example_problems/boolfind/problem.yaml +++ b/example_problems/boolfind/problem.yaml @@ -1,3 +1,4 @@ name: Boolean switch search validation: custom interactive +visualization: default From 3b40afa6f4652cf5ba1f5f152c98772e30ff835b Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 21:53:19 +0200 Subject: [PATCH 08/54] Allow upload of the output_visualizer --- webapp/src/Service/ImportProblemService.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index 26474e5a00..2ff234fb92 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -72,7 +72,7 @@ public function importZippedProblem( $submission_file = 'submissions.json'; $problemIsNew = $problem === null; - $iniKeysProblem = ['name', 'timelimit', 'special_run', 'special_compare', 'externalid']; + $iniKeysProblem = ['name', 'timelimit', 'special_run', 'special_compare', 'special_output_visualizer', 'externalid']; $iniKeysContestProblem = ['allow_submit', 'allow_judge', 'points', 'color', 'short-name']; $defaultTimelimit = 10; @@ -144,6 +144,11 @@ public function importZippedProblem( $this->em->getRepository(Executable::class)->find($problemProperties['special_run']); unset($problemProperties['special_run']); } + if (isset($problemProperties['special_output_visualizer'])) { + $problemProperties['output_visualizer_executable'] = + $this->em->getRepository(Executable::class)->find($problemProperties['special_output_visualizer']); + unset($problemProperties['special_output_visualizer']); + } /** @var ContestProblem|null $contestProblem */ $contestProblem = null; @@ -208,6 +213,7 @@ public function importZippedProblem( ->setSpecialCompareArgs('') ->setRunExecutable() ->setCombinedRunCompare(false) + ->setOutputVisualizerExecutable() ->setMemlimit(null) ->setOutputlimit(null) ->setProblemStatementContent(null) @@ -306,6 +312,12 @@ public function importZippedProblem( } } + if (isset($yamlData['visualization'])) { + if (!$this->searchAndAddOutputVisualizer($zip, $messages, $externalId, $yamlData['visualization'], $problem)) { + return null; + } + } + foreach ($yamlProblemProperties as $key => $value) { $propertyAccessor->setValue($problem, $key, $value); } From d4f440118fcb93646436358c759e1be78253a116 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 22:33:44 +0200 Subject: [PATCH 09/54] First try on import --- webapp/src/Service/ImportProblemService.php | 100 ++++++++++++++------ 1 file changed, 69 insertions(+), 31 deletions(-) diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index 2ff234fb92..c8d02ffcbf 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -938,36 +938,61 @@ public function importProblemFromRequest(Request $request, ?int $contestId = nul ]; } + /** + * @param array{danger: string[], info: string[]} $messages + */ + private function searchAndAddOutputVisualizer(ZipArchive $zip, ?array &$messages, string $externalId, string $visualizerMode, ?Problem $problem): bool + { + $programStrings = []; + $programStrings['package_dir'] = 'output_visualizer/'; + $programStrings['type'] = 'output visualizer'; + $programStrings['clash'] = 'visual'; + return self::helperSearchAndAddProgram($zip, $messages, $externalId, $visualizerMode, $problem, $programStrings); + } + /** * @param array{danger: string[], info: string[]} $messages */ private function searchAndAddValidator(ZipArchive $zip, ?array &$messages, string $externalId, string $validationMode, ?Problem $problem): bool { - $validatorFiles = []; + $programStrings = []; + $programStrings['package_dir'] = 'output_validators/'; + $programStrings['type'] = 'output validator'; + $programStrings['clash'] = 'cmp'; + return self::helperSearchAndAddProgram($zip, $messages, $externalId, $validationMode, $problem, $programStrings); + } + + /** + * @param array{danger: string[], info: string[]} $messages + * @param array $programStrings + */ + private function helperSearchAndAddProgram(ZipArchive $zip, ?array &$messages, string $externalId, string $programMode, ?Problem $problem, array $programStrings): bool + { + $programFiles = []; for ($i = 0; $i < $zip->numFiles; $i++) { $filename = $zip->getNameIndex($i); - if (Utils::startsWith($filename, 'output_validators/') && + if (Utils::startsWith($filename, $programStrings['package_dir']) && !Utils::endsWith($filename, '/')) { - $validatorFiles[] = $filename; + $programFiles[] = $filename; } } - if (sizeof($validatorFiles) == 0) { - $messages['danger'][] = 'Custom validator specified but not found.'; + if (sizeof($programFiles) == 0) { + $messages['danger'][] = 'Custom ' . $programStrings['type'] . ' specified but not found.'; return false; } else { // File(s) have to share common directory. - $validatorDir = mb_substr($validatorFiles[0], 0, mb_strrpos($validatorFiles[0], '/')) . '/'; + $programDir = mb_substr($programFiles[0], 0, mb_strrpos($programFiles[0], '/')) . '/'; $sameDir = true; - foreach ($validatorFiles as $validatorFile) { - if (!Utils::startsWith($validatorFile, $validatorDir)) { + foreach ($programFiles as $programFile) { + if (!Utils::startsWith($programFile, $programDir)) { $sameDir = false; $messages['warning'][] = sprintf('%s does not start with %s.', - $validatorFile, $validatorDir); + $programFile, $programDir); break; } } if (!$sameDir) { - $messages['danger'][] = 'Found multiple custom output validators.'; + $messages['danger'][] = 'Found multiple custom ' . $programStrings['type'] . 's.'; return false; } else { $tmpzipfiledir = exec("mktemp -d --tmpdir=" . @@ -979,9 +1004,9 @@ private function searchAndAddValidator(ZipArchive $zip, ?array &$messages, strin ); } chmod($tmpzipfiledir, 0700); - foreach ($validatorFiles as $validatorFile) { - $content = $zip->getFromName($validatorFile); - $filebase = basename($validatorFile); + foreach ($programFiles as $programFile) { + $content = $zip->getFromName($programFile); + $filebase = basename($programFile); $newfilename = $tmpzipfiledir . "/" . $filebase; file_put_contents($newfilename, $content); if ($filebase === 'build' || $filebase === 'run') { @@ -990,51 +1015,64 @@ private function searchAndAddValidator(ZipArchive $zip, ?array &$messages, strin } } - exec("zip -r -j '$tmpzipfiledir/outputvalidator.zip' '$tmpzipfiledir'", + $newZipFilename = $tmpzipfiledir . '/' . str_replace(' ', '', $programStrings['type']) . '.zip'; + exec("zip -r -j '$newZipFilename' '$tmpzipfiledir'", $dontcare, $retval); if ($retval != 0) { throw new ServiceUnavailableHttpException( - null, 'Failed to create ZIP file for output validator.' + null, 'Failed to create ZIP file for ' . $programStrings['type'] . '.' ); } - $outputValidatorZip = file_get_contents($tmpzipfiledir . '/outputvalidator.zip'); - $outputValidatorName = substr($externalId, 0, 20) . '_cmp'; - if ($this->em->getRepository(Executable::class)->find($outputValidatorName)) { + $programZip = file_get_contents($newZipFilename); + $programName = substr($externalId, 0, 20) . '_' . $programStrings['clash']; + if ($this->em->getRepository(Executable::class)->find($programName)) { // Avoid name clash. $clashCount = 2; while ($this->em->getRepository(Executable::class)->find( - $outputValidatorName . '_' . $clashCount)) { + $programName . '_' . $clashCount)) { $clashCount++; } - $outputValidatorName = $outputValidatorName . "_" . $clashCount; + $programName = $programName . "_" . $clashCount; } - $combinedRunCompare = $validationMode == 'custom interactive'; - if (!($tempzipFile = tempnam($this->dj->getDomjudgeTmpDir(), "/executable-"))) { throw new ServiceUnavailableHttpException(null, 'Failed to create temporary file.'); } - file_put_contents($tempzipFile, $outputValidatorZip); + file_put_contents($tempzipFile, $programZip); $zipArchive = new ZipArchive(); $zipArchive->open($tempzipFile, ZipArchive::CREATE); $executable = new Executable(); $executable - ->setExecid($outputValidatorName) + ->setExecid($programName) ->setImmutableExecutable($this->dj->createImmutableExecutable($zipArchive)) - ->setDescription(sprintf('output validator for %s', $problem->getName())) - ->setType($combinedRunCompare ? 'run' : 'compare'); + ->setDescription(sprintf('%s for %s', $programStrings['type'], $problem->getName())); + + if ($programStrings['type'] === 'output validator') { + $combinedRunCompare = $programMode == 'custom interactive'; + $executable->setType($combinedRunCompare ? 'run' : 'compare'); + } else { + $executable->setType(str_replace(' ', '_', $programStrings['type'])); + } $this->em->persist($executable); - if ($combinedRunCompare) { - $problem->setCombinedRunCompare(true); - $problem->setRunExecutable($executable); + if ($programStrings['type'] === 'output validator') { + if ($combinedRunCompare) { + $problem->setCombinedRunCompare(true); + $problem->setRunExecutable($executable); + } else { + $problem->setCompareExecutable($executable); + } + } elseif ($programStrings['type'] === 'output visualizer') { + $problem->setOutputVisualizerExecutable($executable); } else { - $problem->setCompareExecutable($executable); + $messages['danger'][] = "Unknown type '" . $programStrings['type'] . "'."; + return false; } - $messages['info'][] = "Added output validator '$outputValidatorName'."; + $newMessage = "Added " . $programStrings['type'] . " '$programName'."; + $messages['info'][] = $newMessage; } } return true; From 101cc03a89e42c2db7826847e9a16f4e7538293e Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 19:43:45 +0200 Subject: [PATCH 10/54] Give icon for new executable type --- webapp/src/Controller/Jury/ExecutableController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp/src/Controller/Jury/ExecutableController.php b/webapp/src/Controller/Jury/ExecutableController.php index b3c2913a47..01c4275308 100644 --- a/webapp/src/Controller/Jury/ExecutableController.php +++ b/webapp/src/Controller/Jury/ExecutableController.php @@ -142,6 +142,9 @@ public function indexAction(Request $request): Response case 'run': $execdata['icon']['icon'] = 'person-running'; break; + case 'output_visualizer': + $execdata['icon']['icon'] = 'paint-brush'; + break; default: $execdata['icon']['icon'] = 'question'; } From 69d99dcb7d4dfeafb0a67cb6b253bde6278e212b Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Mon, 14 Oct 2024 11:28:11 +0200 Subject: [PATCH 11/54] Display the problem badge --- webapp/src/Controller/Jury/ExecutableController.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/webapp/src/Controller/Jury/ExecutableController.php b/webapp/src/Controller/Jury/ExecutableController.php index 01c4275308..d8f0204950 100644 --- a/webapp/src/Controller/Jury/ExecutableController.php +++ b/webapp/src/Controller/Jury/ExecutableController.php @@ -90,17 +90,20 @@ public function indexAction(Request $request): Response ->join('cp.problem', 'p') ->leftJoin('p.compare_executable', 'ecomp') ->leftJoin('p.run_executable', 'erun') - ->andWhere('ecomp IS NOT NULL OR erun IS NOT NULL') + ->leftJoin('p.output_visualizer_executable', 'evisual') + ->andWhere('ecomp IS NOT NULL OR erun IS NOT NULL OR evisual IS NOT NULL') ->getQuery()->getResult(); $executablesWithContestProblems = $em->createQueryBuilder() ->select('e') ->from(Executable::class, 'e') ->leftJoin('e.problems_compare', 'pcomp') ->leftJoin('e.problems_run', 'prun') - ->where('pcomp IS NOT NULL OR prun IS NOT NULL') + ->leftJoin('e.problems_output_visualizer', 'pvisual') + ->where('pcomp IS NOT NULL OR prun IS NOT NULL OR pvisual IS NOT NULL') ->leftJoin('pcomp.contest_problems', 'cpcomp') ->leftJoin('prun.contest_problems', 'cprun') - ->andWhere('cprun.contest = :contest OR cpcomp.contest = :contest') + ->leftJoin('pvisual.contest_problems', 'cpvisual') + ->andWhere('cprun.contest = :contest OR cpcomp.contest = :contest OR cpvisual.contest = :contest') ->setParameter('contest', $this->dj->getCurrentContest()) ->getQuery()->getResult(); } @@ -108,7 +111,7 @@ public function indexAction(Request $request): Response foreach ($executables as $e) { $badges = []; if (in_array($e, $executablesWithContestProblems)) { - foreach (array_merge($e->getProblemsRun()->toArray(), $e->getProblemsCompare()->toArray()) as $execProblem) { + foreach (array_merge($e->getProblemsRun()->toArray(), $e->getProblemsCompare()->toArray(), $e->getProblemsOutputVisualizer()->toArray()) as $execProblem) { $execContestProblems = $execProblem->getContestProblems(); foreach ($contestProblemsWithExecutables as $cp) { if ($execContestProblems->contains($cp)) { From df61e4a9204b55badee79373166949103238c9be Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Mon, 14 Oct 2024 11:31:04 +0200 Subject: [PATCH 12/54] Display on the executable page itself --- webapp/templates/jury/executable.html.twig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webapp/templates/jury/executable.html.twig b/webapp/templates/jury/executable.html.twig index 94569d1e4e..6c905432a7 100644 --- a/webapp/templates/jury/executable.html.twig +++ b/webapp/templates/jury/executable.html.twig @@ -59,6 +59,13 @@ {% set used = true %} {% endfor %} + {% elseif executable.type == 'output_visualizer' %} + {% for problem in executable.problemsOutputVisualizer %} + + p{{ problem.probid }} {{ problem | problemBadgeForContest }} + + {% set used = true %} + {% endfor %} {% elseif executable.type == 'compile' %} {% for language in executable.languages %} From a956b58c619c814321bca0fd1798aea445b8922e Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 23:13:02 +0200 Subject: [PATCH 13/54] Add button to generate the visualization Getting this directly via submission.problem.output_visualizer_executable (the property) seems to fail. It does show up in the twig dump but the translation fails when using it. --- webapp/src/Controller/Jury/SubmissionController.php | 1 + webapp/templates/jury/submission.html.twig | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 4470ce3a36..cdda700d3a 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -533,6 +533,7 @@ public function viewAction( 'requestedOutputCount' => $requestedOutputCount, 'version_warnings' => [], 'isMultiPassProblem' => $submission->getProblem()->isMultipassProblem(), + 'hasOutputVisualizer' => $submission->getProblem()->getOutputVisualizerExecutable() ?? false, ]; if ($selectedJudging === null) { diff --git a/webapp/templates/jury/submission.html.twig b/webapp/templates/jury/submission.html.twig index 589c6dde95..0883a9c1e3 100644 --- a/webapp/templates/jury/submission.html.twig +++ b/webapp/templates/jury/submission.html.twig @@ -532,6 +532,12 @@ {{ runs | displayTestcaseResults(judgingDone) }} + {% if hasOutputVisualizer and judgingDone %} +
+ +
+ {% endif %} {% if selectedJudging is not null and runsOutstanding %} {% if selectedJudging.judgeCompletely %} From fbe73584a12b9e49100a45ceb9bb3efdd07273b2 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Sun, 13 Oct 2024 23:12:49 +0200 Subject: [PATCH 14/54] Show executable in problem page --- webapp/templates/jury/problem.html.twig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webapp/templates/jury/problem.html.twig b/webapp/templates/jury/problem.html.twig index 70de500b0f..e8df40d774 100644 --- a/webapp/templates/jury/problem.html.twig +++ b/webapp/templates/jury/problem.html.twig @@ -109,6 +109,20 @@ {{ problem.specialCompareArgs }} {% endif %} + {% if problem.getOutputVisualizerExecutable is not empty %} + + Output visualizer + +
{{ problem.getOutputVisualizerExecutable.execid }} + + + {% endif %} + {% if problem.specialCompareArgs is not empty %} + + Compare script arguments + {{ problem.specialCompareArgs }} + + {% endif %} {% if type is not empty %} Type From 29867274e34724ca03edd0614410f266cd6602e5 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Mon, 14 Oct 2024 18:03:17 +0200 Subject: [PATCH 15/54] Leave this for the demo --- webapp/templates/jury/problem.html.twig | 6 ------ 1 file changed, 6 deletions(-) diff --git a/webapp/templates/jury/problem.html.twig b/webapp/templates/jury/problem.html.twig index e8df40d774..57881c0497 100644 --- a/webapp/templates/jury/problem.html.twig +++ b/webapp/templates/jury/problem.html.twig @@ -117,12 +117,6 @@ {% endif %} - {% if problem.specialCompareArgs is not empty %} - - Compare script arguments - {{ problem.specialCompareArgs }} - - {% endif %} {% if type is not empty %} Type From d79783da68d4f2166b237878f28888b46987e733 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Mon, 14 Oct 2024 18:35:02 +0200 Subject: [PATCH 16/54] Remember if we already requested visualization --- webapp/migrations/Version20241014163343.php | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 webapp/migrations/Version20241014163343.php diff --git a/webapp/migrations/Version20241014163343.php b/webapp/migrations/Version20241014163343.php new file mode 100644 index 0000000000..14a529e8dd --- /dev/null +++ b/webapp/migrations/Version20241014163343.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE executable DROP zipfile'); + $this->addSql('ALTER TABLE judging ADD visualization TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Explicitly requested to visualize the output.\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); + $this->addSql('ALTER TABLE judging DROP visualization'); + } + + public function isTransactional(): bool + { + return false; + } +} From 8de3e52f831bc6c9dda0c466fc69803b1a337c6c Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Mon, 14 Oct 2024 22:58:36 +0200 Subject: [PATCH 17/54] Create the needed judgetasks --- webapp/migrations/Version20241014185658.php | 38 ++++++++++++++ .../Controller/Jury/JudgeRemainingTrait.php | 1 + .../Controller/Jury/SubmissionController.php | 52 +++++++++---------- .../src/Doctrine/DBAL/Types/JudgeTaskType.php | 2 + webapp/src/Entity/JudgeTask.php | 15 ++++++ webapp/src/Entity/Judging.php | 15 ++++++ 6 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 webapp/migrations/Version20241014185658.php diff --git a/webapp/migrations/Version20241014185658.php b/webapp/migrations/Version20241014185658.php new file mode 100644 index 0000000000..7bb7a07231 --- /dev/null +++ b/webapp/migrations/Version20241014185658.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE executable DROP zipfile'); + $this->addSql('ALTER TABLE judgetask ADD output_visualizer_script_id INT UNSIGNED DEFAULT NULL COMMENT \'Output visualizer script ID\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); + $this->addSql('ALTER TABLE judgetask DROP output_visualizer_script_id'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/src/Controller/Jury/JudgeRemainingTrait.php b/webapp/src/Controller/Jury/JudgeRemainingTrait.php index 7358f7bc5e..1ba8bbc898 100644 --- a/webapp/src/Controller/Jury/JudgeRemainingTrait.php +++ b/webapp/src/Controller/Jury/JudgeRemainingTrait.php @@ -29,6 +29,7 @@ protected function judgeRemaining(array $judgings): void $numRequested = $this->em->getConnection()->executeStatement( 'UPDATE judgetask SET valid=1' . ' WHERE jobid=:jobid' + . ' AND type="judging_run"' . ' AND judgehostid IS NULL', [ 'jobid' => $judgingId, diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index cdda700d3a..886a8e7eb3 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -21,6 +21,7 @@ use App\Entity\TeamAffiliation; use App\Entity\TeamCategory; use App\Entity\Testcase; +use App\Entity\QueueTask; use App\Form\Type\SubmissionsFilterType; use App\Service\BalloonService; use App\Service\ConfigurationService; @@ -1306,7 +1307,6 @@ private function maybeGetErrors(string $type, string $expectedConfigString, stri */ protected function createVisualization(array $judgings): void { - throw new BadRequestHttpException("Not yet implemented."); $inProgress = []; $alreadyRequested = []; $invalidJudgings = []; @@ -1315,55 +1315,53 @@ protected function createVisualization(array $judgings): void $judgingId = $judging->getJudgingid(); if ($judging->getResult() === null) { $inProgress[] = $judgingId; - } elseif ($judging->getJudgeCompletely()) { + } elseif (false and $judging->getVisualization()) { $alreadyRequested[] = $judgingId; } elseif (!$judging->getValid()) { $invalidJudgings[] = $judgingId; } else { - $numRequested = $this->em->getConnection()->executeStatement( - 'UPDATE judgetask SET valid=1' - . ' WHERE jobid=:jobid' - . ' AND judgehostid IS NULL', - [ - 'jobid' => $judgingId, - ] - ); - $judging->setJudgeCompletely(true); - - $submission = $judging->getSubmission(); - - $queueTask = new QueueTask(); - $queueTask->setJudging($judging) - ->setPriority(JudgeTask::PRIORITY_LOW) - ->setTeam($submission->getTeam()) - ->setTeamPriority((int)$submission->getSubmittime()) - ->setStartTime(null); - $this->em->persist($queueTask); + $outs = $judging->getRuns()->toArray(); + $tmpRun = null; + $lowestId = count($outs); + foreach ($outs as $run) { + if ($tmpRun !== null and $lowestId > $run->getRunId()) { + continue; + } + if ($run->getRunResult() === 'correct' and $run->getRunId()<$lowestId) { + $tmpRun = $run; + $lowestId = $run->getRunId(); + } + } + $judgeTask = new JudgeTask(); + $judgeTask->setType('output_visualization') + ->setValid(true) + ->setJobid($judgingId); + $numRequested += 1; + $this->em->flush(); } } - $this->em->flush(); if (count($judgings) === 1) { if ($inProgress !== []) { - $this->addFlash('warning', 'Please be patient, this judging is still in progress.'); + $this->addFlash('warning', 'Please be patient, this visualization is still in progress.'); } if ($alreadyRequested !== []) { - $this->addFlash('warning', 'This judging was already requested to be judged completely.'); + $this->addFlash('warning', 'This visualization was already requested to be judged completely.'); } } else { if ($inProgress !== []) { - $this->addFlash('warning', sprintf('Please be patient, these judgings are still in progress: %s', implode(', ', $inProgress))); + $this->addFlash('warning', sprintf('Please be patient, these visualizations are still in progress: %s', implode(', ', $inProgress))); } if ($alreadyRequested !== []) { $this->addFlash('warning', sprintf('These judgings were already requested to be judged completely: %s', implode(', ', $alreadyRequested))); } if ($invalidJudgings !== []) { - $this->addFlash('warning', sprintf('These judgings were skipped as they were superseded by other judgings: %s', implode(', ', $invalidJudgings))); + $this->addFlash('warning', sprintf('These visualizations were skipped as the judgings were superseded by other judgings: %s', implode(', ', $invalidJudgings))); } } if ($numRequested === 0) { $this->addFlash('warning', 'No more remaining runs to be judged.'); } else { - $this->addFlash('info', "Requested $numRequested remaining runs to be judged."); + $this->addFlash('info', "Requested $numRequested to be visualized."); } } } diff --git a/webapp/src/Doctrine/DBAL/Types/JudgeTaskType.php b/webapp/src/Doctrine/DBAL/Types/JudgeTaskType.php index b9c2575113..3fb086456f 100644 --- a/webapp/src/Doctrine/DBAL/Types/JudgeTaskType.php +++ b/webapp/src/Doctrine/DBAL/Types/JudgeTaskType.php @@ -13,12 +13,14 @@ class JudgeTaskType extends Type final public const GENERIC_TASK = 'generic_task'; final public const JUDGING_RUN = 'judging_run'; final public const PREFETCH = 'prefetch'; + final public const OUTPUT_VISUALIZATION = 'output_visualization'; final public const ALL_TYPES = [ self::CONFIG_CHECK, self::DEBUG_INFO, self::GENERIC_TASK, self::JUDGING_RUN, self::PREFETCH, + self::OUTPUT_VISUALIZATION, ]; public function getSQLDeclaration(array $column, AbstractPlatform $platform): string diff --git a/webapp/src/Entity/JudgeTask.php b/webapp/src/Entity/JudgeTask.php index 513efebee1..b00820a405 100644 --- a/webapp/src/Entity/JudgeTask.php +++ b/webapp/src/Entity/JudgeTask.php @@ -99,6 +99,10 @@ public function getSubmitid(): ?int #[Serializer\Type('string')] private ?int $compare_script_id = null; + #[ORM\Column(nullable: true, options: ['comment' => 'Output visualizer script ID', 'unsigned' => true])] + #[Serializer\Type('string')] + private ?int $output_visualizer_script_id = null; + #[ORM\Column(nullable: true, options: ['comment' => 'Testcase ID', 'unsigned' => true])] #[Serializer\Type('string')] private ?int $testcase_id = null; @@ -275,6 +279,17 @@ public function getCompareScriptId(): int return $this->compare_script_id; } + public function setOutputVisualizerScriptId(int $output_visualizer_script_id): JudgeTask + { + $this->output_visualizer_script_id = $output_visualizer_script_id; + return $this; + } + + public function getOutputVisualizerScriptId(): int + { + return $this->output_visualizer_script_id; + } + public function setTestcaseId(int $testcase_id): JudgeTask { $this->testcase_id = $testcase_id; diff --git a/webapp/src/Entity/Judging.php b/webapp/src/Entity/Judging.php index 2526b48c6a..a310a83c20 100644 --- a/webapp/src/Entity/Judging.php +++ b/webapp/src/Entity/Judging.php @@ -113,6 +113,10 @@ class Judging extends BaseApiEntity #[Serializer\Exclude] private bool $judgeCompletely = false; + #[ORM\Column(options: ['comment' => 'Explicitly requested to visualize the output.', 'default' => 0])] + #[Serializer\Exclude] + private bool $visualization = false; + #[ORM\Column(options: ['comment' => 'UUID, to make caching of compilation results safe.'])] #[Serializer\Exclude] private string $uuid; @@ -350,6 +354,17 @@ public function getJudgeCompletely(): bool return $this->judgeCompletely; } + public function setVisualization(bool $visualization): Judging + { + $this->visualization = $visualization; + return $this; + } + + public function getVisualization(): bool + { + return $this->visualization; + } + public function setSubmission(?Submission $submission = null): Judging { $this->submission = $submission; From f3b96e1be401230f6811e8369090d004c516a69c Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 09:20:50 +0200 Subject: [PATCH 18/54] Fix PHPStan issues --- webapp/src/Controller/Jury/SubmissionController.php | 8 ++++---- webapp/src/Service/ImportProblemService.php | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 886a8e7eb3..d5bc6c0bdf 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1315,9 +1315,9 @@ protected function createVisualization(array $judgings): void $judgingId = $judging->getJudgingid(); if ($judging->getResult() === null) { $inProgress[] = $judgingId; - } elseif (false and $judging->getVisualization()) { + /*} elseif ($judging->getVisualization()) { $alreadyRequested[] = $judgingId; - } elseif (!$judging->getValid()) { + }*/ elseif (!$judging->getValid()) { $invalidJudgings[] = $judgingId; } else { $outs = $judging->getRuns()->toArray(); @@ -1344,14 +1344,14 @@ protected function createVisualization(array $judgings): void if ($inProgress !== []) { $this->addFlash('warning', 'Please be patient, this visualization is still in progress.'); } - if ($alreadyRequested !== []) { + if ($alreadyRequested != []) { $this->addFlash('warning', 'This visualization was already requested to be judged completely.'); } } else { if ($inProgress !== []) { $this->addFlash('warning', sprintf('Please be patient, these visualizations are still in progress: %s', implode(', ', $inProgress))); } - if ($alreadyRequested !== []) { + if ($alreadyRequested != []) { $this->addFlash('warning', sprintf('These judgings were already requested to be judged completely: %s', implode(', ', $alreadyRequested))); } if ($invalidJudgings !== []) { diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index c8d02ffcbf..f7cedc19d9 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -1049,6 +1049,7 @@ private function helperSearchAndAddProgram(ZipArchive $zip, ?array &$messages, s ->setImmutableExecutable($this->dj->createImmutableExecutable($zipArchive)) ->setDescription(sprintf('%s for %s', $programStrings['type'], $problem->getName())); + $combinedRunCompare = false; if ($programStrings['type'] === 'output validator') { $combinedRunCompare = $programMode == 'custom interactive'; $executable->setType($combinedRunCompare ? 'run' : 'compare'); From f6baa3a69f95b2f5e0f79beda6ff1fde7031c05a Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 09:44:15 +0200 Subject: [PATCH 19/54] Fix syntax error --- webapp/src/Controller/Jury/SubmissionController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index d5bc6c0bdf..131c15a610 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1317,7 +1317,7 @@ protected function createVisualization(array $judgings): void $inProgress[] = $judgingId; /*} elseif ($judging->getVisualization()) { $alreadyRequested[] = $judgingId; - }*/ elseif (!$judging->getValid()) { + */}/ elseif (!$judging->getValid()) { $invalidJudgings[] = $judgingId; } else { $outs = $judging->getRuns()->toArray(); From 76deaafb65a88df30f72cc0044f6d825bb0500e3 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 09:50:46 +0200 Subject: [PATCH 20/54] Just move the whole code away for now This is needed during testing of the job on the judgehost --- webapp/src/Controller/Jury/SubmissionController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 131c15a610..77c3fc68a5 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1313,11 +1313,11 @@ protected function createVisualization(array $judgings): void $numRequested = 0; foreach ($judgings as $judging) { $judgingId = $judging->getJudgingid(); + /*elseif ($judging->getVisualization()) { + $alreadyRequested[] = $judgingId;*/ if ($judging->getResult() === null) { $inProgress[] = $judgingId; - /*} elseif ($judging->getVisualization()) { - $alreadyRequested[] = $judgingId; - */}/ elseif (!$judging->getValid()) { + } elseif (!$judging->getValid()) { $invalidJudgings[] = $judgingId; } else { $outs = $judging->getRuns()->toArray(); From aa062d7dfca8a559c25dc108778057f3595c8f3d Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 10:06:37 +0200 Subject: [PATCH 21/54] And more errors as the needed code was commented out --- webapp/src/Controller/Jury/SubmissionController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 77c3fc68a5..2629a25f9b 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1344,16 +1344,16 @@ protected function createVisualization(array $judgings): void if ($inProgress !== []) { $this->addFlash('warning', 'Please be patient, this visualization is still in progress.'); } - if ($alreadyRequested != []) { + /*if ($alreadyRequested != []) { $this->addFlash('warning', 'This visualization was already requested to be judged completely.'); - } + }*/ } else { if ($inProgress !== []) { $this->addFlash('warning', sprintf('Please be patient, these visualizations are still in progress: %s', implode(', ', $inProgress))); } - if ($alreadyRequested != []) { + /*if ($alreadyRequested != []) { $this->addFlash('warning', sprintf('These judgings were already requested to be judged completely: %s', implode(', ', $alreadyRequested))); - } + }*/ if ($invalidJudgings !== []) { $this->addFlash('warning', sprintf('These visualizations were skipped as the judgings were superseded by other judgings: %s', implode(', ', $invalidJudgings))); } From 2963350dc7945ccc4f6845cf2c752396b634e167 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 22:14:28 +0200 Subject: [PATCH 22/54] Revert later - Fixate hostname for api/doc debugging --- webapp/src/Controller/API/JudgehostController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 8cfa36c80a..c51f8f3367 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1474,10 +1474,11 @@ private function getTestcaseFiles(string $id): array )] public function getJudgeTasksAction(Request $request): array { - if (!$request->request->has('hostname')) { + /*if (!$request->request->has('hostname')) { throw new BadRequestHttpException('Argument \'hostname\' is mandatory'); } - $hostname = $request->request->get('hostname'); + $hostname = $request->request->get('hostname');*/ + $hostname = 'Computer'; $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); if (!$judgehost) { From f0dcaec34208ca3ba6a73e6e06f09a0aab868c55 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 22:08:23 +0200 Subject: [PATCH 23/54] Fix API for when you test on a deeper dir --- webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php b/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php index 2805d51b55..ff073d4f0d 100644 --- a/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php +++ b/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php @@ -24,6 +24,6 @@ public function describe(OpenApi $api): void // Inject the correct server for the API docs $request = $this->requestStack->getCurrentRequest(); $this->decorated->describe($api); - Util::merge($api->servers[0], ['url' => $request->getSchemeAndHttpHost(),], true); + Util::merge($api->servers[0], ['url' => $request->getSchemeAndHttpHost().$request->getBaseUrl(),], true); } } From 163694447c55344f88590f1327981a53e0b4e0ba Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Wed, 16 Oct 2024 07:43:26 +0200 Subject: [PATCH 24/54] New migrations --- webapp/migrations/Version20241015085315.php | 36 +++++++++++++++++++++ webapp/migrations/Version20241015085654.php | 36 +++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 webapp/migrations/Version20241015085315.php create mode 100644 webapp/migrations/Version20241015085654.php diff --git a/webapp/migrations/Version20241015085315.php b/webapp/migrations/Version20241015085315.php new file mode 100644 index 0000000000..2ffdba1775 --- /dev/null +++ b/webapp/migrations/Version20241015085315.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE executable DROP zipfile'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/migrations/Version20241015085654.php b/webapp/migrations/Version20241015085654.php new file mode 100644 index 0000000000..9ac0aa7052 --- /dev/null +++ b/webapp/migrations/Version20241015085654.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE `judgetask` + MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\', \'output_visualization\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE `judgetask` + MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); + } + + public function isTransactional(): bool + { + return false; + } +} From 1af147a1e006d8a7c3cc665c5edf3a45f02b0da5 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Wed, 16 Oct 2024 07:47:54 +0200 Subject: [PATCH 25/54] Test the search in an unrelated page Debugging via the API is hard, so we hardcode the search and just copy it over in a next commit. --- webapp/src/Controller/Jury/UserController.php | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index 3000e5650f..5e80e683e4 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -161,6 +161,30 @@ public function indexAction(): Response ]; } + $hostname = 'Computer'; + $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); + + /** @var JudgeTask[] $judgetasks */ + $judgetasks = $this->em + ->createQueryBuilder() + ->from(JudgeTask::class, 'jt') + ->select('jt') + ->andWhere('jt.judgehost = :judgehost') + //->andWhere('jt.starttime IS NULL') + ->andWhere('jt.valid = 1') + ->andWhere('jt.type = :type') + ->setParameter('judgehost', $judgehost) + ->setParameter('type', JudgeTaskType::OUTPUT_VISUALIZATION) + ->addOrderBy('jt.priority') + ->addOrderBy('jt.judgetaskid') + ->setMaxResults(1) + ->getQuery() + ->getResult(); + dump($judgetasks); + /*if (!empty($judgetasks)) { + dump($this->serializeJudgeTasks($judgetasks, $judgehost)); + }*/ + return $this->render('jury/users.html.twig', [ 'users' => $users_table, 'table_fields' => $table_fields, From d88d0c0e7ba076a48dbd2141e9ca12d06ade870b Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Wed, 16 Oct 2024 20:27:28 +0200 Subject: [PATCH 26/54] Also get the new visualization `JudgeTask`s --- webapp/src/Controller/API/JudgehostController.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index c51f8f3367..0e8ea859e8 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1668,8 +1668,16 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a $submit_id = $judgeTasks[0]->getSubmission()?->getSubmitid(); $judgetaskids = []; foreach ($judgeTasks as $judgeTask) { - if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { - $judgetaskids[] = $judgeTask->getJudgetaskid(); + if ($judgeTask->getType() == 'judging_run') { + if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } + } else { + // Just pick everything assigned to the judgehost itself or unassigned for now + $assignedJudgehost = $judgeTask->getJudgehost(); + if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } } } From e56a83fba8940f16acc87cf1dce7bb2c973e1ecb Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Wed, 16 Oct 2024 20:43:46 +0200 Subject: [PATCH 27/54] Return the visualizer jobs --- .../Controller/API/JudgehostController.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 0e8ea859e8..8b3c777842 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1650,6 +1650,27 @@ public function getJudgeTasksAction(Request $request): array return $this->serializeJudgeTasks($judgetasks, $judgehost); } + // If there is nothing else, get visualization jobs that are assigned to this host. + /** @var JudgeTask[] $judgetasks */ + $judgetasks = $this->em + ->createQueryBuilder() + ->from(JudgeTask::class, 'jt') + ->select('jt') + ->andWhere('jt.judgehost = :judgehost') + //->andWhere('jt.starttime IS NULL') + ->andWhere('jt.valid = 1') + ->andWhere('jt.type = :type') + ->setParameter('judgehost', $judgehost) + ->setParameter('type', JudgeTaskType::OUTPUT_VISUALIZATION) + ->addOrderBy('jt.priority') + ->addOrderBy('jt.judgetaskid') + ->setMaxResults(1) + ->getQuery() + ->getResult(); + if (!empty($judgetasks)) { + return $this->serializeJudgeTasks($judgetasks, $judgehost); + } + return []; } From 3a0530487fc076755a64793cb82dca972f343966 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 10:19:16 +0200 Subject: [PATCH 28/54] Store the judgetask --- webapp/src/Controller/Jury/SubmissionController.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 2629a25f9b..bf286271ab 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1332,11 +1332,20 @@ protected function createVisualization(array $judgings): void $lowestId = $run->getRunId(); } } + $submission = $judging->getSubmission(); + $executable = $submission->getProblem()->getOutputVisualizerExecutable()->getImmutableExecutable(); $judgeTask = new JudgeTask(); $judgeTask->setType('output_visualization') ->setValid(true) - ->setJobid($judgingId); + ->setJobid($judgingId) + // We need to get the Judging_run first. + //->setJudgehost($judging->getJudgeTask()->getJudgehost()) + ->setSubmission($submission) + ->setPriority(JudgeTask::PRIORITY_LOW) + ->setOutputVisualizerScriptId($executable->getImmutableExecId()) + ->setRunConfig($this->dj->jsonEncode(['hash' => $executable->getHash()])); $numRequested += 1; + $this->em->persist($judgeTask); $this->em->flush(); } } From 87ca226d809de1806882e0494e52b4e0ca0dd255 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Tue, 15 Oct 2024 22:06:07 +0200 Subject: [PATCH 29/54] Working on the judgehost side --- judge/judgedaemon.main.php | 60 +++++++++++++++++++ .../Controller/Jury/SubmissionController.php | 44 ++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 8f4a44c068..d2437644d2 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -751,10 +751,12 @@ function fetch_executable_internal( // Request open submissions to judge. Any errors will be treated as // non-fatal: we will just keep on retrying in this loop. $judging = request('judgehosts/fetch-work', 'POST', ['hostname' => $myhost], false); + var_dump($judging); // If $judging is null, an error occurred; don't try to decode. if (!is_null($judging)) { $row = dj_json_decode($judging); } + var_dump("Working judging."); // nothing returned -> no open submissions for us if (empty($row)) { @@ -776,6 +778,7 @@ function fetch_executable_internal( } continue; } + var_dump("Non empty."); // We have gotten a work packet. $endpoints[$endpointID]["waiting"] = false; @@ -784,7 +787,9 @@ function fetch_executable_internal( logmsg(LOG_INFO, "⇝ Received " . sizeof($row) . " '" . $type . "' judge tasks (endpoint $endpointID)"); + var_dump($row); $jobId = $row[0]['jobid']; + var_dump($jobId); if ($type == 'prefetch') { if ($lastWorkdir !== null) { @@ -815,9 +820,11 @@ function fetch_executable_internal( logmsg(LOG_INFO, " 🔥 Pre-heating judgehost completed."); continue; } + var_dump("Get here..."); // Create workdir for judging. $workdir = judging_directory($workdirpath, $row[0]); + var_dump($workdir); logmsg(LOG_INFO, " Working directory: $workdir"); if ($type == 'debug_info') { @@ -872,6 +879,59 @@ function fetch_executable_internal( continue; } + var_dump($row); + var_dump("Working on output_visual"); + if ($type == 'output_visualization') { + if ($lastWorkdir !== null) { + cleanup_judging($lastWorkdir); + $lastWorkdir = null; + } + foreach ($row as $judgeTask) { + if (isset($judgeTask['output_visualizer_script_id'])) { + // Visualization of output which this host judged requested. + //$run_config = dj_json_decode($judgeTask['run_config']); + $tmpfile = tempnam(TMPDIR, 'output_visualization_'); + [$runpath, $error] = fetch_executable_internal( + $workdirpath, + 'output_visualization', + $judgeTask['output_visualizer_script_id'], + $run_config['hash'] + ); + if (isset($error)) { + // FIXME + continue; + } + + $debug_cmd = implode(' ', array_map('dj_escapeshellarg', + [$runpath, $workdir, $tmpfile])); + system($debug_cmd, $retval); + // FIXME: check retval + + request( + sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost), + urlencode((string)$judgeTask['judgetaskid'])), + 'POST', + ['full_debug' => rest_encode_file($tmpfile, false)], + false + ); + unlink($tmpfile); + + logmsg(LOG_INFO, " ⇡ Uploading debug package of workdir $workdir."); + } else { + // Retrieving full team output for a particular testcase. + $testcasedir = $workdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']); + request( + sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost), + urlencode((string)$judgeTask['judgetaskid'])), + 'POST', + ['output_run' => rest_encode_file($testcasedir . '/program.out', false)], + false + ); + logmsg(LOG_INFO, " ⇡ Uploading full output of testcase $judgeTask[testcase_id]."); + } + } + continue; + } $success_file = "$workdir/.uuid_pid"; $expected_uuid_pid = $row[0]['uuid'] . '_' . (string)getmypid(); diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index bf286271ab..bb39236b3c 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -2,6 +2,7 @@ namespace App\Controller\Jury; +use Doctrine\DBAL\ArrayParameterType; use App\Controller\BaseController; use App\DataTransferObject\SubmissionRestriction; use App\Doctrine\DBAL\Types\JudgeTaskType; @@ -602,6 +603,49 @@ public function viewAction( return $this->render('jury/submission.html.twig', $twigData); } + /** + * @return JudgeTask[]|null + */ + private function getJudgetasks(string|int|null $jobId, int $max_batchsize, Judgehost $judgehost): ?array + { + if ($jobId === null) { + return null; + } + $queryBuilder = $this->em->createQueryBuilder(); + /** @var JudgeTask[] $judgetasks */ + $judgetasks = $queryBuilder + ->from(JudgeTask::class, 'jt') + ->select('jt') + ->andWhere('jt.judgehost IS NULL') + ->andWhere('jt.valid = 1') + ->andWhere('jt.jobid = :jobid') + ->andWhere('jt.type = :type') + ->addOrderBy('jt.priority') + ->addOrderBy('jt.judgetaskid') + ->setParameter('type', JudgeTaskType::JUDGING_RUN) + ->setParameter('jobid', $jobId) + ->setMaxResults($max_batchsize) + ->getQuery() + ->getResult(); + if (empty($judgetasks)) { + // TODO: There is currently a race condition when a jury member requests the remaining test cases to be + // judged in the time between allocating the final batch and the next judgehost checking in and deleting + // the queuetask here. + $this->em->createQueryBuilder() + ->from(QueueTask::class, 'qt') + ->andWhere('qt.judging = :jobid') + ->setParameter('jobid', $jobId) + ->delete() + ->getQuery() + ->execute(); + $this->em->flush(); + $this->dj->auditlog('queuetask', $jobId, 'deleted'); + } else { + return $this->serializeJudgeTasks($judgetasks, $judgehost); + } + return null; + } + #[Route(path: '/request-full-debug/{jid}', name: 'request_full_debug')] public function requestFullDebug(Request $request, Judging $jid): RedirectResponse { From 5d5d6c629ed7ffbce0907314c6a1e58e3579fa15 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Thu, 17 Oct 2024 22:23:22 +0200 Subject: [PATCH 30/54] Fix output to create the image --- .../boolfind_visual/visualize.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py index 6500632ee8..ef5aca0487 100644 --- a/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py +++ b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py @@ -8,9 +8,15 @@ my_name = sys.argv[0] my_input = sys.argv[1] #real_answer = sys.argv[2] -my_feedback = sys.argv[3] +my_feedback = sys.argv[2] - -plt.plot([map(int, x) for x in sys.stdin.readlines()]) -plt.ylabel('Guesses') -plt.savefig(f"{feedback_dir}visual.png") +with open(my_input, 'r') as f: + lines = f.readlines() + vals = [] + for line in lines: + if 'READ' in line: + vals.append(int(line.split(' ')[-1])) + print(line) + plt.plot([0,1]) + plt.ylabel('Guesses') + plt.savefig(f"{my_feedback}") From 52fde24bb98a0e55f86143b9c51237ab2d77a55a Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Thu, 17 Oct 2024 22:24:16 +0200 Subject: [PATCH 31/54] Finish This shows everything but still needs cleanup. --- judge/judgedaemon.main.php | 33 +++-- webapp/migrations/Version20241017180659.php | 42 +++++++ .../Controller/API/JudgehostController.php | 65 +++++++++- .../Controller/Jury/SubmissionController.php | 27 +++- webapp/src/Controller/Jury/UserController.php | 116 +++++++++++++++++- webapp/templates/jury/submission.html.twig | 5 + 6 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 webapp/migrations/Version20241017180659.php diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index d2437644d2..b72fae1e90 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -889,7 +889,7 @@ function fetch_executable_internal( foreach ($row as $judgeTask) { if (isset($judgeTask['output_visualizer_script_id'])) { // Visualization of output which this host judged requested. - //$run_config = dj_json_decode($judgeTask['run_config']); + $run_config = dj_json_decode($judgeTask['run_config']); $tmpfile = tempnam(TMPDIR, 'output_visualization_'); [$runpath, $error] = fetch_executable_internal( $workdirpath, @@ -902,32 +902,29 @@ function fetch_executable_internal( continue; } - $debug_cmd = implode(' ', array_map('dj_escapeshellarg', - [$runpath, $workdir, $tmpfile])); - system($debug_cmd, $retval); + $teamoutput = $workdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']) . '/1/program.out'; + //$feedbackoutput = $wVorkdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']) . '/1/feedback/visual.png'; + var_dump("Working in", $teamoutput); + $visual_cmd = implode(' ', array_map('dj_escapeshellarg', + [$runpath, $teamoutput, $tmpfile])); + var_dump($visual_cmd); + system($visual_cmd, $retval); // FIXME: check retval + var_dump("testcase", $judgeTask['testcase_id']); request( - sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost), + sprintf('judgehosts/add-visual/%s/%s', urlencode($myhost), urlencode((string)$judgeTask['judgetaskid'])), 'POST', - ['full_debug' => rest_encode_file($tmpfile, false)], + ['visual_output' => rest_encode_file($tmpfile, false), + 'testcase_id' => $judgeTask['testcase_id']], false ); + $b = rest_encode_file($tmpfile, false); + var_dump($b); unlink($tmpfile); - logmsg(LOG_INFO, " ⇡ Uploading debug package of workdir $workdir."); - } else { - // Retrieving full team output for a particular testcase. - $testcasedir = $workdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']); - request( - sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost), - urlencode((string)$judgeTask['judgetaskid'])), - 'POST', - ['output_run' => rest_encode_file($testcasedir . '/program.out', false)], - false - ); - logmsg(LOG_INFO, " ⇡ Uploading full output of testcase $judgeTask[testcase_id]."); + logmsg(LOG_INFO, " ⇡ Uploading visual output of workdir $workdir."); } } continue; diff --git a/webapp/migrations/Version20241017180659.php b/webapp/migrations/Version20241017180659.php new file mode 100644 index 0000000000..a9454a9fa3 --- /dev/null +++ b/webapp/migrations/Version20241017180659.php @@ -0,0 +1,42 @@ +addSql('CREATE TABLE visualization (visualization_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Visualization ID\', judgingid INT UNSIGNED DEFAULT NULL COMMENT \'Judging ID\', judgehostid INT UNSIGNED DEFAULT NULL COMMENT \'Judgehost ID\', testcaseid INT UNSIGNED DEFAULT NULL COMMENT \'Testcase ID\', filename VARCHAR(255) NOT NULL COMMENT \'Name of the file where we stored the visualization.\', INDEX IDX_E0936C40E0E4FC3E (judgehostid), INDEX IDX_E0936C40D360BB2B (testcaseid), INDEX judgingid (judgingid), PRIMARY KEY(visualization_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Team output visualization.\' '); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C405D5FEA72 FOREIGN KEY (judgingid) REFERENCES judging (judgingid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40E0E4FC3E FOREIGN KEY (judgehostid) REFERENCES judgehost (judgehostid) ON DELETE SET NULL'); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40D360BB2B FOREIGN KEY (testcaseid) REFERENCES testcase (testcaseid) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C405D5FEA72'); + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40E0E4FC3E'); + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40D360BB2B'); + $this->addSql('DROP TABLE visualization'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 8b3c777842..d911442c10 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -2,6 +2,9 @@ namespace App\Controller\API; +use App\Entity\Visualization; +use App\Entity\Testcase; + use App\DataTransferObject\JudgehostFile; use App\Doctrine\DBAL\Types\JudgeTaskType; use App\Entity\Contest; @@ -550,6 +553,60 @@ public function addDebugInfo( $this->em->flush(); } + /** + * Add visual team output. + */ + #[IsGranted('ROLE_JUDGEHOST')] + #[Rest\Post('/add-visual/{hostname}/{judgeTaskId<\d+>}')] + #[OA\Response(response: 200, description: 'When the visual output has been added')] + public function addVisualization( + Request $request, + #[OA\PathParameter(description: 'The hostname of the judgehost that wants to add the debug info')] + string $hostname, + #[OA\PathParameter(description: 'The ID of the judgetask to add', schema: new OA\Schema(type: 'integer'))] + int $judgeTaskId + ): void { + $judgeTask = $this->em->getRepository(JudgeTask::class)->find($judgeTaskId); + if ($judgeTask === null) { + throw new BadRequestHttpException( + 'Inconsistent data, no judgetask known with judgetaskid = ' . $judgeTaskId . '.'); + } + + foreach (['visual_output', 'testcase_id'] as $argument) { + if (!$request->request->has($argument)) { + throw new BadRequestHttpException( + sprintf("Argument '%s' is mandatory", $argument)); + } + } + + $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); + if (!$judgehost) { + throw new BadRequestHttpException("Who are you and why are you sending us any data?"); + } + + $judging = $this->em->getRepository(Judging::class)->find($judgeTask->getJobId()); + if ($judging === null) { + throw new BadRequestHttpException( + 'Inconsistent data, no judging known with judgingid = ' . $judgeTask->getJobId() . '.'); + } + if ($tempFilename = tempnam($this->dj->getDomjudgeTmpDir(), "visual-")) { + $debug_package = base64_decode($request->request->get('visual_output')); + file_put_contents($tempFilename, $debug_package); + var_dump($debug_package); + } + // FIXME: error checking + var_dump("Received", $request->request->get('testcase_id'), "Processed" ); + $testcase = $this->em->getRepository(Testcase::class)->findOneBy(['testcaseid' => $request->request->get('testcase_id')]); + $visualization = new Visualization(); + $visualization + ->setJudgehost($judgehost) + ->setJudging($judging) + ->setTestcase($testcase) + ->setFilename($tempFilename); + $this->em->persist($visualization); + $this->em->flush(); + } + /** * Add one JudgingRun. When relevant, finalize the judging. * @throws DBALException @@ -1186,7 +1243,7 @@ public function getFilesAction( return match ($type) { 'source' => $this->getSourceFiles($id), 'testcase' => $this->getTestcaseFiles($id), - 'compare', 'compile', 'debug', 'run' => $this->getExecutableFiles($id), + 'compare', 'compile', 'debug', 'run', 'output_visualization' => $this->getExecutableFiles($id), default => throw new BadRequestHttpException('Unknown type requested.'), }; } @@ -1656,7 +1713,7 @@ public function getJudgeTasksAction(Request $request): array ->createQueryBuilder() ->from(JudgeTask::class, 'jt') ->select('jt') - ->andWhere('jt.judgehost = :judgehost') + ->andWhere('jt.judgehost = :judgehost OR jt.judgehost IS NULL') //->andWhere('jt.starttime IS NULL') ->andWhere('jt.valid = 1') ->andWhere('jt.type = :type') @@ -1703,7 +1760,7 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a } $now = Utils::now(); - $numUpdated = $this->em->getConnection()->executeStatement( + $numUpdated = sizeof($judgeTasks);/*$this->em->getConnection()->executeStatement( 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', [ 'judgehostid' => $judgehost->getJudgehostid(), @@ -1718,7 +1775,7 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a if ($numUpdated == 0) { // Bad luck, some other judgehost beat us to it. return []; - } + }*/ // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. $starttime_set = $this->em->getConnection()->executeStatement( diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index bb39236b3c..57dead90d3 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -16,13 +16,14 @@ use App\Entity\JudgingRun; use App\Entity\Language; use App\Entity\Problem; +use App\Entity\QueueTask; use App\Entity\Submission; use App\Entity\SubmissionFile; use App\Entity\Team; use App\Entity\TeamAffiliation; use App\Entity\TeamCategory; use App\Entity\Testcase; -use App\Entity\QueueTask; +use App\Entity\Visualization; use App\Form\Type\SubmissionsFilterType; use App\Service\BalloonService; use App\Service\ConfigurationService; @@ -513,6 +514,14 @@ public function viewAction( ->getSingleScalarResult(); } + $visualization = null; + $createdVisualization = $this->em->getRepository(Visualization::class)->findOneBy([ + 'judging' => $selectedJudging, + ]); + if ($createdVisualization) { + $visualization = ['url' => $this->generateUrl('jury_submission_visual', ['visualId' => $createdVisualization->getVisualizationId()])]; + } + $twigData = [ 'submission' => $submission, 'lastSubmission' => $lastSubmission, @@ -536,6 +545,7 @@ public function viewAction( 'version_warnings' => [], 'isMultiPassProblem' => $submission->getProblem()->isMultipassProblem(), 'hasOutputVisualizer' => $submission->getProblem()->getOutputVisualizerExecutable() ?? false, + 'visualization' => $visualization, ]; if ($selectedJudging === null) { @@ -1385,6 +1395,7 @@ protected function createVisualization(array $judgings): void // We need to get the Judging_run first. //->setJudgehost($judging->getJudgeTask()->getJudgehost()) ->setSubmission($submission) + ->setTestcaseId($run->getTestcase()->getTestcaseId()) ->setPriority(JudgeTask::PRIORITY_LOW) ->setOutputVisualizerScriptId($executable->getImmutableExecId()) ->setRunConfig($this->dj->jsonEncode(['hash' => $executable->getHash()])); @@ -1417,4 +1428,18 @@ protected function createVisualization(array $judgings): void $this->addFlash('info', "Requested $numRequested to be visualized."); } } + + #[Route(path: '/visual/{visualId}', name: 'jury_submission_visual')] + public function visualAction( + Request $request, + ?string $visualId = null, + ): StreamedResponse { + + dump($visualId); + $visualization = $this->em->getRepository(Visualization::class)->findOneBy(['visualization_id' => $visualId]); + dump($visualization, $visualId); + $name = 'visual.j' . $visualization->getJudging()->getJudgingid() + . '.png'; + return Utils::streamAsBinaryFile(file_get_contents($visualization->getFilename()), $name); + } } diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index 5e80e683e4..fc1cb6225a 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -2,6 +2,12 @@ namespace App\Controller\Jury; +use App\Entity\Judgehost; +use App\Entity\JudgeTask; +use App\Entity\Testcase; +use App\Doctrine\DBAL\Types\JudgeTaskType; +use Doctrine\DBAL\ArrayParameterType; + use App\Controller\BaseController; use App\DataTransferObject\SubmissionRestriction; use App\Entity\Role; @@ -164,12 +170,13 @@ public function indexAction(): Response $hostname = 'Computer'; $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); + // If there is nothing else, get visualization jobs that are assigned to this host. /** @var JudgeTask[] $judgetasks */ $judgetasks = $this->em ->createQueryBuilder() ->from(JudgeTask::class, 'jt') ->select('jt') - ->andWhere('jt.judgehost = :judgehost') + ->andWhere('jt.judgehost = :judgehost OR jt.judgehost IS NULL') //->andWhere('jt.starttime IS NULL') ->andWhere('jt.valid = 1') ->andWhere('jt.type = :type') @@ -181,9 +188,11 @@ public function indexAction(): Response ->getQuery() ->getResult(); dump($judgetasks); - /*if (!empty($judgetasks)) { + if (!empty($judgetasks)) { dump($this->serializeJudgeTasks($judgetasks, $judgehost)); - }*/ + } + + dump($testcase = $this->em->getRepository(Testcase::class)->findOneBy(['testcaseid' => '6'])); return $this->render('jury/users.html.twig', [ 'users' => $users_table, @@ -191,6 +200,107 @@ public function indexAction(): Response ]); } + /** + * @param JudgeTask[] $judgeTasks + * @return JudgeTask[] + * @throws Exception + */ + private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): array + { + if (empty($judgeTasks)) { + return []; + } + + // Filter by submit_id. + $submit_id = $judgeTasks[0]->getSubmission()?->getSubmitid(); + $judgetaskids = []; + foreach ($judgeTasks as $judgeTask) { + if ($judgeTask->getType() == 'judging_run') { + if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } + } else { + // Just pick everything assigned to the judgehost itself or unassigned for now + $assignedJudgehost = $judgeTask->getJudgehost(); + if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } + } + } + dump($judgetaskids); + + $now = Utils::now(); + // We do need this, but for now it stops us from debugging. + /*$numUpdated = $this->em->getConnection()->executeStatement( + 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', + [ + 'judgehostid' => $judgehost->getJudgehostid(), + 'starttime' => $now, + 'ids' => $judgetaskids, + ], + [ + 'ids' => ArrayParameterType::INTEGER, + ] + ); + + dump($numUpdated); + if ($numUpdated == 0) { + // Bad luck, some other judgehost beat us to it. + return []; + }*/ + + // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. + $starttime_set = $this->em->getConnection()->executeStatement( + 'UPDATE judging SET starttime = :starttime WHERE judgingid = :jobid AND starttime IS NULL', + [ + 'starttime' => $now, + 'jobid' => $judgeTasks[0]->getJobId(), + ] + ); + + if ($starttime_set && $judgeTasks[0]->getType() == JudgeTaskType::JUDGING_RUN) { + /** @var Submission $submission */ + $submission = $this->em->getRepository(Submission::class)->findOneBy(['submitid' => $submit_id]); + $teamid = $submission->getTeam()->getTeamid(); + + $this->em->getConnection()->executeStatement( + 'UPDATE team SET judging_last_started = :starttime WHERE teamid = :teamid', + [ + 'starttime' => $now, + 'teamid' => $teamid, + ] + ); + } + + // Just return everything here + return $judgeTasks; + if ($numUpdated == sizeof($judgeTasks)) { + // We got everything, let's ship it! + return $judgeTasks; + } + + // A bit unlucky, we only got partially the assigned work, so query what was assigned to us. + $queryBuilder = $this->em->createQueryBuilder(); + $partialJudgeTaskIds = array_column( + $queryBuilder + ->from(JudgeTask::class, 'jt') + ->select('jt.judgetaskid') + ->andWhere('jt.judgehost = :judgehost') + ->setParameter('judgehost', $judgehost) + ->andWhere($queryBuilder->expr()->In('jt.judgetaskid', $judgetaskids)) + ->getQuery() + ->getArrayResult(), + 'judgetaskid'); + + $partialJudgeTasks = []; + foreach ($judgeTasks as $judgeTask) { + if (in_array($judgeTask->getJudgetaskid(), $partialJudgeTaskIds)) { + $partialJudgeTasks[] = $judgeTask; + } + } + return $partialJudgeTasks; + } + #[Route(path: '/{userId<\d+>}', name: 'jury_user')] public function viewAction(int $userId, SubmissionService $submissionService): Response { diff --git a/webapp/templates/jury/submission.html.twig b/webapp/templates/jury/submission.html.twig index 0883a9c1e3..cfd2e0a494 100644 --- a/webapp/templates/jury/submission.html.twig +++ b/webapp/templates/jury/submission.html.twig @@ -841,6 +841,11 @@ {% endif %} {# selectedJudging.result != 'compiler-error' #} + {% if visualization is defined %} +
Team answer visualization
+ + {% endif %} + {% endif %} {# selectedJudging is not null or externalJudgement is not null #} {% endblock %} From ac0679ca88593256b2d0872b3ff87156208602e9 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Thu, 17 Oct 2024 22:26:35 +0200 Subject: [PATCH 32/54] Add forgotten class --- webapp/src/Entity/Visualization.php | 86 +++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 webapp/src/Entity/Visualization.php diff --git a/webapp/src/Entity/Visualization.php b/webapp/src/Entity/Visualization.php new file mode 100644 index 0000000000..4bcd0971b6 --- /dev/null +++ b/webapp/src/Entity/Visualization.php @@ -0,0 +1,86 @@ + 'utf8mb4_unicode_ci', + 'charset' => 'utf8mb4', + 'comment' => 'Team output visualization.', +])] +#[ORM\Index(columns: ['judgingid'], name: 'judgingid')] +class Visualization +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(options: ['comment' => 'Visualization ID', 'unsigned' => true])] + private int $visualization_id; + + #[ORM\ManyToOne(inversedBy: 'visualizations')] + #[ORM\JoinColumn(name: 'judgingid', referencedColumnName: 'judgingid', onDelete: 'CASCADE')] + private Judging $judging; + + #[ORM\Column(options: ['comment' => 'Name of the file where we stored the visualization.'])] + private string $filename; + + #[ORM\ManyToOne] + #[ORM\JoinColumn(name: 'judgehostid', referencedColumnName: 'judgehostid', onDelete: 'SET NULL')] + private Judgehost $judgehost; + + #[ORM\ManyToOne(inversedBy: 'visualizations')] + #[ORM\JoinColumn(name: 'testcaseid', referencedColumnName: 'testcaseid', onDelete: 'CASCADE')] + private Testcase $testcase; + + public function getVisualizationId(): int + { + return $this->visualization_id; + } + + public function getJudging(): Judging + { + return $this->judging; + } + + public function setJudging(Judging $judging): Visualization + { + $this->judging = $judging; + return $this; + } + + public function getFilename(): string + { + return $this->filename; + } + + public function setFilename(string $filename): Visualization + { + $this->filename = $filename; + return $this; + } + + public function getJudgehost(): Judgehost + { + return $this->judgehost; + } + + public function setJudgehost(Judgehost $judgehost): Visualization + { + $this->judgehost = $judgehost; + return $this; + } + + public function getTestcase(): Judgehost + { + return $this->testcase; + } + + public function setTestcase(Testcase $testcase): Visualization + { + $this->testcase = $testcase; + return $this; + } +} From 1edc428f3e0acb2fbd2f858a478f7dacb1785889 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Thu, 17 Oct 2024 22:34:56 +0200 Subject: [PATCH 33/54] Fix the visualizer script --- .../boolfind/output_visualizer/boolfind_visual/visualize.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py index ef5aca0487..533164c807 100644 --- a/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py +++ b/example_problems/boolfind/output_visualizer/boolfind_visual/visualize.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 # # Invoke as: -# input answer_file feedback_dir [additional_arguments] < team_output [ > team_input ] +# answer_file feedback_file import matplotlib.pyplot as plt import sys my_name = sys.argv[0] my_input = sys.argv[1] -#real_answer = sys.argv[2] my_feedback = sys.argv[2] with open(my_input, 'r') as f: @@ -16,7 +15,6 @@ for line in lines: if 'READ' in line: vals.append(int(line.split(' ')[-1])) - print(line) plt.plot([0,1]) plt.ylabel('Guesses') - plt.savefig(f"{my_feedback}") + plt.savefig(f"{my_feedback}", format='png') From 9362b9270a3dc9af724526c9d918cdb066513dfd Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Thu, 17 Oct 2024 22:37:05 +0200 Subject: [PATCH 34/54] Cleanup the judgehost file --- judge/judgedaemon.main.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index b72fae1e90..692d05788c 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -751,12 +751,10 @@ function fetch_executable_internal( // Request open submissions to judge. Any errors will be treated as // non-fatal: we will just keep on retrying in this loop. $judging = request('judgehosts/fetch-work', 'POST', ['hostname' => $myhost], false); - var_dump($judging); // If $judging is null, an error occurred; don't try to decode. if (!is_null($judging)) { $row = dj_json_decode($judging); } - var_dump("Working judging."); // nothing returned -> no open submissions for us if (empty($row)) { @@ -778,7 +776,6 @@ function fetch_executable_internal( } continue; } - var_dump("Non empty."); // We have gotten a work packet. $endpoints[$endpointID]["waiting"] = false; @@ -787,9 +784,7 @@ function fetch_executable_internal( logmsg(LOG_INFO, "⇝ Received " . sizeof($row) . " '" . $type . "' judge tasks (endpoint $endpointID)"); - var_dump($row); $jobId = $row[0]['jobid']; - var_dump($jobId); if ($type == 'prefetch') { if ($lastWorkdir !== null) { @@ -820,11 +815,9 @@ function fetch_executable_internal( logmsg(LOG_INFO, " 🔥 Pre-heating judgehost completed."); continue; } - var_dump("Get here..."); // Create workdir for judging. $workdir = judging_directory($workdirpath, $row[0]); - var_dump($workdir); logmsg(LOG_INFO, " Working directory: $workdir"); if ($type == 'debug_info') { @@ -879,8 +872,6 @@ function fetch_executable_internal( continue; } - var_dump($row); - var_dump("Working on output_visual"); if ($type == 'output_visualization') { if ($lastWorkdir !== null) { cleanup_judging($lastWorkdir); @@ -903,14 +894,10 @@ function fetch_executable_internal( } $teamoutput = $workdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']) . '/1/program.out'; - //$feedbackoutput = $wVorkdir . "/testcase" . sprintf('%05d', $judgeTask['testcase_id']) . '/1/feedback/visual.png'; - var_dump("Working in", $teamoutput); $visual_cmd = implode(' ', array_map('dj_escapeshellarg', [$runpath, $teamoutput, $tmpfile])); - var_dump($visual_cmd); system($visual_cmd, $retval); // FIXME: check retval - var_dump("testcase", $judgeTask['testcase_id']); request( sprintf('judgehosts/add-visual/%s/%s', urlencode($myhost), @@ -920,8 +907,6 @@ function fetch_executable_internal( 'testcase_id' => $judgeTask['testcase_id']], false ); - $b = rest_encode_file($tmpfile, false); - var_dump($b); unlink($tmpfile); logmsg(LOG_INFO, " ⇡ Uploading visual output of workdir $workdir."); From 812042e3862413b95fe42dbadf8347013683b085 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 08:15:02 +0200 Subject: [PATCH 35/54] Check returncode as `FIXME`. --- judge/judgedaemon.main.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 692d05788c..ed133ef0b9 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -334,7 +334,10 @@ function fetch_executable_internal( $execrunpath = $execbuilddir . '/run'; $execrunjurypath = $execbuilddir . '/runjury'; if (!is_dir($execdir) || !file_exists($execdeploypath)) { - system('rm -rf ' . dj_escapeshellarg($execdir) . ' ' . dj_escapeshellarg($execbuilddir)); + system('rm -rf ' . dj_escapeshellarg($execdir) . ' ' . dj_escapeshellarg($execbuilddir), $retval); + if ($retval !== 0) { + logmsg(LOG_WARNING, "Deleting '$execdir' or '$execbuilddir' was unsuccessful."); + } system('mkdir -p ' . dj_escapeshellarg($execbuilddir), $retval); if ($retval !== 0) { error("Could not create directory '$execbuilddir'"); @@ -844,7 +847,9 @@ function fetch_executable_internal( $debug_cmd = implode(' ', array_map('dj_escapeshellarg', [$runpath, $workdir, $tmpfile])); system($debug_cmd, $retval); - // FIXME: check retval + if ($retval !== 0) { + error("Running '$runpath' failed."); + } request( sprintf('judgehosts/add-debug-info/%s/%s', urlencode($myhost), @@ -897,7 +902,9 @@ function fetch_executable_internal( $visual_cmd = implode(' ', array_map('dj_escapeshellarg', [$runpath, $teamoutput, $tmpfile])); system($visual_cmd, $retval); - // FIXME: check retval + if ($retval !== 0) { + error("Running '$runpath' failed."); + } request( sprintf('judgehosts/add-visual/%s/%s', urlencode($myhost), From eca9e355aefc26100e3e13ec4cfa40d6a8b26739 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 08:28:25 +0200 Subject: [PATCH 36/54] Merge migrations --- webapp/migrations/Version20241013101907.php | 42 ---------------- webapp/migrations/Version20241014163343.php | 38 -------------- webapp/migrations/Version20241014185658.php | 38 -------------- webapp/migrations/Version20241015085315.php | 36 ------------- webapp/migrations/Version20241015085654.php | 36 ------------- webapp/migrations/Version20241017180659.php | 42 ---------------- webapp/migrations/Version20241018061817.php | 56 +++++++++++++++++++++ 7 files changed, 56 insertions(+), 232 deletions(-) delete mode 100644 webapp/migrations/Version20241013101907.php delete mode 100644 webapp/migrations/Version20241014163343.php delete mode 100644 webapp/migrations/Version20241014185658.php delete mode 100644 webapp/migrations/Version20241015085315.php delete mode 100644 webapp/migrations/Version20241015085654.php delete mode 100644 webapp/migrations/Version20241017180659.php create mode 100644 webapp/migrations/Version20241018061817.php diff --git a/webapp/migrations/Version20241013101907.php b/webapp/migrations/Version20241013101907.php deleted file mode 100644 index 074d4bba91..0000000000 --- a/webapp/migrations/Version20241013101907.php +++ /dev/null @@ -1,42 +0,0 @@ -addSql('ALTER TABLE executable DROP zipfile'); - $this->addSql('ALTER TABLE problem ADD special_output_visualizer VARCHAR(32) DEFAULT NULL COMMENT \'Executable ID (string)\', CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds; defaults to 1 for traditional problems, 2 for multi-pass problems if not specified.\''); - $this->addSql('ALTER TABLE problem ADD CONSTRAINT FK_D7E7CCC819F5352E FOREIGN KEY (special_output_visualizer) REFERENCES executable (execid) ON DELETE SET NULL'); - $this->addSql('CREATE INDEX special_output_visualizer ON problem (special_output_visualizer)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); - $this->addSql('ALTER TABLE problem DROP FOREIGN KEY FK_D7E7CCC819F5352E'); - $this->addSql('DROP INDEX special_output_visualizer ON problem'); - $this->addSql('ALTER TABLE problem DROP special_output_visualizer, CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\''); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241014163343.php b/webapp/migrations/Version20241014163343.php deleted file mode 100644 index 14a529e8dd..0000000000 --- a/webapp/migrations/Version20241014163343.php +++ /dev/null @@ -1,38 +0,0 @@ -addSql('ALTER TABLE executable DROP zipfile'); - $this->addSql('ALTER TABLE judging ADD visualization TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Explicitly requested to visualize the output.\''); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); - $this->addSql('ALTER TABLE judging DROP visualization'); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241014185658.php b/webapp/migrations/Version20241014185658.php deleted file mode 100644 index 7bb7a07231..0000000000 --- a/webapp/migrations/Version20241014185658.php +++ /dev/null @@ -1,38 +0,0 @@ -addSql('ALTER TABLE executable DROP zipfile'); - $this->addSql('ALTER TABLE judgetask ADD output_visualizer_script_id INT UNSIGNED DEFAULT NULL COMMENT \'Output visualizer script ID\''); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - //$this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); - $this->addSql('ALTER TABLE judgetask DROP output_visualizer_script_id'); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241015085315.php b/webapp/migrations/Version20241015085315.php deleted file mode 100644 index 2ffdba1775..0000000000 --- a/webapp/migrations/Version20241015085315.php +++ /dev/null @@ -1,36 +0,0 @@ -addSql('ALTER TABLE executable DROP zipfile'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE executable ADD zipfile LONGBLOB DEFAULT NULL COMMENT \'Zip file\''); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241015085654.php b/webapp/migrations/Version20241015085654.php deleted file mode 100644 index 9ac0aa7052..0000000000 --- a/webapp/migrations/Version20241015085654.php +++ /dev/null @@ -1,36 +0,0 @@ -addSql('ALTER TABLE `judgetask` - MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\', \'output_visualization\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); - } - - public function down(Schema $schema): void - { - $this->addSql('ALTER TABLE `judgetask` - MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241017180659.php b/webapp/migrations/Version20241017180659.php deleted file mode 100644 index a9454a9fa3..0000000000 --- a/webapp/migrations/Version20241017180659.php +++ /dev/null @@ -1,42 +0,0 @@ -addSql('CREATE TABLE visualization (visualization_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Visualization ID\', judgingid INT UNSIGNED DEFAULT NULL COMMENT \'Judging ID\', judgehostid INT UNSIGNED DEFAULT NULL COMMENT \'Judgehost ID\', testcaseid INT UNSIGNED DEFAULT NULL COMMENT \'Testcase ID\', filename VARCHAR(255) NOT NULL COMMENT \'Name of the file where we stored the visualization.\', INDEX IDX_E0936C40E0E4FC3E (judgehostid), INDEX IDX_E0936C40D360BB2B (testcaseid), INDEX judgingid (judgingid), PRIMARY KEY(visualization_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Team output visualization.\' '); - $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C405D5FEA72 FOREIGN KEY (judgingid) REFERENCES judging (judgingid) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40E0E4FC3E FOREIGN KEY (judgehostid) REFERENCES judgehost (judgehostid) ON DELETE SET NULL'); - $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40D360BB2B FOREIGN KEY (testcaseid) REFERENCES testcase (testcaseid) ON DELETE CASCADE'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C405D5FEA72'); - $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40E0E4FC3E'); - $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40D360BB2B'); - $this->addSql('DROP TABLE visualization'); - } - - public function isTransactional(): bool - { - return false; - } -} diff --git a/webapp/migrations/Version20241018061817.php b/webapp/migrations/Version20241018061817.php new file mode 100644 index 0000000000..7cd3b8c283 --- /dev/null +++ b/webapp/migrations/Version20241018061817.php @@ -0,0 +1,56 @@ +addSql('ALTER TABLE problem ADD special_output_visualizer VARCHAR(32) DEFAULT NULL COMMENT \'Executable ID (string)\', CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds; defaults to 1 for traditional problems, 2 for multi-pass problems if not specified.\''); + $this->addSql('ALTER TABLE problem ADD CONSTRAINT FK_D7E7CCC819F5352E FOREIGN KEY (special_output_visualizer) REFERENCES executable (execid) ON DELETE SET NULL'); + $this->addSql('CREATE INDEX special_output_visualizer ON problem (special_output_visualizer)'); + $this->addSql('ALTER TABLE judgetask ADD output_visualizer_script_id INT UNSIGNED DEFAULT NULL COMMENT \'Output visualizer script ID\''); + $this->addSql('ALTER TABLE `judgetask` + MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\', \'output_visualization\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); + $this->addSql('ALTER TABLE judging ADD visualization TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Explicitly requested to visualize the output.\''); + $this->addSql('CREATE TABLE visualization (visualization_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Visualization ID\', judgingid INT UNSIGNED DEFAULT NULL COMMENT \'Judging ID\', judgehostid INT UNSIGNED DEFAULT NULL COMMENT \'Judgehost ID\', testcaseid INT UNSIGNED DEFAULT NULL COMMENT \'Testcase ID\', filename VARCHAR(255) NOT NULL COMMENT \'Name of the file where we stored the visualization.\', INDEX IDX_E0936C40E0E4FC3E (judgehostid), INDEX IDX_E0936C40D360BB2B (testcaseid), INDEX judgingid (judgingid), PRIMARY KEY(visualization_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB COMMENT = \'Team output visualization.\' '); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C405D5FEA72 FOREIGN KEY (judgingid) REFERENCES judging (judgingid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40E0E4FC3E FOREIGN KEY (judgehostid) REFERENCES judgehost (judgehostid) ON DELETE SET NULL'); + $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40D360BB2B FOREIGN KEY (testcaseid) REFERENCES testcase (testcaseid) ON DELETE CASCADE'); + + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C405D5FEA72'); + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40E0E4FC3E'); + $this->addSql('ALTER TABLE visualization DROP FOREIGN KEY FK_E0936C40D360BB2B'); + $this->addSql('DROP TABLE visualization'); + $this->addSql('ALTER TABLE judging DROP visualization'); + $this->addSql('ALTER TABLE `judgetask` + MODIFY COLUMN `type` ENUM(\'judging_run\', \'generic_task\', \'config_check\', \'debug_info\', \'prefetch\') DEFAULT \'judging_run\' NOT NULL COMMENT \'Type of the judge task.(DC2Type:judge_task_type)\''); + $this->addSql('ALTER TABLE judgetask DROP output_visualizer_script_id'); + $this->addSql('ALTER TABLE problem DROP FOREIGN KEY FK_D7E7CCC819F5352E'); + $this->addSql('DROP INDEX special_output_visualizer ON problem'); + $this->addSql('ALTER TABLE problem DROP special_output_visualizer, CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\''); + + } + + public function isTransactional(): bool + { + return false; + } +} From de746eb3b2cf31f97ab3076baeb0423a379b590b Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 12:36:06 +0200 Subject: [PATCH 37/54] Cleanup unrelated testing --- .../Controller/API/JudgehostController.php | 2 -- .../Controller/Jury/SubmissionController.php | 2 -- webapp/src/Controller/Jury/UserController.php | 27 ------------------- 3 files changed, 31 deletions(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index d911442c10..9500ec2147 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -592,10 +592,8 @@ public function addVisualization( if ($tempFilename = tempnam($this->dj->getDomjudgeTmpDir(), "visual-")) { $debug_package = base64_decode($request->request->get('visual_output')); file_put_contents($tempFilename, $debug_package); - var_dump($debug_package); } // FIXME: error checking - var_dump("Received", $request->request->get('testcase_id'), "Processed" ); $testcase = $this->em->getRepository(Testcase::class)->findOneBy(['testcaseid' => $request->request->get('testcase_id')]); $visualization = new Visualization(); $visualization diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 57dead90d3..fd9ce7eac6 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1435,9 +1435,7 @@ public function visualAction( ?string $visualId = null, ): StreamedResponse { - dump($visualId); $visualization = $this->em->getRepository(Visualization::class)->findOneBy(['visualization_id' => $visualId]); - dump($visualization, $visualId); $name = 'visual.j' . $visualization->getJudging()->getJudgingid() . '.png'; return Utils::streamAsBinaryFile(file_get_contents($visualization->getFilename()), $name); diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index fc1cb6225a..be865e1926 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -167,33 +167,6 @@ public function indexAction(): Response ]; } - $hostname = 'Computer'; - $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); - - // If there is nothing else, get visualization jobs that are assigned to this host. - /** @var JudgeTask[] $judgetasks */ - $judgetasks = $this->em - ->createQueryBuilder() - ->from(JudgeTask::class, 'jt') - ->select('jt') - ->andWhere('jt.judgehost = :judgehost OR jt.judgehost IS NULL') - //->andWhere('jt.starttime IS NULL') - ->andWhere('jt.valid = 1') - ->andWhere('jt.type = :type') - ->setParameter('judgehost', $judgehost) - ->setParameter('type', JudgeTaskType::OUTPUT_VISUALIZATION) - ->addOrderBy('jt.priority') - ->addOrderBy('jt.judgetaskid') - ->setMaxResults(1) - ->getQuery() - ->getResult(); - dump($judgetasks); - if (!empty($judgetasks)) { - dump($this->serializeJudgeTasks($judgetasks, $judgehost)); - } - - dump($testcase = $this->em->getRepository(Testcase::class)->findOneBy(['testcaseid' => '6'])); - return $this->render('jury/users.html.twig', [ 'users' => $users_table, 'table_fields' => $table_fields, From 70ee0e4ee64e19ce3fbc3b14eccadec1440602ac Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:54:26 +0200 Subject: [PATCH 38/54] Update webapp/src/Entity/Visualization.php --- webapp/src/Entity/Visualization.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Entity/Visualization.php b/webapp/src/Entity/Visualization.php index 4bcd0971b6..a6057b6e33 100644 --- a/webapp/src/Entity/Visualization.php +++ b/webapp/src/Entity/Visualization.php @@ -73,7 +73,7 @@ public function setJudgehost(Judgehost $judgehost): Visualization return $this; } - public function getTestcase(): Judgehost + public function getTestcase(): Testcase { return $this->testcase; } From 7bcac2b9f4c945fd1f830b6eff280431a14bb1f4 Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:54:34 +0200 Subject: [PATCH 39/54] Update webapp/src/Controller/API/JudgehostController.php --- webapp/src/Controller/API/JudgehostController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 9500ec2147..a78d59a8bc 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1773,7 +1773,7 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a if ($numUpdated == 0) { // Bad luck, some other judgehost beat us to it. return []; - }*/ + } // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. $starttime_set = $this->em->getConnection()->executeStatement( From 0a7dacaa3b1e28830cba02055aa030fe3322e267 Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:54:43 +0200 Subject: [PATCH 40/54] Update webapp/migrations/Version20241018061817.php --- webapp/migrations/Version20241018061817.php | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/migrations/Version20241018061817.php b/webapp/migrations/Version20241018061817.php index 7cd3b8c283..524f1c679e 100644 --- a/webapp/migrations/Version20241018061817.php +++ b/webapp/migrations/Version20241018061817.php @@ -46,7 +46,6 @@ public function down(Schema $schema): void $this->addSql('ALTER TABLE problem DROP FOREIGN KEY FK_D7E7CCC819F5352E'); $this->addSql('DROP INDEX special_output_visualizer ON problem'); $this->addSql('ALTER TABLE problem DROP special_output_visualizer, CHANGE multipass_limit multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\''); - } public function isTransactional(): bool From 7190795dd85e55218b7b6dbb49c5f4594da52ae6 Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:54:50 +0200 Subject: [PATCH 41/54] Update webapp/migrations/Version20241018061817.php --- webapp/migrations/Version20241018061817.php | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/migrations/Version20241018061817.php b/webapp/migrations/Version20241018061817.php index 524f1c679e..2a58d7d474 100644 --- a/webapp/migrations/Version20241018061817.php +++ b/webapp/migrations/Version20241018061817.php @@ -30,7 +30,6 @@ public function up(Schema $schema): void $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C405D5FEA72 FOREIGN KEY (judgingid) REFERENCES judging (judgingid) ON DELETE CASCADE'); $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40E0E4FC3E FOREIGN KEY (judgehostid) REFERENCES judgehost (judgehostid) ON DELETE SET NULL'); $this->addSql('ALTER TABLE visualization ADD CONSTRAINT FK_E0936C40D360BB2B FOREIGN KEY (testcaseid) REFERENCES testcase (testcaseid) ON DELETE CASCADE'); - } public function down(Schema $schema): void From c424d21c4a7d9a3378bc0c70a49cbcec5ea5776b Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:54:56 +0200 Subject: [PATCH 42/54] Update webapp/src/Controller/API/JudgehostController.php --- webapp/src/Controller/API/JudgehostController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index a78d59a8bc..5966a442a9 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1532,7 +1532,7 @@ public function getJudgeTasksAction(Request $request): array /*if (!$request->request->has('hostname')) { throw new BadRequestHttpException('Argument \'hostname\' is mandatory'); } - $hostname = $request->request->get('hostname');*/ + $hostname = $request->request->get('hostname'); $hostname = 'Computer'; $judgehost = $this->em->getRepository(Judgehost::class)->findOneBy(['hostname' => $hostname]); From fa3e2ed17ddd5ba023f06a07493fe7a8e0c46065 Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:55:02 +0200 Subject: [PATCH 43/54] Update webapp/src/Controller/API/JudgehostController.php --- webapp/src/Controller/API/JudgehostController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 5966a442a9..9dd1bb8343 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1529,7 +1529,7 @@ private function getTestcaseFiles(string $id): array )] public function getJudgeTasksAction(Request $request): array { - /*if (!$request->request->has('hostname')) { + if (!$request->request->has('hostname')) { throw new BadRequestHttpException('Argument \'hostname\' is mandatory'); } $hostname = $request->request->get('hostname'); From 7641e1717c0f5098296abc2a21a2f2fa6d0e5f9b Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:55:07 +0200 Subject: [PATCH 44/54] Update webapp/src/Controller/API/JudgehostController.php --- webapp/src/Controller/API/JudgehostController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index 9dd1bb8343..9a4e7b371f 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -1758,7 +1758,7 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a } $now = Utils::now(); - $numUpdated = sizeof($judgeTasks);/*$this->em->getConnection()->executeStatement( + $numUpdated = $this->em->getConnection()->executeStatement( 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', [ 'judgehostid' => $judgehost->getJudgehostid(), From 5b9c97014e401f1bb97fb7fd185d2a7c5ca54442 Mon Sep 17 00:00:00 2001 From: MCJ Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:55:12 +0200 Subject: [PATCH 45/54] Update webapp/src/Controller/Jury/UserController.php --- webapp/src/Controller/Jury/UserController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index be865e1926..2295c7ed15 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -246,7 +246,6 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a } // Just return everything here - return $judgeTasks; if ($numUpdated == sizeof($judgeTasks)) { // We got everything, let's ship it! return $judgeTasks; From 031d24a67ef34b6c7927dd87367d9c37772a8c8c Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:03:27 +0200 Subject: [PATCH 46/54] Remove dump and debug statements --- webapp/src/Controller/Jury/UserController.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index 2295c7ed15..b91c52ddea 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -200,11 +200,10 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a } } } - dump($judgetaskids); $now = Utils::now(); // We do need this, but for now it stops us from debugging. - /*$numUpdated = $this->em->getConnection()->executeStatement( + $numUpdated = $this->em->getConnection()->executeStatement( 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', [ 'judgehostid' => $judgehost->getJudgehostid(), @@ -216,11 +215,10 @@ private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): a ] ); - dump($numUpdated); if ($numUpdated == 0) { // Bad luck, some other judgehost beat us to it. return []; - }*/ + } // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. $starttime_set = $this->em->getConnection()->executeStatement( From 51d3cc35940fec913244dc3c234864d25873d274 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:09:17 +0200 Subject: [PATCH 47/54] Cleanup unused code --- webapp/src/Controller/Jury/UserController.php | 98 ------------------- 1 file changed, 98 deletions(-) diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index b91c52ddea..cbcee2bbde 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -173,104 +173,6 @@ public function indexAction(): Response ]); } - /** - * @param JudgeTask[] $judgeTasks - * @return JudgeTask[] - * @throws Exception - */ - private function serializeJudgeTasks(array $judgeTasks, Judgehost $judgehost): array - { - if (empty($judgeTasks)) { - return []; - } - - // Filter by submit_id. - $submit_id = $judgeTasks[0]->getSubmission()?->getSubmitid(); - $judgetaskids = []; - foreach ($judgeTasks as $judgeTask) { - if ($judgeTask->getType() == 'judging_run') { - if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { - $judgetaskids[] = $judgeTask->getJudgetaskid(); - } - } else { - // Just pick everything assigned to the judgehost itself or unassigned for now - $assignedJudgehost = $judgeTask->getJudgehost(); - if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) { - $judgetaskids[] = $judgeTask->getJudgetaskid(); - } - } - } - - $now = Utils::now(); - // We do need this, but for now it stops us from debugging. - $numUpdated = $this->em->getConnection()->executeStatement( - 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', - [ - 'judgehostid' => $judgehost->getJudgehostid(), - 'starttime' => $now, - 'ids' => $judgetaskids, - ], - [ - 'ids' => ArrayParameterType::INTEGER, - ] - ); - - if ($numUpdated == 0) { - // Bad luck, some other judgehost beat us to it. - return []; - } - - // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. - $starttime_set = $this->em->getConnection()->executeStatement( - 'UPDATE judging SET starttime = :starttime WHERE judgingid = :jobid AND starttime IS NULL', - [ - 'starttime' => $now, - 'jobid' => $judgeTasks[0]->getJobId(), - ] - ); - - if ($starttime_set && $judgeTasks[0]->getType() == JudgeTaskType::JUDGING_RUN) { - /** @var Submission $submission */ - $submission = $this->em->getRepository(Submission::class)->findOneBy(['submitid' => $submit_id]); - $teamid = $submission->getTeam()->getTeamid(); - - $this->em->getConnection()->executeStatement( - 'UPDATE team SET judging_last_started = :starttime WHERE teamid = :teamid', - [ - 'starttime' => $now, - 'teamid' => $teamid, - ] - ); - } - - // Just return everything here - if ($numUpdated == sizeof($judgeTasks)) { - // We got everything, let's ship it! - return $judgeTasks; - } - - // A bit unlucky, we only got partially the assigned work, so query what was assigned to us. - $queryBuilder = $this->em->createQueryBuilder(); - $partialJudgeTaskIds = array_column( - $queryBuilder - ->from(JudgeTask::class, 'jt') - ->select('jt.judgetaskid') - ->andWhere('jt.judgehost = :judgehost') - ->setParameter('judgehost', $judgehost) - ->andWhere($queryBuilder->expr()->In('jt.judgetaskid', $judgetaskids)) - ->getQuery() - ->getArrayResult(), - 'judgetaskid'); - - $partialJudgeTasks = []; - foreach ($judgeTasks as $judgeTask) { - if (in_array($judgeTask->getJudgetaskid(), $partialJudgeTaskIds)) { - $partialJudgeTasks[] = $judgeTask; - } - } - return $partialJudgeTasks; - } - #[Route(path: '/{userId<\d+>}', name: 'jury_user')] public function viewAction(int $userId, SubmissionService $submissionService): Response { From d34fce397b8943c592ea9356a3ec55ae313a1329 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:15:24 +0200 Subject: [PATCH 48/54] Fix missing `$run` --- webapp/src/Controller/Jury/SubmissionController.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index fd9ce7eac6..273a1cd640 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1392,10 +1392,9 @@ protected function createVisualization(array $judgings): void $judgeTask->setType('output_visualization') ->setValid(true) ->setJobid($judgingId) - // We need to get the Judging_run first. - //->setJudgehost($judging->getJudgeTask()->getJudgehost()) + ->setJudgehost($judging->getJudgeTask()->getJudgehost()) ->setSubmission($submission) - ->setTestcaseId($run->getTestcase()->getTestcaseId()) + ->setTestcaseId($tmpRun->getTestcase()->getTestcaseId()) ->setPriority(JudgeTask::PRIORITY_LOW) ->setOutputVisualizerScriptId($executable->getImmutableExecId()) ->setRunConfig($this->dj->jsonEncode(['hash' => $executable->getHash()])); @@ -1408,16 +1407,16 @@ protected function createVisualization(array $judgings): void if ($inProgress !== []) { $this->addFlash('warning', 'Please be patient, this visualization is still in progress.'); } - /*if ($alreadyRequested != []) { + if ($alreadyRequested != []) { $this->addFlash('warning', 'This visualization was already requested to be judged completely.'); - }*/ + } } else { if ($inProgress !== []) { $this->addFlash('warning', sprintf('Please be patient, these visualizations are still in progress: %s', implode(', ', $inProgress))); } - /*if ($alreadyRequested != []) { + if ($alreadyRequested != []) { $this->addFlash('warning', sprintf('These judgings were already requested to be judged completely: %s', implode(', ', $alreadyRequested))); - }*/ + } if ($invalidJudgings !== []) { $this->addFlash('warning', sprintf('These visualizations were skipped as the judgings were superseded by other judgings: %s', implode(', ', $invalidJudgings))); } From d616057fd8c7893aaa7f21491943817452b9c283 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:35:13 +0200 Subject: [PATCH 49/54] Prevent duplication of code --- .../API/SerializeJudgeTasksTrait.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 webapp/src/Controller/API/SerializeJudgeTasksTrait.php diff --git a/webapp/src/Controller/API/SerializeJudgeTasksTrait.php b/webapp/src/Controller/API/SerializeJudgeTasksTrait.php new file mode 100644 index 0000000000..bc3e7b59b0 --- /dev/null +++ b/webapp/src/Controller/API/SerializeJudgeTasksTrait.php @@ -0,0 +1,103 @@ +getSubmission()?->getSubmitid(); + $judgetaskids = []; + foreach ($judgeTasks as $judgeTask) { + if ($judgeTask->getType() == 'judging_run') { + if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } + } else { + // Just pick everything assigned to the judgehost itself or unassigned for now + $assignedJudgehost = $judgeTask->getJudgehost(); + if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) { + $judgetaskids[] = $judgeTask->getJudgetaskid(); + } + } + } + + $now = Utils::now(); + $numUpdated = $this->em->getConnection()->executeStatement( + 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', + [ + 'judgehostid' => $judgehost->getJudgehostid(), + 'starttime' => $now, + 'ids' => $judgetaskids, + ], + [ + 'ids' => ArrayParameterType::INTEGER, + ] + ); + + if ($numUpdated == 0) { + // Bad luck, some other judgehost beat us to it. + return []; + } + + // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. + $starttime_set = $this->em->getConnection()->executeStatement( + 'UPDATE judging SET starttime = :starttime WHERE judgingid = :jobid AND starttime IS NULL', + [ + 'starttime' => $now, + 'jobid' => $judgeTasks[0]->getJobId(), + ] + ); + + if ($starttime_set && $judgeTasks[0]->getType() == JudgeTaskType::JUDGING_RUN) { + /** @var Submission $submission */ + $submission = $this->em->getRepository(Submission::class)->findOneBy(['submitid' => $submit_id]); + $teamid = $submission->getTeam()->getTeamid(); + + $this->em->getConnection()->executeStatement( + 'UPDATE team SET judging_last_started = :starttime WHERE teamid = :teamid', + [ + 'starttime' => $now, + 'teamid' => $teamid, + ] + ); + } + + if ($numUpdated == sizeof($judgeTasks)) { + // We got everything, let's ship it! + return $judgeTasks; + } + + // A bit unlucky, we only got partially the assigned work, so query what was assigned to us. + $queryBuilder = $this->em->createQueryBuilder(); + $partialJudgeTaskIds = array_column( + $queryBuilder + ->from(JudgeTask::class, 'jt') + ->select('jt.judgetaskid') + ->andWhere('jt.judgehost = :judgehost') + ->setParameter('judgehost', $judgehost) + ->andWhere($queryBuilder->expr()->In('jt.judgetaskid', $judgetaskids)) + ->getQuery() + ->getArrayResult(), + 'judgetaskid'); + + $partialJudgeTasks = []; + foreach ($judgeTasks as $judgeTask) { + if (in_array($judgeTask->getJudgetaskid(), $partialJudgeTaskIds)) { + $partialJudgeTasks[] = $judgeTask; + } + } + return $partialJudgeTasks; + } + +} From ed5e47cb3617d7267186367a8d0143e4dbf97ca7 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:37:21 +0200 Subject: [PATCH 50/54] Not used --- .../API/SerializeJudgeTasksTrait.php | 103 ------------------ 1 file changed, 103 deletions(-) delete mode 100644 webapp/src/Controller/API/SerializeJudgeTasksTrait.php diff --git a/webapp/src/Controller/API/SerializeJudgeTasksTrait.php b/webapp/src/Controller/API/SerializeJudgeTasksTrait.php deleted file mode 100644 index bc3e7b59b0..0000000000 --- a/webapp/src/Controller/API/SerializeJudgeTasksTrait.php +++ /dev/null @@ -1,103 +0,0 @@ -getSubmission()?->getSubmitid(); - $judgetaskids = []; - foreach ($judgeTasks as $judgeTask) { - if ($judgeTask->getType() == 'judging_run') { - if ($judgeTask->getSubmission()?->getSubmitid() == $submit_id) { - $judgetaskids[] = $judgeTask->getJudgetaskid(); - } - } else { - // Just pick everything assigned to the judgehost itself or unassigned for now - $assignedJudgehost = $judgeTask->getJudgehost(); - if ($assignedJudgehost === $judgehost || $assignedJudgehost === null) { - $judgetaskids[] = $judgeTask->getJudgetaskid(); - } - } - } - - $now = Utils::now(); - $numUpdated = $this->em->getConnection()->executeStatement( - 'UPDATE judgetask SET judgehostid = :judgehostid, starttime = :starttime WHERE starttime IS NULL AND valid = 1 AND judgetaskid IN (:ids)', - [ - 'judgehostid' => $judgehost->getJudgehostid(), - 'starttime' => $now, - 'ids' => $judgetaskids, - ], - [ - 'ids' => ArrayParameterType::INTEGER, - ] - ); - - if ($numUpdated == 0) { - // Bad luck, some other judgehost beat us to it. - return []; - } - - // We got at least one, let's update the starttime of the corresponding judging if haven't done so in the past. - $starttime_set = $this->em->getConnection()->executeStatement( - 'UPDATE judging SET starttime = :starttime WHERE judgingid = :jobid AND starttime IS NULL', - [ - 'starttime' => $now, - 'jobid' => $judgeTasks[0]->getJobId(), - ] - ); - - if ($starttime_set && $judgeTasks[0]->getType() == JudgeTaskType::JUDGING_RUN) { - /** @var Submission $submission */ - $submission = $this->em->getRepository(Submission::class)->findOneBy(['submitid' => $submit_id]); - $teamid = $submission->getTeam()->getTeamid(); - - $this->em->getConnection()->executeStatement( - 'UPDATE team SET judging_last_started = :starttime WHERE teamid = :teamid', - [ - 'starttime' => $now, - 'teamid' => $teamid, - ] - ); - } - - if ($numUpdated == sizeof($judgeTasks)) { - // We got everything, let's ship it! - return $judgeTasks; - } - - // A bit unlucky, we only got partially the assigned work, so query what was assigned to us. - $queryBuilder = $this->em->createQueryBuilder(); - $partialJudgeTaskIds = array_column( - $queryBuilder - ->from(JudgeTask::class, 'jt') - ->select('jt.judgetaskid') - ->andWhere('jt.judgehost = :judgehost') - ->setParameter('judgehost', $judgehost) - ->andWhere($queryBuilder->expr()->In('jt.judgetaskid', $judgetaskids)) - ->getQuery() - ->getArrayResult(), - 'judgetaskid'); - - $partialJudgeTasks = []; - foreach ($judgeTasks as $judgeTask) { - if (in_array($judgeTask->getJudgetaskid(), $partialJudgeTaskIds)) { - $partialJudgeTasks[] = $judgeTask; - } - } - return $partialJudgeTasks; - } - -} From f9de23c2c719e16548df218abced6eb3535ef9c3 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 21:43:02 +0200 Subject: [PATCH 51/54] Fix W3C test --- .../Controller/Jury/SubmissionController.php | 43 ------------------- webapp/templates/jury/submission.html.twig | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 273a1cd640..3fa96bd7c7 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -613,49 +613,6 @@ public function viewAction( return $this->render('jury/submission.html.twig', $twigData); } - /** - * @return JudgeTask[]|null - */ - private function getJudgetasks(string|int|null $jobId, int $max_batchsize, Judgehost $judgehost): ?array - { - if ($jobId === null) { - return null; - } - $queryBuilder = $this->em->createQueryBuilder(); - /** @var JudgeTask[] $judgetasks */ - $judgetasks = $queryBuilder - ->from(JudgeTask::class, 'jt') - ->select('jt') - ->andWhere('jt.judgehost IS NULL') - ->andWhere('jt.valid = 1') - ->andWhere('jt.jobid = :jobid') - ->andWhere('jt.type = :type') - ->addOrderBy('jt.priority') - ->addOrderBy('jt.judgetaskid') - ->setParameter('type', JudgeTaskType::JUDGING_RUN) - ->setParameter('jobid', $jobId) - ->setMaxResults($max_batchsize) - ->getQuery() - ->getResult(); - if (empty($judgetasks)) { - // TODO: There is currently a race condition when a jury member requests the remaining test cases to be - // judged in the time between allocating the final batch and the next judgehost checking in and deleting - // the queuetask here. - $this->em->createQueryBuilder() - ->from(QueueTask::class, 'qt') - ->andWhere('qt.judging = :jobid') - ->setParameter('jobid', $jobId) - ->delete() - ->getQuery() - ->execute(); - $this->em->flush(); - $this->dj->auditlog('queuetask', $jobId, 'deleted'); - } else { - return $this->serializeJudgeTasks($judgetasks, $judgehost); - } - return null; - } - #[Route(path: '/request-full-debug/{jid}', name: 'request_full_debug')] public function requestFullDebug(Request $request, Judging $jid): RedirectResponse { diff --git a/webapp/templates/jury/submission.html.twig b/webapp/templates/jury/submission.html.twig index cfd2e0a494..bcccd7bdff 100644 --- a/webapp/templates/jury/submission.html.twig +++ b/webapp/templates/jury/submission.html.twig @@ -841,7 +841,7 @@ {% endif %} {# selectedJudging.result != 'compiler-error' #} - {% if visualization is defined %} + {% if visualization is defined and visualization %}
Team answer visualization
{% endif %} From f7e7e23616a6f721241740a47ff54cfcb92c2673 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 22:51:58 +0200 Subject: [PATCH 52/54] Fixup --- webapp/src/Controller/Jury/SubmissionController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 3fa96bd7c7..ece87dce20 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1349,7 +1349,7 @@ protected function createVisualization(array $judgings): void $judgeTask->setType('output_visualization') ->setValid(true) ->setJobid($judgingId) - ->setJudgehost($judging->getJudgeTask()->getJudgehost()) + ->setJudgehost($tmpRun->getJudgeTask()->getJudgehost()) ->setSubmission($submission) ->setTestcaseId($tmpRun->getTestcase()->getTestcaseId()) ->setPriority(JudgeTask::PRIORITY_LOW) From 28c3904450f6f24f68699bec335292fbbddcbdc9 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 22:52:43 +0200 Subject: [PATCH 53/54] Add last forgotten code --- webapp/src/Controller/Jury/SubmissionController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index ece87dce20..9d3c3ef33e 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1324,8 +1324,8 @@ protected function createVisualization(array $judgings): void $numRequested = 0; foreach ($judgings as $judging) { $judgingId = $judging->getJudgingid(); - /*elseif ($judging->getVisualization()) { - $alreadyRequested[] = $judgingId;*/ + elseif ($judging->getVisualization()) { + $alreadyRequested[] = $judgingId; if ($judging->getResult() === null) { $inProgress[] = $judgingId; } elseif (!$judging->getValid()) { From 2b6afcd7bac62f67f2691610891961c3c7051b86 Mon Sep 17 00:00:00 2001 From: Michael Vasseur Date: Fri, 18 Oct 2024 23:18:29 +0200 Subject: [PATCH 54/54] Fixup --- webapp/src/Controller/Jury/SubmissionController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 9d3c3ef33e..97721fdb62 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -1324,10 +1324,10 @@ protected function createVisualization(array $judgings): void $numRequested = 0; foreach ($judgings as $judging) { $judgingId = $judging->getJudgingid(); - elseif ($judging->getVisualization()) { - $alreadyRequested[] = $judgingId; if ($judging->getResult() === null) { $inProgress[] = $judgingId; + } elseif ($judging->getVisualization()) { + $alreadyRequested[] = $judgingId; } elseif (!$judging->getValid()) { $invalidJudgings[] = $judgingId; } else {