From c98a9e2d557298167beb99c7bad0e700946db6f1 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 16:33:22 +0300 Subject: [PATCH 1/6] build and run options refactoring --- kks/binary.py | 45 ++++++++++++++++++++---------- kks/cmd/build.py | 7 +++-- kks/cmd/gen.py | 7 +++-- kks/cmd/init.py | 2 +- kks/cmd/run.py | 26 +++++++++++++---- kks/cmd/test.py | 32 +++++++++++++++------ kks/util/script.py | 6 ++-- kks/util/{config.py => targets.py} | 2 +- kks/util/testing.py | 18 ++---------- 9 files changed, 88 insertions(+), 57 deletions(-) rename kks/util/{config.py => targets.py} (98%) diff --git a/kks/binary.py b/kks/binary.py index b424500d..def6ba86 100644 --- a/kks/binary.py +++ b/kks/binary.py @@ -5,8 +5,6 @@ import click -from kks.util.config import find_target - GPP_ARGS = [ 'g++', @@ -35,17 +33,35 @@ ] -def compile_solution(directory, target_name, verbose, options): - target = find_target(target_name) - if target is None: - click.secho(f'No target {target_name} found', fg='red', err=True) - return None +class BuildOptions: + def __init__(self, asan=False, verbose=False): + self.asan = asan + self.verbose = verbose + + +class RunOptions: + def __init__(self, asan=False, valgrind=False): + self.asan = asan + self.valgrind = valgrind + + +class TestRunOptions(RunOptions): + def __init__(self, + continue_on_error=False, + ignore_exit_code=False, + asan=False, + valgrind=False, + is_sample=False): + super().__init__(asan=asan, valgrind=valgrind) + self.continue_on_error = continue_on_error + self.ignore_exit_code = ignore_exit_code + self.is_sample = is_sample + - if verbose: +def compile_solution(directory, target, options): + if options.verbose: click.secho(f'Selected target: {target}') - if options.asan is None: - options.asan = target.default_asan # will be used in run_solution # gcc (clang) can compile c and asm files together, so everything should be ok source_files = list(chain(*[directory.glob(f) for f in target.files])) @@ -55,7 +71,7 @@ def compile_solution(directory, target_name, verbose, options): click.secho('Compiling... ', fg='green', err=True, nl=False) - binary = compile_c(directory, source_files, target, verbose, options) + binary = compile_c_or_asm(directory, source_files, target, options) if binary is None: click.secho('Compilation failed!', fg='red', err=True) @@ -67,7 +83,7 @@ def compile_solution(directory, target_name, verbose, options): return binary -def compile_c(workdir, files, target, verbose, options): +def compile_c_or_asm(workdir, files, target, options): compiler_args = [target.compiler] + target.flags if not target.asm64bit and any(f.suffix.lower() == '.s' for f in files): compiler_args.append('-m32') @@ -78,7 +94,6 @@ def compile_c(workdir, files, target, verbose, options): compiler_args, linker_args=[f'-l{lib}' for lib in target.libs], out_file=target.out, - verbose=verbose ) @@ -86,7 +101,7 @@ def compile_cpp(workdir, files, options): return compile_gnu(workdir, files, options, GPP_ARGS) -def compile_gnu(workdir, files, options, compiler_args, linker_args=[], out_file='', verbose=False): +def compile_gnu(workdir, files, options, compiler_args, linker_args=[], out_file=''): filenames = [path.absolute() for path in files] command = compiler_args @@ -97,7 +112,7 @@ def compile_gnu(workdir, files, options, compiler_args, linker_args=[], out_file command += filenames command += linker_args - if verbose: + if options.verbose: click.secho('\nExecuting "{}"'.format(' '.join(map(str, command)))) p = subprocess.run(command, cwd=workdir) diff --git a/kks/cmd/build.py b/kks/cmd/build.py index 774369be..b39f152d 100644 --- a/kks/cmd/build.py +++ b/kks/cmd/build.py @@ -2,7 +2,7 @@ from kks.binary import compile_solution from kks.util.common import get_solution_directory -from kks.util.testing import RunOptions +from kks.binary import BuildOptions @click.command(short_help='Build solution') @@ -15,8 +15,9 @@ def build(target, verbose, asan): directory = get_solution_directory() - options = RunOptions( + options = BuildOptions( asan=asan, + verbose=verbose ) - compile_solution(directory, target, verbose, options) + compile_solution(directory, target, options) diff --git a/kks/cmd/gen.py b/kks/cmd/gen.py index 2d1bdf12..54e94f86 100644 --- a/kks/cmd/gen.py +++ b/kks/cmd/gen.py @@ -1,7 +1,8 @@ import click from tqdm import tqdm -from kks.util.testing import TestSource, RunOptions +from kks.binary import TestRunOptions +from kks.util.testing import TestSource from kks.util.common import get_solution_directory, test_number_to_name, find_test_pairs, get_matching_suffix, \ format_file from kks.util.script import find_script @@ -37,14 +38,14 @@ def gen(output_only, generator, solution, tests, test_range, force, ignore_exit_ directory = get_solution_directory() - options = RunOptions( + options = TestRunOptions( ignore_exit_code=ignore_exit_code ) generator = find_script(directory, 'gen', default=generator, exists=not output_only) solution = find_script(directory, 'solve', default=solution) - with TestSource(generator, solution, options) as test_source: + with TestSource(generator, solution, options) as test_source: # TODO remove options? test_pairs = find_tests_to_gen(directory, tests, test_range) test_pairs = sorted(test_pairs) diff --git a/kks/cmd/init.py b/kks/cmd/init.py index 73535595..a1658587 100644 --- a/kks/cmd/init.py +++ b/kks/cmd/init.py @@ -4,7 +4,7 @@ from kks.util.click import OptFlagCommand, FlagOption, OptFlagOption, Choice2 from kks.util.common import find_workspace, get_hidden_dir, format_file -from kks.util.config import target_file, global_comment +from kks.util.targets import target_file, global_comment @click.command(cls=OptFlagCommand) diff --git a/kks/cmd/run.py b/kks/cmd/run.py index 71fc51c7..db35574a 100644 --- a/kks/cmd/run.py +++ b/kks/cmd/run.py @@ -3,9 +3,10 @@ import click -from kks.binary import compile_solution, run_solution -from kks.util.testing import RunOptions, Test +from kks.binary import BuildOptions, RunOptions, compile_solution, run_solution from kks.util.common import get_solution_directory, find_test_pairs, format_file, test_number_to_name +from kks.util.targets import find_target +from kks.util.testing import Test @click.command(short_help='Run solution') @@ -39,12 +40,25 @@ def run(asan, valgrind, sample, test, file, target, verbose, run_args): directory = get_solution_directory() - options = RunOptions( - asan=asan and not valgrind, + target = find_target(target) + if target is None: + return + + if valgrind: + asan = False + elif asan is None: + asan = target.default_asan + + build_options = BuildOptions( + asan=asan, + verbose=verbose, + ) + run_options = RunOptions( + asan=asan, valgrind=valgrind, ) - binary = compile_solution(directory, target, verbose, options) + binary = compile_solution(directory, target, build_options) if binary is None: return @@ -56,7 +70,7 @@ def run(asan, valgrind, sample, test, file, target, verbose, run_args): output = f'Running binary with arguments ' + click.style(' '.join(run_args), fg='red', bold=True) click.secho(output, fg='green', err=True) - exit(run_solution(binary, list(run_args), options, test_data, capture_output=False).returncode) + exit(run_solution(binary, list(run_args), run_options, test_data, capture_output=False).returncode) def find_test_to_run(directory, test, file, sample): diff --git a/kks/cmd/test.py b/kks/cmd/test.py index 75633e19..acf8a49b 100644 --- a/kks/cmd/test.py +++ b/kks/cmd/test.py @@ -3,11 +3,12 @@ import click from tqdm import tqdm -from kks.binary import compile_solution, run_solution -from kks.util.script import find_script -from kks.util.testing import TestSource, VirtualTestSequence, RunOptions, Test +from kks.binary import BuildOptions, TestRunOptions, compile_solution, run_solution from kks.util.common import get_solution_directory, format_file, find_test_output, test_number_to_name, \ find_test_pairs, print_diff +from kks.util.script import find_script +from kks.util.targets import find_target +from kks.util.testing import TestSource, VirtualTestSequence, Test @click.command(name='test', short_help='Test solutions') @@ -52,15 +53,28 @@ def test_(target, verbose, tests, test_range, files, sample, directory = get_solution_directory() - options = RunOptions( + target = find_target(target) + if target is None: + return + + if valgrind: + asan = False + elif asan is None: + asan = target.default_asan + + build_options = BuildOptions( + asan=asan, + verbose=verbose, + ) + run_options = TestRunOptions( continue_on_error=continue_on_error, ignore_exit_code=ignore_exit_code, - asan=asan and not valgrind, + asan=asan, valgrind=valgrind, is_sample=sample, ) - binary = compile_solution(directory, target, verbose, options) + binary = compile_solution(directory, target, build_options) if binary is None: return @@ -75,11 +89,11 @@ def test_(target, verbose, tests, test_range, files, sample, click.secho('No tests to run!', fg='red') return - run_tests(binary, tests, options) + run_tests(binary, tests, run_options) else: generator = find_script(directory, 'gen', default=generator) solution = find_script(directory, 'solve', default=solution) - with TestSource(generator, solution, options) as test_source: + with TestSource(generator, solution, run_options) as test_source: # TODO remove options? if test_range: l, r = sorted(test_range) test_range = range(l, r + 1) @@ -91,7 +105,7 @@ def test_(target, verbose, tests, test_range, files, sample, tests = VirtualTestSequence(test_source, all_tests) - run_tests(binary, tests, options) + run_tests(binary, tests, run_options) def find_tests_to_run(directory, files, tests, test_range, sample): diff --git a/kks/util/script.py b/kks/util/script.py index f829e45a..8898b2aa 100644 --- a/kks/util/script.py +++ b/kks/util/script.py @@ -3,7 +3,7 @@ import click -from kks.binary import compile_cpp +from kks.binary import BuildOptions, compile_cpp from kks.util.common import format_file @@ -41,9 +41,9 @@ def needs_compilation(script): return script is not None and script.suffix in CPP_EXTENSIONS -def compile_script(workdir, script, options): +def compile_script(workdir, script): if script.suffix in CPP_EXTENSIONS: - return compile_cpp(workdir, [script], options) + return compile_cpp(workdir, [script], BuildOptions()) else: raise Exception(f'Cant compile script with extension {script.suffix}') diff --git a/kks/util/config.py b/kks/util/targets.py similarity index 98% rename from kks/util/config.py rename to kks/util/targets.py index 4fd2e1e5..f6c47496 100644 --- a/kks/util/config.py +++ b/kks/util/targets.py @@ -138,7 +138,7 @@ def get_target(config, target_name): package_target = get_target(package_cfg, name) if package_target is None: - # not found + click.secho(f'No target {target_name} found', fg='red', err=True) return None package_target.replace_macros_add_missing(problem, package_default) diff --git a/kks/util/testing.py b/kks/util/testing.py index 5e5162d7..e46b6d00 100644 --- a/kks/util/testing.py +++ b/kks/util/testing.py @@ -36,7 +36,7 @@ def __enter__(self): path = Path(self.generator_directory.name) click.secho('Compiling generator... ', fg='green', err=True) - self.generator = compile_script(path, self.generator, self.options) + self.generator = compile_script(path, self.generator) if self.generator is None: click.secho('Compilation failed!', fg='red', err=True) raise click.Abort() @@ -47,7 +47,7 @@ def __enter__(self): path = Path(self.solution_directory.name) click.secho('Compiling solution... ', fg='green', err=True) - self.solution = compile_script(path, self.solution, self.options) + self.solution = compile_script(path, self.solution) if self.solution is None: click.secho('Compilation failed!', fg='red', err=True) raise click.Abort() @@ -129,17 +129,3 @@ def read_output(self): return self.output_data else: return None - - -class RunOptions: - def __init__(self, - continue_on_error=False, - ignore_exit_code=False, - asan=True, - valgrind=False, - is_sample=False): - self.continue_on_error = continue_on_error - self.ignore_exit_code = ignore_exit_code - self.asan = asan - self.valgrind = valgrind - self.is_sample = is_sample From 1208aee639f4c51c7f2afd3d6511ea0de0475421 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 17:06:06 +0300 Subject: [PATCH 2/6] fix script error handling in VirtualTestSequence --- kks/util/testing.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/kks/util/testing.py b/kks/util/testing.py index e46b6d00..85c22e4e 100644 --- a/kks/util/testing.py +++ b/kks/util/testing.py @@ -1,6 +1,7 @@ import subprocess import tempfile from pathlib import Path +from sys import exit import click @@ -70,9 +71,14 @@ def __init__(self, generator, tests): def __iter__(self): for test in self.tests: name = test_number_to_name(test) - input_data = self.test_source.generate_input(name).stdout - output_data = self.test_source.generate_output(name, input=input_data).stdout - yield Test.from_data(name, input_data, output_data) + input_process = self.test_source.generate_input(name) + if input_process is None: + exit(1) + input_data = input_process.stdout + output_process = self.test_source.generate_output(name, input=input_data) + if output_process is None: + exit(1) + yield Test.from_data(name, input_data, output_process.stdout) def __len__(self): return len(self.tests) From daec9e6b704401808db6ddba335d508f7bebf7b6 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 17:12:44 +0300 Subject: [PATCH 3/6] never ignore generator exit code --- kks/cmd/gen.py | 2 +- kks/cmd/test.py | 2 +- kks/util/testing.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/kks/cmd/gen.py b/kks/cmd/gen.py index 54e94f86..4e6d6e2f 100644 --- a/kks/cmd/gen.py +++ b/kks/cmd/gen.py @@ -45,7 +45,7 @@ def gen(output_only, generator, solution, tests, test_range, force, ignore_exit_ generator = find_script(directory, 'gen', default=generator, exists=not output_only) solution = find_script(directory, 'solve', default=solution) - with TestSource(generator, solution, options) as test_source: # TODO remove options? + with TestSource(generator, solution, options) as test_source: test_pairs = find_tests_to_gen(directory, tests, test_range) test_pairs = sorted(test_pairs) diff --git a/kks/cmd/test.py b/kks/cmd/test.py index acf8a49b..373d35ca 100644 --- a/kks/cmd/test.py +++ b/kks/cmd/test.py @@ -93,7 +93,7 @@ def test_(target, verbose, tests, test_range, files, sample, else: generator = find_script(directory, 'gen', default=generator) solution = find_script(directory, 'solve', default=solution) - with TestSource(generator, solution, run_options) as test_source: # TODO remove options? + with TestSource(generator, solution, run_options) as test_source: if test_range: l, r = sorted(test_range) test_range = range(l, r + 1) diff --git a/kks/util/testing.py b/kks/util/testing.py index 85c22e4e..b547a64e 100644 --- a/kks/util/testing.py +++ b/kks/util/testing.py @@ -24,8 +24,7 @@ def __init__(self, generator, solution, options): self.solution_directory = tempfile.TemporaryDirectory(prefix='kks-') def generate_input(self, test, stdout=subprocess.PIPE): - return run_script(self.generator, [test], stdout=stdout, - ignore_exit_code=self.options.ignore_exit_code) + return run_script(self.generator, [test], stdout=stdout) def generate_output(self, test, stdin=None, stdout=subprocess.PIPE, input=None): return run_script(self.solution, [test], stdin=stdin, stdout=stdout, input=input, From c80a59426cf98d0baeafd3b33842a4ffc15bb977 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 17:23:22 +0300 Subject: [PATCH 4/6] fix build --- kks/cmd/build.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kks/cmd/build.py b/kks/cmd/build.py index b39f152d..c4cedf29 100644 --- a/kks/cmd/build.py +++ b/kks/cmd/build.py @@ -1,8 +1,8 @@ import click -from kks.binary import compile_solution +from kks.binary import BuildOptions, compile_solution from kks.util.common import get_solution_directory -from kks.binary import BuildOptions +from kks.util.targets import find_target @click.command(short_help='Build solution') @@ -15,6 +15,13 @@ def build(target, verbose, asan): directory = get_solution_directory() + target = find_target(target) + if target is None: + return + + if asan is None: + asan = target.default_asan + options = BuildOptions( asan=asan, verbose=verbose From 01b57266710a8e8c9200e254587d392c4861a7e4 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 19:17:47 +0300 Subject: [PATCH 5/6] use asan for cpp by default --- README.md | 5 +++-- kks/util/script.py | 6 ++++-- kks/util/storage.py | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9aac9dd7..0c8d0a65 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,12 @@ pip3 install . Опции: - `mdwidth` (по умолчанию `100`) - максимальная ширина текста в условиях при конвертации в Markdown + - `save-html-statements`, `save-md-statements` (по умолчанию оба значения `true`) - выбор формата сохранения условий при синхронизации - `max-kr` - считать максимальные баллы для тестирующихся задач из КР (`kks top --max`). Результаты могут значительно отличаться от реальных баллов. + - `global-opt-out` - отказаться от отправки статистики для глобального рейтинга - `deadline-warning-days` - за сколько дней до дедлайна выделять контест в выводе `kks deadlines` и `kks status --todo` (по умолчанию - 1 день) - `sort-todo-by-deadline` (по умолчанию `True`) - включить сортировку по дедлайнам в `kks status --todo` - - `global-opt-out` - отказаться от отправки статистики для глобального рейтинга - - `save-html-statements`, `save-md-statements` (по умолчанию оба значения `true`) - выбор формата сохранения условий при синхронизации + - `cpp-with-asan` (по умолчанию `True`) - использовать asan при компиляции генератора тестов / эталонного решения на C++ Имена переменных окружения, если они используются, должны быть в upper-case. Например, для переопределения опции `save-html-statements` используется переменная окружения `SAVE_HTML_STATEMENTS` diff --git a/kks/util/script.py b/kks/util/script.py index 8898b2aa..639595a4 100644 --- a/kks/util/script.py +++ b/kks/util/script.py @@ -5,6 +5,7 @@ from kks.binary import BuildOptions, compile_cpp from kks.util.common import format_file +from kks.util.storage import Config # Используются для поиска скриптов по имени @@ -41,9 +42,10 @@ def needs_compilation(script): return script is not None and script.suffix in CPP_EXTENSIONS -def compile_script(workdir, script): +def compile_script(workdir, script, options=None): if script.suffix in CPP_EXTENSIONS: - return compile_cpp(workdir, [script], BuildOptions()) + options = options or BuildOptions(asan=Config().options.cpp_with_asan) + return compile_cpp(workdir, [script], options) else: raise Exception(f'Cant compile script with extension {script.suffix}') diff --git a/kks/util/storage.py b/kks/util/storage.py index 8c0177e2..d5242c0d 100644 --- a/kks/util/storage.py +++ b/kks/util/storage.py @@ -89,9 +89,10 @@ class OptionsSection(EnvSection): save_md_statements: bool = True mdwidth: int = 100 max_kr: bool = False + global_opt_out: bool deadline_warning_days: int = 1 sort_todo_by_deadline: bool = True - global_opt_out: bool + cpp_with_asan: bool = True class ConfigModel: From 0ce658b98d33158c229d58759bbb58f415bb4f16 Mon Sep 17 00:00:00 2001 From: vvd170501 <36827317+vvd170501@users.noreply.github.com> Date: Sun, 4 Apr 2021 19:26:39 +0300 Subject: [PATCH 6/6] fix cpp compilation --- kks/binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kks/binary.py b/kks/binary.py index def6ba86..12be101a 100644 --- a/kks/binary.py +++ b/kks/binary.py @@ -98,7 +98,7 @@ def compile_c_or_asm(workdir, files, target, options): def compile_cpp(workdir, files, options): - return compile_gnu(workdir, files, options, GPP_ARGS) + return compile_gnu(workdir, files, options, GPP_ARGS[:]) def compile_gnu(workdir, files, options, compiler_args, linker_args=[], out_file=''):