From 1e2dc71f9a8c19d226505c3e58a0c8c1f14256c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 11:03:57 +0800 Subject: [PATCH 1/8] Add files via upload --- __init__.py | 38 ++++++++++++++++++++ requirements.txt | 2 ++ settings.yaml | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 __init__.py create mode 100644 requirements.txt create mode 100644 settings.yaml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..ee8069d --- /dev/null +++ b/__init__.py @@ -0,0 +1,38 @@ +import shlex +import sys +import threading + +import docker +import ruamel.yaml + + +def _judge(container, command, stdin='', stdout='', timeout=1): + result = container.exec_run(['sh', '-c', 'echo ' + shlex.quote(stdin) + ' | timeout -k ' + (str(timeout) + ' ') * 2 + command], demux=True) + if result.exit_code == 124: + raise TimeoutError('TLE') + if result.exit_code: + raise Exception('RE') + assert result.output[0].decode().rstrip() == stdout.rstrip(), 'WA' + +def judge(settings, language, compiler, source='', tests=[], timeout=1, client=docker.from_env()): + container = client.containers.run(settings[language][compiler]['image'], detach=True, network_disabled=True, tty=True) + try: + container.exec_run(['sh', '-c', 'echo ' + shlex.quote(source) + ' > ' + settings[language][compiler]['source']]) + result = container.exec_run(settings[language][compiler]['compile'], demux=True) + if not result.exit_code: + threads = [] + for stdin, stdout in tests: + threads.append(threading.Thread(target=_judge, args=(container, settings[language][compiler]['judge'], stdin, stdout, 1))) + threads[-1].start() + for thread in threads: + thread.join() + else: + print(result.output[1].decode().rstrip()) + finally: + container.stop() + container.remove() + +if __name__ == '__main__': + yaml = ruamel.yaml.YAML() + settings = yaml.load(open('settings.yaml')) + judge(settings, sys.argv[1], sys.argv[2], sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2])) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4a9e16c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +docker[tls] +ruamel.yaml diff --git a/settings.yaml b/settings.yaml new file mode 100644 index 0000000..14c8700 --- /dev/null +++ b/settings.yaml @@ -0,0 +1,94 @@ +c: + gcc-4.8: + image: gcc:4.8 + source: a.c + compile: gcc a.c + judge: ./a.out + gcc-4.9: + image: gcc:4.9 + source: a.c + compile: gcc a.c + judge: ./a.out + gcc-5: + image: gcc:5 + source: a.c + compile: gcc a.c + judge: ./a.out + gcc: + image: gcc + source: a.c + compile: gcc a.c + judge: ./a.out +cpp: + g++-4.8: + image: gcc:4.8 + source: a.cpp + compile: g++ a.cpp + judge: ./a.out + g++-4.9: + image: gcc:4.9 + source: a.cpp + compile: g++ a.cpp + judge: ./a.out + g++-5: + image: gcc:5 + source: a.cpp + compile: g++ a.cpp + judge: ./a.out + g++: + image: gcc + source: a.cpp + compile: g++ a.cpp + judge: ./a.out +go: + go1.11: + image: golang:1.11 + source: main.go + compile: go build main.go + judge: ./main + go1.12: + image: golang:1.12 + source: main.go + compile: go build main.go + judge: ./main + go1: + image: golang:1 + source: main.go + compile: go build main.go + judge: ./main + go: + image: golang + source: main.go + compile: go build main.go + judge: ./main +python: + python2.7: + image: python:2.7 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py + python2: + image: python:2 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py + python3.5: + image: python:3.5 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py + python3.6: + image: python:3.6 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py + python3.7: + image: python3.7 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py + python3: + image: python:3 + source: __init__.py + compile: python -m compileall -q __init__.py + judge: python __init__.py From 47ca7440140bde0cf560d872ee9aa92591a1e10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 13:22:57 +0800 Subject: [PATCH 2/8] Performance improvement Use `docker rm -f` instead of stoping the container before removing it. --- __init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index ee8069d..52f79a8 100644 --- a/__init__.py +++ b/__init__.py @@ -29,8 +29,7 @@ def judge(settings, language, compiler, source='', tests=[], timeout=1, client=d else: print(result.output[1].decode().rstrip()) finally: - container.stop() - container.remove() + container.remove(force=True) if __name__ == '__main__': yaml = ruamel.yaml.YAML() From 29ab6645f4e1f20965a855dfa396c644fb713f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 14:43:02 +0800 Subject: [PATCH 3/8] Edit according to pylint ************* Module docker-judge __init__.py:10:0: C0301: Line too long (143/100) (line-too-long) __init__.py:18:0: C0301: Line too long (122/100) (line-too-long) __init__.py:20:0: C0301: Line too long (120/100) (line-too-long) __init__.py:25:0: C0301: Line too long (138/100) (line-too-long) __init__.py:1:0: C0103: Module name "docker-judge" doesn't conform to snake_case naming style (invalid-name) __init__.py:1:0: C0111: Missing module docstring (missing-docstring) __init__.py:17:10: W0621: Redefining name 'settings' from outer scope (line 36) (redefined-outer-name) __init__.py:17:0: W0102: Dangerous default value [] as argument (dangerous-default-value) __init__.py:17:0: C0111: Missing function docstring (missing-docstring) __init__.py:17:0: R0913: Too many arguments (7/5) (too-many-arguments) __init__.py:17:61: W0613: Unused argument 'timeout' (unused-argument) __init__.py:35:4: C0103: Constant name "yaml" doesn't conform to UPPER_CASE naming style (invalid-name) __init__.py:36:4: C0103: Constant name "settings" doesn't conform to UPPER_CASE naming style (invalid-name) ----------------------------------- Your code has been rated at 5.67/10 --- __init__.py => dockerjudge/__init__.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) rename __init__.py => dockerjudge/__init__.py (50%) diff --git a/__init__.py b/dockerjudge/__init__.py similarity index 50% rename from __init__.py rename to dockerjudge/__init__.py index 52f79a8..7899931 100644 --- a/__init__.py +++ b/dockerjudge/__init__.py @@ -1,3 +1,5 @@ +'dockerjudge - A Docker Based Online Judge Engine' + import shlex import sys import threading @@ -7,22 +9,27 @@ def _judge(container, command, stdin='', stdout='', timeout=1): - result = container.exec_run(['sh', '-c', 'echo ' + shlex.quote(stdin) + ' | timeout -k ' + (str(timeout) + ' ') * 2 + command], demux=True) + 'Run each test case' + result = container.exec_run(['sh', '-c', 'echo ' + shlex.quote(stdin) + ' | timeout -k ' + + (str(timeout) + ' ') * 2 + command], demux=True) if result.exit_code == 124: raise TimeoutError('TLE') if result.exit_code: raise Exception('RE') assert result.output[0].decode().rstrip() == stdout.rstrip(), 'WA' -def judge(settings, language, compiler, source='', tests=[], timeout=1, client=docker.from_env()): - container = client.containers.run(settings[language][compiler]['image'], detach=True, network_disabled=True, tty=True) +def judge(settings, source='', tests=(), timeout=1, client=docker.from_env()): + 'Main judge function' + container = client.containers.run(settings['image'], detach=True, + network_disabled=True, tty=True) try: - container.exec_run(['sh', '-c', 'echo ' + shlex.quote(source) + ' > ' + settings[language][compiler]['source']]) - result = container.exec_run(settings[language][compiler]['compile'], demux=True) + container.exec_run(['sh', '-c', 'echo ' + shlex.quote(source) + ' > ' + settings['source']]) + result = container.exec_run(settings['compile'], demux=True) if not result.exit_code: threads = [] for stdin, stdout in tests: - threads.append(threading.Thread(target=_judge, args=(container, settings[language][compiler]['judge'], stdin, stdout, 1))) + threads.append(threading.Thread(target=_judge, args=(container, settings['judge'], + stdin, stdout, timeout))) threads[-1].start() for thread in threads: thread.join() @@ -32,6 +39,5 @@ def judge(settings, language, compiler, source='', tests=[], timeout=1, client=d container.remove(force=True) if __name__ == '__main__': - yaml = ruamel.yaml.YAML() - settings = yaml.load(open('settings.yaml')) - judge(settings, sys.argv[1], sys.argv[2], sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2])) + judge(ruamel.yaml.YAML().load(open('settings.yaml'))[sys.argv[1]][sys.argv[2]], + sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2])) From f93aaaa13340775fc878391d93f40340299ffecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 14:56:25 +0800 Subject: [PATCH 4/8] Create .travis.yml --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..894e74b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: python +services: + - docker +python: + - "3.5" + - "3.5-dev" + - "3.6" + - "3.6-dev" + - "3.7-dev" +install: + - pip install -r requirements.txt + - pip install pylint +script: + - pylint dockerjudge From 6abc6c759ad79dce22a7856792731070122222fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 17:56:32 +0800 Subject: [PATCH 5/8] Return result --- dockerjudge/__init__.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/dockerjudge/__init__.py b/dockerjudge/__init__.py index 7899931..71d6a75 100644 --- a/dockerjudge/__init__.py +++ b/dockerjudge/__init__.py @@ -8,15 +8,27 @@ import ruamel.yaml +class Thread(threading.Thread): + 'Subclass of threading.Thread with return value' + return_value = None + def run(self): + try: + if self._target: + self.return_value = self._target(*self._args, **self._kwargs) + finally: + del self._target, self._args, self._kwargs + def _judge(container, command, stdin='', stdout='', timeout=1): 'Run each test case' result = container.exec_run(['sh', '-c', 'echo ' + shlex.quote(stdin) + ' | timeout -k ' + (str(timeout) + ' ') * 2 + command], demux=True) if result.exit_code == 124: - raise TimeoutError('TLE') + return 'TLE' if result.exit_code: - raise Exception('RE') - assert result.output[0].decode().rstrip() == stdout.rstrip(), 'WA' + return 'RE' + if result.output[0].decode().rstrip() != stdout.rstrip(): + return 'WA' + return 'AC' def judge(settings, source='', tests=(), timeout=1, client=docker.from_env()): 'Main judge function' @@ -24,20 +36,22 @@ def judge(settings, source='', tests=(), timeout=1, client=docker.from_env()): network_disabled=True, tty=True) try: container.exec_run(['sh', '-c', 'echo ' + shlex.quote(source) + ' > ' + settings['source']]) - result = container.exec_run(settings['compile'], demux=True) - if not result.exit_code: + compiler = container.exec_run(settings['compile'], demux=True) + if not compiler.exit_code: threads = [] for stdin, stdout in tests: - threads.append(threading.Thread(target=_judge, args=(container, settings['judge'], - stdin, stdout, timeout))) + threads.append(Thread(target=_judge, + args=(container, settings['judge'], stdin, stdout, timeout))) threads[-1].start() + result = [] for thread in threads: thread.join() - else: - print(result.output[1].decode().rstrip()) + result.append(thread.return_value) + return result + return ['CE', compiler.output[1].decode()] finally: container.remove(force=True) if __name__ == '__main__': - judge(ruamel.yaml.YAML().load(open('settings.yaml'))[sys.argv[1]][sys.argv[2]], - sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2])) + print(judge(ruamel.yaml.YAML().load(open('settings.yaml'))[sys.argv[1]][sys.argv[2]], + sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2]))) From ddf0c38f65e1e06336f377b86a6f1ce79b54f7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 17:58:59 +0800 Subject: [PATCH 6/8] Fix bad-continuation ************* Module dockerjudge dockerjudge/__init__.py:57:0: C0330: Wrong continued indentation (add 6 spaces). sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2]))) ^ | (bad-continuation) ------------------------------------------------------------------- Your code has been rated at 9.75/10 --- dockerjudge/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerjudge/__init__.py b/dockerjudge/__init__.py index 71d6a75..ecc31f3 100644 --- a/dockerjudge/__init__.py +++ b/dockerjudge/__init__.py @@ -54,4 +54,4 @@ def judge(settings, source='', tests=(), timeout=1, client=docker.from_env()): if __name__ == '__main__': print(judge(ruamel.yaml.YAML().load(open('settings.yaml'))[sys.argv[1]][sys.argv[2]], - sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2]))) + sys.stdin.read(), zip(sys.argv[3::2], sys.argv[4::2]))) From 1af24e530e5306f352e83b4e38127149bc669163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 19:01:15 +0800 Subject: [PATCH 7/8] Always return stderr of compiler --- dockerjudge/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dockerjudge/__init__.py b/dockerjudge/__init__.py index ecc31f3..499c35b 100644 --- a/dockerjudge/__init__.py +++ b/dockerjudge/__init__.py @@ -47,8 +47,8 @@ def judge(settings, source='', tests=(), timeout=1, client=docker.from_env()): for thread in threads: thread.join() result.append(thread.return_value) - return result - return ['CE', compiler.output[1].decode()] + return [result, (compiler.output[1] or b'').decode()] + return [['CE'] * len(tests), compiler.output[1].decode()] finally: container.remove(force=True) From 2569ce52dbd54cc965a743eb8b3f600b0059dd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=AA=E5=BF=83=E7=A6=BE?= Date: Sat, 27 Apr 2019 20:06:09 +0800 Subject: [PATCH 8/8] Add LICENSE and Travis CI Build Status --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 5a97f5d..b978d70 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # docker-judge +[![Build Status](https://www.travis-ci.org/wangxinhe2006/docker-judge.svg)](https://www.travis-ci.org/wangxinhe2006/docker-judge) + A Docker Based Online Judge Engine + +# [License](LICENSE) +Licensed under [the GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html) + +[![agplv3-155x51.png](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/graphics/agplv3-155x51.png)