Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIO3Pack integration #288

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ install_requires =
importlib-resources
psutil
packaging
sio3pack==1.0.0.dev1

[options.packages.find]
where = src
Expand Down
12 changes: 11 additions & 1 deletion src/sinol_make/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@

from sinol_make import util, sio2jail
from sinol_make.helpers import cache, oicompare
from sinol_make.sio3pack.package import SIO3Package

# Required for side effects
from sinol_make.task_type.normal import NormalTaskType # noqa
from sinol_make.task_type.interactive import InteractiveTaskType # noqa

# SIO3Pack
from sio3pack.exceptions import SIO3PackException

__version__ = "1.9.7"

__version__ = "2.0.0.dev1"


def configure_parsers():
Expand Down Expand Up @@ -93,6 +97,7 @@ def main_exn():
def main():
new_version = None
try:
SIO3Package().from_db(2137)
if util.is_dev(__version__):
print(util.warning('You are using a development version of sinol-make. '
'It may be unstable and contain bugs.'))
Expand All @@ -102,6 +107,11 @@ def main():
util.exit_with_error(err)
except SystemExit as err:
exit(err.code)
except SIO3PackException as err:
print(traceback.format_exc())
util.exit_with_error(f'{err}\n'
'If that is a bug, please report it or submit a bugfix: '
'https://github.com/sio2project/sinol-make/#reporting-bugs-and-contributing-code')
except Exception:
print(traceback.format_exc())
util.exit_with_error('An error occurred while running the command.\n'
Expand Down
2 changes: 1 addition & 1 deletion src/sinol_make/commands/chkwer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def run(self, args):
util.exit_with_error("chkwer can be run only for normal tasks.")

self.cpus = args.cpus or util.default_cpu_count()
self.tests = package_util.get_tests(self.task_id, args.tests)
self.tests = package_util.get_tests(args.tests)

if len(self.tests) == 0:
util.exit_with_error("No tests found.")
Expand Down
7 changes: 4 additions & 3 deletions src/sinol_make/commands/doc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sinol_make import util
from sinol_make.helpers import package_util, paths
from sinol_make.interfaces.BaseCommand import BaseCommand
from sinol_make.sio3pack.package import SIO3Package


class Command(BaseCommand):
Expand Down Expand Up @@ -92,7 +93,7 @@ def run(self, args: argparse.Namespace):
# when it is not provided by the user, instead of using the default
# behavior of defaulting to None.
if not hasattr(args, 'latex_compiler'):
config = package_util.get_config()
config = SIO3Package().get_config()
args.latex_compiler = config.get('sinol_latex_compiler', 'auto')

if args.latex_compiler == 'pdflatex':
Expand All @@ -104,13 +105,13 @@ def run(self, args: argparse.Namespace):
elif args.latex_compiler == 'auto':
self.compilation_method = 'pdflatex'
for extension in ['ps', 'eps']:
if glob.glob(os.path.join(os.getcwd(), 'doc', f'*.{extension}')) != []:
if glob.glob(os.path.join(os.getcwd(), 'doc', f'*.{extension}')): #TODO: SIO3Pack?
self.compilation_method = 'latex_dvi'
else:
util.exit_with_error("Unrecognized latex compiler")

if args.files == []:
self.files = glob.glob(os.path.join(os.getcwd(), 'doc', '*.tex'))
self.files = glob.glob(os.path.join(os.getcwd(), 'doc', '*.tex')) #TODO: SIO3Pack?
else:
self.files = []
for file in args.files:
Expand Down
10 changes: 5 additions & 5 deletions src/sinol_make/commands/export/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import tempfile
import argparse

from sinol_make import util, contest_types
from sinol_make.commands.ingen.ingen_util import get_ingen, compile_ingen, run_ingen, ingen_exists
from sinol_make import util, contest_types, SIO3Package
from sinol_make.commands.ingen.ingen_util import get_ingen_path, compile_ingen, run_ingen, ingen_exists
from sinol_make.helpers import package_util, parsers, paths
from sinol_make.interfaces.BaseCommand import BaseCommand
from sinol_make.commands.outgen import Command as OutgenCommand, compile_correct_solution, get_correct_solution
from sinol_make.commands.outgen import Command as OutgenCommand, compile_correct_solution
from sinol_make.commands.doc import Command as DocCommand
from sinol_make.interfaces.Errors import UnknownContestType

Expand Down Expand Up @@ -54,7 +54,7 @@ def generate_input_tests(self):
shutil.copytree(os.path.join(os.getcwd(), 'prog'), prog_dir)

if ingen_exists(self.task_id):
ingen_path = get_ingen(self.task_id)
ingen_path = get_ingen_path(self.task_id)
ingen_path = os.path.join(prog_dir, os.path.basename(ingen_path))
ingen_exe = compile_ingen(ingen_path, self.args, self.args.compile_mode)
if not run_ingen(ingen_exe, in_dir):
Expand All @@ -75,7 +75,7 @@ def generate_output_files(self):
outputs.append(os.path.join(out_dir, os.path.basename(test).replace('.in', '.out')))
if len(outputs) > 0:
outgen = OutgenCommand()
correct_solution_exe = compile_correct_solution(get_correct_solution(self.task_id), self.args,
correct_solution_exe = compile_correct_solution(SIO3Package().get_correct_solution(), self.args,
self.args.compile_mode)
outgen.args = self.args
outgen.correct_solution_exe = correct_solution_exe
Expand Down
12 changes: 6 additions & 6 deletions src/sinol_make/commands/ingen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os

from sinol_make import util
from sinol_make.commands.ingen.ingen_util import get_ingen, compile_ingen, run_ingen
from sinol_make.commands.ingen.ingen_util import get_ingen_path, compile_ingen, run_ingen
from sinol_make.helpers import parsers, package_util, paths
from sinol_make.interfaces.BaseCommand import BaseCommand

Expand Down Expand Up @@ -65,9 +65,9 @@ def run(self, args: argparse.Namespace):

self.task_id = package_util.get_task_id()
util.change_stack_size_to_unlimited()
self.ingen = get_ingen(self.task_id, args.ingen_path)
print(f'Using ingen file {os.path.basename(self.ingen)}')
self.ingen_exe = compile_ingen(self.ingen, self.args, self.args.compile_mode, self.args.fsanitize)
self.ingen_path = get_ingen_path(args.ingen_path)
print(f'Using ingen file {os.path.basename(self.ingen_path)}')
self.ingen_exe = compile_ingen(self.ingen_path, self.args, self.args.compile_mode, self.args.fsanitize)

previous_tests = []
try:
Expand All @@ -88,8 +88,8 @@ def run(self, args: argparse.Namespace):
self.delete_dangling_files(dates)

with open(paths.get_cache_path("input_tests"), "w") as f:
f.write("\n".join(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in"))))
f.write("\n".join(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in")))) # TODO: refactor

if not self.args.no_validate:
tests = sorted(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in")))
tests = sorted(glob.glob(os.path.join(os.getcwd(), "in", f"{self.task_id}*.in"))) # TODO: refactor
package_util.validate_tests(tests, self.args.cpus, 'input')
16 changes: 8 additions & 8 deletions src/sinol_make/commands/ingen/ingen_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,43 @@
from sinol_make.helpers import package_util, compiler, compile


def ingen_exists(task_id):
def ingen_exists():
"""
Checks if ingen source file exists.
:param task_id: task id, for example abc
:return: True if exists, False otherwise
"""
return package_util.any_files_matching_pattern(task_id, f'{task_id}ingen.*')
task_id = package_util.get_task_id()
return package_util.any_files_matching_pattern(f'{task_id}ingen.*')


def get_ingen(task_id, ingen_path=None):
def get_ingen_path(ingen_path=None) -> str:
"""
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.
:param ingen_path: path to ingen source file
:return: path to ingen source file or None if not found
"""

task_id = package_util.get_task_id()
if ingen_path is not None:
if os.path.exists(ingen_path):
return ingen_path
else:
util.exit_with_error(f'Ingen source file {ingen_path} does not exist.')

ingen = package_util.get_files_matching_pattern(task_id, f'{task_id}ingen.*')
ingen = package_util.get_files_matching_pattern(f'{task_id}ingen.*')
if len(ingen) == 0:
util.exit_with_error(f'Ingen source file for task {task_id} does not exist.')

# Sio2 first chooses shell scripts, then non-shell source codes.
correct_ingen = None
for i in ingen:
if os.path.splitext(i)[1] == '.sh':
if os.path.splitext(i.path)[1] == '.sh':
correct_ingen = i
break
if correct_ingen is None:
correct_ingen = ingen[0]
return correct_ingen
return correct_ingen.path


def compile_ingen(ingen_path: str, args: argparse.Namespace, compilation_flags='default', use_fsanitize=False):
Expand Down
52 changes: 25 additions & 27 deletions src/sinol_make/commands/inwer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from functools import cmp_to_key
from typing import Dict, List

from sio3pack.test import Test

from sinol_make import util, contest_types
from sinol_make.structs.inwer_structs import TestResult, InwerExecution, VerificationResult, TableData
from sinol_make.helpers import package_util, printer, paths, parsers
Expand Down Expand Up @@ -65,21 +67,21 @@ def verify_test(execution: InwerExecution) -> VerificationResult:
out.decode('utf-8')
)

def verify_and_print_table(self) -> Dict[str, TestResult]:
def verify_and_print_table(self) -> Dict[Test, TestResult]:
"""
Verifies all tests and prints the results in a table.
:return: dictionary of TestResult objects
"""
results = {}
sorted_tests = sorted(self.tests, key=lambda test: package_util.get_group(test, self.task_id))
sorted_tests = sorted(self.tests, key=lambda test: test.group)
executions: List[InwerExecution] = []
for test in sorted_tests:
results[test] = TestResult(test, self.task_id)
executions.append(InwerExecution(test, results[test].test_name, self.inwer_executable))
results[test] = TestResult(test)
executions.append(InwerExecution(test.in_file.path, results[test].test_name, self.inwer_executable))

has_terminal, terminal_width, terminal_height = util.get_terminal_size()

table_data = TableData(results, 0, self.task_id)
table_data = TableData(results, 0)
if has_terminal:
run_event = threading.Event()
run_event.set()
Expand Down Expand Up @@ -117,21 +119,17 @@ def verify_tests_order(self):
"""
Verifies if tests are in correct order.
"""
def get_id(test, func=str.isalpha):
basename = os.path.basename(os.path.splitext(test)[0])
return "".join(filter(func, basename[len(self.task_id):]))

ocen = sorted([test for test in self.tests if test.endswith('ocen.in')],
key=lambda test: int("".join(filter(str.isdigit, get_id(test, str.isdigit)))))
ocen = sorted([test for test in self.tests if test.in_file.path.endswith('ocen.in')],
key=lambda test: test.test_id)
tests = list(set(self.tests) - set(ocen))
last_id = None
last_test = None
for test in ocen:
basename = os.path.basename(os.path.splitext(test)[0])
test_id = int("".join(filter(str.isdigit, basename)))
test_id = int("".join(filter(str.isdigit, test.test_id)))
if last_id is not None and test_id != last_id + 1:
util.exit_with_error(f'Test {os.path.basename(test)} is in wrong order. '
f'Last test was {os.path.basename(last_test)}.')
util.exit_with_error(f'Test {test.test_id} is in wrong order. '
f'Last test was {last_test.test_id}.')
last_id = test_id
last_test = test

Expand All @@ -152,9 +150,9 @@ def is_next(last, curr):
last = 'a' + last
return last == curr

def compare_id(test1, test2):
id1 = get_id(test1)
id2 = get_id(test2)
def compare_id(test1: Test, test2: Test):
id1 = test1.test_id
id2 = test2.test_id
if id1 == id2:
return 0
if len(id1) == len(id2):
Expand All @@ -166,26 +164,26 @@ def compare_id(test1, test2):
return 1

groups = {}
for group in package_util.get_groups(self.tests, self.task_id):
groups[group] = sorted([test for test in tests if package_util.get_group(test, self.task_id) == group],
for group in package_util.get_groups():
groups[group] = sorted([test for test in tests if test.group == group],
key=cmp_to_key(compare_id))
for group, group_tests in groups.items():
last_id = None
last_test = None
for test in group_tests:
test_id = get_id(test)
test_id = "".join(filter(not str.isdigit, test.test_id))
if last_id is not None and not is_next(last_id, test_id):
util.exit_with_error(f'Test {os.path.basename(test)} is in wrong order. '
f'Last test was {os.path.basename(last_test)}.')
util.exit_with_error(f'Test {test.test_id} is in wrong order. '
f'Last test was {last_test.test_id}.')
last_id = test_id
last_test = test

def run(self, args: argparse.Namespace):
args = util.init_package_command(args)

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)
package_util.validate_test_names()
self.inwer = inwer_util.get_inwer_path(args.inwer_path)
if self.inwer is None:
if args.inwer_path is None:
util.exit_with_error('No inwer found in `prog/` directory.')
Expand All @@ -195,17 +193,17 @@ def run(self, args: argparse.Namespace):
print(f'Verifying with inwer {util.bold(relative_path)}')

self.cpus = args.cpus or util.default_cpu_count()
self.tests = package_util.get_tests(self.task_id, args.tests)
self.tests = package_util.get_tests(args.tests)
self.contest_type = contest_types.get_contest_type()

if len(self.tests) == 0:
util.exit_with_error('No tests found.')
else:
print('Verifying tests: ' + util.bold(', '.join(self.tests)))
print('Verifying tests: ' + util.bold(', '.join([test.test_id for test in self.tests])))

util.change_stack_size_to_unlimited()
self.inwer_executable = inwer_util.compile_inwer(self.inwer, args, args.compile_mode, args.fsanitize)
results: Dict[str, TestResult] = self.verify_and_print_table()
results: Dict[Test, TestResult] = self.verify_and_print_table()
print('')

failed_tests = []
Expand Down
19 changes: 11 additions & 8 deletions src/sinol_make/commands/inwer/inwer_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@
import os
import sys
from io import StringIO
from typing import Union
from typing import Union, List

import argparse

from sio3pack.test import Test

from sinol_make import util
from sinol_make.commands.inwer import TestResult, TableData
from sinol_make.helpers import compile, package_util
from sinol_make.helpers import compiler
from sinol_make.interfaces.Errors import CompilationError


def get_inwer_path(task_id: str, path=None) -> Union[str, None]:
def get_inwer_path(path=None) -> Union[str, None]:
"""
Returns path to inwer executable for given task or None if no inwer was found.
"""
task_id = package_util.get_task_id()
if path is None:
inwers = package_util.get_files_matching_pattern(task_id, f'{task_id}inwer.*')
inwers = package_util.get_files_matching_pattern(f'{task_id}inwer.*')
if len(inwers) == 0:
return None
return inwers[0]
return inwers[0].path
else:
inwer = os.path.join(os.getcwd(), path)
if os.path.exists(inwer):
Expand All @@ -46,9 +49,9 @@ def compile_inwer(inwer_path: str, args: argparse.Namespace, compilation_flags='
return inwer_exe


def sort_tests(tests, task_id):
def sort_tests(tests: List[Test]) -> List[Test]:
# First sort by group, then by test name.
tests.sort(key=lambda test: [package_util.get_group(test, task_id), test])
tests.sort(key=lambda test: [test.group, test.test_id])
return tests


Expand All @@ -67,8 +70,8 @@ def print_view(term_width, term_height, table_data: TableData):
for result in results.values():
column_lengths[0] = max(column_lengths[0], len(result.test_name))
column_lengths[1] = max(column_lengths[1], len(result.test_group))
tests.append(result.test_path)
tests = sort_tests(tests, table_data.task_id)
tests.append(result.test)
tests = sort_tests(tests)

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.
Expand Down
Loading
Loading