Skip to content

Commit 2c2174e

Browse files
committed
Minimal changes for multi-pass problem support.
Note that this does not include support for interactive multi-pass problems. Currently, we only upload the result of the last pass of each test case run, which we should obviously fix in the long run but it's easier to do in separate changes. General overview of multi-pass problems: A multi-pass problem requires a validator and that validator is in charge of deciding whether another pass is being run. In that case, the file `feedbackdir/nextpass.in` will contain the input of the next pass. The problem specification includes an upper limit of passes (most often 2), and if the validator generates more passes than the limit, it is treated as internal error. Progress towards #2307. Tested with RagnarGrootKoerkamp/BAPCtools#393: ``` [Sep 28 09:49:05.007] judgedaemon[301583]: Judge started on goo-0 [DOMjudge/8.4.0DEV/3c8c16477] [Sep 28 09:49:05.007] judgedaemon[301583]: 🔏 Executing chroot script: 'chroot-startstop.sh check' [Sep 28 09:49:05.015] judgedaemon[301583]: Registering judgehost on endpoint default: http://localhost/domjudge/api [Sep 28 09:49:05.194] judgedaemon[301583]: No submissions in queue (for endpoint default), waiting... [Sep 28 09:49:22.049] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:22.050] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/100/118 [Sep 28 09:49:22.051] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:22.194] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:22.578] judgedaemon[301583]: 💻 Compilation: (absolute.py) 'correct' [Sep 28 09:49:22.646] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:22.647] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:22.882] judgedaemon[301583]: ✔ ...done in 0.075s (CPU: 0.030s), result: correct [Sep 28 09:49:22.882] judgedaemon[301583]: 🔄 Running pass 2... [Sep 28 09:49:23.117] judgedaemon[301583]: ✔ ...done in 0.044s (CPU: 0.026s), result: correct [Sep 28 09:49:23.196] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:23.196] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/101/119 [Sep 28 09:49:23.196] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:23.217] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:23.370] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:23.675] judgedaemon[301583]: 💻 Compilation: (deterministic.py) 'correct' [Sep 28 09:49:23.745] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:23.745] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:23.923] judgedaemon[301583]: ✔ ...done in 0.042s (CPU: 0.026s), result: correct [Sep 28 09:49:23.923] judgedaemon[301583]: 🔄 Running pass 2... [Sep 28 09:49:24.102] judgedaemon[301583]: ✔ ...done in 0.044s (CPU: 0.025s), result: correct [Sep 28 09:49:24.174] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:24.175] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/102/120 [Sep 28 09:49:24.175] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:24.193] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:24.336] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:24.708] judgedaemon[301583]: 💻 Compilation: (rand.py) 'correct' [Sep 28 09:49:24.780] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:24.780] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:25.039] judgedaemon[301583]: ✔ ...done in 0.129s (CPU: 0.094s), result: correct [Sep 28 09:49:25.039] judgedaemon[301583]: 🔄 Running pass 2... [Sep 28 09:49:25.304] judgedaemon[301583]: ✗ ...done in 0.120s (CPU: 0.091s), result: wrong-answer [Sep 28 09:49:25.734] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:25.734] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/103/121 [Sep 28 09:49:25.734] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:25.951] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:26.271] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:26.778] judgedaemon[301583]: 💻 Compilation: (no_output.py) 'correct' [Sep 28 09:49:26.846] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:26.847] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:27.092] judgedaemon[301583]: ✗ ...done in 0.055s (CPU: 0.025s), result: no-output [Sep 28 09:49:27.298] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:27.298] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/104/122 [Sep 28 09:49:27.298] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:27.319] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:27.454] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:27.763] judgedaemon[301583]: 💻 Compilation: (string.py) 'correct' [Sep 28 09:49:27.836] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:27.836] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:28.012] judgedaemon[301583]: ✗ ...done in 0.068s (CPU: 0.027s), result: wrong-answer [Sep 28 09:49:28.225] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:28.225] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/105/123 [Sep 28 09:49:28.225] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:28.246] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:28.381] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:28.727] judgedaemon[301583]: 💻 Compilation: (negative.py) 'correct' [Sep 28 09:49:28.803] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:28.803] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:29.045] judgedaemon[301583]: ✗ ...done in 0.089s (CPU: 0.056s), result: wrong-answer [Sep 28 09:49:29.250] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:29.250] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/106/124 [Sep 28 09:49:29.250] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:29.270] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:29.425] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:29.761] judgedaemon[301583]: 💻 Compilation: (negative_second_pass.py) 'correct' [Sep 28 09:49:29.833] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:29.833] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:30.034] judgedaemon[301583]: ✔ ...done in 0.080s (CPU: 0.057s), result: correct [Sep 28 09:49:30.034] judgedaemon[301583]: 🔄 Running pass 2... [Sep 28 09:49:30.252] judgedaemon[301583]: ✗ ...done in 0.082s (CPU: 0.060s), result: wrong-answer [Sep 28 09:49:30.446] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:30.446] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/107/125 [Sep 28 09:49:30.446] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:30.467] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:30.612] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:30.923] judgedaemon[301583]: 💻 Compilation: (extra_output.py) 'correct' [Sep 28 09:49:30.992] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:30.992] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:31.170] judgedaemon[301583]: ✔ ...done in 0.055s (CPU: 0.025s), result: correct [Sep 28 09:49:31.170] judgedaemon[301583]: 🔄 Running pass 2... [Sep 28 09:49:31.366] judgedaemon[301583]: ✗ ...done in 0.060s (CPU: 0.028s), result: wrong-answer [Sep 28 09:49:31.569] judgedaemon[301583]: ⇝ Received 1 'judging_run' judge tasks (endpoint default) [Sep 28 09:49:31.570] judgedaemon[301583]: Working directory: /home/sitowert/domjudge/output/judgings/goo-0/endpoint-default/108/126 [Sep 28 09:49:31.570] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:31.590] judgedaemon[301583]: 🔒 Executing chroot script: 'chroot-startstop.sh start' [Sep 28 09:49:31.725] judgedaemon[301583]: 📋 Verifying versions. [Sep 28 09:49:32.062] judgedaemon[301583]: 💻 Compilation: (float.py) 'correct' [Sep 28 09:49:32.132] judgedaemon[301583]: 🏃 Running testcase 7... [Sep 28 09:49:32.132] judgedaemon[301583]: 🔄 Running pass 1... [Sep 28 09:49:32.327] judgedaemon[301583]: ✗ ...done in 0.043s (CPU: 0.022s), result: wrong-answer [Sep 28 09:49:32.522] judgedaemon[301583]: 🔓 Executing chroot script: 'chroot-startstop.sh stop' [Sep 28 09:49:32.541] judgedaemon[301583]: No submissions in queue (for endpoint default), waiting... ```
1 parent 9137d92 commit 2c2174e

