Skip to content

cutefluffyfox/zhecker

Repository files navigation

Тестирующая система

Данный проект представляет из себя систему для автоматического тестирования задач, написанных на языке Python.

Введение

Что такое тестирующая система?

Тетирующая система - платформа для проведения контестов по программированию. Есть задачи, есть турниры, есть тесты и есть код, это всё надо связать и по итогам выстраивать рейтинг решений.

Цели проекта

  • сделать минимальный функционал: связать контесты, задачи, пользователей
  • сделать автоматическое тестирование задач (как минимум на Python)
  • добавить в регистрацию подтверждение почты
  • дать возможность пользователям создавать свои задачи/турниры
  • защитить сервер от 'атак' в коде (как выполнение 'DROP TABLE' или 'os.system("shutdown")')
  • создать api для работы с тестирующей системой
  • сделать тестирование асинхронное, чтобы не подвисал сайт

Результат работы над проектом

Структура базы данной

У нас есть несколько основных пунктов: пользователи, контесты, задачи, тесты для задач, попытки пользователей. Контесты к тому же связаны с задачами, задачи с тестами, попытки с задачами и т.д. Схема того, как в приложении выглядит бд:

  • database.db
    • attempts (основная информация о попытках по задачам)
      • id
      • contest_id
      • task_id
      • user_id
      • solution (исходный код)
      • time (время отправки)
      • score (результат попытки)
      • status (статус попытки)
    • contests (информация о контестах)
      • id
      • creators (строчка с id пользователей, которые имеют доступ редактировать контест)
      • title (название контеста)
      • description (описание контеста)
      • tasks (строчка с id задач, которые присутствуют в контесте, порядок имеет значение)
      • start_time (время начала контеста)
      • end_time (время конца контеста)
    • tasks (информация о задачах)
      • id
      • creator (id создателя, создатель всегда один)
      • time_limit (ограничение по времени)
      • title (название задачи)
      • description (описание задачи)
      • reference (код, который проходит все тесты)
      • attempts (колличество попыток по задаче)
      • successful (колличесто удачных попыток по задаче)
    • tests (информация о тестах для задач)
      • id
      • task_id
      • input (входные данные)
      • output (выходные данные, генерируются tasks.reference запущенным на input)
      • output (выходные данные)
      • points (сколько баллоы приносит данный тест)
    • trial (информация о каждом тесте, как код пользователя себя повёл)
      • attempt_id
      • test_id
      • status (статус как код себя повёл на тесте)
      • output (информация что вывел код, заполняется когда status=WA)
    • users (информация о пользователях)
      • id
      • username (ник пользователя, уникальное значение)
      • email (почта, уникальное значение)
      • password (хэшированный с солью пароль)
      • name (имя)
      • surname (фамилия)
      • register_date (дата регистрации)
      • city (город)
      • creator (булевое поле, 0 - не имеет доступ создавать задачи, 1 - имеет доступ)
      • verification (поле для разных ключей: подтверждение почты/получени ключа api)
      • registered (булевое поле, 0 - не зарегестрирован, 1 - зарегестрирован)
      • api_key (ключ для работы с api)

Итоговый функционал

Работа была разбита на 5 блоков:

  • написание дизайна и связка с бд
  • написание функционала ресурсов в бд/структурирование бд
  • написание почты
  • написание самой тестирующей системы и выставления вердиктов
  • написание api

Функционал тестирующей системы таков:

  • авторизация с подтверждением почты
  • просмотр всех задач
  • просмотр всех контестов
  • создание/редактирование/удаление контестов
  • создание/редактирование/удаление задач
  • редактирование/удаление пользователя
  • запрос на получение прав создания задач
  • автоматическое тестирование задач
  • просмотр контестов/задач/пользователя через api

Приступаем к тестированию

Перед началом работы

  1. pip install -r requirements.txt
  2. Открываем config.json и заполняем там поля:
    • SALT - соль для хэша, любая случайная длинная строка
    • APP_KEY - ключ приложения, также любая случайная длинная строка
    • PYTHON_INTERPRETER - краткое название интерпретатора питона (python/python3/python3.7)
    • MAIL_PASSWORD_GOOGLE - пароль от гугл аккаунта, с которого будут отправлены письма
    • MAIL_LOGIN_GOOGLE - логин от гугл аккаунта, с которого будут отправлены письма
    • HOST - где будет запускаться ваш код
    • HOST_FULLNAME - полное имя сайта (с http) где находится сервер, туда будет перенаправлять почта
  3. python main.py

Важные примечания

Для загрузки тестов, вы должны прикладывать .json файл в следующем формате:

[
   {
       "input": "3\n5"
   },
   {
       "input": "5\n10"
   },
   {
       "input": "573\n923",
       "points": 3
   }
]

Интерпритатор питона обязательно должен быть версии 3.7+, так как версия subprocess на питонах ниже не имела нужный функционал

Если у вас MacOS, то вероятно вам прийдётся в PYTHON_INTERPRETER указать python3.7

Гугл почта обязательна, никакой Яндекс/mail не будут работать

Вы можете не задавать данные в config.json, вы можете создать переменные среды

В коде асинхронно запускается отправка GET запросов на себя же, это было сделано чтобы сайт не уходил в состояние idling при деплое на heroku

Страницы не работают на API т.к. у API уменьшенный функционал с целью безопасности

import create_environment - задаёт переменные среды, эту строчку не стирать/менять место

Интересные моменты в коде

Нулевой контест

Чтобы можно было решать все задачи, то создаётся специальный нулевой контест, куда добавляется каждая задача (models.py -> __ init __())

if Contest.get_contest(contest_id=0) is None:
    session.add(Contest(id=0, creators='', title='Tasks', description='Contest with all tasks', start_time=0, end_time=pow(2, 34), tasks=''))
    session.commit()

Шаблон для почты

В переменной email_types узакывается 'имя шаблона', 'имя шаблона'_plain, 'имя шаблона'_subject, что позваляет отправять шаблоны сообщений легко и просто (email_sender.py)

def send_email(receiver: str, content_type: str, **kwargs) -> bool:

Запрос на доступ 'создателя'

Процесс запроса максимально упрощён до уровня 'напишите почему'. Письма с запросами отправляются на адрес администратора MAIL_LOGIN_GOOGLE (models.py -> User -> send_creator_email())

send_email(environ['MAIL_LOGIN_GOOGLE'], content_type='get_creator', message=message, user_id=self.id, confirmation_key=self.verification)

Задача решаема

При создании задачи код, вы не указываете в тестах output с целью того, чтобы в базу дыннх верно записались выходные данные. И это позваляет также убрать все тесты, где код эталон не проходит по времени (models.py -> Task -> add_task())

for test in (tests if tests is not None else []):
    tests_statuses.append(Test.add_test(task_id=task_id, inp=test.get('input'), points=test.get('points')))