コード例 #1
0
ファイル: test.py プロジェクト: rsk0315/oj
def display_snipped_side_by_side_color(answer: str, expected: str):
    """
    Display first differ line and its previous 3 lines and its next 3 lines.
    """
    max_chars = shutil.get_terminal_size()[0] // 2 - 2
    deq = collections.deque(
        maxlen=7
    )  # type: Deque[Tuple[Optional[int], bool, str, str, int, int]]

    count_from_first_difference = 0
    i = 0
    for flag, diff_found, ans_line, exp_line, ans_chars, exp_chars in side_by_side_diff(
            answer, expected):
        if flag: i += 1
        if count_from_first_difference > 0:
            count_from_first_difference += 1
        line_num = i if flag else None
        deq.append(
            (line_num, diff_found, ans_line, exp_line, ans_chars, exp_chars))
        if diff_found:
            if count_from_first_difference == 0:
                count_from_first_difference = 1
        if count_from_first_difference == 4:
            break

    max_line_num_digits = max(
        [len(str(entry[0])) for entry in deq if entry[0] is not None])

    logger.info(
        utils.NO_HEADER + '%s', " " * max_line_num_digits + "|output:" + " " *
        (max_chars - 7 - max_line_num_digits - 1) + "|" + "expected:")
    logger.info(utils.NO_HEADER + '%s',
                "-" * max_chars + "|" + "-" * max_chars)

    last_line_number = 0
    for (line_number, diff_found, ans_line, exp_line, ans_chars,
         exp_chars) in deq:
        num_spaces_after_output = max_chars - ans_chars - max_line_num_digits - 1
        line_number_str = str(line_number) if line_number is not None else ""
        line_num_display = space_padding(
            line_number_str, max_line_num_digits - len(line_number_str)) + "|"
        if not diff_found:
            logger.info(
                utils.NO_HEADER + '%s', line_num_display +
                space_padding(ans_line, num_spaces_after_output) + "|" +
                exp_line)
        else:
            logger.info(
                utils.NO_HEADER + '%s', line_num_display +
                utils.red(space_padding(ans_line, num_spaces_after_output)) +
                "|" + utils.green(exp_line))
        if line_number is not None:
            last_line_number = line_number
    num_snipped_lines = answer.count('\n') + 1 - last_line_number
    if num_snipped_lines > 0:
        logger.info(utils.NO_HEADER + '... (%s lines) ...', num_snipped_lines)
コード例 #2
0
ファイル: pretty_printers.py プロジェクト: ryo-n/oj
def display_side_by_side_color(answer: str, expected: str) -> None:
    max_chars = shutil.get_terminal_size()[0] // 2 - 2

    logger.info(utils.NO_HEADER + 'output:' + " " * (max_chars - 7) + "|" +
                "expected:")
    logger.info(utils.NO_HEADER + '%s',
                "-" * max_chars + "|" + "-" * max_chars)
    for _, diff_found, ans_line, exp_line, ans_chars, exp_chars in _side_by_side_diff(
            answer, expected):
        if diff_found:
            logger.info(
                utils.NO_HEADER + '%s',
                utils.red(_space_padding(ans_line, max_chars - ans_chars)) +
                "|" + utils.green(exp_line))
        else:
            logger.info(
                utils.NO_HEADER + '%s',
                _space_padding(ans_line, max_chars - ans_chars) + "|" +
                exp_line)
