def try_hack_once(generator: str, command: str, hack: str, *, tle: Optional[float], attempt: int, lock: Optional[threading.Lock] = None, generated_input_hashes: Dict[bytes, str]) -> Optional[Tuple[bytes, bytes]]: with BufferedExecutor(lock) as submit: # print the header submit(logger.info, '') submit(logger.info, '%d-th attempt', attempt) # generate input submit(logger.info, 'generate input...') info, proc = utils.exec_command(generator, stdin=None, timeout=tle) input_data: Optional[bytes] = info['answer'] if not check_status(info, proc, submit=submit): return None assert input_data is not None # check the randomness of generator name = '{}-th attempt' conflicted_name = check_randomness_of_generator(input_data, name=name, lock=lock, generated_input_hashes=generated_input_hashes) if conflicted_name is not None: submit(logger.warning, 'The same input is already generated at %s. Please use a random input generator.', conflicted_name) submit(logger.info, utils.NO_HEADER + 'input:') submit(logger.info, utils.NO_HEADER + '%s', pretty_printers.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True)) # generate output submit(logger.info, 'generate output...') info, proc = utils.exec_command(command, input=input_data, timeout=tle) output_data: Optional[bytes] = info['answer'] if not check_status(info, proc, submit=submit): return None assert output_data is not None # hack submit(logger.info, 'hack...') info, proc = utils.exec_command(hack, input=input_data, timeout=tle) answer: str = (info['answer'] or b'').decode() # compare status = 'AC' if proc.returncode is None: submit(logger.info, 'FAILURE: ' + utils.red('TLE')) status = 'TLE' elif proc.returncode != 0: logger.info(utils.FAILURE + '' + utils.red('RE') + ': return code %d', proc.returncode) status = 'RE' expected = output_data.decode() if not simple_match(answer, expected): logger.info(utils.FAILURE + '' + utils.red('WA')) logger.info(utils.NO_HEADER + 'input:\n%s', pretty_printers.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True)) logger.info(utils.NO_HEADER + 'output:\n%s', pretty_printers.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) logger.info(utils.NO_HEADER + 'expected:\n%s', pretty_printers.make_pretty_large_file_content(output_data, limit=40, head=20, tail=10, bold=True)) status = 'WA' if status == 'AC': return None else: return (input_data, output_data)
def try_hack_once(generator: str, command: str, hack: str, *, tle: Optional[float], attempt: int, lock: Optional[threading.Lock] = None) -> Optional[Tuple[bytes, bytes]]: with BufferedExecutor(lock) as submit: # print the header submit(logger.info, '') submit(logger.info, '%d-th attempt', attempt) # generate input submit(logger.info, 'generate input...') info, proc = utils.exec_command(generator, stdin=None, timeout=tle) input_data: Optional[bytes] = info['answer'] if not check_status(info, proc, submit=submit): return None assert input_data is not None # generate output submit(logger.info, 'generate output...') info, proc = utils.exec_command(command, input=input_data, timeout=tle) output_data: Optional[bytes] = info['answer'] if not check_status(info, proc, submit=submit): return None assert output_data is not None # hack submit(logger.info, 'hack...') info, proc = utils.exec_command(hack, input=input_data, timeout=tle) answer: str = (info['answer'] or b'').decode() elapsed: float = info['elapsed'] memory: Optional[float] = info['memory'] # compare status = 'AC' if proc.returncode is None: submit(logger.info, 'FAILURE: ' + utils.red('TLE')) status = 'TLE' elif proc.returncode != 0: logger.info(utils.FAILURE + '' + utils.red('RE') + ': return code %d', proc.returncode) status = 'RE' expected = output_data.decode() if not simple_match(answer, expected): logger.info(utils.FAILURE + '' + utils.red('WA')) logger.info(utils.NO_HEADER + 'input:\n%s', pretty_printers.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True)) logger.info(utils.NO_HEADER + 'output:\n%s', pretty_printers.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) logger.info(utils.NO_HEADER + 'expected:\n%s', pretty_printers.make_pretty_large_file_content(output_data, limit=40, head=20, tail=10, bold=True)) status = 'WA' if status == 'AC': return None else: return (input_data, output_data)
def generate_output_single_case(test_name: str, test_input_path: pathlib.Path, *, lock: Optional[threading.Lock] = None, args: argparse.Namespace) -> None: # print the header if lock is None: logger.info('') logger.info('%s', test_name) # run the command with test_input_path.open('rb') as inf: info, proc = utils.exec_command(args.command, stdin=inf, timeout=args.tle) answer: Optional[bytes] = info['answer'] elapsed: float = info['elapsed'] # acquire lock to print logs properly, if in parallel nullcontext = contextlib.ExitStack() with lock or nullcontext: if lock is not None: logger.info('') logger.info('%s', test_name) # check the result logger.info('time: %f sec', elapsed) if proc.returncode is None: logger.info(utils.red('TLE')) logger.info('skipped.') return elif proc.returncode != 0: logger.info('FIALURE: ' + utils.red('RE') + ': return code %d', proc.returncode) logger.info('skipped.') return assert answer is not None logger.info(utils.NO_HEADER + '' + pretty_printers.make_pretty_large_file_content( answer, limit=40, head=20, tail=10, bold=True)) # find the destination path match_result: Optional[Match[Any]] = fmtutils.match_with_format( args.directory, args.format, test_input_path) if match_result is not None: matched_name: str = match_result.groupdict()['name'] else: assert False test_output_path = fmtutils.path_from_format(args.directory, args.format, name=matched_name, ext='out') # write the result to the file if not test_output_path.parent.is_dir(): os.makedirs(str(test_output_path.parent), exist_ok=True) with test_output_path.open('wb') as fh: fh.write(answer) logger.info(utils.SUCCESS + 'saved to: %s', test_output_path)
def run(self, *, actual_output: bytes, input_path: pathlib.Path, expected_output_path: Optional[pathlib.Path]) -> bool: with tempfile.TemporaryDirectory() as tempdir: actual_output_path = pathlib.Path(tempdir) / 'actual.out' with open(actual_output_path, 'wb') as fh: fh.write(actual_output) # if you use shlex.quote, it fails on Windows. why? command = ' '.join([ self.judge_command, # already quoted and joined command str(input_path.resolve()), str(actual_output_path.resolve()), str(expected_output_path.resolve( ) if expected_output_path is not None else ''), ]) logger.info('$ %s', command) info, proc = utils.exec_command(command) if not self.is_silent: logger.info( utils.NO_HEADER + 'judge\'s output:\n%s', pretty_printers.make_pretty_large_file_content(info['answer'] or b'', limit=40, head=20, tail=10, bold=True)) return proc.returncode == 0
def print_input() -> None: nonlocal is_input_printed if does_print_input and not is_input_printed: is_input_printed = True with test_input_path.open('rb') as inf: logger.info( utils.NO_HEADER + 'input:\n%s', pretty_printers.make_pretty_large_file_content(inf.read(), limit=40, head=20, tail=10, bold=True))
def write_result(input_data: bytes, output_data: Optional[bytes], *, input_path: pathlib.Path, output_path: pathlib.Path, print_data: bool, lock: Optional[threading.Lock] = None) -> None: # acquire lock to print logs properly, if in parallel nullcontext = contextlib.ExitStack( ) # TODO: use contextlib.nullcontext after Python 3.7 with lock or nullcontext: if not input_path.parent.is_dir(): os.makedirs(str(input_path.parent), exist_ok=True) if print_data: logger.info(utils.NO_HEADER + 'input:') logger.info( utils.NO_HEADER + '%s', pretty_printers.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True)) with input_path.open('wb') as fh: fh.write(input_data) logger.info(utils.SUCCESS + 'saved to: %s', input_path) if output_data is not None: if print_data: logger.info(utils.NO_HEADER + 'output:') logger.info( pretty_printers.make_pretty_large_file_content(output_data, limit=40, head=20, tail=10, bold=True)) with output_path.open('wb') as fh: fh.write(output_data) logger.info(utils.SUCCESS + 'saved to: %s', output_path)
def display_result(proc: subprocess.Popen, answer: str, memory: Optional[float], test_input_path: pathlib.Path, test_output_path: Optional[pathlib.Path], *, mle: Optional[float], display_mode: DisplayMode, does_print_input: bool, silent: bool, match_result: Optional[bool]) -> JudgeStatus: """display_result prints the result of the test and its statistics. This function prints many logs and does some I/O. """ # prepare the function to print the input is_input_printed = False def print_input() -> None: nonlocal is_input_printed if does_print_input and not is_input_printed: is_input_printed = True with test_input_path.open('rb') as inf: logger.info( utils.NO_HEADER + 'input:\n%s', pretty_printers.make_pretty_large_file_content(inf.read(), limit=40, head=20, tail=10, bold=True)) # check TLE, RE or not status = JudgeStatus.AC if proc.returncode is None: logger.info(utils.FAILURE + '' + utils.red('TLE')) status = JudgeStatus.TLE if not silent: print_input() elif memory is not None and mle is not None and memory > mle: logger.info(utils.FAILURE + '' + utils.red('MLE')) status = JudgeStatus.MLE if not silent: print_input() elif proc.returncode != 0: logger.info(utils.FAILURE + '' + utils.red('RE') + ': return code %d', proc.returncode) status = JudgeStatus.RE if not silent: print_input() # check WA or not if match_result is not None and not match_result: if status == JudgeStatus.AC: logger.info(utils.FAILURE + '' + utils.red('WA')) status = JudgeStatus.WA if not silent: print_input() if test_output_path is not None: with test_output_path.open('rb') as outf: expected = outf.read().decode() else: expected = '' if display_mode == DisplayMode.SUMMARY: logger.info( utils.NO_HEADER + 'output:\n%s', pretty_printers.make_pretty_large_file_content( answer.encode(), limit=40, head=20, tail=10, bold=True)) logger.info( utils.NO_HEADER + 'expected:\n%s', pretty_printers.make_pretty_large_file_content( expected.encode(), limit=40, head=20, tail=10, bold=True)) elif display_mode == DisplayMode.DIFF: if max(answer.count('\n'), expected.count('\n')) <= 40: pretty_printers.display_side_by_side_color( answer, expected) else: pretty_printers.display_snipped_side_by_side_color( answer, expected) else: assert False if match_result is None: if not silent: print_input() logger.info( utils.NO_HEADER + 'output:\n%s', pretty_printers.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) if status == JudgeStatus.AC: logger.info(utils.SUCCESS + '' + utils.green('AC')) return status
def download(args: argparse.Namespace) -> None: # prepare values problem = dispatch.problem_from_url(args.url) if problem is None: if dispatch.contest_from_url(args.url) is not None: logger.warning('You specified a URL for a contest instead of a problem. If you want to download for all problems of a contest at once, please try to use `oj-prepare` command of https://github.com/online-judge-tools/template-generator') raise requests.exceptions.InvalidURL('The URL "%s" is not supported' % args.url) is_default_format = args.format is None and args.directory is None # must be here since args.directory and args.format are overwritten if args.directory is None: args.directory = pathlib.Path('test') if args.format is None: args.format = '%b.%e' # get samples from the server with utils.new_session_with_our_user_agent(path=args.cookie) as sess: if args.yukicoder_token and isinstance(problem, YukicoderProblem): sess.headers['Authorization'] = 'Bearer {}'.format(args.yukicoder_token) if args.system: samples = problem.download_system_cases(session=sess) else: samples = problem.download_sample_cases(session=sess) if not samples: raise SampleParseError("Sample not found") # append the history for submit subcommand if not args.dry_run and is_default_format: history = onlinejudge_command.download_history.DownloadHistory() if not list(args.directory.glob('*')): # reset the history to help users who use only one directory for many problems history.remove(directory=pathlib.Path.cwd()) history.add(problem, directory=pathlib.Path.cwd()) # prepare files to write def iterate_files_to_write(sample: TestCase, *, i: int) -> Iterator[Tuple[str, pathlib.Path, bytes]]: for ext in ['in', 'out']: data = getattr(sample, ext + 'put_data') if data is None: continue name = sample.name table = {} table['i'] = str(i + 1) table['e'] = ext table['n'] = name table['b'] = os.path.basename(name) table['d'] = os.path.dirname(name) path: pathlib.Path = args.directory / format_utils.percentformat(args.format, table) yield ext, path, data for i, sample in enumerate(samples): for _, path, _ in iterate_files_to_write(sample, i=i): if path.exists(): raise FileExistsError('Failed to download since file already exists: ' + str(path)) # write samples to files for i, sample in enumerate(samples): logger.info('') logger.info('sample %d', i) for ext, path, data in iterate_files_to_write(sample, i=i): content = '' if not args.silent: content = '\n' + pretty_printers.make_pretty_large_file_content(data, limit=40, head=20, tail=10, bold=True) logger.info('%sput: %s%s', ext, sample.name, content) if not args.dry_run: path.parent.mkdir(parents=True, exist_ok=True) with path.open('wb') as fh: fh.write(data) logger.info(utils.SUCCESS + 'saved to: %s', path) if args.log_file: with args.log_file.open(mode='w') as fhs: json.dump(list(map(convert_sample_to_dict, samples)), fhs)
def run(args: argparse.Namespace) -> bool: """ :returns: whether the submission is succeeded or not. """ # guess url history = onlinejudge_command.download_history.DownloadHistory() if args.file.parent.resolve() == pathlib.Path.cwd(): guessed_urls = history.get(directory=pathlib.Path.cwd()) else: logger.warning('cannot guess URL since the given file is not in the current directory') guessed_urls = [] if args.url is None: if len(guessed_urls) == 1: args.url = guessed_urls[0] logger.info('guessed problem: %s', args.url) else: logger.error('failed to guess the URL to submit') logger.info('please manually specify URL as: $ oj submit URL FILE') return False # parse url problem = dispatch.problem_from_url(args.url) if problem is None: return False # read code with args.file.open('rb') as fh: code: bytes = fh.read() # report code logger.info('code (%d byte):', len(code)) logger.info(utils.NO_HEADER + '%s', pretty_printers.make_pretty_large_file_content(code, limit=30, head=10, tail=10, bold=True)) with utils.new_session_with_our_user_agent(path=args.cookie) as sess: # check the login status try: is_logged_in = problem.get_service().is_logged_in(session=sess) except Exception as e: logger.exception('failed to check the login status: %s', e) return False else: if is_logged_in: logger.info('You are logged in.') else: logger.error('You are not logged in. Please run $ oj login %s', problem.get_url()) return False # guess or select language ids language_dict: Dict[LanguageId, str] = {language.id: language.name for language in problem.get_available_languages(session=sess)} matched_lang_ids: Optional[List[str]] = None if args.language in language_dict: matched_lang_ids = [args.language] else: if args.guess: kwargs = { 'language_dict': language_dict, 'cxx_latest': args.guess_cxx_latest, 'cxx_compiler': args.guess_cxx_compiler, 'python_version': args.guess_python_version, 'python_interpreter': args.guess_python_interpreter, } matched_lang_ids = guess_lang_ids_of_file(args.file, code, **kwargs) if not matched_lang_ids: logger.info('failed to guess languages from the file name') matched_lang_ids = list(language_dict.keys()) if args.language is not None: logger.info('you can use `--no-guess` option if you want to do an unusual submission') matched_lang_ids = select_ids_of_matched_languages(args.language.split(), matched_lang_ids, language_dict=language_dict) else: if args.language is None: matched_lang_ids = None else: matched_lang_ids = select_ids_of_matched_languages(args.language.split(), list(language_dict.keys()), language_dict=language_dict) # report selected language ids if matched_lang_ids is not None and len(matched_lang_ids) == 1: args.language = matched_lang_ids[0] logger.info('chosen language: %s (%s)', args.language, language_dict[LanguageId(args.language)]) else: if matched_lang_ids is None: logger.error('language is unknown') logger.info('supported languages are:') elif len(matched_lang_ids) == 0: logger.error('no languages are matched') logger.info('supported languages are:') else: logger.error('Matched languages were not narrowed down to one.') logger.info('You have to choose:') for lang_id in sorted(matched_lang_ids or language_dict.keys()): logger.info(utils.NO_HEADER + '%s (%s)', lang_id, language_dict[LanguageId(lang_id)]) return False # confirm guessed_unmatch = ([problem.get_url()] != guessed_urls) if guessed_unmatch: samples_text = ('samples of "{}'.format('", "'.join(guessed_urls)) if guessed_urls else 'no samples') logger.warning('the problem "%s" is specified to submit, but %s were downloaded in this directory. this may be mis-operation', problem.get_url(), samples_text) if args.wait: logger.info('sleep(%.2f)', args.wait) time.sleep(args.wait) if not args.yes: if guessed_unmatch: problem_id = problem.get_url().rstrip('/').split('/')[-1].split('?')[-1] # this is too ad-hoc key = problem_id[:3] + (problem_id[-1] if len(problem_id) >= 4 else '') sys.stdout.write('Are you sure? Please type "{}" '.format(key)) sys.stdout.flush() c = sys.stdin.readline().rstrip() if c != key: logger.info('terminated.') return False else: sys.stdout.write('Are you sure? [y/N] ') sys.stdout.flush() c = sys.stdin.read(1) if c.lower() != 'y': logger.info('terminated.') return False # submit try: submission = problem.submit_code(code, language_id=LanguageId(args.language), session=sess) except NotLoggedInError: logger.info(utils.FAILURE + 'login required') return False except SubmissionError: logger.info(utils.FAILURE + 'submission failed') return False # show result if args.open: utils.webbrowser_register_explorer_exe() try: browser = webbrowser.get() except webbrowser.Error as e: logger.error('%s', e) logger.info('please set the $BROWSER envvar') else: logger.info('open the submission page with browser: %s', browser) browser.open_new_tab(submission.get_url()) return True