% -*- mode: LaTeX; coding: koi8-r -*-
% $Id: rm_libchecker.tex,v 1.2 2004/04/29 04:12:20 cher Exp $
% Copyright (C) 2003,2004 Alexander Chernov <cher@unicorn.cmc.msu.ru>

\chapter{Разработка проверяющих программ}

В данной главе описывается интерфейс, которому должна
удовлетворять проверяющая программа, работающая в системе
\S{ejudge}. Далее описывается библиотека \V{libchecker} для
проверяющих программ на языке Си, которая призвана упростить
написание проверяющих программ.

\section{Проверяющие программы}

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

Проверяющая программа запускается только в тех случаях, когда
программа участника уложилась в отведённое ей время и
завершилась успешно, то есть с кодом возврата 0. В остальных
случаях диагностируется превышение времени выполнения или ошибка
периода выполнения соответственно, и проверяющая программа не
запускается.

\subsection{Параметры командной строки}

В зависимости от значения конфигурационных переменных
\hyperlink{F:serve.cfg,S:problem,T:use_corr}{\V{use\_corr}},
\hyperlink{F:serve.cfg,S:problem,T:use_info}{\V{use\_info}} и
\hyperlink{F:serve.cfg,S:problem,T:use_tgz}{\V{use\_tgz}}
(доступных как на глобальном уровне, так и на уровне описания
задачи) проверяющая программа может получать от 2 до 6
параметров командной строки.

Первый параметр (\V{argv[1]}) командной строки --- это всегда
путь к файлу с тестовыми данными. Несмотря на то, что каждый раз
файл с тестовыми данными копируется из каталога тестов в рабочий
каталог тестирования, проверяющей программе всегда передаётся
путь к файлу с тестовыми данными, находящемуся в каталоге
тестов, а не в рабочем каталоге тестирования, так как
тестируемая программа может случайно или намеренно испортить
свой входной файл.

Второй параметр (\V{argv[2]}) командной строки проверяющей
программы --- это всегда путь к файлу с результатом работы
тестируемой программы. Этот файл находится в рабочем каталоге
тестирования.

Все последующие параметры командной строки являются
необязательными. Их наличие или отсутствие зависят от значения
перечисленных выше конфигурационных переменных.

Если значение конфигурационной переменной
\hyperlink{F:serve.cfg,S:problem,T:use_corr}{\V{use\_corr}}
установлено в \I{true}, то следующим параметром командной строки
(\V{argv[3]}) передаётся путь к файлу с правильным ответом на
данный тест. Этот файл находится в каталоге правильных ответов.
На самом деле, этот файл не обязан содержать в точности
правильный ответ, но может содержать произвольную информацию,
которая поможет проверяющей программе проверить правильность
ответа тестируемой программы.

Если значение конфигурационной переменной
\hyperlink{F:serve.cfg,S:problem,T:use_info}{\V{use\_info}}
установлено в \I{true}, то следующим параметром командной строки
(\V{argv[3]} или \V{argv[4]} в зависимости от значения
переменной \V{use\_corr}) передаётся путь к файлу с
дополнительной информацией о тесте (см. раздел
\ref{sectionTestInf}).

Наконец, если значение конфигурационной переменной
\hyperlink{F:serve.cfg,S:problem,T:use_tgz}{\V{use\_tgz}}
установлено в \I{true}, проверяющей программе передаются ещё два
параметра командной строки: путь к незаархивированному исходному
содержимому рабочего каталога тестируемой программы и путь к
рабочему каталогу тестируемой программы. Проверяющая программа
может сравнивать первый каталог со вторым, чтобы определить, как
изменился рабочий каталог в результате работы программы.

\subsection{Результат проверки}

Проверяющая программа может выводить произвольную информацию на
стандартный поток вывода и стандартный поток ошибок. Вся
выведенная информация будет добавлена в протокол тестирования.

Проверяющая программа сообщает системе \S{ejudge} свой вердикт с
помощью кода завершения программы.

\begin{center}
\begin{tabular}{|p{2cm}|p{12cm}|}
\hline
\B{Код завершения} & \B{Вердикт} \\
\hline
0 & OK --- тестируемая программа на данном тесте дала правильный
    ответ.\\
4 & Presentation error --- результат, выведенный тестируемой
    программой, не может быть проверен на правильность,
    поскольку нарушен специфицированный в условии задачи формат
    вывода результата.\\
5 & Wrong answer --- тестируемая программа выработала
    неправильный ответ.\\
6 & Checker error --- внутренняя ошибка проверяющей программы.\\
\hline
\end{tabular}
\end{center}

Если проверяющая программа завершается аварийно из-за получения
фатального сигнала, или код её завершения не равен одному из
кодов, перечисленных выше, система \S{ejudge} считает, что
произошла внутренняя ошибка проверяющей программы. Такие посылки
отмечаются в базе данных посылок со статусом
``Manual check required''.

\section{Библиотека \V{libchecker}}

Библиотека предназначена для разработки проверяющих программ для
системы \S{ejudge}. Задача проверяющей программы --- по
результату работы тестируемой программы определить, правильный
ли ответ был получен тестируемой программой.

Библиотека представляет собой каркас (framework), в который
вставляются необходимые действия по проверке результата.
В библиотеке определяется функция \V{main}, то есть при старте
проверяющей программы управление получает модуль инициализации
библиотеки \V{libchecker}. После инициализации библиотеки
вызывается функция \V{checker\_main}, которая играет роль
функции \V{main} для проверяющей программы.

\subsection{Вид проверяющей программы}

Проверяющая программа с использованием библиотеки \V{libchecker}
выглядит следующим образом:

{\small
\begin{lstlisting}[numbers=left,print]
#define NEED_CORR 0
#define NEED_INFO 0
#define NEED_TGZ  0
#include "checker.h"

int checker_main(int argc, char **argv)
{
  // checker code
  checker_OK();
}
\end{lstlisting}
}

