checker works

This commit is contained in:
Egor Matveev 2022-05-05 15:47:16 +03:00
parent 7028ac37b6
commit 5518c34636
25 changed files with 295 additions and 455 deletions

View File

@ -6,6 +6,7 @@ urlpatterns = [
path("status", views.status), path("status", views.status),
path("available", views.available), path("available", views.available),
path("get_dynamic", views.get_dynamic), path("get_dynamic", views.get_dynamic),
path("set_result", views.set_result), path("save_solution", views.save_solution),
path("current_test", views.current_test), path("save_progress", views.save_progress),
path("notify", views.notify)
] ]

View File

@ -9,8 +9,9 @@ from django.utils import timezone
from Checker.models import Checker from Checker.models import Checker
from FileStorage.sync import synchronized_method from FileStorage.sync import synchronized_method
from Main.models import Solution, SolutionFile, ExtraFile from Main.models import Solution, SolutionFile, ExtraFile, Progress
from SprintLib.utils import generate_token from SprintLib.utils import generate_token
from SprintLib.queue import notify as notification
def get_dynamic(request): def get_dynamic(request):
@ -50,7 +51,7 @@ def available(request):
with TemporaryDirectory() as tempdir: with TemporaryDirectory() as tempdir:
with ZipFile(join(tempdir, "package.zip"), 'w') as zip_file: with ZipFile(join(tempdir, "package.zip"), 'w') as zip_file:
for sf in SolutionFile.objects.filter(solution=solution): for sf in SolutionFile.objects.filter(solution=solution):
zip_file.writestr(sf.path, sf.bytes) zip_file.writestr(join('solution', sf.path), sf.bytes)
for ef in ExtraFile.objects.filter(task=solution.task): for ef in ExtraFile.objects.filter(task=solution.task):
zip_file.writestr(ef.filename, ef.bytes) zip_file.writestr(ef.filename, ef.bytes)
response = HttpResponse(open(join(tempdir, 'package.zip'), 'rb').read(), content_type='application/octet-stream', status=201) response = HttpResponse(open(join(tempdir, 'package.zip'), 'rb').read(), content_type='application/octet-stream', status=201)
@ -62,17 +63,22 @@ def available(request):
return JsonResponse({"status": "incorrect token"}, status=403) return JsonResponse({"status": "incorrect token"}, status=403)
def set_result(request): def save_solution(request):
try: try:
checker = Checker.objects.get(dynamic_token=request.GET['token']) checker = Checker.objects.get(dynamic_token=request.GET['token'])
solution = Solution.objects.get(id=request.GET['solution_id']) solution = Solution.objects.get(id=request.GET['solution_id'])
result = request.GET['result'] result = request.GET['result']
test = request.GET.get("test")
extras = request.GET.get('extras')
if checker.set != solution.set: if checker.set != solution.set:
return JsonResponse({"status": "incorrect solution"}, status=403) return JsonResponse({"status": "incorrect solution"}, status=403)
solution.result = result solution.result = result
solution.test = test
solution.extras = extras
if result == 'OK': if result == 'OK':
solution.test = None solution.test = None
solution.save() solution.save()
if not result.startswith('Testing'):
checker.testing_solution = None checker.testing_solution = None
checker.save() checker.save()
return JsonResponse({"status": True}) return JsonResponse({"status": True})
@ -80,15 +86,38 @@ def set_result(request):
return JsonResponse({"status": "incorrect token"}, status=403) return JsonResponse({"status": "incorrect token"}, status=403)
def current_test(request): def notify(request):
try: try:
checker = Checker.objects.get(dynamic_token=request.GET['token']) checker = Checker.objects.get(dynamic_token=request.GET['token'])
solution = Solution.objects.get(id=request.GET['solution_id']) solution = Solution.objects.get(id=request.GET['solution_id'])
if checker.set != solution.set: if checker.set != solution.set:
return JsonResponse({"status": "incorrect solution"}, status=403) return JsonResponse({"status": "incorrect solution"}, status=403)
test = int(request.GET['test']) notification(
solution.test = test solution.user,
solution.save() "solution_result",
f"Задача: {solution.task.name}\n"
f"Результат: {solution.result}\n"
f"Очки решения: {Progress.by_solution(solution).score}\n"
f"Текущий рейтинг: {solution.user.userinfo.rating}")
return JsonResponse({"status": True})
except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403)
def save_progress(request):
try:
checker = Checker.objects.get(dynamic_token=request.GET['token'])
solution = Solution.objects.get(id=request.GET['solution_id'])
if checker.set != solution.set:
return JsonResponse({"status": "incorrect solution"}, status=403)
progress = Progress.objects.get(
user=solution.user, task=solution.task
)
if progress.finished_time is None:
progress.finished_time = solution.time_sent
progress.finished = True
progress.save()
progress.increment_rating()
return JsonResponse({"status": True}) return JsonResponse({"status": True})
except ObjectDoesNotExist: except ObjectDoesNotExist:
return JsonResponse({"status": "incorrect token"}, status=403) return JsonResponse({"status": "incorrect token"}, status=403)

