Рабочий стол > DL Руководство пользователя > ... > Установка задач IOI > Установка задач IOI (v0.6) > Просмотр
Установка задач IOI (v0.6) Войти | Зарегистрироваться   Просмотр версии для печати текущей страницы.

Добавлено Гуленко Алексей, последний раз изменено Гуленко Алексей Mar 02, 2014
Метки: 
(нет)

Система тестирования задач IOI

Version 0.6

Содержание

Введение
Как это работает
Библиотека TesterLib
Библиотека CheckLib
Шаблон программы Grader

Введение

Задачи IOI (во всяком случае, начиная с 2010) по процессу тестирования несколько отличаются от обычно создаваемых на DL (что автоматически означает необходимость выполнять весь процесс от начала до конца самостоятельно).

Собственно, процесс выполняется следующим образом: участник присылает библиотеку с решением (или архив с несколькими), которая подключается при компиляции исходника вспомогательной программы. После чего система подсовывает ей по очереди тесты из одной группы и читает результат. После обработки всех тестов группы считается конечный балл (который, само собой, может быть меньше, чем количество тестов в группе).

Чтобы устанавливать эти задачи на DL, я разработал более-менее работоспособную замену системы тестирования. Здесь описан процесс установки задачи с её использованием.

Библиотеки TesterLib, CheckerLib и код задачи Parrots (IOI 2011 d2 t3) можно скачать одним архивом.

Как это работает

Итак, что происходит при запуске тестирования?

  • Delta открывает файл task.cfg и видит там текст следующего содержания:
    task.cfg
    TYPE = USERS
    CHECKSUBJECT = FILE
    CHECKFILES = {*.*}
    CHECKER = 'tester.exe'
  • В соответствии с текстом, Delta копирует все файлы в корне задачи в корень папки тестирования и запускает tester.exe, ожидая, что тот закончит работу не позднее чем через минуту
  • Тестер производит тестирование
  • Delta читает файл результата и выводит результат как за один тест.

Тестер выполняет задачи, которые по-хорошему должна была выполнять сама Delta:

  • Первым делом он читает свой файл конфигурации
  • Далее он запускает скрипт подготовки, выполняющий компиляцию решения и т.п.
  • Переименовывая по очереди входные файлы тестов в нужное название, тестер запускает программу checker.exe, которая отвечает за тестирование одного теста и выдачу результата этого тестирования.
  • Собрав данные по всем тестам группы, тестер вычисляет балл за группу тестов (если какой-то тест в группе был не пройден, последующие уже не проверяются)
  • В конце тестер выводит информацию по тестам в файл результата

Скрипт подготовки обычно называется prepare.bat и выполняет следующие действия:

  • Даёт файлам исходников правильные названия (они переименованы в малополезной попытке получения хоть какой-то защиты от обмана)
  • Запускает один из скриптов компиляции Дельты

Чекер выполняет следующие действия:

  • Запускает скрипт limiter.bat (передающий работу лимитеру Дельты) с полученными ограничениями по времени/памяти для программы-оценщика (grader.exe)
  • Если программа завершилась некорректно или была остановлена лимитером (или ничего не вывела), чекер замещает её файл вывода своим (где "всё плохо")

Оценщик компилируется в начале тестирования (решение - одна из его библиотек). Он:

  • Считывает входной файл и готовит переменные окружения
  • Запускает функцию (или функции) решения, как описано в условии
  • Контролирует результаты выполнения функции, проверяет результаты её выполнения (насколько это позволяют данные входного файла)

Библиотека TesterLib (Free Pascal)

Для написания тестера достаточно написать несколько специфических (для задачи) функций/процедур (если они нужны), всё остальное реализовано в библиотеке TesterLib.

Конфигурация

Тестер при запуске считывает файл tester.cfg, который обрабатывается следующим образом:

  • Строка начиная с первого символа "#" и до конца игнорируется (комментарии)
  • Пустые строки (содержащие только пробелы и/или комментарий) игнорируются
  • Если строка начинается с символа "<", начинается режим чтения групп тестов (после символа может быть указано их количество)
  • В противном случае строка обрабатывается как опция (в формате "название=значение")