В строках 1, 2 и 3 устанавливается режим работы проверяющей
программы. Перед подключением заголовочного файла
\V{"checker.h"} обязательно должны быть определены три макроса
\V{NEED\_CORR}, \V{NEED\_INFO}, \V{NEED\_TGZ}.

Макрос \V{NEED\_CORR} необходимо установить в ненулевое
значение, если для проверки решения проверяющей программе
требуется файл с правильным ответом. Тогда файл с правильным
ответом будет открыт автоматически при старте проверяющей
программы, и он будет доступен через глобальные переменные
\V{f\_corr} и \V{f\_arr[2]}. Путь к файлу с правильным ответом
передаётся третьим параметром командной строки и может быть
использован и в функции \V{checker\_main}. Обратите внимание,
что чтобы программа \S{run} передавала проверяющей программе
путь к файлу с правильным ответом, в разделе описания задачи
должна быть установлена конфигурационная переменная
\hyperlink{F:serve.cfg,S:problem,T:use_corr}{\V{use\_corr}}.

Если макрос \V{NEED\_CORR} установлен в \V{0}, считается, что
файл с правильным ответом для проверяющей программы не нужен, и
он не открывается.

Макрос \V{NEED\_INFO} необходимо установить в ненулевое
значение, если для проверки решения проверяющей программе
необходим файл с дополнительной информацией о тесте
\hyperlink{F:test.inf}{\V{test.inf}}. Тогда файл с информацией о
тесте будет считан автоматически при старте проверяющей
программы, и информация из него будет доступна через глобальную
переменную \V{test\_info}. Путь к файлу информации о тесте
передаётся в параметрах командной строки и доступен в функции
\V{checker\_main}. Чтобы программа \S{run} передавала
проверяющей программе путь к файлу с правильным ответом, в
разделе описания задачи должна быть установлена конфигурационная
переменная \hyperlink{F:serve.cfg,S:problem,T:use_info}{\V{use\_info}}.

Если макрос \V{NEED\_INFO} установлен в \V{0}, считается, что
файл с информацией о тесте для проверяющей программы не нужен, и
он не открывается.

Макрос \V{NEED\_TGZ} необходимо установить в ненулевое значение,
если тестируемая программа запускается в специально создаваемом
каталоге, начальное содержимое которого копируется из каталога
тестов (см. описание конфигурационной переменной
\hyperlink{F:serve.cfg,S:problem,T:use_tgz}{\V{use\_tgz}}). В
таком случае при старте проверяющей программы будет открыт
рабочий каталог тестируемой программы и открыт эталонный каталог
из тестового каталога. Каталоги открываются с помощью
стандартной функции \V{opendir}. Дескрипторы открытых каталогов
помещаются в глобальные переменные \V{dir\_in} (эталонный
каталог), \V{dir\_out} (рабочий каталог после завершения
тестируемой программы). Дополнительно в глобальные переменные
\V{dir\_in\_path}, \V{dir\_out\_path} помещаются пути к этим
каталогам. Пути к этим каталогам передаются тестирующей
программе в аргументах командной строки, но только если
конфигурационная переменная \V{use\_tgz} для данной задачи
установлена в \I{true}.

При запуске проверяющей программы будет выполнена начальная
инициализация в соответствии с установками, заданными макросами
\V{USE\_CORR}, \V{USE\_INFO} и \V{USE\_TGZ}. Затем управление
будет передано на функцию \V{checker\_main}, которая должна
выполнить проверку и вернуть код возврата в соответствии с
соглашениями об интерфейсе проверяющих программ. Функции
\V{checker\_main} передаются все аргументы командной строки
проверяющей программы.

\subsection{Константы}

{\small
\begin{lstlisting}[print]
enum
{
  RUN_OK               = 0,
  RUN_PRESENTATION_ERR = 4,
  RUN_WRONG_ANSWER_ERR = 5,
  RUN_CHECK_FAILED     = 6
};
\end{lstlisting}
}

Данные константы перечисляют коды завершения, доступные для
проверяющей программы. Если проверяющая программа вернёт
какой-либо код возврата, кроме указанных выше, программа \S{run}
преобразует этот код возврата в код возврата
\V{RUN\_CHECK\_FAILED}.

\subsection{Глобальные переменные}

В данном разделе перечислены глобальные переменные, определяемые
в библиотеке \V{libchecker} и доступные для использования
проверяющими программами.

\subsubsection{Переменная \V{f\_in}}

{\small
\begin{lstlisting}[print]
extern FILE *f_in;
\end{lstlisting}
}

С помощью данного дескриптора потока можно работать с входным
файлом для тестируемой программы, то есть файлом, содержащим
тест. Даже если согласно условию задачи тестируемая программа
должна считывать данные со стандартного потока ввода, данный
дескриптор потока будет связан с файлом входных данных на
диске. Входной файл всегда открывается в каталоге тестов, а
не в рабочем каталоге программы. Если при открытии входного
файла при запуске проверяющей программы возникает ошибка, то
выполнение проверяющей программы немедленно завершается с кодом
возврата \V{RUN\_CHECK\_FAILED}. Функция \V{checker\_main} в
этом случае даже не получает управление.

\subsubsection{Переменная \V{f\_team}}

{\small
\begin{lstlisting}[print]
extern FILE *f_team;
\end{lstlisting}
}

Данная переменная содержит дескриптор потока, связанный с
выходным файлом тестируемой программы. Если согласно условию
задачи тестируемая программа должна выводить результат на
стандартный поток вывода, этот дескриптор потока связан с
временным файлом на диске, где находится вся выдача на
\V{stdout} (вывод в \V{stderr} перенаправляется в другой файл и
проверяющей программе не доступен). Если согласно условию задачи
результаты должны быть записаны в файл, то этот дескриптор
потока связан с этим файлом. Если при открытии выходного
файла при запуске проверяющей программы возникает ошибка, то
выполнение проверяющей программы немедленно завершается с кодом
возврата \V{RUN\_PRESENTATION\_ERR}. Функция \V{checker\_main} в
этом случае даже не получает управление.