View File

@ -1,17 +0,0 @@
FROM docker:dind
RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
RUN python3 -m ensurepip
RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev jpeg-dev zlib-dev libjpeg
RUN pip3 install --no-cache --upgrade pip setuptools
RUN addgroup -S docker
ENV PYTHONUNBUFFERED 1
RUN mkdir -p /usr/src/app/
WORKDIR /usr/src/app/
RUN pip install requests
COPY . /usr/src/app/
CMD ["python", "main.py"]

View File

@ -1,64 +0,0 @@
from dataclasses import dataclass
@dataclass
class Language:
id: int
name: str
work_name: str
file_type: str
logo_url: str
image: str
highlight: str
def __str__(self):
return self.name
languages = [
Language(
id=0,
name="Python3",
work_name="Python3",
file_type="py",
logo_url="https://entredatos.es/wp-content/uploads/2021/05/1200px-Python-logo-notext.svg.png",
image="python:3.6",
highlight="python",
),
Language(
id=1,
name="Kotlin",
work_name="Kotlin",
file_type="kt",
logo_url="https://upload.wikimedia.org/wikipedia/commons/0/06/Kotlin_Icon.svg",
image="zenika/kotlin",
highlight="kotlin",
),
Language(
id=2,
name="C++",
work_name="Cpp",
file_type="cpp",
logo_url="https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/ISO_C%2B%2B_Logo.svg/1822px-ISO_C%2B%2B_Logo.svg.png",
image="gcc",
highlight="cpp",
),
Language(
id=3,
name="Java",
work_name="Java",
file_type="java",
logo_url="https://upload.wikimedia.org/wikipedia/ru/thumb/3/39/Java_logo.svg/1200px-Java_logo.svg.png",
image="openjdk",
highlight="java",
),
Language(
id=4,
name="C#",
work_name="CSharp",
file_type="cs",
logo_url="https://cdn.worldvectorlogo.com/logos/c--4.svg",
image="mono",
highlight="csharp",
),
]

View File

@ -1,84 +0,0 @@
from multiprocessing import Process
from os import getenv, mkdir
from os.path import join, exists
from shutil import rmtree
from tempfile import TemporaryDirectory
from time import sleep
from zipfile import ZipFile
from requests import get
from language import languages
from testers import *
host = "http://dev.sprinthub.ru/"
def process_solution(path, data, language_id, solution_id, timeout, token, host):
with open(join(path, "package.zip"), 'wb') as fs:
fs.write(data)
with ZipFile(join(path, "package.zip"), 'r') as zip_ref:
zip_ref.extractall(path)
language = languages[language_id]
try:
result = eval(language.work_name + "Tester")(path, solution_id, language_id, timeout, token, host).execute()
except Exception as e:
print(str(e))
result = "TE"
return result
def poll(token):
correct_token = True
while correct_token:
code = get(f"{host}checker/status", params={"token": token}).status_code
if code != 200:
correct_token = False
else:
sleep(2)
def main():
request = get(f"{host}checker/get_dynamic", params={"token": getenv("TOKEN")})
if request.status_code != 200:
print("Error happened: " + request.json()['status'])
exit(1)
dynamic_token = request.json()['token']
p = Process(target=poll, args=(dynamic_token,))
p.start()
while True:
data = get(f"{host}checker/available", params={"token": dynamic_token})
if data.status_code == 200:
sleep(2)
continue
elif data.status_code == 201:
tempdir = "/var/tmp/solution/"
try:
mkdir(tempdir)
result = process_solution(
tempdir,
data.content,
int(data.headers['language_id']),
int(data.headers['solution_id']),
int(data.headers['timeout']),
dynamic_token,
host
)
get(f"{host}checker/set_result", params={
"token": dynamic_token,
"solution_id": data.headers['solution_id'],
"result": result
})
finally:
if exists(tempdir):
rmtree(tempdir)
elif data.status_code == 403:
print("token removed")
exit(1)
else:
print("unknown status")
exit(1)
if __name__ == '__main__':
main()

