first commit

This commit is contained in:
2025-10-11 11:23:58 +08:00
commit e8774085f8
48 changed files with 3087 additions and 0 deletions

528
python/datasource.py Executable file
View File

@@ -0,0 +1,528 @@
import logging, os, sys, socket, time, xmlrpclib, bz2, 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, e:
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 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 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.SmartCookie()
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 self.__cookies.has_key('MoodleSession'):
return self.__cookies['MoodleSession'].value
return None
def __sendJsessionCookie(self, connection):
if self.__cookies.has_key('MoodleSession'):
connection.putheader(
'Cookie',
'MoodleSession=%s' % self.__cookies['MoodleSession'].value)
if self.__cookies.has_key('MoodleSessionTest'):
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, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, unicode)):
submit['code'] = submit['code'].__str__()
return submits
except xmlrpclib.Error, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, unicode)):
test['input'] = test['input'].__str__()
if not isinstance(test['output'], (str, unicode)):
test['output'] = test['output'].__str__()
self.logger.debug('Got %d test case(s)', len(tests))
return tests
except xmlrpclib.Error, e:
raise DataSourceError(e)
except socket.error, 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, unicode)):
test['input'] = test['input'].__str__()
if not isinstance(test['output'], (str, unicode)):
test['output'] = test['output'].__str__()
test['input'] = bz2.decompress(test['input'])
test['output'] = bz2.decompress(test['output'])
return test
except xmlrpclib.Error, e:
raise DataSourceError(e)
except socket.error, 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, unicode)):
code['code'] = code['code'].__str__()
self.logger.debug('Got %d presetcodes', len(codes))
return codes
except xmlrpclib.Error, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, unicode)): r['stdout'] = ''
if not isinstance(r['stderr'], (str, unicode)): 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, e:
raise DataSourceError(e)
except socket.error, 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, e:
raise DataSourceError(e)
except socket.error, 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, e:
return DataSourceError(e)
except socket.error, 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, unicode)):
return msg
else:
return msg.__str__()
except xmlrpclib.Error, e:
return DataSourceError(e)
except socket.error, 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, e:
return DataSourceError(e)
except socket.error, e:
self.logger.exception('Failed to get_submit')
time.sleep(self.config.retry_wait)
class DataSourceTest(unittest.TestCase):
def setUp(self):
execfile(os.path.join('..', 'testdata', 'test_config.py'))
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:

BIN
python/datasource.pyc Normal file

Binary file not shown.

69
python/engine.py Executable file
View File

@@ -0,0 +1,69 @@
import logging, os, thread, threading, time, Queue
from engineconfig import getConfig
class JudgeEngine:
def __init__(self):
self.quit_event = threading.Event()
self.test_queue = Queue.Queue()
self.config = getConfig()
self.quit_event.clear()
self.logger = logging.getLogger('main')
def run(self):
# one thread mode is good for debugging
if self.config.test_threads == 1:
thread.start_new_thread(self.transport, ())
self.test()
else:
for i in range(self.config.test_threads):
thread.start_new_thread(self.test, ())
self.transport()
for ds in self.config.datasources:
ds.reset_submits()
self.logger.info('main thread quit')
return
def transport(self):
total = 0
while not self.quit_event.isSet():
self.logger.info(
"%d submit in test queue, %d processed" % \
(self.test_queue.qsize(), total))
c = 16 - self.test_queue.qsize()
if c > 0:
submits = self.config.datasources[0].get_submits(c)
for submit in submits:
self.logger.info('Add submit %s to queue' % submit.id)
self.test_queue.put(submit)
total = total + len(submits)
time.sleep(self.config.fetch_interval)
self.logger.info("fetch thread quit")
return
def test(self):
while not self.quit_event.isSet():
#pdb.set_trace()
try:
submit = self.test_queue.get(True, self.config.fetch_interval)
except Queue.Empty:
continue
tester = self.config.get_tester(submit.language)
if tester:
tester.test(submit)
else:
submit.set_status('compile_failed')
submit.set_compilemessage('No compiler specified')
self.logger.info("test thread quit")
return
def quit(self, signum, frame):
self.logger.info('signal receive: %d' % signum)
self.quit_event.set()
return
class JudgeError(Exception): pass
# vim: set expandtab tabstop=4 shiftwidth=4:

BIN
python/engine.pyc Normal file

Binary file not shown.

239
python/engineconfig.py Executable file
View File

