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

Ключ
Эти линии были удалены. Это слово было удалено.
Эти линии были добавлены. Это слово было добавлено.

Просмотр истории страницы


Есть 0 изменений. Просмотреть первое изменение .

 h1. Система тестирования задач IOI
  
  
 h2. _Version 0.6_
  
  
 h2. {anchor:содержание}Содержание
  
 [Введение|#введение]
 [Как это работает|#howitworks]
 [Библиотека TesterLib|#testerlib]
 [Библиотека CheckLib|#checklib]
 [Шаблон программы Grader|#grader]
  
 h2. {anchor:введение}Введение
  
 Задачи IOI (во всяком случае, начиная с 2010) по процессу тестирования несколько отличаются от обычно создаваемых на DL (что автоматически означает необходимость выполнять весь процесс от начала до конца самостоятельно).
  
 Собственно, процесс выполняется следующим образом: участник присылает _библиотеку_ с решением (или архив с несколькими), которая подключается при компиляции исходника вспомогательной программы. После чего система подсовывает ей по очереди тесты из одной группы и читает результат. После обработки всех тестов группы считается конечный балл (который, само собой, может быть меньше, чем количество тестов в группе).
  
 Чтобы устанавливать эти задачи на DL, я разработал более-менее работоспособную замену системы тестирования. Здесь описан процесс установки задачи с её использованием.
  
 Библиотеки TesterLib, CheckerLib и код задачи Parrots (IOI 2011 d2 t3) можно скачать [одним архивом|http://dl.gsu.by/images/agulenko/IOI_tester.rar].
  
 h2. {anchor:howitworks}Как это работает
  
 Итак, что происходит при запуске тестирования?
 * Delta открывает файл {{[task.cfg|http://dl.gsu.by/doc/use/taskcfg.htm]}} и видит там текст следующего содержания:
 {code:title=task.cfg}
 TYPE = USERS
 CHECKSUBJECT = FILE
 CHECKFILES = {*.*}
 CHECKER = 'tester.exe'
 {code}
  
 * В соответствии с текстом, Delta копирует все файлы в корне задачи в корень папки тестирования и запускает {{tester.exe}}, ожидая, что тот закончит работу не позднее чем через минуту
 * Тестер производит тестирование
 * Delta читает файл результата и выводит результат как за один тест.
  
 Тестер выполняет задачи, которые по-хорошему должна была выполнять сама Delta:
 * Первым делом он читает свой файл конфигурации
 * Далее он запускает скрипт подготовки, выполняющий компиляцию решения и т.п.
 * Переименовывая по очереди входные файлы тестов в нужное название, тестер запускает программу {{checker.exe}}, которая отвечает за тестирование одного теста и выдачу результата этого тестирования.
 * Собрав данные по всем тестам группы, тестер вычисляет балл за группу тестов (если какой-то тест в группе был не пройден, последующие уже не проверяются)
 * В конце тестер выводит информацию по тестам в файл результата
  
 Скрипт подготовки обычно называется {{[prepare.bat|http://dl.gsu.by/images/agulenko/IOI11_Parrots/prepare.bat]}} и выполняет следующие действия:
 * Даёт файлам исходников правильные названия (они переименованы в малополезной попытке получения хоть какой-то защиты от обмана)
 * Запускает один из скриптов компиляции Дельты
  
 Чекер выполняет следующие действия:
 * Запускает скрипт {{[limiter.bat|http://dl.gsu.by/images/agulenko/IOI_common/limiter.bat]}} (передающий работу лимитеру Дельты) с полученными ограничениями по времени/памяти для программы-оценщика ({{grader.exe}})
 * Если программа завершилась некорректно или была остановлена лимитером (или ничего не вывела), чекер замещает её файл вывода своим (где "всё плохо")
  
 Оценщик компилируется в начале тестирования (решение - одна из его библиотек). Он:
 * Считывает входной файл и готовит переменные окружения
 * Запускает функцию (или функции) решения, как описано в условии
 * Контролирует результаты выполнения функции, проверяет результаты её выполнения (насколько это позволяют данные входного файла)
  
 h2. {anchor:testerlib}Библиотека TesterLib (Free Pascal)
  
 Для написания тестера достаточно написать несколько специфических (для задачи) функций/процедур (если они нужны), всё остальное реализовано в библиотеке {{[TesterLib|http://dl.gsu.by/images/agulenko/IOI_common/TesterLib.pp]}}.
  
 h3. {anchor:testerlib_cfg}Конфигурация
  
 Тестер при запуске считывает файл {{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")
  
 h3. {anchor:testerlib_unit}Библиотека
  
 Для использования тестера нужно подключить библиотеку в начале файла:
 {code:title=tester.pas}
 Uses
  TesterLib;
 // ...
 {code}
 В теле тестера нужно вызвать две процедуры:
 {code:title=tester.pas}
 // ...
 BEGIN
  Body();
  POut();
 END.
 {code}
 Эти процедуры имеют несколько параметров-указателей со значениями по умолчанию (все {{NIL}}). Для передачи указателя нужно указать имя переменной (или процедуры/функции) с собакой перед ним: {{@Data}}.
 [Пример тестера.|http://dl.gsu.by/images/agulenko/IOI11_Parrots/tester.pas]
  
 h3. {anchor:testerlib_body}Body()
  
 {code:title=tester.pas}
 Body (@Data, @Check, @Rate, @Reset, @Print);
 {code}
 * {{[Data|#testerlib_data] = Pointer}}
 Структура с данными (любая) для хранения данных по группе тестов
 * {{[Check|#testerlib_check] = Function (Data : Pointer; Chk : PText): Boolean;}}
 Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой {{Approval}}.
 * {{[Rate|#testerlib_rate] = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord;}}
 Функция оценки группы тестов; по умолчанию ставит Max за пройденную группу и 0 за непройденную.
 * {{[Reset|#testerlib_reset] = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);}}
 Процедура инициализации {{Data}} перед новой группой тестов; по умолчанию заполняет нулями по размеру (*НЕ* использовать с полями {{AnsiString}}\!).
 * {{[Print|#testerlib_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\]}} прибавляется к суммарному баллу.
  
 h3. {anchor:testerlib_pout}POut()
  
 {code:title=tester.pas}
 Procedure POut (@IsPartial);
 {code}
 * {{[IsPartial|#testerlib_ispartial] = Function (Remark : AnsiString): Boolean;}}
 Проверяет тест на наличие частичного балла (для вывода в виде Ball/Max); по умолчанию возвращает {{False}}.
  
 В {{POut()}} происходит следующее:
 * Открывается {{ResFile}}, в первую строку выводится суммарный балл
 * Во вторую выводится информация по группам через запятую:
 * {{"-"}} для проваленных ({{Failed\[i\]}})
 * {{"+"}} для полностью пройденных тестов
 * {{"*"}} для частично пройденных тестов
 * Также, для пройденных тестов без частичной разбалловки в скобках выводится балл; для непроваленных тестов с частичным баллом - "балл/максимум". Считается ли тест частичным, определяет функция {{IsPartial(Remark\[i\])}} (вариант по умолчанию всегда возвращает {{False}}).
  
 h3. {anchor:testerlib_data}Data
  
 {code:title=tester.pas}
 Data : Pointer;
 {code}
 {{Data}} -- указатель на структуру данных, сохраняющих данные по тестам (с помощью которых производится вычисление частичных баллов). Он может ссылаться на переменную любого типа. Удобнее всего делать его записью ({{Record}} -- группа переменных):
 {code:title=tester.pas}
 TYPE
  TData = Record
  Sum, R : LongWord;
  Q : Extended;
  End;
  
 VAR
  Data : TData;
 {code}
 При работе с полями параметра Data удобно работать в блоке {{with}}:
 {code:title=tester.pas}
 Procedure ...
  Begin
  With (TData(Data^)) Do Begin
  Inc(R);
  Q:=Sum / R;
  End;
  End;
 {code}
 Если вычислять частичные баллы не требуется, обычно можно обойтись без сохранения промежуточных данных (исключение -- если группа тестов имеет дополнительное ограничение, которое не определяется grader'ом). В этом случае можно вызвать {{Body()}} с параметрами по умолчанию, или передать первым параметром {{NIL}} (если нужно передать ссылку на функцию/процедуру).
  
 h3. {anchor:testerlib_check}Check
  
 {code:title=tester.pas}
 Function Check (Data : Pointer; Chk : PText): Boolean;
 {code}
 Функция {{Check}} получает в качестве параметров два указателя: {{Data}} (переданный первым параметром в {{Body}}) и {{Chk}} (указатель на открытый для чтения файл проверочных данных теста -- \{номер_теста\}.out; если этого файла нет, указатель содержит {{NIL}}); в {{Input}} открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в {{Data\^}}, функция возвращает {{True}} (если тест был пройден) или {{False}} (в противном случае).
  
 Первая строка выходного файла пройденного теста содержит строку, равную константе {{Approval}}. Последующие строки содержат данные, заносимые в {{Data\^}} (например, количество запросов -- в {{Data\^}} сохраняется максимум как худшее значение).
 Также в этой функции выполняются проверки, для которых недостаточно знать входные данные (эти проверки выполняет grader). Нужные данные получаются из файла {{Chk\^}}.
  
 h3. {anchor:testerlib_rate}Rate
  
 {code:title=tester.pas}
 Function Rate (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord;
 {code}
 Функция {{Rate}} получает в качестве параметров {{Max}} (максимальный балл за группу тестов), {{Failed}} (содержит {{True}} если все тесты в группе были пройдены), {{Remark}} (пометка -- третья часть описания группы тестов) и указатель {{Data}}. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение {{Failed}} устанавливается в {{False}}.
  
 Обычно функция возвращает {{Max}} (если {{Failed = True}}) или {{0}} (в противном случае). Необходимость другого поведения (дополнительная проверка или частичный балл) определяется пометкой, а для его обработки используется {{Data\^}}. Если группа имеет одно или несколько дополнительных ограничений, их можно описать в пометке как числа (через пробел), а для прочтения использовать процедуру {{ReadStr}} (она работает так же, как и {{Read}}, но первым параметром принимает строку, из которой читаются данные); в простых случаях достаточно помещать в пометку один символ.
  
 h3. {anchor:testerlib_reset}Reset
  
 {code:title=tester.pas}
 Procedure Reset (Tests : LongWord; Remark : AnsiString; Data : Pointer);
 {code}
 Процедура {{Reset}} получает в качестве параметров {{Tests}} (количество тестов в группе), {{Remark}} (пометка) и указатель {{Data}}. Она инициализирует структуру {{Date\^}} для работы с новой группой тестов.
  
 Обычно процедура заполняет {{Data\^}} нулевым байтом (если {{Data}} -- не {{NIL}}). Однако это не всегда хорошая идея (скажем, если {{Data\^}} содержит поля типа {{AnsiString}} или другие указатели). Также, если для работы с группой требуется заранее иметь информацию о ней, можно использовать {{Tests}} и {{Remark}}.
  
 h3. {anchor:testerlib_print}Print
  
 {code:title=tester.pas}
 Procedure Print (Data : Pointer);
 {code}
 Процедура {{Print}} получает в качестве параметра указатель {{Data}}. Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает {{Data}}.
  
 Обычно {{Print}} ничего не делает.
  
 h3. {anchor:testerlib_ispartial}IsPartial
  
 {code:title=tester.pas}
 Function IsPartial (Remark : AnsiString): Boolean;
 {code}
 Функция {{IsPartial}} получает в качестве параметра {{Remark}} (пометку). Она определяет, может ли группа получить частичный балл (чтобы полностью пройденные группы тестов с частичными баллами отображались в формате Ball/Max).
  
 Обычно {{IsPartial}} возвращает {{False}}.
  
 h2. {anchor:checklib}Библиотека CheckLib
  
 Для написания чекера достаточно использовать библиотеку {{[CheckLib|http://dl.gsu.by/images/agulenko/IOI_common/CheckLib.pp]}} и написать несколько строк кода.
  
 Чекер получает 3 параметра: название скрипта-лимитера и его параметры-ограничения (по памяти и по времени). Обработка этих параметров (как большая часть остального функционала) выполняется библиотекой. Всё, что нужно сделать в теле -- вызвать функцию запуска для каждого grader'а и переместить промежуточные файлы.
  
 Если grader называется {{gradera}}, он будет читать данные из файла {{gradera.in}} и выводить данные в {{gradera.out}}. Соответственно, если после этого запускается grader, который называется {{graderb}}, нужно переместить {{gradera.out}} в {{graderb.in}}. В файле {{tester.cfg}} входной файл будет называться {{gradera.in}}, а выходной -- {{graderb.out}}. Для удобства, используется константа {{Grader}}.
  
 Таким образом, тело этого чекера будет выглядеть следующим образом:
 {code:title=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.
 {code}
 Процедура {{Run}} получает в качестве параметров две строки: название grader'а и название (последнего) выходного файла. Она запускает grader с помощью лимитера, проверяет вывод и, если grader не создал выходной файл (или если лимитер прервал его работу), создаёт выходной файл с соответствующей пометкой в первой строке и прерывает работу чекера.
  
 h2. {anchor:grader}Шаблон программы Grader
  
 Как было упомянуто выше, grader с названием {{gradera}} читает данные из файла {{gradera.in}} и выводит результат в {{gradera.out}}. Первая строка вывода содержит {{Approval}} ('OK') для пройденного теста (или любую другую строку для непройденного), последующие -- информацию для вычисления частичного балла (или пост-проверки).
  
 Обычно чтение выносится в процедуру {{ReadAll}}, вывод -- в процедуру {{WriteAll}}. В теле программы между их вызовами выполняется инициализация вспомогательной библиотеки (если таковая имеется), вызовы функций/процедур решения и проверка их работы.
  
 Таким образом, тело программы может выглядеть приблизительно так:
 {code:title=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.
 {code}
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: http://www.atlassian.com/software/confluence Build:#2.6.1 916) - Ошибка/новая особенность - Свяжитесь с Администраторами