Skip to content

Commit

Permalink
Create ocen archive on export (#179)
Browse files Browse the repository at this point in the history
* Add ocen archive creation

* Fancy ingen printing

* Package for tests

* Add tests

* Fix tests

* Bump version
  • Loading branch information
MasloMaslane authored Feb 11, 2024
1 parent 8a26ac8 commit 80f838b
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/sinol_make/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sinol_make import util, oiejq


__version__ = "1.5.22"
__version__ = "1.5.23"


def configure_parsers():
Expand Down
98 changes: 82 additions & 16 deletions src/sinol_make/commands/export/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import stat
import shutil
import tarfile
import tempfile
import argparse
import yaml

from sinol_make import util
from sinol_make.commands.ingen.ingen_util import get_ingen, 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


class Command(BaseCommand):
Expand All @@ -25,34 +27,91 @@ def configure_subparser(self, subparser: argparse.ArgumentParser):
self.get_name(),
help='Create archive for oioioi upload',
description='Creates archive in the current directory ready to upload to sio2 or szkopul.')
parser.add_argument('-c', '--cpus', type=int,
help=f'number of cpus to use to generate output files '
f'(default: {util.default_cpu_count()})',
default=util.default_cpu_count())
parsers.add_compilation_arguments(parser)

def get_generated_tests(self):
"""
Returns list of generated tests.
Executes ingen to check what tests are generated.
"""
if not ingen_exists(self.task_id):
return []

def generate_input_tests(self):
print('Generating tests...')
temp_package = paths.get_cache_path('export', 'tests')
if os.path.exists(temp_package):
shutil.rmtree(temp_package)
os.makedirs(temp_package)
in_dir = os.path.join(temp_package, 'in')
prog_dir = os.path.join(temp_package, 'prog')
os.makedirs(in_dir)
shutil.copytree(os.path.join(os.getcwd(), 'prog'), prog_dir)
out_dir = os.path.join(temp_package, 'out')
os.makedirs(out_dir)
prog_dir = os.path.join(temp_package, 'prog')
if os.path.exists(os.path.join(os.getcwd(), 'prog')):
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 = os.path.join(prog_dir, os.path.basename(ingen_path))
ingen_exe = compile_ingen(ingen_path, self.args, self.args.weak_compilation_flags)
if not run_ingen(ingen_exe, in_dir):
util.exit_with_error('Failed to run ingen.')

def generate_output_files(self):
tests = paths.get_cache_path('export', 'tests')
in_dir = os.path.join(tests, 'in')
os.makedirs(in_dir, exist_ok=True)
out_dir = os.path.join(tests, 'out')
os.makedirs(out_dir, exist_ok=True)

# Only example output tests are required for export.
ocen_tests = glob.glob(os.path.join(in_dir, f'{self.task_id}0*.in')) + \
glob.glob(os.path.join(in_dir, f'{self.task_id}*ocen.in'))
outputs = []
for test in ocen_tests:
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,
self.args.weak_compilation_flags)
outgen.args = self.args
outgen.correct_solution_exe = correct_solution_exe
outgen.generate_outputs(outputs)

ingen_path = get_ingen(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.weak_compilation_flags)
if not run_ingen(ingen_exe, in_dir):
util.exit_with_error('Failed to run ingen.')
def get_generated_tests(self):
"""
Returns list of generated tests.
Executes ingen to check what tests are generated.
"""
if not ingen_exists(self.task_id):
return []

in_dir = paths.get_cache_path('export', 'tests', 'in')
tests = glob.glob(os.path.join(in_dir, f'{self.task_id}*.in'))
return [package_util.extract_test_id(test, self.task_id) for test in tests]

def create_ocen(self, target_dir: str):
"""
Creates ocen archive for sio2.
:param target_dir: Path to exported package.
"""
attachments_dir = os.path.join(target_dir, 'attachments')
if not os.path.exists(attachments_dir):
os.makedirs(attachments_dir)
tests_dir = paths.get_cache_path('export', 'tests')

with tempfile.TemporaryDirectory() as tmpdir:
ocen_dir = os.path.join(tmpdir, self.task_id)
os.makedirs(ocen_dir)
in_dir = os.path.join(ocen_dir, 'in')
os.makedirs(in_dir)
out_dir = os.path.join(ocen_dir, 'out')
os.makedirs(out_dir)
for ext in ['in', 'out']:
for test in glob.glob(os.path.join(tests_dir, ext, f'{self.task_id}0*.{ext}')) + \
glob.glob(os.path.join(tests_dir, ext, f'{self.task_id}*ocen.{ext}')):
shutil.copy(test, os.path.join(ocen_dir, ext, os.path.basename(test)))

with tarfile.open(os.path.join(attachments_dir, f'{self.task_id}ocen.tgz'), "w:gz") as tar:
tar.add(ocen_dir, arcname=os.path.basename(ocen_dir))

