sinol-make 1.5.29__tar.gz → 1.6.0.dev2__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 (52) hide show
  1. {sinol_make-1.5.29/src/sinol_make.egg-info → sinol_make-1.6.0.dev2}/PKG-INFO +2 -1
  2. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/setup.cfg +1 -0
  3. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/__init__.py +13 -4
  4. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/doc/__init__.py +1 -0
  5. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/export/__init__.py +16 -2
  6. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/gen/__init__.py +4 -0
  7. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/ingen/__init__.py +5 -1
  8. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/ingen/ingen_util.py +15 -5
  9. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/init/__init__.py +2 -2
  10. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/inwer/__init__.py +12 -2
  11. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/inwer/inwer_util.py +2 -2
  12. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/outgen/__init__.py +1 -0
  13. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/run/__init__.py +1 -0
  14. sinol_make-1.6.0.dev2/src/sinol_make/commands/verify/__init__.py +160 -0
  15. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/contest_types/default.py +7 -1
  16. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/contest_types/oi.py +13 -0
  17. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/cache.py +6 -7
  18. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/compile.py +12 -6
  19. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/package_util.py +19 -2
  20. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/cache_structs.py +11 -1
  21. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/util.py +20 -24
  22. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2/src/sinol_make.egg-info}/PKG-INFO +2 -1
  23. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make.egg-info/SOURCES.txt +1 -0
  24. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make.egg-info/requires.txt +1 -0
  25. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/tests/test_multiple_arguments.py +1 -1
  26. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/tests/test_util.py +23 -22
  27. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/LICENSE +0 -0
  28. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/README.md +0 -0
  29. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/pyproject.toml +0 -0
  30. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/commands/outgen/outgen_util.py +0 -0
  31. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/contest_types/__init__.py +0 -0
  32. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/contest_types/icpc.py +0 -0
  33. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/__init__.py +0 -0
  34. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/compiler.py +0 -0
  35. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/parsers.py +0 -0
  36. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/paths.py +0 -0
  37. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/helpers/printer.py +0 -0
  38. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/interfaces/BaseCommand.py +0 -0
  39. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/interfaces/Errors.py +0 -0
  40. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/interfaces/__init__.py +0 -0
  41. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/oiejq/__init__.py +0 -0
  42. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/oiejq/perf_test.py +0 -0
  43. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/__init__.py +0 -0
  44. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/compiler_structs.py +0 -0
  45. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/gen_structs.py +0 -0
  46. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/inwer_structs.py +0 -0
  47. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/run_structs.py +0 -0
  48. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make/structs/status_structs.py +0 -0
  49. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make.egg-info/dependency_links.txt +0 -0
  50. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make.egg-info/entry_points.txt +0 -0
  51. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/src/sinol_make.egg-info/top_level.txt +0 -0
  52. {sinol_make-1.5.29 → sinol_make-1.6.0.dev2}/tests/test_oiejq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sinol_make
3
- Version: 1.5.29
3
+ Version: 1.6.0.dev2
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"
@@ -29,6 +29,7 @@ install_requires =
29
29
  dictdiffer
30
30
  importlib-resources
31
31
  psutil
32
+ packaging
32
33
 
33
34
  [options.packages.find]
34
35
  where = src
@@ -6,7 +6,7 @@ import argcomplete
6
6
 
7
7
  from sinol_make import util, oiejq
8
8
 
9
- __version__ = "1.5.29"
9
+ __version__ = "1.6.0.dev2"
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
- print(util.warning(
96
- f'New version of sinol-make is available (your version: {__version__}, available version: '
97
- f'{new_version}).\nYou can update it by running `pip3 install sinol-make --upgrade`.'))
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...')
@@ -97,6 +98,7 @@ class Command(BaseCommand):
97
98
  Creates ocen archive for sio2.
98
99
  :param target_dir: Path to exported package.
99
100
  """
101
+ print('Generating ocen archive...')
100
102
  attachments_dir = os.path.join(target_dir, 'attachments')
101
103
  if not os.path.exists(attachments_dir):
102
104
  os.makedirs(attachments_dir)
@@ -109,12 +111,25 @@ class Command(BaseCommand):
109
111
  os.makedirs(in_dir)
110
112
  out_dir = os.path.join(ocen_dir, 'out')
111
113
  os.makedirs(out_dir)
114
+ num_tests = 0
112
115
  for ext in ['in', 'out']:
113
116
  for test in glob.glob(os.path.join(tests_dir, ext, f'{self.task_id}0*.{ext}')) + \
114
117
  glob.glob(os.path.join(tests_dir, ext, f'{self.task_id}*ocen.{ext}')):
115
118
  shutil.copy(test, os.path.join(ocen_dir, ext, os.path.basename(test)))
119
+ num_tests += 1
116
120
 
117
- shutil.make_archive(os.path.join(attachments_dir, f'{self.task_id}ocen'), 'zip', tmpdir)
121
+ if num_tests == 0:
122
+ print(util.warning('No ocen tests found.'))
123
+ else:
124
+ shutil.make_archive(os.path.join(attachments_dir, f'{self.task_id}ocen'), 'zip', tmpdir)
125
+
126
+ dlazaw_dir = os.path.join(os.getcwd(), 'dlazaw')
127
+ if os.path.exists(dlazaw_dir):
128
+ print('Archiving dlazaw directory and adding to attachments.')
129
+ os.makedirs(os.path.join(tmpdir, 'dlazaw'), exist_ok=True)
130
+ shutil.copytree(dlazaw_dir, os.path.join(tmpdir, 'dlazaw', 'dlazaw'))
131
+ shutil.make_archive(os.path.join(attachments_dir, 'dlazaw'), 'zip',
132
+ os.path.join(tmpdir, 'dlazaw'))
118
133
 
119
134
  def compile_statement(self):
120
135
  command = DocCommand()
@@ -168,7 +183,6 @@ class Command(BaseCommand):
168
183
 
169
184
  self.generate_output_files()
170
185
  if self.args.export_ocen:
171
- print('Generating ocen archive...')
172
186
  self.create_ocen(target_dir)
173
187
 
174
188
  def clear_files(self, target_dir: str):
@@ -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.
@@ -57,7 +57,7 @@ def compile_ingen(ingen_path: str, args: argparse.Namespace, compilation_flags='
57
57
 
58
58
  compilers = compiler.verify_compilers(args, [ingen_path])
59
59
  ingen_exe, compile_log_path = compile.compile_file(ingen_path, package_util.get_executable(ingen_path),
60
- compilers, compilation_flags, use_fsanitize=True,
60
+ compilers, compilation_flags, use_fsanitize=use_fsanitize,
61
61
  additional_flags='-D_INGEN')
62
62
 
63
63
  if ingen_exe is None:
@@ -86,11 +86,21 @@ def run_ingen(ingen_exe, working_dir=None):
86
86
  print(util.bold(' Ingen output '.center(util.get_terminal_size()[1], '=')))
87
87
  process = subprocess.Popen([ingen_exe], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
88
88
  cwd=working_dir, shell=is_shell)
89
+ whole_output = ''
89
90
  while process.poll() is None:
90
- print(process.stdout.readline().decode('utf-8'), end='')
91
-
92
- print(process.stdout.read().decode('utf-8'), end='')
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='')
93
98
  exit_code = process.returncode
94
99
  print(util.bold(' End of ingen output '.center(util.get_terminal_size()[1], '=')))
95
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
+
96
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,7 +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)
46
+ return parser
43
47
 
44
48
  @staticmethod
45
49
  def verify_test(execution: InwerExecution) -> VerificationResult:
@@ -94,11 +98,14 @@ class Command(BaseCommand):
94
98
  thr.start()
95
99
 
96
100
  keyboard_interrupt = False
101
+ sanitizer_error = False
97
102
  try:
98
103
  with mp.Pool(self.cpus) as pool:
99
104
  for i, result in enumerate(pool.imap(self.verify_test, executions)):
100
105
  table_data.results[result.test_path].set_results(result.valid, result.output)
101
106
  table_data.i = i
107
+ if util.has_sanitizer_error(result.output, 0 if result.valid else 1):
108
+ sanitizer_error = True
102
109
  except KeyboardInterrupt:
103
110
  keyboard_interrupt = True
104
111
 
@@ -108,6 +115,10 @@ class Command(BaseCommand):
108
115
 
109
116
  print("\n".join(inwer_util.print_view(terminal_width, terminal_height, table_data)[0]))
110
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.'))
111
122
  if keyboard_interrupt:
112
123
  util.exit_with_error('Keyboard interrupt.')
113
124
 
@@ -203,7 +214,7 @@ class Command(BaseCommand):
203
214
  print('Verifying tests: ' + util.bold(', '.join(self.tests)))
204
215
 
205
216
  util.change_stack_size_to_unlimited()
206
- self.inwer_executable = inwer_util.compile_inwer(self.inwer, args, args.compile_mode)
217
+ self.inwer_executable = inwer_util.compile_inwer(self.inwer, args, args.compile_mode, args.fsanitize)
207
218
  results: Dict[str, TestResult] = self.verify_and_print_table()
208
219
  print('')
209
220
 
@@ -218,4 +229,3 @@ class Command(BaseCommand):
218
229
  print("Verifying tests order...")
219
230
  self.verify_tests_order()
220
231
  print(util.info('Verification successful.'))
221
- exit(0)
@@ -29,13 +29,13 @@ 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
37
  inwer_exe, compile_log_path = compile.compile_file(inwer_path, package_util.get_executable(inwer_path), compilers,
38
- compilation_flags, use_fsanitize=True,
38
+ compilation_flags, use_fsanitize=use_fsanitize,
39
39
  additional_flags='-D_INWER')
40
40
 
41
41
  if inwer_exe is None:
@@ -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.')
@@ -301,6 +301,7 @@ class Command(BaseCommand):
301
301
  parser.add_argument('-a', '--apply-suggestions', dest='apply_suggestions', action='store_true',
302
302
  help='apply suggestions from expected scores report')
303
303
  add_compilation_arguments(parser)
304
+ return parser
304
305
 
305
306
  def parse_time(self, time_str):
306
307
  if len(time_str) < 3: return -1
@@ -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.")
@@ -35,7 +35,7 @@ def get_cache_file(solution_path: str) -> CacheFile:
35
35
  return CacheFile()
36
36
 
37
37
 
38
- def check_compiled(file_path: str) -> Union[str, None]:
38
+ def check_compiled(file_path: str, compilation_flags: str, sanitizers: bool) -> Union[str, None]:
39
39
  """
