From 0c4bf6fbc155682b257f86477342d5c3ead4634b Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Thu, 6 Apr 2023 15:44:54 +0200 Subject: [PATCH 01/11] Split some more reusable logic in sio.executors.common --- sio/executors/common.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sio/executors/common.py b/sio/executors/common.py index 0da4f35..2ad222d 100644 --- a/sio/executors/common.py +++ b/sio/executors/common.py @@ -83,28 +83,30 @@ def _run(environ, executor, use_sandboxes): except Exception as e: raise Exception("Failed to open archive: " + six.text_type(e)) - with file_executor as fe: - with open(input_name, 'rb') as inf: + return _run_core(environ, file_executor, input_name, tempcwd('out'), tempcwd(exe_filename), + 'exec_', use_sandboxes) + + finally: + rmtree(zipdir) + + +def _run_core(environ, file_executor, input_name, output_name, exe_filename, environ_prefix, use_sandboxes): + with file_executor as fe: + with open(input_name, 'rb') as inf: # Open output file in append mode to allow appending # only to the end of the output file. Otherwise, # a contestant's program could modify the middle of the file. - with open(tempcwd('out'), 'ab') as outf: - renv = fe( + with open(output_name, 'ab') as outf: + return fe( tempcwd(exe_filename), [], stdin=inf, stdout=outf, ignore_errors=True, environ=environ, - environ_prefix='exec_', + environ_prefix=environ_prefix, ) - finally: - rmtree(zipdir) - - return renv - - def _fake_run_as_exe_is_output_file(environ): # later code expects 'out' file to be present after compilation ft.download(environ, 'exe_file', tempcwd('out')) From 12fd246c8bc16dffbf6ed6c2d1eee1ad3f1096af Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Thu, 6 Apr 2023 15:46:47 +0200 Subject: [PATCH 02/11] Allow to pass FDs to executed programs in sio.workers.executors --- sio/workers/executors.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sio/workers/executors.py b/sio/workers/executors.py index 7119fac..2b77a26 100644 --- a/sio/workers/executors.py +++ b/sio/workers/executors.py @@ -79,6 +79,7 @@ def execute_command( real_time_limit=None, ignore_errors=False, extra_ignore_errors=(), + pass_fds=(), **kwargs ): """Utility function to run arbitrary command. @@ -111,6 +112,9 @@ def execute_command( ``stdout`` Only when ``capture_output=True``: output of the command + + ``pass_fds`` + Extra file descriptors to pass to the command. """ # Using temporary file is way faster than using subproces.PIPE # and it prevents deadlocks. @@ -141,6 +145,7 @@ def execute_command( env=env, cwd=tempcwd(), preexec_fn=os.setpgrp, + pass_fds=pass_fds, ) kill_timer = None From 07d76e11557475b04ebb51497efda0448ec4023a Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Thu, 6 Apr 2023 15:48:28 +0200 Subject: [PATCH 03/11] Encoder-decoder task support --- setup.py | 4 + sio/executors/encdec_common.py | 256 +++++++++++++++++++++++++++++++++ sio/executors/executor.py | 6 +- sio/executors/sio2jail_exec.py | 6 +- sio/executors/unsafe_exec.py | 6 +- 5 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 sio/executors/encdec_common.py diff --git a/setup.py b/setup.py index eae7f98..8358276 100644 --- a/setup.py +++ b/setup.py @@ -37,9 +37,13 @@ 'ping = sio.workers.ping:run', 'compile = sio.compilers.job:run', 'exec = sio.executors.executor:run', + 'encdec-exec = sio.executors.executor:encdec_run', 'sio2jail-exec = sio.executors.sio2jail_exec:run', + 'sio2jail-encdec-exec = sio.executors.sio2jail_exec:encdec_run', 'cpu-exec = sio.executors.executor:run', + 'cpu-encdec-exec = sio.executors.executor:encdec_run', 'unsafe-exec = sio.executors.unsafe_exec:run', + 'unsafe-encdec-exec = sio.executors.unsafe_exec:encdec_run', 'ingen = sio.executors.ingen:run', 'inwer = sio.executors.inwer:run', ], diff --git a/sio/executors/encdec_common.py b/sio/executors/encdec_common.py new file mode 100644 index 0000000..c4251e0 --- /dev/null +++ b/sio/executors/encdec_common.py @@ -0,0 +1,256 @@ +from __future__ import absolute_import +import os +import logging +from shutil import rmtree +import tempfile +from zipfile import ZipFile, is_zipfile +from sio.executors.checker import _limit_length +from sio.executors.common import _run_core +from sio.workers import ft +from sio.workers.executors import ExecError, PRootExecutor, UnprotectedExecutor +from sio.workers.util import decode_fields, replace_invalid_UTF, tempcwd +from sio.workers.file_runners import get_file_runner + +# TODO XXX FIXME +# Hide the files like enc_in or hint from the contestants +# Would not be nice if someone just sideloaded enc_in in decoder + +import six + +logger = logging.getLogger(__name__) + + +DEFAULT_SUPPLEMENTARY_TIME_LIMIT = 30000 # in ms +DEFAULT_SUPPLEMENTARY_MEM_LIMIT = 268 * 2**10 # in KiB + + +class ChannelError(Exception): + pass + + +class CheckerError(Exception): + pass + + +def _populate_environ(renv, environ, prefix): + """Takes interesting fields from renv into environ""" + for key in ( + "time_used", + "mem_used", + "num_syscalls", + "result_code", + "result_string", + ): + if key in renv: + environ[prefix + key] = renv[key] + + +def _run_supplementary(env, command, executor, environ_prefix, **kwargs): + with executor: + return executor( + command, + capture_output=True, + split_lines=True, + mem_limit=DEFAULT_SUPPLEMENTARY_MEM_LIMIT, + time_limit=DEFAULT_SUPPLEMENTARY_TIME_LIMIT, + environ=env, + environ_prefix=environ_prefix, + **kwargs + ) + + +def _run_encoder(environ, file_executor, exe_filename, use_sandboxes): + ft.download(environ, "in_file", "enc_in", add_to_cache=True) + return _run_core( + environ, + file_executor, + tempcwd("enc_in"), + tempcwd("enc_out"), + tempcwd(exe_filename), + "encoder_", + use_sandboxes, + ) + + +def _run_channel_core(env, result_file, checker_file, use_sandboxes=False): + command = [ + "./chn", + "enc_in", + "enc_out", + "hint", + str(result_file.fileno()), + str(checker_file.fileno()), + ] + + def execute_channel(with_stderr=False, stderr=None): + return _run_supplementary( + env, + command, + PRootExecutor("null-sandbox") + if env.get("untrusted_channel", False) and use_sandboxes + else UnprotectedExecutor(), + "channel_", + ignore_errors=True, + forward_stderr=with_stderr, + stderr=stderr, + pass_fds=(result_file.fileno(), checker_file.fileno()), + ) + + with tempfile.TemporaryFile() as stderr_file: + renv = execute_channel(stderr=stderr_file) + if renv["return_code"] >= 2: + stderr_file.seek(0) + stderr = stderr_file.read() + raise ChannelError( + "Channel returned code(%d) >= 2. Channel stdout: " + '"%s", stderr: "%s". Channel environ dump: %s' + % (renv["return_code"], renv["stdout"], stderr, env) + ) + + return renv["stdout"] + + +def _run_channel(environ, use_sandboxes=False): + ft.download(environ, "hint_file", "hint", add_to_cache=True) + ft.download(environ, "chn_file", "chn", add_to_cache=True) + os.chmod(tempcwd("chn"), 0o700) + result_filename = tempcwd("dec_in") + checker_filename = tempcwd("chn_out") + + try: + with open(result_filename, "wb") as result_file, open( + checker_filename, "wb" + ) as checker_file: + output = _run_channel_core( + environ, result_file, checker_file, use_sandboxes + ) + except (ChannelError, ExecError) as e: + logger.error("Channel failed! %s", e) + logger.error("Environ dump: %s", environ) + raise SystemError(e) + + while len(output) < 3: + output.append("") + + if six.ensure_binary(output[0]) == b"OK": + environ["channel_result_code"] = "OK" + if output[1]: + environ["channel_result_string"] = _limit_length(output[1]).decode("utf-8") + return True + else: + environ["failed_step"] = "channel" + environ["channel_result_code"] = "WA" + environ["channel_result_string"] = _limit_length(output[1]).decode("utf-8") + return False + + +def _run_decoder(environ, file_executor, exe_filename, use_sandboxes): + return _run_core( + environ, + file_executor, + tempcwd("dec_in"), + tempcwd("dec_out"), + tempcwd(exe_filename), + "decoder_", + use_sandboxes, + ) + + +def _run_checker_core(env, use_sandboxes=False): + command = ["./chk", "enc_in", "hint", "chn_out", "dec_out"] + + def execute_checker(with_stderr=False, stderr=None): + return _run_supplementary( + env, + command, + PRootExecutor("null-sandbox") + if env.get("untrusted_checker", False) and use_sandboxes + else UnprotectedExecutor(), + "checker_", + ignore_errors=True, + forward_stderr=with_stderr, + stderr=stderr, + ) + + with tempfile.TemporaryFile() as stderr_file: + renv = execute_checker(stderr=stderr_file) + if renv["return_code"] >= 2: + stderr_file.seek(0) + stderr = stderr_file.read() + raise CheckerError( + "Checker returned code(%d) >= 2. Checker stdout: " + '"%s", stderr: "%s". Checker environ dump: %s' + % (renv["return_code"], renv["stdout"], stderr, env) + ) + + return renv["stdout"] + + +def _run_checker(environ, use_sandboxes=False): + ft.download(environ, "chk_file", "chk", add_to_cache=True) + os.chmod(tempcwd("chk"), 0o700) + + try: + output = _run_checker_core(environ, use_sandboxes) + except (ChannelError, ExecError) as e: + logger.error("Checker failed! %s", e) + logger.error("Environ dump: %s", environ) + raise SystemError(e) + + while len(output) < 3: + output.append("") + + if six.ensure_binary(output[0]) == b"OK": + environ["checker_result_code"] = "OK" + if output[1]: + environ["checker_result_string"] = _limit_length(output[1]).decode("utf-8") + environ["checker_result_percentage"] = float(output[2] or 100) + return True + else: + environ["failed_step"] = "checker" + environ["checker_result_code"] = "WA" + environ["checker_result_string"] = _limit_length(output[1]).decode("utf-8") + environ["checker_result_percentage"] = 0 + return False + + +def run(environ, executor, use_sandboxes=True): + """ + Common code for executors. + + :param: environ Recipe to pass to `filetracker` and `sio.workers.executors` + For all supported options, see the global documentation for + `sio.workers.executors` and prefix them with ``encoder_`` + or ``decoder_``. + :param: executor Executor instance used for executing commands. + :param: use_sandboxes Enables safe checking output correctness. + See `sio.executors.checkers`. True by default. + """ + + file_executor = get_file_runner(executor, environ) + exe_filename = file_executor.preferred_filename() + + ft.download(environ, "exe_file", exe_filename, add_to_cache=True) + os.chmod(tempcwd(exe_filename), 0o700) + + encoder_environ = environ.copy() + renv = _run_encoder(encoder_environ, file_executor, exe_filename, use_sandboxes) + _populate_environ(renv, environ, "encoder_") + + if renv["result_code"] != "OK": + environ["failed_step"] = "encoder" + return environ + + if not _run_channel(environ, use_sandboxes): + return environ + + renv = _run_decoder(environ, file_executor, exe_filename, use_sandboxes) + _populate_environ(renv, environ, "decoder_") + + if renv["result_code"] != "OK": + environ["failed_step"] = "decoder" + return environ + + _run_checker(environ, use_sandboxes) + + return environ diff --git a/sio/executors/executor.py b/sio/executors/executor.py index 40ce9af..11786d4 100644 --- a/sio/executors/executor.py +++ b/sio/executors/executor.py @@ -1,7 +1,11 @@ from __future__ import absolute_import -from sio.executors import common +from sio.executors import common, encdec_common from sio.workers.executors import SupervisedExecutor def run(environ): return common.run(environ, SupervisedExecutor()) + + +def encdec_run(environ): + return encdec_common.run(environ, SupervisedExecutor()) diff --git a/sio/executors/sio2jail_exec.py b/sio/executors/sio2jail_exec.py index ac23e4a..ba05006 100644 --- a/sio/executors/sio2jail_exec.py +++ b/sio/executors/sio2jail_exec.py @@ -1,6 +1,10 @@ -from sio.executors import common +from sio.executors import common, encdec_common from sio.workers.executors import Sio2JailExecutor def run(environ): return common.run(environ, Sio2JailExecutor()) + + +def encdec_run(environ): + return encdec_common.run(environ, Sio2JailExecutor()) diff --git a/sio/executors/unsafe_exec.py b/sio/executors/unsafe_exec.py index 3b0ae38..f2c7af6 100644 --- a/sio/executors/unsafe_exec.py +++ b/sio/executors/unsafe_exec.py @@ -1,7 +1,11 @@ from __future__ import absolute_import -from sio.executors import common +from sio.executors import common, encdec_common from sio.workers.executors import DetailedUnprotectedExecutor def run(environ): return common.run(environ, DetailedUnprotectedExecutor(), use_sandboxes=False) + + +def encdec_run(environ): + return encdec_common.run(environ, DetailedUnprotectedExecutor(), use_sandboxes=False) From 9d7089fe928cea4674704fd3fc8ca2ad1731c71f Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Mon, 24 Apr 2023 00:34:51 +0000 Subject: [PATCH 04/11] Fix security issue in sio.executors.encdec_common --- sio/executors/encdec_common.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/sio/executors/encdec_common.py b/sio/executors/encdec_common.py index c4251e0..67f9872 100644 --- a/sio/executors/encdec_common.py +++ b/sio/executors/encdec_common.py @@ -1,20 +1,16 @@ from __future__ import absolute_import import os import logging -from shutil import rmtree +from shutil import copy2, rmtree import tempfile from zipfile import ZipFile, is_zipfile from sio.executors.checker import _limit_length from sio.executors.common import _run_core from sio.workers import ft from sio.workers.executors import ExecError, PRootExecutor, UnprotectedExecutor -from sio.workers.util import decode_fields, replace_invalid_UTF, tempcwd +from sio.workers.util import decode_fields, replace_invalid_UTF, tempcwd, TemporaryCwd from sio.workers.file_runners import get_file_runner -# TODO XXX FIXME -# Hide the files like enc_in or hint from the contestants -# Would not be nice if someone just sideloaded enc_in in decoder - import six logger = logging.getLogger(__name__) @@ -214,6 +210,23 @@ def _run_checker(environ, use_sandboxes=False): return False +def _run_decoder_hide_files(environ, file_executor, exe_filename, use_sandboxes, orig_dir): + # We now have quite a lot of interes + # be nice if some decoder read them. + with TemporaryCwd() as new_dir: + # Copy the executable and input + for f in 'dec_in', exe_filename: + copy2(os.path.join(orig_dir, f), tempcwd(f)) + + renv = _run_decoder(environ, file_executor, exe_filename, use_sandboxes) + + # Copy the output + for f in 'dec_out',: + copy2(tempcwd(f), os.path.join(orig_dir, f)) + + return renv + + def run(environ, executor, use_sandboxes=True): """ Common code for executors. @@ -244,7 +257,7 @@ def run(environ, executor, use_sandboxes=True): if not _run_channel(environ, use_sandboxes): return environ - renv = _run_decoder(environ, file_executor, exe_filename, use_sandboxes) + renv = _run_decoder_hide_files(environ, file_executor, exe_filename, use_sandboxes, tempcwd()) _populate_environ(renv, environ, "decoder_") if renv["result_code"] != "OK": From 68a27d5c02012e43d85cf3b37767b07d51126c5c Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Mon, 15 May 2023 20:58:37 +0200 Subject: [PATCH 05/11] Remove usage of six and normalize string/bytes a little bit --- sio/executors/encdec_common.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sio/executors/encdec_common.py b/sio/executors/encdec_common.py index 67f9872..bac00cd 100644 --- a/sio/executors/encdec_common.py +++ b/sio/executors/encdec_common.py @@ -11,8 +11,6 @@ from sio.workers.util import decode_fields, replace_invalid_UTF, tempcwd, TemporaryCwd from sio.workers.file_runners import get_file_runner -import six - logger = logging.getLogger(__name__) @@ -126,9 +124,9 @@ def _run_channel(environ, use_sandboxes=False): raise SystemError(e) while len(output) < 3: - output.append("") + output.append(b"") - if six.ensure_binary(output[0]) == b"OK": + if output[0] == b"OK": environ["channel_result_code"] = "OK" if output[1]: environ["channel_result_string"] = _limit_length(output[1]).decode("utf-8") @@ -194,9 +192,9 @@ def _run_checker(environ, use_sandboxes=False): raise SystemError(e) while len(output) < 3: - output.append("") + output.append(b"") - if six.ensure_binary(output[0]) == b"OK": + if output[0] == b"OK": environ["checker_result_code"] = "OK" if output[1]: environ["checker_result_string"] = _limit_length(output[1]).decode("utf-8") From bd1a4651bf5e2e44be6ad2d0529657b567258441 Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Thu, 18 May 2023 15:04:22 +0200 Subject: [PATCH 06/11] Added a test case --- sio/executors/test/sources/rle.cpp | 59 ++++++++++++++++++++ sio/executors/test/sources/rle0a.hint | 1 + sio/executors/test/sources/rle0a.in | 2 + sio/executors/test/sources/rle1.hint | 1 + sio/executors/test/sources/rle1.in | 2 + sio/executors/test/sources/rlechk.cpp | 35 ++++++++++++ sio/executors/test/sources/rlechn.cpp | 42 ++++++++++++++ sio/executors/test/test_encdec.py | 79 +++++++++++++++++++++++++++ 8 files changed, 221 insertions(+) create mode 100644 sio/executors/test/sources/rle.cpp create mode 100644 sio/executors/test/sources/rle0a.hint create mode 100644 sio/executors/test/sources/rle0a.in create mode 100644 sio/executors/test/sources/rle1.hint create mode 100644 sio/executors/test/sources/rle1.in create mode 100644 sio/executors/test/sources/rlechk.cpp create mode 100644 sio/executors/test/sources/rlechn.cpp create mode 100644 sio/executors/test/test_encdec.py diff --git a/sio/executors/test/sources/rle.cpp b/sio/executors/test/sources/rle.cpp new file mode 100644 index 0000000..6ce9ce0 --- /dev/null +++ b/sio/executors/test/sources/rle.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +void enkoder() { + int last = '\n'; + int ch; + std::size_t count = 0; + + while ((ch = std::getchar()) != '\n') { + if (ch != last) { + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + count = 0; + } + + ++count; + last = ch; + } + + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + std::putchar('\n'); +} + +void dekoder() { + int ch; + + while ((ch = std::getchar()) != '\n') { + std::size_t count; + assert(std::scanf("%zu;", &count) == 1); + while (count--) std::putchar(ch); + } + + std::putchar('\n'); +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/sources/rle0a.hint b/sio/executors/test/sources/rle0a.hint new file mode 100644 index 0000000..39aa27c --- /dev/null +++ b/sio/executors/test/sources/rle0a.hint @@ -0,0 +1 @@ +C1;Z1;Y1;M1;P1;I1; diff --git a/sio/executors/test/sources/rle0a.in b/sio/executors/test/sources/rle0a.in new file mode 100644 index 0000000..78abf17 --- /dev/null +++ b/sio/executors/test/sources/rle0a.in @@ -0,0 +1,2 @@ +E +CZYMPI diff --git a/sio/executors/test/sources/rle1.hint b/sio/executors/test/sources/rle1.hint new file mode 100644 index 0000000..d98a846 --- /dev/null +++ b/sio/executors/test/sources/rle1.hint @@ -0,0 +1 @@ +A1;B1;A1;B1;A1;B1;A1;B1;A1;B2;A7;B1;A1;B1;A1;B1;A2;B1;A2;B13;A7;B3;A2;B11;A7; diff --git a/sio/executors/test/sources/rle1.in b/sio/executors/test/sources/rle1.in new file mode 100644 index 0000000..4eb4b84 --- /dev/null +++ b/sio/executors/test/sources/rle1.in @@ -0,0 +1,2 @@ +E +ABABABABABBAAAAAAABABABAABAABBBBBBBBBBBBBAAAAAAABBBAABBBBBBBBBBBAAAAAAA diff --git a/sio/executors/test/sources/rlechk.cpp b/sio/executors/test/sources/rlechk.cpp new file mode 100644 index 0000000..87158bd --- /dev/null +++ b/sio/executors/test/sources/rlechk.cpp @@ -0,0 +1,35 @@ +#include +#include +#include + +using namespace std; + +int main(int argc, char **argv) { + assert(argc == 5); + + FILE *in = fopen(argv[1], "r"); + assert(in); + FILE *hint = fopen(argv[2], "r"); + assert(hint); + FILE *channel_out = fopen(argv[3], "r"); + assert(channel_out); + FILE *encoder_out = fopen(argv[4], "r"); + assert(encoder_out); + + assert(fseek(in, 2, SEEK_SET) >= 0); + + int ch; + while ((ch = fgetc(encoder_out)) != EOF) { + if (fgetc(in) != ch) { + puts("WA\nZły wynik\n0"); + return 0; + } + } + + puts("OK\nBardzo dobrze\n100"); + + fclose(in); + fclose(hint); + fclose(channel_out); + fclose(encoder_out); +} diff --git a/sio/executors/test/sources/rlechn.cpp b/sio/executors/test/sources/rlechn.cpp new file mode 100644 index 0000000..c712d13 --- /dev/null +++ b/sio/executors/test/sources/rlechn.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +using namespace std; + +int main(int argc, char **argv) { + assert(argc == 6); + + FILE *in = fopen(argv[1], "r"); + assert(in); + FILE *out = fopen(argv[2], "r"); + assert(out); + FILE *hint = fopen(argv[3], "r"); + assert(hint); + FILE *result = fdopen(atoi(argv[4]), "w"); + assert(result); + FILE *checker = fdopen(atoi(argv[5]), "w"); + assert(checker); + + fputs("D\n", result); + + int ch; + while ((ch = fgetc(out)) != EOF) { + if (fgetc(hint) != ch) { + puts("WA\nZły wynik\n0"); + return 0; + } + + assert(fputc(ch, result) != EOF); + } + + assert(fputs("OK\n", checker) != EOF); + + puts("OK\nBardzo dobrze\n100"); + + fclose(in); + fclose(out); + fclose(hint); + fclose(result); + fclose(checker); +} diff --git a/sio/executors/test/test_encdec.py b/sio/executors/test/test_encdec.py new file mode 100644 index 0000000..d74104b --- /dev/null +++ b/sio/executors/test/test_encdec.py @@ -0,0 +1,79 @@ +import glob +import os.path + +from filetracker.client.dummy import DummyClient +from sio.assertion_utils import ok_, eq_ +from sio.compilers.job import run as run_compiler +from sio.executors.sio2jail_exec import encdec_run +from sio.workers import ft +from sio.workers.util import TemporaryCwd, tempcwd + + +SOURCES = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'sources') + + +def not_in_(a, b, msg=None): + """Shorthand for 'assert a not in b, "%r in %r" % (a, b)""" + if a in b: + raise AssertionError(msg or "%r in %r" % (a, b)) + + +def print_env(env): + from pprint import pprint + + pprint(env) + + +def upload_files(): + "Uploads all files from SOURCES to a newly created dummy filetracker" + + # DummyClient operates in the working directory. + ft.set_instance(DummyClient()) + for path in glob.glob(os.path.join(SOURCES, '*')): + ft.upload({'path': '/' + os.path.basename(path)}, 'path', path) + + +def test_encdec_run(): + with TemporaryCwd(): + upload_files() + with TemporaryCwd('compile_code'): + run_compiler({ + 'source_file': '/rle.cpp', + 'compiler': 'system-cpp', + 'out_file': '/rle.e' + }) + with TemporaryCwd('compile_chn'): + run_compiler({ + 'source_file': '/rlechn.cpp', + 'compiler': 'system-cpp', + 'out_file': '/rlechn.e' + }) + with TemporaryCwd('compile_chk'): + run_compiler({ + 'source_file': '/rlechk.cpp', + 'compiler': 'system-cpp', + 'out_file': '/rlechk.e' + }) + # Stolen from a sample problem package I used to test the encdec + with TemporaryCwd('run_0a'): + renv = encdec_run({ + 'chn_file': '/rlechn.e', + 'chk_file': '/rlechk.e', + 'exe_file': '/rle.e', + 'hint_file': '/rle0a.hint', + 'in_file': '/rle0a.in' + }) + print_env(renv) + not_in_('failed_step', renv) + eq_(renv['checker_result_percentage'], 100.) + with TemporaryCwd('run_1'): + renv = encdec_run({ + 'chn_file': '/rlechn.e', + 'chk_file': '/rlechk.e', + 'exe_file': '/rle.e', + 'hint_file': '/rle1.hint', + 'in_file': '/rle1.in' + }) + print_env(renv) + not_in_('failed_step', renv) + eq_(renv['checker_result_percentage'], 100.) From e12d1435962c45e06e778bbfe4f9d362e4d162d1 Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Thu, 18 May 2023 15:12:01 +0200 Subject: [PATCH 07/11] Maybe don't use sio2jail by default because GH Actions seems not to like it very much --- sio/executors/test/test_encdec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sio/executors/test/test_encdec.py b/sio/executors/test/test_encdec.py index d74104b..a22a5e7 100644 --- a/sio/executors/test/test_encdec.py +++ b/sio/executors/test/test_encdec.py @@ -4,7 +4,7 @@ from filetracker.client.dummy import DummyClient from sio.assertion_utils import ok_, eq_ from sio.compilers.job import run as run_compiler -from sio.executors.sio2jail_exec import encdec_run +from sio.executors.unsafe_exec import encdec_run from sio.workers import ft from sio.workers.util import TemporaryCwd, tempcwd From 40668f2a3fc81d7a1cbeb2084d3eac002fa19fab Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Sun, 21 May 2023 16:56:47 +0200 Subject: [PATCH 08/11] WIP more tests, they can't seem to pass unfortunately --- sio/executors/test/sources/rlechk.cpp | 1 + sio/executors/test/sources/rlechn.cpp | 1 + sio/executors/test/sources/rleloopdec.cpp | 51 ++++++++ sio/executors/test/sources/rleloopenc.cpp | 27 ++++ sio/executors/test/sources/rlememenc.cpp | 41 +++++++ sio/executors/test/test_encdec.py | 143 +++++++++++++++------- 6 files changed, 222 insertions(+), 42 deletions(-) create mode 100644 sio/executors/test/sources/rleloopdec.cpp create mode 100644 sio/executors/test/sources/rleloopenc.cpp create mode 100644 sio/executors/test/sources/rlememenc.cpp diff --git a/sio/executors/test/sources/rlechk.cpp b/sio/executors/test/sources/rlechk.cpp index 87158bd..438d6e3 100644 --- a/sio/executors/test/sources/rlechk.cpp +++ b/sio/executors/test/sources/rlechk.cpp @@ -25,6 +25,7 @@ int main(int argc, char **argv) { return 0; } } + if (fgetc(in) != EOF) puts("WA\nZły wynik\n0"); puts("OK\nBardzo dobrze\n100"); diff --git a/sio/executors/test/sources/rlechn.cpp b/sio/executors/test/sources/rlechn.cpp index c712d13..d16b7c0 100644 --- a/sio/executors/test/sources/rlechn.cpp +++ b/sio/executors/test/sources/rlechn.cpp @@ -29,6 +29,7 @@ int main(int argc, char **argv) { assert(fputc(ch, result) != EOF); } + if (fgetc(hint) != EOF) puts("WA\nZły wynik\n0"); assert(fputs("OK\n", checker) != EOF); diff --git a/sio/executors/test/sources/rleloopdec.cpp b/sio/executors/test/sources/rleloopdec.cpp new file mode 100644 index 0000000..773cfdd --- /dev/null +++ b/sio/executors/test/sources/rleloopdec.cpp @@ -0,0 +1,51 @@ +#include +#include +#include + +void enkoder() { + int last = '\n'; + int ch; + std::size_t count = 0; + + while ((ch = std::getchar()) != '\n') { + if (ch != last) { + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + count = 0; + } + + ++count; + last = ch; + } + + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + std::putchar('\n'); +} + +void dekoder() { + while (true) {} +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/sources/rleloopenc.cpp b/sio/executors/test/sources/rleloopenc.cpp new file mode 100644 index 0000000..638cfce --- /dev/null +++ b/sio/executors/test/sources/rleloopenc.cpp @@ -0,0 +1,27 @@ +#include +#include +#include + +void enkoder() { + while (true) {} +} + +void dekoder() { +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/sources/rlememenc.cpp b/sio/executors/test/sources/rlememenc.cpp new file mode 100644 index 0000000..e028a8d --- /dev/null +++ b/sio/executors/test/sources/rlememenc.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +void enkoder() { + std::vector ps; + while (true) { + ps.push_back(std::malloc(1024 * 1024)); + } +} + +void dekoder() { + int ch; + + while ((ch = std::getchar()) != '\n') { + std::size_t count; + assert(std::scanf("%zu;", &count) == 1); + while (count--) std::putchar(ch); + } + + std::putchar('\n'); +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/test_encdec.py b/sio/executors/test/test_encdec.py index a22a5e7..daaac3c 100644 --- a/sio/executors/test/test_encdec.py +++ b/sio/executors/test/test_encdec.py @@ -4,14 +4,73 @@ from filetracker.client.dummy import DummyClient from sio.assertion_utils import ok_, eq_ from sio.compilers.job import run as run_compiler -from sio.executors.unsafe_exec import encdec_run +import sio.executors.unsafe_exec +from sio.testing_utils import str_to_bool from sio.workers import ft from sio.workers.util import TemporaryCwd, tempcwd + +# Stolen from a sample problem package I used to test the encdec +EXECUTORS = {'unsafe': sio.executors.unsafe_exec} +RLE_TESTS = ['0a', '1'] SOURCES = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'sources') +if str_to_bool(os.environ.get('TEST_SIO2JAIL', True)): + import sio.executors.sio2jail_exec + EXECUTORS['sio2jail'] = sio.executors.sio2jail_exec + + +def common_preparations(): + with TemporaryCwd(): + upload_files() + with TemporaryCwd('compile_chn'): + run_compiler({ + 'source_file': '/rlechn.cpp', + 'compiler': 'system-cpp', + 'out_file': '/rlechn.e' + }) + with TemporaryCwd('compile_chk'): + run_compiler({ + 'source_file': '/rlechk.cpp', + 'compiler': 'system-cpp', + 'out_file': '/rlechk.e' + }) + + +def compile_file(file): + with TemporaryCwd('compile_code'): + run_compiler({ + 'source_file': '/%s.cpp' % file, + 'compiler': 'system-cpp', + 'out_file': '/%s.e' % file, + }) + + +def in_(a, b, msg=None): + """Shorthand for 'assert a in b, "%r not in %r" % (a, b)""" + if a not in b: + raise AssertionError(msg or "%r not in %r" % (a, b)) + + +def make_run_env(file, test, new_settings=None): + result = { + 'chn_file': '/rlechn.e', + 'chk_file': '/rlechk.e', + 'exe_file': '/%s.e' % file, + 'hint_file': '/rle%s.hint' % test, + 'in_file': '/rle%s.in' % test, + 'encoder_memory_limit': '65536', + 'encoder_time_limit': 2000, + 'decoder_memory_limit': '65536', + 'decoder_time_limit': 2000, + } + if new_settings: + result.update(new_settings) + return result + + def not_in_(a, b, msg=None): """Shorthand for 'assert a not in b, "%r in %r" % (a, b)""" if a in b: @@ -24,6 +83,16 @@ def print_env(env): pprint(env) +def run_all_configurations(file, func, new_settings=None): + for execname, executor in EXECUTORS.items(): + for t in RLE_TESTS: + with TemporaryCwd('run_%s_%s' % (execname, t)): + print('Running test %s under executor %s' % (t, execname)) + renv = executor.encdec_run(make_run_env(file, t, new_settings(execname, t) if new_settings else None)) + print_env(renv) + func(execname, t, renv) + + def upload_files(): "Uploads all files from SOURCES to a newly created dummy filetracker" @@ -34,46 +103,36 @@ def upload_files(): def test_encdec_run(): - with TemporaryCwd(): - upload_files() - with TemporaryCwd('compile_code'): - run_compiler({ - 'source_file': '/rle.cpp', - 'compiler': 'system-cpp', - 'out_file': '/rle.e' - }) - with TemporaryCwd('compile_chn'): - run_compiler({ - 'source_file': '/rlechn.cpp', - 'compiler': 'system-cpp', - 'out_file': '/rlechn.e' - }) - with TemporaryCwd('compile_chk'): - run_compiler({ - 'source_file': '/rlechk.cpp', - 'compiler': 'system-cpp', - 'out_file': '/rlechk.e' - }) - # Stolen from a sample problem package I used to test the encdec - with TemporaryCwd('run_0a'): - renv = encdec_run({ - 'chn_file': '/rlechn.e', - 'chk_file': '/rlechk.e', - 'exe_file': '/rle.e', - 'hint_file': '/rle0a.hint', - 'in_file': '/rle0a.in' - }) - print_env(renv) - not_in_('failed_step', renv) - eq_(renv['checker_result_percentage'], 100.) - with TemporaryCwd('run_1'): - renv = encdec_run({ - 'chn_file': '/rlechn.e', - 'chk_file': '/rlechk.e', - 'exe_file': '/rle.e', - 'hint_file': '/rle1.hint', - 'in_file': '/rle1.in' - }) - print_env(renv) + common_preparations() + compile_file('rle') + def check(execname, t, renv): not_in_('failed_step', renv) eq_(renv['checker_result_percentage'], 100.) + run_all_configurations('rle', check) + + +def test_encdec_encoder_timeout(): + common_preparations() + compile_file('rleloopenc') + def check(execname, t, renv): + eq_(renv['failed_step'], 'encoder') + eq_(renv['encoder_result_code'], 'TLE') + run_all_configurations('rleloopenc', check) + + +def test_encdec_encoder_outofmem(): + common_preparations() + compile_file('rlememenc') + def check(execname, t, renv): + eq_(renv['failed_step'], 'encoder') + in_(renv['encoder_result_code'], ('MLE', 'RE')) + run_all_configurations('rlememenc', check) + + +def test_encdec_decoder_timeout(): + common_preparations() + compile_file('rleloopdec') + def check(execname, t, renv): + eq_(renv['failed_step'], 'decoder') + eq_(renv['decoder_result_code'], 'TLE') + run_all_configurations('rleloopdec', check) From 689c645835e5e50a23dbaac146ee886a8bd436e0 Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Mon, 29 May 2023 15:15:23 +0200 Subject: [PATCH 09/11] [TEMPFIX] Add -static to system-gcc because who cares about macOS --- sio/compilers/system_gcc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sio/compilers/system_gcc.py b/sio/compilers/system_gcc.py index 2fba963..bd9040a 100644 --- a/sio/compilers/system_gcc.py +++ b/sio/compilers/system_gcc.py @@ -28,13 +28,13 @@ def _make_cmdline(self, executor): class CCompiler(CStyleCompiler): compiler = 'gcc' # Without -static as there is no static compilation on Mac - options = ['-O2', '-s', '-lm'] + options = ['-static', '-O2', '-s', '-lm'] class CPPCompiler(CStyleCompiler): lang = 'cpp' compiler = 'g++' - options = ['-std=gnu++0x', '-O2', '-s', '-lm'] + options = ['-std=gnu++0x', '-static', '-O2', '-s', '-lm'] def run_gcc(environ): From 6656d71231913e79e633da252252c1fb7c2413ee Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Mon, 18 Dec 2023 14:40:47 +0100 Subject: [PATCH 10/11] Update sandbox references in sio.workers.executors from ancient, broken versions --- sio/workers/executors.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sio/workers/executors.py b/sio/workers/executors.py index 2b77a26..5668d40 100644 --- a/sio/workers/executors.py +++ b/sio/workers/executors.py @@ -604,7 +604,7 @@ class Sio2JailExecutor(SandboxExecutor): REAL_TIME_LIMIT_ADDEND = 1000 # (in ms) def __init__(self): - super(Sio2JailExecutor, self).__init__('sio2jail_exec-sandbox') + super(Sio2JailExecutor, self).__init__('sio2jail_exec-sandbox-1.4.4') def _execute(self, command, **kwargs): options = [] @@ -640,6 +640,7 @@ def _execute(self, command, **kwargs): try: result_file = tempfile.NamedTemporaryFile(dir=tempcwd()) kwargs['ignore_errors'] = True + print(command) renv = execute_command( command + [noquote('2>'), result_file.name], **kwargs ) @@ -781,7 +782,7 @@ class PRootExecutor(BaseExecutor): def __init__(self, sandbox): """``sandbox`` has to be a sandbox name.""" self.chroot = get_sandbox(sandbox) - self.proot = SandboxExecutor('proot-sandbox') + self.proot = SandboxExecutor('proot-sandbox_amd64') self.options = [] with self.chroot: From f8392771a29746e48f37dabc5c2e57a3e1c9427a Mon Sep 17 00:00:00 2001 From: Jakub Kaszycki Date: Mon, 18 Dec 2023 14:43:50 +0100 Subject: [PATCH 11/11] Improve enc/dec test suite, most notably test on sio2jail (it was fixed) --- sio/executors/test/sources/rlecrashdec.cpp | 50 +++++++++++++++++++ sio/executors/test/sources/rlecrashenc.cpp | 35 ++++++++++++++ sio/executors/test/sources/rlememdec.cpp | 56 ++++++++++++++++++++++ sio/executors/test/test_encdec.py | 44 +++++++++++++---- 4 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 sio/executors/test/sources/rlecrashdec.cpp create mode 100644 sio/executors/test/sources/rlecrashenc.cpp create mode 100644 sio/executors/test/sources/rlememdec.cpp diff --git a/sio/executors/test/sources/rlecrashdec.cpp b/sio/executors/test/sources/rlecrashdec.cpp new file mode 100644 index 0000000..8c34642 --- /dev/null +++ b/sio/executors/test/sources/rlecrashdec.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +void enkoder() { + int last = '\n'; + int ch; + std::size_t count = 0; + + while ((ch = std::getchar()) != '\n') { + if (ch != last) { + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + count = 0; + } + + ++count; + last = ch; + } + + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + std::putchar('\n'); +} + +void dekoder() { std::abort(); } + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/sources/rlecrashenc.cpp b/sio/executors/test/sources/rlecrashenc.cpp new file mode 100644 index 0000000..1cf5620 --- /dev/null +++ b/sio/executors/test/sources/rlecrashenc.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +void enkoder() { std::abort(); } + +void dekoder() { + int ch; + + while ((ch = std::getchar()) != '\n') { + std::size_t count; + assert(std::scanf("%zu;", &count) == 1); + while (count--) std::putchar(ch); + } + + std::putchar('\n'); +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/sources/rlememdec.cpp b/sio/executors/test/sources/rlememdec.cpp new file mode 100644 index 0000000..c132d06 --- /dev/null +++ b/sio/executors/test/sources/rlememdec.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +void enkoder() { + int last = '\n'; + int ch; + std::size_t count = 0; + + while ((ch = std::getchar()) != '\n') { + if (ch != last) { + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + count = 0; + } + + ++count; + last = ch; + } + + if (last != '\n') { + std::putchar(last); + std::printf("%zu;", count); + } + + std::putchar('\n'); +} + +void dekoder() { + std::vector ps; + while (true) { + ps.push_back(std::malloc(1024 * 1024)); + } +} + +// rlelib.cpp +// +#include +#include + +extern void dekoder(); +extern void enkoder(); + +int main() { + int ch = std::getchar(); + + assert(ch == 'D' || ch == 'E'); + assert(std::getchar() == '\n'); + + (ch == 'D' ? dekoder : enkoder)(); +} diff --git a/sio/executors/test/test_encdec.py b/sio/executors/test/test_encdec.py index daaac3c..a8f046f 100644 --- a/sio/executors/test/test_encdec.py +++ b/sio/executors/test/test_encdec.py @@ -5,14 +5,18 @@ from sio.assertion_utils import ok_, eq_ from sio.compilers.job import run as run_compiler import sio.executors.unsafe_exec +import sio.executors.sio2jail_exec from sio.testing_utils import str_to_bool from sio.workers import ft from sio.workers.util import TemporaryCwd, tempcwd +EXECUTORS = { + 'sio2jail': sio.executors.sio2jail_exec, + 'unsafe': sio.executors.unsafe_exec +} # Stolen from a sample problem package I used to test the encdec -EXECUTORS = {'unsafe': sio.executors.unsafe_exec} RLE_TESTS = ['0a', '1'] SOURCES = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'sources') @@ -41,11 +45,13 @@ def common_preparations(): def compile_file(file): with TemporaryCwd('compile_code'): - run_compiler({ + renv = run_compiler({ 'source_file': '/%s.cpp' % file, 'compiler': 'system-cpp', 'out_file': '/%s.e' % file, }) + eq_(renv['result_code'], 'OK') + return renv def in_(a, b, msg=None): @@ -54,11 +60,12 @@ def in_(a, b, msg=None): raise AssertionError(msg or "%r not in %r" % (a, b)) -def make_run_env(file, test, new_settings=None): +def make_run_env(compile_renv, file, test, new_settings=None): result = { 'chn_file': '/rlechn.e', 'chk_file': '/rlechk.e', 'exe_file': '/%s.e' % file, + 'exec_info': compile_renv['exec_info'], 'hint_file': '/rle%s.hint' % test, 'in_file': '/rle%s.in' % test, 'encoder_memory_limit': '65536', @@ -84,11 +91,12 @@ def print_env(env): def run_all_configurations(file, func, new_settings=None): + compile_renv = compile_file(file) for execname, executor in EXECUTORS.items(): for t in RLE_TESTS: with TemporaryCwd('run_%s_%s' % (execname, t)): print('Running test %s under executor %s' % (t, execname)) - renv = executor.encdec_run(make_run_env(file, t, new_settings(execname, t) if new_settings else None)) + renv = executor.encdec_run(make_run_env(compile_renv, file, t, new_settings(execname, t) if new_settings else None)) print_env(renv) func(execname, t, renv) @@ -104,7 +112,6 @@ def upload_files(): def test_encdec_run(): common_preparations() - compile_file('rle') def check(execname, t, renv): not_in_('failed_step', renv) eq_(renv['checker_result_percentage'], 100.) @@ -113,7 +120,6 @@ def check(execname, t, renv): def test_encdec_encoder_timeout(): common_preparations() - compile_file('rleloopenc') def check(execname, t, renv): eq_(renv['failed_step'], 'encoder') eq_(renv['encoder_result_code'], 'TLE') @@ -122,17 +128,39 @@ def check(execname, t, renv): def test_encdec_encoder_outofmem(): common_preparations() - compile_file('rlememenc') def check(execname, t, renv): eq_(renv['failed_step'], 'encoder') in_(renv['encoder_result_code'], ('MLE', 'RE')) run_all_configurations('rlememenc', check) +def test_encdec_encoder_crash(): + common_preparations() + def check(execname, t, renv): + eq_(renv['failed_step'], 'encoder') + in_(renv['encoder_result_code'], ('RE')) + run_all_configurations('rlecrashenc', check) + + def test_encdec_decoder_timeout(): common_preparations() - compile_file('rleloopdec') def check(execname, t, renv): eq_(renv['failed_step'], 'decoder') eq_(renv['decoder_result_code'], 'TLE') run_all_configurations('rleloopdec', check) + + +def test_encdec_decoder_outofmem(): + common_preparations() + def check(execname, t, renv): + eq_(renv['failed_step'], 'decoder') + in_(renv['decoder_result_code'], ('MLE', 'RE')) + run_all_configurations('rlememdec', check) + + +def test_encdec_decoder_crash(): + common_preparations() + def check(execname, t, renv): + eq_(renv['failed_step'], 'decoder') + in_(renv['decoder_result_code'], ('RE')) + run_all_configurations('rlecrashdec', check)