В режиме чтения групп тестов (игнорирование по тем же правилам):

  • Если строка начинается с символа ">", режим чтения групп заканчивается.
  • Строка разбивается на части, разделяемые запятой (",")
  • Первая часть - число баллов за тест
  • Вторая (если есть и не пуста) - число тестов в группе (иначе 0)
  • Третья - дополнительная пометка (для вычисления количества баллов; тж. см. FRate в подразделе "Библиотека")

Поддерживаются следующие опции:

  • InFile - входной файл (по умолчанию "grader.in")
  • OutFile - выходной файл (по умолчанию "grader.out")
  • ResFile - файл результата (по умолчанию "$result$.txt")
  • PreRun - скрипт подготовки (указывать всегда, т.к. по умолчанию не выполняется)
  • MemLimit - ограничение по памяти (можно указывать в байтах или добавлять "B", "KB", "MB", "GB"; по умолчанию "256MB")
  • TimeLimit - ограничение по времени (в секундах; по умолчанию "2")
  • Limiter - скрипт вызова лимитера (по умолчанию "limiter.bat")

Библиотека

Для использования тестера нужно подключить библиотеку в начале файла:

tester.pas
Uses
  TesterLib;
// ...

В теле тестера нужно вызвать две процедуры:

tester.pas
// ...
BEGIN
  Body();
  POut();
END.

Эти процедуры имеют несколько параметров-указателей со значениями по умолчанию (все NIL). Для передачи указателя нужно указать имя переменной (или процедуры/функции) с собакой перед ним: @Data.
Пример тестера.

Body()

tester.pas
Body (@Data, @Check, @Rate, @Reset, @Print);
  • Data = Pointer
    Структура с данными (любая) для хранения данных по группе тестов
  • Check = Function (Data : Pointer; Chk : PText): Boolean;
    Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой Approval.
  • Rate = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord;
    Функция оценки группы тестов; по умолчанию ставит Max за пройденную группу и 0 за непройденную.
  • Reset = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);
    Процедура инициализации Data перед новой группой тестов; по умолчанию заполняет нулями по размеру (НЕ использовать с полями AnsiString!).
  • Print = Procedure (Data : Pointer)
    Делает отладочный вывод; по умолчанию – ничего.

В Body() происходит следующее:

  • Перебираются тесты (именованные в формате DL, т.е. "${номер}.in" и "${номер}.out") и группы тестов (группа 1 содержит первые Tests[1] тестов, группа 2 содержит следующие Tests[2] тестов, и т.д.). Здесь и далее: i – номер группы, j – номер теста в группе, k – глобальный номер теста.
  • В начале обработки группы вызывается процедура Reset(Tests[i], Remark[i], Data), которая инициализирует Data^ для обработки новой группы (по умолчанию выполняется FillChar(Data^, SizeOf(Data^), $00)).
  • Failed[i] (пометка о проваленном тесте) устанавливается в False.
  • Отладочный вывод: в stdout выводится (i + "[" + Tests[i] + "]")
  • В начале обработки теста выводится "+"
  • Если установлен Failed[i], тест пропускается
  • Файл k + ".in" переименовывается в InFile
  • Вызывается чекер с параметрами Limiter, MemLimit и TimeLimit
  • InFile возвращается на место
  • Если файл k + ".out" существует, он открывается для чтения в Chk и адрес сохраняется в PChk, иначе в PChk сохраняется NIL
  • Открывается OutFile и вызывается Check(Data, PChk), которая читает из Input выходной файл (а из PChk^ проверочный, если там не NIL), обновляет Data^ и возвращает новое значение Failed[i].
  • Файлы закрываются
  • После обработки всех тестов в группе балл за группу (Res[i]) вычисляется с помощью Rate(Max[i], Failed[i], Remark[i], Data); также она может обновить Failed[i] на случай, если группа имеет специфическую проверку, не реализованную в Check() (по умолчанию Rate() просто проверяет текущее значение Failed[i]).
  • Отладочный вывод: выполняется Print(Data), после чего выводится (" " + Res[i]).
  • Res[i] прибавляется к суммарному баллу.

