From f09dacbdc2ed8ded703bae4f8e49e3cd74e2ab1b Mon Sep 17 00:00:00 2001 From: Kimiyuki Onaka Date: Tue, 5 May 2020 22:39:02 +0900 Subject: [PATCH] Catch exceptions from list_attributes() and list_dependencies() see https://github.com/online-judge-tools/verification-helper/issues/197 --- onlinejudge_verify/docs.py | 43 ++++++++++++++++---------- onlinejudge_verify/languages/models.py | 17 ++++++++++ onlinejudge_verify/marker.py | 25 +++++++++++---- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/onlinejudge_verify/docs.py b/onlinejudge_verify/docs.py index f663c16c..81b58555 100644 --- a/onlinejudge_verify/docs.py +++ b/onlinejudge_verify/docs.py @@ -92,6 +92,7 @@ def __init__(self, file_path: pathlib.Path, source_path: pathlib.Path) -> None: self.file_path = file_path.resolve() self.source_path = source_path.resolve() self.parser = FileParser(file_path) + self.required = [] self.brief = self.parser.get_contents_by_tag(r'@brief') @@ -115,33 +116,41 @@ def __init__(self, file_path: pathlib.Path, source_path: pathlib.Path) -> None: else: self.category = category_list[-1] + # pathlib 型に直し、相対パスである場合は絶対パスに直す + docs_list = self.parser.get_contents_by_tag(r'@docs') + self.docs = [pathlib.Path(path) for path in docs_list] + self.docs = self.to_abspath(self.docs) + + # see で指定されるのは URL: パス修正は不要 + self.see = self.parser.get_contents_by_tag(r'(?:@see|@sa)', re_escape=False) + # language object を用意しておく language = onlinejudge_verify.languages.get(self.file_path) assert language is not None + + # language.list_dependencie() をもとに sself.depends を絶対パスの list として持つ try: - attributes = language.list_attributes(self.file_path, basedir=pathlib.Path.cwd()) + self.depends = language.list_dependencies(self.file_path, basedir=pathlib.Path.cwd()) except: + # 失敗したら中断 traceback.print_exc() - attributes = {} + self.depends = [] + self.verification_status = VerificationStatus.FAILED + return + self.depends.extend(map(pathlib.Path, self.parser.get_contents_by_tag(r'@depends'))) + self.depends = sorted(set(self.to_abspath(self.depends))) - # see で指定されるのは URL: パス修正は不要 - self.see = self.parser.get_contents_by_tag(r'(?:@see|@sa)', re_escape=False) + # language.list_attributes() をもとに PROBLEM のデータを取得 + try: + attributes = language.list_attributes(self.file_path, basedir=pathlib.Path.cwd()) + except: + # 失敗したら中断 + traceback.print_exc() + self.verification_status = VerificationStatus.FAILED + return if 'PROBLEM' in attributes: self.see.append(attributes['PROBLEM']) - # pathlib 型に直し、相対パスである場合は絶対パスに直す - docs_list = self.parser.get_contents_by_tag(r'@docs') - self.docs = [pathlib.Path(path) for path in docs_list] - self.docs = self.to_abspath(self.docs) - - # pathlib 型に直し、相対パスである場合は絶対パスに直す - depends_list = self.parser.get_contents_by_tag(r'@depends') - depends_list.extend(map(str, language.list_dependencies(self.file_path, basedir=pathlib.Path.cwd()))) - self.depends = [pathlib.Path(path) for path in depends_list] - self.depends = list(set(self.to_abspath(self.depends))) - self.depends.sort() - - self.required = [] # 表示するverification statusを決める is_verified = get_verification_marker().is_verified(self.file_path.relative_to(self.source_path)) is_failed = get_verification_marker().is_failed(self.file_path.relative_to(self.source_path)) diff --git a/onlinejudge_verify/languages/models.py b/onlinejudge_verify/languages/models.py index 5a301ff7..ab20bce4 100644 --- a/onlinejudge_verify/languages/models.py +++ b/onlinejudge_verify/languages/models.py @@ -9,6 +9,10 @@ class LanguageEnvironment(object): @abc.abstractmethod def compile(self, path: pathlib.Path, *, basedir: pathlib.Path, tempdir: pathlib.Path) -> None: + """ + :throws Exception: + """ + raise NotImplementedError @abc.abstractmethod @@ -18,14 +22,27 @@ def get_execute_command(self, path: pathlib.Path, *, basedir: pathlib.Path, temp class Language(object): def list_attributes(self, path: pathlib.Path, *, basedir: pathlib.Path) -> Dict[str, str]: + """ + :throws Exception: + """ + return list_special_comments(path) @abc.abstractmethod def list_dependencies(self, path: pathlib.Path, *, basedir: pathlib.Path) -> List[pathlib.Path]: + """ + :throws Exception: + """ + raise NotImplementedError @abc.abstractmethod def bundle(self, path: pathlib.Path, *, basedir: pathlib.Path) -> bytes: + """ + :throws Exception: + :throws NotImplementedError: + """ + raise NotImplementedError def is_verification_file(self, path: pathlib.Path, *, basedir: pathlib.Path) -> bool: diff --git a/onlinejudge_verify/marker.py b/onlinejudge_verify/marker.py index 56094ff9..5088c4f8 100644 --- a/onlinejudge_verify/marker.py +++ b/onlinejudge_verify/marker.py @@ -5,11 +5,14 @@ import json import pathlib import subprocess +import traceback from typing import * import onlinejudge_verify.languages import onlinejudge_verify.utils +_error_timestamp = datetime.datetime.fromtimestamp(0, tz=datetime.timezone(datetime.timedelta())) + class VerificationMarker(object): json_path: pathlib.Path @@ -30,9 +33,15 @@ def get_current_timestamp(self, path: pathlib.Path) -> datetime.datetime: else: language = onlinejudge_verify.languages.get(path) assert language is not None - timestamp = max([x.stat().st_mtime for x in language.list_dependencies(path, basedir=pathlib.Path.cwd())]) - system_local_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo - return datetime.datetime.fromtimestamp(timestamp, tz=system_local_timezone).replace(microsecond=0) # microsecond=0 is required because it's erased on timestamps.*.json + try: + depending_files = language.list_dependencies(path, basedir=pathlib.Path.cwd()) + except Exception: + traceback.print_exc() + return _error_timestamp + else: + timestamp = max([x.stat().st_mtime for x in depending_files]) + system_local_timezone = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo + return datetime.datetime.fromtimestamp(timestamp, tz=system_local_timezone).replace(microsecond=0) # microsecond=0 is required because it's erased on timestamps.*.json def is_verified(self, path: pathlib.Path) -> bool: path = path.resolve().relative_to(pathlib.Path.cwd()) @@ -70,7 +79,7 @@ def load_timestamps(self, *, jobs: Optional[int] = None) -> None: self.new_timestamps = {} def load(path, timestamp): - if path.exists() and self.get_current_timestamp(path) <= timestamp: + if path.exists() and _error_timestamp < self.get_current_timestamp(path) <= timestamp: self.mark_verified(path) return #「そもそもテストを実行していない」のか「実行した上で失敗した」のか区別できないが、verifyできてない事には変わりないので一旦はfailedとみなす @@ -125,11 +134,15 @@ def get_verification_marker(*, jobs: Optional[int] = None) -> VerificationMarker def _get_last_commit_time_to_verify(path: pathlib.Path) -> datetime.datetime: language = onlinejudge_verify.languages.get(path) assert language is not None - depending_files = language.list_dependencies(path, basedir=pathlib.Path.cwd()) + try: + depending_files = language.list_dependencies(path, basedir=pathlib.Path.cwd()) + except Exception: + traceback.print_exc() + return _error_timestamp code = ['git', 'log', '-1', '--date=iso', '--pretty=%ad', '--'] + list(map(str, depending_files)) timestamp = subprocess.check_output(code).decode().strip() if not timestamp: - return datetime.datetime.fromtimestamp(0, tz=datetime.timezone(datetime.timedelta())) + return _error_timestamp return datetime.datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S %z')