View File

@ -1,120 +0,0 @@
from os import listdir
from os.path import join, exists
from subprocess import call, TimeoutExpired
from SprintLib.language import *
from requests import get
class TestException(Exception):
pass
class BaseTester:
working_directory = "app"
checker_code = None
def exec_command(self, command, working_directory="app", timeout=None):
return call(
f'docker exec -i solution sh -c "cd {working_directory} && {command}"',
shell=True,
timeout=timeout,
)
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith("." + self.language.file_type)
]
code = self.exec_command(
f'{self.build_command} {" ".join(files)}',
working_directory=self.working_directory,
)
if code != 0:
raise TestException("CE")
def test(self, filename):
print('testing ' + filename)
code = self.exec_command(
f"< {filename} {self.command} > output.txt",
timeout=self.timeout / 1000,
)
if code != 0:
raise TestException("RE")
result = open(join(self.path, "output.txt"), "r").read().strip().replace('\r\n', '\n')
print("got result", result)
if self.checker_code is not None:
print('using checker')
with open(join(self.path, 'expected.txt'), 'w') as fs:
fs.write(self.predicted)
with open(join(self.path, 'checker.py'), 'w') as fs:
fs.write(self.checker_code)
code = call(f'docker exec -i checker sh -c "cd app && python checker.py"', shell=True, timeout=1)
if code != 0:
raise TestException("WA")
else:
print('using simple check')
if result != self.predicted:
print('incorrect')
raise TestException("WA")
print('correct')
def after_test(self):
pass
@property
def command(self):
return "./executable.exe"
@property
def build_command(self):
return ""
@property
def path(self):
return self._path
@property
def language(self):
return languages[self.language_id]
def __init__(self, path, solution_id, language_id, timeout, token, host):
self.solution_id = solution_id
self._path = path
self.language_id = language_id
self.timeout = timeout
self.token = token
self.host = host
def execute(self):
docker_command = f"docker run --name solution --volume={self.path}:/{self.working_directory} -t -d {self.language.image}"
print(docker_command)
call(docker_command, shell=True)
checker = join(self.path, 'checker.py')
if exists(checker):
self.checker_code = open(checker, 'r').read()
call(f"docker run --name checker --volume={self.path}:/app -t -d python:3.6", shell=True)
print("Container created")
result = None
try:
self.before_test()
print("before test finished")
for file in listdir(self.path):
if not file.endswith(".a") and exists(join(self.path, file + '.a')):
self.predicted = open(join(self.path, file + '.a'), 'r').read().strip().replace('\r\n', '\n')
print('predicted:', self.predicted)
get(f"{self.host}checker/current_test", params={"token": self.token, 'test': file, 'solution_id': self.solution_id})
self.test(file)
self.after_test()
result = "OK"
except TestException as e:
result = str(e)
except TimeoutExpired:
result = "TL"
except Exception as e:
print(str(e))
result = "TE"
call(f"docker rm --force solution", shell=True)
call(f"docker rm --force checker", shell=True)
return result

View File

@ -1,11 +0,0 @@
from .BaseTester import BaseTester
class CSharpTester(BaseTester):
@property
def build_command(self):
return "csc /out:executable.exe"
@property
def command(self):
return "mono executable.exe"

View File

@ -1,7 +0,0 @@
from .BaseTester import BaseTester
class CppTester(BaseTester):
@property
def build_command(self):
return "g++ -o executable.exe"