@@ -0,0 +1,239 @@
#!/usr/bin/env python2.6
import string
from string import split
from os import path
from Queue import Queue
from subprocess import Popen, PIPE
class EngineConfig:
def __init__(self):
self.profile = 'default'
self.test_threads = 2
self.retry = 10
self.retry_wait = 5
self.datasources = []
self.fetch_interval = 1
self.mips = 1000
self.languages = {}
self.maxmem = 65536
self.maxtime = 300
self.compile_timelimit = 30
self.output_size_limit = 1024 * 1024 * 2
self.output_sendback_size_limit = 1024 * 64
self.logdir = 'log'
self.debug = False
self.judgescript_wait = 10
self.python = '/usr/bin/python3'
self.no_cleanup = False
self.datadir = path.join('data', self.profile)
self.rundir_root = '/var/lib/bitoj'
self.runas = Queue()
self.setup_testers()
# Init runas with ojrun?? users
pa = Popen(['getent', 'passwd'], stdout=PIPE)
pb = Popen(['awk', '-F:', '/^ojrun[0-9][0-9]:/{print $1}'],
stdin=pa.stdout, stdout=PIPE)
output = pb.communicate()[0]
if output:
for user in string.split(output, '\n'):
user = string.strip(user)
if user: self.runas.put(user)
pa.wait()
pb.wait()
def add_tester(self, profile, tester):
self.languages[profile] = tester
def get_tester(self, profile):
if self.languages.has_key(profile):
return self.languages[profile]
def add_datasource(self, ds):
from datasource import DataSource
self.datasources.append(DataSource(ds))
def add_xmlrpc_datasource(self, url):
from datasource import XmlRpcDataSource
self.add_datasource(XmlRpcDataSource(url))
def setup_testers(self):
default_compileguard = (
'<judgehome>/scripts/compile-guard', ' <datadir>'
)
default_runguard = split(
'/usr/bin/sudo -u <user> <judgehome>/scripts/binary-guard ' +
'-e <extraproc> ' +
'-t <timelimit> -T 5 -m <maxmem> -d <rundir> -o <statfile> -p -x',
' '
)
maxmem_runguard = split(
'/usr/bin/sudo -u <user> <judgehome>/scripts/binary-guard ' +
'-e <extraproc> ' +
'-t <timelimit> -T 5 -m <maxmem> -d <rundir> -o <statfile> -p -x',
' '
)
java_runguard = split(
'/usr/bin/sudo -u <user> ' +
'<judgehome>/scripts/java-guard -t <timelimit> -T 5 -m 262144 ' +
'-e <extraproc> ' +
'-d <rundir> -o <statfile> -p -x', ' '
)
python_runguard = split(
'<judgehome>/scripts/python-guardd ' +
'-t <timelimit> -T 5 -m <maxmem> -d <rundir> -o <statfile> -p -x',
' '
)
mono_runguard = split(
'/usr/bin/sudo -u <user> ' +
'<judgehome>/scripts/mono-guard -t <timelimit> -T 5 -m <maxmem> ' +
'-e <extraproc> ' +
'-d <rundir> -o <statfile> -p -x', ' '
)
bash_runguard = split(
'/usr/bin/sudo -u <user> <judgehome>/scripts/bash-guard ' +
'-e <extraproc> ' +
'-t <timelimit> -T 5 -m <maxmem> -d <rundir> -o <statfile> -p -x',
' '
)
default_compare = (
'<judgehome>/scripts/compare-guard', '<language>', '<codefile>',
'<stdinfile>', '<stdoutfile>', '<resultfile>'
)
# Options for testers
from tester import SimpleTester, ComboTester
cbc = SimpleTester(source = 'main.c',
target = 'main',
compile = ('<judgehome>/scripts/gcc-3.3-bc',),
run = ('<datadir>/main', ),
runenv = {
'GCC_BOUNDS_OPTS' : '-no-message -no-statistics -no-check-mmap',
},
basemem = {'Data' : 48, 'Stack' : 84 },
compileguard = default_compileguard,
runguard = maxmem_runguard,
)
cnobc = SimpleTester(
source = 'main.c', target = 'main',
compile = ('<judgehome>/scripts/gcc-3.3-nobc',),
run = ('<datadir>/main', ),
runenv = {},
basemem = {'Data' : 28, 'Stack' : 84 },
compileguard = default_compileguard,
runguard = default_runguard,
)
gcc33 = ComboTester()
gcc33.add_tester(cbc, has_timelimit = False, has_memlimit = False,
check_result = True, is_lasttest = False)
gcc33.add_tester(cnobc, has_timelimit = True, has_memlimit = True,
check_result = True, is_lasttest = True)
gcc33.comparecmd = default_compare
gcc33.source = 'main.c'
gxx33 = SimpleTester(
source = 'main.cpp', target = 'main',
compile = ('<judgehome>/scripts/g++-3.3',),
run = ('<datadir>/main',),
runenv = {},
basemem = {'Data' : 52, 'Stack' : 84 },
compileguard = default_compileguard,
runguard = default_runguard,
comparecmd = default_compare,
)
j2se15 = SimpleTester(
source = 'Main.java', target = 'Main.class',
compile = ('<judgehome>/scripts/javac-1.5',),
run = split('/usr/bin/java -cp <datadir> -Xms8M -Xmx64M Main'),
runenv = {},
basemem = {'RSS' : 7560 },
baseproc = 8,
compileguard = default_compileguard,
runguard = java_runguard,
comparecmd = default_compare,
)
j2se16 = SimpleTester(
source = 'Main.java', target = 'Main.class',
compile = ('<judgehome>/scripts/javac-1.6',),
run = split('/usr/bin/java -cp <datadir> -Xms8M -Xmx64M Main'),
runenv = {},
basemem = {'RSS' : 7560 },
baseproc = 8,
compileguard = default_compileguard,
runguard = java_runguard,
comparecmd = default_compare,
)
fpc = SimpleTester(
source = 'main.pas', target = 'main',
compile = ('<judgehome>/scripts/fpc',),
run = ('<datadir>/main', ),
runenv = {},
basemem = {'Data' : 32, 'Stack' : 84 },
compileguard = default_compileguard,
runguard = default_runguard,
comparecmd = default_compare,
)
python3 = SimpleTester(
source = 'main.py', target = 'main.py',
compile = ('/bin/true',),
run = ('/usr/bin/python3', '<datadir>/main.py'),
runenv = {},
basemem = {'RSS' : 2048 },
compileguard = (),
runguard = python_runguard,
comparecmd = default_compare,
)
gmcs20 = SimpleTester(
source = 'main.cs', target = 'main.exe',
compile = ('<judgehome>/scripts/gmcs-2.0',),
run = ('/usr/bin/mono', '<datadir>/main.exe'),
runenv = { 'MONO_SHARED_DIR' : '<datadir>' },
basemem = {'RSS' : 8192 },
compileguard = (),
runguard = mono_runguard,
comparecmd = default_compare,
)
bash3 = SimpleTester(
source = 'main.sh', target = 'main.sh',
compile = ('/bin/true',),
run = ('/bin/bash', '<datadir>/main.sh'),
runenv = {},
basemem = {'RSS' : 2048 },
extraproc = 3,
compileguard = (),
runguard = bash_runguard,
comparecmd = default_compare,
)
self.add_tester('gcc-3.3', gcc33)
self.add_tester('gcc-3.3-nobc', cnobc)
self.add_tester('gcc-3.3-bc', cbc)
self.add_tester('g++-3.3', gxx33)
self.add_tester('java-1.5', j2se15)
self.add_tester('java-1.6', j2se16)
self.add_tester('fpc-2.2', fpc)
self.add_tester('python3', python3)
self.add_tester('gmcs-2.0', gmcs20)
self.add_tester('bash-3', bash3)
config = None
def getConfig():
global config
if not config:
config = EngineConfig()
return config
# vim: set expandtab tabstop=4 shiftwidth=4:

BIN
python/engineconfig.pyc Normal file

Binary file not shown.

271
python/entity.py Executable file
View File

