Skip to content

Commit

Permalink
#208: support downloading system cases of HackerRank using the offici…
Browse files Browse the repository at this point in the history
…al feature
  • Loading branch information
kmyk committed Feb 4, 2019
1 parent 708d118 commit 2aa6eb5
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 59 deletions.
5 changes: 3 additions & 2 deletions onlinejudge/implementation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ def set_file(self, key: str, filename: str, content: bytes) -> None:
def unset(self, key: str) -> None:
del self.payload[key]

def request(self, session: requests.Session, action: Optional[str] = None, **kwargs) -> requests.Response:
def request(self, session: requests.Session, method: str = None, action: Optional[str] = None, **kwargs) -> requests.Response:
action = action or self.form['action']
url = urllib.parse.urljoin(self.url, action)
method = self.form['method'].upper()
if method is None:
method = self.form['method'].upper()
log.status('%s: %s', method, url)
log.debug('payload: %s', str(self.payload))
resp = session.request(method, url, data=self.payload, files=self.files, **kwargs)
Expand Down
82 changes: 32 additions & 50 deletions onlinejudge/service/hackerrank.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Python Version: 3.x
import datetime
import io
import json
import posixpath
import re
import time
import urllib.parse
import zipfile
from typing import *

import bs4
Expand Down Expand Up @@ -81,61 +83,41 @@ def __init__(self, contest_slug: str, challenge_slug: str):
self.contest_slug = contest_slug
self.challenge_slug = challenge_slug

def download(self, session: Optional[requests.Session] = None, method: str = 'run_code') -> List[TestCase]:
if method == 'run_code':
return self.download_with_running_code(session=session)
elif method == 'parse_html':
return self.download_with_parsing_html(session=session)
else:
raise ValueError
def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[TestCase]:
log.warning('use --system option')
raise NotImplementedError

def download_with_running_code(self, session: Optional[requests.Session] = None) -> List[TestCase]:
def download_system_cases(self, session: Optional[requests.Session] = None) -> List[TestCase]:
session = session or utils.new_default_session()
# 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/{}/compile_tests'.format(self.contest_slug, self.challenge_slug)
payload = {'code': ':', 'language': 'bash', 'customtestcase': False}
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.error('Run Code: failed')
# example: https://www.hackerrank.com/rest/contests/hourrank-1/challenges/beautiful-array/download_testcases
url = f'https://www.hackerrank.com/rest/contests/{self.contest_slug}/challenges/{self.challenge_slug}/download_testcases'
resp = utils.request('GET', url, session=session, raise_for_status=False)
if resp.status_code != 200:
log.error('response: %s', resp.content.decode())
return []
model_id = it['model']['id']
now = datetime.datetime.now()
unixtime = int(datetime.datetime.now().timestamp() * 10**3)
url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/compile_tests/{}?_={}'.format(self.contest_slug, self.challenge_slug, it['model']['id'], unixtime)
# sleep
log.status('sleep(3)')
time.sleep(3)
# get
resp = utils.request('GET', url, session=session, headers={'X-CSRF-Token': csrftoken})
# parse
it = json.loads(resp.content.decode())
log.debug('json: %s', it)
if not it['status']:
log.error('Run Code: failed')
return []
samples: List[TestCase] = []
for i, (inf, outf) in enumerate(zip(it['model']['stdin'], it['model']['expected_output'])):
inname = 'Testcase {} Input'.format(i)
outname = 'Testcase {} Expected Output'.format(i)
samples += [TestCase(
LabeledString(inname, utils.textfile(inf)),
LabeledString(outname, utils.textfile(outf)),
)]
return samples

