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.
Files changed (52) hide show
  1. {sinol_make-1.5.28/src/sinol_make.egg-info → sinol_make-1.6.0.dev1}/PKG-INFO +2 -1
  2. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/setup.cfg +1 -0
  3. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/__init__.py +13 -4
  4. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/doc/__init__.py +1 -0
  5. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/export/__init__.py +1 -0
  6. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/gen/__init__.py +4 -0
  7. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/ingen/__init__.py +5 -1
  8. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/ingen/ingen_util.py +17 -6
  9. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/init/__init__.py +2 -2
  10. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/inwer/__init__.py +12 -9
  11. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/inwer/inwer_util.py +16 -6
  12. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/outgen/__init__.py +1 -0
  13. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/run/__init__.py +22 -12
  14. sinol_make-1.6.0.dev1/src/sinol_make/commands/verify/__init__.py +160 -0
  15. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/default.py +7 -1
  16. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/oi.py +13 -0
  17. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/cache.py +31 -11
  18. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/compile.py +5 -1
  19. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/package_util.py +19 -2
  20. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/util.py +20 -24
  21. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1/src/sinol_make.egg-info}/PKG-INFO +2 -1
  22. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/SOURCES.txt +1 -0
  23. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/requires.txt +1 -0
  24. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/tests/test_multiple_arguments.py +1 -1
  25. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/tests/test_util.py +23 -22
  26. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/LICENSE +0 -0
  27. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/README.md +0 -0
  28. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/pyproject.toml +0 -0
  29. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/commands/outgen/outgen_util.py +0 -0
  30. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/__init__.py +0 -0
  31. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/contest_types/icpc.py +0 -0
  32. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/__init__.py +0 -0
  33. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/compiler.py +0 -0
  34. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/parsers.py +0 -0
  35. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/paths.py +0 -0
  36. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/helpers/printer.py +0 -0
  37. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/BaseCommand.py +0 -0
  38. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/Errors.py +0 -0
  39. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/interfaces/__init__.py +0 -0
  40. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/oiejq/__init__.py +0 -0
  41. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/oiejq/perf_test.py +0 -0
  42. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/__init__.py +0 -0
  43. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/cache_structs.py +0 -0
  44. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/compiler_structs.py +0 -0
  45. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/gen_structs.py +0 -0
  46. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/inwer_structs.py +0 -0
  47. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/run_structs.py +0 -0
  48. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make/structs/status_structs.py +0 -0
  49. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/dependency_links.txt +0 -0
  50. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/entry_points.txt +0 -0
  51. {sinol_make-1.5.28 → sinol_make-1.6.0.dev1}/src/sinol_make.egg-info/top_level.txt +0 -0
  52. {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.5.28
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"
@@ -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.28"
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
- 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...')
@@ -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), compilers,
60
- compilation_flags, use_fsanitize=True)
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
- print(process.stdout.readline().decode('utf-8'), end='')
90
-
91
- 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='')
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 = None) -> Union[str, None]:
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
- return compile.compile_file(inwer_path, package_util.get_executable(inwer_path), compilers, compilation_flags,
38
- use_fsanitize=True)
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[2] - 9 - 3) # 9 is for " | " between columns, 3 for margin.
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) + "+" + "-" * (column_lengths[2] + 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: " + "\n".join(lines)
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.save_to_cache_extra_compilation_files(self.config.get("extra_compilation_files", []), self.ID)
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 save_to_cache_extra_compilation_files(extra_compilation_files, task_id):
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
- info = get_cache_file(file_path)
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
- info.md5sum = md5sum
99
- info.save(file_path)
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', use_fsanitize=False) \
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 = 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
@@ -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.28
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
@@ -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