@@ -0,0 +1,271 @@
import string, os, stat, threading, logging, bz2
import unittest
from engineconfig import getConfig
from judgescript import InternalJudge, ExternalJudge
class Problem:
def __init__(self, datasource, row):
self.tests = None
self.presetcodes = {}
self.datafiles = None
self.datasource = datasource
self.id = row['id']
self.timemodified = row['timemodified']
self.vcode = row['validator_code']
if not isinstance(self.vcode, (str, unicode)):
self.vcode = self.vcode.__str__()
self.vtype = row['validator_type']
self.vlang = row['validator_lang']
self.gcode = row['generator_code']
if not isinstance(self.gcode, (str, unicode)):
self.gcode = self.vcode.__str__()
self.gtype = row['generator_type']
self.standard_code = row['standard_code']
if row.has_key('input_filename') and row['input_filename']:
self.input_filename = row['input_filename']
else:
self.input_filename = None
if row.has_key('output_filename') and row['output_filename']:
self.output_filename = row['output_filename']
else:
self.output_filename = None
def get_judge_script(self):
if self.vtype == 'comparetext':
return InternalJudge()
elif self.vtype == 'comparetextwithpe':
return InternalJudge(allowpe = True)
elif self.vtype == 'comparefile':
code = open('scripts/compare-file.py').read()
return ExternalJudge(self.id, 'python-2.5', code)
elif self.vtype == 'customized':
return ExternalJudge(self.id, self.vlang, self.vcode)
def get_testcases(self):
if not self.tests:
self.tests = self.datasource.get_tests(self.id, False)
for testcase in self.tests:
testcase.problem = self
return self.tests
def get_input_filename(self):
return self.input_filename
def get_output_filename(self):
return self.output_filename
def get_presetcodes(self, lang):
if not self.presetcodes.has_key(lang):
codes = self.datasource.get_presetcodes(self.id, lang)
for code in codes: code.problem = self
self.presetcodes[lang] = codes
return self.presetcodes[lang]
def get_datafiles(self):
if not self.datafiles:
self.datafiles = self.datasource.get_datafiles(self.id)
return self.datafiles
class Submit:
def __init__(self, datasource, row):
self.datasource = datasource
self.problem = None
self.id = row['id']
self.problem_id = row['problem_id']
self.language = row['language']
self.code = row['code']
def get_problem(self):
if not self.problem:
self.problem = self.datasource.get_problem(self.problem_id)
return self.problem
def get_testcases(self):
return self.get_problem().get_testcases()
def get_judge_script(self):
return self.get_problem().get_judge_script()
def get_input_filename(self):
return self.get_problem().get_input_filename()
def get_output_filename(self):
return self.get_problem().get_output_filename()
def get_presetcodes(self):
return self.get_problem().get_presetcodes(self.language)
def set_compilemessage(self, msg):
return self.datasource.update_submit_compilemessage(self.id, msg)
def set_status(self, newstatus):
return self.datasource.update_submits_status(self.id, newstatus)
def get_status(self):
return self.datasource.get_submit_status(self.id)
def get_compilemessage(self):
return self.datasource.get_submit_compilemessage(self.id)
def update_test_results(self, results):
config = getConfig()
newresults = []
for r in results:
# stdout
f = file(r[4], 'r')
r[4] = f.read(config.output_sendback_size_limit)
f.close()
# stderr
f = file(r[5], 'r')
r[5] = f.read(config.output_sendback_size_limit)
f.close()
# strip stdout and stderr send back to datasource
# preventing post data too big
if len(r[4]) > config.output_sendback_size_limit:
r[4] = r[4][0:config.output_sendback_size_limit]
if len(r[5]) > config.output_sendback_size_limit:
r[5] = r[5][0:config.output_sendback_size_limit]
newresults.append({ 'test_id' : r[0],
'judge_result' : r[1],
'exitcode' : r[2],
'signal' : r[3],
'stdout' : r[4],
'stderr' : r[5],
'timeused' : r[6],
'memused' : r[7] })
return self.datasource.update_submit_test_results(self.id, newresults)
class TestCase:
write_lock = threading.Lock()
def __init__(self, datasource, row):
config = getConfig()
logger = logging.getLogger('main')
self.id = row['id']
self.problem_id = row['problem_id']
self.timemodified = row['timemodified']
#self.input = string.replace(row['input'], '\r\n', '\n')
#self.output = string.replace(row['output'], '\r\n', '\n')
self.timelimit = row['timelimit']
if not self.timelimit:
self.timelimit = 1
self.memlimit = row['memlimit']
if not self.memlimit:
self.memlimit = config.maxmem
if row.has_key('nproc') and row['nproc']:
self.nproc = string.atoi(row['nproc'])
else:
self.nproc = 0
config = getConfig()
testdir = os.path.join(config.datadir, 'testcase')
if not os.path.exists(testdir): os.mkdir(testdir)
self.infile = os.path.join(testdir, self.id + '.in')
self.outfile = os.path.join(testdir, self.id + '.out')
TestCase.write_lock.acquire()
if os.path.exists(self.infile):
inmtime = os.stat(self.infile)[stat.ST_MTIME]
else:
inmtime = 0
if os.path.exists(self.outfile):
outmtime = os.stat(self.outfile)[stat.ST_MTIME]
else:
outmtime = 0
logger.debug("inmtime=%d outmtime=%d timemodified=%d" % (inmtime, outmtime, self.timemodified))
if inmtime <= self.timemodified or outmtime <= self.timemodified:
logger.debug('Creating input/output file %s and %s' % (self.infile, self.outfile))
row = datasource.get_test(self.id)
input = string.replace(row['input'], '\r\n', '\n')
output = string.replace(row['output'], '\r\n', '\n')
f = file(self.infile, 'w')
f.write(input)
if len(input) > 0 and input[-1] != '\n': f.write('\n')
f.close()
f = file(self.outfile, 'w')
f.write(output)
if len(output) > 0 and output[-1] != '\n': f.write('\n')
f.close()
logger.debug('Finished')
else:
logger.debug('Skip input/output file creation')
TestCase.write_lock.release()
def get_input_file(self):
return self.infile
def get_output_file(self):
return self.outfile
class PresetCode:
def __init__(self, datasource, row):
self.datasource = datasource
self.problem = None
self.id = row['id']
self.name = row['name']
self.code = row['code']
self.isheader = row['isheader']
class DataFile:
write_lock = threading.Lock()
def __init__(self, datasource, row):
self.datasource = datasource
self.problem = None
# Data passed from datasource
self.id = row['id']
self.problem_id = row['problem_id']
self.filename = row['filename']
self.type = row['type']
self.timemodified = row['timemodified']
# Save datafile
config = getConfig()
testdir = os.path.join(config.datadir, 'testcase')
if not os.path.exists(testdir): os.mkdir(testdir)
self.absolute_path = os.path.join(
testdir, self.problem_id + '.' + self.filename)
DataFile.write_lock.acquire()
mtime = 0
if os.path.exists(self.absolute_path):
mtime = os.stat(self.absolute_path)[stat.ST_MTIME]
if mtime < self.timemodified:
data = datasource.get_datafile_data(self.id)
data = bz2.decompress(data)
if self.type == 'text':
f = open(self.absolute_path, 'w')
f.write(string.replace(data, '\r\n', '\n'))
f.close()
else:
f = open(self.absolute_path, 'wb')
f.write(data)
f.close()
DataFile.write_lock.release()
if __name__ == '__main__':
unittest.main()
# vim: set expandtab tabstop=4 shiftwidth=4:

