mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-11 13:08:42 +00:00
feat(judger): uoj_judger_v2
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
ref: https://github-redirect.dependabot.com/UniversalOJ/UOJ-System/pull/113 Co-authored-by: vfleaking <vfleaking@163.com> Co-authored-by: Yefori-Go <110314400+Yefori-Go@users.noreply.github.com>
This commit is contained in:
parent
354c417737
commit
bcf5ce8b06
@ -1,9 +1,9 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG CLONE_ADDFLAG
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
|
||||
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
|
||||
|
||||
ADD . /opt/uoj_judger
|
||||
WORKDIR /opt/uoj_judger
|
||||
|
@ -3,7 +3,8 @@
|
||||
getAptPackage(){
|
||||
printf "\n\n==> Getting environment packages\n"
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
|
||||
apt-get update
|
||||
apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
|
||||
}
|
||||
|
||||
setJudgeConf(){
|
||||
@ -21,12 +22,6 @@ UOJEOF
|
||||
su judger <<EOD
|
||||
ln -s /var/uoj_data_copy /opt/uoj_judger/uoj_judger/data
|
||||
cd /opt/uoj_judger && chmod +x judge_client
|
||||
cat >uoj_judger/include/uoj_work_path.h <<UOJEOF
|
||||
#define UOJ_WORK_PATH "/opt/uoj_judger/uoj_judger"
|
||||
#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
|
||||
#define UOJ_JUDGER_PYTHON3_VERSION "3.8"
|
||||
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
|
||||
UOJEOF
|
||||
cd uoj_judger && make -j$(($(nproc) + 1))
|
||||
EOD
|
||||
}
|
||||
|
@ -5,433 +5,600 @@ import sys
|
||||
import os
|
||||
import pipes
|
||||
import socket
|
||||
from threading import Thread
|
||||
from threading import Thread, Lock, Condition, RLock
|
||||
import fcntl
|
||||
import shutil
|
||||
|
||||
import traceback
|
||||
import time
|
||||
import logging
|
||||
import codecs
|
||||
from contextlib import closing
|
||||
from typing import *
|
||||
|
||||
import requests
|
||||
|
||||
import queue as queue
|
||||
from queue import Queue, Empty
|
||||
|
||||
taskQ = Queue()
|
||||
submission = None
|
||||
jconf: dict
|
||||
|
||||
# path related function
|
||||
|
||||
def uoj_url(uri):
|
||||
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
|
||||
def uoj_judger_path(path = ''):
|
||||
return "uoj_judger" + path
|
||||
|
||||
# os related funciton
|
||||
def clean_up_folder(path):
|
||||
for f in os.listdir(path):
|
||||
f_path = os.path.join(path, f)
|
||||
if os.path.isfile(f_path):
|
||||
os.unlink(f_path)
|
||||
else:
|
||||
shutil.rmtree(f_path)
|
||||
for f in os.listdir(path):
|
||||
f_path = os.path.join(path, f)
|
||||
if os.path.isfile(f_path):
|
||||
os.unlink(f_path)
|
||||
else:
|
||||
shutil.rmtree(f_path)
|
||||
|
||||
|
||||
def execute(cmd):
|
||||
if os.system(cmd):
|
||||
raise Exception('failed to execute: %s' % cmd)
|
||||
if os.system(cmd) != 0:
|
||||
raise Exception('failed to execute: %s' % cmd)
|
||||
|
||||
|
||||
def freopen(f, g):
|
||||
os.dup2(g.fileno(), f.fileno())
|
||||
g.close()
|
||||
os.dup2(g.fileno(), f.fileno())
|
||||
g.close()
|
||||
|
||||
|
||||
# utf 8
|
||||
def uoj_text_replace(e):
|
||||
return ''.join('<b>\\x%02x</b>' % b for b in e.object[e.start:e.end]), e.end
|
||||
|
||||
|
||||
codecs.register_error('uoj_text_replace', uoj_text_replace)
|
||||
|
||||
|
||||
# init
|
||||
def init():
|
||||
global jconf
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
with open('.conf.json', 'r') as fp:
|
||||
jconf = json.load(fp)
|
||||
assert 'uoj_protocol' in jconf
|
||||
assert 'uoj_host' in jconf
|
||||
assert 'judger_name' in jconf
|
||||
assert 'judger_password' in jconf
|
||||
assert 'socket_port' in jconf
|
||||
assert 'socket_password' in jconf
|
||||
global jconf
|
||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||
with open('.conf.json', 'r') as fp:
|
||||
jconf = json.load(fp)
|
||||
assert 'uoj_protocol' in jconf
|
||||
assert 'uoj_host' in jconf
|
||||
assert 'judger_name' in jconf
|
||||
assert 'judger_password' in jconf
|
||||
assert 'socket_port' in jconf
|
||||
assert 'socket_password' in jconf
|
||||
if 'judger_cpus' not in jconf:
|
||||
jconf['judger_cpus'] = [
|
||||
'0'] # it should be distinct physical cores! Usually, you can just set it to some even numbers
|
||||
|
||||
# socket server
|
||||
def socket_server_loop():
|
||||
SOCK_CLOEXEC = 524288
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('', jconf['socket_port']))
|
||||
s.listen(5)
|
||||
|
||||
while True:
|
||||
try:
|
||||
conn, addr = s.accept()
|
||||
with closing(conn) as conn:
|
||||
data = conn.recv(1024).decode()
|
||||
assert data != None
|
||||
task = json.loads(data)
|
||||
assert task['password'] == jconf['socket_password']
|
||||
assert 'cmd' in task
|
||||
|
||||
taskQ.put(task)
|
||||
|
||||
if task['cmd'] == 'stop':
|
||||
print('the judge client is closing...')
|
||||
taskQ.join()
|
||||
conn.sendall(b'ok')
|
||||
return 'stop'
|
||||
except Exception:
|
||||
print('['+time.asctime()+']', 'connection rejected', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr)
|
||||
|
||||
def start_judger_server():
|
||||
global socket_server_thread
|
||||
|
||||
print_judge_client_status()
|
||||
print('hello!', file=sys.stderr)
|
||||
|
||||
socket_server_thread = Thread(target = socket_server_loop)
|
||||
socket_server_thread.setDaemon(True)
|
||||
socket_server_thread.start()
|
||||
|
||||
judger_loop()
|
||||
# path related function
|
||||
def uoj_url(uri):
|
||||
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
|
||||
|
||||
# report thread
|
||||
def report_loop():
|
||||
if 'is_hack' in submission:
|
||||
return
|
||||
while not submission_judged:
|
||||
try:
|
||||
with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
try:
|
||||
status = f.read(100)
|
||||
except Exception:
|
||||
status = None
|
||||
finally:
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
|
||||
if status != None:
|
||||
data = {}
|
||||
data['update-status'] = True
|
||||
data['id'] = submission['id']
|
||||
if 'is_custom_test' in submission:
|
||||
data['is_custom_test'] = True
|
||||
data['status'] = status
|
||||
uoj_interact(data)
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# handle task in main thread
|
||||
def handle_task():
|
||||
need_restart = False
|
||||
try:
|
||||
while True:
|
||||
task = taskQ.get_nowait()
|
||||
|
||||
if task['cmd'] == 'update':
|
||||
try:
|
||||
uoj_download('/judger', 'judger_update.zip')
|
||||
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
|
||||
except:
|
||||
print("error when update", file=sys.stderr)
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
uoj_sync_judge_client()
|
||||
need_restart = True
|
||||
elif task['cmd'] == 'stop':
|
||||
taskQ.task_done()
|
||||
socket_server_thread.join()
|
||||
|
||||
print_judge_client_status()
|
||||
print("goodbye!", file=sys.stderr)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
taskQ.task_done()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
if need_restart:
|
||||
os.execl('./judge_client', './judge_client')
|
||||
def uoj_judger_path(path=''):
|
||||
return os.getcwd() + "/uoj_judger" + path
|
||||
|
||||
def print_judge_client_status():
|
||||
print('[' + time.asctime() + ']', end=' ', file=sys.stderr)
|
||||
if submission != None:
|
||||
print(submission, end=' ', file=sys.stderr)
|
||||
print(file=sys.stderr)
|
||||
|
||||
# interact with uoj_judger
|
||||
def get_judger_result():
|
||||
res = {}
|
||||
with open(uoj_judger_path('/result/result.txt'), 'r') as fres:
|
||||
res['score'] = 0
|
||||
res['time'] = 0
|
||||
res['memory'] = 0
|
||||
while True:
|
||||
line = fres.readline()
|
||||
if line == '':
|
||||
break
|
||||
line = line.strip()
|
||||
if line == 'details':
|
||||
res['details'] = fres.read()
|
||||
break
|
||||
|
||||
sp = line.split()
|
||||
assert len(sp) >= 1
|
||||
if sp[0] == 'error':
|
||||
res['error'] = line[len('error') + 1:]
|
||||
else:
|
||||
assert len(sp) == 2
|
||||
res[sp[0]] = sp[1]
|
||||
res['score'] = int(res['score'])
|
||||
res['time'] = int(res['time'])
|
||||
res['memory'] = int(res['memory'])
|
||||
res['status'] = 'Judged'
|
||||
return res
|
||||
class RWLock:
|
||||
def __init__(self):
|
||||
self.rwlock = 0
|
||||
self.writers_waiting = 0
|
||||
self.monitor = Lock()
|
||||
self.readers_ok = Condition(self.monitor)
|
||||
self.writers_ok = Condition(self.monitor)
|
||||
|
||||
def update_problem_data(problem_id, problem_mtime):
|
||||
try:
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
return
|
||||
copy_name = uoj_judger_path('/data/%d' % problem_id)
|
||||
copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id)
|
||||
if os.path.isdir(copy_name):
|
||||
quoted_copy_name = pipes.quote(copy_name)
|
||||
if os.path.getmtime(copy_name) >= problem_mtime:
|
||||
execute('touch -a %s' % quoted_copy_name)
|
||||
return
|
||||
else:
|
||||
execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name))
|
||||
del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99]
|
||||
for fname in del_list:
|
||||
quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname))
|
||||
os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname))
|
||||
|
||||
uoj_download('/problem/%d' % problem_id, copy_zip_name)
|
||||
execute('cd %s && unzip -o -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id))
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
raise Exception('failed to update problem data of #%d' % problem_id)
|
||||
else:
|
||||
print_judge_client_status()
|
||||
print('updated problem data of #%d successfully' % problem_id, file=sys.stderr)
|
||||
def acquire_read(self):
|
||||
self.monitor.acquire()
|
||||
while self.rwlock < 0 or self.writers_waiting:
|
||||
self.readers_ok.wait()
|
||||
self.rwlock += 1
|
||||
self.monitor.release()
|
||||
|
||||
def acquire_write(self):
|
||||
self.monitor.acquire()
|
||||
while self.rwlock != 0:
|
||||
self.writers_waiting += 1
|
||||
self.writers_ok.wait()
|
||||
self.writers_waiting -= 1
|
||||
self.rwlock = -1
|
||||
self.monitor.release()
|
||||
|
||||
def promote(self):
|
||||
self.monitor.acquire()
|
||||
self.rwlock -= 1
|
||||
while self.rwlock != 0:
|
||||
self.writers_waiting += 1
|
||||
self.writers_ok.wait()
|
||||
self.writers_waiting -= 1
|
||||
self.rwlock = -1
|
||||
self.monitor.release()
|
||||
|
||||
def demote(self):
|
||||
self.monitor.acquire()
|
||||
self.rwlock = 1
|
||||
self.readers_ok.notifyAll()
|
||||
self.monitor.release()
|
||||
|
||||
def release(self):
|
||||
self.monitor.acquire()
|
||||
if self.rwlock < 0:
|
||||
self.rwlock = 0
|
||||
else:
|
||||
self.rwlock -= 1
|
||||
wake_writers = self.writers_waiting and self.rwlock == 0
|
||||
wake_readers = self.writers_waiting == 0
|
||||
self.monitor.release()
|
||||
if wake_writers:
|
||||
self.writers_ok.acquire()
|
||||
self.writers_ok.notify()
|
||||
self.writers_ok.release()
|
||||
elif wake_readers:
|
||||
self.readers_ok.acquire()
|
||||
self.readers_ok.notify_all()
|
||||
self.readers_ok.release()
|
||||
|
||||
|
||||
class Judger:
|
||||
problem_data_lock = RWLock()
|
||||
send_and_fetch_lock = RLock()
|
||||
|
||||
judger_id: int
|
||||
cpus: str
|
||||
main_path: str
|
||||
submission: Optional[dict]
|
||||
submission_judged: bool
|
||||
main_thread: Optional[Thread]
|
||||
report_thread: Optional[Thread]
|
||||
_taskQ: Queue
|
||||
|
||||
def log_judge_client_status(self):
|
||||
logging.info('submission: ' + str(self.submission))
|
||||
|
||||
def __init__(self, judger_id, cpus):
|
||||
self.judger_id = judger_id
|
||||
self.cpus = cpus
|
||||
self.main_path = '/tmp/' + jconf['judger_name'] + '/' + str(self.judger_id)
|
||||
self.submission = None
|
||||
self.main_thread = Thread(target=self._main_loop)
|
||||
self.report_thread = None
|
||||
self.submission_judged = False
|
||||
self._taskQ = Queue()
|
||||
|
||||
def start(self):
|
||||
execute('mkdir -p %s' % (self.main_path + '/result'))
|
||||
execute('mkdir -p %s' % (self.main_path + '/work'))
|
||||
|
||||
self.log_judge_client_status()
|
||||
logging.info('hello from judger #%d' % self.judger_id)
|
||||
|
||||
self.main_thread.start()
|
||||
|
||||
def suspend(self):
|
||||
self._taskQ.put('suspend')
|
||||
self._taskQ.join()
|
||||
|
||||
def resume(self):
|
||||
self._taskQ.put('resume')
|
||||
self._taskQ.join()
|
||||
|
||||
def exit(self):
|
||||
self._taskQ.put('exit')
|
||||
self._taskQ.join()
|
||||
self.main_thread.join()
|
||||
|
||||
# report thread
|
||||
def _report_loop(self):
|
||||
if 'is_hack' in self.submission:
|
||||
return
|
||||
while not self.submission_judged:
|
||||
try:
|
||||
with open(self.main_path + '/result/cur_status.txt', 'r') as f:
|
||||
fcntl.flock(f, fcntl.LOCK_SH)
|
||||
try:
|
||||
status = f.read(100)
|
||||
except Exception:
|
||||
status = None
|
||||
finally:
|
||||
fcntl.flock(f, fcntl.LOCK_UN)
|
||||
|
||||
if status != None:
|
||||
data = {}
|
||||
data['update-status'] = True
|
||||
data['id'] = self.submission['id']
|
||||
if 'is_custom_test' in self.submission:
|
||||
data['is_custom_test'] = True
|
||||
data['status'] = status
|
||||
uoj_interact(data)
|
||||
time.sleep(0.2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _get_result(self):
|
||||
res = {}
|
||||
with open(self.main_path + '/result/result.txt', 'r', encoding='utf-8', errors='uoj_text_replace') as fres:
|
||||
res['score'] = 0
|
||||
res['time'] = 0
|
||||
res['memory'] = 0
|
||||
while True:
|
||||
line = fres.readline()
|
||||
if line == '':
|
||||
break
|
||||
line = line.strip()
|
||||
if line == 'details':
|
||||
res['details'] = fres.read()
|
||||
break
|
||||
|
||||
sp = line.split()
|
||||
assert len(sp) >= 1
|
||||
if sp[0] == 'error':
|
||||
res['error'] = line[len('error') + 1:]
|
||||
else:
|
||||
assert len(sp) == 2
|
||||
res[sp[0]] = sp[1]
|
||||
res['score'] = int(res['score'])
|
||||
res['time'] = int(res['time'])
|
||||
res['memory'] = int(res['memory'])
|
||||
res['status'] = 'Judged'
|
||||
return res
|
||||
|
||||
def _remove_problem_data(self, problem_id):
|
||||
quoted_path = pipes.quote(uoj_judger_path(f'/data/{problem_id}'))
|
||||
execute(f'chmod 700 {quoted_path} -R && rm -rf {quoted_path}')
|
||||
|
||||
def _update_problem_data_atime(self, problem_id):
|
||||
path = uoj_judger_path(f'/data/{problem_id}')
|
||||
execute(f'touch -a {pipes.quote(path)}')
|
||||
|
||||
def _update_problem_data(self, problem_id, problem_mtime):
|
||||
Judger.problem_data_lock.acquire_read()
|
||||
try:
|
||||
copy_name = uoj_judger_path(f'/data/{problem_id}')
|
||||
copy_zip_name = uoj_judger_path(f'/data/{problem_id}.zip')
|
||||
if os.path.isdir(copy_name):
|
||||
if os.path.getmtime(copy_name) >= problem_mtime:
|
||||
self._update_problem_data_atime(problem_id)
|
||||
return
|
||||
else:
|
||||
Judger.problem_data_lock.promote()
|
||||
self._remove_problem_data(problem_id)
|
||||
else:
|
||||
Judger.problem_data_lock.promote()
|
||||
|
||||
del_list = sorted(os.listdir(uoj_judger_path('/data')),
|
||||
key=lambda p: os.path.getatime(uoj_judger_path(f'/data/{p}')))[:-99]
|
||||
for p in del_list:
|
||||
self._remove_problem_data(p)
|
||||
|
||||
uoj_download(f'/problem/{problem_id}', copy_zip_name)
|
||||
execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (
|
||||
uoj_judger_path('/data'), problem_id, problem_id, problem_id))
|
||||
os.utime(uoj_judger_path(f'/data/{problem_id}'), (time.time(), problem_mtime))
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('problem update error')
|
||||
raise Exception(f'failed to update problem data of #{problem_id}')
|
||||
else:
|
||||
self.log_judge_client_status()
|
||||
logging.info(f'updated problem data of #{problem_id} successfully')
|
||||
finally:
|
||||
Judger.problem_data_lock.release()
|
||||
|
||||
def _judge(self):
|
||||
clean_up_folder(self.main_path + '/work')
|
||||
clean_up_folder(self.main_path + '/result')
|
||||
self._update_problem_data(self.submission['problem_id'], self.submission['problem_mtime'])
|
||||
|
||||
with open(self.main_path + '/work/submission.conf', 'w') as fconf:
|
||||
uoj_download(self.submission['content']['file_name'], self.main_path + '/work/all.zip')
|
||||
execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(self.main_path + '/work'))
|
||||
for k, v in self.submission['content']['config']:
|
||||
print(k, v, file=fconf)
|
||||
|
||||
if 'is_hack' in self.submission:
|
||||
if self.submission['hack']['input_type'] == 'USE_FORMATTER':
|
||||
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input_raw.txt')
|
||||
execute('%s <%s >%s' % (
|
||||
pipes.quote(uoj_judger_path('/run/formatter')),
|
||||
pipes.quote(self.main_path + '/work/hack_input_raw.txt'),
|
||||
pipes.quote(self.main_path + '/work/hack_input.txt')
|
||||
))
|
||||
else:
|
||||
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input.txt')
|
||||
print('test_new_hack_only on', file=fconf)
|
||||
elif 'is_custom_test' in self.submission:
|
||||
print('custom_test on', file=fconf)
|
||||
|
||||
self.submission_judged = False
|
||||
self.report_thread = Thread(target=self._report_loop)
|
||||
self.report_thread.start()
|
||||
|
||||
Judger.problem_data_lock.acquire_read()
|
||||
try:
|
||||
main_judger_cmd = pipes.quote(uoj_judger_path('/main_judger'))
|
||||
if self.cpus != 'all':
|
||||
main_judger_cmd = 'taskset -c %s %s' % (pipes.quote(self.cpus), main_judger_cmd)
|
||||
execute('cd %s && %s' % (pipes.quote(self.main_path), main_judger_cmd))
|
||||
finally:
|
||||
Judger.problem_data_lock.release()
|
||||
self.submission_judged = True
|
||||
self.report_thread.join()
|
||||
self.report_thread = None
|
||||
|
||||
return self._get_result()
|
||||
|
||||
def _send_and_fetch(self, result=None, fetch_new=True):
|
||||
"""send judgement result, and fetch new submission to judge"""
|
||||
|
||||
data = {}
|
||||
files = {}
|
||||
|
||||
if not fetch_new:
|
||||
data['fetch_new'] = False
|
||||
|
||||
if result is not None:
|
||||
data['submit'] = True
|
||||
if 'is_hack' in self.submission:
|
||||
data['is_hack'] = True
|
||||
data['id'] = self.submission['hack']['id']
|
||||
if result != False and result['score']:
|
||||
try:
|
||||
logging.info("succ hack!")
|
||||
files = {
|
||||
('hack_input', open(self.main_path + '/work/hack_input.txt', 'rb')),
|
||||
('std_output', open(self.main_path + '/work/std_output.txt', 'rb'))
|
||||
}
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('hack: submit error')
|
||||
result = False
|
||||
elif 'is_custom_test' in self.submission:
|
||||
data['is_custom_test'] = True
|
||||
data['id'] = self.submission['id']
|
||||
else:
|
||||
data['id'] = self.submission['id']
|
||||
|
||||
if 'judge_time' in self.submission:
|
||||
data['judge_time'] = self.submission['judge_time']
|
||||
|
||||
if 'tid' in self.submission:
|
||||
data['tid'] = self.submission['tid']
|
||||
|
||||
if result == False:
|
||||
result = {
|
||||
'score': 0,
|
||||
'error': 'Judgment Failed',
|
||||
'details': 'Unknown Error'
|
||||
}
|
||||
result['status'] = 'Judged'
|
||||
data['result'] = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
while True:
|
||||
Judger.send_and_fetch_lock.acquire()
|
||||
try:
|
||||
ret = uoj_interact(data, files)
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('uoj_interact error')
|
||||
else:
|
||||
break
|
||||
finally:
|
||||
Judger.send_and_fetch_lock.release()
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
self.submission = json.loads(ret)
|
||||
except Exception:
|
||||
if ret != 'Nothing to judge':
|
||||
logging.info(ret)
|
||||
self.submission = None
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _main_loop(self):
|
||||
while True:
|
||||
if not self._taskQ.empty():
|
||||
while True:
|
||||
task = self._taskQ.get()
|
||||
if task == 'resume':
|
||||
self._taskQ.task_done()
|
||||
break
|
||||
elif task == 'exit':
|
||||
self._taskQ.task_done()
|
||||
return
|
||||
else:
|
||||
self._taskQ.task_done()
|
||||
|
||||
if not self._send_and_fetch():
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
self.log_judge_client_status()
|
||||
logging.info('judging')
|
||||
|
||||
while True:
|
||||
try:
|
||||
res = self._judge()
|
||||
except Exception:
|
||||
self.log_judge_client_status()
|
||||
logging.exception('judge error')
|
||||
res = False
|
||||
if not self._send_and_fetch(result=res, fetch_new=self._taskQ.empty()):
|
||||
break
|
||||
|
||||
|
||||
class JudgerServer:
|
||||
taskQ: Queue
|
||||
judgers: List[Judger]
|
||||
socket_thread: Thread
|
||||
|
||||
def __init__(self):
|
||||
self.taskQ = Queue()
|
||||
self.judgers = [Judger(i, cpus) for i, cpus in enumerate(jconf['judger_cpus'])]
|
||||
self.socket_thread = Thread(target=self._socket_loop)
|
||||
|
||||
def _socket_loop(self):
|
||||
try:
|
||||
SOCK_CLOEXEC = 524288
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('', jconf['socket_port']))
|
||||
s.listen(5)
|
||||
|
||||
while True:
|
||||
try:
|
||||
conn, addr = s.accept()
|
||||
with closing(conn) as conn:
|
||||
data = conn.recv(1024)
|
||||
assert data != None
|
||||
task = json.loads(data)
|
||||
assert task['password'] == jconf['socket_password']
|
||||
assert 'cmd' in task
|
||||
|
||||
self.taskQ.put(task)
|
||||
|
||||
if task['cmd'] == 'stop':
|
||||
logging.info('the judge client is closing...')
|
||||
self.taskQ.join()
|
||||
conn.sendall(b'ok')
|
||||
return 'stop'
|
||||
except Exception:
|
||||
logging.exception('connection rejected')
|
||||
else:
|
||||
logging.info('a new task accomplished')
|
||||
except Exception:
|
||||
self.taskQ.put({'cmd': 'stop'})
|
||||
logging.exception('socket server error!')
|
||||
|
||||
def run(self):
|
||||
self.socket_thread.start()
|
||||
for judger in self.judgers:
|
||||
judger.start()
|
||||
|
||||
while True:
|
||||
task = self.taskQ.get()
|
||||
|
||||
need_restart = False
|
||||
block_wait = False
|
||||
|
||||
for judger in self.judgers:
|
||||
judger.suspend()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if task['cmd'] in ['update', 'self-update']:
|
||||
try:
|
||||
uoj_download('/judger', 'judger_update.zip')
|
||||
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
|
||||
except:
|
||||
print(sys.stderr, "error when update")
|
||||
if jconf['judger_name'] == 'main_judger':
|
||||
uoj_sync_judge_client()
|
||||
need_restart = True
|
||||
elif task['cmd'] == 'stop':
|
||||
self.taskQ.task_done()
|
||||
self.socket_thread.join()
|
||||
for judger in self.judgers:
|
||||
judger.exit()
|
||||
logging.info("goodbye!")
|
||||
sys.exit(0)
|
||||
|
||||
self.taskQ.task_done()
|
||||
task = self.taskQ.get(block=block_wait)
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
if need_restart:
|
||||
os.execl('./judge_client', './judge_client')
|
||||
|
||||
for judger in self.judgers:
|
||||
judger.resume()
|
||||
|
||||
def judge():
|
||||
global report_thread
|
||||
global submission_judged
|
||||
|
||||
clean_up_folder(uoj_judger_path('/work'))
|
||||
clean_up_folder(uoj_judger_path('/result'))
|
||||
update_problem_data(submission['problem_id'], submission['problem_mtime'])
|
||||
|
||||
with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf:
|
||||
uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip'))
|
||||
execute("cd %s && unzip -o -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work')))
|
||||
for k, v in submission['content']['config']:
|
||||
print(k, v, file=fconf)
|
||||
|
||||
if 'is_hack' in submission:
|
||||
if submission['hack']['input_type'] == 'USE_FORMATTER':
|
||||
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt'))
|
||||
execute('%s <%s >%s' % (
|
||||
pipes.quote(uoj_judger_path('/run/formatter')),
|
||||
pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')),
|
||||
pipes.quote(uoj_judger_path('/work/hack_input.txt'))))
|
||||
else:
|
||||
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt'))
|
||||
print('test_new_hack_only on', file=fconf)
|
||||
elif 'is_custom_test' in submission:
|
||||
print('custom_test on', file=fconf)
|
||||
|
||||
report_thread = Thread(target = report_loop)
|
||||
report_thread.setDaemon(True)
|
||||
|
||||
submission_judged = False
|
||||
report_thread.start()
|
||||
execute(pipes.quote(uoj_judger_path('/main_judger')))
|
||||
submission_judged = True
|
||||
report_thread.join()
|
||||
|
||||
return get_judger_result()
|
||||
|
||||
# interact with uoj web server
|
||||
def uoj_interact(data, files = {}):
|
||||
data = data.copy()
|
||||
data.update({
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
})
|
||||
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
|
||||
# TODO: set a larger timeout
|
||||
def uoj_interact(data, files={}):
|
||||
data = data.copy()
|
||||
data.update({
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
})
|
||||
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
|
||||
|
||||
|
||||
def uoj_download(uri, filename):
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
with open(filename, 'wb') as f:
|
||||
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
|
||||
for chunk in r.iter_content(chunk_size=65536):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
with open(filename, 'wb') as f:
|
||||
r = requests.post(uoj_url('/judge/download' + uri), data=data, stream=True)
|
||||
for chunk in r.iter_content(chunk_size=65536):
|
||||
if chunk:
|
||||
f.write(chunk)
|
||||
|
||||
|
||||
def uoj_sync_judge_client():
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
|
||||
if ret != "ok":
|
||||
raise Exception('failed to sync judge clients: %s' % ret)
|
||||
data = {
|
||||
'judger_name': jconf['judger_name'],
|
||||
'password': jconf['judger_password']
|
||||
}
|
||||
ret = requests.post(uoj_url('/judge/sync-judge-client'), data=data).text
|
||||
if ret != "ok":
|
||||
raise Exception('failed to sync judge clients: %s' % ret)
|
||||
|
||||
def send_and_fetch(result = None, fetch_new = True):
|
||||
global submission
|
||||
|
||||
"""send judgement result, and fetch new submission to judge"""
|
||||
|
||||
data = {}
|
||||
files = {}
|
||||
|
||||
if not fetch_new:
|
||||
data['fetch_new'] = False
|
||||
|
||||
if result != None:
|
||||
data['submit'] = True
|
||||
if 'is_hack' in submission:
|
||||
data['is_hack'] = True
|
||||
data['id'] = submission['hack']['id']
|
||||
if result != False and result['score']:
|
||||
try:
|
||||
print("succ hack!", file=sys.stderr)
|
||||
files = {
|
||||
('hack_input', open('uoj_judger/work/hack_input.txt', 'r')),
|
||||
('std_output', open('uoj_judger/work/std_output.txt', 'r'))
|
||||
}
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
result = False
|
||||
elif 'is_custom_test' in submission:
|
||||
data['is_custom_test'] = True
|
||||
data['id'] = submission['id']
|
||||
else:
|
||||
data['id'] = submission['id']
|
||||
|
||||
if result == False:
|
||||
result = {
|
||||
'score': 0,
|
||||
'error': 'Judgement Failed',
|
||||
'details': 'Unknown Error'
|
||||
}
|
||||
result['status'] = 'Judged'
|
||||
data['result'] = json.dumps(result, ensure_ascii=False)
|
||||
|
||||
while True:
|
||||
try:
|
||||
ret = uoj_interact(data, files)
|
||||
print(ret)
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
else:
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
submission = json.loads(ret)
|
||||
except Exception as e:
|
||||
submission = None
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# judge client
|
||||
def judger_loop():
|
||||
ok = False
|
||||
while True:
|
||||
fetch_new = True
|
||||
|
||||
if ok and not (taskQ.empty() and socket_server_thread.isAlive()):
|
||||
fetch_new = False
|
||||
|
||||
if not ok:
|
||||
while True:
|
||||
if not taskQ.empty():
|
||||
handle_task()
|
||||
if not socket_server_thread.isAlive():
|
||||
raise Exception('socket server exited unexpectedly')
|
||||
|
||||
if send_and_fetch():
|
||||
break
|
||||
|
||||
print('['+time.asctime()+']', 'Nothing to judge...')
|
||||
time.sleep(2)
|
||||
|
||||
ok = True
|
||||
print_judge_client_status()
|
||||
print('judging', file=sys.stderr)
|
||||
|
||||
try:
|
||||
res = judge()
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
res = False
|
||||
|
||||
ok = send_and_fetch(result=res,fetch_new=fetch_new)
|
||||
|
||||
# main function
|
||||
def main():
|
||||
init()
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
start_judger_server()
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] == 'start':
|
||||
pid = os.fork()
|
||||
if pid == -1:
|
||||
raise Exception('fork failed')
|
||||
elif pid > 0:
|
||||
return
|
||||
else:
|
||||
freopen(sys.stdout, open(os.devnull, 'wb'))
|
||||
freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0))
|
||||
start_judger_server()
|
||||
elif sys.argv[1] == 'update':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'update'
|
||||
}).encode())
|
||||
return
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise Exception('update failed')
|
||||
elif sys.argv[1] == 'stop':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'stop'
|
||||
}).encode())
|
||||
if s.recv(10).decode() != 'ok':
|
||||
raise Exception('stop failed')
|
||||
return
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise Exception('stop failed')
|
||||
raise Exception('invalid argument')
|
||||
init()
|
||||
|
||||
logging.basicConfig(filename='log/judge.log', level=logging.INFO,
|
||||
format='%(asctime)-15s [%(levelname)s]: %(message)s')
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
JudgerServer().run()
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1] == 'start':
|
||||
pid = os.fork()
|
||||
if pid == -1:
|
||||
raise Exception('fork failed')
|
||||
elif pid > 0:
|
||||
return
|
||||
else:
|
||||
freopen(sys.stdout, open(os.devnull, 'w'))
|
||||
freopen(sys.stderr, open('log/judge.log', 'a', encoding='utf-8', errors='uoj_text_replace'))
|
||||
JudgerServer().run()
|
||||
elif sys.argv[1] in ['update', 'self-update']:
|
||||
try:
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': sys.argv[1]
|
||||
}).encode())
|
||||
return
|
||||
except OSError:
|
||||
JudgerServer.update(broadcast=sys.argv[1] == 'update')
|
||||
return
|
||||
except Exception:
|
||||
logging.exception('update error')
|
||||
raise Exception('update failed')
|
||||
elif sys.argv[1] == 'stop':
|
||||
try:
|
||||
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
||||
s.connect(('127.0.0.1', jconf['socket_port']))
|
||||
s.sendall(json.dumps({
|
||||
'password': jconf['socket_password'],
|
||||
'cmd': 'stop'
|
||||
}).encode())
|
||||
if s.recv(10) != b'ok':
|
||||
raise Exception('stop failed')
|
||||
return
|
||||
except Exception:
|
||||
logging.exception('stop error')
|
||||
raise Exception('stop failed')
|
||||
raise Exception('invalid argument')
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
main()
|
||||
except Exception:
|
||||
print_judge_client_status()
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
logging.exception('critical error!')
|
||||
sys.exit(1)
|
||||
|
@ -1,5 +1,5 @@
|
||||
INCLUDE_PATH = include
|
||||
CXXFLAGS = -I./include -O2
|
||||
CXXFLAGS = -I./include -O2 --std=c++17 -Wall -lstdc++fs
|
||||
|
||||
EXE_CHECKER = \
|
||||
builtin/checker/bcmp \
|
||||
@ -26,24 +26,35 @@ EXE = main_judger \
|
||||
run/formatter \
|
||||
run/run_program \
|
||||
run/run_interaction \
|
||||
builtin/judger/judger
|
||||
run/compile \
|
||||
builtin/judger/judger \
|
||||
$(EXE_CHECKER)
|
||||
|
||||
all: $(EXE) $(EXE_CHECKER)
|
||||
runner: $(EXE)
|
||||
checker: $(EXE_CHECKER)
|
||||
OBJ = tests/catch_amalgamated.o tests/test.o
|
||||
|
||||
all: $(EXE)
|
||||
|
||||
% : %.cpp
|
||||
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@
|
||||
|
||||
run/run_program: include/uoj_env.h run/run_program_conf.h
|
||||
run/formatter : include/testlib.h
|
||||
run/run_interaction: run/run_interaction.cpp include/uoj_env.h
|
||||
$(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@
|
||||
run/formatter : include/testlib.h
|
||||
run/compile : include/uoj_run.h
|
||||
|
||||
builtin/judger/judger: include
|
||||
main_judger: include
|
||||
run/run_program : run/run_program.cpp run/run_program_conf.h run/run_program_sandbox.h include/uoj_run.h
|
||||
$(CXX) $(CXXFLAGS) $< -o $@ -lseccomp -pthread
|
||||
|
||||
run/run_interaction : run/run_interaction.cpp include/uoj_run.h
|
||||
$(CXX) $(CXXFLAGS) $< -o $@ -pthread
|
||||
|
||||
builtin/judger/judger: include/*.h
|
||||
main_judger: include/*.h
|
||||
|
||||
tests/test.o: include/*.h
|
||||
|
||||
tests/test: tests/catch_amalgamated.o tests/test.o
|
||||
$(CXX) $(CXXFLAGS) $^ -o $@ -pthread
|
||||
|
||||
$(EXE_CHECKER): include/testlib.h
|
||||
|
||||
clean:
|
||||
rm -f $(EXE) $(EXE_CHECKER)
|
||||
rm -f $(EXE) $(OBJ)
|
||||
|
@ -1,150 +1,51 @@
|
||||
#include "uoj_judger.h"
|
||||
|
||||
struct SubtaskInfo {
|
||||
bool passed;
|
||||
int score;
|
||||
PointInfo submit_answer_test_point_with_auto_generation(int i, TestPointConfig tpc = TestPointConfig()) {
|
||||
static set<string> compiled;
|
||||
static set<int> generated;
|
||||
|
||||
SubtaskInfo() {
|
||||
int output_file_id = conf_int("output_file_id", i, i);
|
||||
tpc.submit_answer = true;
|
||||
tpc.output_file_name = work_path + "/" + conf_output_file_name(conf_int("output_file_id", i, i));
|
||||
|
||||
string gen_name = conf_str("output_gen", output_file_id, "");
|
||||
if (gen_name.empty()) {
|
||||
generated.insert(output_file_id);
|
||||
}
|
||||
if (generated.count(output_file_id)) {
|
||||
return test_point("", i, tpc);
|
||||
}
|
||||
SubtaskInfo(const bool &_p, const int &_s)
|
||||
: passed(_p), score(_s){}
|
||||
};
|
||||
|
||||
void ordinary_test() {
|
||||
int n = conf_int("n_tests", 10);
|
||||
int m = conf_int("n_ex_tests", 0);
|
||||
int nT = conf_int("n_subtasks", 0);
|
||||
tpc.limit = conf_run_limit("gen", output_file_id, RL_GENERATOR_DEFAULT);
|
||||
generated.insert(output_file_id);
|
||||
|
||||
if (!conf_is("submit_answer", "on")) {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (!compiled.count(gen_name)) {
|
||||
report_judge_status_f("Compiling %s", gen_name.c_str());
|
||||
if (auto c_ret = compile_submission_program(gen_name); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
compiled.insert(gen_name);
|
||||
}
|
||||
|
||||
bool passed = true;
|
||||
if (nT == 0) {
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
add_point_info(po);
|
||||
}
|
||||
} else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") {
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
po.scr = i == 1 ? 0 : -100;
|
||||
add_point_info(po);
|
||||
break;
|
||||
} else {
|
||||
po.scr = i == 1 ? 100 : 0;
|
||||
add_point_info(po);
|
||||
}
|
||||
}
|
||||
tpc.submit_answer = false;
|
||||
return test_point(gen_name, i, tpc);
|
||||
}
|
||||
|
||||
void ordinary_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
PrimaryDataTestConfig pdtc;
|
||||
pdtc.disable_ex_tests = true;
|
||||
primary_data_test([](int i) {
|
||||
return submit_answer_test_point_with_auto_generation(i);
|
||||
}, pdtc);
|
||||
} else {
|
||||
map<int, SubtaskInfo> subtasks;
|
||||
map<int,int> minScore;
|
||||
for (int t = 1; t <= nT; t++) {
|
||||
string subtaskType = conf_str("subtask_type", t, "packed");
|
||||
int startI = conf_int("subtask_end", t - 1, 0) + 1;
|
||||
int endI = conf_int("subtask_end", t, 0);
|
||||
|
||||
vector<PointInfo> points;
|
||||
minScore[t] = 100;
|
||||
|
||||
vector<int> dependences;
|
||||
if (conf_str("subtask_dependence", t, "none") == "many") {
|
||||
string cur = "subtask_dependence_" + vtos(t);
|
||||
int p = 1;
|
||||
while (conf_int(cur, p, 0) != 0) {
|
||||
dependences.push_back(conf_int(cur, p, 0));
|
||||
p++;
|
||||
}
|
||||
} else if (conf_int("subtask_dependence", t, 0) != 0) {
|
||||
dependences.push_back(conf_int("subtask_dependence", t, 0));
|
||||
}
|
||||
bool skipped = false;
|
||||
for (vector<int>::iterator it = dependences.begin(); it != dependences.end(); it++) {
|
||||
if (subtaskType == "packed") {
|
||||
if (!subtasks[*it].passed) {
|
||||
skipped = true;
|
||||
break;
|
||||
}
|
||||
} else if (subtaskType == "min") {
|
||||
minScore[t] = min(minScore[t], minScore[*it]);
|
||||
}
|
||||
}
|
||||
if (skipped) {
|
||||
add_subtask_info(t, 0, "Skipped", points);
|
||||
continue;
|
||||
}
|
||||
|
||||
int tfull = conf_int("subtask_score", t, 100 / nT);
|
||||
int tscore = scale_score(minScore[t], tfull);
|
||||
string info = "Accepted";
|
||||
for (int i = startI; i <= endI; i++) {
|
||||
report_judge_status_f("Judging Test #%d of Subtask #%d", i, t);
|
||||
PointInfo po = test_point("answer", i);
|
||||
if (subtaskType == "packed") {
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
po.scr = i == startI ? 0 : -tfull;
|
||||
tscore = 0;
|
||||
points.push_back(po);
|
||||
info = po.info;
|
||||
break;
|
||||
} else {
|
||||
po.scr = i == startI ? tfull : 0;
|
||||
tscore = tfull;
|
||||
points.push_back(po);
|
||||
}
|
||||
} else if (subtaskType == "min") {
|
||||
minScore[t] = min(minScore[t], po.scr);
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, tfull);
|
||||
if (po.scr <= tscore) {
|
||||
tscore = po.scr;
|
||||
points.push_back(po);
|
||||
info = po.info;
|
||||
} else {
|
||||
points.push_back(po);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subtasks[t] = SubtaskInfo(info == "Accepted", tscore);
|
||||
|
||||
add_subtask_info(t, tscore, info, points);
|
||||
report_judge_status_f("Compiling");
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
}
|
||||
if (conf_is("submit_answer", "on") || !passed) {
|
||||
end_judge_ok();
|
||||
}
|
||||
|
||||
tot_score = 100;
|
||||
for (int i = 1; i <= m; i++) {
|
||||
report_judge_status_f("Judging Extra Test #%d", i);
|
||||
PointInfo po = test_point("answer", -i);
|
||||
if (po.scr != 100) {
|
||||
po.num = -1;
|
||||
po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
|
||||
po.scr = -3;
|
||||
add_point_info(po);
|
||||
end_judge_ok();
|
||||
}
|
||||
}
|
||||
if (m != 0) {
|
||||
PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
|
||||
add_point_info(po);
|
||||
primary_data_test([](int i) {
|
||||
return test_point("answer", i);
|
||||
});
|
||||
}
|
||||
end_judge_ok();
|
||||
}
|
||||
@ -153,8 +54,7 @@ void hack_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
end_judge_judgement_failed("Hack is not supported in this problem.");
|
||||
} else {
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
TestPointConfig tpc;
|
||||
@ -170,80 +70,52 @@ void hack_test() {
|
||||
|
||||
void sample_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
int n = conf_int("n_tests", 10);
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Test #%d", i);
|
||||
if (conf_is("check_existence_only_in_sample_test", "on")) {
|
||||
if (conf_is("check_existence_only_in_sample_test", "on")) {
|
||||
main_data_test([](int i) {
|
||||
TestPointConfig tpc = TestPointConfig();
|
||||
tpc.auto_complete(i);
|
||||
|
||||
string usrout = file_preview(tpc.output_file_name);
|
||||
if (usrout == "") {
|
||||
add_point_info(PointInfo(i, 0, -1, -1,
|
||||
"default",
|
||||
file_preview(tpc.input_file_name), usrout,
|
||||
"wrong answer empty file\n"));
|
||||
} else {
|
||||
PointInfo po = PointInfo(i, 100, -1, -1,
|
||||
"default",
|
||||
file_preview(tpc.input_file_name), usrout,
|
||||
"ok nonempty file\n");
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
add_point_info(po);
|
||||
}
|
||||
} else {
|
||||
PointInfo po = test_point("answer", i);
|
||||
tpc.checker = "nonempty";
|
||||
return submit_answer_test_point_with_auto_generation(i, tpc);
|
||||
});
|
||||
} else {
|
||||
main_data_test([](int i) {
|
||||
PointInfo po = submit_answer_test_point_with_auto_generation(i);
|
||||
if (po.scr != 0) {
|
||||
po.info = "Accepted";
|
||||
po.scr = 100;
|
||||
}
|
||||
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
|
||||
po.res = "no comment";
|
||||
add_point_info(po);
|
||||
}
|
||||
return po;
|
||||
});
|
||||
}
|
||||
end_judge_ok();
|
||||
} else {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
int n = conf_int("n_sample_tests", 0);
|
||||
bool passed = true;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
report_judge_status_f("Judging Sample Test #%d", i);
|
||||
PointInfo po = test_point("answer", -i);
|
||||
po.num = i;
|
||||
if (po.scr != 100) {
|
||||
passed = false;
|
||||
}
|
||||
po.scr = scale_score(po.scr, 100 / n);
|
||||
add_point_info(po);
|
||||
}
|
||||
if (passed) {
|
||||
tot_score = 100;
|
||||
}
|
||||
sample_data_test([](int i) {
|
||||
return test_point("answer", i);
|
||||
});
|
||||
end_judge_ok();
|
||||
}
|
||||
}
|
||||
|
||||
void custom_test() {
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
end_judge_judgement_failed("Custom test is not supported in this problem.");
|
||||
} else {
|
||||
report_judge_status_f("Compiling");
|
||||
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
|
||||
if (!c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
report_judge_status_f("Judging");
|
||||
add_custom_test_info(ordinary_custom_test("answer"));
|
||||
|
||||
end_judge_ok();
|
||||
report_judge_status_f("Compiling");
|
||||
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
|
||||
end_judge_compile_error(c_ret);
|
||||
}
|
||||
|
||||
CustomTestConfig ctc;
|
||||
if (conf_is("submit_answer", "on")) {
|
||||
ctc.base_limit = conf_run_limit("gen", 0, RL_GENERATOR_DEFAULT);
|
||||
}
|
||||
|
||||
report_judge_status_f("Judging");
|
||||
add_custom_test_info(ordinary_custom_test("answer", ctc));
|
||||
|
||||
end_judge_ok();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
@ -1,38 +0,0 @@
|
||||
#include "uoj_work_path.h"
|
||||
|
||||
#define UOJ_DATA_PATH UOJ_WORK_PATH "/data"
|
||||
#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result"
|
||||
|
||||
#define RS_SPJ_BASE 1000
|
||||
#define failed_spj RS_SPJ_BASE
|
||||
#define successed_hack 100000
|
||||
#define RS_SPJ RS_SPJ_BASE
|
||||
#define RS_HACK successed_hack
|
||||
#define RS_AC 0
|
||||
#define RS_WA 1
|
||||
#define RS_RE 2
|
||||
#define RS_MLE 3
|
||||
#define RS_TLE 4
|
||||
#define RS_OLE 5
|
||||
#define RS_DGS 6
|
||||
#define RS_JGF 7
|
||||
#define RS_SPJ_AC (RS_SPJ + RS_AC)
|
||||
#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE)
|
||||
#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE)
|
||||
#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE)
|
||||
#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE)
|
||||
#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS)
|
||||
#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF)
|
||||
#define RS_HK_RE (successed_hack + RS_RE)
|
||||
#define RS_HK_MLE (successed_hack + RS_MLE)
|
||||
#define RS_HK_TLE (successed_hack + RS_TLE)
|
||||
#define RS_HK_OLE (successed_hack + RS_OLE)
|
||||
#define RS_HK_DGS (successed_hack + RS_DGS)
|
||||
#define RS_HK_JGF (successed_hack + RS_JGF)
|
||||
#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE)
|
||||
#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE)
|
||||
#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE)
|
||||
#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE)
|
||||
#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS)
|
||||
#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF)
|
||||
|
File diff suppressed because it is too large
Load Diff
2478
judger/uoj_judger/include/uoj_judger_v2.h
Normal file
2478
judger/uoj_judger/include/uoj_judger_v2.h
Normal file
File diff suppressed because it is too large
Load Diff
438
judger/uoj_judger/include/uoj_run.h
Normal file
438
judger/uoj_judger/include/uoj_run.h
Normal file
@ -0,0 +1,438 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <cstdarg>
|
||||
#include <filesystem>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
#define UOJ_GCC "/usr/bin/gcc-11"
|
||||
#define UOJ_GPLUSPLUS "/usr/bin/g++-11"
|
||||
#define UOJ_PYTHON2_7 "/usr/bin/python2.7"
|
||||
#define UOJ_PYTHON3 "/usr/bin/python3.10"
|
||||
#define UOJ_FPC "/usr/bin/fpc"
|
||||
#define UOJ_OPEN_JDK8 "/usr/lib/jvm/java-8-openjdk-amd64"
|
||||
#define UOJ_OPEN_JDK11 "/usr/lib/jvm/java-11-openjdk-amd64"
|
||||
#define UOJ_OPEN_JDK17 "/usr/lib/jvm/java-17-openjdk-amd64"
|
||||
|
||||
std::string escapeshellarg(int arg) {
|
||||
return std::to_string(arg);
|
||||
}
|
||||
std::string escapeshellarg(const std::string &arg) {
|
||||
std::string res = "'";
|
||||
for (char c : arg) {
|
||||
if (c == '\'') {
|
||||
res += "'\\''";
|
||||
} else {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
res += "'";
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& spaced_out(std::ostream &out, const T &arg) {
|
||||
return out << arg;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::ostream& spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
|
||||
return spaced_out(out << arg << " ", rest...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& add_spaced_out(std::ostream &out, const T &arg) {
|
||||
return out << " " << arg;
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
std::ostream& add_spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
|
||||
return spaced_out(out << " " << arg, rest...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
int execute(const Args& ...args) {
|
||||
std::ostringstream sout;
|
||||
spaced_out(sout, args...);
|
||||
#ifdef UOJ_SHOW_EVERY_CMD
|
||||
std::cerr << sout.str() << std::endl;
|
||||
#endif
|
||||
int status = system(sout.str().c_str());
|
||||
if (status == -1 || !WIFEXITED(status)) {
|
||||
return -1;
|
||||
}
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
int executef(const char *fmt, ...) {
|
||||
const int L = 1 << 10;
|
||||
char cmd[L];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int res = vsnprintf(cmd, L, fmt, ap);
|
||||
if (res < 0 || res >= L) {
|
||||
return -1;
|
||||
}
|
||||
res = execute(cmd);
|
||||
va_end(ap);
|
||||
return res;
|
||||
}
|
||||
|
||||
class cannot_determine_class_name_error : std::invalid_argument {
|
||||
public:
|
||||
explicit cannot_determine_class_name_error()
|
||||
: std::invalid_argument("cannot determine the class name!") {}
|
||||
};
|
||||
|
||||
std::string get_class_name_from_file(const std::string &fname) {
|
||||
std::ifstream fin(fname);
|
||||
if (!fin) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
std::string class_name;
|
||||
if (!(fin >> class_name)) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
if (class_name.length() > 100) {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
for (char &c : class_name) {
|
||||
if (!isalnum(c) && c != '_') {
|
||||
throw cannot_determine_class_name_error();
|
||||
}
|
||||
}
|
||||
return class_name;
|
||||
}
|
||||
|
||||
bool put_class_name_to_file(const std::string &fname, const std::string &class_name) {
|
||||
std::ofstream fout(fname);
|
||||
if (!fout) {
|
||||
return false;
|
||||
}
|
||||
if (!(fout << class_name << std::endl)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> lang_upgrade_map = {
|
||||
{"Java7" , "Java8" },
|
||||
{"Java14", "Java17" },
|
||||
};
|
||||
|
||||
std::string upgraded_lang(const std::string &lang) {
|
||||
return lang_upgrade_map.count(lang) ? lang_upgrade_map[lang] : lang;
|
||||
}
|
||||
|
||||
namespace runp {
|
||||
namespace fs = std::filesystem;
|
||||
fs::path run_path;
|
||||
|
||||
struct limits_t {
|
||||
int time;
|
||||
int memory;
|
||||
int output;
|
||||
int real_time;
|
||||
int stack;
|
||||
|
||||
limits_t() = default;
|
||||
limits_t(const int &_time, const int &_memory, const int &_output)
|
||||
: time(_time), memory(_memory), output(_output), real_time(-1), stack(-1) {
|
||||
}
|
||||
};
|
||||
|
||||
// result type
|
||||
enum RS_TYPE {
|
||||
RS_AC = 0,
|
||||
RS_WA = 1,
|
||||
RS_RE = 2,
|
||||
RS_MLE = 3,
|
||||
RS_TLE = 4,
|
||||
RS_OLE = 5,
|
||||
RS_DGS = 6,
|
||||
RS_JGF = 7
|
||||
};
|
||||
|
||||
inline std::string rstype_str(RS_TYPE id) {
|
||||
switch (id) {
|
||||
case RS_AC: return "Accepted";
|
||||
case RS_WA: return "Wrong Answer";
|
||||
case RS_RE : return "Runtime Error";
|
||||
case RS_MLE: return "Memory Limit Exceeded";
|
||||
case RS_TLE: return "Time Limit Exceeded";
|
||||
case RS_OLE: return "Output Limit Exceeded";
|
||||
case RS_DGS: return "Dangerous Syscalls";
|
||||
case RS_JGF: return "Judgment Failed";
|
||||
default : return "Unknown Result";
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_type_from_lang(std::string lang) {
|
||||
lang = upgraded_lang(lang);
|
||||
if (lang == "Python2.7") {
|
||||
return "python2.7";
|
||||
} else if (lang == "Python3") {
|
||||
return "python3";
|
||||
} else if (lang == "Java8") {
|
||||
return "java8";
|
||||
} else if (lang == "Java11") {
|
||||
return "java11";
|
||||
} else if (lang == |