From da56d3b1198c77ec80b474853beabad32520d0f6 Mon Sep 17 00:00:00 2001 From: chaemon Date: Wed, 29 Sep 2021 03:44:57 +0900 Subject: [PATCH] =?UTF-8?q?submit=E3=81=AE=E9=9A=9B=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=83=89=E5=AE=9F=E8=A1=8C=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=20(#234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * submitの際のコマンド実行を追加 * submit_filenameがない場合はFalseを返すように変更 * test_indent_estimationをaddしわすれていた * コミットするブランチを間違えた * submit.pyを修正 * USER_CONFIG_PATHを読み込んだログを出力するように * exec_on_submitのテストを追加 * テストを追加 * test_exec_on_submitにおいてtemp_dirで実行するように変更 --- atcodertools/config/config.py | 25 +++++- atcodertools/config/submit_config.py | 10 +++ atcodertools/tools/submit.py | 79 ++++++++++++++++--- atcodertools/tools/tester.py | 6 +- .../exec_on_submit/config.toml | 4 + .../exec_on_submit/exec_after_submit.sh | 1 + .../exec_on_submit/exec_before_submit.sh | 1 + .../exec_on_submit/main.cpp | 7 ++ .../exec_on_submit/metadata.json | 16 ++++ tests/test_atcoder_client_mock.py | 40 ++++++++++ 10 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 atcodertools/config/submit_config.py create mode 100644 tests/resources/test_atcoder_client_mock/exec_on_submit/config.toml create mode 100755 tests/resources/test_atcoder_client_mock/exec_on_submit/exec_after_submit.sh create mode 100755 tests/resources/test_atcoder_client_mock/exec_on_submit/exec_before_submit.sh create mode 100644 tests/resources/test_atcoder_client_mock/exec_on_submit/main.cpp create mode 100644 tests/resources/test_atcoder_client_mock/exec_on_submit/metadata.json diff --git a/atcodertools/config/config.py b/atcodertools/config/config.py index 4e20d3de..cd68c12c 100644 --- a/atcodertools/config/config.py +++ b/atcodertools/config/config.py @@ -1,20 +1,27 @@ from argparse import Namespace -from typing import TextIO, Dict, Any, Optional +from typing import TextIO, Dict, Any, Set, Optional from enum import Enum +import os +from os.path import expanduser import toml +from atcodertools.config.compiler_config import CompilerConfig from atcodertools.codegen.code_style_config import CodeStyleConfig from atcodertools.config.etc_config import EtcConfig from atcodertools.config.postprocess_config import PostprocessConfig from atcodertools.config.tester_config import TesterConfig -from atcodertools.config.compiler_config import CompilerConfig +from atcodertools.config.submit_config import SubmitConfig + + +USER_CONFIG_PATH = os.path.join(expanduser("~"), ".atcodertools.toml") class ConfigType(Enum): CODESTYLE = "codestyle" POSTPROCESS = "postprocess" TESTER = "tester" + SUBMIT = "submit" ETC = "etc" COMPILER = "compiler" @@ -46,16 +53,17 @@ def __init__(self, code_style_config: CodeStyleConfig = CodeStyleConfig(), postprocess_config: PostprocessConfig = PostprocessConfig(), tester_config: TesterConfig = TesterConfig(), + submit_config: SubmitConfig = SubmitConfig(), etc_config: EtcConfig = EtcConfig(), - compiler_config: CompilerConfig = CompilerConfig() ): self.code_style_config = code_style_config self.postprocess_config = postprocess_config self.tester_config = tester_config + self.submit_config = submit_config self.etc_config = etc_config @classmethod - def load(cls, fp: TextIO, get_config_type, args: Optional[Namespace] = None, lang=None): + def load(cls, fp: TextIO, get_config_type: Set[ConfigType], args: Optional[Namespace] = None, lang=None): """ :param fp: .toml file's file pointer :param args: command line arguments @@ -94,6 +102,15 @@ def load(cls, fp: TextIO, get_config_type, args: Optional[Namespace] = None, lan compile_only_when_diff_detected=args.compile_only_when_diff_detected, compile_command=args.compile_command)) result.tester_config = TesterConfig(**tester_config_dic) + if ConfigType.SUBMIT in get_config_type: + submit_config_dic = get_config_dic( + config_dic, ConfigType.SUBMIT, lang) + if args: + submit_config_dic = _update_config_dict(submit_config_dic, + dict(exec_before_submit=args.exec_before_submit, + exec_after_submit=args.exec_after_submit, + submit_filename=args.submit_filename)) + result.submit_config = SubmitConfig(**submit_config_dic) if ConfigType.ETC in get_config_type: etc_config_dic = get_config_dic(config_dic, ConfigType.ETC) if args: diff --git a/atcodertools/config/submit_config.py b/atcodertools/config/submit_config.py new file mode 100644 index 00000000..908e635f --- /dev/null +++ b/atcodertools/config/submit_config.py @@ -0,0 +1,10 @@ +class SubmitConfig: + + def __init__(self, + exec_before_submit: str = None, + exec_after_submit: str = None, + submit_filename: str = None + ): + self.exec_before_submit = exec_before_submit + self.exec_after_submit = exec_after_submit + self.submit_filename = submit_filename diff --git a/atcodertools/tools/submit.py b/atcodertools/tools/submit.py index 03901a61..9729001c 100755 --- a/atcodertools/tools/submit.py +++ b/atcodertools/tools/submit.py @@ -11,11 +11,14 @@ from atcodertools.client.atcoder import AtCoderClient, LoginError from atcodertools.tools import tester from atcodertools.common.logging import logger +from atcodertools.config.config import Config, ConfigType, USER_CONFIG_PATH from atcodertools.tools.models.metadata import Metadata +from atcodertools.tools import get_default_config_path +from atcodertools.executils.run_command import run_command -def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> bool: +def main(prog, args, credential_supplier=None, use_local_session_cache=True, client=None) -> bool: parser = argparse.ArgumentParser( prog=prog, formatter_class=argparse.RawTextHelpFormatter) @@ -67,9 +70,44 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> type=float, default=None) - args = parser.parse_args(args) + parser.add_argument('--exec-before-submit', + help='exec command before submit:' + ' [Default] None', + type=str, + default=None) + + parser.add_argument('--exec-after-submit', + help='run command after submit:' + ' [Default] None', + type=str, + default=None) + + parser.add_argument('--submit-filename', + help='file for submit will changed to this name:' + ' [Default] None', + type=str, + default=None) + parser.add_argument("--config", + help="File path to your config file\n{0}{1}".format("[Default (Primary)] {}\n".format( + USER_CONFIG_PATH), + "[Default (Secondary)] {}\n".format( + get_default_config_path())), + type=str, + default=None) + + args = parser.parse_args(args) + if args.config is None: + if os.path.exists(USER_CONFIG_PATH): + args.config = USER_CONFIG_PATH + logger.info( + f"config is loaded from USER_CONFIG_PATH({USER_CONFIG_PATH})") + else: + args.config = get_default_config_path() + logger.info( + f"No USER_CONFIG_PATH({USER_CONFIG_PATH}). Default config path({args.config}) is laoded. ") metadata_file = os.path.join(args.dir, "metadata.json") + try: metadata = Metadata.load_from(metadata_file) except IOError: @@ -77,15 +115,18 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> "{0} is not found! You need {0} to use this submission functionality.".format(metadata_file)) return False - try: - client = AtCoderClient() - client.login(save_session_cache=not args.save_no_session_cache, - credential_supplier=credential_supplier, - use_local_session_cache=use_local_session_cache, - ) - except LoginError: - logger.error("Login failed. Try again.") - return False + with open(args.config, "r") as f: + config = Config.load(f, {ConfigType.SUBMIT}, args, metadata.lang.name) + if client is None: + try: + client = AtCoderClient() + client.login(save_session_cache=not args.save_no_session_cache, + credential_supplier=credential_supplier, + use_local_session_cache=use_local_session_cache, + ) + except LoginError: + logger.error("Login failed. Try again.") + return False tester_args = [] if args.exec: @@ -100,8 +141,9 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> tester_args += ["-v", str(args.error_value)] if args.force or tester.main("", tester_args): - submissions = client.download_submission_list(metadata.problem.contest) if not args.unlock_safety: + submissions = client.download_submission_list( + metadata.problem.contest) for submission in submissions: if submission.problem_id == metadata.problem.problem_id: logger.error(with_color("Cancel submitting because you already sent some code to the problem. Please " @@ -110,9 +152,18 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> return False code_path = args.code or os.path.join(args.dir, metadata.code_filename) + + if config.submit_config.exec_before_submit: + run_command(config.submit_config.exec_before_submit, args.dir) + if not config.submit_config.submit_filename: + logger.error("submit_filename is not specified") + return False + code_path = config.submit_config.submit_filename + logger.info(f"changed to submitfile: {code_path}") + for encoding in ['utf8', 'utf-8_sig', 'cp932']: try: - with open(code_path, 'r', encoding=encoding) as f: + with open(os.path.join(args.dir, code_path), 'r', encoding=encoding) as f: source = f.read() break except UnicodeDecodeError: @@ -124,6 +175,8 @@ def main(prog, args, credential_supplier=None, use_local_session_cache=True) -> logger.info("{} {}".format( with_color("Done!", Fore.LIGHTGREEN_EX), metadata.problem.contest.get_submissions_url(submission))) + if config.submit_config.exec_after_submit: + run_command(config.submit_config.exec_after_submit, args.dir) return True diff --git a/atcodertools/tools/tester.py b/atcodertools/tools/tester.py index d75d2912..33b86338 100755 --- a/atcodertools/tools/tester.py +++ b/atcodertools/tools/tester.py @@ -7,7 +7,6 @@ import sys from pathlib import Path from typing import List, Tuple, Optional -from os.path import expanduser from colorama import Fore @@ -19,15 +18,12 @@ from atcodertools.tools.models.metadata import Metadata, DEFAULT_METADATA from atcodertools.tools.utils import with_color from atcodertools.tools.compiler import compile_main_and_judge_programs, BadStatusCodeException -from atcodertools.config.config import Config, ConfigType +from atcodertools.config.config import Config, ConfigType, USER_CONFIG_PATH from atcodertools.tools import get_default_config_path DEFAULT_EPS = 0.000000001 -USER_CONFIG_PATH = os.path.join(expanduser("~"), ".atcodertools.toml") - - class NoExecutableFileError(Exception): pass diff --git a/tests/resources/test_atcoder_client_mock/exec_on_submit/config.toml b/tests/resources/test_atcoder_client_mock/exec_on_submit/config.toml new file mode 100644 index 00000000..06b8a456 --- /dev/null +++ b/tests/resources/test_atcoder_client_mock/exec_on_submit/config.toml @@ -0,0 +1,4 @@ +[submit] +exec_before_submit='bash exec_before_submit.sh' +submit_filename='exec_before_submit_is_completed' +exec_after_submit='bash exec_after_submit.sh' diff --git a/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_after_submit.sh b/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_after_submit.sh new file mode 100755 index 00000000..fb9b1bb2 --- /dev/null +++ b/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_after_submit.sh @@ -0,0 +1 @@ +touch exec_after_submit_is_completed diff --git a/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_before_submit.sh b/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_before_submit.sh new file mode 100755 index 00000000..f0f8b123 --- /dev/null +++ b/tests/resources/test_atcoder_client_mock/exec_on_submit/exec_before_submit.sh @@ -0,0 +1 @@ +echo Kyuuridenamida > exec_before_submit_is_completed diff --git a/tests/resources/test_atcoder_client_mock/exec_on_submit/main.cpp b/tests/resources/test_atcoder_client_mock/exec_on_submit/main.cpp new file mode 100644 index 00000000..289c3d98 --- /dev/null +++ b/tests/resources/test_atcoder_client_mock/exec_on_submit/main.cpp @@ -0,0 +1,7 @@ +#include + +using namespace std; + +int main(){ + return 0; +} diff --git a/tests/resources/test_atcoder_client_mock/exec_on_submit/metadata.json b/tests/resources/test_atcoder_client_mock/exec_on_submit/metadata.json new file mode 100644 index 00000000..8c6bf035 --- /dev/null +++ b/tests/resources/test_atcoder_client_mock/exec_on_submit/metadata.json @@ -0,0 +1,16 @@ +{ + "code_filename": "main.cpp", + "judge": { + "judge_type": "normal" + }, + "lang": "cpp", + "problem": { + "alphabet": "A", + "contest": { + "contest_id": "abc215" + }, + "problem_id": "abc215_a" + }, + "sample_in_pattern": "in_*.txt", + "sample_out_pattern": "out_*.txt" +} diff --git a/tests/test_atcoder_client_mock.py b/tests/test_atcoder_client_mock.py index b96f24bf..57e10fa3 100644 --- a/tests/test_atcoder_client_mock.py +++ b/tests/test_atcoder_client_mock.py @@ -1,12 +1,14 @@ import os import tempfile import unittest +import shutil from typing import Dict from atcodertools.client.atcoder import AtCoderClient from atcodertools.client.models.contest import Contest from atcodertools.client.models.problem import Problem from atcodertools.common.language import CPP +from atcodertools.tools import submit RESOURCE_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), @@ -119,6 +121,44 @@ def test_check_logging_in_fail(self): ) self.assertFalse(self.client.check_logging_in()) + @restore_client_after_run + def test_exec_on_submit(self): + global submitted_source_code + submitted_source_code = None + + def create_fake_request_func_for_source(get_url_to_resp: Dict[str, MockResponse] = None, + post_url_to_resp: Dict[str, + MockResponse] = None, + ): + global submitted_source_code + + def func(url, method="GET", **kwargs): + global submitted_source_code + if method == "GET": + return get_url_to_resp.get(url) + submitted_source_code = kwargs["data"]['sourceCode'] + return post_url_to_resp.get(url) + return func + + contest = Contest("abc215") + + self.client._request = create_fake_request_func_for_source( + {contest.get_submit_url(): fake_resp("submit/after_get.html")}, + {contest.get_submit_url(): fake_resp("submit/after_post.html")} + ) + + test_dir = os.path.join(self.temp_dir, "exec_on_submit") + shutil.copytree(os.path.join(RESOURCE_DIR, "exec_on_submit"), test_dir) + config_path = os.path.join(test_dir, "config.toml") + + submit.main('', ["--dir", test_dir, "--config", + config_path, "-f", "-u"], client=self.client) + self.assertTrue(os.path.exists(os.path.join( + test_dir, "exec_before_submit_is_completed"))) + self.assertEqual(submitted_source_code, "Kyuuridenamida\n") + self.assertTrue(os.path.exists(os.path.join( + test_dir, "exec_after_submit_is_completed"))) + if __name__ == "__main__": unittest.main()