Skip to content

Commit

Permalink
Merge pull request #289 from kmyk/issue/275
Browse files Browse the repository at this point in the history
#275 add the submit feature for yukicoder
  • Loading branch information
fukatani authored Feb 1, 2019
2 parents efc9382 + e4a0fab commit 2d9d6ee
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 26 deletions.
2 changes: 1 addition & 1 deletion onlinejudge/implementation/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ def get_parser() -> argparse.ArgumentParser:
AtCoder
Codeforces
TopCoder (Marathon Match)
Yukicoder
(Yukicoder has been removed)
''')
subparser.add_argument('url', nargs='?', help='the URL of the problem to submit. if not given, guessed from history of download command.')
subparser.add_argument('file', type=pathlib.Path)
Expand Down
3 changes: 3 additions & 0 deletions onlinejudge/implementation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ def get(self) -> Dict[str, str]:
def set_file(self, key: str, filename: str, content: bytes) -> None:
self.files[key] = (filename, content) # type: ignore

def unset(self, key: str) -> None:
del self.payload[key]

def request(self, session: requests.Session, action: Optional[str] = None, **kwargs) -> requests.Response:
action = action or self.form['action']
url = urllib.parse.urljoin(self.url, action)
Expand Down
44 changes: 44 additions & 0 deletions onlinejudge/service/yukicoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,50 @@ def _parse_sample_tag(self, tag: bs4.Tag) -> Optional[Tuple[str, str]]:
return utils.textfile(s.lstrip()), pprv.string + ' ' + prv.string
return None

def submit_code(self, code: bytes, language: str, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission: # or SubmissionError
session = session or utils.new_default_session()
# get
url = 'https://yukicoder.me/problems/no/{}/submit'.format(self.problem_no)
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 onlinejudge.type.SubmissionError
# post
form = utils.FormSender(form, url=resp.url)
form.set('lang', language)
form.set_file('file', '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 onlinejudge.type.DummySubmission(resp.url)
else:
log.failure('failure')
log.debug('redirected to %s', resp.url)
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 onlinejudge.type.SubmissionError

def get_language_dict(self, session: Optional[requests.Session] = None) -> Dict[str, onlinejudge.type.Language]:
session = session or utils.new_default_session()
# get
# We use the problem page since it is available without logging in
resp = utils.request('GET', self.get_url(), session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
select = soup.find('select', id='lang')
language_dict = {}
for option in select.find_all('option'):
language_dict[option.attrs['value']] = {'description': ' '.join(option.string.split())}
return language_dict

def get_url(self) -> str:
if self.problem_no:
return 'https://yukicoder.me/problems/no/{}'.format(self.problem_no)
Expand Down
4 changes: 1 addition & 3 deletions tests/command_download_yukicoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@

class DownloadYukicoderTest(unittest.TestCase):
def snippet_call_download(self, *args, **kwargs):
if kwargs.get('is_system') and 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return
tests.command_download.snippet_call_download(self, *args, **kwargs)

def test_call_download_yukicoder_no_9002(self):
Expand Down Expand Up @@ -63,6 +60,7 @@ def test_call_download_yukicoder_no_260(self):
'sample-1.out': '90e2a51705594d033a3abe9d77b2b7ad',
})

@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_download_yukicoder_no_8_system(self):
self.snippet_call_download(
'https://yukicoder.me/problems/no/8', {
Expand Down
63 changes: 48 additions & 15 deletions tests/command_submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@


class SubmitAtCoderTest(unittest.TestCase):
@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_practice_1(self):
if 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return

url = 'https://atcoder.jp/contests/practice/tasks/practice_1'
code = '''\
Expand All @@ -36,10 +34,8 @@ def test_call_submit_practice_1(self):
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 'submit', '-y', '--no-open', url, 'main.cpp'], stdout=sys.stdout, stderr=sys.stderr)

@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_practice_2(self):
if 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return

