Skip to content

Commit 6dd4966

Browse files
Simplify Judge class (kyuridenamida#180)
* Make logic naive for readability * Stop using the current directory in setter command * Fix broken test * Fix bug that setter creates files in a wrong path
1 parent 9ccab88 commit 6dd4966

File tree

9 files changed

+183
-136
lines changed

9 files changed

+183
-136
lines changed

atcodertools/common/judgetype.py

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# -*- coding: utf-8 -*-
33
from abc import ABCMeta, abstractmethod
44
from enum import Enum
5-
import platform
65

76

87
class NoJudgeTypeException(Exception):
@@ -23,8 +22,9 @@ class ErrorType(Enum):
2322

2423

2524
class Judge(metaclass=ABCMeta):
25+
@property
2626
@abstractmethod
27-
def verify(self, output, expected):
27+
def judge_type(self):
2828
pass
2929

3030
@abstractmethod
@@ -33,8 +33,10 @@ def to_dict(self):
3333

3434

3535
class NormalJudge(Judge):
36+
judge_type = JudgeType.Normal
37+
3638
def __init__(self):
37-
self.judge_type = JudgeType.Normal
39+
pass
3840

3941
def verify(self, output, expected):
4042
return output == expected
@@ -54,6 +56,8 @@ def from_dict(cls, dic):
5456

5557

5658
class DecimalJudge(Judge):
59+
judge_type = JudgeType.Decimal
60+
5761
def __init__(self,
5862
error_type: ErrorType = ErrorType.AbsoluteOrRelative,
5963
diff: float = DEFAULT_EPS
@@ -107,60 +111,28 @@ def from_dict(cls, dic):
107111
return r
108112

109113

110-
def get_judge_filename():
111-
judge_code_filename = "judge"
112-
if platform.system() == "Windows":
113-
judge_exec_filename = "judge.exe"
114-
else:
115-
judge_exec_filename = "judge"
116-
return judge_code_filename, judge_exec_filename
117-
118-
119114
class MultiSolutionJudge(Judge):
115+
judge_type = JudgeType.MultiSolution
120116

121-
def __init__(self, judge_code_lang: 'Language'):
122-
123-
self.judge_type = JudgeType.MultiSolution
124-
self.judge_code_filename, self.judge_exec_filename = get_judge_filename()
125-
126-
self.judge_code_lang = judge_code_lang
117+
def __init__(self):
118+
pass
127119

128120
def verify(self, output, expected):
129121
raise NotImplementedError()
130122

131123
def to_dict(self):
132124
return {
133-
"judge_type": self.judge_type.value,
134-
"judge_code_filename": self.judge_code_filename,
135-
"judge_exec_filename": self.judge_exec_filename,
136-
"judge_code_lang": "cpp"
125+
"judge_type": self.judge_type.value
137126
}
138127

139-
@classmethod
140-
def from_dict(cls, dic):
141-
from atcodertools.common.language import Language
142-
return MultiSolutionJudge(Language.from_name(dic["judge_code_lang"]))
143-
144128

145129
class InteractiveJudge(Judge):
146-
147-
def __init__(self, judge_code_lang: 'Language'):
148-
self.judge_type = JudgeType.Interactive
149-
self.judge_code_filename, self.judge_exec_filename = get_judge_filename()
150-
self.judge_code_lang = judge_code_lang
130+
judge_type = JudgeType.Interactive
151131

152132
def verify(self, output, expected):
153133
raise NotImplementedError()
154134

155135
def to_dict(self):
156136
return {
157-
"judge_type": self.judge_type.value,
158-
"judge_code_filename": self.judge_code_filename,
159-
"judge_exec_filename": self.judge_exec_filename,
160-
"judge_code_lang": "cpp"
137+
"judge_type": self.judge_type.value
161138
}
162-
163-
@classmethod
164-
def from_dict(cls, dic):
165-
from atcodertools.common.language import Language
166-
return InteractiveJudge(Language.from_name(dic["judge_code_lang"]))

atcodertools/config/config.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,18 @@ def __init__(
3131
without_login: Optional[bool] = None,
3232
parallel: Optional[bool] = None,
3333
save_no_session_cache: Optional[bool] = None,
34-
lang: Optional[str] = None
34+
lang: Optional[str] = None,
35+
compile_before_testing: Optional[bool] = None,
36+
compile_only_when_diff_detected: Optional[bool] = None
3537
):
3638
self.template = template
3739
self.workspace = workspace
3840
self.without_login = without_login
3941
self.parallel = parallel
4042
self.save_no_session_cache = save_no_session_cache
4143
self.lang = lang
44+
self.compile_before_testing = compile_before_testing
45+
self.compile_only_when_diff_detected = compile_only_when_diff_detected
4246

4347
@classmethod
4448
def load(cls, program_args: argparse.Namespace):
@@ -49,7 +53,9 @@ def load(cls, program_args: argparse.Namespace):
4953
"without_login",
5054
"parallel",
5155
"save_no_session_cache",
52-
"lang"
56+
"lang",
57+
"compile_before_testing",
58+
"compile_only_when_diff_detected"
5359
)})
5460

