sinol-make 1.5.28__tar.gz → 1.6.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.5.28/src/sinol_make.egg-info → sinol_make-1.6.0.dev1}/PKG-INFO +2 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/setup.cfg +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/__init__.py +13 -4
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/doc/__init__.py +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/export/__init__.py +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/gen/__init__.py +4 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/ingen/__init__.py +5 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/ingen/ingen_util.py +17 -6
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/init/__init__.py +2 -2
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/inwer/__init__.py +12 -9
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/inwer/inwer_util.py +16 -6
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/outgen/__init__.py +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/run/__init__.py +22 -12
- sinol_make-1.6.0.dev1/src/sinol_make/commands/verify/__init__.py +160 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/default.py +7 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/oi.py +13 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/cache.py +31 -11
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/compile.py +5 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/package_util.py +19 -2
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/util.py +20 -24
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1/src/sinol_make.egg-info}/PKG-INFO +2 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/SOURCES.txt +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/requires.txt +1 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/tests/test_multiple_arguments.py +1 -1
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/tests/test_util.py +23 -22
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/LICENSE +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/README.md +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/pyproject.toml +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/outgen/outgen_util.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/__init__.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/icpc.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/__init__.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/compiler.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/parsers.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/paths.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/printer.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/BaseCommand.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/Errors.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/__init__.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/oiejq/__init__.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/oiejq/perf_test.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/__init__.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/cache_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/compiler_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/gen_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/inwer_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/run_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/status_structs.py +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/dependency_links.txt +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/entry_points.txt +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/top_level.txt +0 -0
- {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/tests/test_oiejq.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sinol_make
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -21,6 +21,7 @@ Requires-Dist: PyYAML
|
|
|
21
21
|
Requires-Dist: dictdiffer
|
|
22
22
|
Requires-Dist: importlib-resources
|
|
23
23
|
Requires-Dist: psutil
|
|
24
|
+
Requires-Dist: packaging
|
|
24
25
|
Provides-Extra: tests
|
|
25
26
|
Requires-Dist: pytest; extra == "tests"
|
|
26
27
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
@@ -6,7 +6,7 @@ import argcomplete
|
|
|
6
6
|
|
|
7
7
|
from sinol_make import util, oiejq
|
|
8
8
|
|
|
9
|
-
__version__ = "1.
|
|
9
|
+
__version__ = "1.6.0.dev1"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def configure_parsers():
|
|
@@ -79,6 +79,9 @@ def main_exn():
|
|
|
79
79
|
def main():
|
|
80
80
|
new_version = None
|
|
81
81
|
try:
|
|
82
|
+
if util.is_dev(__version__):
|
|
83
|
+
print(util.warning('You are using a development version of sinol-make. '
|
|
84
|
+
'It may be unstable and contain bugs.'))
|
|
82
85
|
new_version = util.check_for_updates(__version__)
|
|
83
86
|
main_exn()
|
|
84
87
|
except argparse.ArgumentError as err:
|
|
@@ -92,6 +95,12 @@ def main():
|
|
|
92
95
|
'https://github.com/sio2project/sinol-make/#reporting-bugs-and-contributing-code')
|
|
93
96
|
finally:
|
|
94
97
|
if new_version is not None:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
if not util.is_dev(new_version):
|
|
99
|
+
print(util.warning(
|
|
100
|
+
f'New version of sinol-make is available (your version: {__version__}, available version: '
|
|
101
|
+
f'{new_version}).\nYou can update it by running `pip3 install sinol-make --upgrade`.'))
|
|
102
|
+
elif util.is_dev(new_version):
|
|
103
|
+
print(util.warning(
|
|
104
|
+
f'New development version of sinol-make is available (your version: {__version__}, available '
|
|
105
|
+
f'version: {new_version}).\nYou can update it by running `pip3 install sinol-make --pre --upgrade`.'
|
|
106
|
+
))
|
|
@@ -76,6 +76,7 @@ class Command(BaseCommand):
|
|
|
76
76
|
' pdflatex - uses pdflatex. Works with .png and .jpg images.\n'
|
|
77
77
|
' latex_dvi - uses latex and dvipdf. Works with .ps and .eps images.', default='auto')
|
|
78
78
|
parser.add_argument('files', type=str, nargs='*', help='files to compile')
|
|
79
|
+
return parser
|
|
79
80
|
|
|
80
81
|
def run(self, args: argparse.Namespace):
|
|
81
82
|
args = util.init_package_command(args)
|
|
@@ -37,6 +37,7 @@ class Command(BaseCommand):
|
|
|
37
37
|
parser.add_argument('--export-ocen', dest='export_ocen', action='store_true',
|
|
38
38
|
help='Create ocen archive')
|
|
39
39
|
parsers.add_compilation_arguments(parser)
|
|
40
|
+
return parser
|
|
40
41
|
|
|
41
42
|
def generate_input_tests(self):
|
|
42
43
|
print('Generating tests...')
|
|
@@ -37,7 +37,11 @@ class Command(BaseCommand):
|
|
|
37
37
|
default=util.default_cpu_count())
|
|
38
38
|
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
|
|
39
39
|
help='do not validate test contents')
|
|
40
|
+
parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
|
|
41
|
+
help='Use -fsanitize=address,undefined for ingen compilation. Warning: this may fail on some '
|
|
42
|
+
'systems. To fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
40
43
|
parsers.add_compilation_arguments(parser)
|
|
44
|
+
return parser
|
|
41
45
|
|
|
42
46
|
def run(self, args: argparse.Namespace):
|
|
43
47
|
args = util.init_package_command(args)
|
|
@@ -30,7 +30,11 @@ class Command(BaseCommand):
|
|
|
30
30
|
help='path to ingen source file, for example prog/abcingen.cpp')
|
|
31
31
|
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
|
|
32
32
|
help='do not validate test contents')
|
|
33
|
+
parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
|
|
34
|
+
help='Use -fsanitize=address,undefined for compilation. Warning: this may fail on some '
|
|
35
|
+
'systems. Tof fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
33
36
|
parsers.add_compilation_arguments(parser)
|
|
37
|
+
return parser
|
|
34
38
|
|
|
35
39
|
def run(self, args: argparse.Namespace):
|
|
36
40
|
args = util.init_package_command(args)
|
|
@@ -42,7 +46,7 @@ class Command(BaseCommand):
|
|
|
42
46
|
util.change_stack_size_to_unlimited()
|
|
43
47
|
self.ingen = get_ingen(self.task_id, args.ingen_path)
|
|
44
48
|
print(util.info(f'Using ingen file {os.path.basename(self.ingen)}'))
|
|
45
|
-
self.ingen_exe = compile_ingen(self.ingen, self.args, self.args.compile_mode)
|
|
49
|
+
self.ingen_exe = compile_ingen(self.ingen, self.args, self.args.compile_mode, self.args.fsanitize)
|
|
46
50
|
|
|
47
51
|
previous_tests = []
|
|
48
52
|
try:
|
|
@@ -47,7 +47,7 @@ def get_ingen(task_id, ingen_path=None):
|
|
|
47
47
|
return correct_ingen
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def compile_ingen(ingen_path: str, args: argparse.Namespace, compilation_flags='default'):
|
|
50
|
+
def compile_ingen(ingen_path: str, args: argparse.Namespace, compilation_flags='default', use_fsanitize=False):
|
|
51
51
|
"""
|
|
52
52
|
Compiles ingen and returns path to compiled executable.
|
|
53
53
|
If ingen_path is shell script, then it will be returned.
|
|
@@ -56,8 +56,9 @@ def compile_ingen(ingen_path: str, args: argparse.Namespace, compilation_flags='
|
|
|
56
56
|
return ingen_path
|
|
57
57
|
|
|
58
58
|
compilers = compiler.verify_compilers(args, [ingen_path])
|
|
59
|
-
ingen_exe, compile_log_path = compile.compile_file(ingen_path, package_util.get_executable(ingen_path),
|
|
60
|
-
compilation_flags, use_fsanitize=
|
|
59
|
+
ingen_exe, compile_log_path = compile.compile_file(ingen_path, package_util.get_executable(ingen_path),
|
|
60
|
+
compilers, compilation_flags, use_fsanitize=use_fsanitize,
|
|
61
|
+
additional_flags='-D_INGEN')
|
|
61
62
|
|
|
62
63
|
if ingen_exe is None:
|
|
63
64
|
compile.print_compile_log(compile_log_path)
|
|
@@ -85,11 +86,21 @@ def run_ingen(ingen_exe, working_dir=None):
|
|
|
85
86
|
print(util.bold(' Ingen output '.center(util.get_terminal_size()[1], '=')))
|
|
86
87
|
process = subprocess.Popen([ingen_exe], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
87
88
|
cwd=working_dir, shell=is_shell)
|
|
89
|
+
whole_output = ''
|
|
88
90
|
while process.poll() is None:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
out = process.stdout.readline().decode('utf-8')
|
|
92
|
+
if out != '':
|
|
93
|
+
print(out, end='')
|
|
94
|
+
whole_output += out
|
|
95
|
+
out = process.stdout.read().decode('utf-8')
|
|
96
|
+
whole_output += out
|
|
97
|
+
print(out, end='')
|
|
92
98
|
exit_code = process.returncode
|
|
93
99
|
print(util.bold(' End of ingen output '.center(util.get_terminal_size()[1], '=')))
|
|
94
100
|
|
|
101
|
+
if util.has_sanitizer_error(whole_output, exit_code):
|
|
102
|
+
print(util.warning('Warning: if ingen failed due to sanitizer errors, you can either run '
|
|
103
|
+
'`sudo sysctl vm.mmap_rnd_bits = 28` to fix this or disable sanitizers with the '
|
|
104
|
+
'--no-fsanitize flag.'))
|
|
105
|
+
|
|
95
106
|
return exit_code == 0
|
|
@@ -23,6 +23,7 @@ class Command(BaseCommand):
|
|
|
23
23
|
description='Create package from predefined template with given id.'
|
|
24
24
|
)
|
|
25
25
|
parser.add_argument('task_id', type=str, help='Id of the task to create')
|
|
26
|
+
return parser
|
|
26
27
|
|
|
27
28
|
def download_template(self):
|
|
28
29
|
repo = 'https://github.com/sio2project/sinol-make.git'
|
|
@@ -69,8 +70,7 @@ class Command(BaseCommand):
|
|
|
69
70
|
|
|
70
71
|
self.move_folder()
|
|
71
72
|
self.update_config()
|
|
72
|
-
|
|
73
|
+
|
|
73
74
|
self.used_tmpdir.cleanup()
|
|
74
75
|
|
|
75
76
|
print(util.info(f'Successfully created task "{self.task_id}"'))
|
|
76
|
-
|
|
@@ -39,14 +39,11 @@ class Command(BaseCommand):
|
|
|
39
39
|
help='test to verify, for example in/abc{0,1}*')
|
|
40
40
|
parser.add_argument('-c', '--cpus', type=int,
|
|
41
41
|
help=f'number of cpus to use (default: {util.default_cpu_count()})')
|
|
42
|
+
parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
|
|
43
|
+
help='Use -fsanitize=address,undefined for compilation. Warning: this may fail on some '
|
|
44
|
+
'systems. Tof fix this, run `sudo sysctl vm.mmap_rnd_bits = 28`.')
|
|
42
45
|
add_compilation_arguments(parser)
|
|
43
|
-
|
|
44
|
-
def compile_inwer(self, args: argparse.Namespace):
|
|
45
|
-
self.inwer_executable, compile_log_path = inwer_util.compile_inwer(self.inwer, args, args.compile_mode)
|
|
46
|
-
if self.inwer_executable is None:
|
|
47
|
-
util.exit_with_error('Compilation failed.', lambda: compile.print_compile_log(compile_log_path))
|
|
48
|
-
else:
|
|
49
|
-
print(util.info('Compilation successful.'))
|
|
46
|
+
return parser
|
|
50
47
|
|
|
51
48
|
@staticmethod
|
|
52
49
|
def verify_test(execution: InwerExecution) -> VerificationResult:
|
|
@@ -101,11 +98,14 @@ class Command(BaseCommand):
|
|
|
101
98
|
thr.start()
|
|
102
99
|
|
|
103
100
|
keyboard_interrupt = False
|
|
101
|
+
sanitizer_error = False
|
|
104
102
|
try:
|
|
105
103
|
with mp.Pool(self.cpus) as pool:
|
|
106
104
|
for i, result in enumerate(pool.imap(self.verify_test, executions)):
|
|
107
105
|
table_data.results[result.test_path].set_results(result.valid, result.output)
|
|
108
106
|
table_data.i = i
|
|
107
|
+
if util.has_sanitizer_error(result.output, 0 if result.valid else 1):
|
|
108
|
+
sanitizer_error = True
|
|
109
109
|
except KeyboardInterrupt:
|
|
110
110
|
keyboard_interrupt = True
|
|
111
111
|
|
|
@@ -115,6 +115,10 @@ class Command(BaseCommand):
|
|
|
115
115
|
|
|
116
116
|
print("\n".join(inwer_util.print_view(terminal_width, terminal_height, table_data)[0]))
|
|
117
117
|
|
|
118
|
+
if sanitizer_error:
|
|
119
|
+
print(util.warning('Warning: if inwer failed due to sanitizer errors, you can either run '
|
|
120
|
+
'`sudo sysctl vm.mmap_rnd_bits = 28` to fix this or disable sanitizers with the '
|
|
121
|
+
'--no-fsanitize flag.'))
|
|
118
122
|
if keyboard_interrupt:
|
|
119
123
|
util.exit_with_error('Keyboard interrupt.')
|
|
120
124
|
|
|
@@ -210,7 +214,7 @@ class Command(BaseCommand):
|
|
|
210
214
|
print('Verifying tests: ' + util.bold(', '.join(self.tests)))
|
|
211
215
|
|
|
212
216
|
util.change_stack_size_to_unlimited()
|
|
213
|
-
self.compile_inwer(args)
|
|
217
|
+
self.inwer_executable = inwer_util.compile_inwer(self.inwer, args, args.compile_mode, args.fsanitize)
|
|
214
218
|
results: Dict[str, TestResult] = self.verify_and_print_table()
|
|
215
219
|
print('')
|
|
216
220
|
|
|
@@ -225,4 +229,3 @@ class Command(BaseCommand):
|
|
|
225
229
|
print("Verifying tests order...")
|
|
226
230
|
self.verify_tests_order()
|
|
227
231
|
print(util.info('Verification successful.'))
|
|
228
|
-
exit(0)
|
|
@@ -13,7 +13,7 @@ from sinol_make.helpers import compiler
|
|
|
13
13
|
from sinol_make.interfaces.Errors import CompilationError
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def get_inwer_path(task_id: str, path
|
|
16
|
+
def get_inwer_path(task_id: str, path=None) -> Union[str, None]:
|
|
17
17
|
"""
|
|
18
18
|
Returns path to inwer executable for given task or None if no inwer was found.
|
|
19
19
|
"""
|
|
@@ -29,13 +29,21 @@ def get_inwer_path(task_id: str, path = None) -> Union[str, None]:
|
|
|
29
29
|
return None
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
def compile_inwer(inwer_path: str, args: argparse.Namespace, compilation_flags='default'):
|
|
32
|
+
def compile_inwer(inwer_path: str, args: argparse.Namespace, compilation_flags='default', use_fsanitize=False):
|
|
33
33
|
"""
|
|
34
34
|
Compiles inwer and returns path to compiled executable and path to compile log.
|
|
35
35
|
"""
|
|
36
36
|
compilers = compiler.verify_compilers(args, [inwer_path])
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
inwer_exe, compile_log_path = compile.compile_file(inwer_path, package_util.get_executable(inwer_path), compilers,
|
|
38
|
+
compilation_flags, use_fsanitize=use_fsanitize,
|
|
39
|
+
additional_flags='-D_INWER')
|
|
40
|
+
|
|
41
|
+
if inwer_exe is None:
|
|
42
|
+
compile.print_compile_log(compile_log_path)
|
|
43
|
+
util.exit_with_error('Failed inwer compilation.')
|
|
44
|
+
else:
|
|
45
|
+
print(util.info('Compilation successful.'))
|
|
46
|
+
return inwer_exe
|
|
39
47
|
|
|
40
48
|
|
|
41
49
|
def sort_tests(tests, task_id):
|
|
@@ -62,11 +70,13 @@ def print_view(term_width, term_height, table_data: TableData):
|
|
|
62
70
|
tests.append(result.test_path)
|
|
63
71
|
tests = sort_tests(tests, table_data.task_id)
|
|
64
72
|
|
|
65
|
-
column_lengths[3] = max(10, term_width - column_lengths[0] - column_lengths[1] - column_lengths[
|
|
73
|
+
column_lengths[3] = max(10, term_width - column_lengths[0] - column_lengths[1] - column_lengths[
|
|
74
|
+
2] - 9 - 3) # 9 is for " | " between columns, 3 for margin.
|
|
66
75
|
margin = " "
|
|
67
76
|
|
|
68
77
|
def print_line_separator():
|
|
69
|
-
res = "-" * (column_lengths[0] + 3) + "+" + "-" * (column_lengths[1] + 1) + "+" + "-" * (
|
|
78
|
+
res = "-" * (column_lengths[0] + 3) + "+" + "-" * (column_lengths[1] + 1) + "+" + "-" * (
|
|
79
|
+
column_lengths[2] + 1) + "+"
|
|
70
80
|
res += "-" * (term_width - len(res) - 1)
|
|
71
81
|
print(res)
|
|
72
82
|
|
|
@@ -34,6 +34,7 @@ class Command(BaseCommand):
|
|
|
34
34
|
parser.add_argument('-n', '--no-validate', default=False, action='store_true',
|
|
35
35
|
help='do not validate test contents')
|
|
36
36
|
parsers.add_compilation_arguments(parser)
|
|
37
|
+
return parser
|
|
37
38
|
|
|
38
39
|
def generate_outputs(self, outputs_to_generate):
|
|
39
40
|
print(f'Generating output files for {len(outputs_to_generate)} tests on {self.args.cpus} cpus.')
|
|
@@ -7,6 +7,7 @@ import threading
|
|
|
7
7
|
import time
|
|
8
8
|
import psutil
|
|
9
9
|
import glob
|
|
10
|
+
import shutil
|
|
10
11
|
from io import StringIO
|
|
11
12
|
from typing import Dict
|
|
12
13
|
|
|
@@ -300,6 +301,7 @@ class Command(BaseCommand):
|
|
|
300
301
|
parser.add_argument('-a', '--apply-suggestions', dest='apply_suggestions', action='store_true',
|
|
301
302
|
help='apply suggestions from expected scores report')
|
|
302
303
|
add_compilation_arguments(parser)
|
|
304
|
+
return parser
|
|
303
305
|
|
|
304
306
|
def parse_time(self, time_str):
|
|
305
307
|
if len(time_str) < 3: return -1
|
|
@@ -438,7 +440,7 @@ class Command(BaseCommand):
|
|
|
438
440
|
return self.check_output_checker(name, input_file, output_file_path, answer_file_path)
|
|
439
441
|
|
|
440
442
|
def execute_oiejq(self, name, timetool_path, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
|
|
441
|
-
time_limit, memory_limit, hard_time_limit):
|
|
443
|
+
time_limit, memory_limit, hard_time_limit, execution_dir):
|
|
442
444
|
command = f'"{timetool_path}" "{executable}"'
|
|
443
445
|
env = os.environ.copy()
|
|
444
446
|
env["MEM_LIMIT"] = f'{memory_limit}K'
|
|
@@ -449,7 +451,7 @@ class Command(BaseCommand):
|
|
|
449
451
|
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file, \
|
|
450
452
|
open(result_file_path, "w") as result_file:
|
|
451
453
|
process = subprocess.Popen(command, shell=True, stdin=input_file, stdout=output_file,
|
|
452
|
-
stderr=result_file, env=env, preexec_fn=os.setsid)
|
|
454
|
+
stderr=result_file, env=env, preexec_fn=os.setsid, cwd=execution_dir)
|
|
453
455
|
|
|
454
456
|
def sigint_handler(signum, frame):
|
|
455
457
|
try:
|
|
@@ -518,7 +520,7 @@ class Command(BaseCommand):
|
|
|
518
520
|
|
|
519
521
|
|
|
520
522
|
def execute_time(self, name, executable, result_file_path, input_file_path, output_file_path, answer_file_path,
|
|
521
|
-
time_limit, memory_limit, hard_time_limit):
|
|
523
|
+
time_limit, memory_limit, hard_time_limit, execution_dir):
|
|
522
524
|
if sys.platform == 'darwin':
|
|
523
525
|
time_name = 'gtime'
|
|
524
526
|
elif sys.platform == 'linux':
|
|
@@ -531,7 +533,7 @@ class Command(BaseCommand):
|
|
|
531
533
|
mem_limit_exceeded = False
|
|
532
534
|
with open(input_file_path, "r") as input_file, open(output_file_path, "w") as output_file:
|
|
533
535
|
process = subprocess.Popen(command, stdin=input_file, stdout=output_file, stderr=subprocess.DEVNULL,
|
|
534
|
-
preexec_fn=os.setsid)
|
|
536
|
+
preexec_fn=os.setsid, cwd=execution_dir)
|
|
535
537
|
|
|
536
538
|
def sigint_handler(signum, frame):
|
|
537
539
|
try:
|
|
@@ -596,7 +598,7 @@ class Command(BaseCommand):
|
|
|
596
598
|
program_exit_code = int(lines[0].strip().split(" ")[-1])
|
|
597
599
|
elif not mem_limit_exceeded:
|
|
598
600
|
result.Status = Status.RE
|
|
599
|
-
result.Error = "Unexpected output from time command: " + "
|
|
601
|
+
result.Error = "Unexpected output from time command: " + "".join(lines)
|
|
600
602
|
return result
|
|
601
603
|
|
|
602
604
|
if program_exit_code is not None and program_exit_code != 0:
|
|
@@ -630,7 +632,7 @@ class Command(BaseCommand):
|
|
|
630
632
|
Run an execution and return the result as ExecutionResult object.
|
|
631
633
|
"""
|
|
632
634
|
|
|
633
|
-
(name, executable, test, time_limit, memory_limit, timetool_path) = data_for_execution
|
|
635
|
+
(name, executable, test, time_limit, memory_limit, timetool_path, execution_dir) = data_for_execution
|
|
634
636
|
file_no_ext = paths.get_executions_path(name, package_util.extract_test_id(test, self.ID))
|
|
635
637
|
output_file = file_no_ext + ".out"
|
|
636
638
|
result_file = file_no_ext + ".res"
|
|
@@ -638,12 +640,12 @@ class Command(BaseCommand):
|
|
|
638
640
|
|
|
639
641
|
if self.timetool_name == 'oiejq':
|
|
640
642
|
return self.execute_oiejq(name, timetool_path, executable, result_file, test, output_file, self.get_output_file(test),
|
|
641
|
-
time_limit, memory_limit, hard_time_limit_in_s)
|
|
643
|
+
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
|
|
642
644
|
elif self.timetool_name == 'time':
|
|
643
645
|
return self.execute_time(name, executable, result_file, test, output_file, self.get_output_file(test),
|
|
644
|
-
time_limit, memory_limit, hard_time_limit_in_s)
|
|
646
|
+
time_limit, memory_limit, hard_time_limit_in_s, execution_dir)
|
|
645
647
|
|
|
646
|
-
def run_solutions(self, compiled_commands, names, solutions):
|
|
648
|
+
def run_solutions(self, compiled_commands, names, solutions, executables_dir):
|
|
647
649
|
"""
|
|
648
650
|
Run solutions on tests and print the results as a table to stdout.
|
|
649
651
|
"""
|
|
@@ -653,6 +655,13 @@ class Command(BaseCommand):
|
|
|
653
655
|
all_results = collections.defaultdict(
|
|
654
656
|
lambda: collections.defaultdict(lambda: collections.defaultdict(map)))
|
|
655
657
|
|
|
658
|
+
for lang, files in self.config.get('extra_execution_files', {}).items():
|
|
659
|
+
for file in files:
|
|
660
|
+
shutil.copy(os.path.join(os.getcwd(), "prog", file), executables_dir)
|
|
661
|
+
# Copy swig generated .so files
|
|
662
|
+
for file in glob.glob(os.path.join(os.getcwd(), "prog", f"_{self.ID}lib.so")):
|
|
663
|
+
shutil.copy(file, executables_dir)
|
|
664
|
+
|
|
656
665
|
for (name, executable, result) in compiled_commands:
|
|
657
666
|
lang = package_util.get_file_lang(name)
|
|
658
667
|
solution_cache = cache.get_cache_file(os.path.join(os.getcwd(), "prog", name))
|
|
@@ -670,7 +679,7 @@ class Command(BaseCommand):
|
|
|
670
679
|
all_results[name][self.get_group(test)][test] = test_result.result
|
|
671
680
|
else:
|
|
672
681
|
executions.append((name, executable, test, test_time_limit, test_memory_limit,
|
|
673
|
-
self.timetool_path))
|
|
682
|
+
self.timetool_path, os.path.dirname(executable)))
|
|
674
683
|
all_results[name][self.get_group(test)][test] = ExecutionResult(Status.PENDING)
|
|
675
684
|
os.makedirs(paths.get_executions_path(name), exist_ok=True)
|
|
676
685
|
else:
|
|
@@ -743,7 +752,7 @@ class Command(BaseCommand):
|
|
|
743
752
|
executables = [paths.get_executables_path(package_util.get_executable(solution)) for solution in solutions]
|
|
744
753
|
compiled_commands = zip(solutions, executables, compilation_results)
|
|
745
754
|
names = solutions
|
|
746
|
-
return self.run_solutions(compiled_commands, names, solutions)
|
|
755
|
+
return self.run_solutions(compiled_commands, names, solutions, paths.get_executables_path())
|
|
747
756
|
|
|
748
757
|
def convert_status_to_string(self, dictionary):
|
|
749
758
|
"""
|
|
@@ -1196,7 +1205,8 @@ class Command(BaseCommand):
|
|
|
1196
1205
|
title = self.config["title"]
|
|
1197
1206
|
print("Task: %s (tag: %s)" % (title, self.ID))
|
|
1198
1207
|
self.cpus = args.cpus or util.default_cpu_count()
|
|
1199
|
-
cache.
|
|
1208
|
+
cache.process_extra_compilation_files(self.config.get("extra_compilation_files", []), self.ID)
|
|
1209
|
+
cache.process_extra_execution_files(self.config.get("extra_execution_files", {}), self.ID)
|
|
1200
1210
|
cache.remove_results_if_contest_type_changed(self.config.get("sinol_contest_type", "default"))
|
|
1201
1211
|
|
|
1202
1212
|
checker = package_util.get_files_matching_pattern(self.ID, f'{self.ID}chk.*')
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
import shutil
|
|
4
|
+
import argparse
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
from sinol_make import util, contest_types
|
|
8
|
+
from sinol_make.helpers import parsers, package_util, paths
|
|
9
|
+
from sinol_make.interfaces.BaseCommand import BaseCommand
|
|
10
|
+
from sinol_make.commands.gen import Command as GenCommand
|
|
11
|
+
from sinol_make.commands.doc import Command as DocCommand
|
|
12
|
+
from sinol_make.commands.inwer import Command as InwerCommand, inwer_util
|
|
13
|
+
from sinol_make.commands.run import Command as RunCommand
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Command(BaseCommand):
|
|
17
|
+
"""
|
|
18
|
+
Class for `verify` command.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def get_name(self):
|
|
22
|
+
return "verify"
|
|
23
|
+
|
|
24
|
+
def configure_subparser(self, subparser):
|
|
25
|
+
parser = subparser.add_parser(
|
|
26
|
+
self.get_name(),
|
|
27
|
+
help='Verify the package',
|
|
28
|
+
description='Verify the whole package. This command will first '
|
|
29
|
+
'run stress tests (if the file `prog/{task_id}stresstest.sh` exists), '
|
|
30
|
+
'verify the config, generate tests, generate problem '
|
|
31
|
+
'statements, run inwer and run all solutions. '
|
|
32
|
+
'Ingen and inwer are compiled with sanitizers (-fsanitize=address,undefined), '
|
|
33
|
+
'which may fail on some systems. To fix this, run `sudo sysctl vm.mmap_rnd_bits = 28` '
|
|
34
|
+
'or disable sanitizers with --no-fsanitize.'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
parser.add_argument('-f', '--no-fsanitize', action='store_true', default=False,
|
|
38
|
+
help='do not use sanitizers for ingen and inwer programs')
|
|
39
|
+
parser.add_argument('-c', '--cpus', type=int,
|
|
40
|
+
help=f'number of cpus that sinol-make will use '
|
|
41
|
+
f'(default: {util.default_cpu_count()})',
|
|
42
|
+
default=util.default_cpu_count())
|
|
43
|
+
parsers.add_compilation_arguments(parser)
|
|
44
|
+
|
|
45
|
+
def remove_cache(self):
|
|
46
|
+
"""
|
|
47
|
+
Remove whole cache dir
|
|
48
|
+
"""
|
|
49
|
+
cache_dir = paths.get_cache_path()
|
|
50
|
+
if os.path.exists(cache_dir):
|
|
51
|
+
shutil.rmtree(cache_dir)
|
|
52
|
+
|
|
53
|
+
def check_extra_files(self):
|
|
54
|
+
"""
|
|
55
|
+
Checks if extra_compilation_files and extra_execution_files exist.
|
|
56
|
+
"""
|
|
57
|
+
extra_compilation_files = self.config.get('extra_compilation_files', [])
|
|
58
|
+
for file in extra_compilation_files:
|
|
59
|
+
if not os.path.exists(os.path.join(os.getcwd(), "prog", file)):
|
|
60
|
+
util.exit_with_error(f"Extra compilation file `{file}` does not exist. "
|
|
61
|
+
f"It should be in `prog` directory.")
|
|
62
|
+
if extra_compilation_files:
|
|
63
|
+
print(util.info("All extra compilation files exist."))
|
|
64
|
+
|
|
65
|
+
extra_execution_files = self.config.get('extra_execution_files', {})
|
|
66
|
+
for lang, files in extra_execution_files.items():
|
|
67
|
+
for file in files:
|
|
68
|
+
if not os.path.exists(os.path.join(os.getcwd(), "prog", file)):
|
|
69
|
+
util.exit_with_error(f"Extra execution file `{file}` for language `{lang}` does not exist. "
|
|
70
|
+
f"It should be in `prog` directory.")
|
|
71
|
+
if extra_execution_files:
|
|
72
|
+
print(util.info("All extra execution files exist."))
|
|
73
|
+
|
|
74
|
+
def verify_scores(self, scored_groups):
|
|
75
|
+
config_scores = self.config.get('scores', {})
|
|
76
|
+
if not config_scores:
|
|
77
|
+
return
|
|
78
|
+
if '0' in scored_groups:
|
|
79
|
+
scored_groups.remove('0')
|
|
80
|
+
if 0 in scored_groups:
|
|
81
|
+
scored_groups.remove(0)
|
|
82
|
+
|
|
83
|
+
for group in scored_groups:
|
|
84
|
+
if int(group) not in config_scores:
|
|
85
|
+
util.exit_with_error(f"Score for group '{group}' not found. "
|
|
86
|
+
f"You must either provide scores for all groups "
|
|
87
|
+
f"or not provide them at all (to have them assigned automatically).")
|
|
88
|
+
|
|
89
|
+
for group in config_scores:
|
|
90
|
+
if int(group) not in scored_groups:
|
|
91
|
+
util.exit_with_error(f"Score for group '{group}' found in config, "
|
|
92
|
+
f"but no such test group exists in scored groups. "
|
|
93
|
+
f"You must either provide scores for all groups "
|
|
94
|
+
f"or not provide them at all (to have them assigned automatically).")
|
|
95
|
+
|
|
96
|
+
print(util.info("All scores are provided for all groups."))
|
|
97
|
+
|
|
98
|
+
def prepare_args(self, command):
|
|
99
|
+
parser = argparse.ArgumentParser()
|
|
100
|
+
subparser = parser.add_subparsers(dest='command')
|
|
101
|
+
command_parser = command.configure_subparser(subparser)
|
|
102
|
+
command_args = command_parser.parse_args([])
|
|
103
|
+
for key, value in vars(self.args).items():
|
|
104
|
+
setattr(command_args, key, value)
|
|
105
|
+
setattr(command_args, 'fsanitize', not self.args.no_fsanitize)
|
|
106
|
+
return command_args
|
|
107
|
+
|
|
108
|
+
def run_stresstests(self):
|
|
109
|
+
stresstests_path = os.path.join(os.getcwd(), 'prog', self.task_id + 'stresstest.sh')
|
|
110
|
+
if not os.path.exists(stresstests_path):
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
print(util.bold(' Running stress tests '.center(util.get_terminal_size()[1], '=')))
|
|
114
|
+
print(f"See the comments in `prog/{self.task_id}stresstest.sh` for details.".center(
|
|
115
|
+
util.get_terminal_size()[1], ' '))
|
|
116
|
+
st = os.stat(stresstests_path)
|
|
117
|
+
os.chmod(stresstests_path, st.st_mode | stat.S_IEXEC)
|
|
118
|
+
p = subprocess.Popen([stresstests_path], shell=True)
|
|
119
|
+
p.wait()
|
|
120
|
+
if p.returncode != 0:
|
|
121
|
+
util.exit_with_error("Stress tests failed.")
|
|
122
|
+
|
|
123
|
+
def run(self, args: argparse.Namespace):
|
|
124
|
+
self.args = util.init_package_command(args)
|
|
125
|
+
self.config = package_util.get_config()
|
|
126
|
+
self.task_id = package_util.get_task_id()
|
|
127
|
+
self.contest = contest_types.get_contest_type()
|
|
128
|
+
|
|
129
|
+
self.remove_cache()
|
|
130
|
+
self.check_extra_files()
|
|
131
|
+
self.contest.verify_pre_gen()
|
|
132
|
+
|
|
133
|
+
# Run stresstests (if present)
|
|
134
|
+
self.run_stresstests()
|
|
135
|
+
|
|
136
|
+
# Generate tests
|
|
137
|
+
print(util.bold(' Generating tests '.center(util.get_terminal_size()[1], '=')))
|
|
138
|
+
gen = GenCommand()
|
|
139
|
+
gen.run(self.prepare_args(gen))
|
|
140
|
+
self.verify_scores(package_util.get_groups(package_util.get_all_inputs(self.task_id), self.task_id))
|
|
141
|
+
|
|
142
|
+
# Generate problem statements
|
|
143
|
+
print(util.bold(' Generating problem statements '.center(util.get_terminal_size()[1], '=')))
|
|
144
|
+
doc = DocCommand()
|
|
145
|
+
doc.run(self.prepare_args(doc))
|
|
146
|
+
|
|
147
|
+
# Run inwer
|
|
148
|
+
if inwer_util.get_inwer_path(self.task_id) is None:
|
|
149
|
+
print(util.warning("Package doesn't have inwer."))
|
|
150
|
+
else:
|
|
151
|
+
print(util.bold(' Running inwer '.center(util.get_terminal_size()[1], '=')))
|
|
152
|
+
inwer = InwerCommand()
|
|
153
|
+
inwer.run(self.prepare_args(inwer))
|
|
154
|
+
|
|
155
|
+
# Run solutions
|
|
156
|
+
print(util.bold(' Running solutions '.center(util.get_terminal_size()[1], '=')))
|
|
157
|
+
run = RunCommand()
|
|
158
|
+
run.run(self.prepare_args(run))
|
|
159
|
+
|
|
160
|
+
print(util.info('Package verification successful.'))
|
|
@@ -64,7 +64,7 @@ class DefaultContest:
|
|
|
64
64
|
:param scores: Dictionary: {"<group>": <points>}
|
|
65
65
|
:return: Maximum possible score.
|
|
66
66
|
"""
|
|
67
|
-
if groups[0] == 0 and len(groups) == 1:
|
|
67
|
+
if len(groups) == 0 or (groups[0] == 0 and len(groups) == 1):
|
|
68
68
|
return 0
|
|
69
69
|
|
|
70
70
|
possible_score = 0
|
|
@@ -126,3 +126,9 @@ class DefaultContest:
|
|
|
126
126
|
Returns maximum score for single test
|
|
127
127
|
"""
|
|
128
128
|
return 100
|
|
129
|
+
|
|
130
|
+
def verify_pre_gen(self):
|
|
131
|
+
"""
|
|
132
|
+
Called by verify command before generating tests.
|
|
133
|
+
"""
|
|
134
|
+
pass
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
|
|
3
|
+
from sinol_make import util
|
|
4
|
+
from sinol_make.helpers import package_util
|
|
3
5
|
from sinol_make.structs.status_structs import ExecutionResult
|
|
4
6
|
from sinol_make.contest_types.default import DefaultContest
|
|
5
7
|
|
|
@@ -28,3 +30,14 @@ class OIContest(DefaultContest):
|
|
|
28
30
|
return result.Points
|
|
29
31
|
else:
|
|
30
32
|
return 1 + int((result.Points - 1) * ((time_limit - result.Time) / (time_limit / 2.0)))
|
|
33
|
+
|
|
34
|
+
def verify_pre_gen(self):
|
|
35
|
+
"""
|
|
36
|
+
Verify if scores sum up to 100.
|
|
37
|
+
"""
|
|
38
|
+
config = package_util.get_config()
|
|
39
|
+
if 'scores' not in config:
|
|
40
|
+
return
|
|
41
|
+
total_score = sum(config['scores'].values())
|
|
42
|
+
if total_score != 100:
|
|
43
|
+
util.exit_with_error(f"Total score in config is {total_score}, but should be 100.")
|
|
@@ -70,14 +70,29 @@ def save_compiled(file_path: str, exe_path: str, is_checker: bool = False):
|
|
|
70
70
|
remove_results_cache()
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def
|
|
73
|
+
def _check_file_changed(file_path, lang, task_id):
|
|
74
|
+
solutions_re = package_util.get_solutions_re(task_id)
|
|
75
|
+
md5sum = util.get_file_md5(file_path)
|
|
76
|
+
info = get_cache_file(file_path)
|
|
77
|
+
|
|
78
|
+
if info.md5sum != md5sum:
|
|
79
|
+
for solution in os.listdir(paths.get_cache_path('md5sums')):
|
|
80
|
+
# Remove only files in the same language and matching the solution regex
|
|
81
|
+
if package_util.get_file_lang(solution) == lang and \
|
|
82
|
+
solutions_re.match(solution) is not None:
|
|
83
|
+
os.unlink(paths.get_cache_path('md5sums', solution))
|
|
84
|
+
|
|
85
|
+
info.md5sum = md5sum
|
|
86
|
+
info.save(file_path)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def process_extra_compilation_files(extra_compilation_files, task_id):
|
|
74
90
|
"""
|
|
75
91
|
Checks if extra compilation files have changed and saves them to cache.
|
|
76
92
|
If they have, removes all cached solutions that use them.
|
|
77
93
|
:param extra_compilation_files: List of extra compilation files
|
|
78
94
|
:param task_id: Task id
|
|
79
95
|
"""
|
|
80
|
-
solutions_re = package_util.get_solutions_re(task_id)
|
|
81
96
|
for file in extra_compilation_files:
|
|
82
97
|
file_path = os.path.join(os.getcwd(), "prog", file)
|
|
83
98
|
if not os.path.exists(file_path):
|
|
@@ -86,17 +101,22 @@ def save_to_cache_extra_compilation_files(extra_compilation_files, task_id):
|
|
|
86
101
|
lang = package_util.get_file_lang(file)
|
|
87
102
|
if lang == 'h':
|
|
88
103
|
lang = 'cpp'
|
|
89
|
-
|
|
104
|
+
_check_file_changed(file_path, lang, task_id)
|
|
90
105
|
|
|
91
|
-
if info.md5sum != md5sum:
|
|
92
|
-
for solution in os.listdir(paths.get_cache_path('md5sums')):
|
|
93
|
-
# Remove only files in the same language and matching the solution regex
|
|
94
|
-
if package_util.get_file_lang(solution) == lang and \
|
|
95
|
-
solutions_re.match(solution) is not None:
|
|
96
|
-
os.unlink(paths.get_cache_path('md5sums', solution))
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
107
|
+
def process_extra_execution_files(extra_execution_files, task_id):
|
|
108
|
+
"""
|
|
109
|
+
Checks if extra execution files have changed and saves them to cache.
|
|
110
|
+
If they have, removes all cached solutions that use them.
|
|
111
|
+
:param extra_execution_files: List of extra execution files
|
|
112
|
+
:param task_id: Task id
|
|
113
|
+
"""
|
|
114
|
+
for lang, files in extra_execution_files.items():
|
|
115
|
+
for file in files:
|
|
116
|
+
file_path = os.path.join(os.getcwd(), "prog", file)
|
|
117
|
+
if not os.path.exists(file_path):
|
|
118
|
+
continue
|
|
119
|
+
_check_file_changed(file_path, lang, task_id)
|
|
100
120
|
|
|
101
121
|
|
|
102
122
|
def remove_results_cache():
|
|
@@ -112,7 +112,8 @@ def compile(program, output, compilers: Compilers = None, compile_log=None, comp
|
|
|
112
112
|
return True
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
def compile_file(file_path: str, name: str, compilers: Compilers, compilation_flags='default',
|
|
115
|
+
def compile_file(file_path: str, name: str, compilers: Compilers, compilation_flags='default',
|
|
116
|
+
use_fsanitize=False, additional_flags=None) \
|
|
116
117
|
-> Tuple[Union[str, None], str]:
|
|
117
118
|
"""
|
|
118
119
|
Compile a file
|
|
@@ -121,6 +122,7 @@ def compile_file(file_path: str, name: str, compilers: Compilers, compilation_fl
|
|
|
121
122
|
:param compilers: Compilers object
|
|
122
123
|
:param compilation_flags: Group of compilation flags to use
|
|
123
124
|
:param use_fsanitize: Whether to use fsanitize when compiling C/C++ programs. Sanitizes address and undefined behavior.
|
|
125
|
+
:param additional_flags: Additional flags for c / c++ compiler.
|
|
124
126
|
:return: Tuple of (executable path or None if compilation failed, log path)
|
|
125
127
|
"""
|
|
126
128
|
os.makedirs(paths.get_executables_path(), exist_ok=True)
|
|
@@ -135,6 +137,8 @@ def compile_file(file_path: str, name: str, compilers: Compilers, compilation_fl
|
|
|
135
137
|
if isinstance(args, str):
|
|
136
138
|
args = [args]
|
|
137
139
|
extra_compilation_args = [os.path.join(os.getcwd(), "prog", file) for file in args]
|
|
140
|
+
if additional_flags is not None:
|
|
141
|
+
extra_compilation_args.append(additional_flags)
|
|
138
142
|
|
|
139
143
|
output = paths.get_executables_path(name)
|
|
140
144
|
compile_log_path = paths.get_compilation_log_path(os.path.splitext(name)[0] + '.compile_log')
|
|
@@ -266,6 +266,14 @@ def get_memory_limit(test_path, config, lang, task_id, args=None):
|
|
|
266
266
|
return _get_limit(LimitTypes.MEMORY_LIMIT, test_path, str_config, lang, task_id)
|
|
267
267
|
|
|
268
268
|
|
|
269
|
+
def get_in_tests_re(task_id: str) -> re.Pattern:
|
|
270
|
+
return re.compile(r'^%s(([0-9]+)([a-z]?[a-z0-9]*))\.in$' % re.escape(task_id))
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def get_out_tests_re(task_id: str) -> re.Pattern:
|
|
274
|
+
return re.compile(r'^%s(([0-9]+)([a-z]?[a-z0-9]*))\.out$' % re.escape(task_id))
|
|
275
|
+
|
|
276
|
+
|
|
269
277
|
def validate_test_names(task_id):
|
|
270
278
|
"""
|
|
271
279
|
Checks if all files in the package have valid names.
|
|
@@ -278,12 +286,12 @@ def validate_test_names(task_id):
|
|
|
278
286
|
invalid_files.append(os.path.basename(file))
|
|
279
287
|
return invalid_files
|
|
280
288
|
|
|
281
|
-
in_test_re =
|
|
289
|
+
in_test_re = get_in_tests_re(task_id)
|
|
282
290
|
invalid_in_tests = get_invalid_files(os.path.join("in", "*.in"), in_test_re)
|
|
283
291
|
if len(invalid_in_tests) > 0:
|
|
284
292
|
util.exit_with_error(f'Input tests with invalid names: {", ".join(invalid_in_tests)}.')
|
|
285
293
|
|
|
286
|
-
out_test_re =
|
|
294
|
+
out_test_re = get_out_tests_re(task_id)
|
|
287
295
|
invalid_out_tests = get_invalid_files(os.path.join("out", "*.out"), out_test_re)
|
|
288
296
|
if len(invalid_out_tests) > 0:
|
|
289
297
|
util.exit_with_error(f'Output tests with invalid names: {", ".join(invalid_out_tests)}.')
|
|
@@ -376,3 +384,12 @@ def validate_test(test_path: str):
|
|
|
376
384
|
|
|
377
385
|
if num_empty != 0:
|
|
378
386
|
util.exit_with_error(f'Exactly one empty line expected in {basename}')
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def get_all_inputs(task_id):
|
|
390
|
+
in_test_re = get_in_tests_re(task_id)
|
|
391
|
+
inputs = []
|
|
392
|
+
for file in glob.glob(os.path.join(os.getcwd(), "in", "*.in")):
|
|
393
|
+
if in_test_re.match(os.path.basename(file)):
|
|
394
|
+
inputs.append(file)
|
|
395
|
+
return inputs
|
|
@@ -7,6 +7,7 @@ import hashlib
|
|
|
7
7
|
import multiprocessing
|
|
8
8
|
import resource
|
|
9
9
|
from typing import Union
|
|
10
|
+
from packaging.version import parse as parse_version
|
|
10
11
|
|
|
11
12
|
from sinol_make.contest_types import get_contest_type
|
|
12
13
|
from sinol_make.helpers import paths, cache
|
|
@@ -150,10 +151,18 @@ def import_importlib_resources():
|
|
|
150
151
|
return importlib
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
def
|
|
154
|
+
def is_dev(version):
|
|
155
|
+
"""
|
|
156
|
+
Function to check if the version is a development version.
|
|
157
|
+
"""
|
|
158
|
+
return parse_version(version).is_devrelease
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def check_for_updates(current_version, check=True) -> Union[str, None]:
|
|
154
162
|
"""
|
|
155
163
|
Function to check if there is a new version of sinol-make.
|
|
156
164
|
:param current_version: current version of sinol-make
|
|
165
|
+
:param check: whether to check for new version
|
|
157
166
|
:return: returns new version if there is one, None otherwise
|
|
158
167
|
"""
|
|
159
168
|
importlib = import_importlib_resources()
|
|
@@ -164,8 +173,9 @@ def check_for_updates(current_version) -> Union[str, None]:
|
|
|
164
173
|
|
|
165
174
|
# We check for new version asynchronously, so that it doesn't slow down the program.
|
|
166
175
|
# If the main process exits, the check_version process will also exit.
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
if check:
|
|
177
|
+
process = multiprocessing.Process(target=check_version, daemon=True)
|
|
178
|
+
process.start()
|
|
169
179
|
version_file = data_dir.joinpath("version")
|
|
170
180
|
|
|
171
181
|
try:
|
|
@@ -178,7 +188,9 @@ def check_for_updates(current_version) -> Union[str, None]:
|
|
|
178
188
|
return None
|
|
179
189
|
|
|
180
190
|
try:
|
|
181
|
-
if
|
|
191
|
+
if not is_dev(version) and parse_version(version) > parse_version(current_version):
|
|
192
|
+
return version
|
|
193
|
+
if is_dev(current_version) and is_dev(version) and parse_version(version) > parse_version(current_version):
|
|
182
194
|
return version
|
|
183
195
|
else:
|
|
184
196
|
return None
|
|
@@ -217,26 +229,6 @@ def check_version():
|
|
|
217
229
|
pass
|
|
218
230
|
|
|
219
231
|
|
|
220
|
-
def compare_versions(version_a, version_b):
|
|
221
|
-
"""
|
|
222
|
-
Function to compare two versions.
|
|
223
|
-
Returns 1 if version_a > version_b, 0 if version_a == version_b, -1 if version_a < version_b.
|
|
224
|
-
"""
|
|
225
|
-
|
|
226
|
-
def convert(version):
|
|
227
|
-
return tuple(map(int, version.split(".")))
|
|
228
|
-
|
|
229
|
-
version_a = convert(version_a)
|
|
230
|
-
version_b = convert(version_b)
|
|
231
|
-
|
|
232
|
-
if version_a > version_b:
|
|
233
|
-
return 1
|
|
234
|
-
elif version_a == version_b:
|
|
235
|
-
return 0
|
|
236
|
-
else:
|
|
237
|
-
return -1
|
|
238
|
-
|
|
239
|
-
|
|
240
232
|
def lines_diff(lines1, lines2):
|
|
241
233
|
"""
|
|
242
234
|
Function to compare two lists of lines.
|
|
@@ -441,3 +433,7 @@ def exit_with_error(text, func=None):
|
|
|
441
433
|
except TypeError:
|
|
442
434
|
pass
|
|
443
435
|
exit(1)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def has_sanitizer_error(output, exit_code):
|
|
439
|
+
return ('ELF_ET_DYN_BASE' in output or 'ASan' in output) and exit_code != 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sinol_make
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
@@ -21,6 +21,7 @@ Requires-Dist: PyYAML
|
|
|
21
21
|
Requires-Dist: dictdiffer
|
|
22
22
|
Requires-Dist: importlib-resources
|
|
23
23
|
Requires-Dist: psutil
|
|
24
|
+
Requires-Dist: packaging
|
|
24
25
|
Provides-Extra: tests
|
|
25
26
|
Requires-Dist: pytest; extra == "tests"
|
|
26
27
|
Requires-Dist: pytest-cov; extra == "tests"
|
|
@@ -21,6 +21,7 @@ src/sinol_make/commands/inwer/inwer_util.py
|
|
|
21
21
|
src/sinol_make/commands/outgen/__init__.py
|
|
22
22
|
src/sinol_make/commands/outgen/outgen_util.py
|
|
23
23
|
src/sinol_make/commands/run/__init__.py
|
|
24
|
+
src/sinol_make/commands/verify/__init__.py
|
|
24
25
|
src/sinol_make/contest_types/__init__.py
|
|
25
26
|
src/sinol_make/contest_types/default.py
|
|
26
27
|
src/sinol_make/contest_types/icpc.py
|
|
@@ -21,7 +21,7 @@ def test_simple_package(create_package):
|
|
|
21
21
|
|
|
22
22
|
@pytest.mark.parametrize("create_package", [util.get_inwer_package_path()], indirect=True)
|
|
23
23
|
def test_inwer_package(create_package):
|
|
24
|
-
run("sinol-make
|
|
24
|
+
run("sinol-make gen inwer run")
|
|
25
25
|
run("sinol-make ingen prog/weringen.cpp inwer prog/werinwer.cpp --tests wer1a.in run --tests wer2a.in")
|
|
26
26
|
run("sinol-make ingen inwer run export --no-statement")
|
|
27
27
|
|
|
@@ -44,28 +44,6 @@ def test_file_diff():
|
|
|
44
44
|
assert util.file_diff(a_file, b_file) is False
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def test_compare_versions():
|
|
48
|
-
"""
|
|
49
|
-
Tests for compare_versions function
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
assert util.compare_versions('1.0.0', '1.0.0') == 0
|
|
53
|
-
assert util.compare_versions('1.0.0', '1.0.1') == -1
|
|
54
|
-
assert util.compare_versions('1.0.1', '1.0.0') == 1
|
|
55
|
-
assert util.compare_versions('1.0.0', '1.1.0') == -1
|
|
56
|
-
assert util.compare_versions('1.1.0', '1.0.0') == 1
|
|
57
|
-
assert util.compare_versions('1.0.0', '2.0.0') == -1
|
|
58
|
-
assert util.compare_versions('2.0.0', '1.0.0') == 1
|
|
59
|
-
with pytest.raises(ValueError):
|
|
60
|
-
util.compare_versions('1.0.0', '')
|
|
61
|
-
with pytest.raises(ValueError):
|
|
62
|
-
util.compare_versions('', '1.0.0')
|
|
63
|
-
with pytest.raises(ValueError):
|
|
64
|
-
util.compare_versions('1.0.0', 'abc')
|
|
65
|
-
with pytest.raises(ValueError):
|
|
66
|
-
util.compare_versions('abc', '1.0.0')
|
|
67
|
-
|
|
68
|
-
|
|
69
47
|
@requests_mock.Mocker(kw="mocker")
|
|
70
48
|
def test_check_version(**kwargs):
|
|
71
49
|
"""
|
|
@@ -74,6 +52,29 @@ def test_check_version(**kwargs):
|
|
|
74
52
|
"""
|
|
75
53
|
mocker = kwargs["mocker"]
|
|
76
54
|
|
|
55
|
+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", json={"info": {"version": "1.0.0.dev2"}})
|
|
56
|
+
util.check_version()
|
|
57
|
+
version = util.check_for_updates("1.0.0.dev1", False)
|
|
58
|
+
assert version == "1.0.0.dev2"
|
|
59
|
+
assert util.is_dev(version)
|
|
60
|
+
|
|
61
|
+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", json={"info": {"version": "1.0.0"}})
|
|
62
|
+
util.check_version()
|
|
63
|
+
version = util.check_for_updates("1.0.0.dev1", False)
|
|
64
|
+
assert version == "1.0.0"
|
|
65
|
+
assert not util.is_dev(version)
|
|
66
|
+
|
|
67
|
+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", json={"info": {"version": "2.0.0.dev1"}})
|
|
68
|
+
util.check_version()
|
|
69
|
+
version = util.check_for_updates("1.0.0", False)
|
|
70
|
+
assert version is None
|
|
71
|
+
|
|
72
|
+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", json={"info": {"version": "1.0.1"}})
|
|
73
|
+
util.check_version()
|
|
74
|
+
version = util.check_for_updates("1.0.0", False)
|
|
75
|
+
assert version == "1.0.1"
|
|
76
|
+
assert not util.is_dev(version)
|
|
77
|
+
|
|
77
78
|
importlib = util.import_importlib_resources()
|
|
78
79
|
|
|
79
80
|
data_dir = importlib.files('sinol_make').joinpath("data")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|