def copy_package_required_files(self, target_dir: str):
"""
Copies package files and directories from
Expand All @@ -79,19 +138,24 @@ def copy_package_required_files(self, target_dir: str):
for test in glob.glob(os.path.join(os.getcwd(), ext, f'{self.task_id}0*.{ext}')):
shutil.copy(test, os.path.join(target_dir, ext))

print('Generating tests...')
generated_tests = self.get_generated_tests()
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, self.task_id) not in generated_tests:
tests_to_copy.append((ext, test))

cache_test_dir = paths.get_cache_path('export', 'tests')
if len(tests_to_copy) > 0:
print(util.warning(f'Found {len(tests_to_copy)} tests that are not generated by ingen.'))
for test in tests_to_copy:
print(util.warning(f'Copying {os.path.basename(test[1])}...'))
shutil.copy(test[1], os.path.join(target_dir, test[0], os.path.basename(test[1])))
shutil.copy(test[1], os.path.join(cache_test_dir, test[0], os.path.basename(test[1])))

self.generate_output_files()
print('Generating ocen archive...')
self.create_ocen(target_dir)

def clear_files(self, target_dir: str):
"""
Expand All @@ -112,6 +176,7 @@ def create_makefile_in(self, target_dir: str, config: dict):
with open(os.path.join(target_dir, 'makefile.in'), 'w') as f:
cxx_flags = '-std=c++20'
c_flags = '-std=gnu99'

def format_multiple_arguments(obj):
if isinstance(obj, str):
return obj
Expand Down Expand Up @@ -163,6 +228,7 @@ def run(self, args: argparse.Namespace):
os.makedirs(export_package_path)

util.change_stack_size_to_unlimited()
self.generate_input_tests()
self.copy_package_required_files(export_package_path)
self.clear_files(export_package_path)
self.create_makefile_in(export_package_path, config)
Expand Down
2 changes: 2 additions & 0 deletions src/sinol_make/commands/ingen/ingen_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ def run_ingen(ingen_exe, working_dir=None):
st = os.stat(ingen_exe)
os.chmod(ingen_exe, st.st_mode | stat.S_IEXEC)

print(util.bold(' Ingen output '.center(util.get_terminal_size()[1], '=')))
process = subprocess.Popen([ingen_exe], stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
cwd=working_dir, shell=is_shell)
while process.poll() is None:
print(process.stdout.readline().decode('utf-8'), end='')

print(process.stdout.read().decode('utf-8'), end='')
exit_code = process.returncode
print(util.bold(' End of ingen output '.center(util.get_terminal_size()[1], '=')))

return exit_code == 0
10 changes: 7 additions & 3 deletions src/sinol_make/commands/outgen/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def generate_outputs(self, outputs_to_generate):
arguments = []
for output in outputs_to_generate:
output_basename = os.path.basename(output)
input = os.path.join(os.getcwd(), 'in', os.path.splitext(output_basename)[0] + '.in')
in_dir = os.path.join("/", *(os.path.abspath(output).split(os.sep)[:-2]), 'in')
input = os.path.join(in_dir, os.path.splitext(output_basename)[0] + '.in')
arguments.append(OutputGenerationArguments(self.correct_solution_exe, input, output))

with mp.Pool(self.args.cpus) as pool:
Expand All @@ -55,11 +56,14 @@ def generate_outputs(self, outputs_to_generate):
else:
print(util.info('Successfully generated all output files.'))

def calculate_md5_sums(self):
def calculate_md5_sums(self, tests=None):
"""
Calculates md5 sums for each test.
:return: Tuple (dictionary of md5 sums, list of outputs tests that need to be generated)
"""
if tests is None:
tests = glob.glob(os.path.join(os.getcwd(), 'in', '*.in'))

old_md5_sums = None
try:
with open(os.path.join(os.getcwd(), 'in', '.md5sums'), 'r') as f:
Expand All @@ -71,7 +75,7 @@ def calculate_md5_sums(self):

md5_sums = {}
outputs_to_generate = []
for file in glob.glob(os.path.join(os.getcwd(), 'in', '*.in')):
for file in tests:
basename = os.path.basename(file)
output_basename = os.path.splitext(os.path.basename(basename))[0] + '.out'
output_path = os.path.join(os.getcwd(), 'out', output_basename)
Expand Down
36 changes: 36 additions & 0 deletions tests/commands/export/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,39 @@ def test_handwritten_tests(create_package):
extracted = os.path.join(tmpdir, task_id)
for file in ["in/hwr0.in", "in/hwr0a.in", "out/hwr0.out", "out/hwr0a.out"]:
assert os.path.exists(os.path.join(extracted, file))


