Skip to content

Commit

Permalink
submitの際のコマンド実行を追加 (#234)
Browse files Browse the repository at this point in the history
* submitの際のコマンド実行を追加

* submit_filenameがない場合はFalseを返すように変更

* test_indent_estimationをaddしわすれていた

* コミットするブランチを間違えた

* submit.pyを修正

* USER_CONFIG_PATHを読み込んだログを出力するように

* exec_on_submitのテストを追加

* テストを追加

* test_exec_on_submitにおいてtemp_dirで実行するように変更
  • Loading branch information
chaemon authored Sep 28, 2021
1 parent 949b695 commit da56d3b
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 22 deletions.
25 changes: 21 additions & 4 deletions atcodertools/config/config.py
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions atcodertools/config/submit_config.py
Original file line number Diff line number Diff line change
@@ -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
79 changes: 66 additions & 13 deletions atcodertools/tools/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -67,25 +70,63 @@ 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:
logger.error(
"{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:
Expand All @@ -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 "
Expand All @@ -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:
Expand All @@ -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

Expand Down
6 changes: 1 addition & 5 deletions atcodertools/tools/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
touch exec_after_submit_is_completed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
echo Kyuuridenamida > exec_before_submit_is_completed
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include<iostream>

using namespace std;

int main(){
return 0;
}
Original file line number Diff line number Diff line change
@@ -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"
}
40 changes: 40 additions & 0 deletions tests/test_atcoder_client_mock.py
Original file line number Diff line number Diff line change
@@ -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__)),
Expand Down Expand Up @@ -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()

0 comments on commit da56d3b

Please sign in to comment.