40
40
  Check if a file is compiled
41
41
  :param file_path: Path to the file
@@ -44,7 +44,7 @@ def check_compiled(file_path: str) -> Union[str, None]:
44
44
  md5sum = util.get_file_md5(file_path)
45
45
  try:
46
46
  info = get_cache_file(file_path)
47
- if info.md5sum == md5sum:
47
+ if info.md5sum == md5sum and info.compilation_flags == compilation_flags and info.sanitizers == sanitizers:
48
48
  exe_path = info.executable_path
49
49
  if os.path.exists(exe_path):
50
50
  return exe_path
@@ -53,19 +53,18 @@ def check_compiled(file_path: str) -> Union[str, None]:
53
53
  return None
54
54
 
55
55
 
56
- def save_compiled(file_path: str, exe_path: str, is_checker: bool = False):
56
+ def save_compiled(file_path: str, exe_path: str, compilation_flags: str, sanitizers: bool, is_checker: bool = False):
57
57
  """
58
58
  Save the compiled executable path to cache in `.cache/md5sums/<basename of file_path>`,
59
59
  which contains the md5sum of the file and the path to the executable.
60
60
  :param file_path: Path to the file
61
61
  :param exe_path: Path to the compiled executable
62
+ :param compilation_flags: Compilation flags used
63
+ :param sanitizers: Whether -fsanitize=undefined,address was used
62
64
  :param is_checker: Whether the compiled file is a checker. If True, all cached tests are removed.
63
65
  """
64
- info = CacheFile()
65
- info.executable_path = exe_path
66
- info.md5sum = util.get_file_md5(file_path)
66
+ info = CacheFile(util.get_file_md5(file_path), exe_path, compilation_flags, sanitizers)
67
67
  info.save(file_path)
68
-
69
68
  if is_checker:
70
69
  remove_results_cache()
71
70
 
@@ -37,10 +37,17 @@ def compile(program, output, compilers: Compilers = None, compile_log=None, comp
37
37
  if use_fsanitize and util.is_macos_arm():
38
38
  use_fsanitize = False
39
39
 
40
+ if compilation_flags == 'w':
41
+ compilation_flags = 'weak'
42
+ elif compilation_flags == 'o':
43
+ compilation_flags = 'oioioi'
44
+ elif compilation_flags == 'd':
45
+ compilation_flags = 'default'
46
+
40
47
  if extra_compilation_files is None:
41
48
  extra_compilation_files = []
42
49
 
43
- compiled_exe = check_compiled(program)
50
+ compiled_exe = check_compiled(program, compilation_flags, use_fsanitize)
44
51
  if compiled_exe is not None:
45
52
  if compile_log is not None:
46
53
  compile_log.write(f'Using cached executable {compiled_exe}\n')
@@ -53,12 +60,11 @@ def compile(program, output, compilers: Compilers = None, compile_log=None, comp
53
60
  shutil.copy(file, os.path.join(os.path.dirname(output), os.path.basename(file)))
54
61
 
55
62
  gcc_compilation_flags = ''
56
- if compilation_flags == 'weak' or compilation_flags == 'w':
57
- compilation_flags = 'weak'
63
+ if compilation_flags == 'weak':
58
64
  gcc_compilation_flags = '' # Disable all warnings
59
- elif compilation_flags == 'oioioi' or compilation_flags == 'o':
65
+ elif compilation_flags == 'oioioi':
60
66
  gcc_compilation_flags = ' -Wall -Wno-unused-result -Werror' # Same flags as oioioi
61
- elif compilation_flags == 'default' or compilation_flags == 'd':
67
+ elif compilation_flags == 'default':
62
68
  gcc_compilation_flags = ' -Werror -Wall -Wextra -Wshadow -Wconversion -Wno-unused-result -Wfloat-equal'
63
69
  else:
64
70
  util.exit_with_error(f'Unknown compilation flags group: {compilation_flags}')
@@ -108,7 +114,7 @@ def compile(program, output, compilers: Compilers = None, compile_log=None, comp
108
114
  if process.returncode != 0:
109
115
  raise CompilationError('Compilation failed')
110
116
  else:
111
- save_compiled(program, output, is_checker)
117
+ save_compiled(program, output, compilation_flags, use_fsanitize, is_checker)
112
118
  return True
113
119
 
114
120
 
@@ -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 = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).in$' % (re.escape(task_id)))
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 = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).out$' % (re.escape(task_id)))
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
@@ -43,20 +43,28 @@ class CacheFile:
43
43
  md5sum: str
44
44
  # Path to the executable
45
45
  executable_path: str
46
+ # Compilation flags used
47
+ compilation_flags: str
48
+ # Whether -fsanitize=undefined,address was used
49
+ sanitizers: bool
46
50
  # Test results
47
51
  tests: Dict[str, CacheTest]
48
52
 
49
- def __init__(self, md5sum="", executable_path="", tests=None):
53
+ def __init__(self, md5sum="", executable_path="", compilation_flags="default", sanitizers=False, tests=None):
50
54
  if tests is None:
51
55
  tests = {}
52
56
  self.md5sum = md5sum
53
57
  self.executable_path = executable_path
58
+ self.compilation_flags = compilation_flags
59
+ self.sanitizers = sanitizers
54
60
  self.tests = tests
55
61
 
56
62
  def to_dict(self) -> Dict:
57
63
  return {
58
64
  "md5sum": self.md5sum,
59
65
  "executable_path": self.executable_path,
66
+ "compilation_flags": self.compilation_flags,
67
+ "sanitizers": self.sanitizers,
60
68
  "tests": {k: v.to_dict() for k, v in self.tests.items()}
61
69
  }
62
70
 
@@ -65,6 +73,8 @@ class CacheFile:
65
73
  return CacheFile(
66
74
  md5sum=dict.get("md5sum", ""),
67
75
  executable_path=dict.get("executable_path", ""),
76
+ compilation_flags=dict.get("compilation_flags", "default"),
77
+ sanitizers=dict.get("sanitizers", False),
68
78
  tests={k: CacheTest(
69
79
  time_limit=v["time_limit"],
70
80
  memory_limit=v["memory_limit"],
@@ -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 check_for_updates(current_version) -> Union[str, None]:
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
- process = multiprocessing.Process(target=check_version, daemon=True)
168
- process.start()
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 compare_versions(current_version, version) == -1:
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.5.29
3
+ Version: 1.6.0.dev2
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
@@ -5,6 +5,7 @@ PyYAML
5
5
  dictdiffer
6
6
  importlib-resources
7
7
  psutil
8
+ packaging
8
9
 
9
10
  [tests]
10
11
  pytest
@@ -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 ingen inwer run")
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