From eeaecb63416fdfca95838f7285d16ef2f74531f5 Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Sun, 22 Sep 2024 17:02:58 +0900 Subject: [PATCH 1/2] Fix handling output file in DOMjudge reactive problem Before this change, DOMJudgeReactiveRunner ignores output file because reactive problems don't usually have output, and interactor don't use it. However, when packing zip to upload, diff file is mandatory, so even though it just creates an empty file, DOMJudgeReactiveRunner should make sure diff file is created so that later pack and upload won't fail due to missing diff file. Fixes #102. --- rime/plugins/judge_system/domjudge.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/rime/plugins/judge_system/domjudge.py b/rime/plugins/judge_system/domjudge.py index b58b02f..b6c1c6d 100755 --- a/rime/plugins/judge_system/domjudge.py +++ b/rime/plugins/judge_system/domjudge.py @@ -6,7 +6,6 @@ import shutil import signal import subprocess -import tempfile import threading import time @@ -213,17 +212,17 @@ def Run(self, reactive, args, cwd, input, output, timeout, precise): if os.path.exists(feedback_dir_name): shutil.rmtree(feedback_dir_name) os.makedirs(feedback_dir_name, exist_ok=True) - # 2nd argument is an "expected output" file, which is not supported - # in rime interactive for now. - # As a placeholder, using a temporary file. - with tempfile.NamedTemporaryFile() as tmpfile: - judge_args = reactive.run_args + \ - (input, tmpfile.name, feedback_dir_name, ) - solution_args = args - task = DOMJudgeReactiveTask( - judge_args, solution_args, - cwd=cwd, timeout=timeout, exclusive=precise) - (judge_proc, solution_proc) = yield task + + # Makes sure output file exists. + open(output, 'w').close() + + judge_args = reactive.run_args + \ + (input, output, feedback_dir_name, ) + solution_args = args + task = DOMJudgeReactiveTask( + judge_args, solution_args, + cwd=cwd, timeout=timeout, exclusive=precise) + (judge_proc, solution_proc) = yield task judge_code = judge_proc.returncode solution_code = solution_proc.returncode From 19b0131423c54945d315af5a968568836d7096de Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Sun, 22 Sep 2024 19:37:06 +0900 Subject: [PATCH 2/2] Save solution's stderr to a file under rime-out Fixes #101 --- rime/basic/codes.py | 39 +++++++++++++++++---------- rime/basic/consts.py | 1 + rime/basic/targets/solution.py | 5 ++-- rime/basic/targets/testset.py | 7 ++--- rime/plugins/judge_system/domjudge.py | 24 ++++++++++------- rime/plugins/plus/flexible_judge.py | 29 +++++++++++--------- 6 files changed, 64 insertions(+), 41 deletions(-) diff --git a/rime/basic/codes.py b/rime/basic/codes.py index 6a0dda9..473ed35 100644 --- a/rime/basic/codes.py +++ b/rime/basic/codes.py @@ -1,5 +1,6 @@ #!/usr/bin/python +import contextlib import optparse import os import os.path @@ -35,13 +36,14 @@ def Compile(self): @taskgraph.task_method def Run(self, args, cwd, input, output, timeout, precise, - redirect_error=False, ok_returncode=0, ng_returncode=None): + redirect_error=False, stderr_file=None, + ok_returncode=0, ng_returncode=None): """Run the code and return RunResult.""" try: result = yield self._ExecForRun( args=tuple(list(self.run_args) + list(args)), cwd=cwd, input=input, output=output, timeout=timeout, precise=precise, - redirect_error=redirect_error, + redirect_error=redirect_error, stderr_file=stderr_file, ok_returncode=ok_returncode, ng_returncode=ng_returncode) except Exception as e: result = codes.RunResult('On execution: %s' % e, None) @@ -73,18 +75,27 @@ def _ExecForCompile(self, args): @taskgraph.task_method def _ExecForRun(self, args, cwd, input, output, timeout, precise, - redirect_error=False, ok_returncode=0, ng_returncode=None): - with open(input, 'r') as infile: - with open(output, 'w') as outfile: - if redirect_error: - errfile = subprocess.STDOUT - else: - errfile = files.OpenNull() - yield (yield self._ExecInternal( - args=args, cwd=cwd, - stdin=infile, stdout=outfile, stderr=errfile, - timeout=timeout, precise=precise, - ok_returncode=ok_returncode, ng_returncode=ng_returncode)) + redirect_error=False, stderr_file=None, + ok_returncode=0, ng_returncode=None): + with contextlib.ExitStack() as exitStack: + infile = exitStack.enter_context(open(input, 'r')) + outfile = exitStack.enter_context(open(output, 'w')) + + if redirect_error and stderr_file: + # Internal inconsistency, this should not happen. + raise taskgraph.Bailout([False]) + elif redirect_error: + errfile = subprocess.STDOUT + elif stderr_file: + errfile = exitStack.enter_context(open(stderr_file, 'w')) + else: + errfile = files.OpenNull() + + yield (yield self._ExecInternal( + args=args, cwd=cwd, + stdin=infile, stdout=outfile, stderr=errfile, + timeout=timeout, precise=precise, + ok_returncode=ok_returncode, ng_returncode=ng_returncode)) @taskgraph.task_method def _ExecInternal(self, args, cwd, stdin, stdout, stderr, diff --git a/rime/basic/consts.py b/rime/basic/consts.py index ea4cdd3..0897762 100644 --- a/rime/basic/consts.py +++ b/rime/basic/consts.py @@ -15,6 +15,7 @@ CACHE_EXT = '.cache' LOG_EXT = '.log' VALIDATION_EXT = '.validation' +STDERR_EXT = '.stderr' RIME_OUT_DIR = 'rime-out' diff --git a/rime/basic/targets/solution.py b/rime/basic/targets/solution.py index 228626b..ec7031d 100644 --- a/rime/basic/targets/solution.py +++ b/rime/basic/targets/solution.py @@ -86,11 +86,12 @@ def Build(self, ui): yield True @taskgraph.task_method - def Run(self, args, cwd, input, output, timeout, precise): + def Run(self, args, cwd, input, output, timeout, precise, + stderr_file=None): """Run this solution.""" yield (yield self.code.Run( args=args, cwd=cwd, input=input, output=output, - timeout=timeout, precise=precise)) + timeout=timeout, precise=precise, stderr_file=stderr_file)) @taskgraph.task_method def Test(self, ui): diff --git a/rime/basic/targets/testset.py b/rime/basic/targets/testset.py index 5032c59..c8fccf0 100644 --- a/rime/basic/targets/testset.py +++ b/rime/basic/targets/testset.py @@ -507,17 +507,18 @@ def _TestOneCaseNoCache(self, solution, testcase, ui): Never cache results. Returns TestCaseResult. """ - outfile, judgefile = [ + outfile, judgefile, stderrfile = [ os.path.join( solution.out_dir, os.path.splitext(os.path.basename(testcase.infile))[0] + ext) - for ext in (consts.OUT_EXT, consts.JUDGE_EXT)] + for ext in (consts.OUT_EXT, consts.JUDGE_EXT, consts.STDERR_EXT)] precise = (ui.options.precise or ui.options.parallelism <= 1) res = yield solution.Run( args=(), cwd=solution.out_dir, input=testcase.infile, output=outfile, - timeout=testcase.timeout, precise=precise) + timeout=testcase.timeout, precise=precise, + stderr_file=stderrfile) if res.status == core_codes.RunResult.TLE: yield test.TestCaseResult(solution, testcase, test.TestCaseResult.TLE, diff --git a/rime/plugins/judge_system/domjudge.py b/rime/plugins/judge_system/domjudge.py index b6c1c6d..185208a 100755 --- a/rime/plugins/judge_system/domjudge.py +++ b/rime/plugins/judge_system/domjudge.py @@ -73,9 +73,10 @@ def Run(self, judge, infile, difffile, outfile, cwd, judgefile): class DOMJudgeReactiveTask(taskgraph.Task): - def __init__(self, judge_args, solution_args, **kwargs): + def __init__(self, judge_args, solution_args, solution_stderr, **kwargs): self.judge_args = judge_args self.solution_args = solution_args + self.solution_stderr = solution_stderr self.judge_proc = None self.solution_proc = None if 'timeout' in kwargs: @@ -155,7 +156,8 @@ def _StartProcess(self): **self.kwargs) self.solution_proc = subprocess.Popen( self.solution_args, stdin=self.judge_proc.stdout, - stdout=self.judge_proc.stdin, **self.kwargs) + stdout=self.judge_proc.stdin, stderr=self.solution_stderr, + **self.kwargs) # Makes writing side responsible to close the pipe. def pipe_closer(write_proc, read_proc): @@ -205,7 +207,8 @@ class DOMJudgeReactiveRunner(flexible_judge.ReactiveRunner): PREFIX = 'domjudge' @taskgraph.task_method - def Run(self, reactive, args, cwd, input, output, timeout, precise): + def Run(self, reactive, args, cwd, input, output, timeout, precise, + stderr_file): feedback_dir_name = os.path.join( cwd, os.path.splitext(os.path.basename(input))[0] + '.feedback') @@ -216,13 +219,14 @@ def Run(self, reactive, args, cwd, input, output, timeout, precise): # Makes sure output file exists. open(output, 'w').close() - judge_args = reactive.run_args + \ - (input, output, feedback_dir_name, ) - solution_args = args - task = DOMJudgeReactiveTask( - judge_args, solution_args, - cwd=cwd, timeout=timeout, exclusive=precise) - (judge_proc, solution_proc) = yield task + with open(stderr_file, 'w') as solution_stderr: + judge_args = reactive.run_args + \ + (input, output, feedback_dir_name, ) + solution_args = args + task = DOMJudgeReactiveTask( + judge_args, solution_args, solution_stderr, + cwd=cwd, timeout=timeout, exclusive=precise) + (judge_proc, solution_proc) = yield task judge_code = judge_proc.returncode solution_code = solution_proc.returncode diff --git a/rime/plugins/plus/flexible_judge.py b/rime/plugins/plus/flexible_judge.py index 93200c8..c82c2d8 100644 --- a/rime/plugins/plus/flexible_judge.py +++ b/rime/plugins/plus/flexible_judge.py @@ -50,15 +50,17 @@ def Run(self, judge, infile, difffile, outfile, cwd, judgefile): class ReactiveRunner(object): - def Run(self, reactive, solution, args, cwd, input, output, timeout, - precise): + def Run(self, reactive, args, cwd, input, output, timeout, precise, + stderr_file): raise NotImplementedError() class KUPCReactiveRunner(ReactiveRunner): PREFIX = 'kupc' - def Run(self, reactive, args, cwd, input, output, timeout, precise): + def Run(self, reactive, args, cwd, input, output, timeout, precise, + stderr_file): + # Not sure how solution's stderr should be handled. return reactive.Run( args=("'%s'" % ' '.join(args),), cwd=cwd, @@ -72,16 +74,16 @@ def Run(self, reactive, args, cwd, input, output, timeout, precise): class TestlibReactiveRunner(ReactiveRunner): PREFIX = 'testlib' - def Run(self, reactive, solution, args, cwd, input, output, timeout, - precise): + def Run(self, reactive, args, cwd, input, output, timeout, precise, + stderr_file): raise NotImplementedError() class NEERCReactiveRunner(ReactiveRunner): PREFIX = 'neerc' - def Run(self, reactive, solution, args, cwd, input, output, timeout, - precise): + def Run(self, reactive, args, cwd, input, output, timeout, precise, + stderr_file): raise NotImplementedError() @@ -110,11 +112,11 @@ def _TestOneCaseNoCache(self, solution, testcase, ui): Never cache results. Returns TestCaseResult. """ - outfile, judgefile = [ + outfile, judgefile, stderrfile = [ os.path.join( solution.out_dir, os.path.splitext(os.path.basename(testcase.infile))[0] + ext) - for ext in (consts.OUT_EXT, consts.JUDGE_EXT)] + for ext in (consts.OUT_EXT, consts.JUDGE_EXT, consts.STDERR_EXT)] precise = (ui.options.precise or ui.options.parallelism <= 1) # reactive if self.reactives: @@ -129,7 +131,8 @@ def _TestOneCaseNoCache(self, solution, testcase, ui): args=solution.code.run_args, cwd=solution.out_dir, input=testcase.infile, output=outfile, - timeout=testcase.timeout, precise=precise) + timeout=testcase.timeout, precise=precise, + stderr_file=stderrfile) # Normally, res is codes.RunResult. # Some reactive variants returns TestCaseResult if isinstance(res, test.TestCaseResult): @@ -143,7 +146,8 @@ def _TestOneCaseNoCache(self, solution, testcase, ui): args=(), cwd=solution.out_dir, input=testcase.infile, output=outfile, - timeout=testcase.timeout, precise=precise) + timeout=testcase.timeout, precise=precise, + stderr_file=stderrfile) if res.status == core_codes.RunResult.TLE: yield test.TestCaseResult(solution, testcase, test.TestCaseResult.TLE, @@ -195,7 +199,8 @@ def _RunReferenceSolutionOne(self, reference_solution, testcase, ui): cwd=reference_solution.out_dir, input=testcase.infile, output=testcase.difffile, - timeout=None, precise=False) + timeout=None, precise=False, + stderr_file=os.devnull) # Some reactive variants returns TestCaseResult if isinstance(res, test.TestCaseResult): if res.verdict != test.TestCaseResult.AC: