sinol-make 1.6.0.dev4__tar.gz → 1.7.0.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sinol_make-1.6.0.dev4/src/sinol_make.egg-info → sinol_make-1.7.0.dev1}/PKG-INFO +4 -1
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/README.md +3 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/setup.cfg +1 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/__init__.py +3 -2
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/export/__init__.py +1 -1
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/gen/__init__.py +4 -4
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/ingen/__init__.py +25 -9
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/inwer/__init__.py +1 -3
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/outgen/__init__.py +4 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/run/__init__.py +36 -305
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/verify/__init__.py +10 -4
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/__init__.py +3 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/default.py +6 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/icpc.py +3 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/oi.py +4 -1
- sinol_make-1.7.0.dev1/src/sinol_make/contest_types/oij.py +32 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/cache.py +3 -3
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/compile.py +4 -5
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/package_util.py +2 -1
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/parsers.py +6 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/oiejq/__init__.py +20 -8
- sinol_make-1.7.0.dev1/src/sinol_make/structs/package_structs.py +7 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/run_structs.py +2 -0
- sinol_make-1.7.0.dev1/src/sinol_make/task_type/__init__.py +17 -0
- sinol_make-1.7.0.dev1/src/sinol_make/task_type/base.py +273 -0
- sinol_make-1.7.0.dev1/src/sinol_make/task_type/interactive_io.py +219 -0
- sinol_make-1.7.0.dev1/src/sinol_make/task_type/normal.py +77 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/util.py +1 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1/src/sinol_make.egg-info}/PKG-INFO +4 -1
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/SOURCES.txt +6 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/entry_points.txt +1 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/tests/test_oiejq.py +41 -4
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/LICENSE +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/pyproject.toml +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/doc/__init__.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/ingen/ingen_util.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/init/__init__.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/inwer/inwer_util.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/outgen/outgen_util.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/__init__.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/compiler.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/func_cache.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/paths.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/printer.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/BaseCommand.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/Errors.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/__init__.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/oiejq/perf_test.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/__init__.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/cache_structs.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/compiler_structs.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/gen_structs.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/inwer_structs.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/status_structs.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/dependency_links.txt +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/requires.txt +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/top_level.txt +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/tests/test_multiple_arguments.py +0 -0
- {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/tests/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sinol_make
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0.dev1
|
|
4
4
|
Summary: CLI tool for creating sio2 task packages
|
|
5
5
|
Author: Mateusz Masiarz
|
|
6
6
|
Author-email: m.masiarz@fri.edu.pl
|
|
@@ -113,6 +113,9 @@ Run `sinol-make ingen --help` to see available flags.
|
|
|
113
113
|
program to use, what tests to check and how many CPUs to use. Run `sinol-make inwer --help` to see available flags.
|
|
114
114
|
- `sinol-make export` -- Creates archive ready to upload to sio2 or szkopul. Run `sinol-make export --help` to see all available flags.
|
|
115
115
|
- `sinol-make doc` -- Compiles all LaTeX files in doc/ directory to PDF. Run `sinol-make doc --help` to see all available flags.
|
|
116
|
+
- `sinol-make verify` -- Verifies the package. This command runs stress tests (if available), verifies the config,
|
|
117
|
+
generates tests, generates problem statements, runs inwer and run all solutions. Ingen and inwer are compiled with
|
|
118
|
+
address and UB sanitizers. Run `sinol-make verify --help` to see all available flags.
|
|
116
119
|
- `sinol-make init [id]` -- Creates package from template [on github](https://github.com/sio2project/sinol-make/tree/main/example_package) and sets task id to provided `[id]`. Requires an internet connection to run.
|
|
117
120
|
|
|
118
121
|
You can also run multiple commands at once, for example:
|
|
@@ -83,6 +83,9 @@ Run `sinol-make ingen --help` to see available flags.
|
|
|
83
83
|
program to use, what tests to check and how many CPUs to use. Run `sinol-make inwer --help` to see available flags.
|
|
84
84
|
- `sinol-make export` -- Creates archive ready to upload to sio2 or szkopul. Run `sinol-make export --help` to see all available flags.
|
|
85
85
|
- `sinol-make doc` -- Compiles all LaTeX files in doc/ directory to PDF. Run `sinol-make doc --help` to see all available flags.
|
|
86
|
+
- `sinol-make verify` -- Verifies the package. This command runs stress tests (if available), verifies the config,
|
|
87
|
+
generates tests, generates problem statements, runs inwer and run all solutions. Ingen and inwer are compiled with
|
|
88
|
+
address and UB sanitizers. Run `sinol-make verify --help` to see all available flags.
|
|
86
89
|
- `sinol-make init [id]` -- Creates package from template [on github](https://github.com/sio2project/sinol-make/tree/main/example_package) and sets task id to provided `[id]`. Requires an internet connection to run.
|
|
87
90
|
|
|
88
91
|
You can also run multiple commands at once, for example:
|
|
@@ -6,7 +6,8 @@ import argcomplete
|
|
|
6
6
|
|
|
7
7
|
from sinol_make import util, oiejq
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
__version__ = "1.7.0.dev1"
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def configure_parsers():
|
|
@@ -33,7 +34,7 @@ def configure_parsers():
|
|
|
33
34
|
|
|
34
35
|
def check_oiejq():
|
|
35
36
|
if util.is_linux() and not oiejq.check_oiejq():
|
|
36
|
-
print(util.warning('`oiejq` in `~/.local/bin/` not found, installing
|
|
37
|
+
print(util.warning('Up to date `oiejq` in `~/.local/bin/` not found, installing new version...'))
|
|
37
38
|
try:
|
|
38
39
|
if oiejq.install_oiejq():
|
|
39
40
|
print(util.info('`oiejq` was successfully installed.'))
|
|
@@ -119,7 +119,7 @@ class Command(BaseCommand):
|
|
|
119
119
|
if num_tests == 0:
|
|
120
120
|
print(util.warning('No ocen tests found.'))
|
|
121
121
|
elif os.path.exists(dlazaw_dir):
|
|
122
|
-
print(util.warning('Skipping ocen
|
|
122
|
+
print(util.warning('Skipping ocen archive creation because dlazaw directory exists.'))
|
|
123
123
|
else:
|
|
124
124
|
shutil.make_archive(os.path.join(attachments_dir, f'{self.task_id}ocen'), 'zip', tmpdir)
|
|
125
125
|
|
|
@@ -5,6 +5,7 @@ from sinol_make.commands.ingen import Command as IngenCommand
|
|
|
5
5
|
from sinol_make.commands.outgen import Command as OutgenCommand
|
|
6
6
|
from sinol_make.helpers import parsers
|
|
7
7
|
from sinol_make.interfaces.BaseCommand import BaseCommand
|
|
8
|
+
from sinol_make.task_type import get_task_type
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Command(BaseCommand):
|
|
@@ -34,14 +35,13 @@ class Command(BaseCommand):
|
|
|
34
35
|
parsers.add_cpus_argument(parser, 'number of cpus to use to generate output files')
|
|
35
36
|
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
|
|
36
37
|
help='do not validate test contents')
|
|
37
|
-
|
|
38
|
-
help='Use -fsanitize=address,undefined for ingen compilation. Warning: this may fail on some '
|
|
39
|
-
'systems. To fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
38
|
+
parsers.add_fsanitize_argument(parser)
|
|
40
39
|
parsers.add_compilation_arguments(parser)
|
|
41
40
|
return parser
|
|
42
41
|
|
|
43
42
|
def run(self, args: argparse.Namespace):
|
|
44
43
|
args = util.init_package_command(args)
|
|
44
|
+
task_type = get_task_type()
|
|
45
45
|
|
|
46
46
|
self.args = args
|
|
47
47
|
self.ins = args.only_inputs
|
|
@@ -55,6 +55,6 @@ class Command(BaseCommand):
|
|
|
55
55
|
command = IngenCommand()
|
|
56
56
|
command.run(args)
|
|
57
57
|
|
|
58
|
-
if self.outs:
|
|
58
|
+
if self.outs and task_type.run_outgen():
|
|
59
59
|
command = OutgenCommand()
|
|
60
60
|
command.run(args)
|
|
@@ -31,19 +31,39 @@ class Command(BaseCommand):
|
|
|
31
31
|
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
|
|
32
32
|
help='do not validate test contents')
|
|
33
33
|
parsers.add_cpus_argument(parser, 'number of cpus used for validating tests')
|
|
34
|
-
|
|
35
|
-
help='Use -fsanitize=address,undefined for compilation. Warning: this may fail on some '
|
|
36
|
-
'systems. Tof fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
34
|
+
parsers.add_fsanitize_argument(parser)
|
|
37
35
|
parsers.add_compilation_arguments(parser)
|
|
38
36
|
return parser
|
|
39
37
|
|
|
38
|
+
def delete_dangling_files(self, dates):
|
|
39
|
+
to_delete = set()
|
|
40
|
+
for test in glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in")):
|
|
41
|
+
basename = os.path.basename(test)
|
|
42
|
+
if basename in dates and dates[basename] == os.path.getmtime(test):
|
|
43
|
+
to_delete.add(os.path.basename(test))
|
|
44
|
+
if to_delete:
|
|
45
|
+
config = package_util.get_config()
|
|
46
|
+
if 'sinol_static_tests' not in config:
|
|
47
|
+
print(util.warning('Old input files won\'t be deleted, '
|
|
48
|
+
'because static tests are not defined. '
|
|
49
|
+
'You can define them in config.yml with `sinol_static_tests` key.'))
|
|
50
|
+
else:
|
|
51
|
+
static_files = config['sinol_static_tests']
|
|
52
|
+
if isinstance(static_files, str):
|
|
53
|
+
static_files = [static_files]
|
|
54
|
+
static_files = set([os.path.basename(test) for test in static_files])
|
|
55
|
+
to_delete = to_delete - static_files
|
|
56
|
+
if to_delete:
|
|
57
|
+
print('Cleaning up old input files.')
|
|
58
|
+
for test in to_delete:
|
|
59
|
+
os.remove(os.path.join(os.getcwd(), "in", test))
|
|
60
|
+
|
|
40
61
|
def run(self, args: argparse.Namespace):
|
|
41
62
|
args = util.init_package_command(args)
|
|
42
63
|
|
|
43
64
|
self.args = args
|
|
44
65
|
|
|
45
66
|
self.task_id = package_util.get_task_id()
|
|
46
|
-
package_util.validate_test_names(self.task_id)
|
|
47
67
|
util.change_stack_size_to_unlimited()
|
|
48
68
|
self.ingen = get_ingen(self.task_id, args.ingen_path)
|
|
49
69
|
print(f'Using ingen file {os.path.basename(self.ingen)}')
|
|
@@ -65,11 +85,7 @@ class Command(BaseCommand):
|
|
|
65
85
|
else:
|
|
66
86
|
util.exit_with_error('Failed to generate input files.')
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
for test in glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in")):
|
|
70
|
-
basename = os.path.basename(test)
|
|
71
|
-
if basename in dates and dates[basename] == os.path.getmtime(test):
|
|
72
|
-
os.unlink(test)
|
|
88
|
+
self.delete_dangling_files(dates)
|
|
73
89
|
|
|
74
90
|
with open(paths.get_cache_path("input_tests"), "w") as f:
|
|
75
91
|
f.write("\n".join(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in"))))
|
|
@@ -37,9 +37,7 @@ class Command(BaseCommand):
|
|
|
37
37
|
parser.add_argument('-t', '--tests', type=str, nargs='+',
|
|
38
38
|
help='test to verify, for example in/abc{0,1}*')
|
|
39
39
|
parsers.add_cpus_argument(parser, 'number of cpus to use when verifying tests')
|
|
40
|
-
|
|
41
|
-
help='Use -fsanitize=address,undefined for compilation. Warning: this may fail on some '
|
|
42
|
-
'systems. Tof fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
40
|
+
parsers.add_fsanitize_argument(parser)
|
|
43
41
|
parsers.add_compilation_arguments(parser)
|
|
44
42
|
return parser
|
|
45
43
|
|
|
@@ -10,6 +10,7 @@ from sinol_make.commands.outgen.outgen_util import get_correct_solution, compile
|
|
|
10
10
|
from sinol_make.structs.gen_structs import OutputGenerationArguments
|
|
11
11
|
from sinol_make.helpers import parsers, package_util, cache, paths
|
|
12
12
|
from sinol_make.interfaces.BaseCommand import BaseCommand
|
|
13
|
+
from sinol_make.task_type import get_task_type
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class Command(BaseCommand):
|
|
@@ -106,6 +107,9 @@ class Command(BaseCommand):
|
|
|
106
107
|
|
|
107
108
|
def run(self, args: argparse.Namespace):
|
|
108
109
|
args = util.init_package_command(args)
|
|
110
|
+
task_type = get_task_type()
|
|
111
|
+
if not task_type.run_outgen():
|
|
112
|
+
util.exit_with_error('This task type does not support output generation.')
|
|
109
113
|
|
|
110
114
|
self.args = args
|
|
111
115
|
self.task_id = package_util.get_task_id()
|
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
import signal
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
|
+
import traceback
|
|
8
9
|
import psutil
|
|
9
10
|
import glob
|
|
10
11
|
import shutil
|
|
@@ -17,7 +18,7 @@ import multiprocessing as mp
|
|
|
17
18
|
from io import StringIO
|
|
18
19
|
from typing import Dict
|
|
19
20
|
|
|
20
|
-
from sinol_make import contest_types, oiejq, util
|
|
21
|
+
from sinol_make import contest_types, oiejq, task_type, util
|
|
21
22
|
from sinol_make.structs.run_structs import ExecutionData, PrintData
|
|
22
23
|
from sinol_make.structs.cache_structs import CacheTest, CacheFile
|
|
23
24
|
from sinol_make.interfaces.BaseCommand import BaseCommand
|
|
@@ -304,19 +305,11 @@ class Command(BaseCommand):
|
|
|
304
305
|
parser.add_argument('--ignore-expected', dest='ignore_expected', action='store_true',
|
|
305
306
|
help='ignore expected scores from config.yml. When this flag is set, '
|
|
306
307
|
'the expected scores are not compared with the actual scores.')
|
|
308
|
+
parser.add_argument('--no-outputs', dest='allow_no_outputs', action='store_true',
|
|
309
|
+
help='allow running the script without full outputs')
|
|
307
310
|
parsers.add_compilation_arguments(parser)
|
|
308
311
|
return parser
|
|
309
312
|
|
|
310
|
-
def parse_time(self, time_str):
|
|
311
|
-
if len(time_str) < 3: return -1
|
|
312
|
-
return int(time_str[:-2])
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
def parse_memory(self, memory_str):
|
|
316
|
-
if len(memory_str) < 3: return -1
|
|
317
|
-
return int(memory_str[:-2])
|
|
318
|
-
|
|
319
|
-
|
|
320
313
|
def extract_file_name(self, file_path):
|
|
321
314
|
return os.path.split(file_path)[1]
|
|
322
315
|
|
|
@@ -332,9 +325,6 @@ class Command(BaseCommand):
|
|
|
332
325
|
return file + ext
|
|
333
326
|
util.exit_with_error("Source file not found for executable %s" % executable)
|
|
334
327
|
|
|
335
|
-
def get_executables(self, args_solutions):
|
|
336
|
-
return [package_util.get_executable(solution) for solution in package_util.get_solutions(self.ID, args_solutions)]
|
|
337
|
-
|
|
338
328
|
|
|
339
329
|
def get_possible_score(self, groups):
|
|
340
330
|
possible_score = 0
|
|
@@ -351,17 +341,19 @@ class Command(BaseCommand):
|
|
|
351
341
|
return sorted(list(set([self.get_group(test) for test in tests])))
|
|
352
342
|
|
|
353
343
|
|
|
354
|
-
def compile_solutions(self, solutions,
|
|
344
|
+
def compile_solutions(self, solutions, remove_all_cache=False):
|
|
355
345
|
os.makedirs(paths.get_compilation_log_path(), exist_ok=True)
|
|
356
346
|
os.makedirs(paths.get_executables_path(), exist_ok=True)
|
|
357
347
|
print("Compiling %d solutions..." % len(solutions))
|
|
358
|
-
args = [(solution, True,
|
|
348
|
+
args = [(solution, True, remove_all_cache) for solution in solutions]
|
|
359
349
|
with mp.Pool(self.cpus) as pool:
|
|
360
350
|
compilation_results = pool.starmap(self.compile, args)
|
|
361
351
|
return compilation_results
|
|
362
352
|
|
|
363
353
|
|
|
364
|
-
def compile(self, solution, use_extras
|
|
354
|
+
def compile(self, solution, use_extras=False, remove_all_cache=False):
|
|
355
|
+
os.makedirs(paths.get_compilation_log_path(), exist_ok=True)
|
|
356
|
+
os.makedirs(paths.get_executables_path(), exist_ok=True)
|
|
365
357
|
compile_log_file = paths.get_compilation_log_path("%s.compile_log" % package_util.get_file_name(solution))
|
|
366
358
|
source_file = os.path.join(os.getcwd(), "prog", self.get_solution_from_exe(solution))
|
|
367
359
|
output = paths.get_executables_path(package_util.get_executable(solution))
|
|
@@ -382,7 +374,7 @@ class Command(BaseCommand):
|
|
|
382
374
|
try:
|
|
383
375
|
with open(compile_log_file, "w") as compile_log:
|
|
384
376
|
compile.compile(source_file, output, self.compilers, compile_log, self.args.compile_mode,
|
|
385
|
-
extra_compilation_args, extra_compilation_files,
|
|
377
|
+
extra_compilation_args, extra_compilation_files, remove_all_cache=remove_all_cache)
|
|
386
378
|
print(util.info("Compilation of file %s was successful."
|
|
387
379
|
% package_util.get_file_name(solution)))
|
|
388
380
|
return True
|
|
@@ -392,262 +384,21 @@ class Command(BaseCommand):
|
|
|
392
384
|
compile.print_compile_log(compile_log_file)
|
|
393
385
|
return False
|
|
394
386
|
|
|
395
|
-
def check_output_diff(self, output_file, answer_file):
|
|
396
|
-
"""
|
|
397
|
-
Checks whether the output file and the answer file are the same.
|
|
398
|
-
"""
|
|
399
|
-
return util.file_diff(output_file, answer_file)
|
|
400
|
-
|
|
401
|
-
def check_output_checker(self, name, input_file, output_file, answer_file):
|
|
402
|
-
"""
|
|
403
|
-
Checks if the output file is correct with the checker.
|
|
404
|
-
Returns True if the output file is correct, False otherwise and number of points.
|
|
405
|
-
"""
|
|
406
|
-
command = [self.checker_executable, input_file, output_file, answer_file]
|
|
407
|
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
408
|
-
process.wait()
|
|
409
|
-
checker_output = process.communicate()[0].decode("utf-8").splitlines()
|
|
410
|
-
|
|
411
|
-
if len(checker_output) == 0:
|
|
412
|
-
raise CheckerOutputException("Checker output is empty.")
|
|
413
|
-
|
|
414
|
-
if checker_output[0].strip() == "OK":
|
|
415
|
-
points = 100
|
|
416
|
-
if len(checker_output) >= 3:
|
|
417
|
-
try:
|
|
418
|
-
points = int(checker_output[2].strip())
|
|
419
|
-
except ValueError:
|
|
420
|
-
pass
|
|
421
|
-
|
|
422
|
-
return True, points
|
|
423
|
-
else:
|
|
424
|
-
return False, 0
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
def check_output(self, name, input_file, output_file_path, output, answer_file_path):
|
|
428
|
-
"""
|
|
429
|
-
Checks if the output file is correct.
|
|
430
|
-
Returns a tuple (is correct, number of points).
|
|
431
|
-
"""
|
|
432
|
-
try:
|
|
433
|
-
has_checker = self.checker is not None
|
|
434
|
-
except AttributeError:
|
|
435
|
-
has_checker = False
|
|
436
|
-
|
|
437
|
-
if not has_checker:
|
|
438
|
-
with open(answer_file_path, "r") as answer_file:
|
|
439
|
-
correct = util.lines_diff(output, answer_file.readlines())
|
|
440
|
-
return correct, 100 if correct else 0
|
|
441
|
-
else:
|
|
442
|
-
with open(output_file_path, "w") as output_file:
|
|
443
|
-
output_file.write("\n".join(output) + "\n")
|
|
444
|
-
return self.check_output_checker(name, input_file, output_file_path, answer_file_path)
|
|
445
|
-
|
|
446
|
-
def execute_oiejq(self, name, timetool_path, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
|
|
447
|
-
time_limit, memory_limit, hard_time_limit, execution_dir):
|
|
448
|
-
command = f'"{timetool_path}" "{executable}"'
|
|
449
|
-
env = os.environ.copy()
|
|
450
|
-
env["MEM_LIMIT"] = f'{memory_limit}K'
|
|
451
|
-
env["MEASURE_MEM"] = "1"
|
|
452
|
-
env["UNDER_OIEJQ"] = "1"
|
|
453
|
-
|
|
454
|
-
timeout = False
|
|
455
|
-
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file, \
|
|
456
|
-
open(result_file_path, "w") as result_file:
|
|
457
|
-
process = subprocess.Popen(command, shell=True, stdin=input_file, stdout=output_file,
|
|
458
|
-
stderr=result_file, env=env, preexec_fn=os.setsid, cwd=execution_dir)
|
|
459
|
-
|
|
460
|
-
def sigint_handler(signum, frame):
|
|
461
|
-
try:
|
|
462
|
-
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
463
|
-
except ProcessLookupError:
|
|
464
|
-
pass
|
|
465
|
-
sys.exit(1)
|
|
466
|
-
signal.signal(signal.SIGINT, sigint_handler)
|
|
467
|
-
|
|
468
|
-
try:
|
|
469
|
-
process.wait(timeout=hard_time_limit)
|
|
470
|
-
except subprocess.TimeoutExpired:
|
|
471
|
-
timeout = True
|
|
472
|
-
try:
|
|
473
|
-
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
474
|
-
except ProcessLookupError:
|
|
475
|
-
pass
|
|
476
|
-
process.communicate()
|
|
477
|
-
|
|
478
|
-
with open(result_file_path, "r") as result_file:
|
|
479
|
-
lines = result_file.read()
|
|
480
|
-
with open(output_file_path, "r") as output_file:
|
|
481
|
-
output = output_file.read()
|
|
482
|
-
result = ExecutionResult()
|
|
483
|
-
|
|
484
|
-
if not timeout:
|
|
485
|
-
lines = lines.splitlines()
|
|
486
|
-
output = output.splitlines()
|
|
487
|
-
|
|
488
|
-
for line in lines:
|
|
489
|
-
line = line.strip()
|
|
490
|
-
if ": " in line:
|
|
491
|
-
(key, value) = line.split(": ")[:2]
|
|
492
|
-
if key == "Time":
|
|
493
|
-
result.Time = self.parse_time(value)
|
|
494
|
-
elif key == "Memory":
|
|
495
|
-
result.Memory = self.parse_memory(value)
|
|
496
|
-
else:
|
|
497
|
-
setattr(result, key, value)
|
|
498
|
-
|
|
499
|
-
if timeout:
|
|
500
|
-
result.Status = Status.TL
|
|
501
|
-
elif getattr(result, "Time") is not None and result.Time > time_limit:
|
|
502
|
-
result.Status = Status.TL
|
|
503
|
-
elif getattr(result, "Memory") is not None and result.Memory > memory_limit:
|
|
504
|
-
result.Status = Status.ML
|
|
505
|
-
elif getattr(result, "Status") is None:
|
|
506
|
-
result.Status = Status.RE
|
|
507
|
-
elif result.Status == "OK": # Here OK is a string, because it is set while parsing oiejq's output.
|
|
508
|
-
if result.Time > time_limit:
|
|
509
|
-
result.Status = Status.TL
|
|
510
|
-
elif result.Memory > memory_limit:
|
|
511
|
-
result.Status = Status.ML
|
|
512
|
-
else:
|
|
513
|
-
try:
|
|
514
|
-
correct, result.Points = self.check_output(name, input_file_path, output_file_path, output, answer_file_path)
|
|
515
|
-
if not correct:
|
|
516
|
-
result.Status = Status.WA
|
|
517
|
-
except CheckerOutputException as e:
|
|
518
|
-
result.Status = Status.CE
|
|
519
|
-
result.Error = e.message
|
|
520
|
-
else:
|
|
521
|
-
result.Status = result.Status[:2]
|
|
522
|
-
|
|
523
|
-
return result
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
def execute_time(self, name, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
|
|
527
|
-
time_limit, memory_limit, hard_time_limit, execution_dir):
|
|
528
|
-
if sys.platform == 'darwin':
|
|
529
|
-
time_name = 'gtime'
|
|
530
|
-
elif sys.platform == 'linux':
|
|
531
|
-
time_name = 'time'
|
|
532
|
-
elif sys.platform == 'win32' or sys.platform == 'cygwin':
|
|
533
|
-
raise Exception("Measuring time with GNU time on Windows is not supported.")
|
|
534
|
-
|
|
535
|
-
command = [f'{time_name}', '-f', '%U\\n%M\\n%x', '-o', result_file_path, executable]
|
|
536
|
-
timeout = False
|
|
537
|
-
mem_limit_exceeded = False
|
|
538
|
-
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file:
|
|
539
|
-
process = subprocess.Popen(command, stdin=input_file, stdout=output_file, stderr=subprocess.DEVNULL,
|
|
540
|
-
preexec_fn=os.setsid, cwd=execution_dir)
|
|
541
|
-
|
|
542
|
-
def sigint_handler(signum, frame):
|
|
543
|
-
try:
|
|
544
|
-
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
545
|
-
except ProcessLookupError:
|
|
546
|
-
pass
|
|
547
|
-
sys.exit(1)
|
|
548
|
-
signal.signal(signal.SIGINT, sigint_handler)
|
|
549
|
-
|
|
550
|
-
start_time = time.time()
|
|
551
|
-
while process.poll() is None:
|
|
552
|
-
try:
|
|
553
|
-
time_process = psutil.Process(process.pid)
|
|
554
|
-
executable_process = None
|
|
555
|
-
for child in time_process.children():
|
|
556
|
-
if child.name() == executable:
|
|
557
|
-
executable_process = child
|
|
558
|
-
break
|
|
559
|
-
if executable_process is not None and executable_process.memory_info().rss > memory_limit * 1024:
|
|
560
|
-
try:
|
|
561
|
-
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
562
|
-
except ProcessLookupError:
|
|
563
|
-
pass
|
|
564
|
-
mem_limit_exceeded = True
|
|
565
|
-
break
|
|
566
|
-
except psutil.NoSuchProcess:
|
|
567
|
-
pass
|
|
568
|
-
|
|
569
|
-
if time.time() - start_time > hard_time_limit:
|
|
570
|
-
try:
|
|
571
|
-
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
|
|
572
|
-
except ProcessLookupError:
|
|
573
|
-
pass
|
|
574
|
-
timeout = True
|
|
575
|
-
break
|
|
576
|
-
|
|
577
|
-
with open(output_file_path, "r") as output_file:
|
|
578
|
-
output = output_file.read()
|
|
579
|
-
result = ExecutionResult()
|
|
580
|
-
program_exit_code = None
|
|
581
|
-
if not timeout:
|
|
582
|
-
output = output.splitlines()
|
|
583
|
-
with open(result_file_path, "r") as result_file:
|
|
584
|
-
lines = result_file.readlines()
|
|
585
|
-
if len(lines) == 3:
|
|
586
|
-
"""
|
|
587
|
-
If programs runs successfully, the output looks like this:
|
|
588
|
-
- first line is CPU time in seconds
|
|
589
|
-
- second line is memory in KB
|
|
590
|
-
- third line is exit code
|
|
591
|
-
This format is defined by -f flag in time command.
|
|
592
|
-
"""
|
|
593
|
-
result.Time = round(float(lines[0].strip()) * 1000)
|
|
594
|
-
result.Memory = int(lines[1].strip())
|
|
595
|
-
program_exit_code = int(lines[2].strip())
|
|
596
|
-
elif len(lines) > 0 and "Command terminated by signal " in lines[0]:
|
|
597
|
-
"""
|
|
598
|
-
If there was a runtime error, the first line is the error message with signal number.
|
|
599
|
-
For example:
|
|
600
|
-
Command terminated by signal 11
|
|
601
|
-
"""
|
|
602
|
-
program_exit_code = int(lines[0].strip().split(" ")[-1])
|
|
603
|
-
elif not mem_limit_exceeded:
|
|
604
|
-
result.Status = Status.RE
|
|
605
|
-
result.Error = "Unexpected output from time command: " + "".join(lines)
|
|
606
|
-
return result
|
|
607
|
-
|
|
608
|
-
if program_exit_code is not None and program_exit_code != 0:
|
|
609
|
-
result.Status = Status.RE
|
|
610
|
-
elif timeout:
|
|
611
|
-
result.Status = Status.TL
|
|
612
|
-
elif mem_limit_exceeded:
|
|
613
|
-
result.Memory = memory_limit + 1 # Add one so that the memory is red in the table
|
|
614
|
-
result.Status = Status.ML
|
|
615
|
-
elif result.Time > time_limit:
|
|
616
|
-
result.Status = Status.TL
|
|
617
|
-
elif result.Memory > memory_limit:
|
|
618
|
-
result.Status = Status.ML
|
|
619
|
-
else:
|
|
620
|
-
try:
|
|
621
|
-
correct, result.Points = self.check_output(name, input_file_path, output_file_path, output,
|
|
622
|
-
answer_file_path)
|
|
623
|
-
if correct:
|
|
624
|
-
result.Status = Status.OK
|
|
625
|
-
else:
|
|
626
|
-
result.Status = Status.WA
|
|
627
|
-
except CheckerOutputException as e:
|
|
628
|
-
result.Status = Status.CE
|
|
629
|
-
result.Error = e.message
|
|
630
|
-
|
|
631
|
-
return result
|
|
632
|
-
|
|
633
|
-
|
|
634
387
|
def run_solution(self, data_for_execution: ExecutionData):
|
|
635
388
|
"""
|
|
636
389
|
Run an execution and return the result as ExecutionResult object.
|
|
637
390
|
"""
|
|
638
391
|
|
|
639
|
-
(name, executable, test, time_limit, memory_limit, timetool_path, execution_dir) = data_for_execution
|
|
392
|
+
(name, executable, test, output_test, time_limit, memory_limit, timetool_path, execution_dir) = data_for_execution
|
|
640
393
|
file_no_ext = paths.get_executions_path(name, package_util.extract_test_id(test, self.ID))
|
|
641
394
|
output_file = file_no_ext + ".out"
|
|
642
395
|
result_file = file_no_ext + ".res"
|
|
643
|
-
|
|
396
|
+
hard_time_limit = math.ceil(2 * time_limit / 1000.0)
|
|
397
|
+
|
|
398
|
+
oiejq = self.timetool_name == 'oiejq'
|
|
399
|
+
return self.task_type.run(oiejq, timetool_path, executable, result_file, os.path.join(os.getcwd(), test), output_file,
|
|
400
|
+
output_test, time_limit, memory_limit, hard_time_limit, execution_dir)
|
|
644
401
|
|
|
645
|
-
if self.timetool_name == 'oiejq':
|
|
646
|
-
return self.execute_oiejq(name, timetool_path, executable, result_file, test, output_file, self.get_output_file(test),
|
|
647
|
-
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
|
|
648
|
-
elif self.timetool_name == 'time':
|
|
649
|
-
return self.execute_time(name, executable, result_file, test, output_file, self.get_output_file(test),
|
|
650
|
-
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
|
|
651
402
|
|
|
652
403
|
def run_solutions(self, compiled_commands, names, solutions, executables_dir):
|
|
653
404
|
"""
|
|
@@ -682,8 +433,9 @@ class Command(BaseCommand):
|
|
|
682
433
|
test_result.time_tool == self.timetool_name:
|
|
683
434
|
all_results[name][self.get_group(test)][test] = test_result.result
|
|
684
435
|
else:
|
|
685
|
-
executions.append((name, executable, test,
|
|
686
|
-
|
|
436
|
+
executions.append((name, executable, test,
|
|
437
|
+
os.path.join(os.getcwd(), self.get_output_file(test)), test_time_limit,
|
|
438
|
+
test_memory_limit, self.timetool_path, os.path.dirname(executable)))
|
|
687
439
|
all_results[name][self.get_group(test)][test] = ExecutionResult(Status.PENDING)
|
|
688
440
|
os.makedirs(paths.get_executions_path(name), exist_ok=True)
|
|
689
441
|
else:
|
|
@@ -805,7 +557,7 @@ class Command(BaseCommand):
|
|
|
805
557
|
if group not in self.scores:
|
|
806
558
|
util.exit_with_error(f'Group {group} doesn\'t have points specified in config file.')
|
|
807
559
|
|
|
808
|
-
if self.
|
|
560
|
+
if self.task_type.has_checker():
|
|
809
561
|
for solution in results.keys():
|
|
810
562
|
new_expected_scores[solution] = {
|
|
811
563
|
"expected": results[solution],
|
|
@@ -1030,6 +782,7 @@ class Command(BaseCommand):
|
|
|
1030
782
|
self.ID = package_util.get_task_id()
|
|
1031
783
|
self.SOURCE_EXTENSIONS = ['.c', '.cpp', '.py', '.java']
|
|
1032
784
|
self.SOLUTIONS_RE = package_util.get_solutions_re(self.ID)
|
|
785
|
+
self.task_type = task_type.get_task_type()
|
|
1033
786
|
|
|
1034
787
|
|
|
1035
788
|
def validate_arguments(self, args):
|
|
@@ -1126,9 +879,14 @@ class Command(BaseCommand):
|
|
|
1126
879
|
|
|
1127
880
|
print(util.warning('Missing output files for tests: ' + ', '.join(
|
|
1128
881
|
[self.extract_file_name(test) for test in missing_tests])))
|
|
882
|
+
if self.args.allow_no_outputs != True:
|
|
883
|
+
util.exit_with_error('There are tests without outputs. \n'
|
|
884
|
+
'Run outgen to fix this issue or add the --no-outputs flag to ignore the issue.')
|
|
1129
885
|
print(util.warning('Running only on tests with output files.'))
|
|
1130
886
|
self.tests = valid_input_files
|
|
1131
887
|
self.groups = self.get_groups(self.tests)
|
|
888
|
+
if len(self.groups) < 1:
|
|
889
|
+
util.exit_with_error('No tests with valid outputs.')
|
|
1132
890
|
|
|
1133
891
|
def check_are_any_tests_to_run(self):
|
|
1134
892
|
"""
|
|
@@ -1142,7 +900,7 @@ class Command(BaseCommand):
|
|
|
1142
900
|
if len(example_tests) == len(self.tests):
|
|
1143
901
|
print(util.warning('Running only on example tests.'))
|
|
1144
902
|
|
|
1145
|
-
if not self.has_lib:
|
|
903
|
+
if self.task_type.require_outputs() and not self.has_lib:
|
|
1146
904
|
self.validate_existence_of_outputs()
|
|
1147
905
|
else:
|
|
1148
906
|
util.exit_with_error('There are no tests to run.')
|
|
@@ -1160,34 +918,13 @@ class Command(BaseCommand):
|
|
|
1160
918
|
if results[solution][group][test].Error is not None:
|
|
1161
919
|
error_msg += f'Solution {solution} had an error on test {test}: {results[solution][group][test].Error}\n'
|
|
1162
920
|
if error_msg != "":
|
|
1163
|
-
util.
|
|
1164
|
-
|
|
1165
|
-
def compile_checker(self):
|
|
1166
|
-
checker_basename = os.path.basename(self.checker)
|
|
1167
|
-
self.checker_executable = paths.get_executables_path(checker_basename + ".e")
|
|
1168
|
-
|
|
1169
|
-
checker_compilation = self.compile_solutions([self.checker], is_checker=True)
|
|
1170
|
-
if not checker_compilation[0]:
|
|
1171
|
-
util.exit_with_error('Checker compilation failed.')
|
|
1172
|
-
|
|
1173
|
-
def check_had_checker(self, has_checker):
|
|
1174
|
-
"""
|
|
1175
|
-
Checks if there was a checker and if it is now removed (or the other way around) and if so, removes tests cache.
|
|
1176
|
-
In theory, removing cache after adding a checker is redundant, because during its compilation, the cache is
|
|
1177
|
-
removed.
|
|
1178
|
-
"""
|
|
1179
|
-
had_checker = os.path.exists(paths.get_cache_path("checker"))
|
|
1180
|
-
if (had_checker and not has_checker) or (not had_checker and has_checker):
|
|
1181
|
-
cache.remove_results_cache()
|
|
1182
|
-
if has_checker:
|
|
1183
|
-
with open(paths.get_cache_path("checker"), "w") as f:
|
|
1184
|
-
f.write("")
|
|
1185
|
-
else:
|
|
1186
|
-
try:
|
|
1187
|
-
os.remove(paths.get_cache_path("checker"))
|
|
1188
|
-
except FileNotFoundError:
|
|
1189
|
-
pass
|
|
921
|
+
print(util.error(error_msg))
|
|
1190
922
|
|
|
923
|
+
def compile_additional_files(self, files):
|
|
924
|
+
for name, args, kwargs in files:
|
|
925
|
+
print(f'Compiling {name}...')
|
|
926
|
+
if not self.compile(*args, **kwargs):
|
|
927
|
+
util.exit_with_error(f'Compilation of {name} failed.')
|
|
1191
928
|
|
|
1192
929
|
def run(self, args):
|
|
1193
930
|
args = util.init_package_command(args)
|
|
@@ -1213,14 +950,7 @@ class Command(BaseCommand):
|
|
|
1213
950
|
cache.process_extra_execution_files(self.config.get("extra_execution_files", {}), self.ID)
|
|
1214
951
|
cache.remove_results_if_contest_type_changed(self.config.get("sinol_contest_type", "default"))
|
|
1215
952
|
|
|
1216
|
-
|
|
1217
|
-
if len(checker) != 0:
|
|
1218
|
-
print("Checker found: %s" % os.path.basename(checker[0]))
|
|
1219
|
-
self.checker = checker[0]
|
|
1220
|
-
self.compile_checker()
|
|
1221
|
-
else:
|
|
1222
|
-
self.checker = None
|
|
1223
|
-
self.check_had_checker(self.checker is not None)
|
|
953
|
+
self.compile_additional_files(self.task_type.get_files_to_compile())
|
|
1224
954
|
|
|
1225
955
|
lib = package_util.get_files_matching_pattern(self.ID, f'{self.ID}lib.*')
|
|
1226
956
|
self.has_lib = len(lib) != 0
|
|
@@ -1253,7 +983,8 @@ class Command(BaseCommand):
|
|
|
1253
983
|
self.config = util.try_fix_config(self.config)
|
|
1254
984
|
try:
|
|
1255
985
|
validation_results = self.validate_expected_scores(results)
|
|
1256
|
-
except Exception:
|
|
986
|
+
except Exception as e:
|
|
987
|
+
print(traceback.format_exc())
|
|
1257
988
|
util.exit_with_error("Validating expected scores failed. "
|
|
1258
989
|
"This probably means that `sinol_expected_scores` is broken. "
|
|
1259
990
|
"Delete it and run `sinol-make run --apply-suggestions` again.")
|