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

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

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


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

 h1. Система тестирования задач IOI
  
  
 h2. _Version 0.8_
  
 ([История версий|Установка задач IOI - история версий])
  
 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]}} и выполняет следующие действия:
 * Даёт файлам исходников правильные названия (они переименованы в малополезной попытке получения хоть какой-то защиты от обмана)
 * Запускает один из скриптов компиляции Дельты
  * Запускает один из скриптов компиляции
 *- Для получения информации об ошибках компиляции необходимо проверять/возвращать ErrorLevel (см. скрипт в примере).
  
 Чекер выполняет следующие действия:
 * Запускает скрипт {{[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 в подразделе "Библиотека")
  * Третья - дополнительная пометка (для вычисления количества баллов; тж. см. [Rate()|#testerlib_rate] в параметрах [Body()|#testerlib_body])
  
 Поддерживаются следующие опции:
* InFile - входной файл (по умолчанию "grader.in")
 * OutFile - выходной файл (по умолчанию "grader.out")
 * ResFile - файл результата (по умолчанию "$result$.txt")
 * PreRun - скрипт подготовки (указывать всегда, т.к. по умолчанию не выполняется)
 * MemLimit - ограничение по памяти (можно указывать в байтах или добавлять "B", "KB", "MB", "GB"; по умолчанию "256MB")
 * TimeLimit - ограничение по времени (в секундах; по умолчанию "2")
 * Limiter - скрипт вызова лимитера (по умолчанию "limiter.bat")
  * InFile - входной файл (по умолчанию "{{grader.in}}")
 * OutFile - выходной файл (по умолчанию "{{grader.out}}")
 * ResFile - файл результата (по умолчанию "{{$result$.txt}}")
 * PreRun - скрипт подготовки (указывать всегда, т.к. по умолчанию не выполняется; если выполнится с ненулевым кодом завершения, это будет обработано как ошибка компиляции решения)
 * MemLimit - ограничение по памяти (можно указывать в байтах или добавлять "{{B}}", "{{KB}}", "{{MB}}", "{{GB}}"; по умолчанию "{{256MB}}")
 * TimeLimit - ограничение по времени (можно указывать в милисекундах или добавлять "{{MS}}", "{{S}}"; по умолчанию "{{2s}}")
 * Limiter - скрипт вызова лимитера (по умолчанию "{{limiter.bat}}")
 * Comment - выводимые комментарии ("{{+}}" для прошедших тестов, "{{\-}}" для непрошедших, "{{\*}}" для частично прошедших; по умолчанию "{{\-\*}}")
  
 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;}}
  * {{[Check|#testerlib_check] = Function (Data : Pointer; Var Comment : AnsiString; Chk : PText): Boolean;}}
 Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой {{Approval}} и сохраняет её значение как комментарий.
 * {{[Rate|#testerlib_rate] = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Var Comment : AnsiString; Data : Pointer): LongWord;}}
 Функция оценки группы тестов; по умолчанию ставит Max за пройденную группу и 0 за непройденную.
* {{[Reset|#testerlib_reset] = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);}}
  * {{[Reset|#testerlib_reset] = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);}}
 Процедура инициализации {{Data}} перед новой группой тестов; по умолчанию заполняет нулями по размеру (*НЕ* использовать с полями {{AnsiString}}\!).
* {{[Print|#testerlib_print] = Procedure (Data : Pointer)}}
  * {{[Print|#testerlib_print] = Procedure (Data : Pointer; Remark : AnsiString)}}
 Делает отладочный вывод; по умолчанию -- ничего.
  
 В {{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\])}}.
  * Отладочный вывод: выполняется {{Print(Data)}}, после чего выводится {{(" " + Res\[i\])}} и (для соответствующих групп тестов, см. [Конфигурация|#testerlib_cfg]) комментарий.
 * {{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}}).
* Для соответствующих групп тестов (см. [Конфигурация|#testerlib_cfg]) выводится комментарий.
  
 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
  h3. {anchor:testerlib_check}Check()
  
 {code:title=tester.pas}
Function Check (Data : Pointer; Chk : PText): Boolean;
  Function Check (Data : Pointer; Var Comment : AnsiString; Chk : PText): Boolean;
 {code}
Функция {{Check}} получает в качестве параметров два указателя: {{Data}} (переданный первым параметром в {{Body}}) и {{Chk}} (указатель на открытый для чтения файл проверочных данных теста -- \{номер_теста\}.out; если этого файла нет, указатель содержит {{NIL}}); в {{Input}} открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в {{Data\^}}, функция возвращает {{True}} (если тест был пройден) или {{False}} (в противном случае).
  Функция {{Check}} получает в качестве параметров два указателя: {{Data}} (переданный первым параметром в {{Body}}) и {{Chk}} (указатель на открытый для чтения файл проверочных данных теста -- \{номер_теста\}.out; если этого файла нет, указатель содержит {{NIL}}); в {{Input}} открыт выходной файл grader'а. После прочтения данных из этих файлов и сохранения нужной информации в {{Data\^}}, функция возвращает {{True}} (если тест был пройден) или {{False}} (в противном случае). Также она может модифицировать комментарий группы теста ({{Comment}}) на своё усмотрение.
  
 Первая строка выходного файла пройденного теста содержит строку, равную константе {{Approval}}. Последующие строки содержат данные, заносимые в {{Data\^}} (например, количество запросов -- в {{Data\^}} сохраняется максимум как худшее значение).
 Также в этой функции выполняются проверки, для которых недостаточно знать входные данные (эти проверки выполняет grader). Нужные данные получаются из файла {{Chk\^}}.
  
h3. {anchor:testerlib_rate}Rate
  h3. {anchor:testerlib_rate}Rate()
  
 {code:title=tester.pas}
Function Rate (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Data : Pointer): LongWord;
  Function Rate (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Var Comment : AnsiString; Data : Pointer): LongWord;
 {code}
Функция {{Rate}} получает в качестве параметров {{Max}} (максимальный балл за группу тестов), {{Failed}} (содержит {{True}} если все тесты в группе были пройдены), {{Remark}} (пометка -- третья часть описания группы тестов) и указатель {{Data}}. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение {{Failed}} устанавливается в {{False}}.
  Функция {{Rate}} получает в качестве параметров {{Max}} (максимальный балл за группу тестов), {{Failed}} (содержит {{True}} если все тесты в группе были пройдены), {{Remark}} (пометка -- третья часть описания группы тестов) и указатель {{Data}}. Она возвращает итоговый балл за группу; если группа по какой-то причине не пройдена, значение {{Failed}} устанавливается в {{False}}. Также можно изменить значение комментария ({{Comment}}).
  
 Обычно функция возвращает {{Max}} (если {{Failed = True}}) или {{0}} (в противном случае). Необходимость другого поведения (дополнительная проверка или частичный балл) определяется пометкой, а для его обработки используется {{Data\^}}. Если группа имеет одно или несколько дополнительных ограничений, их можно описать в пометке как числа (через пробел), а для прочтения использовать процедуру {{ReadStr}} (она работает так же, как и {{Read}}, но первым параметром принимает строку, из которой читаются данные); в простых случаях достаточно помещать в пометку один символ.
  
h3. {anchor:testerlib_reset}Reset
  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
  h3. {anchor:testerlib_print}Print()
  
 {code:title=tester.pas}
Procedure Print (Data : Pointer);
  Procedure Print (Data : Pointer; Remark : AnsiString);
 {code}
Процедура {{Print}} получает в качестве параметра указатель {{Data}}. Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает {{Data}}.
  Процедура {{Print}} получает в качестве параметра указатель {{Data}} и пометку группы тестов ({{Remark}}). Она делает отладочный вывод в консоль (эта информация может быть полезна при запуске тестера вручную). Вывод выполняется после вычисления баллов за группу. В качестве источника данных используется структура, на которую указывает {{Data}}.
  
 Обычно {{Print}} ничего не делает.
  
h3. {anchor:testerlib_ispartial}IsPartial
  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 не создал выходной файл (или если лимитер прервал его работу), создаёт выходной файл с соответствующей пометкой в первой строке и прерывает работу чекера.
  Процедура {{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) - Ошибка/новая особенность - Свяжитесь с Администраторами