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 login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.get_default_session() # NOTE: you can see this login page with https://community.topcoder.com/longcontest/?module=Submit url = 'https://community.topcoder.com/longcontest/' username, password = get_credentials() data = { 'nextpage': 'https://www.topcoder.com/', 'module': 'Login', 'ha': username, 'pass': password, 'rem': 'on', } resp = utils.request('POST', url, session=session, data=data) if 'longcontest' not in resp.url: log.success('Success') else: log.failure('Failure') raise LoginError
def login(self, *, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.get_default_session() if self.is_logged_in(session=session): return # get url = 'https://atcoder.jp/login' resp = _request('GET', url, session=session, allow_redirects=False) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', action='') if not form: raise LoginError('something wrong') # post username, password = get_credentials() form = utils.FormSender(form, url=resp.url) form.set('username', username) form.set('password', password) resp = form.request(session) _list_alert(resp, print_=True) # result if 'login' not in resp.url: log.success('Welcome,') # AtCoder redirects to the top page if success else: log.failure('Username or Password is incorrect.') raise LoginError
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 submission_from_url(url: str) -> Optional[Submission]: for cls in submissions: submission = cls.from_url(url) if submission is not None: log.status('submission recognized: %s: %s', str(submission), url) return submission log.failure('unknown submission: %s', url) return None
def _report_messages(cls, msgs: List[str], unexpected: bool = False) -> bool: for msg in msgs: log.status('message: %s', msg) if msgs and unexpected: log.failure('unexpected messages found') return bool(msgs)
def contest_from_url(url: str) -> Optional[Contest]: for cls in contests: contest = cls.from_url(url) if contest is not None: log.status('contest recognized: %s: %s', str(contest), url) return contest log.failure('unknown contest: %s', url) return None
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ assert language_id in [ language.id for language in self.get_available_languages(session=session) ] session = session or utils.new_default_session() # get url = 'http://{}.contest.atcoder.jp/submit'.format( self.contest_id) # TODO: use beta.atcoder.jp resp = _request('GET', url, session=session) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) if AtCoderService._report_messages(msgs, unexpected=True): raise SubmissionError # check whether logged in path = utils.normpath(urllib.parse.urlparse(resp.url).path) if path.startswith('/login'): log.error('not logged in') raise NotLoggedInError # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', action=re.compile(r'^/submit\?task_id=')) if not form: log.error('form not found') raise SubmissionError log.debug('form: %s', str(form)) # post task_id = self._get_task_id(session=session) form = utils.FormSender(form, url=resp.url) form.set('task_id', str(task_id)) form.set('source_code', code) form.set('language_id_{}'.format(task_id), str(language_id)) resp = form.request(session=session) resp.raise_for_status() # result msgs = AtCoderService._get_messages_from_cookie(resp.cookies) AtCoderService._report_messages(msgs) if '/submissions/me' in resp.url: # example: https://practice.contest.atcoder.jp/submissions/me#32174 # CAUTION: this URL is not a URL of the submission log.success('success: result: %s', resp.url) # NOTE: ignore the returned legacy URL and use beta.atcoder.jp's one url = 'https://beta.atcoder.jp/contests/{}/submissions/me'.format( self.contest_id) return utils.DummySubmission(url, problem=self) else: log.failure('failure') log.debug('redirected to %s', resp.url) raise SubmissionError('it may be a rate limit')
def test_reactive(args: 'argparse.Namespace') -> None: with fifo() as (fhr1, fhw1): with fifo() as (fhr2, fhw2): with subprocess.Popen(args.command, shell=True, stdin=fhr2, stdout=fhw1, stderr=sys.stderr) as proc1: with subprocess.Popen(args.judge, shell=True, stdin=fhr1, stdout=fhw2, stderr=sys.stderr) as proc2: proc1.communicate() proc2.communicate() if proc1.returncode != 0: log.failure('RE: solution returns %d', proc1.returncode) if proc2.returncode == 0: log.success('AC') else: log.failure('WA: judge returns %d', proc2.returncode)
def service_from_url(url: str) -> Optional[Service]: for cls in services: service = cls.from_url(url) if service is not None: log.status('service recognized: %s: %s', str(service), url) return service submission = submission_from_url(url) if submission is not None: return submission.get_service() problem = problem_from_url(url) if problem is not None: return problem.get_service() log.failure('unknown service: %s', url) return None
def submit_code( self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None ) -> onlinejudge.type.Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ session = session or utils.get_default_session() if not self.get_service().is_logged_in(session=session): raise NotLoggedInError # get resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) csrftoken = soup.find('meta', attrs={ 'name': 'csrf-token' }).attrs['content'] # post url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/submissions'.format( self.contest_slug, self.challenge_slug) payload = { 'code': code, 'language': str(language_id), 'contest_slug': self.contest_slug } log.debug('payload: %s', payload) resp = utils.request('POST', url, session=session, json=payload, headers={'X-CSRF-Token': csrftoken}) # parse it = json.loads(resp.content.decode()) log.debug('json: %s', it) if not it['status']: log.failure('Submit Code: failed') raise SubmissionError model_id = it['model']['id'] url = self.get_url().rstrip('/') + '/submissions/code/{}'.format( model_id) log.success('success: result: %s', url) return utils.DummySubmission(url, problem=self)
def problem_from_url(url: str) -> Optional[Problem]: """ >>> onlinejudge.dispatch.problem_from_url("https://atcoder.jp/contests/abc077/tasks/arc084_b") <onlinejudge.service.atcoder.AtCoderProblem object at 0x7fa0538ead68> >>> onlinejudge.dispatch.problem_from_url("https://codeforces.com/contest/1012/problem/D") <onlinejudge.service.codeforces.CodeforcesProblem object at 0x7fa05a916710> """ for cls in problems: problem = cls.from_url(url) if problem is not None: log.status('problem recognized: %s: %s', str(problem), url) return problem log.failure('unknown problem: %s', url) return None
def submit_code( self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None ) -> onlinejudge.type.Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ session = session or utils.get_default_session() # get resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', class_='submitForm') if form is None: log.error('not logged in') raise NotLoggedInError log.debug('form: %s', str(form)) # make data form = utils.FormSender(form, url=resp.url) form.set('programTypeId', language_id) form.set_file('sourceFile', filename or 'code', code) resp = form.request(session=session) resp.raise_for_status() # result if resp.url.endswith('/my'): # example: https://codeforces.com/contest/598/my log.success('success: result: %s', resp.url) return utils.DummySubmission(resp.url, problem=self) else: log.failure('failure') # parse error messages soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) msgs = [] # type: List[str] for span in soup.findAll('span', class_='error'): msgs += [span.string] log.warning('Codeforces says: "%s"', span.string) raise SubmissionError( 'it may be the "You have submitted exactly the same code before" error: ' + str(msgs))
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(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 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 submit_code( self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None ) -> onlinejudge.type.Submission: """ :raises NotLoggedInError: """ session = session or utils.get_default_session() # get url = self.get_url() + '/submit' resp = utils.request('GET', url, session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', id='submit_form') if not form: log.error('form not found') raise NotLoggedInError # post form = utils.FormSender(form, url=resp.url) form.set('lang', language_id) form.set_file('file', filename or 'code', code) form.unset('custom_test') resp = form.request(session=session) resp.raise_for_status() # result if 'submissions' in resp.url: # example: https://yukicoder.me/submissions/314087 log.success('success: result: %s', resp.url) return utils.DummySubmission(resp.url, problem=self) else: log.failure('failure') soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) for div in soup.findAll('div', attrs={'role': 'alert'}): log.warning('yukicoder says: "%s"', div.string) raise SubmissionError
def submit_code(self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> Submission: """ :raises NotImplementedError: :raises SubmissionError: """ session = session or utils.get_default_session() # get resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form') if form is None: log.error('not logged in') raise LoginError log.debug('form: %s', str(form)) if form.find('select' ) and form.find('select').attrs['name'] != 'languageId': log.error("Wrong submission URL") raise SubmissionError # make data form = utils.FormSender(form, url=resp.url) form.set('languageId', language_id) form.set_file('source', 'code', code) resp = form.request(session=session) resp.raise_for_status() # result if '/s/' in resp.url: # example: https://toph.co/s/201410 log.success('success: result: %s', resp.url) return utils.DummySubmission(resp.url, problem=self) else: log.failure('failure') log.debug('redirected to %s', resp.url) raise SubmissionError
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 submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None, kind: str = 'example') -> onlinejudge.type.Submission: """ :param kind: must be one of `example` (default) or `full` :raises NotLoggedInError: :raises SubmissionError: """ assert kind in ['example', 'full'] session = session or utils.new_default_session() # TODO: implement self.is_logged_in() # if not self.is_logged_in(session=session): # raise NotLoggedInError # module=MatchDetails url = 'https://community.topcoder.com/tc?module=MatchDetails&rd=%d' % self.rd resp = utils.request('GET', url, session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) path = soup.find('a', text='Register/Submit').attrs['href'] assert path.startswith('/') and 'module=ViewReg' in path # module=ViewActiveContests url = 'https://community.topcoder.com' + path resp = utils.request('GET', url, session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) path = [tag.attrs['href'] for tag in soup.find_all('a', text='Submit') if ('rd=%d' % self.rd) in tag.attrs['href']] if len(path) == 0: log.error('link to submit not found: Are you logged in? Are you registered? Is the contest running?') raise SubmissionError('something wrong') assert len(path) == 1 path = path[0] assert path.startswith('/') and 'module=Submit' in path query = dict(urllib.parse.parse_qsl(urllib.parse.urlparse(path).query)) self.cd = query['cd'] self.compid = query['compid'] # module=Submit submit_url = 'https://community.topcoder.com' + path resp = utils.request('GET', submit_url, session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) # post url = 'https://community.topcoder.com/longcontest/' data = { 'module': 'Submit', 'rd': self.rd, 'cd': self.cd, 'compid': self.compid, 'Action': 'submit', 'exOn': { 'example': 'true', 'full': 'false' }[kind], 'lid': str(language_id), 'code': code, } resp = utils.request('POST', url, session=session, data=data) # check if module=SubmitSuccess if 'module=SubmitSuccess' in resp.content.decode(resp.encoding): url = 'http://community.topcoder.com/longcontest/?module=SubmitSuccess&rd={}&cd={}&compid={}'.format(self.rd, self.cd, self.compid) log.success('success: result: %s', url) return utils.DummySubmission(url, problem=self) else: # module=Submit to get error messages resp = utils.request('GET', submit_url, session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) messages = soup.find('textarea', {'name': 'messages'}).text log.failure('%s', messages) raise SubmissionError('it may be a rate limit: ' + messages)
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 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(log.emit, '') submit(log.info, '%d-th attempt', attempt) # generate input submit(log.status, 'generate input...') info, proc = utils.exec_command(generator, stdin=None, timeout=tle) input_data = info['answer'] # type: Optional[bytes] if not check_status(info, proc, submit=submit): return None assert input_data is not None # generate output submit(log.status, 'generate output...') info, proc = utils.exec_command(command, input=input_data, timeout=tle) output_data = info['answer'] # type: Optional[bytes] if not check_status(info, proc, submit=submit): return None assert output_data is not None # hack submit(log.status, 'hack...') info, proc = utils.exec_command(hack, input=input_data, timeout=tle) answer = (info['answer'] or b'').decode() # type: str elapsed = info['elapsed'] # type: float memory = info['memory'] # type: Optional[float] # compare status = 'AC' if proc.returncode is None: submit(log.failure, log.red('TLE')) status = 'TLE' elif proc.returncode != 0: log.failure(log.red('RE') + ': return code %d', proc.returncode) status = 'RE' expected = output_data.decode() if not simple_match(answer, expected): log.failure(log.red('WA')) log.emit( 'input:\n%s', utils.make_pretty_large_file_content(input_data, limit=40, head=20, tail=10, bold=True)) log.emit( 'output:\n%s', utils.make_pretty_large_file_content(answer.encode(), limit=40, head=20, tail=10, bold=True)) log.emit( 'expected:\n%s', utils.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 compare_and_report(proc: subprocess.Popen, answer: str, elapsed: float, 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) -> str: # prepare the comparing function if error: # float mode match = lambda a, b: compare_as_floats(a, b, error) else: rstrip_targets = ' \t\r\n\f\v\0' # ruby's one, follow AnarchyGolf def match(a, b): if a == b: return True if rstrip and a.rstrip(rstrip_targets) == b.rstrip(rstrip_targets): log.warning('WA if no rstrip') return True return False # prepare the function to print the input is_input_printed = False def print_input(): 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: log.emit( 'input:\n%s', utils.snip_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: log.failure(log.red('TLE')) status = 'TLE' print_input() elif memory is not None and mle is not None and memory > mle: log.failure(log.red('MLE')) status = 'MLE' print_input() elif proc.returncode != 0: log.failure(log.red('RE') + ': return code %d', proc.returncode) status = 'RE' print_input() # check WA or not if test_output_path is not None: with test_output_path.open() as outf: expected = outf.read() # compare if mode == 'all': if not match(answer, expected): log.failure(log.red('WA')) print_input() if not 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(expected.encode(), limit=40, head=20, tail=10, bold=True)) status = 'WA' elif mode == 'line': answer_words = answer.splitlines() correct_words = expected.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)) status = 'WA' elif y is None: print_input() log.failure( log.red('WA') + ': line %d: unexpected line: output "%s"', i + 1, log.bold(x)) status = '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)) status = 'WA' else: assert False else: if not 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 status == 'AC': log.success(log.green('AC')) return status
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, b): # 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 log.status('$ %s', actual_command) info, proc = utils.exec_command(actual_command) if not silent: log.emit( 'judge\'s output:\n%s', utils.snip_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, b): if a == b: return True if rstrip and a.rstrip(rstrip_targets) == b.rstrip(rstrip_targets): log.warning('WA if no rstrip') return True return False # prepare the function to print the input is_input_printed = False def print_input(): 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: log.emit( 'input:\n%s', utils.snip_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: log.failure(log.red('TLE')) status = 'TLE' print_input() elif memory is not None and mle is not None and memory > mle: log.failure(log.red('MLE')) status = 'MLE' print_input() elif proc.returncode != 0: log.failure(log.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 = '' log.warning('expected output is not found') # compare if not match(answer, expected): log.failure(log.red('WA')) print_input() if not silent: if mode == "simple": 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(expected.encode(), limit=40, head=20, tail=10, bold=True)) elif mode == "side-by-side": display_side_by_side_color(answer, expected) else: assert False status = 'WA' else: if not 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 status == 'AC': log.success(log.green('AC')) return status
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): log.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 = [] # type: 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': log.warning("-j/--jobs opiton is unstable on Windows environmet") with concurrent.futures.ThreadPoolExecutor( max_workers=args.jobs) as executor: lock = threading.Lock() futures = [] # type: 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 = -1.0 # type: float slowest_name = '' heaviest = -1.0 # type: float 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 log.emit('') log.status('slowest: %f sec (for %s)', slowest, slowest_name) if heaviest >= 0: if heaviest < MEMORY_WARNING: log.status('max memory: %f MB (for %s)', heaviest, heaviest_name) else: log.warning('max memory: %f MB (for %s)', heaviest, heaviest_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)
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)