|  | 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} |