5561

@@ -128,9 +134,13 @@ def load(cls, fp: TextIO, args: Optional[ProgramArgs] = None):
128134
)
129135
etc_config_dic = _update_config_dict(
130136
etc_config_dic,
131-
dict(download_without_login=args.without_login,
132-
parallel_download=args.parallel,
133-
save_no_session_cache=args.save_no_session_cache)
137+
dict(
138+
download_without_login=args.without_login,
139+
parallel_download=args.parallel,
140+
save_no_session_cache=args.save_no_session_cache,
141+
compile_before_testing=args.compile_before_testing,
142+
compile_only_when_diff_detected=args.compile_only_when_diff_detected
143+
)
134144
)
135145

136146
return Config(

atcodertools/executils/run_program.py

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,46 @@
22
import time
33
from enum import Enum
44
import threading
5-
from atcodertools.common.judgetype import JudgeType
5+
from typing import Optional
6+
7+
from atcodertools.common.judgetype import Judge, MultiSolutionJudge, InteractiveJudge, DecimalJudge, \
8+
NormalJudge
69
import tempfile
710

11+
from atcodertools.common.language import Language
12+
813

914
class ExecStatus(Enum):
1015
NORMAL = "NORMAL"
1116
TLE = "TLE"
1217
RE = "RE"
13-
JUDGEERROR = "JUDGEERROR"
18+
JUDGE_ERROR = "JUDGE_ERROR"
1419

1520

1621
class JudgeStatus(Enum):
1722
AC = "AC"
1823
WA = "WA"
1924

2025

26+
class UnknownJudgeError(Exception):
27+
pass
28+
29+
2130
class JudgeError(Exception):
2231
def __init__(self, stdout: str = "", stderr: str = ""):
2332
self.stdout = stdout
2433
self.stderr = stderr
2534

2635

2736
class ExecResult:
28-
def __init__(self, status: ExecStatus, output: str = None, stderr: str = None, elapsed_sec: float = None, special_judge_status: JudgeStatus = None, judge_message: str = None):
37+
def __init__(
38+
self,
39+
status: ExecStatus, output: str = None,
40+
stderr: str = None,
41+
elapsed_sec: float = None,
42+
special_judge_status: JudgeStatus = None,
43+
judge_message: str = None
44+
):
2945
self.status = status
3046
self.output = output
3147
self.stderr = stderr
@@ -37,32 +53,47 @@ def __init__(self, status: ExecStatus, output: str = None, stderr: str = None, e
3753
else:
3854
self.elapsed_ms = None
3955

40-
def is_correct_output(self, expected_answer_text=None, judge_method=None, sample_input_file=None, sample_output_file=None, cwd=None):
56+
def is_correct_output(
57+
self,
58+
expected_answer_text: Optional[str] = None,
59+
judge_method: Optional[Judge] = None,
60+
sample_input_file: Optional[str] = None,
61+
sample_output_file: Optional[str] = None,
62+
cwd: Optional[str] = None,
63+
judge_program_language: Optional[Language] = None
64+
):
4165
if self.status != ExecStatus.NORMAL:
4266
return False
67+
4368
if self.special_judge_status is not None:
4469
return self.special_judge_status == JudgeStatus.AC
4570

46-
if judge_method.judge_type == JudgeType.MultiSolution:
47-
judge_exec_res = run_multisolution_judge_program(judge_method.judge_code_lang.get_test_command('judge', cwd),
48-
self.output,
49-
sample_input_file,
50-
sample_output_file
51-
)
71+
if isinstance(judge_method, MultiSolutionJudge):
72+
judge_exec_res = run_multisolution_judge_program(
73+
judge_program_language.get_test_command('judge', cwd),
74+
self.output,
75+
sample_input_file,
76+
sample_output_file
77+
)
5278
self.judge_message = judge_exec_res.stderr
5379
return judge_exec_res.special_judge_status == JudgeStatus.AC
54-
elif judge_method.judge_type == JudgeType.Interactive:
55-
raise("No judge status error for interactive!!")
56-
else:
80+
elif isinstance(judge_method, InteractiveJudge):
81+
raise UnknownJudgeError("No judge status error for interactive!!")
82+
elif isinstance(judge_method, DecimalJudge):
83+
return judge_method.verify(self.output, expected_answer_text)
84+
elif isinstance(judge_method, NormalJudge):
5785
return judge_method.verify(self.output, expected_answer_text)
86+
else:
87+
raise NotImplementedError
5888

5989
def has_stderr(self):
6090
if self.stderr is None:
6191
return False
6292
return len(self.stderr) > 0
6393

6494

65-
def run_program(exec_cmd: str, input_file: str, timeout_sec: int, args=None, current_working_dir: str = None) -> ExecResult:
95+
def run_program(exec_cmd: str, input_file: str, timeout_sec: int, args=None,
96+
current_working_dir: str = None) -> ExecResult:
6697
if args is None:
6798
args = []
6899
try:
@@ -87,7 +118,8 @@ def run_program(exec_cmd: str, input_file: str, timeout_sec: int, args=None, cur
87118
return ExecResult(ExecStatus.RE, e.stdout, e.stderr)
88119

89120

90-
def run_multisolution_judge_program(judge_cmd: str, output: str, sample_input_file: str, sample_output_file: str, args=None, current_working_dir: str = None) -> ExecResult:
121+
def run_multisolution_judge_program(judge_cmd: str, output: str, sample_input_file: str, sample_output_file: str,
122+
args=None, current_working_dir: str = None) -> ExecResult:
91123
if args is None:
92124
args = []
93125
try:
@@ -126,7 +158,8 @@ def run_interactive_program(exec_file: str, exec_judge_file: str, input_file: st
126158
elapsed_sec = -time.time()
127159

128160
class RunThread(threading.Thread):
129-
def __init__(self, cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input_file=None, timeout_sec=None):
161+
def __init__(self, cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
162+
input_file=None, timeout_sec=None):
130163
threading.Thread.__init__(self)
131164
self.proc = subprocess.Popen(cmd + args,
132165
stdin=stdin,
@@ -155,6 +188,7 @@ def run(self):
155188

156189
def close(self):
157190
self.proc.stdin.close()
191+
158192
main_thread = RunThread(
159193
[exec_file], input_file=input_file, timeout_sec=timeout_sec)
160194
judge_thread = RunThread(exec_judge_file.split() + [input_file, output_file],
@@ -206,4 +240,4 @@ def close(self):
206240
except subprocess.CalledProcessError as e:
207241
return ExecResult(ExecStatus.RE, e.stdout, e.stderr)
208242
except JudgeError as e:
209-
return ExecResult(ExecStatus.JUDGEERROR, e.stdout, e.stderr)
243+
return ExecResult(ExecStatus.JUDGE_ERROR, e.stdout, e.stderr)

atcodertools/tools/compiler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/python3
22
import argparse
33

4-
from atcodertools.common.judgetype import JudgeType
4+
from atcodertools.common.judgetype import MultiSolutionJudge, InteractiveJudge
55
from atcodertools.executils.run_command import run_command_with_returncode
66
from atcodertools.tools.models.metadata import Metadata
77
import os
@@ -25,7 +25,7 @@ def _compile(code_filename: str, exec_filename: str, compile_cmd: str, cwd: str,
2525
code, stdout = run_command_with_returncode(compile_cmd, cwd)
2626
print(stdout)
2727
if code != 0:
28-
raise BadStatusCodeException()
28+
raise BadStatusCodeException
2929

3030

3131
def compile_main_and_judge_programs(metadata: Metadata, cwd="./", force_compile=False) -> None:
@@ -40,9 +40,9 @@ def compile_main_and_judge_programs(metadata: Metadata, cwd="./", force_compile=
4040
except BadStatusCodeException as e:
4141
raise e
4242

43-
if metadata.judge_method.judge_type in [JudgeType.MultiSolution, JudgeType.Interactive]:
43+
if isinstance(metadata.judge_method, MultiSolutionJudge) or isinstance(metadata.judge_method, InteractiveJudge):
4444
print("[Judge Program]")
45-
lang = metadata.judge_method.judge_code_lang
45+
# TODO: Use judge_lang instead of lang
4646
compile_cmd = lang.get_compile_command('judge')
4747
code_filename = lang.get_code_filename('judge')
4848
exec_filename = lang.get_exec_filename('judge')

atcodertools/tools/models/metadata.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from atcodertools.client.models.problem import Problem
55
from atcodertools.common.judgetype import NormalJudge, DecimalJudge, MultiSolutionJudge, InteractiveJudge, Judge, \
66
NoJudgeTypeException
7-
from atcodertools.common.language import Language
7+
from atcodertools.common.language import Language, CPP
88

99
DEFAULT_IN_EXAMPLE_PATTERN = 'in_*.txt'
1010
DEFAULT_OUT_EXAMPLE_PATTERN = "out_*.txt"
@@ -45,9 +45,9 @@ def from_dict(cls, dic):
4545
elif judge_type == "decimal":
4646
judge_method = DecimalJudge.from_dict(dic["judge"])
4747
elif judge_type == "multisolution":
48-
judge_method = MultiSolutionJudge.from_dict(dic["judge"])
48+
judge_method = MultiSolutionJudge()
4949
elif judge_type == "interactive":
50-
judge_method = InteractiveJudge.from_dict(dic["judge"])
50+
judge_method = InteractiveJudge()
5151
else:
5252
raise NoJudgeTypeException()
5353
else:
@@ -78,6 +78,6 @@ def save_to(self, filename):
7878
code_filename=None,
7979
sample_in_pattern=DEFAULT_IN_EXAMPLE_PATTERN,
8080
sample_out_pattern=DEFAULT_OUT_EXAMPLE_PATTERN,
81-
lang=None,
81+
lang=CPP,
8282
judge_method=NormalJudge()
8383
)

0 commit comments

Comments
 (0)