コード例 #3
0
ファイル: test.py プロジェクト: ryo-n/oj
def test(args: 'argparse.Namespace') -> None:
    # list tests
    if not args.test:
        args.test = fmtutils.glob_with_format(args.directory,
                                              args.format)  # by default
    if args.ignore_backup:
        args.test = fmtutils.drop_backup_or_hidden_files(args.test)
    tests = fmtutils.construct_relationship_of_files(args.test, args.directory,
                                                     args.format)

    # check wheather GNU time is available
    if not check_gnu_time(args.gnu_time):
        logger.warning('GNU time is not available: %s', args.gnu_time)
        args.gnu_time = None
    if args.mle is not None and args.gnu_time is None:
        raise RuntimeError('--mle is used but GNU time does not exist')

    # run tests
    history: List[Dict[str, Any]] = []
    if args.jobs is None:
        for name, paths in sorted(tests.items()):
            history += [
                test_single_case(name,
                                 paths['in'],
                                 paths.get('out'),
                                 args=args)
            ]
    else:
        if os.name == 'nt':
            logger.warning(
                "-j/--jobs opiton is unstable on Windows environmet")
        with concurrent.futures.ThreadPoolExecutor(
                max_workers=args.jobs) as executor:
            lock = threading.Lock()
            futures: List[concurrent.futures.Future] = []
            for name, paths in sorted(tests.items()):
                futures += [
                    executor.submit(test_single_case,
                                    name,
                                    paths['in'],
                                    paths.get('out'),
                                    lock=lock,
                                    args=args)
                ]
            for future in futures:
                history += [future.result()]

    # summarize
    slowest: float = -1.0
    slowest_name = ''
    heaviest: float = -1.0
    heaviest_name = ''
    ac_count = 0
    for result in history:
        if result['status'] == 'AC':
            ac_count += 1
        if slowest < result['elapsed']:
            slowest = result['elapsed']
            slowest_name = result['testcase']['name']
        if result['memory'] is not None and heaviest < result['memory']:
            heaviest = result['memory']
            heaviest_name = result['testcase']['name']

    # print the summary
    logger.info('')
    logger.info('slowest: %f sec  (for %s)', slowest, slowest_name)
    if heaviest >= 0:
        if heaviest < MEMORY_WARNING:
            logger.info('max memory: %f MB  (for %s)', heaviest, heaviest_name)
        else:
            logger.warning('max memory: %f MB  (for %s)', heaviest,
                           heaviest_name)
    if ac_count == len(tests):
        logger.info(
            utils.SUCCESS + 'test ' + utils.green('success') + ': %d cases',
            len(tests))
    else:
        logger.info(
            utils.FAILURE + 'test ' + utils.red('failed') +
            ': %d AC / %d cases', ac_count, len(tests))

    if args.json:
        print(json.dumps(history))

    if ac_count != len(tests):
        sys.exit(1)
コード例 #4
0
ファイル: test.py プロジェクト: 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
コード例 #5
0
def main():
    cache_dir = './.wrong_answer'
    target_dir = '.'

    # Parse input arguments
    parser = argparse.ArgumentParser(
        description="""Wrong Answer!! - Download AtCoder system test data""",
        epilog="""This program downloads the data by using the result of
            its own crawler. Because of this, you might not be able to download the
            latest contest's test data. If you think the data already opened was not
            registered to this program's DB, please leave a issue at the github page.
            Thank you.""",
        usage='%(prog)s [contest] problem [test_case]')
    parser.add_argument(
        '-c',
        '--contest',
        help=
        'specify contest name explicitly (You can omit this when you use triple)'
    )
    parser.add_argument('-u',
                        '--updatedb',
                        action='store_true',
                        help='update database')
    parser.add_argument('-l',
                        '--list',
                        action='store_true',
                        help='print contest list')
    parser.add_argument('cases', nargs='*')

    args = parser.parse_args()
    contest = args.contest

    if args.updatedb:
        download(GITHUB_URL + '/folders.txt', BASE_URLS)
        log.info(f"{BASE_URLS} updated.")
        if len(args.cases) == 0:
            exit(0)
    if args.list:
        with open(BASE_URLS, "r") as f:
            for X in f:
                X = X.split()[0]
                print(X)
        exit(0)

    argc = len(args.cases)
    if argc == 0:
        if args.contest is not None:
            url = findContest(args.contest).split()[1]
            print(
                """Downloading whole test sets for a contest could be very huge and slow.
    If you really want to do that, use below URL with a browser.
    But this program does not support whole contest downloading on purpose.
    """)
            URL = re.sub(r'dl=0', 'dl=1', url)
            print(URL)
            exit(0)
        else:
            parser.print_help()
            exit(1)
    elif argc == 1:
        # Download whole test cases of the specified problem.
        problem = args.cases[0].upper()
        MODE = Mode.PROBLEM
    elif argc == 2:
        # Download the test case of the problem.
        problem = args.cases[0].upper()
        case = args.cases[1]
        MODE = Mode.TESTCASE
    elif argc == 3:
        # Download the test case of the problem, of the contest
        contest = args.cases[0].upper()
        problem = args.cases[1].upper()
        case = args.cases[2]
        MODE = Mode.TESTCASE
    else:
        parser.print_help()
        exit(1)

# If contest is not set, use CWD as contest name
# Old ABC has some test data in ARC.
# You can find ARC contest name from the problem's page URL
    if contest is None:
        contest = os.path.basename(os.getcwd()).upper()
        log.warning(
            f"Contest name not specified. Use current dir '{contest}' as contest name."
        )
        if (contest[0:3] == "ABC"):

            # This is my rule. Each problem's web page urls are in '.problems'
            if Path('.problems').exists():
                with open('.problems') as f:
                    url = f.readlines()[ord(problem) - ord('A')].rsplit()[0]
                    comtest = url.split('/')[-1].split('_')[0].upper()
            else:
                url = f"https://atcoder.jp/contests/{contest}"
                result = get_contest.main(dispatch.contest_from_url(url),
                                          is_full=False,
                                          session=session)
                ps = result['problems']
                for i in ps:
                    if i['context']['alphabet'] == problem:
                        url = i['url']
                        break
                else:
                    log.error(
                        f"Specified problem '{problem}' not found in the contest page"
                    )
                    exit(1)
                comtest = url.split('/')[-1].split('_')[0].upper()

            if (contest != comtest):
                log.warning(f"Look like the data are in '{comtest}'.")
                log.warning(
                    f"Download data from '{comtest}' instead of '{contest}'")
                log.warning(
                    f"If you see errors, please use --contest option next time."
                )
                contest = comtest