\subsubsection{Переменная \V{f\_corr}}

{\small
\begin{lstlisting}[print]
extern FILE *f_corr;
\end{lstlisting}
}

Данная переменная содержит дескриптор потока, связанный с
эталонным файлом (файлом с правильным ответом). Этот файл
открывается, если макрос \V{USE\_CORR} установлен в ненулевое
значение. Если при этом в проверяющую программу передано
неверное количество параметров командной строки, или при
открытии файла возникла ошибка, выполнение проверяющей программы
немедленно завершается с кодом возврата \V{RUN\_CHECK\_FAILED}.
Функция \V{checker\_main} в этом случае даже не получает
управление.

\subsubsection{Переменная \V{f\_arr}}

{\small
\begin{lstlisting}[print]
extern FILE *f_arr[3];
\end{lstlisting}
}

Данный массив содержит те же самые дескрипторы потоков, что и
описанные выше глобальные переменные и введён для удобства
программиста. \V{f\_arr[0]} соответствует \V{f\_in},
\V{f\_arr[1]} соответствует \V{f\_team}, а \V{f\_arr[2]} ---
\V{f\_corr} (если последний открыт).

Некоторые функции требуют в качестве параметра, задающего
входной поток, целое число. Это целое число используется как
индекс в массиве \V{f\_arr}, то есть \V{0} --- входной файл,
\V{1} --- выходной файл тестируемой программы, \V{2} ---
эталонный файл.

\subsubsection{Переменная \V{f\_arr\_names}}

{\small
\begin{lstlisting}[print]
extern const unsigned char * const f_arr_names[3];
\end{lstlisting}
}

Этот массив содержит <<имена>> потоков для массива \V{f\_arr}. В
соответствующем Си-файле массив определяется следующим образом:

{\small
\begin{lstlisting}[print]
const unsigned char * const f_arr_names[3] =
{
  "input",
  "team output",
  "correct output"
};
\end{lstlisting}
}

\subsubsection{Переменная \V{dir\_in}}

{\small
\begin{lstlisting}[print]
extern DIR *dir_in;
\end{lstlisting}
}

Данная переменная содержит дескриптор открытого эталонного
рабочего каталога тестируемой программы. Переменная доступна,
только если макрос \V{USE\_TGZ} установлен в ненулевое значение.
Эталонный рабочий каталог находится в том же каталоге, где и
тесты. Эталонный рабочий каталог можно считать специальной
формой входных данных программы. Если в проверяющую программу
передано неверное количество параметров командной строки, или
при открытии каталога возникла ошибка, выполнение проверяющей
программы немедленно завершается с кодом возврата
\V{RUN\_CHECK\_FAILED}. Функция \V{checker\_main} в этом случае
даже не получает управление.

\subsubsection{Переменная \V{dir\_out}}

{\small
\begin{lstlisting}[print]
extern DIR *dir_out;
\end{lstlisting}
}

Данная переменная содержит дескриптор открытого рабочего
каталога тестируемой программы, оставшегося после её завершения.
Переменная доступна, только если макрос \V{USE\_TGZ} установлен
в ненулевое значение. Если в проверяющую программу передано
неверное количество параметров командной строки, или при
открытии каталога возникла ошибка, выполнение проверяющей
программы немедленно завершается с кодом возврата
\V{RUN\_CHECK\_FAILED}. Функция \V{checker\_main} в этом случае
даже не получает управление.

\subsubsection{Переменная \V{dir\_in\_path}}

{\small
\begin{lstlisting}[print]
extern unsigned char *dir_in_path;
\end{lstlisting}
}

Данная переменная содержит путь к эталонному рабочему каталогу
тестируемой программы. Переменная доступна, только если макрос
\V{USE\_TGZ} установлен в ненулевое значение.

\subsubsection{Переменная \V{dir\_out\_path}}

{\small
\begin{lstlisting}[print]
extern unsigned char *dir_out_path;
\end{lstlisting}
}

Данная переменная содержит путь к рабочему каталогу тестируемой
программы. Переменная доступна, только если макрос \V{USE\_TGZ}
установлен в ненулевое значение.

\subsubsection{Переменная \V{test\_info}}

{\small
\begin{lstlisting}[print]
struct testinfo_struct
{
  int cmd_argc;
  unsigned char **cmd_argv;
  unsigned char *comment;
  unsigned char *team_comment;
};
typedef struct testinfo_struct testinfo_t;

extern testinfo_t test_info;
\end{lstlisting}
}

Переменная \V{test\_info} содержит дополнительную информацию о
тесте, заданную в \V{.inf}-файле. Переменная \V{test\_info}
доступна, если макрос \V{NEED\_INFO} установлен в ненулевое
значение. Если в проверяющую программу передано неверное
количество параметров командной строки, или при открытии
файла с дополнительной информацией о тесте возникла ошибка,
выполнение проверяющей  программы немедленно завершается с кодом
возврата \V{RUN\_CHECK\_FAILED}. Функция \V{checker\_main} в
этом случае даже не получает управление.

Поля структуры \V{testinfo\_t} имеют следующее назначение.

\begin{itemize}
\item
\K{cmd\_argc}. Содержит количество аргументов командной строки,
которые были переданы в тестируемую программу при её запуске.
\item
\K{cmd\_argv}. Аргументы командной строки, которые были переданы
в тестируемую программу при её запуске.
\item
\K{comment}. Дополнительный комментарий к тесту. Текст
комментария добавляется в судейский протокол тестирования.
\item
\K{team\_comment}. Дополнительный комментарий к тесту. Текст
комментария добавляется в пользовательский протокол
тестирования.
\end{itemize}

\subsection{Макросы распределения памяти}

{\small
\begin{lstlisting}[print]
#define XCALLOC(p,s)
#define XREALLOC(p,s)
#define XALLOCA(p,s)
#define XALLOCAZ(p,s)
#define XMEMMOVE(d,s,c)
#define XMEMZERO(d,c)
\end{lstlisting}
}