url = 'https://atcoder.jp/contests/practice/tasks/practice_2'
code = '''\
Expand Down Expand Up @@ -74,10 +70,8 @@ def quick_sort(s):
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 'submit', '-y', '--no-open', url, 'main.py'], stdout=sys.stdout, stderr=sys.stderr)

@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_practice_1_with_history(self):
if 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return

url = 'https://atcoder.jp/contests/practice/tasks/practice_1'
files = [
Expand All @@ -93,10 +87,8 @@ def test_call_submit_practice_1_with_history(self):


class SubmitCodeforcesTest(unittest.TestCase):
@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_beta_1_a(self):
if 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return

url = 'https://codeforces.com/contest/1/problem/A'
code = '\n'.join([
Expand All @@ -115,10 +107,8 @@ def test_call_submit_beta_1_a(self):
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 's', '-y', '--no-open', url, 'a.py'], stdout=sys.stdout, stderr=sys.stderr)

@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_beta_3_b(self):
if 'CI' in os.environ:
print('NOTE: this test is skipped since login is required')
return

url = 'https://codeforces.com/contest/3/problem/B'
code = r'''#include <bits/stdc++.h>
Expand Down Expand Up @@ -190,3 +180,46 @@ def test_call_submit_beta_3_b(self):
ojtools = os.path.abspath('oj')
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 's', '-y', '--no-open', url, 'main.cpp'], stdout=sys.stdout, stderr=sys.stderr)


class SubmitYukicoderTest(unittest.TestCase):
@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_9000(self):

url = 'https://yukicoder.me/problems/no/9000'
code = '\n'.join([
'#!/usr/bin/env python2',
'print "Hello World!"',
]) + '\n'
files = [
{
'path': 'a.py',
'data': code
},
]
ojtools = os.path.abspath('oj')
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 's', '-y', '--no-open', url, 'a.py'], stdout=sys.stdout, stderr=sys.stderr)

@unittest.skipIf('CI' in os.environ, 'login is required')
def test_call_submit_beta_3_b(self):

url = 'https://yukicoder.me/problems/no/9001'
code = r'''#include <bits/stdc++.h>
using namespace std;
int main() {
int a, b; cin >> a >> b;
string s; cin >> s;
cout << a + b << ' ' << s << endl;
return 0;
}
'''
files = [
{
'path': 'main.cpp',
'data': code
},
]
ojtools = os.path.abspath('oj')
with tests.utils.sandbox(files):
subprocess.check_call([ojtools, 's', '-y', '--no-open', url, 'main.cpp'], stdout=sys.stdout, stderr=sys.stderr)
14 changes: 7 additions & 7 deletions tests/continuous_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@
class ContinuousIntegrationTest(unittest.TestCase):
"""A dummy test to run the commands same to CI on local environments"""

@unittest.skipIf('CI' in os.environ, 'the same command is call from .travis.yml')
def test_isort(self):
if 'CI' not in os.environ:
subprocess.check_call(['isort', '--check-only', '--diff', '--recursive'] + paths, stdout=sys.stdout, stderr=sys.stderr)
subprocess.check_call(['isort', '--check-only', '--diff', '--recursive'] + paths, stdout=sys.stdout, stderr=sys.stderr)

@unittest.skipIf('CI' in os.environ, 'the same command is call from .travis.yml')
def test_yapf(self):
if 'CI' not in os.environ:
output = subprocess.check_output(['yapf', '--diff', '--recursive'] + paths, stderr=sys.stderr)
self.assertEqual(output, b'')
output = subprocess.check_output(['yapf', '--diff', '--recursive'] + paths, stderr=sys.stderr)
self.assertEqual(output, b'')

@unittest.skipIf('CI' in os.environ, 'the same command is call from .travis.yml')
def test_mypy(self):
if 'CI' not in os.environ:
subprocess.check_call(['mypy', '--show-traceback'] + paths, stdout=sys.stdout, stderr=sys.stderr)
subprocess.check_call(['mypy', '--show-traceback'] + paths, stdout=sys.stdout, stderr=sys.stderr)

0 comments on commit 2d9d6ee

Please sign in to comment.