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

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

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

Version 0.8

(История версий)

Содержание

Введение
Как это работает
Библиотека 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 и выполняет следующие действия:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Библиотека

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

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; Var Comment : AnsiString; Chk : PText): Boolean;
    Функция проверки теста; по умолчанию сравнивает первую строку со строковой константой Approval и сохраняет её значение как комментарий.
  • Rate = Function (Max : LongWord; Var Failed : Boolean; Remark : AnsiString; Var Comment : AnsiString; Data : Pointer): LongWord;
    Функция оценки группы тестов; по умолчанию ставит Max за пройденную группу и 0 за непройденную.
  • Reset = Procedure (Tests : LongWord; Remark : AnsiString; Data : Pointer);
    Процедура инициализации Data перед новой группой тестов; по умолчанию заполняет нулями по размеру (НЕ использовать с полями AnsiString!).
  • 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]) и (для соответствующих групп тестов, см. Конфигурация) комментарий.
  • 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;  Var Comment : AnsiString;  Chk : PText): Boolean;

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

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

Rate()

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

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

Обычно функция возвращает 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;  Remark : AnsiString);

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

From: IOI-announce [mailto:ioi-announce-bounces@lists.ioinformatics.org] On Behalf Of Hamid Zarrabi-Zadeh
Sent: Sunday, December 31, 2017 12:11 PM
To: IOI General Assembly; ioi-announce@lists.ioinformatics.org
Cc: IOI Technical Committee; IHTC; HTC 2017
Subject: [IOI-announce] IOI 2017 Technical Tools

Dear IOI Colleagues,

On behalf of the IOI 2017 host technical committee, I am very pleased to announce that the systems and tools developed by the IOI 2017 HTC are now available as open source on Github. The released set contains the following items:
• Task Preparation System (TPS): https://github.com/ioi-2017/tps A system for preparing contest tasks, including task statements, solutions, generators, validators, checkers, and graders.
• Translation System: https://github.com/ioi-2017/translation A user-friendly markdown editing environment for translating the IOI tasks, with parallel view, pdf generation, revision history, and notification system.
• Site Administration System: https://github.com/ioi-2017/site-admin A web interface for visualizing and monitoring the contest site, and executing commands remotely on the contestants and judging machines.
Printing System https://github.com/ioi-2017/print A system for handling all print requests during the IOI, including those from CMS, translation system, contestant machines, and custom mass prints.
• Automation Scripts: https://github.com/ioi-2017/ops A collection of Ansible scripts to automate prearranged technical operations, such as preparing raw system images and backing up contestant machines.
Hope they are useful to the IOI community and the next IOI hosts.

Best regards,
Hamid

From: IOI-announce [mailto:ioi-announce-bounces@lists.ioinformatics.org] On Behalf Of Ali Sharifi-Zarchi
Sent: Sunday, December 31, 2017 7:58 PM
To: IOI General Assembly; ioi-announce@lists.ioinformatics.org
Cc: IOI Scientific Committee; hsc2017@googlegroups.com
Subject: [IOI-announce] IOI 2017 Task Materials
 Dear IOI Colleagues,

It's my great pleasure to announce, on behalf of the IOI 2017 host scientific committee (HSC), that all IOI 2017 task materials are available on the IOI 2017 website http://ioi2017.org/contest/tasks/, as well as the official IOI website http://ioinformatics.org/locations/ioi17/contest/index.shtml;. These materials have been available on the IOI 2017 website since a few months ago, but editorials (short hints about the task solutions) are slightly updated and now everything is final.

The task materials include:

  • Task statements
  • Editorials
  • Test cases, test-case generators, and test-case validators (to check the test cases meet constraints of the task statement)
  • Correct, incorrect and partially correct solutions
  • Private and Public Graders (which are compiled with contestant programs)
  • Checkers (the programs which evaluate the contestants' outputs)
  • And the Task Preparation System (TPS) scripts https://github.com/ioi-2017/tps;, which are amazing for creating a new contest!You can find more details about the folder structure of the task materials here https://github.com/ioi-2017/tps/tree/master/docs;.

Have a shiny new year

Ali Sharifi-Zarchi
Chair of the HSC, IOI 2017, Tehran, Iran.

Powered by Atlassian Confluence, the Enterprise Wiki. (Version: http://www.atlassian.com/software/confluence Build:#2.6.1 916) - Ошибка/новая особенность - Свяжитесь с Администраторами