POut()

tester.pas
Procedure POut (@IsPartial);
  • IsPartial = Function (Remark : AnsiString): Boolean;
    Проверяет тест на наличие частичного балла (для вывода в виде Ball/Max); по умолчанию возвращает False.

В POut() происходит следующее:

  • Открывается ResFile, в первую строку выводится суммарный балл
  • Во вторую выводится информация по группам через запятую:
  • "-" для проваленных (Failed[i])
  • "+" для полностью пройденных тестов
  • "*" для частично пройденных тестов
  • Также, для пройденных тестов без частичной разбалловки в скобках выводится балл; для непроваленных тестов с частичным баллом - "балл/максимум". Считается ли тест частичным, определяет функция IsPartial(Remark[i]) (вариант по умолчанию всегда возвращает False).

Data

tester.pas
Data  : Pointer;

Data – указатель на структуру данных, сохраняющих данные по тестам (с помощью которых производится вычисление частичных баллов). Он может ссылаться на переменную любого типа. Удобнее всего делать его записью (Record – группа переменных):

tester.pas
TYPE
  TData = Record
    Sum, R  : LongWord;
    Q       : Extended;
  End;

VAR
  Data  : TData;

При работе с полями параметра Data удобно работать в блоке with:

tester.pas
Procedure ...
  Begin
    With (TData(Data^)) Do Begin
      Inc(R);
      Q:=Sum / R;
    End;
  End;

