From d9d93020791bd238a22331dee5176860281cd44e Mon Sep 17 00:00:00 2001 From: Hiroki Sato Date: Sun, 22 Sep 2024 19:37:06 +0900 Subject: [PATCH] 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 | 33 +++++++++++++---------- rime/plugins/plus/flexible_judge.py | 28 ++++++++++--------- 6 files changed, 68 insertions(+), 45 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 b58b02f..5c2b5d3 100755 --- a/rime/plugins/judge_system/domjudge.py +++ b/rime/plugins/judge_system/domjudge.py @@ -74,9 +74,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: @@ -156,7 +157,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): @@ -206,24 +208,27 @@ 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') 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 + + with open(stderr_file, 'w') as solution_stderr: + # 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, 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..e5417d7 100644 --- a/rime/plugins/plus/flexible_judge.py +++ b/rime/plugins/plus/flexible_judge.py @@ -50,15 +50,16 @@ 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): return reactive.Run( args=("'%s'" % ' '.join(args),), cwd=cwd, @@ -72,16 +73,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 +111,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 +130,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 +145,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 +198,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: