diff --git a/.github/workflows/Arch.yaml b/.github/workflows/Arch.yaml index ec947f1b..f3777c8c 100644 --- a/.github/workflows/Arch.yaml +++ b/.github/workflows/Arch.yaml @@ -20,7 +20,7 @@ jobs: - name: Prepare system run: | sysctl kernel.perf_event_paranoid=-1 - pacman -Syu --noconfirm diffutils time gcc dpkg + pacman -Syu --noconfirm diffutils time gcc dpkg ghostscript texlive-latexextra - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: diff --git a/.github/workflows/Ubuntu.yaml b/.github/workflows/Ubuntu.yaml index c754d1db..b63baad1 100644 --- a/.github/workflows/Ubuntu.yaml +++ b/.github/workflows/Ubuntu.yaml @@ -14,6 +14,8 @@ jobs: - /home/actions/oiejq:/github/home/.local/bin env: DEB_PYTHON_INSTALL_LAYOUT: deb + DEBIAN_FRONTEND: noninteractive + TZ: Europe/Warsaw options: --privileged steps: @@ -22,7 +24,7 @@ jobs: - name: Prepare system run: | apt update - apt install -y sqlite3 sqlite3-doc build-essential dpkg + apt install -y sqlite3 sqlite3-doc build-essential dpkg texlive-latex-extra ghostscript sysctl kernel.perf_event_paranoid=-1 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 diff --git a/.github/workflows/macOS.yaml b/.github/workflows/macOS.yaml index 35591120..dbe1eda0 100644 --- a/.github/workflows/macOS.yaml +++ b/.github/workflows/macOS.yaml @@ -25,7 +25,7 @@ jobs: run: | rm -f /usr/local/bin/2to3* /usr/local/bin/python3* /usr/local/bin/idle3* \ /usr/local/bin/pydoc3* # Homebrew will fail if these exist - brew install gnu-time coreutils diffutils dpkg + brew install gnu-time coreutils diffutils dpkg ghostscript texlive - name: Install Python dependencies run: | python3 -m pip install .[tests] diff --git a/README.md b/README.md index 26325033..18a83c4f 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ You can also specify your ingen source file which will be used. Run `sinol-make - `sinol-make inwer` -- Verifies whether input files are correct using your "inwer.cpp" program. You can specify what inwer program to use, what tests to check and how many CPUs to use. Run `sinol-make inwer --help` to see available flags. - `sinol-make export` -- Creates archive ready to upload to sio2 or szkopul. Run `sinol-make export --help` to see all available flags. +- `sinol-make doc` -- Compiles all LaTeX files in doc/ directory to PDF. Run `sinol-make doc --help` to see all available flags. ### Reporting bugs and contributing code diff --git a/src/sinol_make/__init__.py b/src/sinol_make/__init__.py index 933c60f0..968856fb 100644 --- a/src/sinol_make/__init__.py +++ b/src/sinol_make/__init__.py @@ -8,7 +8,9 @@ from sinol_make import util, oiejq -__version__ = "1.5.6" + +__version__ = "1.5.7" + def configure_parsers(): parser = argparse.ArgumentParser( diff --git a/src/sinol_make/commands/doc/__init__.py b/src/sinol_make/commands/doc/__init__.py new file mode 100644 index 00000000..d6fd78fc --- /dev/null +++ b/src/sinol_make/commands/doc/__init__.py @@ -0,0 +1,71 @@ +import os +import glob +import argparse +import subprocess + +from sinol_make import util +from sinol_make.interfaces.BaseCommand import BaseCommand + + +class Command(BaseCommand): + """ + Class for `doc` command. + """ + + def get_name(self): + return "doc" + + def compile_file(self, file_path): + print(util.info(f'Compiling {os.path.basename(file_path)}...')) + os.chdir(os.path.dirname(file_path)) + subprocess.run(['latex', file_path]) + dvi_file = os.path.splitext(file_path)[0] + '.dvi' + dvi_file_path = os.path.join(os.path.dirname(file_path), dvi_file) + if not os.path.exists(dvi_file_path): + print(util.error('Compilation failed.')) + return False + + process = subprocess.run(['dvipdf', dvi_file_path]) + if process.returncode != 0: + print(util.error('Compilation failed.')) + return False + print(util.info(f'Compilation successful for file {os.path.basename(file_path)}.')) + return True + + def configure_subparser(self, subparser: argparse.ArgumentParser): + parser = subparser.add_parser( + self.get_name(), + help='Compile latex files to pdf', + description='Compiles latex files to pdf. By default compiles all files in the `doc` directory.\n' + 'You can also specify files to compile.') + parser.add_argument('files', type=str, nargs='*', help='files to compile') + + def run(self, args: argparse.Namespace): + util.exit_if_not_package() + + if args.files == []: + self.files = glob.glob(os.path.join(os.getcwd(), 'doc', '*.tex')) + else: + self.files = [] + for file in args.files: + if not os.path.exists(file): + print(util.warning(f'File {file} does not exist.')) + else: + self.files.append(os.path.abspath(file)) + if self.files == []: + print(util.warning('No files to compile.')) + return + + original_cwd = os.getcwd() + failed = [] + for file in self.files: + if not self.compile_file(file): + failed.append(file) + os.chdir(original_cwd) + + if failed: + for failed_file in failed: + print(util.error(f'Failed to compile {failed_file}')) + util.exit_with_error('Compilation failed.') + else: + print(util.info('Compilation was successful for all files.')) diff --git a/src/sinol_make/commands/export/__init__.py b/src/sinol_make/commands/export/__init__.py index 3727398d..8dbbc759 100644 --- a/src/sinol_make/commands/export/__init__.py +++ b/src/sinol_make/commands/export/__init__.py @@ -45,7 +45,7 @@ def get_generated_tests(self): util.exit_with_error('Failed to run ingen.') tests = glob.glob(os.path.join(working_dir, f'{self.task_id}*.in')) - return [package_util.extract_test_id(test) for test in tests] + return [package_util.extract_test_id(test, self.task_id) for test in tests] def copy_package_required_files(self, target_dir: str): """ @@ -74,7 +74,7 @@ def copy_package_required_files(self, target_dir: str): tests_to_copy = [] for ext in ['in', 'out']: for test in glob.glob(os.path.join(os.getcwd(), ext, f'{self.task_id}*.{ext}')): - if package_util.extract_test_id(test) not in generated_tests: + if package_util.extract_test_id(test, self.task_id) not in generated_tests: tests_to_copy.append(test) if len(tests_to_copy) > 0: @@ -83,6 +83,16 @@ def copy_package_required_files(self, target_dir: str): print(util.warning(f'Coping {os.path.basename(test)}...')) shutil.copy(test, os.path.join(target_dir, os.path.splitext(os.path.basename(test))[1])) + def clear_files(self, target_dir: str): + """ + Clears unnecessary files from target directory. + :param target_dir: Directory to clear files from. + """ + files_to_remove = ['doc/*~', 'doc/*.aux', 'doc/*.log', 'doc/*.dvi', 'doc/*.err', 'doc/*.inf'] + for pattern in files_to_remove: + for f in glob.glob(os.path.join(target_dir, pattern)): + os.remove(f) + def create_makefile_in(self, target_dir: str, config: dict): """ Creates required `makefile.in` file. @@ -90,8 +100,8 @@ def create_makefile_in(self, target_dir: str, config: dict): :param config: Config dictionary. """ with open(os.path.join(target_dir, 'makefile.in'), 'w') as f: - cxx_flags = '-std=c++17' - c_flags = '-std=c17' + cxx_flags = '-std=c++20' + c_flags = '-std=gnu99' def format_multiple_arguments(obj): if isinstance(obj, str): return obj @@ -132,6 +142,7 @@ def run(self, args: argparse.Namespace): self.args = args self.task_id = package_util.get_task_id() + package_util.validate_test_names(self.task_id) with open(os.path.join(os.getcwd(), 'config.yml'), 'r') as config_file: config = yaml.load(config_file, Loader=yaml.FullLoader) @@ -143,6 +154,7 @@ def run(self, args: argparse.Namespace): util.change_stack_size_to_unlimited() self.copy_package_required_files(export_package_path) + self.clear_files(export_package_path) self.create_makefile_in(export_package_path, config) archive = self.compress(export_package_path) diff --git a/src/sinol_make/commands/gen/__init__.py b/src/sinol_make/commands/gen/__init__.py index 4e625d7e..b02ab0b7 100644 --- a/src/sinol_make/commands/gen/__init__.py +++ b/src/sinol_make/commands/gen/__init__.py @@ -93,6 +93,7 @@ def run(self, args: argparse.Namespace): self.args = args self.task_id = package_util.get_task_id() + package_util.validate_test_names(self.task_id) self.ingen = gen_util.get_ingen(self.task_id, args.ingen_path) print(util.info(f'Using ingen file {os.path.basename(self.ingen)}')) diff --git a/src/sinol_make/commands/gen/gen_util.py b/src/sinol_make/commands/gen/gen_util.py index 42f9d94f..5123372f 100644 --- a/src/sinol_make/commands/gen/gen_util.py +++ b/src/sinol_make/commands/gen/gen_util.py @@ -16,15 +16,14 @@ def ingen_exists(task_id): :param task_id: task id, for example abc :return: True if exists, False otherwise """ - return len(glob.glob(os.path.join(os.getcwd(), 'prog', task_id + 'ingen.*'))) > 0 + return package_util.any_files_matching_pattern(task_id, f'{task_id}ingen.*') -def get_ingen(task_id=None, ingen_path=None): +def get_ingen(task_id, ingen_path=None): """ Find ingen source file in `prog/` directory. If `ingen_path` is specified, then it will be used (if exists). - :param task_id: task id, for example abc. If None, then - will return any ingen matching "*ingen.*" + :param task_id: task id, for example abc. :param ingen_path: path to ingen source file :return: path to ingen source file or None if not found """ @@ -35,9 +34,7 @@ def get_ingen(task_id=None, ingen_path=None): else: util.exit_with_error(f'Ingen source file {ingen_path} does not exist.') - if task_id is None: - task_id = '*' - ingen = glob.glob(os.path.join(os.getcwd(), 'prog', task_id + 'ingen.*')) + ingen = package_util.get_files_matching_pattern(task_id, f'{task_id}ingen.*') if len(ingen) == 0: util.exit_with_error(f'Ingen source file for task {task_id} does not exist.') @@ -78,7 +75,7 @@ def get_correct_solution(task_id): :param task_id: task id, for example abc :return: path to correct solution or None if not found """ - correct_solution = glob.glob(os.path.join(os.getcwd(), 'prog', task_id + '.*')) + correct_solution = package_util.get_files_matching_pattern(task_id, f'{task_id}.*') if len(correct_solution) == 0: util.exit_with_error(f'Correct solution for task {task_id} does not exist.') return correct_solution[0] diff --git a/src/sinol_make/commands/inwer/__init__.py b/src/sinol_make/commands/inwer/__init__.py index cf45bed6..2f942adf 100644 --- a/src/sinol_make/commands/inwer/__init__.py +++ b/src/sinol_make/commands/inwer/__init__.py @@ -87,7 +87,7 @@ def verify_and_print_table(self) -> Dict[str, TestResult]: sorted_tests = sorted(self.tests, key=lambda x: x[0]) executions: List[InwerExecution] = [] for test in sorted_tests: - results[test] = TestResult(test) + results[test] = TestResult(test, self.task_id) executions.append(InwerExecution(test, results[test].test_name, self.inwer_executable)) has_terminal, terminal_width, terminal_height = util.get_terminal_size() @@ -123,6 +123,7 @@ def run(self, args: argparse.Namespace): util.exit_if_not_package() self.task_id = package_util.get_task_id() + package_util.validate_test_names(self.task_id) self.inwer = inwer_util.get_inwer_path(self.task_id, args.inwer_path) if self.inwer is None: if args.inwer_path is None: @@ -133,7 +134,7 @@ def run(self, args: argparse.Namespace): print(f'Verifying with inwer {util.bold(relative_path)}') self.cpus = args.cpus or mp.cpu_count() - self.tests = package_util.get_tests(args.tests) + self.tests = package_util.get_tests(self.task_id, args.tests) if len(self.tests) == 0: util.exit_with_error('No tests found.') diff --git a/src/sinol_make/commands/inwer/inwer_util.py b/src/sinol_make/commands/inwer/inwer_util.py index b9ec0653..58ed5a9a 100644 --- a/src/sinol_make/commands/inwer/inwer_util.py +++ b/src/sinol_make/commands/inwer/inwer_util.py @@ -18,7 +18,7 @@ def get_inwer_path(task_id: str, path = None) -> Union[str, None]: Returns path to inwer executable for given task or None if no inwer was found. """ if path is None: - inwers = glob.glob(os.path.join(os.getcwd(), 'prog', f'{task_id}inwer.*')) + inwers = package_util.get_files_matching_pattern(task_id, f'{task_id}inwer.*') if len(inwers) == 0: return None return inwers[0] diff --git a/src/sinol_make/commands/inwer/structs.py b/src/sinol_make/commands/inwer/structs.py index 5dfd30b5..f78d6f7c 100644 --- a/src/sinol_make/commands/inwer/structs.py +++ b/src/sinol_make/commands/inwer/structs.py @@ -14,10 +14,10 @@ class TestResult: valid: bool output: str - def __init__(self, test_path): + def __init__(self, test_path, task_id): self.test_path = test_path self.test_name = os.path.split(test_path)[-1] - self.test_group = str(package_util.get_group(self.test_path)) + self.test_group = str(package_util.get_group(self.test_path, task_id)) self.verified = False self.valid = False diff --git a/src/sinol_make/commands/run/__init__.py b/src/sinol_make/commands/run/__init__.py index cebe4415..958d3db3 100644 --- a/src/sinol_make/commands/run/__init__.py +++ b/src/sinol_make/commands/run/__init__.py @@ -58,7 +58,7 @@ def update_group_status(group_status, new_status): return group_status -def print_view(term_width, term_height, program_groups_scores, all_results, print_data: PrintData, names, executions, +def print_view(term_width, term_height, task_id, program_groups_scores, all_results, print_data: PrintData, names, executions, groups, scores, tests, possible_score, cpus, hide_memory, config, contest, args): width = term_width - 13 # First column has 6 characters, the " | " separator has 3 characters and 4 for margin programs_in_row = width // 13 # Each program has 10 characters and the " | " separator has 3 characters @@ -79,7 +79,7 @@ def print_view(term_width, term_height, program_groups_scores, all_results, prin for solution in names: lang = package_util.get_file_lang(solution) for test in tests: - time_sum += package_util.get_time_limit(test, config, lang, args) + time_sum += package_util.get_time_limit(test, config, lang, task_id, args) time_remaining = (len(executions) - print_data.i - 1) * 2 * time_sum / cpus / 1000.0 title = 'Done %4d/%4d. Time remaining (in the worst case): %5d seconds.' \ @@ -122,17 +122,17 @@ def print_table_end(): if results[test].Time is not None: if program_times[program][0] < results[test].Time: program_times[program] = (results[test].Time, package_util.get_time_limit(test, config, - lang, args)) + lang, task_id, args)) elif status == Status.TL: - program_times[program] = (2 * package_util.get_time_limit(test, config, lang, args), - package_util.get_time_limit(test, config, lang, args)) + program_times[program] = (2 * package_util.get_time_limit(test, config, lang, task_id, args), + package_util.get_time_limit(test, config, lang, task_id, args)) if results[test].Memory is not None: if program_memory[program][0] < results[test].Memory: program_memory[program] = (results[test].Memory, package_util.get_memory_limit(test, config, - lang, args)) + lang, task_id, args)) elif status == Status.ML: - program_memory[program] = (2 * package_util.get_memory_limit(test, config, lang, args), - package_util.get_memory_limit(test, config, lang, args)) + program_memory[program] = (2 * package_util.get_memory_limit(test, config, lang, task_id, args), + package_util.get_memory_limit(test, config, lang, task_id, args)) if status == Status.PENDING: group_status = Status.PENDING else: @@ -187,29 +187,29 @@ def print_group_seperator(): last_group = None for test in tests: - group = package_util.get_group(test) + group = package_util.get_group(test, task_id) if last_group != group: if last_group is not None: print_group_seperator() last_group = group - print(margin + "%6s" % package_util.extract_test_id(test), end=" | ") + print(margin + "%6s" % package_util.extract_test_id(test, task_id), end=" | ") for program in program_group: lang = package_util.get_file_lang(program) - result = all_results[program][package_util.get_group(test)][test] + result = all_results[program][package_util.get_group(test, task_id)][test] status = result.Status if status == Status.PENDING: print(10 * ' ', end=" | ") else: print("%3s" % colorize_status(status), - ("%17s" % color_time(result.Time, package_util.get_time_limit(test, config, lang, args))) + ("%17s" % color_time(result.Time, package_util.get_time_limit(test, config, lang, task_id, args))) if result.Time is not None else 7*" ", end=" | ") print() if not hide_memory: print(8*" ", end=" | ") for program in program_group: lang = package_util.get_file_lang(program) - result = all_results[program][package_util.get_group(test)][test] - print(("%20s" % color_memory(result.Memory, package_util.get_memory_limit(test, config, lang, args))) + result = all_results[program][package_util.get_group(test, task_id)][test] + print(("%20s" % color_memory(result.Memory, package_util.get_memory_limit(test, config, lang, task_id, args))) if result.Memory is not None else 10*" ", end=" | ") print() @@ -277,9 +277,9 @@ def extract_file_name(self, file_path): def get_group(self, test_path): - if package_util.extract_test_id(test_path).endswith("ocen"): + if package_util.extract_test_id(test_path, self.ID).endswith("ocen"): return 0 - return int("".join(filter(str.isdigit, package_util.extract_test_id(test_path)))) + return int("".join(filter(str.isdigit, package_util.extract_test_id(test_path, self.ID)))) def get_executable_key(self, executable): @@ -613,7 +613,7 @@ def run_solution(self, data_for_execution: ExecutionData): """ (name, executable, test, time_limit, memory_limit, timetool_path) = data_for_execution - file_no_ext = paths.get_executions_path(name, package_util.extract_test_id(test)) + file_no_ext = paths.get_executions_path(name, package_util.extract_test_id(test, self.ID)) output_file = file_no_ext + ".out" result_file = file_no_ext + ".res" hard_time_limit_in_s = math.ceil(2 * time_limit / 1000.0) @@ -649,8 +649,10 @@ def run_solutions(self, compiled_commands, names, solutions): lang = package_util.get_file_lang(name) if result: for test in self.tests: - executions.append((name, executable, test, package_util.get_time_limit(test, self.config, lang, self.args), - package_util.get_memory_limit(test, self.config, lang, self.args), self.timetool_path)) + executions.append((name, executable, test, + package_util.get_time_limit(test, self.config, lang, self.ID, self.args), + package_util.get_memory_limit(test, self.config, lang, self.ID, self.args), + self.timetool_path)) all_results[name][self.get_group(test)][test] = ExecutionResult(Status.PENDING) os.makedirs(paths.get_executions_path(name), exist_ok=True) else: @@ -667,8 +669,8 @@ def run_solutions(self, compiled_commands, names, solutions): run_event = threading.Event() run_event.set() thr = threading.Thread(target=printer.printer_thread, - args=(run_event, print_view, program_groups_scores, all_results, print_data, names, - executions, self.groups, self.scores, self.tests, self.possible_score, + args=(run_event, print_view, self.ID, program_groups_scores, all_results, print_data, + names, executions, self.groups, self.scores, self.tests, self.possible_score, self.cpus, self.args.hide_memory, self.config, self.contest, self.args)) thr.start() @@ -690,7 +692,7 @@ def run_solutions(self, compiled_commands, names, solutions): run_event.clear() thr.join() - print("\n".join(print_view(terminal_width, terminal_height, program_groups_scores, all_results, print_data, + print("\n".join(print_view(terminal_width, terminal_height, self.ID, program_groups_scores, all_results, print_data, names, executions, self.groups, self.scores, self.tests, self.possible_score, self.cpus, self.args.hide_memory, self.config, self.contest, self.args)[0])) @@ -747,15 +749,15 @@ def get_whole_groups(self): Returns a list of groups for which all tests were run. """ group_sizes = {} - for test in package_util.get_tests(): - group = package_util.get_group(test) + for test in package_util.get_tests(self.ID): + group = package_util.get_group(test, self.ID) if group not in group_sizes: group_sizes[group] = 0 group_sizes[group] += 1 run_group_sizes = {} for test in self.tests: - group = package_util.get_group(test) + group = package_util.get_group(test, self.ID) if group not in run_group_sizes: run_group_sizes[group] = 0 run_group_sizes[group] += 1 @@ -1055,7 +1057,7 @@ def exit(self): cnt=len(self.failed_compilations), letter='' if len(self.failed_compilations) == 1 else 's')) def set_scores(self): - self.tests = package_util.get_tests(self.args.tests) + self.tests = package_util.get_tests(self.ID, self.args.tests) self.groups = self.get_groups(self.tests) self.scores = collections.defaultdict(int) @@ -1099,10 +1101,10 @@ def get_valid_input_files(self): Returns list of input files that have corresponding output file. """ output_tests = glob.glob(os.path.join(os.getcwd(), "out", "*.out")) - output_tests_ids = [package_util.extract_test_id(test) for test in output_tests] + output_tests_ids = [package_util.extract_test_id(test, self.ID) for test in output_tests] valid_input_files = [] for test in self.tests: - if package_util.extract_test_id(test) in output_tests_ids: + if package_util.extract_test_id(test, self.ID) in output_tests_ids: valid_input_files.append(test) return valid_input_files @@ -1157,6 +1159,7 @@ def run(self, args): util.exit_if_not_package() self.set_constants() + package_util.validate_test_names(self.ID) self.args = args with open(os.path.join(os.getcwd(), "config.yml"), 'r') as config: try: @@ -1178,7 +1181,7 @@ def run(self, args): print("Task: %s (tag: %s)" % (title, self.ID)) self.cpus = args.cpus or mp.cpu_count() - checker = glob.glob(os.path.join(os.getcwd(), "prog", f'{self.ID}chk.*')) + checker = package_util.get_files_matching_pattern(self.ID, f'{self.ID}chk.*') if len(checker) != 0: print(util.info("Checker found: %s" % os.path.basename(checker[0]))) self.checker = checker[0] @@ -1191,7 +1194,7 @@ def run(self, args): else: self.checker = None - lib = glob.glob(os.path.join(os.getcwd(), "prog", f'{self.ID}lib.*')) + lib = package_util.get_files_matching_pattern(self.ID, f'{self.ID}lib.*') self.has_lib = len(lib) != 0 self.set_scores() @@ -1204,8 +1207,8 @@ def run(self, args): lang = package_util.get_file_lang(solution) for test in self.tests: # The functions will exit if the limits are not set - _ = package_util.get_time_limit(test, self.config, lang, self.args) - _ = package_util.get_memory_limit(test, self.config, lang, self.args) + _ = package_util.get_time_limit(test, self.config, lang, self.ID, self.args) + _ = package_util.get_memory_limit(test, self.config, lang, self.ID, self.args) results, all_results = self.compile_and_run(solutions) self.check_errors(all_results) diff --git a/src/sinol_make/helpers/compile.py b/src/sinol_make/helpers/compile.py index a234b0d8..b8defdeb 100644 --- a/src/sinol_make/helpers/compile.py +++ b/src/sinol_make/helpers/compile.py @@ -100,11 +100,11 @@ def compile(program, output, compilers: Compilers = None, compile_log = None, we if ext == '.cpp': arguments = [compilers.cpp_compiler_path or compiler.get_cpp_compiler_path(), program] + \ extra_compilation_args + ['-o', output] + \ - f'--std=c++17 -O3 -lm {gcc_compilation_flags} -fdiagnostics-color'.split(' ') + f'--std=c++20 -O3 -lm {gcc_compilation_flags} -fdiagnostics-color'.split(' ') elif ext == '.c': arguments = [compilers.c_compiler_path or compiler.get_c_compiler_path(), program] + \ extra_compilation_args + ['-o', output] + \ - f'--std=c17 -O3 -lm {gcc_compilation_flags} -fdiagnostics-color'.split(' ') + f'--std=gnu99 -O3 -lm {gcc_compilation_flags} -fdiagnostics-color'.split(' ') elif ext == '.py': if sys.platform == 'win32' or sys.platform == 'cygwin': # TODO: Make this work on Windows diff --git a/src/sinol_make/helpers/compiler.py b/src/sinol_make/helpers/compiler.py index 4162e0c3..6b11bc36 100644 --- a/src/sinol_make/helpers/compiler.py +++ b/src/sinol_make/helpers/compiler.py @@ -52,6 +52,8 @@ def get_cpp_compiler_path(): else: return 'g++' elif sys.platform == 'darwin': + if check_if_installed('g++-12'): # g++12 is currently the default compiler on sio. + return 'g++-12' for i in [9, 10, 11]: compiler = 'g++-' + str(i) if check_if_installed(compiler): @@ -65,10 +67,12 @@ def get_python_interpreter_path(): Get the Python interpreter """ - if not check_if_installed('python3'): - return None - else: - return 'python3' + if check_if_installed('python3.11'): # python3.11 is currently the default interpreter on sio. + return 'python3.11' + for ver in ['3.9', '3.8', '3.7', '3']: + if check_if_installed('python' + ver): + return 'python' + ver + return None def get_java_compiler_path(): diff --git a/src/sinol_make/helpers/package_util.py b/src/sinol_make/helpers/package_util.py index fedd8f49..bf634a73 100644 --- a/src/sinol_make/helpers/package_util.py +++ b/src/sinol_make/helpers/package_util.py @@ -1,6 +1,8 @@ import os +import re import yaml import glob +import fnmatch from enum import Enum from typing import List, Union, Dict, Any @@ -22,38 +24,40 @@ def get_task_id() -> str: util.exit_with_error("Invalid task id. Task id should be 3 characters long.") -def extract_test_id(test_path): +def extract_test_id(test_path, task_id): """ Extracts test group and number from test path. For example for test abc1a.in it returns 1a. :param test_path: Path to test file. + :param task_id: Task id. :return: Test group and number. """ - return os.path.split(os.path.splitext(test_path)[0])[1][3:] + return os.path.split(os.path.splitext(test_path)[0])[1][len(task_id):] -def get_group(test_path): - if extract_test_id(test_path).endswith("ocen"): +def get_group(test_path, task_id): + if extract_test_id(test_path, task_id).endswith("ocen"): return 0 - return int("".join(filter(str.isdigit, extract_test_id(test_path)))) + return int("".join(filter(str.isdigit, extract_test_id(test_path, task_id)))) -def get_test_key(test): - return get_group(test), test +def get_test_key(test, task_id): + return get_group(test, task_id), test -def get_tests(arg_tests: Union[List[str], None] = None) -> List[str]: +def get_tests(task_id: str, arg_tests: Union[List[str], None] = None) -> List[str]: """ Returns list of tests to run. + :param task_id: Task id. :param arg_tests: Tests specified in command line arguments. If None, all tests are returned. :return: List of tests to run. """ if arg_tests is None: all_tests = ["in/%s" % test for test in os.listdir("in/") if test[-3:] == ".in"] - return sorted(all_tests, key=get_test_key) + return sorted(all_tests, key=lambda test: get_test_key(test, task_id)) else: - return sorted(list(set(arg_tests)), key=get_test_key) + return sorted(list(set(arg_tests)), key=lambda test: get_test_key(test, task_id)) def get_file_name(file_path): @@ -109,9 +113,9 @@ def _get_limit_from_dict(dict: Dict[str, Any], limit_type: LimitTypes, test_id: return None -def _get_limit(limit_type: LimitTypes, test_path: str, config: Dict[str, Any], lang: str): - test_id = extract_test_id(test_path) - test_group = str(get_group(test_path)) +def _get_limit(limit_type: LimitTypes, test_path: str, config: Dict[str, Any], lang: str, task_id: str): + test_id = extract_test_id(test_path, task_id) + test_group = str(get_group(test_path, task_id)) allow_test_limit = config.get("sinol_undocumented_test_limits", False) global_limit = _get_limit_from_dict(config, limit_type, test_id, test_group, test_path, allow_test_limit) override_limits_dict = config.get("override_limits", {}).get(lang, {}) @@ -129,7 +133,7 @@ def _get_limit(limit_type: LimitTypes, test_path: str, config: Dict[str, Any], l util.exit_with_error(f'Memory limit was not defined for test {os.path.basename(test_path)} in config.yml.') -def get_time_limit(test_path, config, lang, args=None): +def get_time_limit(test_path, config, lang, task_id, args=None): """ Returns time limit for given test. """ @@ -137,10 +141,10 @@ def get_time_limit(test_path, config, lang, args=None): return args.tl * 1000 str_config = util.stringify_keys(config) - return _get_limit(LimitTypes.TIME_LIMIT, test_path, str_config, lang) + return _get_limit(LimitTypes.TIME_LIMIT, test_path, str_config, lang, task_id) -def get_memory_limit(test_path, config, lang, args=None): +def get_memory_limit(test_path, config, lang, task_id, args=None): """ Returns memory limit for given test. """ @@ -148,4 +152,59 @@ def get_memory_limit(test_path, config, lang, args=None): return int(args.ml * 1024) str_config = util.stringify_keys(config) - return _get_limit(LimitTypes.MEMORY_LIMIT, test_path, str_config, lang) + return _get_limit(LimitTypes.MEMORY_LIMIT, test_path, str_config, lang, task_id) + + +def validate_test_names(task_id): + """ + Checks if all files in the package have valid names. + """ + def get_invalid_files(path, pattern): + invalid_files = [] + for file in glob.glob(os.path.join(os.getcwd(), path)): + if not pattern.match(os.path.basename(file)): + invalid_files.append(os.path.basename(file)) + return invalid_files + + in_test_re = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).in$' % (re.escape(task_id))) + invalid_in_tests = get_invalid_files(os.path.join("in", "*.in"), in_test_re) + if len(invalid_in_tests) > 0: + util.exit_with_error(f'Input tests with invalid names: {", ".join(invalid_in_tests)}.') + + out_test_re = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).out$' % (re.escape(task_id))) + invalid_out_tests = get_invalid_files(os.path.join("out", "*.out"), out_test_re) + if len(invalid_out_tests) > 0: + util.exit_with_error(f'Output tests with invalid names: {", ".join(invalid_out_tests)}.') + + +def get_all_code_files(task_id: str) -> List[str]: + """ + Returns all code files in package. + :param task_id: Task id. + :return: List of code files. + """ + result = glob.glob(os.path.join(os.getcwd(), "prog", f"{task_id}ingen.sh")) + for ext in ["c", "cpp", "py", "java"]: + result += glob.glob(os.path.join(os.getcwd(), f"prog/{task_id}*.{ext}")) + return result + + +def get_files_matching_pattern(task_id: str, pattern: str) -> List[str]: + """ + Returns all files in package matching given pattern. + :param task_id: Task id. + :param pattern: Pattern to match. + :return: List of files matching the pattern. + """ + all_files = get_all_code_files(task_id) + return [file for file in all_files if fnmatch.fnmatch(os.path.basename(file), pattern)] + + +def any_files_matching_pattern(task_id: str, pattern: str) -> bool: + """ + Returns True if any file in package matches given pattern. + :param task_id: Task id. + :param pattern: Pattern to match. + :return: True if any file in package matches given pattern. + """ + return len(get_files_matching_pattern(task_id, pattern)) > 0 diff --git a/tests/commands/doc/test_integration.py b/tests/commands/doc/test_integration.py new file mode 100644 index 00000000..5ef28e16 --- /dev/null +++ b/tests/commands/doc/test_integration.py @@ -0,0 +1,32 @@ +import pytest + +from sinol_make import configure_parsers +from sinol_make.commands.doc import Command +from tests.fixtures import create_package +from tests import util + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_simple(capsys, create_package): + """ + Test `doc` command with no parameters. + """ + parser = configure_parsers() + args = parser.parse_args(["doc"]) + command = Command() + command.run(args) + out = capsys.readouterr().out + assert "Compilation was successful for all files." in out + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_argument(capsys, create_package): + """ + Test `doc` command with specified file. + """ + parser = configure_parsers() + args = parser.parse_args(["doc", "doc/doczad.tex"]) + command = Command() + command.run(args) + out = capsys.readouterr().out + assert "Compilation was successful for all files." in out diff --git a/tests/commands/doc/test_unit.py b/tests/commands/doc/test_unit.py new file mode 100644 index 00000000..d4f4545c --- /dev/null +++ b/tests/commands/doc/test_unit.py @@ -0,0 +1,12 @@ +import os +import pytest + +from sinol_make.commands.doc import Command +from tests.fixtures import create_package +from tests import util + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_compile_file(create_package): + command = Command() + assert command.compile_file(os.path.abspath(os.path.join(os.getcwd(), "doc/doczad.tex"))) is True diff --git a/tests/commands/export/test_integration.py b/tests/commands/export/test_integration.py index 851d52c6..f7fe1dea 100644 --- a/tests/commands/export/test_integration.py +++ b/tests/commands/export/test_integration.py @@ -5,6 +5,7 @@ import tempfile from sinol_make import configure_parsers +from sinol_make.commands.doc import Command as DocCommand from tests import util from tests.fixtures import create_package from .util import * @@ -56,3 +57,26 @@ def test_simple(create_package, capsys): task_id = package_util.get_task_id() out = capsys.readouterr().out _test_archive(package_path, out, f'{task_id}.tgz') + + +@pytest.mark.parametrize("create_package", [util.get_doc_package_path()], indirect=True) +def test_doc_cleared(create_package): + """ + Test if files in `doc` directory are cleared. + """ + parser = configure_parsers() + args = parser.parse_args(["doc"]) + command = DocCommand() + command.run(args) + args = parser.parse_args(["export"]) + command = Command() + command.run(args) + + with tempfile.TemporaryDirectory() as tmpdir: + with tarfile.open(f'{package_util.get_task_id()}.tgz', "r") as tar: + tar.extractall(tmpdir) + + extracted = os.path.join(tmpdir, package_util.get_task_id()) + assert os.path.exists(extracted) + for pattern in ['doc/*~', 'doc/*.aux', 'doc/*.log', 'doc/*.dvi', 'doc/*.err', 'doc/*.inf']: + assert glob.glob(os.path.join(extracted, pattern)) == [] diff --git a/tests/commands/export/util.py b/tests/commands/export/util.py index ac103636..9bf2f8bd 100644 --- a/tests/commands/export/util.py +++ b/tests/commands/export/util.py @@ -53,8 +53,8 @@ def _get_value_from_key(key, seperator): assert _get_value_from_key("SLOW_TIMELIMIT", "=") == str(4 * config["time_limit"]) assert _get_value_from_key("MEMLIMIT", "=") == str(config["memory_limit"]) - cxx_flags = '-std=c++17' - c_flags = '-std=c17' + cxx_flags = '-std=c++20' + c_flags = '-std=gnu99' def format_multiple_arguments(obj): if isinstance(obj, str): return obj diff --git a/tests/commands/gen/test_unit.py b/tests/commands/gen/test_unit.py index e82eea59..fe823cfd 100644 --- a/tests/commands/gen/test_unit.py +++ b/tests/commands/gen/test_unit.py @@ -20,9 +20,6 @@ def test_get_ingen(): shutil.copytree(simple_package_path, os.path.join(tmpdir, 'simple')) os.chdir(os.path.join(tmpdir, 'simple')) - ingen_path = gen_util.get_ingen() - assert os.path.basename(ingen_path) == "abcingen.cpp" - ingen_path = gen_util.get_ingen("abc") assert os.path.basename(ingen_path) == "abcingen.cpp" @@ -37,9 +34,6 @@ def test_get_ingen(): shutil.copytree(gen_package_path, os.path.join(tmpdir, 'gen')) os.chdir(os.path.join(tmpdir, 'gen')) - ingen_path = gen_util.get_ingen() - assert os.path.basename(ingen_path) == "geningen.sh" - ingen_path = gen_util.get_ingen("gen") assert os.path.basename(ingen_path) == "geningen.sh" diff --git a/tests/commands/inwer/test_integration.py b/tests/commands/inwer/test_integration.py index 767e2d4f..9fadfb6d 100644 --- a/tests/commands/inwer/test_integration.py +++ b/tests/commands/inwer/test_integration.py @@ -2,6 +2,7 @@ from sinol_make import configure_parsers from sinol_make.commands.inwer import Command +from sinol_make.helpers import package_util from tests import util from tests.fixtures import * @@ -12,7 +13,8 @@ def test_default(capsys, create_package): Test `inwer` command with no parameters. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) parser = configure_parsers() args = parser.parse_args(["inwer"]) command = Command() @@ -33,7 +35,8 @@ def test_specified_inwer(capsys, create_package): Test `inwer` command with specified inwer. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) parser = configure_parsers() args = parser.parse_args(["inwer", "prog/werinwer.cpp"]) command = Command() @@ -66,7 +69,8 @@ def test_asserting_inwer(capsys, create_package): Test `inwer` command with inwer that uses assert for verifying. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) parser = configure_parsers() args = parser.parse_args(["inwer", "prog/werinwer3.cpp"]) command = Command() @@ -87,7 +91,8 @@ def test_flag_tests(capsys, create_package): Test `inwer` command with --tests flag. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) parser = configure_parsers() args = parser.parse_args(["inwer", "prog/werinwer.cpp", "--tests", "in/wer2a.in"]) command = Command() @@ -125,7 +130,8 @@ def test_no_output(capsys, create_package): Test `inwer` command when inwer doesn't print anything. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) parser = configure_parsers() args = parser.parse_args(["inwer", "prog/werinwer4.cpp"]) command = Command() diff --git a/tests/commands/inwer/test_unit.py b/tests/commands/inwer/test_unit.py index 83c738a9..744fc404 100644 --- a/tests/commands/inwer/test_unit.py +++ b/tests/commands/inwer/test_unit.py @@ -36,7 +36,8 @@ def test_asserting_inwer(create_package): Test asserting inwer. """ package_path = create_package - util.create_ins(package_path) + task_id = package_util.get_task_id() + util.create_ins(package_path, task_id) inwer_path = os.path.join(os.getcwd(), 'prog', 'werinwer3.cpp') args = argparse.Namespace( c_compiler_path=compiler.get_c_compiler_path(), diff --git a/tests/commands/run/test_integration.py b/tests/commands/run/test_integration.py index fcf0c1e1..0b846108 100644 --- a/tests/commands/run/test_integration.py +++ b/tests/commands/run/test_integration.py @@ -60,7 +60,7 @@ def test_no_expected_scores(capsys, create_package, time_tool): out = capsys.readouterr().out assert "Solutions were added:" in out - solution = glob.glob(os.path.join(package_path, "prog", "???.*"))[0] + solution = package_util.get_files_matching_pattern(command.ID, f"{command.ID}.*")[0] assert os.path.basename(solution) in out @@ -220,7 +220,8 @@ def test_flag_solutions(capsys, create_package, time_tool): package_path = create_package create_ins_outs(package_path) - solutions = glob.glob(os.path.join(package_path, "prog", "????.*")) + task_id = package_util.get_task_id() + solutions = package_util.get_files_matching_pattern(task_id, f'{task_id}?.*') parser = configure_parsers() args = parser.parse_args(["run", "--solutions", solutions[0], "--time-tool", time_tool]) command = Command() diff --git a/tests/commands/run/test_unit.py b/tests/commands/run/test_unit.py index 5fd808ae..989c2883 100644 --- a/tests/commands/run/test_unit.py +++ b/tests/commands/run/test_unit.py @@ -51,7 +51,7 @@ def test_execution(create_package, time_tool): assert result == [True] create_ins_outs(package_path) - test = package_util.get_tests(None)[0] + test = package_util.get_tests("abc", None)[0] with open(os.path.join(package_path, "config.yml"), "r") as config_file: config = yaml.load(config_file, Loader=yaml.FullLoader) @@ -79,7 +79,7 @@ def test_run_solutions(create_package, time_tool): command.args = argparse.Namespace(solutions_report=False, time_tool=time_tool, weak_compilation_flags=False, hide_memory=False) create_ins_outs(package_path) - command.tests = package_util.get_tests(None) + command.tests = package_util.get_tests("abc", None) command.groups = list(sorted(set([command.get_group(test) for test in command.tests]))) command.scores = command.config["scores"] command.possible_score = command.get_possible_score(command.groups) @@ -133,7 +133,7 @@ def test_validate_expected_scores_success(): os.chdir(get_simple_package_path()) command = get_command() command.scores = command.config["scores"] - command.tests = package_util.get_tests(None) + command.tests = package_util.get_tests("abc", None) # Test with correct expected scores. command.args = argparse.Namespace(solutions=["prog/abc.cpp"], tests=None) @@ -440,7 +440,7 @@ def test_get_valid_input_files(create_package): package_path = create_package command = get_command(package_path) create_ins_outs(package_path) - command.tests = package_util.get_tests(None) + command.tests = package_util.get_tests(command.ID, None) outputs = glob.glob(os.path.join(package_path, "out", "*.out")) os.unlink(outputs[0]) diff --git a/tests/helpers/test_package_util.py b/tests/helpers/test_package_util.py index d71efb6a..de5f294a 100644 --- a/tests/helpers/test_package_util.py +++ b/tests/helpers/test_package_util.py @@ -17,20 +17,23 @@ def test_get_task_id(create_package): def test_extract_test_id(): - assert package_util.extract_test_id("in/abc1a.in") == "1a" - assert package_util.extract_test_id("in/abc10a.in") == "10a" - assert package_util.extract_test_id("in/abc12ca.in") == "12ca" - assert package_util.extract_test_id("in/abc0ocen.in") == "0ocen" + assert package_util.extract_test_id("in/abc1a.in", "abc") == "1a" + assert package_util.extract_test_id("in/abc10a.in", "abc") == "10a" + assert package_util.extract_test_id("in/abc12ca.in", "abc") == "12ca" + assert package_util.extract_test_id("in/abc0ocen.in", "abc") == "0ocen" + assert package_util.extract_test_id("in/long_task_id2bc.in", "long_task_id") == "2bc" def test_get_group(): - assert package_util.get_group("in/abc1a.in") == 1 + assert package_util.get_group("in/abc1a.in", "abc") == 1 + assert package_util.get_group("in/long_name2ocen.in", "long_name") == 0 def test_get_tests(create_package): - create_ins(create_package) os.chdir(create_package) - tests = package_util.get_tests(None) + task_id = package_util.get_task_id() + create_ins(create_package, task_id) + tests = package_util.get_tests("abc", None) assert tests == ["in/abc1a.in", "in/abc2a.in", "in/abc3a.in", "in/abc4a.in"] @@ -60,17 +63,17 @@ def test_get_time_limit(): } } - assert package_util.get_time_limit("in/abc1a.in", config, "cpp") == 1000 - assert package_util.get_time_limit("in/abc2a.in", config, "cpp") == 2000 - assert package_util.get_time_limit("in/abc2b.in", config, "cpp") == 2000 - assert package_util.get_time_limit("in/abc3a.in", config, "cpp") == 1000 - assert package_util.get_time_limit("in/abc3ocen.in", config, "cpp") == 5000 + assert package_util.get_time_limit("in/abc1a.in", config, "cpp", "abc") == 1000 + assert package_util.get_time_limit("in/abc2a.in", config, "cpp", "abc") == 2000 + assert package_util.get_time_limit("in/abc2b.in", config, "cpp", "abc") == 2000 + assert package_util.get_time_limit("in/abc3a.in", config, "cpp", "abc") == 1000 + assert package_util.get_time_limit("in/abc3ocen.in", config, "cpp", "abc") == 5000 - assert package_util.get_time_limit("in/abc1a.in", config, "py") == 2000 - assert package_util.get_time_limit("in/abc2a.in", config, "py") == 3000 - assert package_util.get_time_limit("in/abc2b.in", config, "py") == 3000 - assert package_util.get_time_limit("in/abc3a.in", config, "py") == 2000 - assert package_util.get_time_limit("in/abc3ocen.in", config, "py") == 6000 + assert package_util.get_time_limit("in/abc1a.in", config, "py", "abc") == 2000 + assert package_util.get_time_limit("in/abc2a.in", config, "py", "abc") == 3000 + assert package_util.get_time_limit("in/abc2b.in", config, "py", "abc") == 3000 + assert package_util.get_time_limit("in/abc3a.in", config, "py", "abc") == 2000 + assert package_util.get_time_limit("in/abc3ocen.in", config, "py", "abc") == 6000 # Test getting default time limit. config = { @@ -85,12 +88,12 @@ def test_get_time_limit(): } } } - assert package_util.get_time_limit("in/abc1a.in", config, "cpp") == 1000 - assert package_util.get_time_limit("in/abc1a.in", config, "py") == 2000 + assert package_util.get_time_limit("in/abc1a.in", config, "cpp", "abc") == 1000 + assert package_util.get_time_limit("in/abc1a.in", config, "py", "abc") == 2000 with pytest.raises(SystemExit): - package_util.get_time_limit("in/abc2a.in", config, "cpp") + package_util.get_time_limit("in/abc2a.in", config, "cpp", "abc") with pytest.raises(SystemExit): - package_util.get_time_limit("in/abc2a.in", config, "py") + package_util.get_time_limit("in/abc2a.in", config, "py", "abc") config = { "time_limits": { @@ -105,11 +108,11 @@ def test_get_time_limit(): } } } - assert package_util.get_time_limit("in/abc1a.in", config, "cpp") == 1000 + assert package_util.get_time_limit("in/abc1a.in", config, "cpp", "abc") == 1000 with pytest.raises(SystemExit): - package_util.get_time_limit("in/abc2a.in", config, "cpp") - assert package_util.get_time_limit("in/abc1a.in", config, "py") == 1000 - assert package_util.get_time_limit("in/abc2a.in", config, "py") == 500 + package_util.get_time_limit("in/abc2a.in", config, "cpp", "abc") + assert package_util.get_time_limit("in/abc1a.in", config, "py", "abc") == 1000 + assert package_util.get_time_limit("in/abc2a.in", config, "py", "abc") == 500 def test_get_memory_limit(): @@ -130,15 +133,15 @@ def test_get_memory_limit(): } } - assert package_util.get_memory_limit("in/abc1a.in", config, "cpp") == 256 - assert package_util.get_memory_limit("in/abc2a.in", config, "cpp") == 512 - assert package_util.get_memory_limit("in/abc2b.in", config, "cpp") == 512 - assert package_util.get_memory_limit("in/abc3ocen.in", config, "cpp") == 128 + assert package_util.get_memory_limit("in/abc1a.in", config, "cpp", "abc") == 256 + assert package_util.get_memory_limit("in/abc2a.in", config, "cpp", "abc") == 512 + assert package_util.get_memory_limit("in/abc2b.in", config, "cpp", "abc") == 512 + assert package_util.get_memory_limit("in/abc3ocen.in", config, "cpp", "abc") == 128 - assert package_util.get_memory_limit("in/abc1a.in", config, "py") == 512 - assert package_util.get_memory_limit("in/abc2a.in", config, "py") == 1024 - assert package_util.get_memory_limit("in/abc2b.in", config, "py") == 1024 - assert package_util.get_memory_limit("in/abc3ocen.in", config, "py") == 256 + assert package_util.get_memory_limit("in/abc1a.in", config, "py", "abc") == 512 + assert package_util.get_memory_limit("in/abc2a.in", config, "py", "abc") == 1024 + assert package_util.get_memory_limit("in/abc2b.in", config, "py", "abc") == 1024 + assert package_util.get_memory_limit("in/abc3ocen.in", config, "py", "abc") == 256 # Test getting default memory limit. config = { @@ -153,12 +156,12 @@ def test_get_memory_limit(): } } } - assert package_util.get_memory_limit("in/abc1a.in", config, "cpp") == 1024 - assert package_util.get_memory_limit("in/abc1a.in", config, "py") == 2048 + assert package_util.get_memory_limit("in/abc1a.in", config, "cpp", "abc") == 1024 + assert package_util.get_memory_limit("in/abc1a.in", config, "py", "abc") == 2048 with pytest.raises(SystemExit): - package_util.get_memory_limit("in/abc2a.in", config, "cpp") + package_util.get_memory_limit("in/abc2a.in", config, "cpp", "abc") with pytest.raises(SystemExit): - package_util.get_memory_limit("in/abc2a.in", config, "py") + package_util.get_memory_limit("in/abc2a.in", config, "py", "abc") config = { "memory_limits": { @@ -173,8 +176,30 @@ def test_get_memory_limit(): } } } - assert package_util.get_memory_limit("in/abc1a.in", config, "cpp") == 1024 + assert package_util.get_memory_limit("in/abc1a.in", config, "cpp", "abc") == 1024 with pytest.raises(SystemExit): - package_util.get_memory_limit("in/abc2a.in", config, "cpp") - assert package_util.get_memory_limit("in/abc1a.in", config, "py") == 1024 - assert package_util.get_memory_limit("in/abc2a.in", config, "py") == 512 + package_util.get_memory_limit("in/abc2a.in", config, "cpp", "abc") + assert package_util.get_memory_limit("in/abc1a.in", config, "py", "abc") == 1024 + assert package_util.get_memory_limit("in/abc2a.in", config, "py", "abc") == 512 + + +@pytest.mark.parametrize("create_package", [util.get_simple_package_path()], indirect=True) +def test_validate_files(create_package, capsys): + package_path = create_package + util.create_ins_outs(package_path) + task_id = package_util.get_task_id() + assert task_id == "abc" + package_util.validate_test_names(task_id) + + os.rename(os.path.join(package_path, "in", "abc1a.in"), os.path.join(package_path, "in", "def1a.in")) + with pytest.raises(SystemExit): + package_util.validate_test_names(task_id) + out = capsys.readouterr().out + assert "def1a.in" in out + + os.rename(os.path.join(package_path, "in", "def1a.in"), os.path.join(package_path, "in", "abc1a.in")) + os.rename(os.path.join(package_path, "out", "abc1a.out"), os.path.join(package_path, "out", "def1a.out")) + with pytest.raises(SystemExit): + package_util.validate_test_names(task_id) + out = capsys.readouterr().out + assert "def1a.out" in out diff --git a/tests/packages/doc/config.yml b/tests/packages/doc/config.yml new file mode 100644 index 00000000..0de939ee --- /dev/null +++ b/tests/packages/doc/config.yml @@ -0,0 +1,3 @@ +title: Package for testing `doc` command +time_limit: 1000 +memory_limit: 1024 diff --git a/tests/packages/doc/doc/doczad.tex b/tests/packages/doc/doc/doczad.tex new file mode 100644 index 00000000..4372318a --- /dev/null +++ b/tests/packages/doc/doc/doczad.tex @@ -0,0 +1,4 @@ +\documentclass{article} +\begin{document} + Hello World! +\end{document} diff --git a/tests/packages/doc/in/.gitkeep b/tests/packages/doc/in/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/packages/doc/out/.gitkeep b/tests/packages/doc/out/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/util.py b/tests/util.py index 5aa837cf..74ecf9d1 100644 --- a/tests/util.py +++ b/tests/util.py @@ -2,7 +2,7 @@ import glob import subprocess -from sinol_make.helpers import compile, paths +from sinol_make.helpers import compile, paths, package_util def get_simple_package_path(): @@ -88,6 +88,13 @@ def get_override_limits_package_path(): return os.path.join(os.path.dirname(__file__), "packages", "ovl") +def get_doc_package_path(): + """ + Get path to package for testing `doc` command (/test/packages/doc) + """ + return os.path.join(os.path.dirname(__file__), "packages", "doc") + + def get_long_name_package_path(): """ Get path to package with long name (/test/packages/long_package_name) @@ -102,11 +109,11 @@ def get_undocumented_options_package_path(): return os.path.join(os.path.dirname(__file__), "packages", "undocumented_options") -def create_ins(package_path): +def create_ins(package_path, task_id): """ Create .in files for package. """ - ingen = glob.glob(os.path.join(package_path, "prog", "*ingen.*"))[0] + ingen = package_util.get_files_matching_pattern(task_id, f'{task_id}ingen.*')[0] ingen_executable = paths.get_executables_path("ingen.e") os.makedirs(paths.get_executables_path(), exist_ok=True) assert compile.compile(ingen, ingen_executable) @@ -115,11 +122,11 @@ def create_ins(package_path): os.chdir(package_path) -def create_outs(package_path): +def create_outs(package_path, task_id): """ Create .out files for package. """ - solution = glob.glob(os.path.join(package_path, "prog", "???.*"))[0] + solution = package_util.get_files_matching_pattern(task_id, f'{task_id}.*')[0] solution_executable = paths.get_executables_path("solution.e") os.makedirs(paths.get_executables_path(), exist_ok=True) assert compile.compile(solution, solution_executable) @@ -135,7 +142,9 @@ def create_ins_outs(package_path): """ Create .in and .out files for package. """ - create_ins(package_path) - has_lib = len(glob.glob(os.path.join(package_path, "prog", "???lib.*"))) > 0 + os.chdir(package_path) + task_id = package_util.get_task_id() + create_ins(package_path, task_id) + has_lib = package_util.any_files_matching_pattern(task_id, f"{task_id}lib.*") if not has_lib: - create_outs(package_path) + create_outs(package_path, task_id)