File tree

11 files changed

+222
-73
lines changed

11 files changed

+222
-73
lines changed

judge/judgedaemon.main.php

Lines changed: 101 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,21 +1342,6 @@ function judge(array $judgeTask): bool
13421342
return false;
13431343
}
13441344

1345-
// Copy program with all possible additional files to testcase
1346-
// dir. Use hardlinks to preserve space with big executables.
1347-
$programdir = $testcasedir . '/execdir';
1348-
system('mkdir -p ' . dj_escapeshellarg($programdir), $retval);
1349-
if ($retval!==0) {
1350-
error("Could not create directory '$programdir'");
1351-
}
1352-
1353-
foreach (glob("$workdir/compile/*") as $compile_file) {
1354-
system('cp -PRl ' . dj_escapeshellarg($compile_file) . ' ' . dj_escapeshellarg($programdir), $retval);
1355-
if ($retval!==0) {
1356-
error("Could not copy program to '$programdir'");
1357-
}
1358-
}
1359-
13601345
// do the actual test-run
13611346
$combined_run_compare = $compare_config['combined_run_compare'];
13621347
[$run_runpath, $error] = fetch_executable(
@@ -1403,60 +1388,109 @@ function judge(array $judgeTask): bool
14031388
putenv('SCRIPTMEMLIMIT=' . $compare_config['script_memory_limit']);
14041389
putenv('SCRIPTFILELIMIT=' . $compare_config['script_filesize_limit']);
14051390

1406-
$test_run_cmd = LIBJUDGEDIR . "/testcase_run.sh $cpuset_opt " .
1407-
implode(' ', array_map('dj_escapeshellarg', [
1408-
$tcfile['input'],
1409-
$tcfile['output'],
1410-
"$run_config[time_limit]:$hardtimelimit",
1411-
$testcasedir,
1412-
$run_runpath,
1413-
$compare_runpath,
1414-
$compare_config['compare_args']
1415-
]));
1416-
system($test_run_cmd, $retval);
1391+
$input = $tcfile['input'];
1392+
$output = $tcfile['output'];
1393+
$passLimit = $run_config['pass_limit'];
1394+
for ($passCnt = 1; $passCnt <= $passLimit; $passCnt++) {
1395+
$nextPass = false;
1396+
if ($passLimit > 1) {
1397+
logmsg(LOG_INFO, " 🔄 Running pass $passCnt...");
1398+
}
14171399

1418-
// What does the exitcode mean?
1419-
if (! isset($EXITCODES[$retval])) {
1420-
alert('error');
1421-
error("Unknown exitcode ($retval) from testcase_run.sh for s$judgeTask[submitid]");
1422-
}
1423-
$result = $EXITCODES[$retval];
1400+
$passdir = $testcasedir . '/' . $passCnt;
1401+
mkdir($passdir, 0755, true);
14241402

1425-
// Try to read metadata from file
1426-
$runtime = null;
1427-
$metadata = read_metadata($testcasedir . '/program.meta');
1403+
// Copy program with all possible additional files to testcase
1404+
// dir. Use hardlinks to preserve space with big executables.
1405+
$programdir = $passdir . '/execdir';
1406+
system('mkdir -p ' . dj_escapeshellarg($programdir), $retval);
1407+
if ($retval!==0) {
1408+
error("Could not create directory '$programdir'");
1409+
}
14281410

1429-
if (isset($metadata['time-used'])) {
1430-
$runtime = @$metadata[$metadata['time-used']];
1431-
}
1411+
foreach (glob("$workdir/compile/*") as $compile_file) {
1412+
system('cp -PRl ' . dj_escapeshellarg($compile_file) . ' ' . dj_escapeshellarg($programdir), $retval);
1413+
if ($retval!==0) {
1414+
error("Could not copy program to '$programdir'");
1415+
}
1416+
}
14321417

1433-
if ($result === 'compare-error') {
1434-
if ($combined_run_compare) {
1435-
logmsg(LOG_ERR, "comparing failed for combined run/compare script '" . $judgeTask['run_script_id'] . "'");
1436-
$description = 'combined run/compare script ' . $judgeTask['run_script_id'] . ' crashed';
1437-
disable('run_script', 'run_script_id', $judgeTask['run_script_id'], $description, $judgeTask['judgetaskid']);
1438-
} else {
1439-
logmsg(LOG_ERR, "comparing failed for compare script '" . $judgeTask['compare_script_id'] . "'");
1440-
$description = 'compare script ' . $judgeTask['compare_script_id'] . ' crashed';
1441-
disable('compare_script', 'compare_script_id', $judgeTask['compare_script_id'], $description, $judgeTask['judgetaskid']);
1418+
$test_run_cmd = LIBJUDGEDIR . "/testcase_run.sh $cpuset_opt " .
1419+
implode(' ', array_map('dj_escapeshellarg', [
1420+
$input,
1421+
$output,
1422+
"$run_config[time_limit]:$hardtimelimit",
1423+
$passdir,
1424+
$run_runpath,
1425+
$compare_runpath,
1426+
$compare_config['compare_args']
1427+
]));
1428+
system($test_run_cmd, $retval);
1429+
1430+
// What does the exitcode mean?
1431+
if (!isset($EXITCODES[$retval])) {
1432+
alert('error');
1433+
error("Unknown exitcode ($retval) from testcase_run.sh for s$judgeTask[submitid]");
14421434
}
1443-
return false;
1444-
}
1435+
$result = $EXITCODES[$retval];
14451436

1446-
$new_judging_run = [
1447-
'runresult' => urlencode($result),
1448-
'runtime' => urlencode((string)$runtime),
1449-
'output_run' => rest_encode_file($testcasedir . '/program.out', $output_storage_limit),
1450-
'output_error' => rest_encode_file($testcasedir . '/program.err', $output_storage_limit),
1451-
'output_system' => rest_encode_file($testcasedir . '/system.out', $output_storage_limit),
1452-
'metadata' => rest_encode_file($testcasedir . '/program.meta', false),
1453-
'output_diff' => rest_encode_file($testcasedir . '/feedback/judgemessage.txt', $output_storage_limit),
1454-
'hostname' => $myhost,
1455-
'testcasedir' => $testcasedir,
1456-
];
1437+
// Try to read metadata from file
1438+
$runtime = null;
1439+
$metadata = read_metadata($passdir . '/program.meta');
14571440

1458-
if (file_exists($testcasedir . '/feedback/teammessage.txt')) {
1459-
$new_judging_run['team_message'] = rest_encode_file($testcasedir . '/feedback/teammessage.txt', $output_storage_limit);
1441+
if (isset($metadata['time-used'])) {
1442+
$runtime = @$metadata[$metadata['time-used']];
1443+
}
1444+
1445+
if ($result === 'compare-error') {
1446+
if ($combined_run_compare) {
1447+
logmsg(LOG_ERR, "comparing failed for combined run/compare script '" . $judgeTask['run_script_id'] . "'");
1448+
$description = 'combined run/compare script ' . $judgeTask['run_script_id'] . ' crashed';
1449+
disable('run_script', 'run_script_id', $judgeTask['run_script_id'], $description, $judgeTask['judgetaskid']);
1450+
} else {
1451+
logmsg(LOG_ERR, "comparing failed for compare script '" . $judgeTask['compare_script_id'] . "'");
1452+
$description = 'compare script ' . $judgeTask['compare_script_id'] . ' crashed';
1453+
disable('compare_script', 'compare_script_id', $judgeTask['compare_script_id'], $description, $judgeTask['judgetaskid']);
1454+
}
1455+
return false;
1456+
}
1457+
1458+
$new_judging_run = [
1459+
'runresult' => urlencode($result),
1460+
'runtime' => urlencode((string)$runtime),
1461+
'output_run' => rest_encode_file($passdir . '/program.out', $output_storage_limit),
1462+
'output_error' => rest_encode_file($passdir . '/program.err', $output_storage_limit),
1463+
'output_system' => rest_encode_file($passdir . '/system.out', $output_storage_limit),
1464+
'metadata' => rest_encode_file($passdir . '/program.meta', false),
1465+
'output_diff' => rest_encode_file($passdir . '/feedback/judgemessage.txt', $output_storage_limit),
1466+
'hostname' => $myhost,
1467+
'testcasedir' => $testcasedir,
1468+
];
1469+
1470+
if (file_exists($passdir . '/feedback/teammessage.txt')) {
1471+
$new_judging_run['team_message'] = rest_encode_file($passdir . '/feedback/teammessage.txt', $output_storage_limit);
1472+
}
1473+
1474+
if ($passLimit > 1) {
1475+
$walltime = $metadata['wall-time'] ?? '?';
1476+
logmsg(LOG_INFO, ' ' . ($result === 'correct' ? " \033[0;32m✔\033[0m" : " \033[1;31m✗\033[0m")
1477+
. ' ...done in ' . $walltime . 's (CPU: ' . $runtime . 's), result: ' . $result);
1478+
}
1479+
1480+
if ($result !== 'correct') {
1481+
break;
1482+
}
1483+
if (file_exists($passdir . '/feedback/nextpass.in')) {
1484+
$input = $passdir . '/feedback/nextpass.in';
1485+
$nextPass = true;
1486+
} else {
1487+
break;
1488+
}
1489+
}
1490+
if ($nextPass) {
1491+
$description = 'validator produced more passes than allowed ($passLimit)';
1492+
disable('compare_script', 'compare_script_id', $judgeTask['compare_script_id'], $description, $judgeTask['judgetaskid']);
1493+
return false;
14601494
}
14611495

14621496
$ret = true;
@@ -1485,9 +1519,11 @@ function judge(array $judgeTask): bool
14851519
$ret = (bool)$needsMoreWork;
14861520
}
14871521

1488-
$walltime = $metadata['wall-time'] ?? '?';
1489-
logmsg(LOG_INFO, ' ' . ($result === 'correct' ? " \033[0;32m✔\033[0m" : " \033[1;31m✗\033[0m")
1490-
. ' ...done in ' . $walltime . 's (CPU: ' . $runtime . 's), result: ' . $result);
1522+
if ($passLimit == 1) {
1523+
$walltime = $metadata['wall-time'] ?? '?';
1524+
logmsg(LOG_INFO, ' ' . ($result === 'correct' ? " \033[0;32m✔\033[0m" : " \033[1;31m✗\033[0m")
1525+
. ' ...done in ' . $walltime . 's (CPU: ' . $runtime . 's), result: ' . $result);
1526+
}
14911527

14921528
// done!
14931529
return $ret;

judge/run-interactive.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ MYDIR="$(dirname $0)"
3636

3737
# Run the program while redirecting its stdin/stdout to 'runjury' via
3838
# 'runpipe'. Note that "$@" expands to separate, quoted arguments.
39-
exec ../dj-bin/runpipe ${DEBUG:+-v} -M "$META" -o "$PROGOUT" "$MYDIR/runjury" "$TESTIN" "$TESTOUT" "$FEEDBACK" = "$@"
39+
exec ../../dj-bin/runpipe ${DEBUG:+-v} -M "$META" -o "$PROGOUT" "$MYDIR/runjury" "$TESTIN" "$TESTOUT" "$FEEDBACK" = "$@"

judge/testcase_run.sh

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ cleanup ()
2626
{
2727
# Remove some copied files to save disk space
2828
if [ "$WORKDIR" ]; then
29-
rm -f "$WORKDIR/../dj-bin/runpipe" 2> /dev/null || true
29+
rm -f "$WORKDIR/../../dj-bin/runpipe" 2> /dev/null || true
3030

3131
# Replace testdata by symlinks to reduce disk usage
3232
if [ -f "$WORKDIR/testdata.in" ]; then
@@ -157,7 +157,8 @@ fi
157157

158158
cd "$WORKDIR"
159159

160-
PREFIX="/$(basename "$PWD")"
160+
# Get the last two directory entries of $PWD
161+
PREFIX="/$(basename $(realpath "$PWD/.."))/$(basename "$PWD")"
161162

162163
# Make testing/execute dir accessible for RUNUSER:
163164
chmod a+x "$WORKDIR" "$WORKDIR/execdir"
@@ -174,10 +175,10 @@ logmsg $LOG_INFO "setting up testing (chroot) environment"
174175
cp "$TESTIN" "$WORKDIR/testdata.in"
175176

176177
# shellcheck disable=SC2174
177-
mkdir -p -m 0711 ../bin ../dj-bin ../dev
178+
mkdir -p -m 0711 ../../bin ../../dj-bin ../../dev
178179
# copy a support program for interactive problems:
179-
cp -pL "$RUNPIPE" ../dj-bin/runpipe
180-
chmod a+rx ../dj-bin/runpipe
180+
cp -pL "$RUNPIPE" ../../dj-bin/runpipe
181+
chmod a+rx ../../dj-bin/runpipe
181182

182183
# If we need to create a writable temp directory, do so
183184
if [ "$CREATE_WRITABLE_TEMP_DIR" ]; then
@@ -203,7 +204,7 @@ exitcode=0
203204
# shellcheck disable=SC2153
204205
runcheck "$RUN_SCRIPT" $RUNARGS \
205206
$GAINROOT "$RUNGUARD" ${DEBUG:+-v -V "DEBUG=$DEBUG"} ${TMPDIR:+ -V "TMPDIR=$TMPDIR"} $CPUSET_OPT \
206-
-r "$PWD/.." \
207+
-r "$PWD/../.." \
207208
--nproc=$PROCLIMIT \
208209
--no-core --streamsize=$FILELIMIT \
209210
--user="$RUNUSER" --group="$RUNGROUP" \
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20240921081301 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Add multipass problem support';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE problem ADD is_multipass_problem TINYINT(1) DEFAULT 0 NOT NULL COMMENT \'Whether this problem is a multi-pass problem.\', ADD multipass_limit INT UNSIGNED DEFAULT NULL COMMENT \'Optional limit on the number of rounds for multi-pass problems; defaults to 2 if not specified.\'');
24+
}
25+
26+
public function down(Schema $schema): void
27+
{
28+
// this down() migration is auto-generated, please modify it to your needs
29+
$this->addSql('ALTER TABLE problem DROP is_multipass_problem, DROP multipass_limit');
30+
}
31+
32+
public function isTransactional(): bool
33+
{
34+
return false;
35+
}
36+
}