View File

@ -1,8 +0,0 @@
from .BaseTester import BaseTester
class GoTester(BaseTester):
working_directory = "../app"
def build_command(self):
return "go build -o executable.exe"

View File

@ -1,27 +0,0 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class JavaTester(BaseTester):
_executable = None
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith(".java")
]
code = self.exec_command(f"javac {' '.join(files)}")
if code != 0:
raise TestException("CE")
for file in listdir(self.path):
if file.endswith(".class"):
self._executable = file.rstrip(".class")
break
if self._executable is None:
raise TestException("TE")
@property
def command(self):
return f"java -classpath . {self._executable}"

View File

@ -1,21 +0,0 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class KotlinTester(BaseTester):
def before_test(self):
files = [
file
for file in listdir(self.path)
if file.endswith(".kt")
]
code = self.exec_command(
f'kotlinc {" ".join(files)} -include-runtime -d solution.jar'
)
if code != 0:
raise TestException("CE")
@property
def command(self):
return "java -jar solution.jar"

View File

@ -1,19 +0,0 @@
from os import listdir
from .BaseTester import BaseTester, TestException
class Python3Tester(BaseTester):
file = None
def before_test(self):
for file in listdir(self.path):
if file.endswith(".py") and file != 'checker.py':
self.file = file
break
if self.file is None:
raise TestException("TE")
@property
def command(self):
return f"python3 {self.file}"

View File

@ -1,7 +0,0 @@
from .BaseTester import BaseTester
from .Python3Tester import Python3Tester
from .CppTester import CppTester
from .GoTester import GoTester
from .JavaTester import JavaTester
from .CSharpTester import CSharpTester
from .KotlinTester import KotlinTester

View File

@ -14,3 +14,5 @@ WORKDIR /usr/src/app/
COPY . /usr/src/app/ COPY . /usr/src/app/
RUN pip3 install -r requirements.txt RUN pip3 install -r requirements.txt
CMD ["./manage.py", "checker"]

View File

@ -5,10 +5,18 @@ from SprintLib.utils import get_bytes, write_bytes, delete_file
class FileStorageMixin: class FileStorageMixin:
@cached_property _bytes = None
@property
def bytes(self): def bytes(self):
if self._bytes is not None:
return self._bytes
return get_bytes(self.fs_id) return get_bytes(self.fs_id)
@bytes.setter
def bytes(self, value):
self._bytes = value
@cached_property @cached_property
def text(self): def text(self):
try: try:

View File

