Настройка системы Contester для школьной олимпиады. Часть 1
Уже четвёртый год я являюсь членом жюри и системным администратором регионального этапа школьной олимпиады по информатике. В мои задачи входит подготовка всей информационной системы для автоматизированной проверки решений. Это настройка необходимого количества рабочих станций (по числу участников + резерв), а также установка и настройка сервера. Сервер необходим для централизованной проверки решений всех участников в ходе олимпиады. Работает это так: участник решает задачи по программированию и тестирует их локально. После того, как он убедится, что программа работает верно, он загружает исходный код решения на сервер с помощью веб-интерфейса. На сервере код автоматически компилируется и прогоняется на большом наборе тестов, неизвестных участникам.
Для серверной части я выбрал систему Contester. Однако он уже давно не обновлялся и поэтому ничего не знает о новых версиях компиляторов. К тому же, каждый год требования меняются. Также отсутствуют какие-либо возможности изменения работы алгоритмов тестирования. В этой статье я решил опубликовать свои конфигурационные файлы, а также поделиться методами итоговой проверки. Вся информация представленная в статье является открытой.
Итак, для начала ставим на сервер Contester и все необходимые компиляторы. В моём случае список был такой:
- Borland Delphi 7;
- Microsoft Visual Studio 2013 Express;
- JDK 1.8.0.71;
- Python 2.7.9;
- Python 3.5.1;
- Free Pascal 2.6.4;
- CodeBlocks (он содержит необходимый GNU C++ MinGW).
Запускаем Contester и видим, что автоматически подхватился только Delphi. Ещё в списке присутствовал .NET 2.0 и 3.5 для C#, но по-положению у нас должна быть более поздняя версия, поэтому его уберём. Кстати, ещё проблема была при попытке заставить всё это работать на 64-битной винде (C# и Java нормально не подключались). На 32-битной всё завелось без проблем.
Теперь надо рассказать контестеру о всех наших компиляторах. Для этого редактируем файл contestercfg.xml (C:\Program Files\Contester\contestercfg.xml). Там мы указываем строки компиляции и запуска решений:
<?xml version="1.0" encoding="windows-1251"?> <ContesterConfiguration> <!-- Файл конфигурации. См. страницу "Помощь" --> <!-- Linux: Learn it from /opt/firebird/SYSDBA.password --> <DatabasePassword>masterkey</DatabasePassword> <Compiler syntax="csnet" state="set"> <CompilerName>Microsoft Visual C# 2013 Express</CompilerName> <SourceFile>solver.cs</SourceFile> <CompileLine1>csc.exe /o+ /d:ONLINE_JUDGE solver.cs</CompileLine1> <TargetFile1>solver.exe</TargetFile1> <ExecuteLine>solver.exe</ExecuteLine> <AddPathVariable>C:\Windows\Microsoft.NET\Framework\v4.0.30319</AddPathVariable> </Compiler> <Compiler syntax="java" state="set"> <CompilerName>Java Development Kit 1.8.0.71</CompilerName> <SourceFile>solver.java</SourceFile> <CompileLine1>javac.exe -g solver.java</CompileLine1> <TargetFile1>solver.class</TargetFile1> <ExecuteLine>java.exe -Xss32m solver</ExecuteLine> <AddPathVariable>C:\Program Files\Java\jdk1.8.0_71\bin</AddPathVariable> </Compiler> <Compiler syntax="python" state="set"> <CompilerName>Python 2.7.9</CompilerName> <SourceFile>solver.py</SourceFile> <ExecuteLine>C:\Python27\python.exe solver.py</ExecuteLine> <AddPathVariable>C:\Python27</AddPathVariable> </Compiler> <Compiler syntax="python" state="set"> <CompilerName>Python 3.5.1</CompilerName> <SourceFile>solver.py</SourceFile> <ExecuteLine>C:\Users\username\AppData\Local\Programs\Python\Python35-32\python.exe solver.py</ExecuteLine> <AddPathVariable>C:\Users\username\AppData\Local\Programs\Python\Python35-32</AddPathVariable> </Compiler> <Compiler syntax="vbnet" state="set"> <CompilerName>Microsoft Visual Basic 2013 Express</CompilerName> <SourceFile>solver.vb</SourceFile> <CompileLine1>vbc.exe solver.vb</CompileLine1> <TargetFile1>solver.exe</TargetFile1> <ExecuteLine>solver.exe</ExecuteLine> <AddPathVariable>C:\Windows\Microsoft.NET\Framework\v4.0.30319</AddPathVariable> </Compiler> <Compiler syntax="pas" state="set"> <CompilerName>Free Pascal 2.6.4</CompilerName> <SourceFile>solver.pas</SourceFile> <CompileLine1>fpc.exe -Mtp -dONLINE_JUDGE -osolver.exe solver.pas</CompileLine1> <TargetFile1>solver.exe</TargetFile1> <ExecuteLine>solver.exe</ExecuteLine> <AddPathVariable>C:\FPC\2.6.4\bin\i386-win32</AddPathVariable> </Compiler> <Compiler syntax="cpp" state="set"> <CompilerName>Microsoft Visual C++ 2013 Express</CompilerName> <SourceFile>solver.cpp</SourceFile> <CompileLine1>C:\Program Files\Microsoft Visual Studio 12.0\VC\vcvarsall.bat && cl.exe solver.cpp /EHsc /O2 /D "ONLINE_JUDGE" /I"C:\Program Files\Microsoft Visual Studio 12.0\VC\include"</CompileLine1> <TargetFile1>solver.exe</TargetFile1> <ExecuteLine>solver.exe</ExecuteLine> <AddPathVariable>C:\Program Files\Contester\utils</AddPathVariable> </Compiler> <Compiler syntax="cpp" state="set"> <CompilerName>GNU C++ MinGW</CompilerName> <SourceFile>solver.cpp</SourceFile> <CompileLine1>"C:\Program Files\CodeBlocks\MinGW\mingwvars.bat" && mingw32-g++ solver.cpp -o solver.exe -DONLINE_JUDGE -std=c++11 -O2 -I "C:\Program Files\CodeBlocks\MinGW\lib\gcc\mingw32\4.7.1\include\c++"</CompileLine1> <TargetFile1>solver.exe</TargetFile1> <ExecuteLine>solver.exe</ExecuteLine> <AddPathVariable>C:\Program Files\CodeBlocks\MinGW\bin</AddPathVariable> </Compiler> <Compiler syntax="csnet" state="deny"> <CompilerName>Microsoft .NET 3.5 Framework</CompilerName> </Compiler> <Compiler syntax="csnet" state="deny"> <CompilerName>Microsoft .NET 2.0 Framework</CompilerName> </Compiler> <Compiler syntax="vbnet" state="deny"> <CompilerName>Microsoft .NET 3.5 Framework</CompilerName> </Compiler> <Compiler syntax="vbnet" state="deny"> <CompilerName>Microsoft .NET 2.0 Framework</CompilerName> </Compiler> <Compiler syntax="cpp" state="deny"> <CompilerName>Microsoft Visual C++ 2005 Express</CompilerName> </Compiler> </ContesterConfiguration>
Обязательно сверьте пути к компиляторам со своими. Последние строки с параметром state=»deny» необходимы для отключения автоматически определённых компиляторов, но которые нам не нужны.
Теперь перезапускаем контестер, убеждаемся, что перечислены все компиляторы и проверяем работу их всех на какой-нибудь простой задаче. Например, «A+B». Если всё хорошо, то можно загружать задачи.