webapp/src/Controller/Jury/ProblemController.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ public function indexAction(): Response
8989
'memlimit' => ['title' => 'memory limit', 'sort' => true],
9090
'outputlimit' => ['title' => 'output limit', 'sort' => true],
9191
'num_testcases' => ['title' => '# test cases', 'sort' => true],
92+
'type' => ['title' => 'type', 'sort' => true],
9293
];
9394

9495
$contestCountData = $this->em->createQueryBuilder()
@@ -206,9 +207,17 @@ public function indexAction(): Response
206207
$problemdata['badges'] = ['value' => $badges];
207208

208209
// merge in the rest of the data
210+
$type = '';
211+
if ($p->getCombinedRunCompare()) {
212+
$type .= ' interactive';
213+
}
214+
if ($p->isMultipassProblem()) {
215+
$type .= ' multi-pass';
216+
}
209217
$problemdata = array_merge($problemdata, [
210218
'num_contests' => ['value' => (int)($contestCounts[$p->getProbid()] ?? 0)],
211219
'num_testcases' => ['value' => (int)$row['testdatacount']],
220+
'type' => ['value' => $type],
212221
]);
213222

214223
// Save this to our list of rows
@@ -484,6 +493,13 @@ public function viewAction(Request $request, SubmissionService $submissionServic
484493
new SubmissionRestriction(problemId: $problem->getProbid()),
485494
);
486495