@ -22,6 +22,8 @@ class Solution(models.Model):
set = models.ForeignKey(Set, null=True, blank=True, on_delete=models.SET_NULL) set = models.ForeignKey(Set, null=True, blank=True, on_delete=models.SET_NULL)
extras = models.JSONField(default=dict) extras = models.JSONField(default=dict)
_solutionfiles = None
class Meta: class Meta:
indexes = [ indexes = [
models.Index(fields=['task', 'user', '-time_sent']), models.Index(fields=['task', 'user', '-time_sent']),
@ -29,6 +31,16 @@ class Solution(models.Model):
models.Index(fields=['set', '-time_sent']), models.Index(fields=['set', '-time_sent']),
] ]
@property
def solutionfiles(self):
if self._solutionfiles is not None:
return self._solutionfiles
return SolutionFile.objects.filter(solution=self)
@solutionfiles.setter
def solutionfiles(self, value):
self._solutionfiles = value
@cached_property @cached_property
def settask(self): def settask(self):
return SetTask.objects.filter(set=self.set, task=self.task).first() return SetTask.objects.filter(set=self.set, task=self.task).first()
@ -83,10 +95,6 @@ class Solution(models.Model):
return "info" return "info"
return "danger" return "danger"
@property
def volume_directory(self):
return "/sprint-data/worker/" + str(self.id)
def exec_command(self, command, working_directory="app", timeout=None): def exec_command(self, command, working_directory="app", timeout=None):
return call( return call(
f'docker exec -i solution_{self.id} sh -c "cd {working_directory} && {command}"', f'docker exec -i solution_{self.id} sh -c "cd {working_directory} && {command}"',

View File

@ -1,8 +1,8 @@
from cached_property import cached_property
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import JSONField from django.db.models import JSONField
from django.utils import timezone
from Main.models.dump import Dump from Main.models.dump import Dump
from Main.models.extrafile import ExtraFile from Main.models.extrafile import ExtraFile
@ -22,6 +22,8 @@ class Task(models.Model):
allow_sharing = models.BooleanField(default=False) allow_sharing = models.BooleanField(default=False)
changes = JSONField(default=list) changes = JSONField(default=list)
_extrafiles = None
def __str__(self): def __str__(self):
return self.name return self.name
@ -35,7 +37,32 @@ class Task(models.Model):
@property @property
def tests(self): def tests(self):
return ExtraFile.objects.filter(task=self, is_test=True).order_by('filename') for file in sorted(self.extrafiles, key=lambda x: x.filename):
if file.filename.isnumeric() or file.filename.endswith('.a') and file.filename[:-2].isnumeric():
yield file
@property
def extrafiles(self):
if self._extrafiles is not None:
return self._extrafiles
return ExtraFile.objects.filter(task=self)
@extrafiles.setter
def extrafiles(self, value):
self._extrafiles = value
@property
def dockerfiles(self):
for file in self.extrafiles:
if file.filename.startswith('Dockerfile_'):
yield file
@cached_property
def checkerfile(self):
for file in self.extrafiles:
if file.filename == 'extrafile.py':
return file
return None
@property @property
def tests_count(self): def tests_count(self):
@ -44,7 +71,7 @@ class Task(models.Model):
@property @property
def samples(self): def samples(self):
data = [] data = []
for test in self.tests.order_by("test_number"): for test in self.tests:
if test.is_sample and test.readable: if test.is_sample and test.readable:
data.append({"input": test.text, "output": test.answer.text}) data.append({"input": test.text, "output": test.answer.text})
count = 1 count = 1

View File

@ -20,7 +20,7 @@ class SendCodeView(BaseView):
"message": "Пользователя с таким именем не существует", "message": "Пользователя с таким именем не существует",
} }
code = randrange(10000, 100000) code = randrange(10000, 100000)
print(code) print(f"Отправлен код для {username}", code)
user.userinfo.code = code user.userinfo.code = code
user.userinfo.save() user.userinfo.save()
notify(user, "any", "Код для входа в сервис: " + str(code)) notify(user, "any", "Код для входа в сервис: " + str(code))

View File

