Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
Arquivo: test.py Projeto: ryo-n/oj
 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))
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
Arquivo: test.py Projeto: ryo-n/oj
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
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
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