496+
$type = '';
497+
if ($problem->getCombinedRunCompare()) {
498+
$type .= ' interactive';
499+
}
500+
if ($problem->isMultipassProblem()) {
501+
$type .= ' multi-pass';
502+
}
487503
$data = [
488504
'problem' => $problem,
489505
'problemAttachmentForm' => $problemAttachmentForm->createView(),
@@ -493,6 +509,7 @@ public function viewAction(Request $request, SubmissionService $submissionServic
493509
'defaultOutputLimit' => (int)$this->config->get('output_limit'),
494510
'defaultRunExecutable' => (string)$this->config->get('default_run'),
495511
'defaultCompareExecutable' => (string)$this->config->get('default_compare'),
512+
'type' => $type,
496513
'showContest' => count($this->dj->getCurrentContests(honorCookie: true)) > 1,
497514
'showExternalResult' => $this->dj->shadowMode(),
498515
'lockedProblem' => $lockedProblem,

webapp/src/Controller/Jury/SubmissionController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ public function viewAction(
532532
'combinedRunCompare' => $submission->getProblem()->getCombinedRunCompare(),
533533
'requestedOutputCount' => $requestedOutputCount,
534534
'version_warnings' => [],
535+
'isMultiPassProblem' => $submission->getProblem()->isMultipassProblem(),
535536
];
536537

537538
if ($selectedJudging === null) {

webapp/src/Entity/Problem.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ class Problem extends BaseApiEntity implements
109109
#[Serializer\Exclude]
110110
private ?string $problemstatement_type = null;
111111

112+
#[ORM\Column(options: [
113+
'comment' => 'Whether this problem is a multi-pass problem.',
114+
'default' => 0,
115+
])]
116+
#[Serializer\Exclude]
117+
private bool $isMultipassProblem = false;
118+
119+
#[ORM\Column(
120+
nullable: true,
121+
options: ['comment' => 'Optional limit on the number of rounds; defaults to 1 for traditional problems, 2 for multi-pass problems if not specified.', 'unsigned' => true]
122+
)]
123+
#[Assert\GreaterThan(0)]
124+
#[Serializer\Exclude]
125+
private ?int $multipassLimit = null;
126+
112127
/**
113128
* @var Collection<int, Submission>
114129
*/
@@ -273,6 +288,31 @@ public function getCombinedRunCompare(): bool
273288
return $this->combined_run_compare;
274289
}
275290

