Skip to content

Commit 89a4970

Browse files
Add support for package with checker in run (#40)
* Add support for checker in `run` * Add simple package for checker * Fix tests and refactor * Add checker package to tests * Fix tests * Apply suggestions from code review Co-authored-by: Tomasz Nowak <[email protected]> * Add file diff function * Add tests for file_diff function * Fix indentation * Redirect checker's stderr to /dev/null * Assume that correct answer files are generated when using checker * Fix tests * Fixes errors * Change point calculation * Change how sinol_expected_scores looks * Fix checker package config * Apply suggestions from code review Co-authored-by: Tomasz Nowak <[email protected]> * Refactor changes list creation * Refactor point's change print message * Pretty error printing from checker * Version bump --------- Co-authored-by: Tomasz Nowak <[email protected]>
1 parent 4a34229 commit 89a4970

24 files changed

+491
-72
lines changed

src/sinol_make/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from sinol_make import util
55

6-
__version__ = "1.2.2"
6+
__version__ = "1.3.0"
77

88
def configure_parsers():
99
parser = argparse.ArgumentParser(

src/sinol_make/commands/run/__init__.py

+189-34
Large diffs are not rendered by default.

src/sinol_make/commands/run/structs.py

+18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ class ResultChange:
88
old_result: str
99
result: str
1010

11+
@dataclass
12+
class PointsChange:
13+
solution: str
14+
group: int
15+
old_points: int
16+
new_points: int
17+
1118
@dataclass
1219
class ValidationResult:
1320
added_solutions: set
@@ -27,6 +34,17 @@ class ExecutionResult:
2734
Time: float
2835
# Memory in KB
2936
Memory: int
37+
# Points for this test
38+
Points: int
39+
# Error message
40+
Error: str
41+
42+
def __init__(self, status=None):
43+
self.Status = status
44+
self.Time = None
45+
self.Memory = None
46+
self.Points = 0
47+
self.Error = None
3048

3149
@dataclass
3250
class ExecutionData:

src/sinol_make/interfaces/Errors.py

+7
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ def __init__(self, message):
44

55
def __str__(self):
66
return self.message
7+
8+
class CheckerOutputException(Exception):
9+
def __init__(self, message):
10+
self.message = message
11+
12+
def __str__(self):
13+
return self.message

tests/commands/run/test_integration.py

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import yaml, pytest
2-
from ...util import *
31
from ...fixtures import *
42
from .util import *
53
from sinol_make import configure_parsers
64

75

8-
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path()], indirect=True)
6+
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
7+
get_checker_package_path()], indirect=True)
98
def test_simple(create_package, time_tool):
109
"""
1110
Test a simple run.
1211
"""
1312
package_path = create_package
14-
command = get_command()
1513
create_ins_outs(package_path)
1614

1715
parser = configure_parsers()
@@ -21,15 +19,15 @@ def test_simple(create_package, time_tool):
2119
command.run(args)
2220

2321

24-
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path()], indirect=True)
22+
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
23+
get_checker_package_path()], indirect=True)
2524
def test_no_expected_scores(capsys, create_package, time_tool):
2625
"""
2726
Test with no sinol_expected_scores in config.yml.
2827
Should run, but exit with exit code 1.
2928
Checks if a message about added solutions is printed.
3029
"""
3130
package_path = create_package
32-
command = get_command()
3331
create_ins_outs(package_path)
3432

3533
config_path = os.path.join(package_path, "config.yml")
@@ -48,17 +46,19 @@ def test_no_expected_scores(capsys, create_package, time_tool):
4846

4947
out = capsys.readouterr().out
5048
assert "Solutions were added:" in out
49+
solution = glob.glob(os.path.join(package_path, "prog", "???.*"))[0]
50+
assert os.path.basename(solution) in out
5151

5252

53-
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path()], indirect=True)
53+
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
54+
get_checker_package_path()], indirect=True)
5455
def test_apply_suggestions(create_package, time_tool):
5556
"""
5657
Test with no sinol_expected_scores in config.yml.
5758
Verifies that suggestions are applied.
5859
Checks if the genereated config.yml is correct.
5960
"""
6061
package_path = create_package
61-
command = get_command()
6262
create_ins_outs(package_path)
6363

6464
config_path = os.path.join(package_path, "config.yml")
@@ -83,7 +83,6 @@ def test_incorrect_expected_scores(capsys, create_package, time_tool):
8383
Checks if a message about incorrect result is printed.
8484
"""
8585
package_path = create_package
86-
command = get_command()
8786
create_ins_outs(package_path)
8887

8988
config_path = os.path.join(package_path, "config.yml")
@@ -106,42 +105,47 @@ def test_incorrect_expected_scores(capsys, create_package, time_tool):
106105
assert "Solution abc.cpp passed group 1 with status OK while it should pass with status WA." in out
107106

108107

108+
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_checker_package_path()], indirect=True)
109109
def test_flag_tests(create_package, time_tool):
110110
"""
111111
Test flag --tests.
112112
Checks if correct tests are run.
113113
"""
114114
package_path = create_package
115-
command = get_command()
116115
create_ins_outs(package_path)
117116

117+
test = glob.glob(os.path.join(package_path, "in", "???*.in"))[0]
118118
parser = configure_parsers()
119-
args = parser.parse_args(["run", "--tests", "in/abc1a.in", "--time_tool", time_tool])
119+
args = parser.parse_args(["run", "--tests", test, "--time_tool", time_tool])
120120
command = Command()
121-
command.run(args)
121+
try:
122+
command.run(args)
123+
except SystemExit:
124+
pass
122125

123-
assert command.tests == ["in/abc1a.in"]
126+
assert command.tests == [test]
124127

125128

129+
@pytest.mark.parametrize("create_package", [get_simple_package_path(), get_verify_status_package_path(),
130+
get_checker_package_path()], indirect=True)
126131
def test_flag_solutions(capsys, create_package, time_tool):
127132
"""
128133
Test flag --solutions.
129134
Checks if correct solutions are run (by checking the output).
130135
"""
131136
package_path = create_package
132-
command = get_command()
133137
create_ins_outs(package_path)
134138

139+
solutions = glob.glob(os.path.join(package_path, "prog", "????.*"))
135140
parser = configure_parsers()
136-
args = parser.parse_args(["run", "--solutions", "prog/abc1.cpp", "prog/abc2.cpp", "--time_tool", time_tool])
141+
args = parser.parse_args(["run", "--solutions", solutions[0], "--time_tool", time_tool])
137142
command = Command()
138143
command.run(args)
139144

140145
out = capsys.readouterr().out
141146

142-
assert "abc1.cpp" in out
143-
assert "abc2.cpp" in out
144-
assert "abc3.cpp" not in out
147+
assert os.path.basename(solutions[0]) in out
148+
assert os.path.basename(solutions[1]) not in out
145149

146150

147151
@pytest.mark.parametrize("create_package", [get_weak_compilation_flags_package_path()], indirect=True)

tests/commands/run/test_unit.py

+21-15
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,15 @@ def test_run_solutions(create_package, time_tool):
8282
command.time_limit = command.config["time_limit"]
8383
command.timetool_path = util.get_oiejq_path()
8484

85-
print(command.compile_and_run(["abc.cpp"]))
86-
assert command.compile_and_run(["abc.cpp"]) == {"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK"}}
87-
assert command.compile_and_run(["abc.cpp", "abc4.cpp"]) == {
85+
def flatten_results(results):
86+
new_results = {}
87+
for solution in results.keys():
88+
new_results[solution] = dict((group, group_result["status"])
89+
for group, group_result in results[solution].items())
90+
return new_results
91+
92+
assert flatten_results(command.compile_and_run(["abc.cpp"])[0]) == {"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK"}}
93+
assert flatten_results(command.compile_and_run(["abc.cpp", "abc4.cpp"])[0]) == {
8894
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK"},
8995
"abc4.cpp": {1: "OK", 2: "OK", 3: "WA", 4: "RE"}
9096
}
@@ -126,7 +132,7 @@ def test_validate_expected_scores_success():
126132
# Test with correct expected scores.
127133
command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=None)
128134
results = {
129-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK"},
135+
"abc.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}, 3: {"status": "OK", "points": 25}, 4: {"status": "OK", "points": 25}},
130136
}
131137
results = command.validate_expected_scores(results)
132138
assert results.expected_scores == results.new_expected_scores
@@ -135,7 +141,7 @@ def test_validate_expected_scores_success():
135141
# Test with incorrect result.
136142
command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=None)
137143
results = {
138-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "WA"},
144+
"abc.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}, 3: {"status": "OK", "points": 25}, 4: {"status": "WA", "points": 0}},
139145
}
140146
results = command.validate_expected_scores(results)
141147
assert results.expected_scores != results.new_expected_scores
@@ -144,10 +150,10 @@ def test_validate_expected_scores_success():
144150
# Test with removed solution.
145151
command.args = argparse.Namespace(solutions=None, tests=None)
146152
results = {
147-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK"},
148-
"abc1.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "WA"},
149-
"abc2.cpp": {1: "OK", 2: "WA", 3: "WA", 4: "TL"},
150-
"abc3.cpp": {1: "OK", 2: "WA", 3: "WA", 4: "ML"},
153+
"abc.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}, 3: {"status": "OK", "points": 25}, 4: {"status": "OK", "points": 25}},
154+
"abc1.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}, 3: {"status": "OK", "points": 25}, 4: {"status": "WA", "points": 0}},
155+
"abc2.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "WA", "points": 0}, 3: {"status": "WA", "points": 0}, 4: {"status": "TL", "points": 0}},
156+
"abc3.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "WA", "points": 0}, 3: {"status": "WA", "points": 0}, 4: {"status": "ML", "points": 0}},
151157
}
152158
results = command.validate_expected_scores(results)
153159
assert results.expected_scores != results.new_expected_scores
@@ -157,8 +163,8 @@ def test_validate_expected_scores_success():
157163
command.config["scores"][5] = 0
158164
command.args = argparse.Namespace(solutions=["prog/abc.cpp", "prog/abc5.cpp"], tests=None)
159165
results = {
160-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK", 5: "WA"},
161-
"abc5.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK", 5: "WA"},
166+
"abc.cpp": {1: {"status": "OK", "points": 20}, 2: {"status": "OK", "points": 20}, 3: {"status": "OK", "points": 20}, 4: {"status": "OK", "points": 20}, 5: {"status": "WA", "points": 0}},
167+
"abc5.cpp": {1: {"status": "OK", "points": 20}, 2: {"status": "OK", "points": 20}, 3: {"status": "OK", "points": 20}, 4: {"status": "OK", "points": 20}, 5: {"status": "WA", "points": 0}},
162168
}
163169
results = command.validate_expected_scores(results)
164170
assert results.expected_scores != results.new_expected_scores
@@ -168,7 +174,7 @@ def test_validate_expected_scores_success():
168174
# Test with removed group.
169175
command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=None)
170176
results = {
171-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK"},
177+
"abc.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}, 3: {"status": "OK", "points": 25}},
172178
}
173179
results = command.validate_expected_scores(results)
174180
assert results.expected_scores != results.new_expected_scores
@@ -177,7 +183,7 @@ def test_validate_expected_scores_success():
177183
# Test with correct expected scores and --tests flag.
178184
command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=["in/abc1a.in", "in/abc2a.in"])
179185
results = {
180-
"abc.cpp": {1: "OK", 2: "OK"},
186+
"abc.cpp": {1: {"status": "OK", "points": 25}, 2: {"status": "OK", "points": 25}},
181187
}
182188
results = command.validate_expected_scores(results)
183189
assert results.expected_scores == results.new_expected_scores
@@ -186,12 +192,12 @@ def test_validate_expected_scores_success():
186192
def test_validate_expected_scores_fail(capsys):
187193
command = get_command()
188194
os.chdir(get_simple_package_path())
189-
command.scores = command.config["scores"]
195+
command.scores = {1: 20, 2: 20, 3: 20, 4: 20}
190196

191197
# Test with missing points for group in config.
192198
command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=None)
193199
results = {
194-
"abc.cpp": {1: "OK", 2: "OK", 3: "OK", 4: "OK", 5: "OK"},
200+
"abc.cpp": {1: {"status": "OK", "points": 20}, 2: {"status": "OK", "points": 20}, 3: {"status": "OK", "points": 20}, 4: {"status": "OK", "points": 20}, 5: {"status": "OK", "points": 20}},
195201
}
196202
with pytest.raises(SystemExit) as e:
197203
command.validate_expected_scores(results)

tests/commands/run/util.py

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ...util import *
88
from sinol_make.commands.run import Command
99
from sinol_make.helpers import compiler
10+
from sinol_make.helpers import compile
1011

1112

1213
def get_command(path = None):
@@ -25,6 +26,7 @@ def get_command(path = None):
2526
java_compiler_path=compiler.get_java_compiler_path()
2627
)
2728
command.config = yaml.load(open(os.path.join(path, "config.yml"), "r"), Loader=yaml.FullLoader)
29+
command.checker = None
2830
command.failed_compilations = []
2931
set_default_args(command)
3032
return command

tests/packages/chk/config.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
title: Package with output checker
2+
memory_limit: 16000
3+
time_limit: 1000
4+
scores:
5+
1: 50
6+
2: 50
7+
sinol_expected_scores:
8+
chk.cpp:
9+
expected: {1: OK, 2: OK}
10+
points: 100
11+
chk1.cpp:
12+
expected: {1: WA, 2: WA}
13+
points: 0
14+
chk2.cpp:
15+
expected:
16+
1: {points: 25, status: OK}
17+
2: {points: 25, status: OK}
18+
points: 50
19+
chk3.cpp:
20+
expected: {1: OK, 2: WA}
21+
points: 50

tests/packages/chk/in/.gitkeep

Whitespace-only changes.

tests/packages/chk/out/.gitkeep

Whitespace-only changes.

tests/packages/chk/out/chk1a.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6

tests/packages/chk/out/chk1b.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-1

tests/packages/chk/out/chk1c.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6

tests/packages/chk/out/chk2a.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
5

tests/packages/chk/out/chk2b.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2

tests/packages/chk/out/chk2c.out

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-1

tests/packages/chk/prog/chk.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include <bits/stdc++.h>
2+
3+
using namespace std;
4+
5+
int main() {
6+
int n, s = 0;
7+
8+
cin >> n;
9+
for (int i = 0; i < n; i++) {
10+
int a;
11+
cin >> a;
12+
s += a;
13+
}
14+
15+
if (s % n != 0)
16+
cout << -1 << endl;
17+
else
18+
cout << s / 2 - 1 << endl;
19+
}

tests/packages/chk/prog/chk1.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <bits/stdc++.h>
2+
3+
using namespace std;
4+
5+
int main() {
6+
int n, s = 0;
7+
cin >> n;
8+
9+
for (int i = 0; i < n; i++) {
10+
int a;
11+
cin >> a;
12+
s += a;
13+
}
14+
15+
cout << s + 1 << endl;
16+
}

tests/packages/chk/prog/chk2.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <bits/stdc++.h>
2+
3+
using namespace std;
4+
5+
int main() {
6+
int n, s = 0;
7+
8+
cin >> n;
9+
for (int i = 0; i < n; i++) {
10+
int a;
11+
cin >> a;
12+
s += a;
13+
}
14+
15+
if (s % n != 0) {
16+
cout << "-1";
17+
return 0;
18+
}
19+
20+
cout << s / 2 + 1;
21+
}

0 commit comments

Comments
 (0)