Validační server - automatická kontrola semestrálních prací

Případová studie - Předmět KIV/PPA1

Zpět na hlavní stránku

Na ukázku konfigurace a naprogramování validačního procesu zde uvádím příklad validace průběžných příkladů odevzdávaných studenty předmětu KIV/PPA1 (Počítače a programování - základní předmět, výuka Javy) na Západočeské univerzitě v Plzni.

Předpokládáme, že validační server běží na stroji vs.kiv.zcu.cz.

Je k dispozici aktualizovaný dokument "návod pro práci s validátorem pro učitele předmětu". Najdete tam mimo jiné rozebraný problém s analýzou výstupů programů.

Základní informace o předmětu

Zdroj této kapitoly: WWW stránky předmětu KIV/PPA1.

Základní informace

Jedná se o relativně krátké programy, které se tematicky váží k látce probírané na příslušném cvičení. Jsou součástí většiny cvičení. Odevzdávají se pouze elektronicky pomocí Portálu a jsou okamžitě vyhodnoceny pomocí validátoru.

Smysl domácích úloh je postupně vést studenty k řešení stále složitějších programů. Proto je důležité domácí úkoly vypracovávat průběžně.

Princip

Studentský program musí mít totožný výstup, jako referenční program připravený vyučujícím. Shodnost výstupů studentova a referenčního programu kontroluje tzv. "validační server", který je automaticky aktivován ihned po elektronickém odevzdání studentova programu.

Validační server ohlásí výsledek validace a v případě, že je neúspěšný, poskytne i chybové hlášení. Student se tak může pokoušet odevzdávat svůj program tak dlouho, dokud nebude úspěšný. Neúspěšné pokusy se nepočítají.

Student má k dispozici referenční program (.class soubor) a také ukázky typických výstupů referenčního programu.

Výhody a nevýhody

Výhody:

Nevýhody:

Detailní informace o odevzdávání

Studenti mají dále k dispozici tuto stránku s informacemi. Popisuje přesně způsob odevzdání, pojmenování souborů atd. Doporučuji přečíst alespoň zběžně, než budete pokračovat ve čtení této případové studie.

Konfigurace domény PPA1

Ve validačním serveru je pro účely tohoto předmětu doména "ppa1", nahrána samozřejmě v adresáři "data/domains/ppa1". Konfigurační soubor domény vypadá následovně:

<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
 
<properties version="1.0">
 
    <comment>Validacni domena pro predmet PPA1</comment>
 
    <!-- ucuje, zda je zapnut 'debug' rezim, tj. zda mj. prochazi kompletni stacktrace do vystupnich html -->
    <entry key="core.debug">0</entry>
 
    <!-- zda po sobe uklizet docasne soubory - pro ladeni. 1/0 -->
    <entry key="core.do_cleanup">0</entry>
 
    <!-- pokud je core.concurrency_disabled = 1, pak je zakazan jakykoliv paralelismus,
     tj. ulohy validace se spousteji za sebou -->
    <entry key="core.concurrency_disabled">0</entry>
    
 
    <!-- maximalni velikost odevzdaneho souboru v KB -->
    <entry key="max_file_size">50</entry>
 
    <!-- seznam pripon souboru, ktere se akceptuji. Pokud neni uvedeno nebo obsahuje znak '*',
         akceptuji se vsechny soubory -->
    <entry key="accept_extensions">java</entry>
 
    <!-- maxilani doba celeho procesu validace v teto domene sekundach.
     Default hodnota je 30 vterin, neni-li uvedena -->
    <entry key="max_validation_time">300</entry>
 
    <!-- nazev souboru s lokalizacnimi hlaskami pro tuhle domenu. Cesta je relativne
    vuci adresari domeny! -->
    <entry key="resource_bundle">nl.ppa1</entry>
 
    <!-- nazev souboru s java policy nastavenim. Vyuzije se v pripade, kdy se spousti Java tridy.
    Pote je tenhle soubor predan noev spustenemu JVM jako parametr a z nej se nactou bezpecnostni opatreni.
     Cesta je relativne k adreasri domeny.-->
    <entry key="java_policy_file">run.policy</entry>
 
    <!-- Jake vsechny vystupy validace generovat -->
    <entry key="custom.output.classes">cz.zcu.validationserver.output.HTMLResultOutput;
    cz.zcu.validationserver.output.XMLResultOutput;
    cz.zcu.validationserver.output.PPA1MySQLOutput</entry>
 
    <!-- jake generovat typy vystupu: moznosti: 'file', 'string' ci oboji oddelene carkou -->
    <entry key="html_output_type">file</entry>
 
    <!-- zde musi byt cesta k html adresari, kam se ukladaji vysledne html soubory. Tato
     cesta se nebere relativne vuci data adresari! -->
    <entry key="html_output_dir">data/html</entry>
 
    <!-- zacatek URL k http serveru, pres ktery budou vysledky pristupne -->
    <entry key="html_output_url">http://localhost/vs/</entry>
 
    <!-- url na css styl pro exportovane html soubory -->
    <entry key="html_css_url">/validator/vs.css</entry>
 
    <!-- zde je cesta k souboru ve formatu XML s jednoduchym "logem" vysledku. Je
     to soubor, ktery potrebuje Pavel Herout pro svoje statistiky -->
    <entry key="xml_result_file">data/results.xml</entry>
 
    <!-- Konfiguracni udaje pro vystup do MySQL pro PPA1 -->
    <entry key="output.mysql.server_name">vs.kiv.zcu.cz</entry>
    <entry key="output.mysql.database">validator</entry>
    <entry key="output.mysql.user_name">validator</entry>
    <entry key="output.mysql.password">HESLO</entry>
 
    <!-- kde jsou ulozeny vzorove soubory pro PPA1  -->
    <entry key="ppa1_vzory_dir">data/domains/ppa1/vzory</entry>
 