Здесь возникает вторая проблема: по-умолчанию поддерживаются только чекеры (тестирующие программы) на паскале. А в последнее время их всё больше стало на C++. Можно конечно попробовать переписать чекер на паскаль, а можно поискать библиотеку testlib.h. Последняя версия такой библиотеки находится здесь. Но она не подходит к самому контестеру. В Интернете я потом нашёл исправленную старую версию testlib для контестера (ссылку потерял), добавил в неё несколько функций из последней официальной версии. [drain file 107 url Скачать testlib.h] ([drain file 107 size]). Библиотеку надо поместить в папку utils контестера.
Так, теперь по изменениям в чекерах. В первую очередь меняем название библиотеки и путь до неё. Если чекер на паскале, то меняем «testlib» на
unJudge17 in '..\utils\unJudge17.pas'
Было:
program checker1; uses testlib, sysutils; ...
Стало:
program checker1; uses unJudge17 in '..\utils\unJudge17.pas', sysutils; ...
Если чекер на C++, то меняем так:
#include "testlib.h" using namespace std; int main(int argc, char * argv[]) { ...
на
#include "..\utils\testlib.h" using namespace std; int main(int argc, char * argv[]) { ...
Теперь, если чекер на паскале, то необходимо внести ещё некоторые изменения. Список доступных функций можно посмотреть в файле utils\unJudge17.pas.
Чтение слова:
ja := ans.readword(blanks, blanks);
pa := ouf.readword(blanks, blanks);
меняем на
ja := ans.readstring;
pa := ouf.readstring;
Чтение действительного числа:
ja := ans.readreal;
pa := ouf.readreal;
меняем на
ja := ans.readfloat;
pa := ouf.readfloat;
и т.д.
Итогом должны получиться принятые (Approved!) чекеры для каждой задачи:
Теперь пробуем запускать «эталонные» решения жюри. При этом учитываем особенность контестера: Входные данные допускается читать как с консоли, так и из файла input.txt, выходные данные необходимо выводить в консоль. То есть, если решение выглядело так:
#include <cstdio> #include <iostream> #include <vector> using namespace std; int main() { freopen("zadacha.in", "rt", stdin); freopen("zadacha.out", "wt", stdout); ...
то его надо исправить примерно так:
#include <cstdio> #include <iostream> #include <vector> using namespace std; int main() { freopen("input.txt", "rt", stdin); //freopen("zadacha.out", "wt", stdout); ...
На всякий случай можно проверить решения жюри на разных языках (в этом году были C++, GNU C++, Python), естественно учитывая особенности ввода/вывода.
В результате, если всё хорошо, получаем вот такую картину:

Все задачи готовы и для каждой было проверено решение жюри (зелёные галочки). Так как олимпиада проходит в два этапа (два дня), то во избежании утечки, из второго тура названия задач пока убираем, оставим только цифры.
Устанавливаем для каждого тура время начала и окончания:
Теперь перенастраиваем сеть. На сервере (DHCP + DNS + ActiveDirectory) создаём отдельную подсеть, переносим в неё все компьютеры участвующие в олимпиаде, блокируем им Интернет. Создаём в домене отдельного пользователя («Олимпиада», «Olimp» и т.п.) с урезанными правами. Ещё раз проверяем работу всей системы, пробуем зайти с компьютеров на сервер контестера и идём отдыхать 🙂
Продолжение следует…