@pytest.mark.parametrize("create_package", [util.get_ocen_package_path()], indirect=True)
def test_ocen_archive(create_package):
"""
Test creation of ocen archive.
"""
parser = configure_parsers()
args = parser.parse_args(["export"])
command = Command()
command.run(args)
task_id = package_util.get_task_id()
in_handwritten = ["ocen0.in", "ocen0a.in", "ocen1a.in", "ocen1ocen.in"]
out_handwritten = ["ocen0.out"]
ocen_tests = ["ocen0", "ocen0a", "ocen0b", "ocen1ocen", "ocen2ocen"]

with tempfile.TemporaryDirectory() as tmpdir:
package_path = os.path.join(tmpdir, task_id)
os.mkdir(package_path)
with tarfile.open(f'{task_id}.tgz', "r") as tar:
sinol_util.extract_tar(tar, tmpdir)

for ext in ["in", "out"]:
tests = [os.path.basename(f) for f in glob.glob(os.path.join(package_path, ext, f'*.{ext}'))]
assert set(tests) == set(in_handwritten if ext == "in" else out_handwritten)

ocen_tar = os.path.join(package_path, "attachments", f"{task_id}ocen.tgz")
assert os.path.exists(ocen_tar)
ocen_dir = os.path.join(package_path, "ocen_dir")

with tarfile.open(ocen_tar, "r") as tar:
sinol_util.extract_tar(tar, ocen_dir)

for ext in ["in", "out"]:
tests = [os.path.basename(f) for f in glob.glob(os.path.join(ocen_dir, task_id, ext, f'*.{ext}'))]
assert set(tests) == set([f'{test}.{ext}' for test in ocen_tests])
8 changes: 8 additions & 0 deletions tests/commands/export/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def _create_package(tmpdir, path):
os.chdir(package_path)
command = get_command()
util.create_ins_outs(package_path)
command.args = argparse.Namespace(cpus=1, weak_compilation_flags=False,
cpp_compiler_path=compiler.get_cpp_compiler_path(),
c_compiler_path=None, python_interpreter_path=None,
java_compiler_path=None)
return command


Expand All @@ -21,9 +25,11 @@ def test_get_generated_tests():
"""
with tempfile.TemporaryDirectory() as tmpdir:
command = _create_package(tmpdir, util.get_handwritten_package_path())
command.generate_input_tests()
assert set(command.get_generated_tests()) == {"1a", "2a"}

command = _create_package(tmpdir, util.get_simple_package_path())
command.generate_input_tests()
assert set(command.get_generated_tests()) == {"1a", "2a", "3a", "4a"}


Expand All @@ -36,6 +42,7 @@ def test_copy_package_required_files():
res_dir = os.path.join(tmpdir, "res")
os.mkdir(res_dir)
command = _create_package(tmpdir, util.get_handwritten_package_path())
command.generate_input_tests()
command.copy_package_required_files(res_dir)

assert_configs_equal(os.getcwd(), res_dir)
Expand All @@ -47,6 +54,7 @@ def test_copy_package_required_files():
shutil.rmtree(res_dir)
os.mkdir(res_dir)
command = _create_package(tmpdir, util.get_simple_package_path())
command.generate_input_tests()
command.copy_package_required_files(res_dir)

assert_configs_equal(os.getcwd(), res_dir)
Expand Down
4 changes: 4 additions & 0 deletions tests/packages/ocen/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Package for testing ocen archive creation
sinol_task_id: ocen
time_limit: 1000
memory_limit: 10240
1 change: 1 addition & 0 deletions tests/packages/ocen/in/ocen0.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2 3
1 change: 1 addition & 0 deletions tests/packages/ocen/in/ocen0a.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3 4
1 change: 1 addition & 0 deletions tests/packages/ocen/in/ocen1a.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1 2
1 change: 1 addition & 0 deletions tests/packages/ocen/in/ocen1ocen.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3 5
1 change: 1 addition & 0 deletions tests/packages/ocen/out/ocen0.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5
9 changes: 9 additions & 0 deletions tests/packages/ocen/prog/ocen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <bits/stdc++.h>

using namespace std;

int main() {
int a, b;
cin >> a >> b;
cout << a + b;
}
15 changes: 15 additions & 0 deletions tests/packages/ocen/prog/oceningen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <bits/stdc++.h>

using namespace std;

int main() {
ofstream f("ocen0b.in");
f << "0 0\n";
f.close();
f.open("ocen2ocen.in");
f << "1 2\n";
f.close();
f.open("ocen2a.in");
f << "1 1\n";
f.close();
}
8 changes: 8 additions & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ def get_large_output_package_path():
"""
return os.path.join(os.path.dirname(__file__), "packages", "large_output")


def get_ocen_package_path():
"""
Get path to package for testing ocen archive creation (/tests/packages/ocen)
"""
return os.path.join(os.path.dirname(__file__), "packages", "ocen")


def create_ins(package_path, task_id):
"""
Create .in files for package.
Expand Down

0 comments on commit 80f838b

Please sign in to comment.