Files
bitoj_python/python/datasource.py

543 lines
19 KiB
Python
Executable File

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import os
import sys
import socket
import time
import xmlrpc.client as xmlrpclib
import bz2
import http.cookies as Cookie
import unittest
from engineconfig import getConfig
from entity import Submit, Problem, TestCase, PresetCode, DataFile
class DataSourceError(Exception):
pass
class DataSource:
def __init__(self, driver):
self.driver = driver
self.config = getConfig()
self.logger = logging.getLogger('main')
self.judge_id = self.driver.get_judge_id()
def _get_config_retry(self):
ret = self.config.retry
if ret < 1:
ret = 1
return ret
def _get_config_retry_wait(self):
ret = self.config.retry_wait
if ret < 0.1:
ret = 0.1
return ret
def _do_action(self, func, args):
retry = self._get_config_retry()
retry_wait = self._get_config_retry_wait()
ret = None
while retry > 0:
try:
ret = func(*args)
except DataSourceError:
self.logger.exception("DataSourceError")
if retry > 0:
time.sleep(retry_wait)
retry = retry - 1
continue
else:
self.logger.info('Quit after retried.')
sys.exit(1)
break
return ret
def get_submits(self, limit):
func = self.driver.get_submits
args = (self.judge_id, limit)
rows = self._do_action(func, args)
ret = []
if rows:
for row in rows:
ret.append(Submit(self, row))
return ret
def reset_submits(self):
func = self.driver.reset_submits
args = (self.judge_id,)
return self._do_action(func, args)
def get_problem(self, pid):
func = self.driver.get_problem
args = (pid, )
return Problem(self, self._do_action(func, args))
def get_tests(self, pid, full):
func = self.driver.get_tests
args = (pid, full)
rows = self._do_action(func, args)
ret = []
if rows:
for row in rows:
ret.append(TestCase(self, row))
return ret
def get_test(self, tid, raw=True):
func = self.driver.get_test
args = (tid, )
row = self._do_action(func, args)
if raw:
return row
else:
return TestCase(self, row)
def get_presetcodes(self, pid, lang):
func = self.driver.get_presetcodes
args = (pid, lang)
rows = self._do_action(func, args)
return list(map(lambda row: PresetCode(self, row), rows))
def get_datafiles(self, pid):
func = self.driver.get_datafiles
args = (pid, )
rows = self._do_action(func, args)
return list(map(lambda row: DataFile(self, row), rows))
def get_datafile_data(self, datafileid):
func = self.driver.get_datafile_data
args = (datafileid, )
return self._do_action(func, args)
def update_submits_status(self, sids, status):
func = self.driver.update_submits_status
if not isinstance(sids, (tuple, list)):
sids = (sids, )
args = (sids, status)
self.logger.debug('set submits %s status to %s', sids.__str__(), status)
return self._do_action(func, args)
def update_submit_compilemessage(self, sid, msg):
func = self.driver.update_submit_compilemessage
args = (sid, msg)
return self._do_action(func, args)
def get_submit_status(self, sid):
func = self.driver.get_submit_status
args = (sid, )
return self._do_action(func, args)
def get_submit_compilemessage(self, sid):
func = self.driver.get_submit_compilemessage
args = (sid, )
return self._do_action(func, args)
def update_submit_test_results(self, sid, results):
func = self.driver.update_submit_test_results
args = (sid, results)
self.logger.info('update_submits_status: submit %s, %d records' % \
(sid, len(results)))
return self._do_action(func, args)
def get_submit(self, sid):
func = self.driver.get_submit
args = (sid, )
ret = self._do_action(func, args)
return Submit(self, ret)
class JspAuthTransport(xmlrpclib.Transport):
def __init__(self):
xmlrpclib.Transport.__init__(self)
self.__cookies = Cookie.SimpleCookie()
def request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
h = self.make_connection(host)
if verbose:
h.set_debuglevel(1)
self.send_request(h, handler, request_body)
self.__sendJsessionCookie(h)
self.send_host(h, host)
self.send_user_agent(h)
self.send_content(h, request_body)
errcode, errmsg, headers = h.getreply()
if errcode != 200:
raise xmlrpclib.ProtocolError(
host + handler,
errcode, errmsg,
headers
)
self.verbose = verbose
self.__processCookies(headers)
return self.parse_response(h.getfile())
def get_jsession_id(self):
if 'MoodleSession' in self.__cookies:
return self.__cookies['MoodleSession'].value
return None
def __sendJsessionCookie(self, connection):
if 'MoodleSession' in self.__cookies:
connection.putheader(
'Cookie',
'MoodleSession=%s' % self.__cookies['MoodleSession'].value)
if 'MoodleSessionTest' in self.__cookies:
connection.putheader(
'Cookie',
'MoodleSessionTest=%s' % self.__cookies['MoodleSessionTest'].value)
def __processCookies(self, headers):
if headers.getheader('Set-Cookie'):
self.__cookies.load(headers.getheader('Set-Cookie'))
def send_content(self, connection, request_body):
connection.putheader("Content-Type", "text/xml")
connection.putheader("Content-Length", str(len(request_body)))
connection.endheaders()
if request_body:
connection.send(request_body)
class XmlRpcDataSource:
def __init__(self, url):
self.transport = JspAuthTransport()
self.server = xmlrpclib.Server(url, transport = self.transport)
self.config = getConfig()
self.logger = logging.getLogger('main')
def get_judge_id(self):
while True:
try:
return self.server.oj.get_judge_id()
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_judge_id')
time.sleep(self.config.retry_wait)
def reset_submits(self, judgeid):
while True:
try:
return self.server.oj.reset_submits(judgeid)
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to reset_submits')
time.sleep(self.config.retry_wait)
def get_submits(self, judgeid, limit):
while True:
try:
submits = self.server.oj.get_submits(judgeid, limit)
for submit in submits:
if not isinstance(submit['code'], str):
submit['code'] = submit['code'].__str__()
return submits
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
time.sleep(self.config.retry_wait)
self.logger.exception('Failed to get_submits')
def get_problem(self, problemid):
while True:
try:
return self.server.oj.get_problem(problemid)
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_problem')
time.sleep(self.config.retry_wait)
def get_tests(self, problemid, full):
while True:
try:
tests = self.server.oj.get_tests(problemid, full)
for test in tests:
if not isinstance(test['input'], str):
test['input'] = test['input'].__str__()
if not isinstance(test['output'], str):
test['output'] = test['output'].__str__()
self.logger.debug('Got %d test case(s)', len(tests))
return tests
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_tests')
time.sleep(self.config.retry_wait)
def get_test(self, testid):
while True:
try:
test = self.server.oj.get_gztest(testid)
if not isinstance(test['input'], str):
test['input'] = test['input'].__str__()
if not isinstance(test['output'], str):
test['output'] = test['output'].__str__()
test['input'] = bz2.decompress(test['input'])
test['output'] = bz2.decompress(test['output'])
return test
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_tests')
time.sleep(self.config.retry_wait)
def get_presetcodes(self, problemid, lang):
while True:
try:
codes = self.server.oj.get_presetcodes(problemid, lang)
for code in codes:
if not isinstance(code['code'], str):
code['code'] = code['code'].__str__()
self.logger.debug('Got %d presetcodes', len(codes))
return codes
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_presetcodes')
time.sleep(self.config.retry_wait)
def get_datafiles(self, problemid):
while True:
try:
files = self.server.oj.get_datafiles(problemid)
self.logger.debug('Got %d datafiles', len(files))
return files
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_datafiles')
time.sleep(self.config.retry_wait)
def get_datafile_data(self, datafileid):
while True:
try:
data = self.server.oj.get_datafile_data(datafileid)
return str(data)
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_datafiles')
time.sleep(self.config.retry_wait)
def update_submit_compilemessage(self, id, compilemsg):
compilemsg = xmlrpclib.Binary(compilemsg)
while True:
try:
return self.server.oj.update_submit_compilemessage(
id, compilemsg)
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to update_submit_compilemessage')
time.sleep(self.config.retry_wait)
def update_submit_test_results(self, id, results):
for r in results:
if not isinstance(r['stdout'], str): r['stdout'] = ''
if not isinstance(r['stderr'], str): r['stderr'] = ''
r['stdout'] = xmlrpclib.Binary(r['stdout'])
r['stderr'] = xmlrpclib.Binary(r['stderr'])
while True:
try:
return self.server.oj.update_submit_test_results(id, results)
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to update_submit_compilemessage')
time.sleep(self.config.retry_wait)
def update_submits_status(self, submitids, newstatus):
while True:
mc = xmlrpclib.MultiCall(self.server)
for id in submitids:
mc.oj.update_submit_status(id, newstatus)
try:
return mc()
except xmlrpclib.Error as e:
raise DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to update_submits_status')
time.sleep(self.config.retry_wait)
def get_submit_status(self, sid):
while True:
try:
return self.server.oj.get_submit_status(sid)
except xmlrpclib.Error as e:
return DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_submit_status')
time.sleep(self.config.retry_wait)
def get_submit_compilemessage(self, sid):
while True:
try:
msg = self.server.oj.get_submit_compilemessage(sid)
if isinstance(msg, str):
return msg
else:
return msg.__str__()
except xmlrpclib.Error as e:
return DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_submit_status')
time.sleep(self.config.retry_wait)
def get_submit(self, sid):
while True:
try:
return self.server.oj.get_submit(sid)
except xmlrpclib.Error as e:
return DataSourceError(e)
except socket.error as e:
self.logger.exception('Failed to get_submit')
time.sleep(self.config.retry_wait)
class DataSourceTest(unittest.TestCase):
def setUp(self):
exec(open(os.path.join('..', 'testdata', 'test_config.py')).read())
self.config = getConfig()
self.datasource = self.config.datasources[0]
self.dbname = os.path.join('..', 'testdata', self.config.testdb)
def testGetSubmits(self):
submits = self.datasource.get_submits(4)
self.assertTrue(len(submits), 4)
s = submits[0]
self.assertEqual(s.id, '0000000001')
self.assertEqual(s.problem_id, '0000000001')
self.assertEqual(s.language, 'gcc-3.3')
self.assertEqual(s.code, '')
s = submits[1]
self.assertEqual(s.code, '#include <stdio.h>')
def testGetSubmit(self):
s = self.datasource.get_submit(1)
self.assertEqual(s.id, '0000000001')
self.assertEqual(s.problem_id, '0000000001')
self.assertEqual(s.language, 'gcc-3.3')
self.assertEqual(s.code, '')
def testResetSubmits(self):
self.datasource.reset_submits()
def testGetProblem(self):
p = self.datasource.get_problem('0000000001')
self.assertEqual(p.id, '0000000001')
def testGetTests(self):
tests = self.datasource.get_tests('0000000001', True)
self.assertTrue(len(tests), 2)
t = tests[0]
self.assertEqual(t.id, '0000000001')
self.assertEqual(t.problem_id, '0000000001')
self.assertEqual(t.timemodified, 0)
self.assertEqual(t.input, '1 2')
self.assertEqual(t.output, '3')
self.assertEqual(t.timelimit, 10)
self.assertEqual(t.memlimit, 1024 * 1024)
def testGetTest(self):
t = self.datasource.get_test('0000000001', False)
self.assertEqual(t.id, '0000000001')
self.assertEqual(t.problem_id, '0000000001')
self.assertEqual(t.timemodified, 0)
self.assertEqual(t.input, '1 2')
self.assertEqual(t.output, '3')
self.assertEqual(t.timelimit, 10)
self.assertEqual(t.memlimit, 1024 * 1024)
def testUpdateSubmitCompileMessage(self):
self.datasource.update_submit_compilemessage('0000000001', 'hello')
msg = self.datasource.get_submit_compilemessage('0000000001')
self.assertEqual(msg, 'hello')
self.datasource.update_submit_compilemessage('0000000001', '')
def testUpdateSubmitStatus(self):
self.datasource.update_submits_status(('0000000004', '0000000001'),
'compiling')
s = self.datasource.get_submit_status('0000000001')
self.assertEqual(s, 'compiling')
s = self.datasource.get_submit_status('0000000004')
self.assertEqual(s, 'compiling')
s = self.datasource.get_submit_status('0000000002')
self.assertEqual(s, 'new')
self.datasource.update_submits_status(('0000000004', '0000000001'), 'new')
s = self.datasource.get_submit_status('0000000001')
self.assertEqual(s, 'new')
def testUpdateSubmitTestResults(self):
from pysqlite2 import dbapi2 as sqlite
conn = sqlite.connect(self.dbname)
cur = conn.cursor()
cur.execute('DELETE FROM submit_test_result')
conn.commit()
cur.close()
conn.close()
id = '0000000001'
r1 = { 'test_id' : '0000000001',
'stdout' : '3',
'stderr' : '',
'exitcode' : 0,
'signal' : 0,
'timeused' : 0,
'memused' : 0}
r2 = { 'test_id' : '0000000002',
'stdout' : '4',
'stderr' : 'abc',
'exitcode' : 0,
'signal' : 11,
'timeused' : 1,
'memused' : 2}
self.datasource.update_submit_test_results(id, (r1, r2))
conn = sqlite.connect(self.dbname)
cur = conn.cursor()
cur.execute('SELECT * FROM submit_test_result')
rows = cur.fetchall()
self.assertEqual(len(rows), 2)
cur.close()
conn.close()
row = rows[0]
self.assertEqual(row[1], 1) #sid
self.assertEqual(row[2], 1) #tid
self.assertEqual(row[3], '3') #stdout
self.assertEqual(row[4], '') #stderr
self.assertEqual(row[5], 0) #exitcode
self.assertEqual(row[6], 0) #signal
self.assertEqual(row[7], 0) #timeused
self.assertEqual(row[8], 0) #memused
row = rows[1]
self.assertEqual(row[1], 1) #sid
self.assertEqual(row[2], 2) #tid
self.assertEqual(row[3], '4') #stdout
self.assertEqual(row[4], 'abc') #stderr
self.assertEqual(row[5], 0) #exitcode
self.assertEqual(row[6], 11) #signal
self.assertEqual(row[7], 1) #timeused
self.assertEqual(row[8], 2) #memused
if __name__ == '__main__':
unittest.main()
# vim: set expandtab tabstop=4 shiftwidth=4: