DOMjudge - co to jest i przekrój możliwości
DOMjudge jest narzędziem do automatycznego sprawdzania nadesłanych zadań spisanych w różnych językach programowania. Ma otwarte źródła i wciąż jest intensywnie rozwijany (co widać po
commitach), dlatego był już używany w wielu konkursach programistycznych, których lista jest na
stronie domowej.
Możliwości
Narzędzie dostarcza bogatego interfejsu dla użytkowników biorących udział w konkursach, administratorów i sędziów. W konkursach programistycznych cenną jest możliwość pracy w zespołach, w których to dowolny członek może wysłać rozwiązanie liczące się jako rozwiązanie zespołowe. Wyniki pracy zespołów można ze sobą porównywać w wygodnych tabelkach.
Z punktu widzenia testowania nadesłanych rozwiązań istnieje możliwość modyfikacji sposobu budowania plików w określonych językach programowania, ważne, że nadsyłane rozwiązania mogą składać się z wielu plików źrółowych. Poza testowaniem przez komunikację między testowanym programem, przy pomocy standardowego wejścia i wyjścia, istnieje możliwość napisania innych programów (niekoniecznie muszą to być skrypty), które będą testować nadesłane rozwiązania przy pomocy innych mechanizmów komunikacji międzyprocesorowej, np. potoków. Przy większym konkursie jest możliwość używania wielu maszyn do weryfikacji nadsyłanych rozwiązań. Do testowanego rozwiązania można narzucić limity na czas wykonywania, zużycie pamięci i inne limity podczas kompilacji i wykonywania.
Olbrzymią zaletą narzędzia jest dobra dokumentacja z dużą ilością dostępnych przykładów. Jest ona podzielona na dokumentację dla administratorów, użytkowników i sędziów.
Aby korzystać z narzędzia wystarczy dostęp przy pomocy przeglądarki przez protokoły
HTTP/
HTTPS, jest również dostępne api dla tych protokołów.
Zalet i możliwości tego narzędzia jest znacznie więcej, wiele z nich, Drogi Czytelniku, zauważysz czytając niniejszy artykuł.
Wady
Oczywiście narzędzie to, jak wszystko co piszą informatycy, ma pewne wady, znalazłem m.in:
Zanim zaczniesz czytać dalej
Rozbudowane narzędzie wymaga niestety trochę czasu, aby je zrozumieć, dlatego też, Drogi Czytelniku, przeanalizuj wady (i zalety) jakie wymieniłem powyżej. Przeanalizuj także poniższe screeny z interfejsu różnych typów użytkowników i ponawiguj sobie po
przykładowych interfejsach interaktywnych dostępnych on-line. Zastanów się do jakich zastosowań potrzebujesz takiego narzędzia i czy nie lepiej napisać własnego.
Jeśli jeszcze Cię nie zniechęciłem, to dobrze.
Interfejs graficzny przez przeglądarkę
Poniżej zamieściłem parę przykładowych screenów widzianych przez przeglądarkę przy pracy z narzędziem DOMjudge:
Patrząc na komórkę pod danym zadaniem programistycznym mamy dwie liczby. Oznaczają one (ilość wysyłek zanim dane rozwiązanie okazało się poprawne) / (ilość minut które upłynęły od pojawieniu się problemu programistycznego do czasu wysłania poprawnego rozwiązania).
Jak widać możliwe jest wysyłanie rozwiązań problemu w wielu językach programowania, jest historia nadesłanych rozwiązań oraz wyniki testów automatycznych dla poszczególnych wysłanych zadań.
Na tej stronie widać przekrój możliwości administratora DOMjudge, a jak wiadomo administrator może wszystko.
Przydatnym jest na stronie głównej dostępność dokumentacji.
Jak widać każdy z problemów może być dostępny w wielu konkursach.
Jak widać każdy z problemów ma swój tekst dostępny dla użytkowników. Widać też ustawienia dostępne dla danego problemu, w tym skrypty służące do uruchamiania i weryfikowania rezultatu po wykonaniu programu. Ustawienia te można dowolnie zmieniać.
Widać w jakich konkursach jest widoczne dane zadanie programistyczne, a także widać jego ostatnio wysłane rozwiązania.
Jak widać poza podglądem nadesłanych rozwiązań, każdy z sędziów może się przypisać do danego rozwiązania, aby je dodatkowo zweryfikować ręcznie. Po wejściu w nadesłane rozwiązanie można podejrzeć kod źródłowy, wraz z kolorowaniem składni, zmianami od ostatniej wersji dokonanymi przez dany zespół, można zatwierdzić dane rozwiązanie. Sędzia i administrator mogą zarządzić ponowne przeprowadzenie testów automatycznych, można również zostawić komentarz w zadaniu, jak i odpowiedzieć na uwagi od użytkownika.
Interfejs interaktywny w realu
Na oficjalnej stronie jest możliwość przeklikania rzeczywistych interfejsów narzędzia, wypełnionych przez przykładowe dane (
link):
Plan na niniejszy artykuł
W niniejszym artykule skupię się na tym jak używać tego narzędzia od strony zarówno administratorów i sędziów, jak i uczestników konkursu. Będą przykłady używania narzędzia wraz z przykładowymi zadaniami. Będę się skupiał na zadaniach napisanych w C++, aczkolwiek narzędzie domyślnie wspiera wiele języków programowania.
Instalacja DomJudge
Aby zainstalować to narzędzie potrzebujemy system Linuxowy, najwygodniej będzie nam jak będzie to system oparty na Debianie np. Mint lub Ubuntu, gdyż w dokumentacji mamy gotowe komendy pod instalacje wielu zależności. Z istotnych zależności potrzebujemy:
Są jeszcze pewne opcjonalne zależności, opcjonalne wg dokumentacji, ale warto je zainstalować:
Szczegóły odnośnie konfiguracji i instalacji narzędzia polecam przeczytać z
dokumentacji dla administratorów (polecam
wersję PDF).
Szybka instalacja dla ustawień domyślnych
Poniżej szybka instalacja dla ustawień domyślnych. Przeprowadzałem to na systemi
Ubuntu 18.04, dlatego dla tego systemu zamieszczam komendy. Jeśli u kogoś będzie brakować pewnych narzędzi w repozytorium należy dodać odpowiednie repozytoria lub doinstalować ręcznie.
Instalacja zależności dla DOMjudge
Po wpisaniu poniższych komend będzie trochę interakcji z użytkownikiem, warto wtedy zapamiętać hasło dla php-myadmin:
sudo apt-get install gcc g++ make zip unzip mysql-server apache2 php php-cli libapache2-mod-php php-gd php-curl php-mysql php-json php-zip php-mcrypt php-gmp php-xml php-mbstring bsdmainutils ntp phpmyadmin libcgroup-dev linuxdoc-tools linuxdoc-tools-text groff texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended texlive-lang-european
Instalacja zależności dla submit-clienta
To jest przydatne jedynie gdy chcemy z takiego narzędzia korzystać, lub przygotować je dla użytkowników-uczestników.
Gdy chcemy je zbudować dla uczestników, najlepiej się upewnić, że instalujemy dependencje w wersji statycznej, w przeciwnym razie wystarczy nam komenda:
sudo apt install libcurl4-gnutls-dev libjsoncpp-dev libmagic-dev
Instalacja zależności dla C++
sudo apt install libcurl4-gnutls-dev libjsoncpp-dev libmagic-dev make sudo debootstrap libcgroup-dev php-cli php-curl php-json php-zip php-dev procps gcc g++
Budowanie DOMjudge
Najpierw należy pobrać najnowszą wersję, możemy to zrobić z
oficjalnej strony, następnie należy to rozpakować na systemie linuxowym, adres wnętrza rozpakowanego katalogu będę określał
$DOMJUDGE
.
cd $DOMJUDGE
./configure --with-baseurl=localhost/domjudge
make all
sudo make install-domserver install-judgehost install-docs
Oczywiście, jeśli chcemy na danej maszynie możemy zbudować tylko serwer, albo tylko
judgehost.
Konfiguracja bazy danych (MySQL)
Root w mysqlu nie ma domyślnie hasła, warto je ustawić:
sudo mysqladmin -u root password ukryteHaslo
Możemy sprawdzić czy się nam uda wejść do bazy przy użyciu danego hasła:
sudo mysql -u root -p
Mając to możemy skonfigurować bazę danych:
sudo $DOMJUDGE/sql/dj_setup_database -u root -p ukryteHaslo install
Konfiguracja serwera Apache
cd $DOMJUDGE
sudo cp etc/apache.conf /etc/apache2/conf-available/domjudge.conf
sudo service apache2 reload && sudo a2enconf domjudge && sudo apache2ctl graceful
Konfiguracja procesu automatycznie sprawdzającego (judgedaemon)
sudo useradd -d /nonexistent -U -M -s /bin/false domjudge-run
sudo groupadd domjudge-run
sudo cp $DOMJUDGE/etc/sudoers-domjudge /etc/sudoers.d/
sudo sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT=.*$/GRUB_CMDLINE_LINUX_DEFAULT="quiet cgroup_enable=memory swapaccount=1"/g' /etc/default/grub
sudo update-grub
sudo $DOMJUDGE/misc-tools/create_cgroups
sudo $DOMJUDGE/misc-tools/dj_make_chroot
Opcjonalne narzędzie do porównywania między wersjami wysłanych rozwiązań -xdiff php extention
Musimy wpierw zainstalować
bibliotekę xfiff, następnie możemy zainstalować:
sudo pecl install xdiff
, pozostaje jeszcze dodać informacje o tym do php.ini, którego położenie można poznać wpisując
php --ini
Proces automatycznie sprawdzający czy przyszły nowe rozwiązania
Po zainstalowaniu i skonfigurowaniu narzędzia trzeba jeszcze uruchomić proces automatycznie testujący wysłane rozwiązania, jeśli mamy go zainstalowanego w domyślnej lokalizacji robimy to następującą komendą:
sudo /usr/local/bin/judgedaemon
Po jej wywołaniu w konsoli będą się pojawiały komunikaty na standardowe wyjście po pojawieniu się nowych rozwiązań.
Opcjonalne połączonie HTTPS
Jest to możliwe, najpierw trzeba skonfigurować SSL w serwerze Apache np.
wg tego opisu. Następnie powinniśmy ustawić judgedaemon kopiując certyfikat i dokonując rehashowania w taki sposób:
sudo cp /etc/apache2/ssl/apache.crt /etc/ssl/certs/
sudo c_rehash
Od teraz możemy używać naszego narzędzia przez HTTPS. Oczywiście jeśli sami sobie wygenerowaliśmy certyfikat to przeglądarki będą wszystkich ostrzegać przed naszą stroną.
Sprawdzenie czy wszystko działa
Mając już w pełnu skonfigurowane DOMjuudge możemy już normalnie używać tego narzędzia, domyślnym loginem i hasłem jest
admin
/
admin
, hasło zmieniamy wchodząc do zakładki
users, interfejs do zmiany hasła jest dość prosty, chociaż szczegóły o zmianie hasła opisane są później.
Jeśli ustawiliśmy adres
localhost/domjudge/
to mamy dostępne następujące podstrony:
Możemy też wejść jako administrator do
home->
Config checker celem sprawdzenia czy nam wszystko działa, domyślnie mogą być pewne warningi, których możemy się pozbyć zmieniając
php.ini
i ustawienia bazy danych, lub zignorować.
Opis ikon, zanim pokażę jak dodać przykładowe zadanie
Zacznę od tego, że wiele powszechnie stosowanych funkcji jest ukrytych pod przyciskami-ikonami, odpowiednio służącymi do dodawania, zapisywania na dysk, edycji i usuwania:
Przy pomocy tych ikon można operować na wszystkim, do czego takie opcje są przydatne. Przycisk dodawania często znajduje się pod listą elementów (np. konkursów, drużyn, zadań programistycznych itp.), natomiast pozostałe ikony znajdują się obok każdego elementu. Bardzo ciekawym jest ikona zapisu na dysku, gdyż stanowi pewnego rodzaju kopię zapasową, którą możemy potem załadować.
Dodawanie własnego zadania programistycznego
Aby dodać zadanie programistyczne, gdy wystarczy nam działanie domyślne, nie potrzeba nic wcześniej ustawiać. Poza tym zadanie po dodaniu można normalnie edytować.
Jak dodać zadanie
Aby dodać zadanie do zrobienia należy wejść do zakładki
problems, następnie kliknąć przycisk dodawania nowego problemu, pojawi się nam wtedy okienko:
Mamy tam następujące możliwości ustawień:
Bardziej szczegółowo o narzędziach do uruchamiania nadesłanego rozwiązania, po ewentualnym skompilowaniu oraz porównywania, informacje znajdują się poniżej.
Treści domyślnych narzędzi/skryptów budujących i testujących znajdują się po rozpakowaniu DOMjudge w katalogu
{domjudge}/sql/files/defaultdata/
, poza tym można je podejrzeć wchodząc do
executables.
Należy uważać z limitem zużytej pamięci RAM, gdyż jeśli program go przekroczy pojawi się bardzo nieintuicyjna informacja-błąd wykonywania z programem przerwanym signałem 9:
Możemy jeszcze załadować zadanie programistyczne przez archiwum ZIP. Tego typu opcja jest przydatna, gdy chcemy przenieść ustawienia z innego serwera. W innych przypadkach jest to trochę niewygodne, gdyż paczka musi spełnić
wymagania paczki z problemami Kattis, szczegółowy opis jakie pola musimy wypełnić znajduje się
w dokumentacji dla sędziów w rozdziale
Problem package format. Ciekawym faktem jest, iż do takiego archiwum możemy załączyć wzorcowe rozwiązanie, które od razu jest sprawdzane. Są też
pewne narzędzia do veryfikacji takich archiwów.
Dodawanie przypadków testowych
Po dodaniu własnego zadania programistycznego pozostaje jedynie dodać przypadki testowe, jeśli nie dodamy chociaż jednego to w interfejsie administratora będziemy mieli sygnalizowany błąd
internal error, oraz nie będziemy mogli testować naszego programu. Jeden przypadek testowy w zadaniu programistycznym to dwa pliki, z których najczęściej jeden zawiera dane na standardowe wejście testowanego rozwiązania, drugi dane oczekiwane na standardowym wyjściu programu, będące odpowiedzią na zadane standardowe wejście. Oczywiście dzięki możliwości używania własnych skryptów do testowania i porównywania możemy traktować te pliki inaczej.
Dodajemy przypadki testowe już w istniejącym zadaniu programistycznym, aby je dodać musimy więc wejść do danego problemu, kliknąć
details/edit przy polu nazwanym
Testcases, po kliknięciu naszym oczom pojawi się ekran podobny do:
Poza możliwością załadowania plików, warto jeszcze wpisać opis przypadku testowego. Możemy jeszcze zaznaczyć, czy podane w ramach tego testu dane testowe mają być "przykładowymi", czyli widocznymi dla uczestników konkursu. Ładować obrazka nie musimy. Po ustawieniu wszystkich informacji możemy zatwierdzić nasz test.
Możemy podmienić pliki z przypadkami testowymi, natomiast niestety nie możemy ich usunąć z danego testu z poziomu interfejsu administratora, dlatego lepiej nie dodawać na zapas.
Po dodaniu zadania programistycznego, aby można było przyjmować jego rozwiązania, należy go dodać do jakiegoś konkursu, ja w tym celu utworzyłem nowy konkurs, ale można też skorzystać z istniejącego.
Przykład dodawania własnego zadania
Pewien wstęp już mamy więc teraz nam pozostaje dodać nowe zadanie w zakładce
problems. Niech to będzie liczenie silni, przykładowo ustawmy nazwę
silnia, limit czasowy 1 sekunda, potrzebujemy jeszcze załączyć tekst: np. w formacie TXT, proponuję:
Mamy napisać program, który na standardowe wejście otrzymuje liczbę, w odpowiedzi ma na standardowe wyjście wypisać wartość silni dla otrzymanej liczby. Testowanie ma odbywać się tak długo, dopóki nie zostanie podany znak końca pliku na standardowe wejście.
Liczby są podawane w formie liczba\n, w tej samej formie oczekujemy odpowiedzi.
Zakres podawanych liczb na standardowe wejście to liczby całkowite [0, 21].
Po dodaniu zadania wchodzimy w jego edycję i jako przypadki testowe proponuję jako
Input testdata: załadować plik:
1
3
0
10
20
Natomiast jako
Output testdata: ładujemy:
1
6
1
3628800
2432902008176640000
polecam w tym przypadku zaznaczyć, iż jest to "przykładowy" przypadek testowy, oraz mimo wszystko dodać opis.
Przykładowe rozwiązanie silni w C++17, z naciskiem na szybkość wykonywania
Poniższe rozwiązanie zostało zaproponowane przez pewnego
Eksperta C++ z niniejszego serwisu, który jednak planuje pozostać anonimowy:
#include <cstdio>
#include <cstdint>
#include <cinttypes>
#include <array>
using T = std::uint_fast64_t;
constexpr T factorial( T n )
{
T result = 1;
while( n > 1 )
result *= n--;
return result;
}
constexpr auto makeFactorials()
{
std::array < T, 21 > a { };
for( T i = 0; i < a.size(); ++i )
a[ i ] = factorial( i );
return a;
}
constexpr auto factorials = makeFactorials();
int main()
{
T n;
while( std::scanf( "%" SCNuFAST64, & n ) != EOF )
std::printf( "%" PRIuFAST64 "\n", n < factorials.size() ? factorials[ n ]
: T
{ } );
}
Czego nam brakuje, aby można było automatycznie sprawdzać rozwiązania tego zadania?
Poniżej opis pewnych kroków, które należy w tym celu wykonać, ich szczegółowy opis znajduje się w dalszych częściach artykułu:
Dodawanie nowego konkursu
Aby dodać nowy konkurs wchodzimy na stronę główną z poziomu użytkownika-administratora, wybieramy
contests, potem klikamy ikonę dodawania nowego i naszym oczom pojawi się ekran podobny do:
na nim uzupełniłem pola, które są obowiązkowe, a oto opis pewnych pól:
Po ustawieniu tego wszystkiego pozostaje jedynie skonfigurować użytkowników i drużyny. Należy jeszcze pamiętać o tym, aby co najmniej jeden proces wykonujący kompilacje był aktywny.
Tworzenie użytkowników i drużyn
Niestety do tworzenia nowego użytkownika i drużyny nie ma w interfejsie webowym możliwości utworzenia wielu użytkowników i drużyn równocześnie. Musimy każdorazowo utworzyć użytkownika, a aby ten miał możliwość wysyłania zadań musi należeć do jakiejś drużyny, oczywiście drużynę można też dodać z poziomu interfejsu webowego.
Tworzenie drużyny
Aby utworzyć drużynę wchodzimy na zakładkę
teams, potem klikamy ikonkę dodania nowej drużyny i naszym oczom pojawia się interfejs:
Na powyższym widoku widać jakie opcje możemy ustawić dla drużyny, pozwolę sobie opisać jedynie część z nich:
Tworzenie użytkownika
W celu utworzenia użytkownika wchodzimy na zakładkę
users, gdzie możemy albo kliknąć tworzenie nowego użytkownika, albo edytować już istniejącego -interfejsy obydwu funkcji są bardzo podobne. Ja utworzyłem tego użytkownika, tworząc drużynę, dlatego mój ekran edycji istniejącego użytkownika wygląda tak:
Na uwagę zasługuje fakt, iż użytkownik może się zalogować jedynie, jeśli ustawimy mu hasło. Ciekawe jest, że jeśli ustawimy adres IP, to logowanie użytkownika o tym adresie IP będzie następowało automatycznie, gdy będzie on wchodził na stronę z tego adresu IP.
Utworzony użytkownik-uczestnik nie może nic zmienić w swoim profilu, nawet hasła, wszystkie zmiany musi za niego zrobić administrator!
Programistyczne tworzenie użytkowników
Za dodawanie użytkowników jest odpowiedzialna funkcja PHP:
function do_register()
znajdująca się w pliku
auth.php
, jak widać hashowanie hasła w PHPa odbywa się przy użyciu kodu:
function dj_password_hash($password)
{
return password_hash($password, PASSWORD_DEFAULT,
array('cost' => PASSWORD_HASH_COST));
}
stała
PASSWORD_HASH_COST
znajduje się w pliku
domserver-config.php
, która w czasie pisania tego artykułu wynosiła
10
. Wiedząc to można napisać kod, który wstawia wiele rekordów do bazy. Aby wiedzieć, która tabela jest za to odpowiedzialna polecam
dokomentację dla administratorów, rozdział
Configure the contest data.
Grupowa generacja haseł użytkownikom
Jest możliwość ze strony głównej panelu administratora wygenerowania wielu losowych haseł naraz, w tym celu wchodzimy do
Manage team passwords i tam losowe hasła możemy wygenerować:
Opcja jest bardzo przydatna w sytuacji, gdy utworzymy wiele drużyn, tworząc od razu użytkownika w ramach drużyny.
Samodzielna rejestracja użytkowników
Ta opcja jest domyślnie wyłączona, ale jeśli wejdziemy z głównej strony panelu administratora do
Configuration settings, możemy zmienić te ustawienia obok pola
Allow registration, do zarejestrowania potrzeba tylko nazwa użytkownika i hasło.
Wysyłanie zadań przez użytkowników
Mając już wszystkie przygotowania zrobione, oraz potworzonych użytkowników, mogą oni wysyłać zadania, w tym celu muszą zalogować się:
http://{adres naszego domjudge}
, tam należy wybrać
login, z prawej strony na górze wybieramy konkurs, na który chcemy wysłać zadanie i pojawi się nam strona:
możemy wybrać plik/pliki, wybieramy, którego zadania programistycznego jest to rozwiązania, a język programowania się nam ustawi automatycznie, zależnie od rozszerzenia pliku, oczywiście jeśli chcemy możemy go zmienić.
Możemy załączać równocześnie wiele plików źródłowych i nagłówkowych, ale muszą to być oddzielne pliki. Jeśli chcielibyśmy móc wrzucać archiwum, powinniśmy zmienić skrypt kompilujący tak, aby wpierw je rozpakowywał.
Jeśli się wszystko zgadza, możemy kliknąć
submit. Po wysłaniu pojawi się nam poniżej informacja, że wysłaliśmy rozwiązanie problemu X, w języku CPP, a rezultat pojawi się za chwilę
PENDING, nic nie musimy robić, a jeśli wszystko pójdzie dobrze, strona po chwili się automatycznie odświeży i naszym oczom powinien pokazać się wynik automatycznego testu:
widać tam ilość zdobytych punktów, pozycję w rankingu oraz ilość prób zanim dane zadanie zostało zaakceptowane łamane na ilość minut między aktywacją zadania a wysłaniem poprawnego rozwiązania, na obrazku widać dwa problemy, z których rozwiązaliśmy tylko jeden.
W czasie, gdy kompilowane są nadesłane rozwiązania, możemy podejrzeć standardowe wyjście procesu budującego, który dane zadanie zbudował (jeśli mamy wiele uruchomionych trzeba znaleźć tego, co budował) i zobaczymy m. in. jaka jest ścieżka robocza, jakie skrypty są wywoływane, co było wypisywane na ekran itp.
Znaczenie rezultatów
Warto wiedzieć, co oznaczają rezultaty i na jakim etapie dany rezultat może się pojawić, oto możliwe rezultaty.
Dodam, że zwracając odpowiednie kody błędów ze skryptów użytkownik otrzymuje dany rezultat.
Jak DOMjudge sprawdza -opis kroków
Mamy już możliwość sprawdzania rozwiązań prostego zadania, bazując na ustawieniach domyślnych, warto więc się pochylić pochylić nad sposobem działania automatycznego sprawdzania, aby wiedzieć jak ono działa.
Po krótce proces sprawdzania składa się zasadniczo z trzech kroków:
Bardziej szczegółowy sposób działania DOMjudge
Bardziej szczegółowo wygląda to w taki sposób (dodam, że kolejny etap uruchomi się, jeśli poprzedni wykona się poprawnie):
Podetapy każdego z kroków automatycznego sprawdzania
Każdy z tych trzech kroków (kompilacja, uruchamianie i sprawdzenie rezultatów) ma dwa podetapy, za które odpowiadają dwa pliki wykonywalne:
Pliki wykonywalne
build i
run jeśli będą skryptami powinny zaczynać się od specjalnej, pierwszej linii komentarza mówiącej przez jaki interpreter dany skrypt ma zostać wykonany, przykładowo dla basha będzie to:
#!/bin/bash
Dodam, że wygodniej jeśli pliki te są skryptami, gdyż można je wtenczas edytować przez przeglądarkę z panelu administratora.
Zmiana domyślnych kroków budowania, uruchamiania i porównywania
Można użyć utworzone przez siebie kroki, ale jeśli potrzebujemy tylko drobnych zmian (np. wprowadzenia pewnych flag kompilacji typu
-Werror
itp. i nie przeszkadza nam, aby taki sposób kompilacji był domyślny możemy to zmienić, w tym celu wchodzimy ze strony głównej administratora na "Executables" (przetłumaczę to jako
narzędzia wykonywale). Następnie pojawiają się nam wszystkie narzędzia jakie mamy, są tam kompilatory dla różnych języków programowania, narzędzia do uruchamiania programów oraz narzędzia do porównywania wyników:
Celem zmiany klikamy na edycję wiersza, który chcemy edytować, pojawia się wtedy możliwość zmiany typu "skryptu", do wyboru:
mamy też możliwość edycji treści plików tekstowych w przeglądarce: "file contents" (widać tutaj przewagę plików-skryptów nad binarnymi). Dane narzędzie może się składać z wielu plików, każdy z tych plików możemy edytować przez przeglądarkę (pamiętając oczywiście o zatwierdzeniu zmian).
Interesującą możliwością jest dodanie wyświetlania pewnych informacji wewnątrz skryptów, jeśli zostaną one dodane do skryptów kompilujących to na konsoli procesu, który akurat się podejmie kompilacji, pojawią się te informacje. Jeśli z kolei zdecydowalibyśmy się na dodanie wyświetlania pewnych informacji w skrypcie uruchamiającym to wydruk ten nie będzie widoczny w konsoli procesu-daemona, tylko jako wydruk programu.
Wreszcie "skrypt porównujący", który domyślnie jest programem napisanym w C++ porównuje linie ze sobą i mamy możliwość skonfigurowania jego wrażliwości przez opcje:
Skrypty te nie mają dostępu do dużej ilości informacji, jedynie absolutnie konieczne do wykonania przez nich pracy, czyli np. nie dostaniemy się z poziomu skryptów do nazwy użytkownika i innych danych, które nie są nam konieczne w tym momencie.
Własny krok automatycznego sprawdzania
Przygotowanie paczki
Jak wiemy z poprzedniego punktu, aby wykonać któregokolwiek z tych kroków musimy mieć plik wykonywalny
build, po jego wykonaniu mamy mieć plik wykonywalny
run, dlatego też nasza paczka najczęściej będzie miała pliki
build i
run, poza tym paczka może zawierać jeszcze inne pliki, o niemalże dowolnych nazwach i dowolnej ilości, ale warto te pliki uwzględnić w
build lub
run.
Paczka taka powinna być zwykłym
archiwum ZIP, które ma bezpośrednio (bez katalogów) pliki
build i ewentualnie
run, oraz opcjonalnie możemy załączyć również inne pliki.
Nie każde archiwum ZIP jest akceptowane przez DOMjudge, przykładowo gdy z GUI wybierzemy "Utwórz archiwum" może ono nie być poprawne, natomiast bez problemu akceptowalne jest archiwum zrobione z wiersza poleceń:
zip -r nazwaPaczki.zip run build [plik1.cc [plik2.cc [...]]]
Możliwości ustawiania własnych kroków budowy
Dzięki podziałowi na etapy sprawdzania, do każdego z etapów możemy ustawiać własne, niemalże dowolne zachowania: mamy możliwość w łatwy sposób zmienić sposób uruchamiania naszego programu, oraz jego testowania, nie tylko przez sprawdzanie standardowego wejścia i wyjścia. Przykładowo testując nasz program możemy uruchomić testowany program tworząc niemalże dowolną interakcję z wysłanym programem np. potok (przykład na to jest w przykładowych problemach dostarczonych wraz z narzędziem DOMjudge, o nazwie
boolfind_run, który można pobrać po wejściu na podstronę
executables). Nie możemy natomiast w łatwy sposób zmieniać sposobu budowy konkretnego zadania -tego typu ustawienia wiążą się albo ze zmianą domyślnego sposobu budowania dla plików danego języka programowania lub dodania innego sposobu budowania dla danego języka programowania oprócz dotychczasowego, ale wtedy koniecznym będzie powiadomienie użytkowników, aby załączając rozwiązanie danego problemu wybrali np. c++2, a jeśli o tym zapomną to skrypty testujące powinny to wykryć i powiadomić nieuważnego użytkownika.
Dla dociekliwych -kto wywołuje poszczególne kroki oraz z jakimi argumentami
Jeśli jesteśmy ciekawi, jakie argumenty są przesyłane do tych skryptów możemy albo je wyświetlić, albo przyjrzeć się skryptom:
compile.sh, który zajmuje się kompilacją danego rozwiązania, domyślnie będzie znajdował się tutaj:
/usr/local/lib/domjudge/judge/compile.sh
oraz
testcase_run.sh, który domyślnie będzie uruchamiał i testował rezultat nadesłanego rozwiązania, domyślnie znajdujący się
/usr/local/lib/domjudge/judge/testcase_run.sh
To jest dla bardziej wtajemniczonych, ale to najlepsze miejsce, aby zawrzeć tutaj taką informacje - jeśli na etapie uruchamiania skompilowanego programu w podkroku build utworzymy plik wykonywalny o nazwie runjury to domyślnie skrypt testcase_run.sh dokona skopiowania dodatkowych programów: runjury i runpipe.
To jest o tyle istotne, że operując na innych nazwach można się dziwić, czemu w przykładzie "wbudowanym boolfind_run" wszystko działa, a w naszym nie.
Dla jeszcze bardziej dociekliwych dodam, że nasze
compile.sh i
testcase_run.sh są wywoływane przez ten właśnie proces, który pobiera i buduje nasze rozwiązania (i co ciekawe jest napisany w PHP):
judgedaemon, znajdujący się domyślnie tutaj:
/usr/local/bin/judgedaemon
.
Dodawanie własnego kroku budowy
Wiemy już z jakich kroków składa się proces automatycznej oceny naszych rozwiązań, czas na informacje jak dodać własny krok.
Do dodania któregokolwiek z trzech kroków (
compile lub
run lub
compare) musimy załadować z konta administratora paczkę opisaną powyżej. Aby to zrobić wchodzimy na stronę główną panelu administratora (
Home), następnie klikami
Executables. Paczkę możemy wtedy załączyć przez formularz ładowania pliku widoczny na screenie (uwaga -nazwa paczki jest od razu ID, którego nie możemy zmieniać). Następnie pojawi się okno podglądu tego narzędzia, gdzie możemy zmienić nazwę, krok, w którym mają być te pliki wykonywalne odpalane (do wyboru
compile,
run,
compare) Możemy też edytować w przeglądarce treść wszystkich plików tekstowych (dlatego skrypty mają przewagę nad binarkami). Jeśli chcielibyśmy ustawić własną nazwę i ID, to pod stroną z narzędziamy (
executables) klikamy ikonę dodawania, uzupełniamy widoczne pola formularza, poza załączaniem archiwum:
zapisujemy, następnie musimy wrócić do edycji narzędzia i załadować paczkę. Z dwóch sposobów polecam jednak od razu załadować paczkę z odpowiednią nazwą.
Testowanie przez testy jednostkowe
Jako, że w tym artykule skupiamy się na C++, więc czemu mamy się ograniczać jedynie do testowania samego wejścia i wyjścia, użyjmy najpopularniejszej biblioteki do testów jednostkowych:
gtest. Poniżej opiszę jak to zrobić.
W niniejszym rozdziale robię coś, czego nie da się zrobić w normalny sposób przy pomocy narzędzia DOMjudge. Nie dość, że pewne funkcjonalności będą zrobione w nieładny sposób, to jeszcze na dodatek przedstawię, gdzie trzeba interweniować w oryginalny kod narzędzia, dlatego jeśli ktoś nie lubi takich praktyk zachęcam do pominięcia tego punktu.
Szybka instalacja gtest
Aby zainstalować gtest należy go pobrać,
najlepiej z oficjalnej strony, następnie wystarczy po rozpakowaniu wejść do katalogu z biblioteką i wykonać następujące komendy:
cmake .
make VERBOSE=1
sudo make install
Możliwości testowania przez testy jednostkowe
Zacznę od przeglądu przykładowych rozwiązań jak to zrobić w opisywanym narzędziu, wraz z wadami poszczególnych rozwiązań:
Ostatnie rozwiązanie wydaje mi się najlepsze ze złych (bo dobrych nie ma), dlatego opiszę jak je zrobić.
Dodawanie własnego języka programowania
Co prawda nie chcemy dodawać nowego języka programowania, a jedynie dodać możliwość kompilowania kodu do biblioteki. Niestety obydwa cele sprowadzają się do tego samego, musimy wykonać te same kroki, jakie wykonalibyśmy dodając kolejny język programowania.
Zanim będziemy chcieli dodać własny język programowania warto dodać narzędzia opisane powyżej, które będą to budować to wszystko.
Co prawda zamiast dodawać drugą wersję kompilowania dla C++ moglibyśmy zmienić domyślny skrypt tak, żeby sprawdzał, czy jest dostarczona funkcja
main()
, jeśli tak to buduje do binarki, w przeciwnym razie do pliku skompilowanego *.o. Sprawdzenie, czy zawieramy funkcję main można zrobić przy użyciu
ctagsów:
ctags -x --c++-types=f tmp.cc | cut -d' ' -f1 | grep main
.
Wykonywanie poszczególnego kroku na własny sposób
Powyżej znajduje się informacja, jak stworzyć paczkę z narzędziami odpalanymi do wykonania któregokolwiek z trzech kroków (
compile lub
run lub
compare) oraz, że ma ona zawierać pliki wykonywalne
build, po których wykonaniu ma istnieć plik wykonywalny
run.
Własny krok kompilacji
Wróćmy do kroku kompilacji (
compile) naszej biblioteki, nasz plik
build nie musi nic robić:
#!/bin/sh
# nothing here to do:)
Plik
run z kolei musi wykonać kompilacje:
#!/bin/bash
FLAGS='-x c++ --std=c++14 -Wall -Wextra -DONLINE_JUDGE -DDOMJUDGE'
function getCppSources
{
for filename; do
file_extention=${filename##*.}
[ ${file_extention:0:1} == "c" ] && echo $filename
done
}
DEST="$1" ; shift
MEMLIMIT="$1" ; shift
CPP_SOURCES=$(getCppSources "$@")
g++ $FLAGS -c -o ${DEST}.o ${CPP_SOURCES}
returnCode="$?"
cp "$DEST".o "$DEST"
exit ${returnCode}
Słowem wyjaśnienia:
Warto uwzględnić fakt, iż co wypiszemy na ekran w pliku build zostanie wyświetlone przez proces wykonujący automatyczne sprawdzenie, natomiast co wypiszemy w pliku run zostanie wypisane jako logi kompilacji dla użytkownika, tym samym wszystkie warningi użytkownik zobaczy.
Tworzenie paczki
Mając już przygotowane pliki możemy utworzyć archiwum zip. Nazwa archiwum jest od razu ID narzędza wykonującego dany krok, którego potem nie można zmienić, a poza tym podlega obostrzeniom dotyczącym ID (znaki alfanumeryczne bez spacji).
Można utorzyć paczkę z poziomu terminala:
zip -r cpp_lib.zip run build
po utworzeniu paczki warto wejść do jej ustawień i zmienić opis paczki (opis jest widoczny dla użytkownika).
Po zrobieniu tej paczki następuje moment, kiedy trzeba ją załadować, wchodzimy więc do
executables i tam klikamy nowy, ładujemy paczkę, następnie ustawiamy opis i do czego dany skrypt służy -w naszym przypadku wybieramy
compile.
Modyfikacja skryptów DomJudge, aby nam darował gdy nie utworzymy plików wykonywalnych
Niestety teraz następuje nieprzyjemny moment, kiedy musimy modyfikować oryginalny kod w narzędziu, a mianowicie ze skryptów wywołujących kompilacje usunąć sprawdzenie, czy po wywołaniu budowania powstał plik wykonywalny (w bashu opcja
-x
).
Zacznijmy od pliku
compile.sh
, który domyślnie będzie znajdował się tutaj:
/usr/local/lib/domjudge/judge/compile.sh
W pliku tym musimy usunąć bashowe sprawdzenie, czy powstał plik wykonywalny (poniżej zacytowałem kod z pliku
compile.sh
):
if [ ! -f compile/program ] || [ ! -x compile/program ]; then
echo "Compiling failed: no executable was created; compiler output:" >compile.out
cat compile.tmp >>compile.out
cleanexit ${E_COMPILER_ERROR:--1}
fi
w taki sposób:
if [ ! -f compile/program ]; then
echo "Compiling failed: no file was created; compiler output:" >compile.out
cat compile.tmp >>compile.out
cleanexit ${E_COMPILER_ERROR:--1}
fi
Sprawdzenie tego odbywa się również w kolejnym skrypcie:
testcase_run.sh
, znajdującym się domyślnie:
/usr/local/lib/domjudge/judge/testcase_run.sh
, z niego musimy usunąć następujące linijki:
[ -x "$WORKDIR/$PROGRAM" ] || error "submission program not found or not executable"
Usunięcie tego typu sprawdzania nie jest bez konsekwencji, gdyż te pliki dotyczą wszystkich języków programowania. Po tej zmianie pozbywamy się jednego z zabezpieczeń, które się jednak przydaje.
Dodawanie języka programowania
Mamy już dodany sposób budowania kodu do biblioteki, więc możemy przejść do dodawania własnego języka programowania. W tym celu wchodzimy z panelu administratora na języki
Languages, naszym oczom ukaże się lista języków, po kliknięciu ikony dodania nowego języka zobaczymy:
Należy mieć na uwadze, że wartość wpisana w "Language name" jest widoczna dla użytkowników.
Własny sposób uruchomienia programu
Uruchomienia, a mamy na razie jedynie bibliotekę. Podobnie jak wcześniej przygotowujemy pliki
run i
build, oraz dodatkowo musimy dodać testy do paczki oraz inne konieczne pliki do skompilowania testów. Dla naszej silni będzie to plik z przykładowymi testami silni
factorian_test.cc
o treści:
#include "factorian.h"
#include <gtest/gtest.h>
using namespace::testing;
TEST( factorians, testZero )
{
ASSERT_EQ( 1, factorian( 0 ) );
}
TEST( factorians, testGreatNumber )
{
ASSERT_EQ( 2432902008176640000LLU, factorian( 20 ) );
}
int main( int argc, char * argv[ ] )
{
::testing::InitGoogleTest( & argc, argv );
GTEST_FLAG( print_time ) = false;
return RUN_ALL_TESTS();
}
Dlaczego stosuję tę flagę
GTEST_FLAG( print_time )
wyjaśnię później, obecnie potrzebujemy jeszcze pliku nagłówkowego
factorian.h
, przykładowo:
#ifndef FACTORIAN_H
#define FACTORIAN_H
#include <cstdint>
typedef std::uint_fast64_t T;
T factorian( T value );
#endif
Plik
build, który będzie u mnie skryptem bashowym będzie budował testy do postaci skompilowanej:
#!/bin/bash
FLAGS='-x c++ --std=c++14 -Wall -Wextra -DONLINE_JUDGE -DDOMJUDGE'
g++ $FLAGS factorian_test.cc -c -o tests.o
exit $?
Wreszcie plik
run, który dokona linkowania skompilowanego kodu i testów do postaci programu, po czym go uruchomi:
#!/bin/bash
LINK='-pthread -lgtest -lgtest_main'
TESTIN="$1"; shift
PROGOUT="$1"; shift
programPath="${@: -1}" # last script argument is executable path
programPathAbsolute=$(readlink -f ..${programPath})
testLibraryAbsolutePath=$(readlink -f ../../executable/cpp_lib_run/tests.o)
g++ --std=c++14 ${testLibraryAbsolutePath} "${programPathAbsolute}".o -o "${programPathAbsolute}" ${LINK} # linking
rm "${programPathAbsolute}".o
exec "$@" < "$TESTIN" > "$PROGOUT"
Teraz parę słów komentarza odnośnie powyższego skryptu:
Krok porównywania wyjścia programu z oczekiwanym wyjściem
Jako output można użyć output wygenerowany przez testy w sytuacji, gdy takowe przejdą. W naszym przypadku będzie podobny do (różnice mogą być w czasie wykonywania poszczególnych funkcji):
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from factorians
[ RUN ] factorians.testZero
[ OK ] factorians.testZero
[ RUN ] factorians.testGreatNumber
[ OK ] factorians.testGreatNumber
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[ PASSED ] 2 tests.
Dzięki temu domyślny sposób porównywania wydruku z programu powinien nam wystarczyć do weryfikacji testu, aczkolwiek lepiej mieć własny do parsowania wydruku w bardziej wyrafinowanych przypadkach. Pomocna może być wtedy zmiana domyślnego sposobu wyświetlania na (do wyboru):
--gtest_output=xml
lub
--gtest_output=json
.
Co prawda wygodnym jest stosowanie argumentów sterujących gtestem takie jak powyższe, niestety jeśli zamienilibyśmy spobób uruchamiania programu ze skryptu
run z:
exec "$@" < "$TESTIN" > "$PROGOUT"
na np.
exec "$@ --gtest_output=json" < "$TESTIN" > "$PROGOUT"
, to pojawiają się błędy z poziomu narzędzia uruchamiającego nasz program
runguard, dlatego takie ustawienia należałoby niestety zrobić z poziomu wnętrza programu, przynajmniej do czasu, aż twórcy narzędzia nie naprawią tej niedogodności, która pachnie błędem.
Implementacja funkcji przy użyciu C++14 nastawiona na szybkość
Plik
#include "factorian.h"
znajduje się w podrozdziale
Własny sposób uruchomienia programu i zawiera
typedef std::uint_fast64_t T;
.
#include "factorian.h"
constexpr T FACTORIAN_LIMIT = 22; T factorianCompileTime[ FACTORIAN_LIMIT ];
template < T N >
struct Factorian
{
constexpr static T value = N * Factorian < N - 1 >::value;
constexpr Factorian() noexcept
{
factorianCompileTime[ N ] = value;
Factorian < N - 1 >();
}
};
template < >
struct Factorian < 0 >
{
constexpr static T value = 1;
constexpr Factorian() noexcept
{
factorianCompileTime[ 0 ] = value;
}
};
T factorian( T value )
{
constexpr static Factorian < FACTORIAN_LIMIT - 1 > factorianInitializer;
static_cast < T >( factorianInitializer ); return factorianCompileTime[ value ];
}
Funkcje dodatkowe
Zasadniczo do użycia narzędzia wystarczy to co powyżej, niemniej jednak narzędzie posiada naprawdę multum funkcji, dlatego opiszę jeszczę parę z nich.
Ustawienia zaawansowane
Ustawień zaawansowanych jest bardzo wiele, na uwagę zasługuje możliwość włączenia samodzielnej rejestracji użytkowników, ale jest wiele innych opcji, o których istnieniu warto wiedzieć. Okno ustawień widać poniżej:
Autentykacja użytkowników
Na ten problem się natknęliśmy wcześniej, ale możliwości jest znacznie więcej:
Wysyłanie zadań z poziomu wiersza poleceń i przy użyciu api
Użycie API bez autoryzacji
Mamy dostępne API pod adresem:
{serwer domjudge}/api
w moim przypadku (domyślnie) jest to adres:
localhost/domjudge/api/
Gdy wejdziemy w powyższy link zobaczymy pełną listę dostępnych komend, możemy wysyłać przy użyciu POST i GET różne zapytania i żądania, do tego celu użyję
narzędzie curl. Do pobrania pewnych informacji nie potrzebujemy uprawnień (
Required role), ale są takie, do których już potrzebujemy.
Przykładowo listę konkursów i języków można pobrać następującymi komendami, poniżej których jest przykładowy output w JSONie (wszak "
nie zadziała jak nie ma JSONa"):
$ curl -X GET localhost/domjudge/api/contests
{"2":{"id":2,"shortname":"demo","name":"Demo contest","start":1514804400,"freeze":null,"end":1546340400,"length":31536000,"unfreeze":null,"penalty":1200},"3":{"id":3,"shortname":"przyklad","name":"Przyk\u0142adowy konkurs","start":1531059120,"freeze":null,"end":1562616720,"length":31557600,"unfreeze":null,"penalty":1200}}
$ curl -X GET localhost/domjudge/api/languages
[{"id":"c","name":"C","extensions":["c"],"allow_judge":true,"time_factor":1},{"id":"cpp","name":"C++","extensions":["cpp","cc","c++"],"allow_judge":true,"time_factor":1},{"id":"cpp_lib","name":"cpp library","extensions":["cpp","cc","c++"],"allow_judge":true,"time_factor":1},{"id":"java","name":"Java","extensions":["java"],"allow_judge":true,"time_factor":1}]grzegorz@ubuntu:~/Pulpit/silnia_gtest$
Możliwości API jest bardzo dużo, można sprawdzać wszystko, co publicznie dostępne, zwracając jeszcze pewne dodatkowe informacje typu IDki, pełne api można podejrzeć
{serwer domjudge}/api
, a jeśli ktoś chce przed zainstalowaniem tego narzędzia, można przejrzeć i przetestować API na oficjalnej stronie
w wersji interaktywnej.
Użycie API z uprawnieniami
Po zalogowaniu przez HTTP/HTTPS i mając aktywną sesję pewne dodatkowe funkcje API będą dostępne zależnie od posiadanych uprawnień. Poza tym, jeśli użytkownik ma podany adres IP w ustawieniach konta to automatycznie API dokona jego autoryzacji.
Poza tym na szczęście jest wygodniejszy sposób, gdy nie mamy ustawionego IP -utworzenie odpowiedniego pliku
~/.netrc
o przykładowej treści:
machine adres.serwera.pl login uzytkownik password mojeHaslo
W tym pliku podajemy tylko adres serwera, czyli jak mamy przykładowo:
localhost/domjudge
to podajemy tylko
localhost
U mnie dla użytkownika
Najlepszy wygląda tak:
machine localhost login Najlepszy password pass123
Mając ten plik możemy przy pomocy curl użyć tego pliki z domyślnej (opcja
--netrc
) lub innej lokalizacji (
--netrc-file plik
):
curl --netrc -F "shortname=nazwaProblemu" -F "langid=nazwaJezykaProgramowania" -F "contest=nazwaKonkursu" -F "code[]=@plik1.cc" -F "code[]=@plik2.cc" ... http://{server}/api/submissions
Jako odpowiedź, w przypadku braku błędu, otrzymamy ID dla tego wysłania.
W moim przypadku, wraz z uwzględnieniem danych zwróconych przez api takich jak dostępne języki programowania i dostępne konkursy mogę wysłać w następujący sposób:
curl --netrc -F "shortname=silnia" -F "langid=cpp_lib" -F "contest=przyklad" -F "code[]=@factorian.cc" -F "code[]=@factorian.h" http://localhost/domjudge/api/submissions
Lepszy spobów wysyłania zadań przez konsolę -sublitclient
Stosując API do wysyłania zadań musimy niestety pobrać pewne informacje przed wysłaniem zadania, jest i na to sposób -wbudowane narzędzie do wysyłania:
submitclient. Aby je zbudować musimy po pobraniu DOMjudge wywołać:
make submitclient
, następnie po wejściu do katalogu:
submit
zobaczymy zbudowane narzędzie
submit.
Możemy go użyć podając odpowiednie argumenty, których listę zobaczymy używając argumentu
--help
. Aby wysłać zadanie musimy napisać przykładowo:
./submit --url='http://localhost/domjudge/' --contest=przyklad --language=cpp --problem=silnia --verbose ~/Pulpit/silnia_gtest/factorian.h ~/Pulpit/silnia_gtest/factorian.cc
w odpowiedzi otrzymamy coś podobnego do:
[Jul 12 13:19:59.193] submit[6623]: connecting to http://localhost/domjudge/api/languages
[Jul 12 13:19:59.208] submit[6623]: connecting to http://localhost/domjudge/api/contests
Submission information:
filenames: /home/grzegorz/Pulpit/silnia_gtest/factorian.h /home/grzegorz/Pulpit/silnia_gtest/factorian.cc
contest: przyklad
problem: silnia
language: cpp library
url: http://localhost/domjudge/
There are warnings for this submission!
Do you want to continue? (y/n)
[Jul 12 13:20:01.605] submit[6623]: connecting to http://localhost/domjudge/api/submissions
[Jul 12 13:20:01.774] submit[6623]: Submission received, id = s43
Może być jeszcze prościej: po dodaniu ścieżki z naszym
submit do zmiennej środowiskowej
PATH
możemy pewne, stałe elementy wpisać do pliku
submit_wrapper.sh, który znajduje się w tym samym katalogu, zaoszczędzimy sobie tym sposobem pisania, gdyż
submit_wrapper.sh woła
submit, który z kolei korzysta z API.
Porównywanie wyjścia programu przy użyciu gramatyki
Jeśli domyślny sposób porównywania standardowego wyjścia programu z oczekiwanym standardowym wyjściem nam nie odpowiada i nie chcemy do każdego z zadań pisać oddzielnego programiku do parsowania i porównywania twórcy DOMjudge napisali w innym repozytorium pomocne narzędzie
do weryfikacji przy użyciu gramatyk.
Balony -alternatywny sposób powiadomień
Jeśli chcemy możemy skonfigurować alternatywny sposób powiadomień dla sędziów, może to być dowolna komenda, którą konfigurujemy w pliku
domserver-config.php
, domyślnie znajdującym się
/usr/local/etc/domjudge/domserver-config.php
, może to być np. wysyłanie maili, w tym celu zmieniamy treść stałej
define('BALLOON_CMD', '');
wstawiając tam komendę, która ma zostać zawołana celem powiadomienia sędziów.
Poza tym musimy jeszcze uruchomić proces, który będzie się zajmował powiadomieniami, domyślnie będzie to:
/usr/local/bin/balloons
Komunikacja między uczestnikiem a sędziami
Każdy uczestnik po wysłaniu zadania może na stronie głównej kliknąć:
request clarification, następnie pojawi się okienko w którym można napisać do sędziego:
Jeden z sędziów na to może odpisać dla całego zespołu lub nawet do wszystkich uczestników. Sędziowie mają możliwość przypisania się do sprawdzenia danego problemu oraz zaznaczenia, że udzielono odpowiedzi, odpowiedź pojawi się u uczestnika:
Nieprzeczytana wiadomość od sędziów będzie pogrubioną czcionką.
Bezpieczeństwo wykonywania
Co jeśli ktoś z uczestników konkursu wyśle zadanie zawierające przykładowo:
system( "rm -rf ~" );
? Otóż nic się nie stanie, gdyż każdy z programów jest uruchamiany w "piaskownicy" przez program
runguard.c
, którego kod można podejrzeć w katalogu
{domjudge}/judge/runguard.c
, program ten izoluje od plików systemowych. Jeśli jednak chcielibyśmy mieć jeszcze większe bezpieczeństwo, jest dla
dockera utworzony
specjalny obraz, dzięki któremu można całkowicie oddzielić narzędzie DOMjudge od naszego systemu.
Bibliografia