@ -3,11 +3,10 @@ from os.path import join, exists
from subprocess import call, TimeoutExpired from subprocess import call, TimeoutExpired
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from SprintLib.queue import notify, send_to_queue
from Main.models import ExtraFile, SolutionFile
from Main.models.progress import Progress from Main.models.progress import Progress
from Sprint.settings import CONSTS from Sprint.settings import CONSTS
from SprintLib.utils import get_bytes, Timer from SprintLib.queue import notify, send_to_queue
from SprintLib.utils import Timer
class TestException(Exception): class TestException(Exception):
@ -70,21 +69,21 @@ class BaseTester:
return "" return ""
def call(self, command): def call(self, command):
print(f"Executing command: {command}")
if exists(self.path):
return call(f'cd {self.path} && {command}', shell=True) return call(f'cd {self.path} && {command}', shell=True)
else:
return call(command, shell=True)
def __init__(self, solution): def __init__(self, solution):
self.solution = solution self.solution = solution
def set_test(self, num): def save_solution(self):
self.solution.result = CONSTS["testing_status"] + f"({num})"
self.solution.save() self.solution.save()
def _setup_networking(self): def _setup_networking(self):
self.dockerfiles = sorted(
list(ExtraFile.objects.filter(filename__startswith="Dockerfile_", readable=True, task=self.solution.task)),
key=lambda x: x.filename)
self.call(f"docker network create solution_network_{self.solution.id}") self.call(f"docker network create solution_network_{self.solution.id}")
for file in self.dockerfiles: for file in self.solution.task.dockerfiles:
add_name = file.filename[11:] add_name = file.filename[11:]
with open(join(self.path, 'Dockerfile'), 'w') as fs: with open(join(self.path, 'Dockerfile'), 'w') as fs:
fs.write(file.text) fs.write(file.text)
@ -97,11 +96,41 @@ class BaseTester:
print('run command', run_command) print('run command', run_command)
self.call(run_command) self.call(run_command)
def notify(self):
self.solution.user.userinfo.refresh_from_db()
notify(
self.solution.user,
"solution_result",
f"Задача: {self.solution.task.name}\n"
f"Результат: {self.solution.result}\n"
f"Очки решения: {Progress.by_solution(self.solution).score}\n"
f"Текущий рейтинг: {self.solution.user.userinfo.rating}")
def cleanup(self):
self.solution.save()
send_to_queue("cleaner", {"type": "container", "name": f"solution_{self.solution.id}"})
send_to_queue("cleaner", {"type": "container", "name": f"solution_{self.solution.id}_checker"})
for file in self.solution.task.dockerfiles:
add_name = file.filename[11:]
send_to_queue("cleaner", {"type": "container", "name": f"solution_container_{self.solution.id}_{add_name}"})
send_to_queue("cleaner", {"type": "image", "name": f"solution_image_{self.solution.id}_{add_name}"})
send_to_queue("cleaner", {"type": "network", "name": f"solution_network_{self.solution.id}"})
def save_progress(self):
progress = Progress.objects.get(
user=self.solution.user, task=self.solution.task
)
if progress.finished_time is None:
progress.finished_time = self.solution.time_sent
progress.finished = True
progress.save()
progress.increment_rating()
def execute(self): def execute(self):
self.solution.result = CONSTS["testing_status"] self.solution.result = CONSTS["testing_status"]
self.solution.save() self.save_solution()
with TemporaryDirectory(dir='/tmp') as self.path: with TemporaryDirectory(dir='/tmp') as self.path:
for file in SolutionFile.objects.filter(solution=self.solution): for file in self.solution.solutionfiles:
dirs = file.path.split("/") dirs = file.path.split("/")
for i in range(len(dirs) - 1): for i in range(len(dirs) - 1):
name = join( name = join(
@ -112,19 +141,19 @@ class BaseTester:
with open( with open(
join(self.path, file.path), "wb" join(self.path, file.path), "wb"
) as fs: ) as fs:
fs.write(get_bytes(file.fs_id).replace(b"\r\n", b"\n")) fs.write(file.bytes.replace(b"\r\n", b"\n"))
for file in ExtraFile.objects.filter(task=self.solution.task): for file in self.solution.task.extrafiles:
with open( with open(
join(self.path, file.filename), 'wb' join(self.path, file.filename), 'wb'
) as fs: ) as fs:
bts = get_bytes(file.fs_id) bts = file.bytes
fs.write(bts) fs.write(bts)
print("Files copied") print("Files copied")
self._setup_networking() self._setup_networking()
docker_command = f"docker run --network solution_network_{self.solution.id} --name solution_{self.solution.id} --volume={self.path}:/{self.working_directory} -t -d {self.solution.language.image}" docker_command = f"docker run --network solution_network_{self.solution.id} --name solution_{self.solution.id} --volume={self.path}:/{self.working_directory} -t -d {self.solution.language.image}"
print(docker_command) print(docker_command)
call(docker_command, shell=True) call(docker_command, shell=True)
checker = ExtraFile.objects.filter(task=self.solution.task, filename='checker.py').first() checker = self.solution.task.checkerfile
if checker is not None: if checker is not None:
self.checker_code = checker.text self.checker_code = checker.text
call(f"docker run --network solution_network_{self.solution.id} --name solution_{self.solution.id}_checker --volume={self.path}:/app -t -d python:3.6", shell=True) call(f"docker run --network solution_network_{self.solution.id} --name solution_{self.solution.id}_checker --volume={self.path}:/app -t -d python:3.6", shell=True)
@ -134,13 +163,11 @@ class BaseTester:
print("before test finished") print("before test finished")
for test in self.solution.task.tests: for test in self.solution.task.tests:
if not test.filename.endswith(".a"): if not test.filename.endswith(".a"):
self.predicted = ExtraFile.objects.get( self.predicted = open(join(self.path, test.filename + '.a'), 'r').read().strip().replace('\r\n', '\n')
task=self.solution.task, filename=test.filename + ".a"
).text.strip().replace('\r\n', '\n')
print('predicted:', self.predicted) print('predicted:', self.predicted)
self.solution.test = int(test.filename) self.solution.test = int(test.filename)
self.solution.extras[test.filename] = {'predicted': self.predicted, 'output': ''} self.solution.extras[test.filename] = {'predicted': self.predicted, 'output': ''}
self.solution.save() self.save_solution()
try: try:
self.test(test.filename) self.test(test.filename)
finally: finally:
@ -149,17 +176,12 @@ class BaseTester:
self.solution.extras[test.filename]['output'] = open(join(self.path, 'output.txt'), 'r').read() self.solution.extras[test.filename]['output'] = open(join(self.path, 'output.txt'), 'r').read()
except UnicodeDecodeError: except UnicodeDecodeError:
self.solution.extras[test.filename]['output'] = '' self.solution.extras[test.filename]['output'] = ''
self.save_solution()
self.after_test() self.after_test()
self.solution.result = CONSTS["ok_status"] self.solution.result = CONSTS["ok_status"]
self.solution.test = None self.solution.test = None
progress = Progress.objects.get( self.save_solution()
user=self.solution.user, task=self.solution.task self.save_progress()
)
if progress.finished_time is None:
progress.finished_time = self.solution.time_sent
progress.finished = True
progress.save()
progress.increment_rating()
except TestException as e: except TestException as e:
self.solution.result = str(e) self.solution.result = str(e)
except TimeoutExpired: except TimeoutExpired:
@ -167,19 +189,6 @@ class BaseTester:
except Exception as e: except Exception as e:
self.solution.result = "TE" self.solution.result = "TE"
print(e) print(e)
self.solution.save() self.save_solution()
send_to_queue("cleaner", {"type": "container", "name": f"solution_{self.solution.id}"}) self.cleanup()
send_to_queue("cleaner", {"type": "container", "name": f"solution_{self.solution.id}_checker"}) self.notify()
for file in self.dockerfiles:
add_name = file.filename[11:]
send_to_queue("cleaner", {"type": "container", "name": f"solution_container_{self.solution.id}_{add_name}"})
send_to_queue("cleaner", {"type": "image", "name": f"solution_image_{self.solution.id}_{add_name}"})
send_to_queue("cleaner", {"type": "network", "name": f"solution_network_{self.solution.id}"})
self.solution.user.userinfo.refresh_from_db()
notify(
self.solution.user,
"solution_result",
f"Задача: {self.solution.task.name}\n"
f"Результат: {self.solution.result}\n"
f"Очки решения: {Progress.by_solution(self.solution).score}\n"
f"Текущий рейтинг: {self.solution.user.userinfo.rating}")

View File

@ -0,0 +1,41 @@
import json
from requests import get
from SprintLib.testers import BaseTester
class DistantTester(BaseTester):
host = ""
token = ""
def request(self, method, params=None):
if params is None:
params = {}
return get(f'{self.host}checker/{method}', params={**{
"token": self.token,
"solution_id": self.solution.id,
}, **params})
def save_solution(self):
self.request("save_solution", {
"test": self.solution.test,
"result": self.solution.result,
"extras": json.dumps(self.solution.extras)
})
def notify(self):
self.request("notify")
def cleanup(self):
self.save_solution()
self.call(f"docker rm --force solution_{self.solution.id}")
self.call(f"docker rm --force solution_{self.solution.id}_checker")
for file in self.solution.task.dockerfiles:
add_name = file.filename[11:]
self.call(f"docker rm --force solution_container_{self.solution.id}_{add_name}")
self.call(f"docker image rm solution_image_{self.solution.id}_{add_name}")
self.call(f"docker network rm solution_network_{self.solution.id}")
def save_progress(self):
self.request("save_progress")

View File

@ -7,9 +7,8 @@ class Python3Tester(BaseTester):
file = None file = None
def before_test(self): def before_test(self):
no_files = [file.filename for file in self.solution.task.files]
for file in listdir(self.path): for file in listdir(self.path):
if file.endswith(".py") and file not in no_files: if file == 'solution.py':
self.file = file self.file = file
break break
if self.file is None: if self.file is None:

View File

@ -6,3 +6,4 @@ from .JavaTester import JavaTester
from .CSharpTester import CSharpTester from .CSharpTester import CSharpTester
from .KotlinTester import KotlinTester from .KotlinTester import KotlinTester
from .SwiftTester import SwiftTester from .SwiftTester import SwiftTester
from .DistantTester import DistantTester

View File

@ -0,0 +1,102 @@
from os import getenv, remove, listdir, walk
from os.path import join, isfile
from tempfile import TemporaryDirectory
from threading import Thread
from time import sleep
from zipfile import ZipFile
from django.core.management import BaseCommand
from requests import get
from Main.models import Solution, Task, ExtraFile, SolutionFile
from SprintLib.language import languages
from SprintLib.testers import *
host = 'http://192.168.0.146:8000/'
class Command(BaseCommand):
help = "Tests solution"
def poll(self, token):
correct_token = True
while correct_token:
code = get(f"{host}checker/status", params={"token": token}).status_code
if code != 200:
correct_token = False
else:
sleep(2)
def handle(self, *args, **options):
print("Starting checker")
request = get(f"{host}checker/get_dynamic", params={"token": getenv("TOKEN")})
if request.status_code != 200:
print("Error happened: " + request.json()['status'])
exit(1)
print("Got dynamic token")
dynamic_token = request.json()['token']
p = Thread(target=self.poll, args=(dynamic_token,))
p.start()
while True:
data = get(f"{host}checker/available", params={"token": dynamic_token})
if data.status_code == 200:
sleep(2)
continue
elif data.status_code == 201:
solution = self.create_solution(data)
print("handled solution", solution.id)
tester_class = eval(solution.language.work_name + "Tester")
class LocalTester(DistantTester, tester_class):
...
tester = LocalTester(solution)
tester.host = host
tester.token = dynamic_token
try:
tester.execute()
except Exception as e:
print(e)
solution.result = "TE"
tester.save_solution()
elif data.status_code == 403:
print("token removed")
exit(1)
else:
print("unknown status")
exit(1)
def create_solution(self, data):
with TemporaryDirectory(dir='/tmp') as path:
with open(join(path, "package.zip"), 'wb') as fs:
fs.write(data.content)
with ZipFile(join(path, "package.zip"), 'r') as zip_ref:
zip_ref.extractall(path)
remove(join(path, "package.zip"))
solution = Solution(
id=int(data.headers['solution_id']),
language_id=int(data.headers['language_id']),
task=Task(
time_limit=int(data.headers['timeout']),
)
)
solution.task.extrafiles = [ExtraFile(
filename=file,
task=solution.task
) for file in listdir(path) if isfile(join(path, file))]
for file in solution.task.extrafiles:
file.bytes = open(join(path, file.filename), 'rb').read()
solution.solutionfiles = [
SolutionFile(
path=join(directory, file)[len(join(path, 'solution')) + 1:],
solution=solution,
)
for directory, _, files in walk(join(path, 'solution')) for file in files
]
for file in solution.solutionfiles:
file.bytes = open(join(path, 'solution', file.path), 'rb').read()
return solution

View File

@ -1,6 +1,3 @@
from os.path import join, exists
from shutil import rmtree
from Main.models import Solution from Main.models import Solution
from SprintLib.queue import MessagingSupport from SprintLib.queue import MessagingSupport
from SprintLib.testers import * from SprintLib.testers import *
@ -14,8 +11,9 @@ class Command(MessagingSupport):
id = payload['id'] id = payload['id']
print(f"Received id {id}") print(f"Received id {id}")
solution = Solution.objects.get(id=id) solution = Solution.objects.get(id=id)
tester = eval(solution.language.work_name + "Tester")(solution)
try: try:
eval(solution.language.work_name + "Tester")(solution).execute() tester.execute()
except Exception as e: except Exception as e:
print(e) print(e)
solution.result = "TE" solution.result = "TE"