def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(), session=session) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) if AtCoderService._report_messages(msgs, unexpected=True): # example message: "message: You cannot see this page." log.warning('are you logged in?') return [] # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() lang = None for pre, h3 in self._find_sample_tags(soup): s = utils.textfile(utils.dos2unix(pre.string.lstrip())) name = h3.string l = self._get_tag_lang(pre) if lang is None: lang = l elif lang != l: log.info( 'skipped due to language: current one is %s, not %s: %s ', lang, l, name) continue samples.add(s.encode(), name) return samples.get()
def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: session = session or utils.new_default_session() # get url = self.get_url(contests=False) + '/file/statement/samples.zip' resp = utils.request('GET', url, session=session, raise_for_status=False) if resp.status_code == 404: log.warning('samples.zip not found') log.info( 'this 404 happens in both cases: 1. no sample cases as intended; 2. just an error' ) return [] resp.raise_for_status() # parse with zipfile.ZipFile(io.BytesIO(resp.content)) as fh: samples = [] # type: List[TestCase] for filename in sorted(fh.namelist()): log.debug('filename: %s', filename) if filename.endswith('.in'): inpath = filename outpath = filename[:-3] + '.ans' indata = fh.read(inpath).decode() outdata = fh.read(outpath).decode() samples += [ TestCase(LabeledString(inpath, indata), LabeledString(outpath, outdata)) ] return samples
def login(self, *, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.get_default_session() url = 'https://codeforces.com/enter' # get resp = utils.request('GET', url, session=session) if resp.url != url: # redirected log.info('You have already signed in.') return # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', id='enterForm') log.debug('form: %s', str(form)) username, password = get_credentials() form = utils.FormSender(form, url=resp.url) form.set('handleOrEmail', username) form.set('password', password) form.set('remember', 'on') # post resp = form.request(session) resp.raise_for_status() if resp.url != url: # redirected log.success('Welcome, %s.', username) else: log.failure('Invalid handle or password.') raise LoginError('Invalid handle or password.')
def code_statistics(args: 'argparse.Namespace') -> None: with open(args.file, 'rb') as fh: code = fh.read() stat = get_statistics(code) stat['size'] = len(code) for key in ('size', 'binary', 'alnum', 'symbol', 'whitespace'): log.info('%s = %d', key, stat[key])
def main(args: Optional[List[str]] = None) -> None: log.addHandler(log.logging.StreamHandler(sys.stderr)) log.setLevel(log.logging.INFO) version_check() parser = get_parser() namespace = parser.parse_args(args=args) try: run_program(namespace, parser=parser) except NotImplementedError as e: log.debug('\n' + traceback.format_exc()) log.error('NotImplementedError') log.info( 'The operation you specified is not supported yet. Pull requests are welcome.' ) log.info( 'see: https://github.com/kmyk/online-judge-tools/blob/master/CONTRIBUTING.md' ) except onlinejudge.type.NotLoggedInError: log.error('login required') sys.exit(1) except requests.exceptions.HTTPError as e: log.error(str(e)) log.debug(traceback.format_exc()) sys.exit(1) except requests.exceptions.InvalidURL as e: log.error(str(e)) sys.exit(1) except onlinejudge.type.SampleParseError: log.error('Failed to parse sample.') sys.exit(1) except FileExistsError as e: log.error(str(e)) sys.exit(1)
def download(args: 'argparse.Namespace') -> None: # prepare values problem = onlinejudge.dispatch.problem_from_url(args.url) if problem is None: raise requests.exceptions.InvalidURL('The contest "%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.with_cookiejar(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 onlinejudge.type.SampleParseError("Sample not found") # append the history for submit command if not args.dry_run and is_default_format: history = onlinejudge._implementation.download_history.DownloadHistory() history.add(problem) # write samples to files for i, sample in enumerate(samples): log.emit('') log.info('sample %d', i) 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 = args.directory / format_utils.percentformat(args.format, table) # type: pathlib.Path log.status('%sput: %s', ext, name) if not args.silent: log.emit(utils.snip_large_file_content(data, limit=40, head=20, tail=10, bold=True)) if args.dry_run: continue if path.exists(): raise FileExistsError('Failed to download since file already exists: ' + str(path)) path.parent.mkdir(parents=True, exist_ok=True) with path.open('wb') as fh: fh.write(data) log.success('saved to: %s', path) # print json if args.json: print(json.dumps(list(map(convert_sample_to_dict, samples))))
def is_logged_in_with_message(service: onlinejudge.type.Service, *, session: requests.Session) -> bool: if service.is_logged_in(session=session): log.info('You have already signed in.') return True else: log.warning('You are not signed in.') return False
def version_check() -> None: if utils.is_update_available_on_pypi(): log.warning('update available: %s -> %s', version.__version__, utils.get_latest_version_from_pypi()) log.info('run: $ pip3 install -U %s', version.__package_name__) log.info( 'see: https://github.com/kmyk/online-judge-tools/blob/master/CHANGELOG.md' )
def download(args: 'argparse.Namespace') -> None: # prepare values problem = onlinejudge.dispatch.problem_from_url(args.url) if problem is None: sys.exit(1) 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.with_cookiejar(utils.new_session_with_our_user_agent(), path=args.cookie) as sess: if args.system: samples = problem.download_system_cases(session=sess) # type: ignore else: samples = problem.download_sample_cases(session=sess) # type: ignore # append the history for submit command if not args.dry_run and is_default_format: history = onlinejudge._implementation.download_history.DownloadHistory() history.add(problem) # write samples to files for i, sample in enumerate(samples): log.emit('') log.info('sample %d', i) 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 = args.directory / format_utils.percentformat(args.format, table) # type: pathlib.Path log.status('%sput: %s', ext, name) if not args.silent: log.emit(utils.snip_large_file_content(data, limit=40, head=20, tail=10, bold=True)) if args.dry_run: continue if path.exists(): log.warning('file already exists: %s', path) if not args.overwrite: log.warning('skipped') continue path.parent.mkdir(parents=True, exist_ok=True) with path.open('wb') as fh: fh.write(data) log.success('saved to: %s', path) # print json if args.json: print(json.dumps(list(map(convert_sample_to_dict, samples))))
def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[onlinejudge.type.TestCase]: session = session or utils.get_default_session() # get url = self.get_url(contests=False) + '/file/statement/samples.zip' resp = utils.request('GET', url, session=session, raise_for_status=False) if resp.status_code == 404: log.warning('samples.zip not found') log.info('this 404 happens in both cases: 1. no sample cases as intended; 2. just an error') return [] resp.raise_for_status() # parse return onlinejudge._implementation.testcase_zipper.extract_from_zip(resp.content, '%s.%e', out='ans')
def main(args: Optional[List[str]] = None) -> None: log.addHandler(log.logging.StreamHandler(sys.stderr)) log.setLevel(log.logging.INFO) version_check() parser = get_parser() namespace = parser.parse_args(args=args) try: run_program(namespace, parser=parser) except NotImplementedError as e: log.debug('\n' + traceback.format_exc()) log.error('NotImplementedError') log.info('The operation you specified is not supported yet. Pull requests are welcome.') log.info('see: https://github.com/kmyk/online-judge-tools/blob/master/CONTRIBUTING.md')
def generate_output(args: 'argparse.Namespace') -> None: if not args.test: args.test = cutils.glob_with_format(args.directory, args.format) # by default if args.ignore_backup: args.test = cutils.drop_backup_or_hidden_files(args.test) tests = cutils.construct_relationship_of_files(args.test, args.directory, args.format) for name, it in sorted(tests.items()): log.emit('') log.info('%s', name) if 'out' in it: log.info('output file already exists.') log.info('skipped.') continue with it['in'].open() as inf: begin = time.perf_counter() answer, proc = utils.exec_command(args.command, shell=True, stdin=inf) end = time.perf_counter() log.status('time: %f sec', end - begin) if proc.returncode != 0: log.failure(log.red('RE') + ': return code %d', proc.returncode) log.info('skipped.') continue log.emit(utils.snip_large_file_content(answer, limit=40, head=20, tail=10, bold=True)) match_result = cutils.match_with_format(args.directory, args.format, it['in']) # type: Optional[Match[Any]] if match_result is not None: matched_name = match_result.groupdict()['name'] # type: str else: assert False path = cutils.path_from_format(args.directory, args.format, name=matched_name, ext='out') if not path.parent.is_dir(): os.makedirs(str(path.parent), exist_ok=True) with path.open('wb') as fh: fh.write(answer) log.success('saved to: %s', path)
def _AtCoderProblemContent_parse_sample_cases( soup: bs4.BeautifulSoup) -> List[onlinejudge.type.TestCase]: samples = onlinejudge._implementation.testcase_zipper.SampleZipper() lang = None for pre, h3 in _AtCoderProblemContent_find_sample_tags(soup): s = utils.textfile(utils.dos2unix(pre.string.lstrip())) name = h3.string l = _AtCoderProblemContent_get_tag_lang(pre) if lang is None: lang = l elif lang != l: log.info('skipped due to language: current one is %s, not %s: %s ', lang, l, name) continue samples.add(s.encode(), name) return samples.get()
def login_with_browser(service: onlinejudge.type.Service, *, session: requests.Session) -> None: try: import selenium.webdriver except ImportError: raise try: profile = selenium.webdriver.FirefoxProfile() profile.set_preference("general.useragent.override", session.headers['User-Agent']) driver = selenium.webdriver.Firefox(firefox_profile=profile) except selenium.common.exceptions.WebDriverException as e: raise WebDriverException(e) # get cookies via Selenium url = service.get_url_of_login_page() log.info('open with WebDriver: %s', url) driver.get(url) cookies = [] # type: List[Dict[str, str]] try: while driver.current_url: cookies = driver.get_cookies() time.sleep(0.1) except selenium.common.exceptions.WebDriverException: pass # the window is closed # set cookies to the requests.Session log.info('copy cookies from WebDriver') for c in cookies: log.status('set cookie: %s', c['name']) morsel = http.cookies.Morsel() # type: http.cookies.Morsel morsel.set(c['name'], c['value'], c['value']) morsel.update({ key: value for key, value in c.items() if morsel.isReservedKey(key) }) if not morsel['expires']: expires = datetime.datetime.now( datetime.timezone.utc).astimezone() + datetime.timedelta( days=180) morsel.update({ 'expires': expires.strftime('%a, %d-%b-%Y %H:%M:%S GMT') }) # RFC2109 format cookie = requests.cookies.morsel_to_cookie(morsel) session.cookies.set_cookie(cookie) # type: ignore
def test_single_case(test_name: str, test_input_path: pathlib.Path, test_output_path: Optional[pathlib.Path], *, lock: Optional[threading.Lock] = None, args: 'argparse.Namespace') -> Dict[str, Any]: # print the header earlier if not in parallel if lock is None: log.emit('') log.info('%s', test_name) # run the binary with test_input_path.open() as inf: info, proc = utils.exec_command(args.command, stdin=inf, timeout=args.tle, gnu_time=args.gnu_time) # TODO: the `answer` should be bytes, not str answer = (info['answer'] or b'').decode(errors='replace') # type: str elapsed = info['elapsed'] # type: float memory = info['memory'] # type: Optional[float] # lock is require to avoid mixing logs if in parallel nullcontext = contextlib.ExitStack() # TODO: use contextlib.nullcontext() after updating Python to 3.7 with lock or nullcontext: if lock is not None: log.emit('') log.info('%s', test_name) log.status('time: %f sec', elapsed) if memory: if memory < MEMORY_PRINT: if args.print_memory: log.status('memory: %f MB', memory) elif memory < MEMORY_WARNING: log.status('memory: %f MB', memory) else: log.warning('memory: %f MB', memory) status = compare_and_report(proc, answer, elapsed, memory, test_input_path, test_output_path, mle=args.mle, mode=args.mode, error=args.error, does_print_input=args.print_input, silent=args.silent, rstrip=args.rstrip, judge=args.judge) # return the result testcase = { 'name': test_name, 'input': str(test_input_path.resolve()), } if test_output_path: testcase['output'] = str(test_output_path.resolve()) return { 'status': status, 'testcase': testcase, 'output': answer, 'exitcode': proc.returncode, 'elapsed': elapsed, 'memory': memory, }
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.new_default_session() url = 'https://www.hackerrank.com/auth/login' # get resp = utils.request('GET', url, session=session) if resp.url != url: log.info('You have already signed in.') return # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) csrftoken = soup.find('meta', attrs={ 'name': 'csrf-token' }).attrs['content'] tag = soup.find('input', attrs={'name': 'username'}) while tag.name != 'form': tag = tag.parent form = tag # post username, password = get_credentials() form = utils.FormSender(form, url=resp.url) form.set('login', username) form.set('password', password) form.set('remember_me', 'true') form.set('fallback', 'true') resp = form.request(session, method='POST', action='/rest/auth/login', headers={'X-CSRF-Token': csrftoken}) resp.raise_for_status() # result if '/auth' not in resp.url: log.success('You signed in.') else: log.failure('You failed to sign in. Wrong user ID or password.') raise LoginError( 'You failed to sign in. Wrong user ID or password.')
def construct_relationship_of_files(paths: List[pathlib.Path], directory: pathlib.Path, format: str) -> Dict[str, Dict[str, pathlib.Path]]: tests = collections.defaultdict(dict) # type: Dict[str, Dict[str, pathlib.Path]] for path in paths: m = match_with_format(directory, format, path.resolve()) if not m: log.error('unrecognizable file found: %s', path) sys.exit(1) name = m.groupdict()['name'] ext = m.groupdict()['ext'] assert ext not in tests[name] tests[name][ext] = path for name in tests: if 'in' not in tests[name]: assert 'out' in tests[name] log.error('dangling output case: %s', tests[name]['out']) sys.exit(1) if not tests: log.error('no cases found') sys.exit(1) log.info('%d cases found', len(tests)) return tests
def split_input(args: 'argparse.Namespace') -> None: with open(args.input) as fh: inf = fh.read() if args.footer == split_input_auto_footer: args.footer = inf.splitlines(keepends=True)[-1] with subprocess.Popen(args.command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr) as proc: index = 0 acc = '' for line in inf.splitlines(keepends=True): if args.ignore: args.ignore -= 1 else: acc += line proc.stdin.write(line.encode()) proc.stdin.flush() time.sleep(args.time) if non_block_read(proc.stdout): # if output exists index += 1 path = format_utils.percentformat(args.output, {'i': str(index)}) log.info('case found: %d', index) if args.header: if args.header == args.header.strip(): acc = '\n' + acc acc = args.header + acc if args.footer: acc = acc + args.footer log.emit(log.bold(acc)) with open(path, 'w') as fh: fh.write(acc) log.success('saved to: %s', path) acc = '' while non_block_read(proc.stdout): # consume all pass
def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: """ :raises Exception: if no such problem exists """ session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(type='beta'), raise_for_status=False, session=session) if _list_alert(resp): log.warning('are you logged in?') resp.raise_for_status() # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() lang = None for pre, h3 in self._find_sample_tags(soup): s = utils.textfile(utils.dos2unix(pre.string.lstrip())) name = h3.string l = self._get_tag_lang(pre) if lang is None: lang = l elif lang != l: log.info( 'skipped due to language: current one is %s, not %s: %s ', lang, l, name) continue samples.add(s.encode(), name) return samples.get()
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: log.emit('') log.info('%s', test_name) # run the command with test_input_path.open() as inf: info, proc = utils.exec_command(args.command, stdin=inf, timeout=args.tle) answer = info['answer'] # type: Optional[bytes] elapsed = info['elapsed'] # type: float # acquire lock to print logs properly, if in parallel nullcontext = contextlib.ExitStack() with lock or nullcontext: if lock is not None: log.emit('') log.info('%s', test_name) # check the result log.status('time: %f sec', elapsed) if proc.returncode is None: log.failure(log.red('TLE')) log.info('skipped.') return elif proc.returncode != 0: log.failure(log.red('RE') + ': return code %d', proc.returncode) log.info('skipped.') return assert answer is not None log.emit(utils.snip_large_file_content(answer, limit=40, head=20, tail=10, bold=True)) # find the destination path match_result = fmtutils.match_with_format(args.directory, args.format, test_input_path) # type: Optional[Match[Any]] if match_result is not None: matched_name = match_result.groupdict()['name'] # type: str 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) log.success('saved to: %s', test_output_path)
def login_with_github( self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raise LoginError: """ session = session or utils.get_default_session() url = 'https://yukicoder.me/auth/github' # get resp = utils.request('GET', url, session=session) if urllib.parse.urlparse(resp.url).hostname == 'yukicoder.me': log.info('You have already signed in.') return # redirect to github.com # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form') if not form: log.error('form not found') raise LoginError('something wrong') log.debug('form: %s', str(form)) # post username, password = get_credentials() form = utils.FormSender(form, url=resp.url) form.set('login', username) form.set('password', password) resp = form.request(session) resp.raise_for_status() if urllib.parse.urlparse(resp.url).hostname == 'yukicoder.me': log.success('You signed in.') else: log.failure('You failed to sign in. Wrong user ID or password.') raise LoginError
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.new_default_session() url = 'https://toph.co/login' # get resp = utils.request('GET', url, session=session) if resp.url != url: # redirected log.info('You are already logged in.') return # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', class_='login-form') log.debug('form: %s', str(form)) username, password = get_credentials() form[ 'action'] = '/login' # to avoid KeyError inside form.request method as Toph does not have any defined action form = utils.FormSender(form, url=resp.url) form.set('handle', username) form.set('password', password) # post resp = form.request(session) resp.raise_for_status() resp = utils.request( 'GET', url, session=session ) # Toph's Location header is not getting the expected value if resp.url != url: log.success('Welcome, %s.', username) else: log.failure('Invalid handle/email or password.') raise LoginError('Invalid handle/email or password.')
def generate_output_single_case_exists_ok(test_name: str, test_input_path: pathlib.Path, test_output_path: Optional[pathlib.Path], *, lock: Optional[threading.Lock] = None, args: 'argparse.Namespace') -> None: if test_output_path is not None: nullcontext = contextlib.ExitStack() with lock or nullcontext: log.emit('') log.info('%s', test_name) log.info('output file already exists.') log.info('skipped.') else: generate_output_single_case(test_name, test_input_path, lock=lock, args=args)
def login(args: 'argparse.Namespace') -> None: # get service service = onlinejudge.dispatch.service_from_url(args.url) if service is None: sys.exit(1) # configure kwargs = {} if isinstance(service, onlinejudge.service.yukicoder.YukicoderService): if not args.method: args.method = 'github' if args.method not in ['github', 'twitter']: log.failure('login for yukicoder: invalid option: --method %s', args.method) sys.exit(1) kwargs['method'] = args.method else: if args.method: log.failure('login for %s: invalid option: --method %s', service.get_name(), args.method) sys.exit(1) with utils.with_cookiejar(utils.new_session_with_our_user_agent(), path=args.cookie) as sess: if args.check: if service.is_logged_in(session=sess): log.info('You have already signed in.') else: log.info('You are not signed in.') sys.exit(1) else: # login def get_credentials() -> Tuple[str, str]: if args.username is None: args.username = input('Username: '******'If you don\'t want to give your password to this program, you can give only your session tokens.') log.info('see: https://github.com/kmyk/online-judge-tools/blob/master/LOGIN_WITH_COOKIES.md') try: service.login(get_credentials, session=sess, **kwargs) # type: ignore except onlinejudge.type.LoginError: pass
def submit(args: 'argparse.Namespace') -> None: # guess url history = onlinejudge._implementation.download_history.DownloadHistory() if args.file.parent.resolve() == pathlib.Path.cwd(): guessed_urls = history.get() else: log.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] log.info('guessed problem: %s', args.url) else: log.error('failed to guess the URL to submit') log.info('please manually specify URL as: $ oj submit URL FILE') sys.exit(1) # parse url problem = onlinejudge.dispatch.problem_from_url(args.url) if problem is None: sys.exit(1) # read code with args.file.open('rb') as fh: code = fh.read() # type: bytes format_config = { 'dos2unix': args.format_dos2unix or args.golf, 'rstrip': args.format_dos2unix or args.golf, } code = format_code(code, **format_config) # report code log.info('code (%d byte):', len(code)) log.emit( utils.snip_large_file_content(code, limit=30, head=10, tail=10, bold=True)) with utils.with_cookiejar(utils.new_session_with_our_user_agent(), path=args.cookie) as sess: # guess or select language ids langs = { language.id: { 'description': language.name } for language in problem.get_available_languages(session=sess) } # type: Dict[LanguageId, Dict[str, str]] matched_lang_ids = None # type: Optional[List[str]] if args.language in langs: matched_lang_ids = [args.language] else: if args.guess: kwargs = { 'language_dict': langs, '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: log.info('failed to guess languages from the file name') matched_lang_ids = list(langs.keys()) if args.language is not None: log.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=langs) else: if args.language is None: matched_lang_ids = None else: matched_lang_ids = select_ids_of_matched_languages( args.language.split(), list(langs.keys()), language_dict=langs) # report selected language ids if matched_lang_ids is not None and len(matched_lang_ids) == 1: args.language = matched_lang_ids[0] log.info('chosen language: %s (%s)', args.language, langs[LanguageId(args.language)]['description']) else: if matched_lang_ids is None: log.error('language is unknown') log.info('supported languages are:') elif len(matched_lang_ids) == 0: log.error('no languages are matched') log.info('supported languages are:') else: log.error('Matched languages were not narrowed down to one.') log.info('You have to choose:') for lang_id in sorted(matched_lang_ids or langs.keys()): log.emit('%s (%s)', lang_id, langs[LanguageId(lang_id)]['description']) sys.exit(1) # 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') log.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: log.status('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: log.info('terminated.') return else: sys.stdout.write('Are you sure? [y/N] ') sys.stdout.flush() c = sys.stdin.read(1) if c.lower() != 'y': log.info('terminated.') return # submit kwargs = {} if isinstance(problem, onlinejudge.service.topcoder.TopcoderLongContestProblem): if args.full_submission: kwargs['kind'] = 'full' else: kwargs['kind'] = 'example' try: submission = problem.submit_code(code, language_id=LanguageId( args.language), session=sess, **kwargs) except NotLoggedInError: log.failure('login required') sys.exit(1) except SubmissionError: log.failure('submission failed') sys.exit(1) # show result if args.open: browser = webbrowser.get() log.status('open the submission page with: %s', browser.name) opened = browser.open_new_tab(submission.get_url()) if not opened: log.failure( 'failed to open the url. please set the $BROWSER envvar')
def test(args: 'argparse.Namespace') -> None: # prepare if not args.test: args.test = cutils.glob_with_format(args.directory, args.format) # by default if args.ignore_backup: args.test = cutils.drop_backup_or_hidden_files(args.test) tests = cutils.construct_relationship_of_files(args.test, args.directory, args.format) if args.error: # float mode match = lambda a, b: compare_as_floats(a, b, args.error) else: def match(a, b): if a == b: return True if args.rstrip and a.rstrip(rstrip_targets) == b.rstrip(rstrip_targets): log.warning('WA if no rstrip') return True return False rstrip_targets = ' \t\r\n\f\v\0' # ruby's one, follow AnarchyGolf slowest = -1 # type: Union[int, float] slowest_name = '' ac_count = 0 history = [] # type: List[Dict[str, Any]] for name, it in sorted(tests.items()): is_input_printed = False def print_input(): nonlocal is_input_printed if args.print_input and not is_input_printed: is_input_printed = True with open(it['in'], 'rb') as inf: log.emit('input:\n%s', utils.snip_large_file_content(inf.read(), limit=40, head=20, tail=10, bold=True)) log.emit('') log.info('%s', name) # run the binary with it['in'].open() as inf: begin = time.perf_counter() answer_byte, proc = utils.exec_command(args.command, shell=True, stdin=inf, timeout=args.tle) end = time.perf_counter() elapsed = end - begin answer = answer_byte.decode() # TODO: the `answer` should be bytes, not str if slowest < elapsed: slowest = elapsed slowest_name = name log.status('time: %f sec', elapsed) proc.terminate() # check TLE, RE or not result = 'AC' if proc.returncode is None: log.failure(log.red('TLE')) result = 'TLE' print_input() elif proc.returncode != 0: log.failure(log.red('RE') + ': return code %d', proc.returncode) result = 'RE' print_input() # check WA or not if 'out' in it: with it['out'].open() as outf: correct = outf.read() # compare if args.mode == 'all': if not match(answer, correct): log.failure(log.red('WA')) print_input() if not args.silent: log.emit('output:\n%s', utils.snip_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) log.emit('expected:\n%s', utils.snip_large_file_content(correct.encode(), limit=40, head=20, tail=10, bold=True)) result = 'WA' elif args.mode == 'line': answer_words = answer.splitlines() correct_words = correct.splitlines() for i, (x, y) in enumerate(zip(answer_words + [None] * len(correct_words), correct_words + [None] * len(answer_words))): # type: ignore if x is None and y is None: break elif x is None: print_input() log.failure(log.red('WA') + ': line %d: line is nothing: expected "%s"', i + 1, log.bold(y)) result = 'WA' elif y is None: print_input() log.failure(log.red('WA') + ': line %d: unexpected line: output "%s"', i + 1, log.bold(x)) result = 'WA' elif not match(x, y): print_input() log.failure(log.red('WA') + ': line %d: output "%s": expected "%s"', i + 1, log.bold(x), log.bold(y)) result = 'WA' else: assert False else: if not args.silent: log.emit(('output:\n%s' if is_input_printed else '%s'), utils.snip_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) if result == 'AC': log.success(log.green('AC')) ac_count += 1 # push the result testcase = { 'name': name, 'input': str(it['in'].resolve()), } if 'out' in it: testcase['output'] = str(it['out'].resolve()) history += [{ 'result': result, 'testcase': testcase, 'output': answer, 'exitcode': proc.returncode, 'elapsed': elapsed, }] # summarize log.emit('') log.status('slowest: %f sec (for %s)', slowest, slowest_name) if ac_count == len(tests): log.success('test ' + log.green('success') + ': %d cases', len(tests)) else: log.failure('test ' + log.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)