def download_with_parsing_html(self, session: Optional[requests.Session] = None) -> List[TestCase]:
session = session or utils.new_default_session()
url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}'.format(self.contest_slug, self.challenge_slug)
raise NotImplementedError
with zipfile.ZipFile(io.BytesIO(resp.content)) as fh:
# list names
names = [] # type: List[str]
pattern = re.compile(r'(in|out)put/\1put(\d+).txt')
for filename in sorted(fh.namelist()): # "input" < "output"
if filename.endswith('/'):
continue
log.debug('filename: %s', filename)
m = pattern.match(filename)
assert m
if m.group(1) == 'in':
names += [m.group(2)]
# zip samples
samples = [] # type: List[TestCase]
for name in names:
inpath = f'input/input{name}.txt'
outpath = f'output/output{name}.txt'
indata = fh.read(inpath).decode()
outdata = fh.read(outpath).decode()
samples += [TestCase(LabeledString(inpath, indata), LabeledString(outpath, outdata))]
return samples

def get_url(self) -> str:
if self.contest_slug == 'master':
Expand Down
42 changes: 35 additions & 7 deletions tests/command_download_hackerrank.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,38 @@ class DownloadHackerRankTest(unittest.TestCase):
def snippet_call_download(self, *args, **kwargs):
tests.command_download.snippet_call_download(self, *args, **kwargs)

def test_call_download_hackerrank_beautiful_array(self):
self.snippet_call_download('https://www.hackerrank.com/contests/hourrank-1/challenges/beautiful-array', {
'sample-1.in': 'fb3f7e56dac548ce73f9d8e485e5336b',
'sample-2.out': '897316929176464ebc9ad085f31e7284',
'sample-2.in': '6047a07c8defde4d696513d26e871b20',
'sample-1.out': '6d7fce9fee471194aa8b5b6e47267f03',
})
# def test_call_download_hackerrank_beautiful_array(self):
# self.snippet_call_download('https://www.hackerrank.com/contests/hourrank-1/challenges/beautiful-array', {
# 'sample-1.in': 'fb3f7e56dac548ce73f9d8e485e5336b',
# 'sample-2.out': '897316929176464ebc9ad085f31e7284',
# 'sample-2.in': '6047a07c8defde4d696513d26e871b20',
# 'sample-1.out': '6d7fce9fee471194aa8b5b6e47267f03',
# })

def test_call_download_hackerrank_hourrank_30_a_system(self):
# TODO: these file names should "00.in", "00.out", ..., "10.out"
self.snippet_call_download(
'https://www.hackerrank.com/contests/hourrank-30/challenges/video-conference', {
'1.in': 'b138a1282e79697057d5eca993a29414',
'1.out': 'de044533ac6d30ed89eb5b4e10ff105b',
'2.in': '0e64d38accc35d4b8ac4fc0df3b5b969',
'2.out': '3362cf9066bba541387e5b6787b13e6e',
'3.in': '7df575910d94fecb93861eaf414d86dd',
'3.out': 'eb68db6a13e73b093d620f865e4cc098',
'4.in': 'd87880f0cd02ee106a8cadc5ccd97ed0',
'4.out': 'a24f9580a3701064cb49534689b50b60',
'5.in': 'f5981eb3068da7d2d2c1b84b23ea8710',
'5.out': 'df0a3dfc2217cbc8e8828e423933206b',
'6.in': 'b1387e51b1f9c4e16713647b36e8341b',
'6.out': 'ac14c5fed571104401167dd04fdcf417',
'7.in': 'ba080fc7b89b2aed00fcf03a5db29f8a',
'7.out': '3a365fc4aec7cad9536c598b7d892e7a',
'8.in': '9d3f2cfb7b6412ef40a8b5ef556c3a46',
'8.out': '8e7a02d5c6bdd9358c589b3e400bacb8',
'9.in': '8409f37413e40f3baee0314bcacfc0a4',
'9.out': 'fe2d333498a3bdebaa0f4c88803566ff',
'10.in': '6f3e4c84441ae56e141a600542cc8ec8',
'10.out': '66e67dc4e8edbf66ed9ae2c9a0862f2b',
'11.in': 'fe24b76ea70e0a44213d7f22d183a33b',
'11.out': '8b8ba206ea7bbb02f0361341cb8da7c7',
}, is_system=True)

0 comments on commit 2aa6eb5

Please sign in to comment.