# This URL is for whole test cases of a contest. Ignore.
    [contest, URL] = findContest(contest).split()

    if MODE == Mode.TESTCASE:
        testcases_path = f"{cache_dir}/{problem}.json"
        URL = GITHUB_URL + f"/contests/{contest}/{problem}.json"
        if not os.path.exists(testcases_path):
            os.makedirs(cache_dir, exist_ok=True)
            download(URL, testcases_path)

        with open(testcases_path, "r") as f:
            J = json.load(f)

        res = '.*' + case + '.*'
        for i in J:
            if re.match(res, i, re.I):
                URL1 = J[i]['in']
                URL2 = J[i]['out']
                break
        else:
            log.error(f"Test case '{case}' not found in {testcases_path}")
            with open(testcases_path, "r") as f:
                print()
                log.info(f"Problem {problem} has these test cases.")
                for i in J:
                    print(f"{i}", end=" ")
                print()
            exit(1)
        target_dir += '/' + problem
        downloadSingleCase(URL1, target_dir)
        downloadSingleCase(URL2, target_dir, out=True)
        log.info(utils.SUCCESS + "Succeeded")
        exit(0)

    elif MODE == Mode.PROBLEM:
        cases = f"{cache_dir}/testcases.txt"
        URL = GITHUB_URL + f"/contests/{contest}/testcases.txt"
        if not os.path.exists(cases):
            os.makedirs(cache_dir, exist_ok=True)
            download(URL, cases)
        purls = {}
        found = False
        with open(cases, "r") as f:
            lines = f.readlines()
            for i in lines:
                [p, url] = i.split()
                if p == problem: break
            else:
                log.error(
                    f"Specified problem '{problem}' not found in {cases}.")
                exit(1)
        url = re.sub(r'dl=0', 'dl=1', url)
        log.info(f"Downloading: {url}")
        log.warning(
            "This could be long! They make archive file each time by request.")
        log.info("Waiting a responce...")
        r = session.get(url, headers=agent, stream=True)
        if r.status_code != requests.codes.ok:
            log.error(f"Failed. {r.status_code}")
            exit(1)

        log.info("Got a responce.")
        siz = sizeof_fmt(float(r.headers['Content-Length']))
        log.info(f"Downloading... Size: {siz}")
        b = r.raw.read(40960)
        bs = bytearray()
        while b:
            bs += b
            b = r.raw.read(40960)
            sys.stdout.write('\r')
            sys.stdout.write(str(len(bs)))
            sys.stdout.flush()
        sys.stdout.write("\n")
        sys.stdout.flush()
        zf = zipfile.ZipFile(io.BytesIO(bs))
        target_dir += f"/{problem}"
        Path(target_dir).mkdir(parents=True, exist_ok=True)

        for i in zf.infolist():
            if i.is_dir(): continue
            path = PurePath(i.filename)
            fn = path.stem
            if path.match("*.nkftmpjKHWPL"): continue
            if path.match("*.swp"): continue
            if path.match("out/*"):
                nfn = f"{target_dir}/{fn}.out"
            else:
                nfn = f"{target_dir}/{fn}.in"
            log.info(f"Create: {nfn}")
            with zf.open(i.filename) as fr:
                with open(nfn, "w") as fw:
                    for line in fr:
                        fw.write(line.decode('utf-8').replace('\r', ''))
        log.info(utils.green('AC'))
        exit(0)