</properties>
 

Pro podrobný popis jednotlivých možností viz konfigurace domény a konfigurace serveru.

Validační server tedy nyní ví, že má generovat HTML soubory s výsledky validace a ty ukládat do adresáře /var/www/validator/ppa1, ví kde je uložen css styl pro tyto soubory, ví, přes jakou URL budou soubory dostupné (http://vs.kiv.zcu.cz/validator/ppa1/). Akceptuje pouze soubory s příponou ".java" a maximální možná doba trvání validace je omezena na 15 vteřin.

V adresáři "data/domains/ppa1/nl" je uložen soubor "ppa1.properties". Jedná se o klasický soubor pro ResourceBundle, tedy soubor s lokalizačními hláškami. Bylo-li by potřeba lokalizovat například do angličtiny, stačí dodat soubor "ppa1_en.properties" atd.

ppa1.prekrocil_compile=Vámi zaslaný soubor se překládal déle, než je povoleno. Buď nastala \
  nějaká zvláštní chyba překladače nebo je validační server zaneprázdněný. Zkuste, prosím, \
  poslat po chvíli znovu.
ppa1.prekrocil_spousteni=Vámi zaslaný soubor běžel mnohem déle, než by měl. Zjistěte, zda \
  se v něm neskrývá například nějaký nekonečný cyklus.
ppa1.nelze_prelozit_vzor=Interní chyba serveru, respektive zadávajících - nelze přeložit\
   vzorový příklad
ppa1.neni_podtrzitko=Špatný název odevzdaného souboru - neobsahuje žádné podtržítko
ppa1.chybny_autor=Autor uvedený v názvu souboru (za posledním podtržítkem) nesouhlasí \
  s uživatelem přihlášeným do portálu
ppa1.nejmenovat_vzor=Soubor se nesmí jmenovat 'VZOR'!
ppa1.neexistuje_vzor=Neexistuje vzorová třída, podle které by bylo možné program \
  zkontrolovat. Zkontrolujte si, prosím, název Vámi odevzdané třídy, zda vyhovuje \
  domluveným pravidlům (dejte pozor například i na velikost písmen v názvu souboru - hraje to roli)

Dále je uveden odkaz na soubor "run.policy", který bude využit při spouštění Javovských programů. Jediné, co budou muset studenti ve svých pracích používat řekněme trochu nebezpečného, je přístup k souborům. Proto jim přístup omezíme pouze na adresář, odkud budou soubory číst (a pouze číst). Další informace viz kapitola o zabezpečení:

// Policy soubor pro spousteni vzorovych prikladu
// umoznuje cteni/zapis z/do adresare domeny a vsech podadresaru

// co muze vzorova trida
grant codeBase "file:${validator.classbasedir}/-" {

	// cteni z domain adresare - budou tam pripadne nejake vstupni soubory
	permission java.io.FilePermission "${validator.domaindir}/-", "read";

	// dalsi "default" opravneni
	permission java.util.PropertyPermission "java.version", "read";
	permission java.util.PropertyPermission "java.vendor", "read";
	permission java.util.PropertyPermission "java.vendor.url", "read";
	permission java.util.PropertyPermission "java.class.version", "read";
	permission java.util.PropertyPermission "os.name", "read";
	permission java.util.PropertyPermission "os.version", "read";
	permission java.util.PropertyPermission "os.arch", "read";
	permission java.util.PropertyPermission "file.separator", "read";
	permission java.util.PropertyPermission "path.separator", "read";
	permission java.util.PropertyPermission "line.separator", "read";

	permission java.util.PropertyPermission "java.specification.version", "read";
	permission java.util.PropertyPermission "java.specification.vendor", "read";
	permission java.util.PropertyPermission "java.specification.name", "read";

	permission java.util.PropertyPermission "java.vm.specification.version", "read";
	permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
	permission java.util.PropertyPermission "java.vm.specification.name", "read";
	permission java.util.PropertyPermission "java.vm.version", "read";
	permission java.util.PropertyPermission "java.vm.vendor", "read";
	permission java.util.PropertyPermission "java.vm.name", "read";
};

Pouze podotýkám, že všechny PropertyPermission jsou vlastně také pro tento případ zbytečné, studenti je číst nepotřebují.

Validační proces

Následuje seznam všech souborů, které obsahují script validačního procesu. Začneme souborem "data/domains/ppa1/process.xml":

<?xml version="1.0" ?>
<!DOCTYPE validation-process PUBLIC "-//Validation-Process //" "process.dtd">
 
<validation-process>
 
    <!-- volani podprocesu, ktery zkontroluje, zda se jedna o Java zdrojak -->
    <call process="/common/checkjavasource.xml">
        <param name="file">inputFile</param>
    </call>
 
    <!-- inicializace PPAcek - tedy urceni nazvu souboru, trid atd. -->
    <call process="init.xml"/>
 
    <!-- kompilace vzoroveho programu, pokud uz neni zkompilovan -->
    <cache>
        <file>
            vzorClass
        </file>
        <observed-file>
            vzorFile
        </observed-file>
        <put>
 
            <!-- prelozim vzorovy suobor -->
            <call process="/common/compilejava.xml">
                <param name="file">vzorFile</param>
            </call>
 
            <if>
                <condition>
                    ! validationResult.isStillOK()
                </condition>
                <then>
                    <error key="ppa1.nelze_prelozit_vzor" code="SERVER_ERROR"/>
                    <quit/>
                </then>
            </if>
 
        </put>
    </cache>
 
    <time-limited time="5000" exceed-message="ppa1.prekrocil_compile">
        <!-- prelozim zaslany soubor -->
        <call process="/common/compilejava.xml">
            <param name="file">inputFile</param>
        </call>
    </time-limited>
 
    <quit-if-error/>
 
    <script>
        var index = 1;
    </script>
 
    <time-limited time="5000" exceed-message="ppa1.prekrocil_spousteni">
        <!-- telo cyklu se opakuje dokud plati 'condition', pripadne dokud se nezavola prikaz <break> -->
        <while>
            <!-- dokud existuje soubor .in<index> -->
            <condition>
                inFile = new java.io.File(domainDir,'vzory/'+prikladIdent+'.in'+index);
                inFile.exists()
            </condition>
            <body>
                <!-- nejdriv zjistim vystup vzoroveho prikladu -->
                <script>
                    vzorOutput = new java.io.File(vzoryDir,'out/'+vzorClassName+'.out'+index);
                    (new java.io.File(workDir,'out').mkdirs());
                    testOutput = new java.io.File(workDir,'out/'+className+'.out'+index);
                </script>
 
                <cache>
                    <file>vzorOutput</file>
                    <observed-file>vzorFile</observed-file>
                    <observed-file>inFile</observed-file>
                    <put>
                        <!-- spustim vzorovou tridu -->
                        <call process="/common/invokejava.xml">
                            <param name="mainClass">vzorClassName</param>
                            <param name="classPath">vzoryDir</param>
                            <param name="stdIn">inFile</param>
                            <param name="stdOut">vzorOutput</param>
                            <param name="stdErr">null</param>
                            <param name="args">null</param>
                        </call>
                    </put>
                </cache>
 
                <!-- nyni zjistim vystup testovane tridy -->
                <call process="/common/invokejava.xml">
                    <param name="mainClass">className</param>
                    <param name="classPath">workDir</param>
                    <param name="stdIn">inFile</param>
                    <param name="stdOut">testOutput</param>
                    <param name="stdErr">testOutput</param>
                    <param name="args">null</param>
                </call>
 
		<!-- tyto vystupy porovnam -->
                <call process="/common/comparefiles.xml">
                    <param name="file1">vzorOutput</param>
                    <param name="file2">testOutput</param>
                </call>
 
                <script>
                    index++;
                </script>
 
                <quit-if-error/>
 
            </body>
        </while>
    </time-limited>
 
</validation-process>
        
 

Následuje soubor "init.xml", který se volaný z předchozího scriptu:

<?xml version="1.0" ?>
<!DOCTYPE validation-process PUBLIC "-//Validation-Process //" "process.dtd">
 
<!-- inicializace PPA1 -->
<validation-process>
 
    <!-- inicializace -->
    <script>
        inputFileName = inputFile.getName();
 
        className = inputFileName;
        /* pokud to obsahuje tecku, tak vsechno za ni vcetne ni vyhodim */
        var tecka = className.indexOf('.');
        if (tecka != -1) {
            className = className.substring(0, tecka);
        }
 
        /* najdu posledni podtrzitko */
        var lastDelim = className.lastIndexOf('_');
    </script>
 
    <if>
        <condition>lastDelim == -1</condition>
        <then>
            <error key="ppa1.neni_podtrzitko" code="INVALID_INPUT"/>
            <quit/>
        </then>
    </if>
 
    <script>
        /* rozdelim nazev souboru na identifikator prikladu a jmeno autora */
        prikladIdent = className.substring(0, lastDelim);
        autorIdent = className.substring(lastDelim + 1, className.length());

        /* ulozim identifikator prikladu do resultu */
        validationResult.addCustomResult("priklad", prikladIdent);
    </script>
 
    <!-- souhlasi autor v nazvu souboru s autorem co prisel z portalu? -->
    <if>
        <condition> (!autor.toUpperCase().equals("CLIENT")) &amp;&amp; 
	(! autorIdent.toUpperCase().equals(autor.toUpperCase()) )</condition>
        <then>
            <error key="ppa1.chybny_autor" code="INVALID_INPUT"/>
            <quit/>
        </then>
    </if>
 
    <!-- autor se urcite nesmi jmenovat VZOR -->
    <if>
        <condition>autorIdent.toUpperCase().equals("VZOR")</condition>
        <then>
            <error key="ppa1.nejmenovat_vzor" code="INVALID_INPUT"/>
            <quit/>
        </then>
    </if>
 
    <!-- inicializace plno promennych -->
    <script>
        str = domain.getDomainProperties().getProperty("ppa1_vzory_dir");
        vzoryDir = new java.io.File(str);
        vzorClassName = prikladIdent+'_Vzor';
        vzorFile = new java.io.File(vzoryDir,vzorClassName+'.java');
        vzorClass = new java.io.File(vzoryDir,vzorClassName+'.class');
 
        var vzorOut = new java.io.File(vzoryDir, "out");
        vzorOut.mkdirs();
    </script>
 
    <!-- Odevzdava priklad, pro ktery existuje vzor? -->
    <if>
        <condition>! vzorFile.exists()</condition>
        <then>
            <error key="ppa1.neexistuje_vzor" code="CUSTOM_ERROR"/>
            <quit/>
        </then>
    </if>
 
</validation-process>
        
 

Zpět na hlavní stránku

autor: Lukáš Valenta, září 2007 (lukas.valenta at seznam.cz)