Если вычислять частичные баллы не требуется, обычно можно обойтись без сохранения промежуточных данных (исключение – если группа тестов имеет дополнительное ограничение, которое не определяется grader'ом). В этом случае можно вызвать Body() с параметрами по умолчанию, или передать первым параметром NIL (если нужно передать ссылку на функцию/процедуру).

Check

tester.pas
Function Check (Data : Pointer;  Chk : PText): Boolean;

Функция Check получает в качестве параметров два указателя: Data (переданный первым параметром в Body) и Chk (указатель на открытый для чтения файл проверочных данных теста – {номер_теста}.out; если этого файла нет, указатель содержит NIL); в Input открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в Data^, функция возвращает True (если тест был пройден) или False (в противном случае).

Первая строка выходного файла пройденного теста содержит строку, равную константе Approval. Последующие строки содержат данные, заносимые в Data^ (например, количество запросов – в Data^ сохраняется максимум как худшее значение).
Также в этой функции выполняются проверки, для которых недостаточно знать входные данные (эти проверки выполняет grader). Нужные данные получаются из файла Chk^.

Rate

tester.pas
Function Rate (Max : LongWord;  Var Failed : Boolean;  Remark : AnsiString;  Data : Pointer): LongWord;

Функция Rate получает в качестве параметров Max (максимальный балл за группу тестов), Failed (содержит True если все тесты в группе были пройдены), Remark (пометка – третья часть описания группы тестов) и указатель Data. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение Failed устанавливается в False.

Обычно функция возвращает Max (если Failed = True) или 0 (в противном случае). Необходимость другого поведения (дополнительная проверка или частичный балл) определяется пометкой, а для его обработки используется Data^. Если группа имеет одно или несколько дополнительных ограничений, их можно описать в пометке как числа (через пробел), а для прочтения использовать процедуру ReadStr (она работает так же, как и Read, но первым параметром принимает строку, из которой читаются данные); в простых случаях достаточно помещать в пометку один символ.

Reset

tester.pas
Procedure Reset (Tests : LongWord;  Remark : AnsiString;  Data : Pointer);

Процедура Reset получает в качестве параметров Tests (количество тестов в группе), Remark (пометка) и указатель Data. Она инициализирует структуру Date^ для работы с новой группой тестов.

Обычно процедура заполняет Data^ нулевым байтом (если Data – не NIL). Однако это не всегда хорошая идея (скажем, если Data^ содержит поля типа AnsiString или другие указатели). Также, если для работы с группой требуется заранее иметь информацию о ней, можно использовать Tests и Remark.

Print

tester.pas
Procedure Print (Data : Pointer);

Процедура Print получает в качестве параметра указатель Data. Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает Data.

Обычно Print ничего не делает.

IsPartial

tester.pas
Function IsPartial (Remark : AnsiString): Boolean;

Функция IsPartial получает в качестве параметра Remark (пометку). Она определяет, может ли группа получить частичный балл (чтобы полностью пройденные группы тестов с частичными баллами отображались в формате Ball/Max).

Обычно IsPartial возвращает False.

Библиотека CheckLib

Для написания чекера достаточно использовать библиотеку CheckLib и написать несколько строк кода.

Чекер получает 3 параметра: название скрипта-лимитера и его параметры-ограничения (по памяти и по времени). Обработка этих параметров (как большая часть остального функционала) выполняется библиотекой. Всё, что нужно сделать в теле – вызвать функцию запуска для каждого grader'а и переместить промежуточные файлы.

Если grader называется gradera, он будет читать данные из файла gradera.in и выводить данные в gradera.out. Соответственно, если после этого запускается grader, который называется graderb, нужно переместить gradera.out в graderb.in. В файле tester.cfg входной файл будет называться gradera.in, а выходной – graderb.out. Для удобства, используется константа Grader.

Таким образом, тело этого чекера будет выглядеть следующим образом:

checker.pas
Uses
  CheckLib, SysUtils;

CONST
  Gradera = Grader+'a';
  Graderb = Grader+'b';

BEGIN
  Run(Gradera, Graderb+'.out');
  DeleteFile(Graderb+'.in');
  RenameFile(Gradera+'.out', Graderb+'.in');
  Run(Graderb, Graderb+'.out');
END.

Процедура Run получает в качестве параметров две строки: название grader'а и название (последнего) выходного файла. Она запускает grader с помощью лимитера, проверяет вывод и, если grader не создал выходной файл (или если лимитер прервал его работу), создаёт выходной файл с соответствующей пометкой в первой строке и прерывает работу чекера.

Шаблон программы Grader

Как было упомянуто выше, grader с названием gradera читает данные из файла gradera.in и выводит результат в gradera.out. Первая строка вывода содержит Approval ('OK') для пройденного теста (или любую другую строку для непройденного), последующие – информацию для вычисления частичного балла (или пост-проверки).

Обычно чтение выносится в процедуру ReadAll, вывод – в процедуру WriteAll. В теле программы между их вызовами выполняется инициализация вспомогательной библиотеки (если таковая имеется), вызовы функций/процедур решения и проверка их работы.

Таким образом, тело программы может выглядеть приблизительно так:

gradera.pas
Uses
  // Библиотека решения, остальные библиотеки

CONST
  InFile  = 'gradera.in';
  OutFile = 'gradera.out';

CONST
  Approval = 'OK';

VAR
  // Объявление глобальных переменных

  Procedure ReadAll();
  Var
    // Объявление локальных переменных
  Begin
    Assign(Input, InFile);
    Reset(Input);
    // Чтение входных данных
    Close(Input);
  End;

  Procedure WriteAll();
  Var
    // Объявление локальных переменных
  Begin
    Assign(Output, OutFile);
    Reset(Output);
    // Вывод результатов
    Close(Output);
  End;

  // Вспомогательные процедуры и функции

VAR
  // Объявление локальных для тела переменных
BEGIN
  ReadAll();
  // Вызов решения
  // Проверка результата
  WriteAll();
END.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: http://www.atlassian.com/software/confluence Build:#2.6.1 916) - Ошибка/новая особенность - Свяжитесь с Администраторами