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.
Files changed (59) hide show
  1. {sinol_make-1.6.0.dev4/src/sinol_make.egg-info → sinol_make-1.7.0.dev1}/PKG-INFO +4 -1
  2. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/README.md +3 -0
  3. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/setup.cfg +1 -0
  4. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/__init__.py +3 -2
  5. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/export/__init__.py +1 -1
  6. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/gen/__init__.py +4 -4
  7. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/ingen/__init__.py +25 -9
  8. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/inwer/__init__.py +1 -3
  9. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/outgen/__init__.py +4 -0
  10. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/run/__init__.py +36 -305
  11. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/verify/__init__.py +10 -4
  12. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/__init__.py +3 -0
  13. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/default.py +6 -0
  14. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/icpc.py +3 -0
  15. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/contest_types/oi.py +4 -1
  16. sinol_make-1.7.0.dev1/src/sinol_make/contest_types/oij.py +32 -0
  17. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/cache.py +3 -3
  18. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/compile.py +4 -5
  19. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/package_util.py +2 -1
  20. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/parsers.py +6 -0
  21. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/oiejq/__init__.py +20 -8
  22. sinol_make-1.7.0.dev1/src/sinol_make/structs/package_structs.py +7 -0
  23. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/run_structs.py +2 -0
  24. sinol_make-1.7.0.dev1/src/sinol_make/task_type/__init__.py +17 -0
  25. sinol_make-1.7.0.dev1/src/sinol_make/task_type/base.py +273 -0
  26. sinol_make-1.7.0.dev1/src/sinol_make/task_type/interactive_io.py +219 -0
  27. sinol_make-1.7.0.dev1/src/sinol_make/task_type/normal.py +77 -0
  28. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/util.py +1 -0
  29. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1/src/sinol_make.egg-info}/PKG-INFO +4 -1
  30. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/SOURCES.txt +6 -0
  31. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/entry_points.txt +1 -0
  32. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/tests/test_oiejq.py +41 -4
  33. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/LICENSE +0 -0
  34. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/pyproject.toml +0 -0
  35. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/doc/__init__.py +0 -0
  36. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/ingen/ingen_util.py +0 -0
  37. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/init/__init__.py +0 -0
  38. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/inwer/inwer_util.py +0 -0
  39. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/commands/outgen/outgen_util.py +0 -0
  40. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/__init__.py +0 -0
  41. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/compiler.py +0 -0
  42. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/func_cache.py +0 -0
  43. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/paths.py +0 -0
  44. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/helpers/printer.py +0 -0
  45. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/BaseCommand.py +0 -0
  46. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/Errors.py +0 -0
  47. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/interfaces/__init__.py +0 -0
  48. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/oiejq/perf_test.py +0 -0
  49. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/__init__.py +0 -0
  50. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/cache_structs.py +0 -0
  51. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/compiler_structs.py +0 -0
  52. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/gen_structs.py +0 -0
  53. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/inwer_structs.py +0 -0
  54. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make/structs/status_structs.py +0 -0
  55. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/dependency_links.txt +0 -0
  56. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/requires.txt +0 -0
  57. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/src/sinol_make.egg-info/top_level.txt +0 -0
  58. {sinol_make-1.6.0.dev4 → sinol_make-1.7.0.dev1}/tests/test_multiple_arguments.py +0 -0
  59. {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.6.0.dev4
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:
@@ -44,6 +44,7 @@ tests =
44
44
  [options.entry_points]
45
45
  console_scripts =
46
46
  sinol-make = sinol_make:main
47
+ sm = sinol_make:main
47
48
 
48
49
  [tool:pytest]
49
50
  testpaths =
@@ -6,7 +6,8 @@ import argcomplete
6
6
 
7
7
  from sinol_make import util, oiejq
8
8
 
9
- __version__ = "1.6.0.dev4"
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 now...'))
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 arhive creation because dlazaw directory exists.'))
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
- parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
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
- parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
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
- print('Cleaning up old input files.')
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
- parser.add_argument('-f', '--fsanitize', default=False, action='store_true',
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, is_checker=False):
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, is_checker) for solution in solutions]
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 = False, is_checker = False):
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, is_checker=is_checker)
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
- hard_time_limit_in_s = math.ceil(2 * time_limit / 1000.0)
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, test_time_limit, test_memory_limit,
686
- self.timetool_path, os.path.dirname(executable)))
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.checker is None:
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.exit_with_error(error_msg)
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
- checker = package_util.get_files_matching_pattern(self.ID, f'{self.ID}chk.*')
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.")