コード例 #6
0
ファイル: test.py プロジェクト: rsk0315/oj
def compare_and_report(proc: subprocess.Popen, answer: str,
                       memory: Optional[float], test_input_path: pathlib.Path,
                       test_output_path: Optional[pathlib.Path], *,
                       mle: Optional[float], mode: str, error: Optional[float],
                       does_print_input: bool, silent: bool, rstrip: bool,
                       judge: Optional[str]) -> str:
    rstrip_targets = ' \t\r\n\f\v\0'  # ruby's one, follow AnarchyGolf

    # prepare the comparing function
    if error is not None:  # float mode
        match = lambda a, b: compare_as_floats(a, b, error)
    elif judge is not None:  # special judge mode

        def match(a: str, b: str) -> bool:
            # On Windows, a temp file is not created if we use "with" statement,
            user_output = tempfile.NamedTemporaryFile(delete=False)
            judge_result = False
            try:
                if rstrip:
                    user_output.write(a.rstrip(rstrip_targets).encode())
                else:
                    user_output.write(a.encode())
                user_output.close()

                arg0 = judge
                arg1 = str(test_input_path.resolve())
                arg2 = user_output.name
                arg3 = str((str(test_output_path.resolve())
                            if test_output_path is not None else ''))

                actual_command = '{} {} {} {}'.format(
                    arg0, arg1, arg2, arg3
                )  # TODO: quote arguments for paths including spaces; see https://github.com/kmyk/online-judge-tools/pull/584
                logger.info('$ %s', actual_command)
                info, proc = utils.exec_command(actual_command)
                if not silent:
                    logger.info(
                        utils.NO_HEADER + 'judge\'s output:\n%s',
                        utils.make_pretty_large_file_content(info['answer']
                                                             or b'',
                                                             limit=40,
                                                             head=20,
                                                             tail=10,
                                                             bold=True))
                judge_result = (proc.returncode == 0)
            finally:
                os.unlink(user_output.name)
            return judge_result
    else:

        def match(a: str, b: str) -> bool:
            if a == b:
                return True
            if rstrip and a.rstrip(rstrip_targets) == b.rstrip(rstrip_targets):
                logger.warning('WA if no rstrip')
                return True
            if a == b.replace('\n', '\r\n'):
                logger.warning(r'WA if not replacing "\r\n" with "\n"')
                return True
            if rstrip and a.rstrip(rstrip_targets) == b.replace(
                    '\n', '\r\n').rstrip(rstrip_targets):
                logger.warning('WA if no rstrip')
                logger.warning(r'WA if not replacing "\r\n" with "\n"')
                return True
            if a.replace('\n', '\r\n') == b:
                logger.warning(r'WA if not replacing "\n" with "\r\n"')
                return True
            if rstrip and a.replace(
                    '\n',
                    '\r\n').rstrip(rstrip_targets) == b.rstrip(rstrip_targets):
                # TODO: use a smart way if you need more equality patterns
                logger.warning('WA if no rstrip')
                logger.warning(r'WA if not replacing "\n" with "\r\n"')
                return True
            return False

    # 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',
                    utils.make_pretty_large_file_content(inf.read(),
                                                         limit=40,
                                                         head=20,
                                                         tail=10,
                                                         bold=True))

    # check TLE, RE or not
    status = 'AC'
    if proc.returncode is None:
        logger.info(utils.FAILURE + '' + utils.red('TLE'))
        status = 'TLE'
        print_input()
    elif memory is not None and mle is not None and memory > mle:
        logger.info(utils.FAILURE + '' + utils.red('MLE'))
        status = 'MLE'
        print_input()
    elif proc.returncode != 0:
        logger.info(utils.FAILURE + '' + utils.red('RE') + ': return code %d',
                    proc.returncode)
        status = 'RE'
        print_input()

    # check WA or not
    if (test_output_path is not None) or (judge is not None):
        if test_output_path is not None:
            with test_output_path.open('rb') as outf:
                expected = outf.read().decode()
        else:  # only if --judge-command option
            expected = ''
            logger.warning('expected output is not found')
        # compare
        if not match(answer, expected):
            logger.info(utils.FAILURE + '' + utils.red('WA'))
            print_input()
            if not silent:
                if mode == 'simple':
                    logger.info(
                        utils.NO_HEADER + 'output:\n%s',
                        utils.make_pretty_large_file_content(answer.encode(),
                                                             limit=40,
                                                             head=20,
                                                             tail=10,
                                                             bold=True))
                    logger.info(
                        utils.NO_HEADER + 'expected:\n%s',
                        utils.make_pretty_large_file_content(expected.encode(),
                                                             limit=40,
                                                             head=20,
                                                             tail=10,
                                                             bold=True))
                elif mode == 'side-by-side':
                    if max(answer.count('\n'), expected.count('\n')) <= 40:
                        display_side_by_side_color(answer, expected)
                    else:
                        display_snipped_side_by_side_color(answer, expected)
                else:
                    assert False
            status = 'WA'
    else:
        if not silent:
            header = ('output:\n' if is_input_printed else '')
            logger.info(
                utils.NO_HEADER + '%s%s', header,
                utils.make_pretty_large_file_content(answer.encode(),
                                                     limit=40,
                                                     head=20,
                                                     tail=10,
                                                     bold=True))
    if status == 'AC':
        logger.info(utils.SUCCESS + '' + utils.green('AC'))

    return status