Данные макросы предоставляют удобный интерфейс к функциям
выделения динамической памяти.

\subsubsection{Макрос \V{XCALLOC}}

Макрос \V{XCALLOC(p,s)} выделяет динамическую память и обнуляет её.
Если попытка выделения динамической памяти завершилась ошибкой,
процесс немедленно завершается с кодом возврата
\V{RUN\_CHECK\_FAILED}. Таким образом можно считать, что
выделение памяти с помощью данного макроса всегда завершается
успешно. \V{p} должно быть переменной, элементом массива или
полем структуры (то есть \V{p} --- это адресуемое выражение,
которое может находиться в левой части оператора присваивания
--- \I{lvalue}). \V{p} должно иметь указательный тип. Выражение
для \V{p} не должно иметь побочных эффектов (то есть не должно
использовать операцию присваивания, \V{++} или \V{-{}-}.
 \V{s} --- выражение целочисленного типа, которое не обязано
быть lvalue или не иметь побочных эффектов. Если \V{p} имеет тип
\V{TYPE *}, то вызов макроса \V{XCALLOC(p, n)} выделяет
динамическую память под массив из \V{n} элементов типа \V{TYPE}.
Например,
{\small
\begin{lstlisting}[print]
  int *p;

  XCALLOC(p, 10);
\end{lstlisting}
}
выделит память под массив из 10 элементов типа \V{int} и
присвоит адрес начала массива переменной \V{p}. Выделенная
память может быть освобождена явно с помощью вызова функции
\V{free}.

\subsubsection{Макрос \V{XREALLOC}}

Макрос \V{XREALLOC(p,s)} изменяет размер выделенного блока памяти.
Если попытка изменения размера динамической памяти завершилась
ошибкой, процесс немедленно завершается с кодом возврата
\V{RUN\_CHECK\_FAILED}. \V{p} должно быть lvalue указательного
типа без побочных эффектов. \V{s} --- это выражение целого типа
--- новое количество элементов в массиве. Если размер области
памяти увеличивается, добавленная память не инициализируется.
Выделенная память может быть освобождена явно с помощью вызова
функции \V{free}.

\subsubsection{Макрос \V{XALLOCA}}

Макрос \V{XALLOCA(p,s)} выделяет память в стековой области.
Выделенная память не инициализируется. Если запрашиваемый размер
памяти превышает возможности системы, процесс немедленно
завершается либо по сигналу \V{SIGSEGV} (Segmentation fault),
либо завершится с кодом возврата \V{RUN\_CHECK\_FAILED}. В обоих
случаях будет диагностирована внутренняя ошибка тестировщика.
\V{p} должно быть lvalue указательного типа без побочных
эффектов. \V{s} --- это выражение целого типа --- количество
элементов в массиве. Поскольку память выделяется на стеке, она
будет освобождена автоматически при завершении работы функции, в
которой используется \V{XALLOCA}. Функция \V{free}
использоваться не должна.

\subsubsection{Макрос \V{XALLOCAZ}}

Макрос \V{XALLOCAZ(p,s)} выделяет память в стековой области и
инициализирует её нулями. В остальном данный макрос полностью
аналогичен \V{XALLOCA}.

\subsubsection{Макрос \V{XMEMMOVE}}

Макрос \V{XMEMMOVE(d,s,c)} копирует массив из \V{c} элементов,
начинающийся с адреса \V{s}, по адресу \V{d}. Области памяти
могут накладываться произвольным образом. \V{d} должно быть
выражением указательного типа без побочных эффектов. \V{s} ---
это выражение указательного типа.

\subsubsection{Макрос \V{XMEMZERO}}

Макрос \V{XMEMZERO(d,c)} очищает массив из \V{c} элементов типа
\V{TYPE}, начинающийся с адреса \V{s}. \V{s} должно быть
выражением указательного типа \V{TYPE *}.

\subsection{Функции выдачи диагностики}

\subsubsection{Функция \V{checker\_OK}}

{\small
\begin{lstlisting}[print]
void checker_OK(void);
\end{lstlisting}
}

Данная функция печатает на стандартный поток вывода сообщение
\V{"OK"} и завершает проверяющую программу с кодом возврата 0,
сигнализируя программе \S{run} о том, что тестируемая программа
дала правильный ответ на тесте. Данная функция никогда не
возвращает управление в проверяющую программу.

\subsubsection{Функция \V{fatal}}

{\small
\begin{lstlisting}[print]
void fatal(int code, char const *format, ...);
\end{lstlisting}
}

Данная функция печатает на стандартный поток вывода сообщение в
соответствии со спецификацией формата \V{format} для функции
\V{printf} и дополнительными аргументами, затем печатается
символ перехода на новую строку, затем выполнение проверяющей
программы завершается с кодом возврата \V{code}. Данная функция
никогда не возвращает управление в проверяющую программу.

\subsubsection{Функция \V{fatal\_CF}}

{\small
\begin{lstlisting}[print]
void fatal_CF(char const *format, ...);
\end{lstlisting}
}

Данная функция печатает на стандартный поток вывода сообщение в
соответствии со спецификацией формата \V{format} для функции
\V{printf} и дополнительными аргументами, затем печатается
символ перехода на новую строку, затем выполнение проверяющей
программы завершается с кодом возврата \V{RUN\_CHECK\_FAILED}.
Данная функция никогда не возвращает управление в проверяющую
программу.

Данная функция может использоваться, чтобы диагностировать
внутреннюю ошибку тестирования, например, если входные данные
теста не удовлетворяют ограничениям задачи.

{\small
\begin{lstlisting}[print]
  if (N >= 1000) fatal_CF("Value %d of N is out of range", N); 
\end{lstlisting}
}

\subsubsection{Функция \V{fatal\_PE}}

{\small
\begin{lstlisting}[print]
void fatal_PE(char const *format, ...);
\end{lstlisting}
}

Данная функция печатает на стандартный поток вывода сообщение в
соответствии со спецификацией формата \V{format} для функции
\V{printf} и дополнительными аргументами, затем печатается
символ перехода на новую строку, затем выполнение проверяющей
программы завершается с кодом возврата
\V{RUN\_PRESENTATION\_ERR}. Данная функция никогда не возвращает
управление в проверяющую программу.

Данная функция может использоваться, тогда, когда тестируемая
программа не соблюдает формат выходных данных (Presentation
error).

{\small
\begin{lstlisting}[print]
  if (fscanf(f_team, "%d", &N) != 1)
    fatal_PE("Cannot read N");
\end{lstlisting}
}

\subsubsection{Функция \V{fatal\_WA}}

{\small
\begin{lstlisting}[print]
void fatal_WA(char const *format, ...);
\end{lstlisting}
}

Данная функция печатает на стандартный поток вывода сообщение в
соответствии со спецификацией формата \V{format} для функции
\V{printf} и дополнительными аргументами, затем печатается
символ перехода на новую строку, затем выполнение проверяющей
программы завершается с кодом возврата \V{5} (Wrong answer).
Данная функция никогда не возвращает управление в проверяющую
программу.

Данная функция может использоваться, тогда, когда тестируемая
программа выдаёт неправильный ответ на тест (Wrong answer).

{\small
\begin{lstlisting}[print]
  if (team_ans != corr_ans)
    fatal_WA("Wrong answer: team: %d, corr: %d", team_ans, corr_ans);
\end{lstlisting}
}

\subsection{Функции распределения памяти}

\subsubsection{Функция \V{xmalloc}}

{\small
\begin{lstlisting}[print]
void *xmalloc(size_t size);
\end{lstlisting}
}

Данная функция --- оболочка над стандартной библиотечной
функцией \V{malloc}. В случае, если \V{malloc} возвращает
\V{NULL}, проверяющая программа завершается немедленно с кодом
возврата \V{RUN\_CHECK\_FAILED}. Таким образом, функция
\V{xmalloc} никогда не возвращает \V{NULL}. Память, выделенная
\V{xmalloc} может освобождаться с помощью \V{free}, либо
перераспределяться с помощью \V{xrealloc}, \V{realloc}.

\subsubsection{Функция \V{xcalloc}}

{\small
\begin{lstlisting}[print]
void *xcalloc(size_t nmemb, size_t size);
\end{lstlisting}
}

Данная функция --- оболочка над стандартной библиотечной
функцией \V{calloc}. В случае, если \V{calloc} возвращает
\V{NULL}, проверяющая программа завершается немедленно с кодом
возврата \V{RUN\_CHECK\_FAILED}. Таким образом, функция
\V{xcalloc} никогда не возвращает \V{NULL}. Память, выделенная
\V{xcalloc} может освобождаться с помощью \V{free}, либо
перераспределяться с помощью \V{xrealloc}, \V{realloc}.

\subsubsection{Функция \V{xrealloc}}

{\small
\begin{lstlisting}[print]
void *xrealloc(void *ptr, size_t size);
\end{lstlisting}
}

Данная функция --- оболочка над стандартной библиотечной
функцией \V{realloc}. В случае, если \V{realloc} возвращает
\V{NULL}, проверяющая программа завершается немедленно с кодом
возврата \V{RUN\_CHECK\_FAILED}. Таким образом, функция
\V{xrealloc} никогда не возвращает \V{NULL}. Память, выделенная
\V{xrealloc} может освобождаться с помощью \V{free}, либо
перераспределяться с помощью \V{xrealloc}, \V{realloc}.

\subsubsection{Функция \V{xstrdup}}

{\small
\begin{lstlisting}[print]
unsigned char *xstrdup(const unsigned char *str);
\end{lstlisting}
}

Данная функция --- оболочка над стандартной библиотечной
функцией \V{strdup}. В случае, если \V{strdup} возвращает
\V{NULL}, проверяющая программа завершается немедленно с кодом
возврата \V{RUN\_CHECK\_FAILED}. Таким образом, функция
\V{xstrdup} никогда не возвращает \V{NULL}. Память, выделенная
\V{xstrdup} может освобождаться с помощью \V{free}, либо
перераспределяться с помощью \V{xrealloc}, \V{realloc}.

\subsection{Проверка конца файла}

{\small
\begin{lstlisting}[print]
void checker_in_eof(void);
void checker_team_eof(void);
void checker_corr_eof(void);
\end{lstlisting}
}

Эти функции проверяют, достигнут ли конец файла в данном файле.
Пропускаются все пробельные символы, если после этого в файле
остались непробельные символы, выполнение проверяющей программы
завершается с соответствующей ошибкой.

Функция \V{checker\_in\_eof} проверяет условие конца файла во
входном тестовом файле, если конец файла не достигнут,
проверяющая программа завершается с кодом
\V{RUN\_CHECK\_FAILED}.

Функция \V{checker\_team\_eof} проверяет условие конца файла в
файле результата тестируемой программы. Если конец файла не
достигнут, проверяющая программа завершается с кодом
\V{RUN\_PRESENTATION\_ERR}.

Функция \V{checker\_corr\_eof} проверяет условие конца файла в
эталонном файле результата. Если конец файла не достигнут,
проверяющая программа завершается с кодом
\V{RUN\_CHECK\_FAILED}.

\subsection{Закрытие файла}

{\small
\begin{lstlisting}[print]
void checker_in_close(void);
void checker_team_close(void);
void checker_corr_close(void);
\end{lstlisting}
}

Эти функции закрывают соответствующий входной файл.
\V{checker\_in\_close} закрывает тестовый файл,
\V{checker\_team\_close} закрывает файл результата тестируемой
программы, \V{checker\_corr\_close} закрывает эталонный файл
результата.

\subsection{Чтение целых и вещественных данных}

{\small
\begin{lstlisting}[print]
int  checker_read_in_int(const unsigned char *, int, int *);
int  checker_read_in_long_long(const unsigned char *, int, long long *);
int  checker_read_in_double(const unsigned char *, int, double *);
int  checker_read_in_long_double(const unsigned char *, int, long double *);
int  checker_read_team_int(const unsigned char *, int, int *);
int  checker_read_team_long_long(const unsigned char *, int, long long *);
int  checker_read_team_double(const unsigned char *, int, double *);
int  checker_read_team_long_double(const unsigned char *, int, long double *);
int  checker_read_corr_int(const unsigned char *, int, int *);
int  checker_read_corr_long_long(const unsigned char *, int, long long *);
int  checker_read_corr_double(const unsigned char *, int, double *);
int  checker_read_corr_long_double(const unsigned char *, int, long double *);
\end{lstlisting}
}

Эта группа функций считывает одно целое или вещественное число
из одного из входных файлов. Функции имеют следующий общий вид:

{\small
\begin{lstlisting}[print]
int checker_read_FILE_TYPE(const unsigned char *name,
                           int eof_error_flag,
                           TYPE *p_var);
\end{lstlisting}
}

В имени функции \V{FILE} обозначает файл, из которого
считывается число. \V{in} --- входной тестовый файл, \V{team}
--- результат работы тестируемой программы, \V{corr} ---
эталонный файл результата. \V{TYPE} --- тип считываемого
значения.

Параметр \V{name} содержит имя считываемой переменной.
Оно печатается, если чтение значения переменной завершилось
ошибкой. Параметр \V{eof\_error\_flag} определяет реакцию на
достижение конца файла. Если параметр имеет ненулевое значение,
и при попытке чтения из входного файла число считано не было, но
был достигнут конец файла, выполнение проверяющей программы
завершается с ошибкой. Код ошибки зависит от типа входного
файла. Если параметр \V{eof\_error\_flag} равен 0, при
достижении конца файла функция возвращает -1. Параметр
\V{p\_var} --- это адрес, по которому нужно поместить считанное
значение.

Функция возвращает 1, если число было успешно считано, либо -1,
если был достигнут конец файла, а параметр \V{eof\_error\_flag}
равен нулю. Во всех остальных случаях выполнение проверяющей
программы завершается с кодом возврата, зависящем от
считываемого файла. Если считывается входной тестовый файл
\V{in} или эталонный файл результата V{corr}, код возврата равен
\V{RUN\_CHECK\_FAILED}. Если считывается файл результата работы
тестируемой программы, код возврата равен
\V{RUN\_PRESENTATION\_ERROR}.

{\small
\begin{lstlisting}[print]
int  checker_read_int(int, const unsigned char *, int, int *);
int  checker_read_unsigned_int(int, const unsigned char *, int,
                               unsigned int *);
int  checker_read_long_long(int, const unsigned char *, int, long long *);
int  checker_read_unsigned_long_long(int, const unsigned char *, int,
                                     unsigned long long *);
int  checker_read_double(int, const unsigned char *, int, double *);
int  checker_read_long_double(int, const unsigned char *, int, long double *);
\end{lstlisting}
}

Эта группа функций считывает одно целое или вещественное число
из входного файла, задаваемого параметром функции. Функции имеют
следующий общий вид:

{\small
\begin{lstlisting}[print]
int checker_read_TYPE(int ind,
                      const unsigned char *name,
                      int eof_error_flag,
                      TYPE *p_var);
\end{lstlisting}
}

Здесь параметр \V{ind} определяет файл, из которого будет
считываться значение. Если \V{ind} равен 0, чтение ведётся из
входного тестового файла (\V{in}). Если \V{ind} равен 1, чтение
ведётся из файла результата тестируемой программы. Если \V{ind}
равен 2, чтение ведётся из эталонного файла результата. В
остальном поведение этих функций аналогично поведению функций
группы \V{checker\_read\_FILE\_TYPE}.

\subsection{Чтение текста}

\subsubsection{Функция \V{checker\_skip\_eoln}}

{\small
\begin{lstlisting}[print]
int  checker_skip_eoln(int ind, int eof_error_flag);
\end{lstlisting}
}

Данная функция пропускает все символы в входном файле до символа
перехода на новую строку \verb|'\n'| включительно.

Здесь параметр \V{ind} определяет файл, из которого будет
считываться значение. Если \V{ind} равен 0, чтение ведётся из
входного тестового файла (\V{in}). Если \V{ind} равен 1, чтение
ведётся из файла результата тестируемой программы. Если \V{ind}
равен 2, чтение ведётся из эталонного файла результата.

Параметр \V{eof\_error\_flag} определяет реакцию, когда конец
файла был достигнут ранее, чем символ \verb|'\n'|. Если параметр
имеет ненулевое значение, то в такой ситуации выполнение
проверяющей программы завершается. Если параметр равен 0,
функция возвращает -1.

Функция возвращает 0, если выполнение функции завершилось без
ошибок, и -1, если был достигнут конец файла, и параметр
\V{eof\_error\_flag} равен 0. Во всех остальных случаях
выполнение проверяющей программы завершается с кодом возврата,
зависящем от считываемого файла. Если считывается входной
тестовый файл \V{in} или эталонный файл результата V{corr}, код
возврата равен \V{RUN\_CHECK\_FAILED}. Если считывается файл
результата работы тестируемой программы, код возврата равен
\V{RUN\_PRESENTATION\_ERROR}.

\subsubsection{Функция \V{checker\_read\_file}}

{\small
\begin{lstlisting}[print]
void checker_read_file(int ind, unsigned char **out, size_t *out_size);
\end{lstlisting}
}

Функция считывает файл, задаваемый параметром \V{ind} целиком в
память. Память под считываемый файл выделяется в куче с помощью
функции \V{xrealloc}. Функция не возвращает результат, а при
возможной ошибке ввода вывода выполнение проверяющей программы
завершается с кодом возврата \V{RUN\_CHECK\_FAILED}. Функция
корректно считывает произвольные файлы, в том числе файлы,
содержащие нулевые байты.

Параметр \V{ind} определяет файл, из которого будет считываться
значение. Если \V{ind} равен 0, чтение ведётся из входного
тестового файла (\V{in}). Если \V{ind} равен 1, чтение ведётся
из файла результата тестируемой программы. Если \V{ind} равен 2,
чтение ведётся из эталонного файла результата. Параметр \V{out}
--- это адрес переменной указательного типа, в которую будет
записан адрес начала буфера считанного файла. Параметр
\V{out\_size} --- адрес переменной, в которую будет записан
размер считанного файла.

Под буфер файла выделяется на 1 байт больше, чем размер файла, и
в этот дополнительный байт записывается 0. Таким образом, если
считываемый файл не содержит в середине нулевых байтов, с
буфером считанного файла можно будет работать как с символьной
строкой.

\subsubsection{Функция \V{checker\_read\_buf}}

{\small
\begin{lstlisting}[print]
int  checker_read_buf(int ind,
                      const unsigned char *name,
                      int eof_error_flag,
                      unsigned char *buf,
                      size_t buf_size);
\end{lstlisting}
}

Данная функция считывает символьную строку в буфер ограниченного
размера. При чтении сначала пропускаются все пробельные символы,
затем чтение символьной строки ведётся до первого пробельного
символа, который остаётся во входном потоке. Если длина
символьной строки во входном файле превышает размер буфера,
выполнение проверяющей программы завершается с соответствующим
кодом возврата.

Параметр \V{ind} определяет файл, из которого будет считываться
значение. Если \V{ind} равен 0, чтение ведётся из входного
тестового файла (\V{in}). Если \V{ind} равен 1, чтение ведётся
из файла результата тестируемой программы. Если \V{ind} равен 2,
чтение ведётся из эталонного файла результата.

Параметр \V{name} позволяет задать имя считываемой переменной.
Оно печатается, если выполнение функции завершается из-за ошибки.

Параметр \V{eof\_error\_flag} определяет реакцию, когда конец
файла был достигнут ранее, чем считан хотя бы один значащий
символ строки. Если параметр имеет ненулевое значение, то в
такой ситуации выполнение проверяющей программы завершается.
Если параметр равен 0, функция возвращает -1.

В параметре \V{buf} передаётся адрес начала буфера для
считывания строки. Если \V{buf} --- нулевой указатель, функция
завершает выполнение проверяющей программы с кодом возврата
\V{RUN\_CHECK\_FAILED}.

В параметре \V{buf\_size} передаётся размер буфера. Если размер
буфера равен 0 или превышает 100000, функция завершает
выполнение проверяющей программы с кодом возврата
\V{RUN\_CHECK\_FAILED}. Значение размера буфера на 1 превышает
максимальную длину строки, которая может быть записана в этот
буфер из-за нулевого байта-терминатора строки. Например, если
размер буфера --- 80 байтов, то максимальная длина строки,
которая может быть в него записана --- 79 байтов. Если длина
считанной из файла строки превышает максимальную длину,
выполнение проверяющей программы завершается с соответствующим
кодом возврата.

Функция возвращает длину считанной строки и -1, если был
достигнут конец файла, и параметр \V{eof\_error\_flag} равен 0.
Во всех остальных случаях выполнение проверяющей программы
завершается с кодом возврата, зависящем от считываемого
файла. Если считывается входной тестовый файл \V{in} или
эталонный файл результата V{corr}, код возврата равен
\V{RUN\_CHECK\_FAILED}. Если считывается файл результата работы
тестируемой программы, код возврата равен
\V{RUN\_PRESENTATION\_ERROR}.

\subsubsection{Функция \V{checker\_read\_line}}

{\small
\begin{lstlisting}[print]
int checker_read_line(int ind,
                      const unsigned char *name,
                      int eof_error_flag,
                      unsigned char **out_str);
\end{lstlisting}
}

Данная функция считывает одну текстовую строку из входного
файла. Текстовая строка --- это последовательность произвольных
байтов, завершающихся байтом \verb|'\n'|, который считается
входящим в строку. Память под считываемую строку текста
выделяется с помощью функции \V{xrealloc}. В буфер строки после
символа \verb|'\n'| добавляется нулевой байт. Если строка не
содержит нулевых байтов в середине, то с ней можно работать как
с символьной строкой Си.

Параметр \V{ind} определяет файл, из которого будет считываться
значение. Если \V{ind} равен 0, чтение ведётся из входного
тестового файла (\V{in}). Если \V{ind} равен 1, чтение ведётся
из файла результата тестируемой программы. Если \V{ind} равен 2,
чтение ведётся из эталонного файла результата.

Параметр \V{name} позволяет задать имя считываемой переменной.
Оно печатается, если выполнение функции завершается из-за ошибки.

Параметр \V{eof\_error\_flag} определяет реакцию, когда конец
файла был достигнут ранее, чем считан хотя бы один символ
строки текста. Если параметр имеет ненулевое значение, то в 
такой ситуации выполнение проверяющей программы завершается.
Если параметр равен 0, функция возвращает -1.

Параметр \V{out\_str} --- это адрес указательной переменной, в
которую будет записан адрес начала буфера строки.

Функция возвращает длину считанной строки и -1, если был
достигнут конец файла, и параметр \V{eof\_error\_flag} равен 0.
Во всех остальных случаях выполнение проверяющей программы
завершается с кодом возврата, зависящем от считываемого
файла. Если считывается входной тестовый файл \V{in} или
эталонный файл результата V{corr}, код возврата равен
\V{RUN\_CHECK\_FAILED}. Если считывается файл результата работы
тестируемой программы, код возврата равен
\V{RUN\_PRESENTATION\_ERROR}.

Длина считанной строки, возвращаемая функцией, корректна, даже
если в середине считанной строки присутствуют нулевые байты. В
этом случае нельзя полагать, что длина считанной строки равна
\V{strlen(*out\_str)}.

\subsubsection{Функция \V{checker\_normalize\_line}}

{\small
\begin{lstlisting}[print]
void checker_normalize_line(unsigned char *str);
\end{lstlisting}
}

Данная функция <<нормализует>> поданную на вход символьную
строку \V{str}. Нормализация заключается в отбрасывании
пробельных символов (в том числе \verb|'\n'|) от конца
символьной строки. Поскольку длина строки явно не передаётся,
символьная строка \V{str} не должна содержать нулевых байтов в
середине строки.

\subsubsection{Функция \V{checker\_read\_file\_by\_line}}

{\small
\begin{lstlisting}[print]
void checker_read_file_by_line(int ind,
                               unsigned char ***out_lines,
                               size_t *out_lines_num);
\end{lstlisting}
}

Данная функция считывает файл целиком в память. При чтении файл
разбивается на строки текста, используя \verb|'\n'| как
разделитель строк. Считываемый файл не должен содержать нулевые
байты.

Параметр \V{ind} определяет файл, из которого будет считываться
значение. Если \V{ind} равен 0, чтение ведётся из входного
тестового файла (\V{in}). Если \V{ind} равен 1, чтение ведётся
из файла результата тестируемой программы. Если \V{ind} равен 2,
чтение ведётся из эталонного файла результата.

Параметр \V{out\_lines} --- это адрес переменной, в которую
будет записан адрес начала массива строк. Каждый элемент массива
строк --- это указатель на символьную строку, считанную из
файла.

Параметр \V{out\_lines\_num} --- это адрес переменной, в которую
будет записано количество считанных строк, то есть количество
элементов в массиве строк.

Функция не возвращает результата. При возникновении ошибки
чтения из файла выполнение проверяющей программы завершается с
кодом возврата \V{RUN\_CHECK\_FAILED}. Если при чтение файла был
считан нулевой байт, выполнение проверяющей программы
завершается с соответствующим кодом возврата. Если считывается
входной тестовый файл \V{in} или эталонный файл результата
V{corr}, код возврата равен \V{RUN\_CHECK\_FAILED}. Если
считывается файл результата работы тестируемой программы
\V{team}, код возврата равен \V{RUN\_PRESENTATION\_ERROR}.

\subsubsection{Функция \V{checker\_normalize\_file}}

{\small
\begin{lstlisting}[print]
void checker_normalize_file(unsigned char **out_lines,
                            size_t *out_lines_num);
\end{lstlisting}
}

Данная функция <<нормализует>> файл, считанный в память с
помощью функции \V{checker\_read\_file\_by\_line}. При этом
нормализуется каждая строка файла отбрасыванием пробельных
символов, находящихся в конце строки. После этого отбрасываются
все строки нулевой длины, находящиеся в конце считанного файла.
При этом количество строк в считанном файле уменьшается.

Параметр \V{out\_lines} --- это адрес начала массива строк,
полученный от функции \V{checker\_read\_file\_by\_line}.
Параметр \V{out\_lines\_num} --- это адрес переменной, хранящей
количество строк в считанном файле, тоже полученное от функции
\V{checker\_read\_file\_by\_line}. В результате работы функции
\V{checker\_normalize\_file} значение по адресу, указанному в
данном параметре, может уменьшится. Для пустого файла это
значение равно 0.

\subsection{Примеры}

\subsubsection{Сравнение двух целых чисел}

Приведённая ниже проверяющая программа предполагает, что файл с
результатом работы программы должен содержать единственное целое
число. Это число сравнивается с эталонным ответом.

{\small
\begin{lstlisting}[print,numbers=left]
#define NEED_CORR 1
#define NEED_INFO 0
#define NEED_TGZ  0
#include "checker.h"

int checker_main(int argc, char **argv)
{
  int team_ans, corr_ans;

  // presentation error, если файл пуст или нельзя считать число
  checker_read_team_int("team_ans", 1, &team_ans);

  // checker failed, если файл пуст или нельзя считать число
  checker_read_corr_int("corr_ans", 1, &corr_ans);

  // presentation error, если после числа в файле идёт "мусор"
  checker_team_eof();

  // checker failed, если после числа в файле идёт "мусор"
  checker_corr_eof();

  if (team_ans != corr_ans)
    fatal_WA("Answers do not match: team = %d, corr = %d",
             team_ans, corr_ans);
  checker_OK();
}
\end{lstlisting}
}

\subsubsection{Сравнение двух текстовых файлов}

Приведённая ниже проверяющая программа сравнивает файл с
результатом работы тестируемой программы с эталонным файлом. При
сравнении игнорируются пробелы на концах строк и пустые строки в
конце файлов.

{\small
\begin{lstlisting}[print,numbers=left]
#define NEED_CORR 1
#define NEED_INFO 0
#define NEED_TGZ  0
#include "checker.h"

int checker_main(int argc, char **argv)
{
  unsigned char **team_lines, **corr_lines;
  size_t team_lines_num, corr_lines_num, i;

  // считываем файл результата работы программы
  checker_read_file_by_line(1, &team_lines, &team_lines_num);
  // считываем эталонный файл
  checker_read_file_by_line(2, &corr_lines, &corr_lines_num);
  // отбрасываем пробелы в результате работы программы
  checker_normalize_file(team_lines, &team_lines_num);
  // отбрасываем пробелы в эталонном файле
  checker_normalize_file(corr_lines, &corr_lines_num);

  if (team_lines_num != corr_lines_num)
    fatal_WA("Different number of lines: team = %zu, corr = %zu",
             team_lines_num, corr_lines_num);
  for (i = 0; i < team_lines_num; i++)
    if (strcmp(team_lines[i], corr_lines[i]) != 0)
      fatal_WA("Line %zu differs: team:\n>%s<\ncorr:\n>%s<",
               i + 1, team_lines[i], corr_lines[i]);
  
  checker_OK();
}
\end{lstlisting}
}

% Local variables:
%  compile-command: "make"
% End:
