Проверка задач с помощью своего скрипта
Если вам не подходит архитектура задач по программированию, в Контесте можно настроить поведение системы при получении решений от пользователей. Создайте задачу, в которой пользователь должен реализовать определенную функцию. Вы можете оценить задачу в определенное количество баллов. Чтобы проверить ответ, используйте встроенную в язык систему юнит-тестирования.
Чтобы задать поведение системы при получении посылки, можно использовать компилятор make. В этом случае поведение определяется конфигурационным файлом Makefile.
С помощью такого поведения можно запускать недокерные компиляторы, которые есть в системе. Покажем это на примере использования Python3-интерпретатора.
1. Настройте компиляцию
Настройки компиляции позволяют подготовить посылку к запуску. Этап компиляции в Контесте проводится для любого языка программирования, даже интерпретируемого, но в этом случае для большинства компиляторов ничего не выполняется.
Чтобы подготовить файл пользователя к запуску тестов:
-
В корне файлов задачи создайте Makefile:
all: build build: /bin/sh ./build.sh run: /bin/sh ./run.shВ этом скрипте вы говорите системе: на этапе компиляции — build — выполни скрипт
./build.sh, на этапе запуска пользовательского решения и запуска чекеров — run — выполни скрипт./run.sh.Внимание
Отступы перед функциями build и run и внутри них должны быть табами, а не пробелами. Иначе при запуске собранной задачи вы получите ошибку
CE Makefile:4: *** missing separator. Stop. -
Создайте скрипт build.sh и разместите его в корне файлов задачи, как и Makefile:
#!/bin/bash OUT=participantSolution.py TMP=tempNameForParticipantSolution.py cat $filename > $TMP || exit 1 rm $filename cat $TMP > $OUT || exit 1Код посылки участника помещается в файл на сервере, где будет выполняться. Название файла с пользовательским кодом содержится в переменной
$filename.В этом скрипте вы переносите контент пользовательского решения в файл с именем, которое точно будете знать. Так будет удобнее обращаться к файлу в следующих скриптах. Контент перемещается через TMP-файл: так файлы не пропадут, если участник отправит файл с тем же названием, что и значение в переменной OUT.
Примечание
Названия файлов могут отличаться. Вы можете указать любые удобные и понятные для работы.
В результате получится такая структура файлов:

-
На вкладке Задачи в разделе Дополнительные файлы и обработки добавьте созданные файлы в пункт Файлы для компиляции.
Важно
Первым добавьте Makefile, затем build.sh.

2. Настройте время запуска
Время запуска — это этап, на котором подготовленный код пользователя запускается по определенным правилам. На этом же этапе запускается чекер. Чекер и решение пользователя запускаются столько раз, сколько тестов есть в тестовых наборах.
Используйте этот этап для запуска стандартных тестов языка Python и оценки тестов пречекером.
-
В корне файлов задачи создайте файл run.sh. Этот скрипт вызывается в Makefile:
#!/bin/bash python3 run_tests.pyВ этом скрипте вы запускаете скрипт на Python, в котором прогоняются тесты.
-
В корне файлов задачи создайте файл run_tests.py со скриптом на языке Python, в котором перечисляются и запускаются тесты:
#!/usr/bin/env python # coding: utf-8 import unittest import io from participantSolution import participantSolution # имопрт метода ? def get_basic_score(log): score = 0.0 test_scores = { 'test_first': 0.5, 'test_second': 1.0 } for line in log.strip().split('\n'): line = line.strip().split() if not line: continue if line[-1] == 'ok': score += test_scores[line[0]] return score class SampleTestSuite(unittest.TestCase): def test_first(self): result = participantSolution(123, 321) self.assertEqual(result, 444) def test_second(self): result = participantSolution(1, 5) self.assertEqual(result, 6) # Создаем поток вывода, он понадобится для получения и обработки информации от тестраннера string_io = io.StringIO() # Формируем сьют, запускаем его. Выводим результат в поток вывода с прошлого шага suite = unittest.TestLoader().loadTestsFromTestCase(SampleTestSuite) runner = unittest.TextTestRunner(verbosity=2, stream=string_io).run(suite) # Запускаем функцию обработки стандартного потока вывода score_basic = get_basic_score(string_io.getvalue()) # Выводим в stdout полный лог посылки пользователя print (string_io.getvalue()) # Выводим набранные пользователем очки в stdout — это значение будет передано в чекер # Если достаточно факта прохождения тестов — можно вывести, например, OK/FAIL print(score_basic)- Метод
get_basic_scoreпозволяет обработать консольный вывод модуляunittest. - Класс
SampleTestSuiteсодержит сами тесты в том формате, который требует модульunittest.
В результате получится такая структура файлов:

- Метод
-
Откройте раздел Задачи. В разделе Дополнительные файлы и обработки добавьте созданные файлы в пункт Файлы для времени запуска.
Внимание
Первым добавьте run.sh, затем run_tests.py.

3. Настройте чекер
Чекеры запускаются при каждом выполнении файла пользовательского решения и анализируют вывод в stdin на этапе времени запуска. Стандартные чекеры работают с данными из файлов тестов и читают текст из файла ответа теста.
В этом примере необходимо игнорировать то, что указано в файле тестов, и ориентироваться только на код, выводимый в stdin на этапе запуска решения. Поэтому нужно реализовать собственные чекеры. Подробнее о создании собственных чекеров см. в разделе Настройки. Рассмотрим только один чекер:
-
В корне файлов задачи создайте файл check_py:
#!/bin/sh /usr/bin/python3 checker.py $1 $2 $3В этом файле вы вызываете чекер на языке Python и передаете три аргумента. Это пути до файлов с input (файл теста), output (вывод пользовательского решения на этапе запуска) и answer (ожидаемый ответ файла теста).
-
В корне файлов задачи создайте файл checker.py:
import sys import datetime if __name__ == '__main__': stdout_file = sys.argv[2] with open(stdout_file) as f: lines = f.readlines() last_line = lines[-1].strip().split() basic_score = float(last_line[0]) if basic_score == 0.0: print ('Wrong answer!') sys.exit(1) else: print (basic_score)Скрипт читает файл, который передан во втором аргументе, и находит его последнюю строку. В ней содержатся баллы, которые выставила программа с тестами. Если решение получило 0 баллов, скрипт выводит текст, что решение неверное, и завершает программу чекера с
exitcode 1.Значение
exitcodeопределяет, какой вердикт Контест выставит посылке пользователя. Например, 0 — это OK, 1 — WA, 2 — PE.Вердикт зависит не только от значения
exitcode, но и от типа чекера, который выбирается в настройках задачи. Рассмотрим тип чекера TESTLIB_EXITCODE_CHECKER.В результате получится такая структура файлов:

-
На вкладке Задачи в разделе Настройки чекера укажите:
- Тип чекера: TESTLIB_EXITCODE_CHECKER.
- Чекер выставляет баллы: ДА.
- Файлы чекера: добавьте сначала check_py, затем checker.py.
В результате получатся такие настройки чекера:

4. Создайте файл теста
Несмотря на то, что файлы тестов не используются при запуске, важно создать файл с тестом и проследить, чтобы он был добавлен в тестовый набор.
Файл теста и тестовый набор нужны системе, чтобы знать, сколько раз нужно запустить пользовательский код. Кроме того, тестовые наборы используются в настройках соревнования, чтобы понять, какие данные показать участнику на странице посылки.
Для нашего примера можно создать один пустой файл теста tests/01 и пустой файл ответа tests/01.a.
5. Проверьте результат
Создайте файл авторского решения с кодом:
def participantSolution(a, b):
return a + b
Если все сделано правильно, страница посылки с таким решением будет выглядеть так:
