Skip to content

Commit b97864a

Browse files
Support testlib SPJ
1 parent a131374 commit b97864a

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

cyaron/graders/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .graderregistry import CYaRonGraders, GraderType2, GraderType3
22

33
from .fulltext import fulltext
4-
from .noipstyle import noipstyle
4+
from .noipstyle import noipstyle
5+
from .testlib_checker import TestlibChecker

cyaron/graders/graderregistry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def wrapper(func: GraderType2):
2323
return func
2424

2525
return wrapper
26-
26+
2727
grader = grader2
2828

2929
def grader3(self, name: str):
@@ -47,7 +47,7 @@ def invoke(self, grader: Union[str, GraderType3], content: str, std: str,
4747
else:
4848
return grader(content, std, input_content)
4949

50-
def check(self, name):
50+
def check(self, name: str):
5151
"""Check if a grader is registered."""
5252
return name in self._registry
5353

cyaron/graders/testlib_checker.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import tempfile
2+
import subprocess
3+
import os
4+
import xml.etree.ElementTree as xmlElementTree
5+
from typing import Optional
6+
7+
STDOUT_DEV = "con" if os.name == "nt" else "/dev/stdout"
8+
9+
__all__ = ["TestlibChecker"]
10+
11+
12+
class TestlibCheckerResult:
13+
14+
def __init__(self, result: Optional[str], outcome: str,
15+
pctype: Optional[str]):
16+
self.result = result
17+
self.outcome = outcome
18+
self.pctype = pctype
19+
20+
def __str__(self):
21+
return ' '.join([self.outcome] +
22+
([] if self.pctype is None else [f'({self.pctype})']) +
23+
([] if self.result is None else [self.result]))
24+
25+
26+
class TestlibChecker:
27+
"""
28+
A grader that uses the testlib checker.
29+
"""
30+
31+
def __init__(self, checker_path: str):
32+
self.checker_path = checker_path
33+
34+
def __call__(self, outs: str, ans: str, ins: str):
35+
with tempfile.NamedTemporaryFile(
36+
'w') as inf, tempfile.NamedTemporaryFile(
37+
'w') as outf, tempfile.NamedTemporaryFile('w') as ansf:
38+
inf.write(ins)
39+
outf.write(outs)
40+
ansf.write(ans)
41+
inf.flush()
42+
outf.flush()
43+
ansf.flush()
44+
result = subprocess.run((self.checker_path, inf.name, outf.name,
45+
ansf.name, STDOUT_DEV, '-appes'),
46+
stdout=subprocess.PIPE,
47+
stderr=subprocess.PIPE,
48+
text=True,
49+
check=False)
50+
checker_output = result.stdout
51+
52+
result_element = xmlElementTree.fromstring(checker_output)
53+
if result_element.tag != 'result':
54+
raise ValueError("Invalid output from checker")
55+
result_text = result_element.text
56+
result_outcome = result_element.get('outcome')
57+
if result_outcome is None:
58+
raise ValueError("Invalid output from checker")
59+
result_pctype = result_element.get('pctype')
60+
return result_outcome == 'accepted', TestlibCheckerResult(
61+
result_text, result_outcome, result_pctype)

0 commit comments

Comments
 (0)