291+
public function setMultipassProblem(bool $isMultipassProblem): Problem
292+
{
293+
$this->isMultipassProblem = $isMultipassProblem;
294+
return $this;
295+
}
296+
297+
public function isMultipassProblem(): bool
298+
{
299+
return $this->isMultipassProblem;
300+
}
301+
302+
public function setMultipassLimit(?int $multipassLimit): Problem
303+
{
304+
$this->multipassLimit = $multipassLimit;
305+
return $this;
306+
}
307+
308+
public function getMultipassLimit(): int
309+
{
310+
if ($this->isMultipassProblem) {
311+
return $this->multipassLimit ?? 2;
312+
}
313+
return 1;
314+
}
315+
276316
public function setProblemstatementFile(?UploadedFile $problemstatementFile): Problem
277317
{
278318
$this->problemstatementFile = $problemstatementFile;

webapp/src/Service/DOMJudgeService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,7 @@ public function getRunConfig(ContestProblem $problem, Submission $submission): s
14421442
'output_limit' => $outputLimit,
14431443
'process_limit' => $this->config->get('process_limit'),
14441444
'entry_point' => $submission->getEntryPoint(),
1445+
'pass_limit' => $problem->getProblem()->getMultipassLimit(),
14451446
'hash' => $runExecutable->getHash(),
14461447
]
14471448
);

0 commit comments

Comments
 (0)