---
metadata:
  - name: generator
    content: Diplodoc Platform v5.48.2
alternate:
  - https://admin.contest.yandex.ru/docs/en/examples/advanced-usage/default-tests-for-language.md
  - https://admin.contest.yandex.ru/docs/ru/examples/advanced-usage/default-tests-for-language.md
---
> **Documentation Index:** Fetch the complete configuration index at https://admin.contest.yandex.ru/docs/ru/llms.txt

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

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

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

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

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

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

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

    ```bash
    all: build
    
    build:
        /bin/sh ./build.sh
    run:
        /bin/sh ./run.sh
    ```
    
    В этом скрипте вы говорите системе: на этапе компиляции&nbsp;— **build**&nbsp;— выполни скрипт `./build.sh`, на этапе запуска пользовательского решения и запуска чекеров&nbsp;— **run**&nbsp;— выполни скрипт `./run.sh`.
    
    {% note alert %}
    
    Отступы перед функциями **build** и **run** и внутри них должны быть табами, а не пробелами. Иначе при запуске собранной задачи вы получите ошибку `CE Makefile:4: *** missing separator. Stop.`
    
    {% endnote %}
    
2. Создайте скрипт **build.sh** и разместите его в корне файлов задачи, как и **Makefile**:

    ```bash
    #!/bin/bash
    
    OUT=participantSolution.py
    TMP=tempNameForParticipantSolution.py
    
    cat $filename > $TMP || exit 1
    rm $filename
    cat $TMP > $OUT || exit 1
    ```
    
    Код посылки участника помещается в файл на сервере, где будет выполняться. Название файла с пользовательским кодом содержится в переменной `$filename`.
    
    В этом скрипте вы переносите контент пользовательского решения в файл с именем, которое точно будете знать. Так будет удобнее обращаться к файлу в следующих скриптах. Контент перемещается через TMP-файл: так файлы не пропадут, если участник отправит файл с тем же названием, что и значение в переменной **OUT**.
    
    {% note info %}
    
    Названия файлов могут отличаться. Вы можете указать любые удобные и понятные для работы.
    
    {% endnote %}
    
    В результате получится такая структура файлов:

    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step1-files.png)

3. На вкладке [Задачи](https://admin.contest.yandex.ru/problems/) в разделе **Дополнительные файлы и обработки** добавьте созданные файлы в пункт **Файлы для компиляции**.
    
    {% note warning %}
    
    Первым добавьте **Makefile**, затем **build.sh**.
    
    {% endnote %}
    
    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step1-filesSettings.png)
    

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

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

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

1. В корне файлов задачи создайте файл **run.sh**. Этот скрипт вызывается в **Makefile**:
    
    ```bash
    #!/bin/bash
    
    python3 run_tests.py
    ```
    
    В этом скрипте вы запускаете скрипт на Python, в котором прогоняются тесты.
    
2. В корне файлов задачи создайте файл **run_tests.py** со скриптом на языке Python, в котором перечисляются и запускаются тесты:
    
    ```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&nbsp;— это значение будет передано в чекер
    # Если достаточно факта прохождения тестов&nbsp;— можно вывести, например, OK/FAIL
    print(score_basic)
    
    ```
    
    - Метод `get_basic_score` позволяет обработать консольный вывод модуля `unittest`.
    - Класс `SampleTestSuite` содержит сами тесты в том формате, который требует модуль `unittest`.
    
    В результате получится такая структура файлов:

    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step2-files.png)

3. Откройте раздел [Задачи](https://admin.contest.yandex.ru/problems/). В разделе **Дополнительные файлы и обработки** добавьте созданные файлы в пункт **Файлы для времени запуска**.
    
    {% note alert %}
    
    Первым добавьте **run.sh**, затем **run_tests.py**.
    
    {% endnote %}
    
    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step2-filesSettings.png)

## 3. Настройте чекер {#checker}

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

В этом примере необходимо игнорировать то, что указано в файле тестов, и ориентироваться только на код, выводимый в **stdin** на этапе запуска решения. Поэтому нужно реализовать собственные чекеры. Подробнее о создании собственных чекеров см. в разделе [Настройки](https://admin.contest.yandex.ru/docs/ru/problem/main-problem.md). Рассмотрим только один чекер:

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

    ```bash
    #!/bin/sh
    
    /usr/bin/python3 checker.py $1 $2 $3
    ```
    
    В этом файле вы вызываете чекер на языке Python и передаете три аргумента. Это пути до файлов с **input** (файл теста), **output** (вывод пользовательского решения на этапе запуска) и **answer** (ожидаемый ответ файла теста).
    
2. В корне файлов задачи создайте файл **checker.py**:
    
    ```python
    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&nbsp;— это OK, 1&nbsp;— WA, 2&nbsp;— PE.
    
    Вердикт зависит не только от значения `exitcode`, но и от типа чекера, который выбирается в настройках задачи. Рассмотрим тип чекера **TESTLIB_EXITCODE_CHECKER**.
    
    В результате получится такая структура файлов:

    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step3-files.png)

3. На вкладке [Задачи](https://admin.contest.yandex.ru/problems/) в разделе **Настройки чекера** укажите:

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

    ![](../../_images/examples/advanced-usage-examples/defTestRuner-step3-filesSettings.png)

## 4. Создайте файл теста {#test}

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

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

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

## 5. Проверьте результат {#result}

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

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

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

![](../../_images/examples/advanced-usage-examples/subm_res.png)

<!-- source: ru/_includes/reusables/support-button.md -->
<a href="../../troubleshooting">
  <span class="button">Написать в службу поддержки</span>
</a>



<!-- endsource: ru/_includes/reusables/support-button.md -->