BIN
python/entity.pyc Normal file

Binary file not shown.

286
python/judgescript.py Executable file
View File

@@ -0,0 +1,286 @@
import string, os, time, tempfile, logging
import unittest
from engineconfig import getConfig
class InternalJudge:
def __init__(self, allowpe = False):
self.logger = logging.getLogger('main')
self.allowpe = allowpe
def judge(self, sid, tid, tin, tout, result, errfile, rundir = None):
"""Judge if the result correct.
@param sid String submit id
@param tid String testcase id
@param tin String filename of the standard input
@param tout String filename of the standard output
@param result String filename of the result
"""
return self.compare_file(tin, tout, result, self.allowpe)
def compare_file(self, input, output, result, allowpe):
fo = file(output, 'rb'); fr = file(result, 'rb')
if not allowpe:
r = 'AC'
while r == 'AC':
so = fo.read(8192); sr = fr.read(8192)
if so == '' and sr == '': break
if so != sr: r = 'WA'
else:
so = fo.read(); sr = fr.read()
r = self.compare_string(so, sr)
fo.close(); fr.close()
return r
def compare_string(self, output, result):
if output == result: return 'AC'
outnum = ''
retnum = ''
for c in output:
if c in string.digits: outnum += c
for c in result:
if c in string.digits: retnum += c
self.logger.debug('numbers in output: %s' % outnum)
self.logger.debug('numbers in result: %s' % retnum)
if len(outnum) > 0 and len(retnum) > 0 and outnum == retnum:
return 'PE'
return 'WA'
class ExternalJudge:
def __init__(self, problemid, vlang, vcode):
"""Constructor.
@param pid String problem id
@param vlang String validator language
@param vcode String validator code
"""
self.config = getConfig()
self.logger = logging.getLogger('main')
self.problemid = problemid
self.lang = vlang
self.code = vcode
# save validator code
tester = self.config.get_tester(self.lang)
datadir = os.path.join(self.config.datadir, 'validator', problemid)
if not os.path.exists(datadir):
os.makedirs(datadir)
self.comparecmd = tester.comparecmd
self.codefile = os.path.abspath(os.path.join(datadir, tester.source))
f = file(self.codefile, 'w')
f.write(string.replace(vcode, '\r\n', '\n'))
if len(vcode) > 0 and vcode[-1] != '\n': f.write('\n')
f.close()
self.logger.debug("Save validator code as %s" % self.codefile)
def judge(self, sid, tid, tin, tout, result, errfile, rundir = None):
"""Judge if the result correct.
@param sid String submit id
@param tid String testcase id
@param tin String filename of the standard input
@param tout String filename of the standard output
@param result String filename of the result
@param rundir String in which dir the judge script should be started
"""
rfiledir = os.path.join(self.config.datadir, sid)
rfile = os.path.join(rfiledir, tid + '.rst')
if not os.path.exists(rfiledir):
os.mkdir(rfiledir)
cmd = []
for s in self.comparecmd:
s = s.replace('<judgehome>', self.config.judgehome)
s = s.replace('<language>', self.lang)
s = s.replace('<codefile>', self.codefile)
s = s.replace('<stdinfile>', tin)
s = s.replace('<stdoutfile>', tout)
s = s.replace('<resultfile>', result)
cmd.append(s)
self.logger.debug("Run validator as %s" % cmd.__str__())
pid = os.fork()
if pid == 0:
os.close(1)
os.open(rfile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0666)
os.close(2)
os.open(errfile, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0666)
if rundir: os.chdir(rundir)
os.execv(cmd[0], cmd)
print 'WA'
remaintime = self.config.judgescript_wait
pid1 = 0
while remaintime > 0:
pid1, status = os.waitpid(pid, os.WNOHANG)
if pid1 > 0:
break
time.sleep(0.1)
remaintime -= 0.1
while pid1 == 0:
try:
os.kill(pid, 9)
except os.OSError, e:
pass
pid1, status = os.waitpid(pid, os.WNOHANG)
f = file(rfile, 'r')
ret = string.strip(f.readline())
f.close()
if ret != 'AC' and ret != 'PE': ret = 'WA'
return ret
class InternalJudgeTest(unittest.TestCase):
"""Internal Judge Test Case."""
def testWithoutPE(self):
ij = InternalJudge()
output = 'hello world\n'
result = 'hello world\n'
self.assertEqual(ij.compare_string(output, result), 'AC')
output = 'hello world'
self.assertEqual(ij.compare_string(output, result), 'WA')
result = 'hello world'
self.assertEqual(ij.compare_string(output, result), 'AC')
result = 'hello world '
self.assertEqual(ij.compare_string(output, result), 'WA')
def testWithPE(self):
ij = InternalJudge(True)
input = ''
output = 'hello world\n'
result = 'hello world\n'
self.assertEqual(ij.compare_string(output, result), 'AC')
result = 'hello worl\n'
self.assertEqual(ij.compare_string(output, result), 'WA')
output = '1 2 3 4 5\n'
result = '1\n2\n3\n4\n5\n'
self.assertEqual(ij.compare_string(output, result), 'PE')
result = '12345\n'
self.assertEqual(ij.compare_string(output, result), 'PE')
result = '1 2 3 4 5'
self.assertEqual(ij.compare_string(output, result), 'PE')
class ExternalJudgeTest(unittest.TestCase):
"""External Judge Test Case."""
def setUp(self):
config = getConfig()
config.judgehome = '../'
config.datadir = os.path.join('..', 'testdata')
self.tin = tempfile.NamedTemporaryFile('w')
self.tin.write('3')
self.tin.flush()
self.tout = tempfile.NamedTemporaryFile('w')
self.tout.write('3')
self.tout.flush()
self.rst = tempfile.NamedTemporaryFile('w')
self.rst.write('6')
self.rst.flush()
self.err = tempfile.NamedTemporaryFile('w')
self.err.close()
self.tin.close()
self.tout.close()
self.rst.close()
def testGCC33(self):
code = """
#include <stdio.h>
int main() {
printf("AC\\n");
return 0;
}
"""
self.j = ExternalJudge('p1', 'gcc-3.3', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testGPP33(self):
code = """
#include <iostream>
using namespace std;
int main()
{
cout << "AC" << endl;
return 0;
}
"""
self.j = ExternalJudge('p1', 'g++-3.3', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testFPC(self):
code = """
program main;
begin
write('AC');
end.
"""
self.j = ExternalJudge('p1', 'fpc-2.2', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testJava(self):
code = """
public class Main {
public static void main(String[] args) {
System.out.println("AC");
}
}
"""
self.j = ExternalJudge('p1', 'java-1.5', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
self.j = ExternalJudge('p1', 'java-1.6', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testPython(self):
code = """public class Main
{
public static void Main()
{
System.Console.WriteLine("AC");
}
}
"""
self.j = ExternalJudge('p1', 'gmcs-2.0', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testPython(self):
code = """#!/usr/bin/env python
print 'AC'
"""
self.j = ExternalJudge('p1', 'python-2.5', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
def testBash(self):
code = """echo AC"""
self.j = ExternalJudge('p1', 'bash-3', code)
x = self.j.judge('s1', 't1', self.tin.name, self.tout.name, self.rst.name, self.err.name)
self.assertEquals('AC', x)
if __name__ == '__main__':
unittest.main()
# vim: set expandtab tabstop=4 shiftwidth=4:

BIN
python/judgescript.pyc Normal file

Binary file not shown.

21
python/ojunit.py Executable file
View File

@@ -0,0 +1,21 @@
import unittest, logging, os, sys
from engineconfig import getConfig
class OJTestCase(unittest.TestCase):
def setUp(self):
logger = logging.getLogger('main')
hdlr = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
execfile(os.path.join('..', 'testdata', 'test_config.py'))
self.config = getConfig()
self.ds = self.config.datasources[0]
self.dbname = os.path.join('..', 'testdata', self.config.testdb)
self.logger = logger

BIN
python/ojunit.pyc Normal file

Binary file not shown.

829
python/tester.py Executable file
View File

@@ -0,0 +1,829 @@
import math, os, resource, signal, string, sys, threading, logging, time, pickle
import shutil, Queue
import unittest
import pdb
from engineconfig import getConfig
from ojunit import *
class TesterBase:
def __init__(self):
self.logger = logging.getLogger('main')
def get_datadir(self, submit):
config = getConfig()
# get datadir
datadir = os.path.abspath(config.datadir)
if not os.path.exists(datadir): os.mkdir(datadir)
# get filenames
datadir = os.path.join(datadir, submit.id)
if not os.path.exists(datadir): os.mkdir(datadir)
return datadir
def get_rundir(self, submit):
config = getConfig()
return os.path.join(config.rundir_root, submit.user, 'run')
class SimpleTester(TesterBase):
has_timelimit = True
has_memlimit = True
check_result = True
is_lasttest = True
def __init__(self,
source, target,
compile = None, compileenv = {},
run = None, runenv = {}, basemem = 0,
baseproc = 0, extraproc = 0,
compileguard = None, runguard = None, comparecmd = None):
TesterBase.__init__(self)
self.source = source
self.target = target
self.compilecmd = compile
self.compileenv = compileenv
self.runcmd = run
self.runenv = runenv
self.basemem = basemem
self.baseproc = baseproc
self.extraproc = extraproc
self.compileguard = compileguard
self.runguard = runguard
self.comparecmd = comparecmd
def test(self, submit):
#pdb.set_trace()
if not self.prepare(submit):
self.logger.info('self.prepare')
submit.set_status('compile_failed')
submit.set_compilemessage('Failed to delete source/target file')
return False
submit.set_status('compiling')
if not self.compile(submit):
self.logger.info('self.compile')
submit.set_status('compile_failed')
self.cleanup(submit)
return False
submit.set_status('running')
testcases = submit.get_testcases()
ret = []
for testcase in testcases:
r = self.run(submit, testcase)
ret.append(r)
self.logger.debug('run result: ' + r.__str__())
passed = True
if self.check_result:
for r in ret:
if r[1] == 'AC' or not self.has_timelimit and r[1] == 'TLE':
pass
else:
passed = False
if not passed or self.is_lasttest:
submit.update_test_results(ret)
submit.set_status('finish')
self.cleanup(submit)
return passed
def prepare(self, submit):
datadir = self.get_datadir(submit)
# write presetcodes to dir
presetcodes = submit.get_presetcodes()
for presetcode in presetcodes:
pcname = os.path.join(datadir, presetcode.name)
if os.path.exists(pcname):
try:
os.unlink(pcname)
except OSError, e:
self.logger.exception(
"Failed to delete presetcode file %s" % pcname)
return False
f = open(pcname, 'w')
f.write(string.replace(presetcode.code, '\r\n', '\n'))
f.write('\n');
f.close()
# delete existing source and target file
datadirsource = os.path.join(datadir, self.source)
datadirtarget = os.path.join(datadir, self.target)
if os.path.exists(datadirsource):
try:
os.unlink(datadirsource)
except OSError, e:
self.logger.exception("Failed to delete source")
return False
if os.path.exists(datadirtarget):
try:
os.unlink(datadirtarget)
except OSError, e:
self.logger.exception("Failed to delete target")
return False
# preprocess source code
code = string.replace(submit.code, '\r\n', '\n')
code = string.replace(code, chr(0x1a), '') # char generated by tc
code = string.replace(code, 'getch()', '')
code = string.replace(code, 'getch ()', '')
code = string.replace(code, 'getch ( )', '')
code = string.replace(code, '\r\n', '\n')
# write source to disk
f = open(datadirsource, 'w')
f.write(code)
if len(submit.code) > 0 and submit.code[-1] != '\n': f.write('\n')
f.close()
# setup rundir
config = getConfig()
try:
submit.user = config.runas.get_nowait()
except Queue.Empty:
self.logger.exception("No runas user left, please create more!")
return False
rundir = self.get_rundir(submit)
if not os.path.exists(os.path.dirname(rundir)):
os.mkdir(os.path.dirname(rundir))
if os.path.exists(rundir):
try:
self._remove(rundir)
except OSError, e:
self.logger.exception("Failed to delete rundir")
config.runas.put(submit.user)
return False
os.mkdir(rundir)
os.chmod(rundir, 0775)
return True
def cleanup(self, submit, force = False):
datadir = self.get_datadir(submit)
rundir = self.get_rundir(submit)
config = getConfig()
if not config.no_cleanup or force:
if os.path.exists(datadir):
self._remove(datadir)
if os.path.exists(rundir):
try:
self._remove(rundir)
except OSError:
os.system('/usr/bin/sudo -u %s /bin/rm -rf %s' % (submit.user, rundir))
config.runas.put(submit.user)
def compile(self, submit):
config = getConfig()
datadir = self.get_datadir(submit)
cmd = []
if self.compileguard:
for s in self.compileguard:
s = string.replace(s, '<judgehome>', config.judgehome)
s = string.replace(s, '<datadir>', datadir)
cmd.append(s)
if self.compilecmd:
for s in self.compilecmd:
s = string.replace(s, '<judgehome>', config.judgehome)
s = string.replace(s, '<datadir>', datadir)
cmd.append(s)
cmd.append(self.source)
for code in submit.get_presetcodes():
if not code.isheader:
cmd.append(code.name)
self.logger.debug(string.join(cmd, '_'))
errfile = os.path.join(datadir, 'compile.err')
(exitcode, sig, timeused, memused) = \
self._execute(submit, cmd, timelimit = config.compile_timelimit,
infile = None, outfile = None, errfile = errfile,
env = self.compileenv)
compilemsg = None
if os.path.exists(errfile):
f = file(errfile, 'r')
compilemsg = string.join(f.readlines(), '')
f.close()
if compilemsg:
submit.set_compilemessage(compilemsg)
if exitcode == 0:
self.logger.info('submit %s compile success' % submit.id)
else:
self.logger.info('submit %s compile failed' % submit.id)
self.logger.debug(compilemsg)
return exitcode == 0
def run(self, submit, testcase):
config = getConfig()
datadir = self.get_datadir(submit) #dir save input and result
rundir = self.get_rundir(submit) #dir program runs in
infile = testcase.get_input_file()
outfile = os.path.join(datadir, testcase.id + '.out')
errfile = os.path.join(datadir, testcase.id + '.err')
statfile = os.path.join(rundir, testcase.id + '.stat')
# If submit input filename or output filename is provided,
# no input or output redirect will happen.
submit_input_filename = submit.get_input_filename()
submit_output_filename = submit.get_output_filename()
if submit_input_filename:
shutil.copyfile(infile, os.path.join(rundir, submit_input_filename))
_infile = infile
infile = None
if submit_output_filename:
_outfile = outfile
outfile = None
# Create data file symolic links
datafiles = submit.get_problem().get_datafiles()
for datafile in datafiles:
targetname = os.path.join(rundir, datafile.filename)
try:
shutil.copyfile(datafile.absolute_path, targetname)
except:
pass
# Use extra process setting on testcase if it exist
extraproc = self.baseproc + (testcase.nproc if testcase.nproc else self.extraproc)
cmd = []
if self.runguard:
for s in self.runguard:
s = s.replace('<judgehome>', config.judgehome)
s = s.replace('<extraproc>', '%d' % extraproc)
s = s.replace('<timelimit>', '%d' % (testcase.timelimit))
s = s.replace('<maxmem>', '%d' % config.maxmem)
s = s.replace('<rundir>', rundir)
s = s.replace('<user>', submit.user)
s = s.replace('<statfile>', statfile)
cmd.append(s)
if self.runcmd:
for s in self.runcmd:
s = s.replace('<judgehome>', config.judgehome)
s = s.replace('<datadir>', datadir)
s = s.replace('<user>', submit.user)
cmd.append(s)
if self.runenv:
for k in self.runenv.keys():
s = self.runenv[k]
s = s.replace('<judgehome>', config.judgehome)
s = s.replace('<datadir>', datadir)
s = s.replace('<user>', submit.user)
self.runenv[k] = s
self.logger.debug(string.join(cmd, ' ') + ' ' + str(self.runenv))
(exitcode, sig, timeused, memused) = \
self._execute(submit, cmd, timelimit = testcase.timelimit * 10,
infile = infile, outfile = outfile,
errfile = errfile, env = self.runenv,
rlimit_fsize = config.output_size_limit + 2,
statfile = statfile)
if submit_input_filename:
infile = _infile
if submit_output_filename:
outfile = _outfile
try:
shutil.copyfile(os.path.join(rundir, submit_output_filename),
outfile)
except IOError:
f = file(outfile, 'w')
f.close()
ret = [testcase.id, exitcode, sig, outfile, errfile, timeused, memused]
if timeused > testcase.timelimit:
ret.insert(1, 'TLE')
return ret
if memused > testcase.memlimit:
ret.insert(1, 'MLE')
return ret
if exitcode == 125:
ret.insert(1, 'JSE')
return ret
elif exitcode == 126:
ret.insert(1, 'RFC')
return ret
elif exitcode == 127:
ret.insert(1, 'JGE')
return ret
if sig == signal.SIGABRT or sig == signal.SIGSEGV:
ret.insert(1, 'RE')
return ret
elif sig == signal.SIGKILL or sig == signal.SIGXCPU:
ret.insert(1, 'TLE')
return ret
elif sig == signal.SIGXFSZ:
ret.insert(1, 'OLE')
return ret
elif sig == signal.SIGFPE:
ret.insert(1, 'FPE')
return ret
elif sig > 0:
ret.insert(1, 'KS')
return ret
s = os.stat(outfile)
if s.st_size > config.output_size_limit:
ret.insert(1, 'OLE')
return ret
js = submit.get_judge_script()
ret.insert(1, js.judge(submit.id, testcase.id,
os.path.abspath(testcase.get_input_file()),
os.path.abspath(testcase.get_output_file()),
os.path.abspath(outfile),
os.path.abspath(errfile), rundir))
return ret
def _execute(self, submit, command, timelimit = 1,
infile = None, outfile = None, errfile = None, env = {},
rlimit_fsize = -1, statfile = None):
pid = os.fork()
if pid == 0:
if rlimit_fsize > 0:
resource.setrlimit(resource.RLIMIT_FSIZE,
(rlimit_fsize, rlimit_fsize))
# child process
if infile:
try:
os.close(0)
os.open(infile, os.O_RDONLY)
except Exception, e:
print e
sys.exit(125)
if outfile:
try:
os.close(1)
os.open(outfile, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0666)
except Exception, e:
print e
sys.exit(125)
if errfile:
try:
os.close(2)
os.open(errfile, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0666)
except Exception, e:
print e
sys.exit(125)
#os.chdir(self.get_datadir(submit))
os.execve(command[0], command, env)
sys.exit(125)
# parent process
pid, status = os.waitpid(pid, 0)
timeused = 0; memused = 0; sig = 0; exitcode = 0
# get this child sig and exitcode
if os.WIFEXITED(status): exitcode = os.WEXITSTATUS(status)
if os.WIFSIGNALED(status): sig = os.WTERMSIG(status)
# read information form statfile
if statfile:
try:
stat = pickle.load(file(statfile, 'r'))
exitcode = stat['exitcode']
sig = stat['sig']
timeused = stat['timeused']
memused = 0
if self.basemem.has_key('RSS'):
memused += stat['memrss'] - self.basemem['RSS']
if self.basemem.has_key('Data'):
memused += stat['memdata'] - self.basemem['Data']
if self.basemem.has_key('Stack'):
memused += stat['memstack'] - self.basemem['Stack']
memused = max(0, memused)
except Exception, e:
self.logger.exception(e)
self.logger.error("Failed to read statfile: %s" % statfile)
exitcode = 127 # judge script error
return (exitcode, sig, timeused, memused)
def _remove(self, top):
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
os.rmdir(top)
class ComboTester:
testers = []
def add_tester(self, tester,
has_timelimit = True, has_memlimit = True,
check_result = True, is_lasttest = False):
tester.has_timelimit = has_timelimit
tester.has_memlimit = has_memlimit
tester.check_result = check_result
tester.is_lasttest = is_lasttest
self.testers.append(tester)
def test(self, submit):
r = True
for t in self.testers:
r = t.test(submit)
if not r:
break
return r
class SimpleTesterTestCase(OJTestCase):
@classmethod
def suite(clazz):
tests = ( 'testSteps', 'testNoSource',
'testAC', 'testWA', 'testZero',
'testBinary', 'testCE', 'testOLE', 'testTLE',
'testTargetDeleted',
)
return unittest.TestSuite(map(SimpleTesterTestCase, tests))
def setUp(self):
OJTestCase.setUp(self)
self.st = self.config.languages['gcc-3.3-nobc']
def testSteps(self):
self.logger.info("TESTING: Steps")
submit = self.ds.get_submit(3)
self.st.prepare(submit)
datadir = os.path.join(self.config.datadir, submit.id)
self.assertTrue(os.path.isdir(datadir))
self.assertTrue(os.path.isfile(os.path.join(datadir, self.st.source)))
submit.set_status('compiling')
r = self.st.compile(submit)
self.assertTrue(r)
self.assertTrue(os.path.isfile(os.path.join(datadir, self.st.target)))
self.assertTrue(os.path.isfile(os.path.join(datadir, 'compile.err')))
submit.set_status('running')
testcases = submit.get_testcases()
r = self.st.run(submit, testcases[0])
self.assertEqual(r[0], testcases[0].id)
self.assertEqual(r[1], 'AC')
self.assertEqual(r[2], 0)
self.assertEqual(r[3], 0)
f = file(r[4], 'r')
o = string.join(f.readlines(), '\n')
f.close()
self.assertEqual(o, '3\n')
f = file(r[5], 'r')
o = string.join(f.readlines(), '\n')
f.close()
self.assertEqual(o, '')
r = self.st.run(submit, testcases[1])
f = file(r[4], 'r')
o = string.join(f.readlines(), '\n')
f.close()
self.assertEqual(o, '4\n')
self.st.cleanup(submit)
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir))
def testAC(self):
self.logger.info("TESTING: AC")
submit = self.ds.get_submit(3)
datadir = os.path.join(self.config.datadir, submit.id)
r = self.st.test(submit)
self.assertTrue(r) # passed
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir)) # cleanup
from pysqlite2 import dbapi2 as sqlite
conn = sqlite.connect(self.dbname)
cur = conn.cursor()
cur.execute('SELECT * FROM submit_test_result WHERE sid=?', (submit.id, ))
rows = cur.fetchall()
cur.execute('DELETE FROM submit_test_result')
conn.commit()
self.assertEqual(len(rows), 2)
# test if the submit status change
cur.execute('SELECT status FROM submit WHERE id=?', (submit.id, ))
rows = cur.fetchall()
self.assertEqual(rows[0][0], 10)
cur.close()
conn.close()
def testNoSource(self):
self.logger.info("TESTING: No Source")
submit = self.ds.get_submit(1)
# the code of this submit is empty
datadir = os.path.join(self.config.datadir, submit.id)
r = self.st.test(submit)
self.assertFalse(r)
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir)) # cleanup
self.assertTrue(submit.get_status(), 'compile_failed')
def testNoTarget(self):
self.logger.info("TESTING: No Target")
submit = self.ds.get_submit(1)
# the code of this submit generate nothing
datadir = os.path.join(self.config.datadir, submit.id)
r = self.st.test(submit)
self.assertFalse(r)
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir)) # cleanup
self.assertTrue(submit.get_status(), 'compile_failed')
def testCE(self):
self.logger.info("TESTING: CE")
submit = self.ds.get_submit(7)
datadir = os.path.join(self.config.datadir, submit.id)
r = self.st.test(submit)
self.assertFalse(r) # not passed
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir)) # cleanup
self.assertTrue(len(submit.get_compilemessage()) > 0)
self.assertTrue(submit.get_status(), 'compile_failed')
def testWA(self):
self.logger.info("TESTING: WA")
submit = self.ds.get_submit(4)
datadir = os.path.join(self.config.datadir, submit.id)
r = self.st.test(submit)
self.assertFalse(r) # not passed
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir)) # cleanup
from pysqlite2 import dbapi2 as sqlite
conn = sqlite.connect(self.dbname)
cur = conn.cursor()
cur.execute('SELECT * FROM submit_test_result')
rows = cur.fetchall()
cur.execute('DELETE FROM submit_test_result')
conn.commit()
self.assertEqual(len(rows), 2)
cur.close()
conn.close()
def testTargetDeleted(self):
self.logger.info("TESTING: Target Deleted")
submit = self.ds.get_submit(3)
self.st.prepare(submit)
datadir = os.path.join(self.config.datadir, submit.id)
self.assertTrue(os.path.isdir(datadir))
self.assertTrue(os.path.isfile(os.path.join(datadir, self.st.source)))
submit.set_status('compiling')
r = self.st.compile(submit)
self.assertTrue(r)
self.assertTrue(os.path.isfile(os.path.join(datadir, self.st.target)))
self.assertTrue(os.path.isfile(os.path.join(datadir, 'compile.err')))
self.st.cleanup(submit, True)
submit.set_status('running')
testcases = submit.get_testcases()
r = self.st.run(submit, testcases[0])
self.assertNotEqual(r[1], 'AC')
def testZero(self):
self.logger.info("TESTING: ZERO")
submit = self.ds.get_submit(5)
# this program output '\0'
datadir = os.path.join(self.config.datadir, submit.id)
self.st.prepare(submit)
r = self.st.compile(submit)
self.assertTrue(r)
testcases = submit.get_testcases()
r = self.st.run(submit, testcases[0])
f = file(os.path.join(datadir, '0000000001.out'))
o = string.join(f.readlines(), '\n')
f.close()
self.assertEqual(o, '3\0\n')
self.st.cleanup(submit)
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir))
def testBinary(self):
self.logger.info("TESTING: BINARY")
submit = self.ds.get_submit(6)
# this program output '0xbb'
datadir = os.path.join(self.config.datadir, submit.id)
self.st.prepare(submit)
r = self.st.compile(submit)
self.assertTrue(r)
testcases = submit.get_testcases()
r = self.st.run(submit, testcases[0])
f = file(os.path.join(datadir, '0000000001.out'))
o = string.join(f.readlines(), '\n')
f.close()
self.assertEqual(o, '\xbb')
self.st.cleanup(submit)
self.assertFalse(not self.config.no_cleanup and os.path.exists(datadir))
def testOLE(self):
self.logger.info("TESTING: OLE")
submit = self.ds.get_submit(10)
self.st.prepare(submit)
r = self.st.compile(submit)
self.assertTrue(r, "Submit compile failed")
ts = submit.get_testcases()
r = self.st.run(submit, ts[0])
self.assertEqual(r[1], 'OLE')
def testTLE(self):
self.logger.info("TESTING: TLE")
submit = self.ds.get_submit(11)
self.st.prepare(submit)
self.st.compile(submit)
ts = submit.get_testcases()
r = self.st.run(submit, ts[0])
self.assertEqual(r[1], 'TLE')
class ComboTesterTestCase(OJTestCase):
@classmethod
def suite(clazz):
tests = ( 'testGCC33', )
return unittest.TestSuite(map(ComboTesterTestCase, tests))
def testGCC33(self):
self.ct = self.config.languages['gcc-3.3']
submits = self.ds.get_submits(8)
r = self.ct.test(submits[2])
self.assertTrue(r)
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()
class LanguageTestCase(OJTestCase):
@classmethod
def suite(clazz):
tests = ( 'testGCC33NOBC', 'testGCC33BC', 'testGXX33',
'testJava5', 'testJava6',
'testFreePascal20',
'testPython25',
)
return unittest.TestSuite(map(LanguageTestCase, tests))
def setUp(self):
OJTestCase.setUp(self)
def testGCC33NOBC(self):
st = self.config.languages['gcc-3.3-nobc']
submit = self.ds.get_submit(1001)
r = st.test(submit)
self.assertTrue(r, 'GCC NOBC test failed')
def testGCC33BC(self):
st = self.config.languages['gcc-3.3-bc']
submit = self.ds.get_submit(1001)
r = st.test(submit)
self.assertTrue(r, 'GCC WITH BC test failed')
def testGXX33(self):
st = self.config.languages['g++-3.3']
submit = self.ds.get_submit(1002)
r = st.test(submit)
self.assertTrue(r, 'GCC WITH BC test failed')
def testJava5(self):
st = self.config.languages['java-1.6']
submit = self.ds.get_submit(1004)
r = st.test(submit)
self.assertTrue(r, 'Java 5 test failed')
def testJava6(self):
st = self.config.languages['java-1.6']
submit = self.ds.get_submit(1004)
r = st.test(submit)
self.assertTrue(r, 'Java 6 test failed')
def testFreePascal20(self):
st = self.config.languages['fpc-2.0']
submit = self.ds.get_submit(1005)
r = st.test(submit)
self.assertTrue(r, 'FreePascal 2.0 test failed')
def testPython25(self):
st = self.config.languages['python-2.5']
submit = self.ds.get_submit(1006)
r = st.test(submit)
self.assertTrue(r, 'Python 2.5 test failed')
class SecurityTestCase(OJTestCase):
"""
Security test including:
* File Permission: only rundir are allowed to visit
* Forked Process: can oj kill all these process
* Daemon Process: can oj kill the daemon process
* Sleep: can oj kill sleep process
* Network: network usage should be forbidden
"""
@classmethod
def suite(clazz):
tests = (#'testGCC33FilePermission', 'testJavaFilePermission',
#'testPython25FilePermission',
# 'testSleep',
'testFork', 'testDaemon',
)
return unittest.TestSuite(map(SecurityTestCase, tests))
def testGCC33FilePermission(self):
st = self.config.languages['gcc-3.3-nobc']
submit = self.ds.get_submit(2311)
r = st.test(submit)
self.assertTrue(r, 'gcc 3.3 no bc security test failed')
def testJavaFilePermission(self):
st = self.config.languages['java-1.6']
submit = self.ds.get_submit(2331)
r = st.test(submit)
self.assertTrue(r, 'Java 1.6 security test failed')
def testPython25FilePermission(self):
st = self.config.languages['python-2.5']
submit = self.ds.get_submit(2361)
r = st.test(submit)
self.assertTrue(r, 'Python 2.5 security test failed')
def testFork(self):
st = self.config.languages['gcc-3.3-nobc']
submit = self.ds.get_submit(2412)
st.prepare(submit)
r = st.compile(submit)
self.assertTrue(r, "Failed")
ts = submit.get_testcases()
r = st.run(submit, ts[0])
self.assertEqual(r[1], 'TLE', "Sleep test failed")
def testDaemon(self):
st = self.config.languages['gcc-3.3-nobc']
submit = self.ds.get_submit(2413)
st.prepare(submit)
r = st.compile(submit)
self.assertTrue(r, "Failed")
ts = submit.get_testcases()
r = st.run(submit, ts[0])
self.assertEqual(r[1], 'TLE', "Sleep test failed")
def testSleep(self):
st = self.config.languages['gcc-3.3-nobc']
submit = self.ds.get_submit(2411)
st.prepare(submit)
r = st.compile(submit)
self.assertTrue(r, "Failed")
ts = submit.get_testcases()
r = st.run(submit, ts[0])
self.assertEqual(r[1], 'TLE', "Sleep test failed")
def testNetwork(self):
pass
if __name__ == '__main__':
#unittest.main()
suite = unittest.TestSuite()
#suite.addTests(LanguageTestCase.suite())
#suite.addTests(SimpleTesterTestCase.suite())
#suite.addTest(ComboTesterTestCase.suite())
suite.addTest(SecurityTestCase.suite())
unittest.TextTestRunner().run(suite)
# vim: set expandtab tabstop=4 shiftwidth=4:

BIN
python/tester.pyc Normal file

Binary file not shown.