Проверка задач с помощью своего скрипта

Если вам не подходит архитектура задач по программированию, в Контесте можно настроить поведение системы при получении решений от пользователей. Создайте задачу, в которой пользователь должен реализовать определенную функцию. Вы можете оценить задачу в определенное количество баллов. Чтобы проверить ответ, используйте встроенную в язык систему юнит-тестирования.

Чтобы задать поведение системы при получении посылки, можно использовать компилятор make. В этом случае поведение определяется конфигурационным файлом Makefile.

С помощью такого поведения можно запускать недокерные компиляторы, которые есть в системе. Покажем это на примере использования Python3-интерпретатора.

1. Настройте компиляцию

Настройки компиляции позволяют подготовить посылку к запуску. Этап компиляции в Контесте проводится для любого языка программирования, даже интерпретируемого, но в этом случае для большинства компиляторов ничего не выполняется.

Чтобы подготовить файл пользователя к запуску тестов:

  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.

  2. Создайте скрипт 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.

    Примечание

    Названия файлов могут отличаться. Вы можете указать любые удобные и понятные для работы.

    В результате получится такая структура файлов:

  3. На вкладке Задачи в разделе Дополнительные файлы и обработки добавьте созданные файлы в пункт Файлы для компиляции.

    Важно

    Первым добавьте Makefile, затем build.sh.

2. Настройте время запуска

Время запуска — это этап, на котором подготовленный код пользователя запускается по определенным правилам. На этом же этапе запускается чекер. Чекер и решение пользователя запускаются столько раз, сколько тестов есть в тестовых наборах.

Используйте этот этап для запуска стандартных тестов языка Python и оценки тестов пречекером.

  1. В корне файлов задачи создайте файл run.sh. Этот скрипт вызывается в Makefile:

    #!/bin/bash
    
    python3 run_tests.py
    

    В этом скрипте вы запускаете скрипт на Python, в котором прогоняются тесты.

  2. В корне файлов задачи создайте файл 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.

    В результате получится такая структура файлов:

  3. Откройте раздел Задачи. В разделе Дополнительные файлы и обработки добавьте созданные файлы в пункт Файлы для времени запуска.

    Внимание

    Первым добавьте run.sh, затем run_tests.py.

3. Настройте чекер

Чекеры запускаются при каждом выполнении файла пользовательского решения и анализируют вывод в stdin на этапе времени запуска. Стандартные чекеры работают с данными из файлов тестов и читают текст из файла ответа теста.

В этом примере необходимо игнорировать то, что указано в файле тестов, и ориентироваться только на код, выводимый в stdin на этапе запуска решения. Поэтому нужно реализовать собственные чекеры. Подробнее о создании собственных чекеров см. в разделе Настройки. Рассмотрим только один чекер:

  1. В корне файлов задачи создайте файл check_py:

    #!/bin/sh
    
    /usr/bin/python3 checker.py $1 $2 $3
    

    В этом файле вы вызываете чекер на языке Python и передаете три аргумента. Это пути до файлов с input (файл теста), output (вывод пользовательского решения на этапе запуска) и answer (ожидаемый ответ файла теста).

  2. В корне файлов задачи создайте файл 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.

    В результате получится такая структура файлов:

  3. На вкладке Задачи в разделе Настройки чекера укажите:

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

    В результате получатся такие настройки чекера:

4. Создайте файл теста

Несмотря на то, что файлы тестов не используются при запуске, важно создать файл с тестом и проследить, чтобы он был добавлен в тестовый набор.

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

Для нашего примера можно создать один пустой файл теста tests/01 и пустой файл ответа tests/01.a.

5. Проверьте результат

Создайте файл авторского решения с кодом:

def participantSolution(a, b):
    return a + b

Если все сделано правильно, страница посылки с таким решением будет выглядеть так:

Написать в службу поддержки