HazifeladatEllenorzoTeacher
(→Mappaszerkezet) |
(→Feladattípusok) |
||
(egy szerkesztő 19 közbeeső változata nincs mutatva) | |||
8. sor: | 8. sor: | ||
Ide be lehet ssh-zni, scp-zni vagy a webmail-jébe belépni. | Ide be lehet ssh-zni, scp-zni vagy a webmail-jébe belépni. | ||
+ | |||
+ | == Honlap == | ||
+ | Mivel a <tt>hazi</tt> egy sima felhasználó a leibniz-en, van honlapja is: | ||
+ | |||
+ | http://math.bme.hu/~hazi | ||
+ | |||
+ | Ide lehet közérdekű (publikus) infókat kitenni, de lehet jelszóval védett al-oldalakat is csinálni, mondjuk azok biztonsága elég enyhe. | ||
+ | |||
+ | Érdekesség, hogy vannak [http://math.bme.hu/~hazi/pulse terhelési grafikonok] is. | ||
= Mappaszerkezet = | = Mappaszerkezet = | ||
42. sor: | 51. sor: | ||
= Mit csináljunk = | = Mit csináljunk = | ||
== Hallgatók kezelése == | == Hallgatók kezelése == | ||
− | Ezt lényegében | + | Ezt lényegében egy <tt>hazijavitorendszer/HW/*.tsv</tt> (tab-separated-values) fájl szerkesztésével tehetjük meg. |
Formátuma: | Formátuma: | ||
49. sor: | 58. sor: | ||
borbely@math.bme.hu Gábor Borbély info2,lecturer | borbely@math.bme.hu Gábor Borbély info2,lecturer | ||
+ | * Több ilyen fájl is lehet, ekkor a rendszer lényegében uniózza ezen fájlokat. | ||
* Minden sora egy felhasználó | * Minden sora egy felhasználó | ||
* A felhasználók az email-címükkel vannak azonosítva, a nevük csak tájékoztató jellegű. | * A felhasználók az email-címükkel vannak azonosítva, a nevük csak tájékoztató jellegű. | ||
60. sor: | 70. sor: | ||
** de ha valaki másodszorra hallgatja az info2-t, akkor lehet két kurzusa: <tt>info2_2019,info2_2020</tt> | ** de ha valaki másodszorra hallgatja az info2-t, akkor lehet két kurzusa: <tt>info2_2019,info2_2020</tt> | ||
− | Ha a <tt> | + | Ha a felhasználók <tt>.tsv</tt> fájlját változtatjuk, akkor a módosítások csak akkor jutnak érvényre, ha [[#build|újra <tt>build</tt>-eljük a <tt>docker image</tt>-et]] |
== Feladatok felvétele == | == Feladatok felvétele == | ||
170. sor: | 180. sor: | ||
Ha egyedi tesztelő feltételt akarunk megadni, akkor egy <tt>test.py</tt> fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk: | Ha egyedi tesztelő feltételt akarunk megadni, akkor egy <tt>test.py</tt> fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk: | ||
+ | <python> | ||
def _eval(_input, stdout, stderr, returncode): | def _eval(_input, stdout, stderr, returncode): | ||
− | + | </python> | |
A függvénynek <tt>True</tt> vagy <tt>False</tt>-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. | A függvénynek <tt>True</tt> vagy <tt>False</tt>-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. | ||
Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga '''Server error!''' hibaüzenettel. | Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga '''Server error!''' hibaüzenettel. | ||
197. sor: | 208. sor: | ||
* Egy teszt egy pickle-özött lista, ami az előre (binárisan) lementett bemeneti argumentumok listája. | * Egy teszt egy pickle-özött lista, ami az előre (binárisan) lementett bemeneti argumentumok listája. | ||
* Ezt egy '''<tt>i</tt>''' betűvel kezdődő nevű <tt>.pkl</tt> kiterjesztésű fájlba kell megadni a feladat mappájában. | * Ezt egy '''<tt>i</tt>''' betűvel kezdődő nevű <tt>.pkl</tt> kiterjesztésű fájlba kell megadni a feladat mappájában. | ||
+ | A pickle fájlok előállításához [[#pickle|segédlet itt]]. | ||
Például, ha a <tt>descartes_szorzat</tt> függvényt akarom feladni, ami listák Descartes szorzatát adja vissza, akkor ez lenne a szignatúrája: | Például, ha a <tt>descartes_szorzat</tt> függvényt akarom feladni, ami listák Descartes szorzatát adja vissza, akkor ez lenne a szignatúrája: | ||
− | + | <python> | |
− | + | def descartes_szorzat(x, y): | |
− | + | # return the list of pairs of x and y | |
− | + | </python> | |
És egy tesz fájlt így lehetne előállítani: | És egy tesz fájlt így lehetne előállítani: | ||
208. sor: | 220. sor: | ||
Ekkor az alábbi hívódik meg | Ekkor az alábbi hívódik meg | ||
− | + | <python> | |
descartes_szorzat([1, 2], ['a', 'b']) | descartes_szorzat([1, 2], ['a', 'b']) | ||
− | + | </python> | |
Az ehhez tartozó kimenet pedig szintén egy pickle fájl, aminek a neve '''<tt>o</tt>''' betűvel kezdődik, a nevének többi része pedig megegyezik a hozzá tartozó bemenetével. | Az ehhez tartozó kimenet pedig szintén egy pickle fájl, aminek a neve '''<tt>o</tt>''' betűvel kezdődik, a nevének többi része pedig megegyezik a hozzá tartozó bemenetével. | ||
216. sor: | 228. sor: | ||
Ha egyedi tesztelő feltételt akarunk megadni, akkor egy <tt>test.py</tt> fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk: | Ha egyedi tesztelő feltételt akarunk megadni, akkor egy <tt>test.py</tt> fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk: | ||
− | + | <python> | |
def _eval(_input, _output, _expected_output, _exception, _expected_exception): | def _eval(_input, _output, _expected_output, _exception, _expected_exception): | ||
− | + | </python> | |
A függvénynek <tt>True</tt> vagy <tt>False</tt>-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. | A függvénynek <tt>True</tt> vagy <tt>False</tt>-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. | ||
Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga '''Server error!''' hibaüzenettel. | Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga '''Server error!''' hibaüzenettel. | ||
234. sor: | 246. sor: | ||
Ha új tesztesetet veszünk fel, vagy régit módosítunk vagy törlünk, akkor a módosítások csak akkor jutnak érvényre, ha [[#build|újra <tt>build</tt>-eljük a <tt>docker image</tt>-et]] | Ha új tesztesetet veszünk fel, vagy régit módosítunk vagy törlünk, akkor a módosítások csak akkor jutnak érvényre, ha [[#build|újra <tt>build</tt>-eljük a <tt>docker image</tt>-et]] | ||
+ | |||
+ | === program === | ||
+ | Általában bármilyen parancssorból hívható programot tesztelhetünk ezzel. Akkor is ha interpretált vagy ha fordított nyelven van írva. Persze ehhez installálva kell legyen a szükséges fordító és/vagy interpreter a Docker image-ben. | ||
+ | |||
+ | Akkor használjunk ilyen faladattípust, ha | ||
+ | * azt akarjuk, hogy a beküldés egy interpreter által futtatott vagy fordító által fordított fájl legyen, ami parancssorból működik | ||
+ | |||
+ | Ahhoz hogy egy ilyen programot teszteljünk, az alábbiakat kell megadnunk a <tt>manifest.json</tt> fájlban: | ||
+ | * <tt>"type": "program"</tt> | ||
+ | * <tt>"compile"</tt> ennek az értéke egyetlen parancs, vagy listában megadott parancs plusz paraméterek lehetnek | ||
+ | ** ez fog lefutni a beküldött program előtt | ||
+ | ** például: <tt>["gcc", "-o", "myprogram", "myprogram.c"]</tt> | ||
+ | ** ha ez nincsen megadva, vagy üres lista van megadva, akkor nincsen fordítási lépés. | ||
+ | * <tt>"command"</tt> ez fog lefutni tesztenként | ||
+ | ** lehet egy string vagy string-ek listája | ||
+ | ** például: <tt>"./myprogram"</tt>, ha előzőleg lefordítottuk | ||
+ | ** vagy <tt>["python3", "fahrenheit.py"]</tt> | ||
+ | ** vagy <tt>["wolfram", "-script", "calculate.m"]</tt> | ||
+ | * Ezek a parancsok mind a beküldő felhasználójának home-mappájában (<tt>/home/dummy</tt>) fognak lefutni és a <tt>dummy</tt> felhasználó nevében (és jogosultságaival). | ||
+ | * Ezen kívül a szokásos description, deadline, visible és course mezők is lehetnek. | ||
+ | |||
+ | Egy teszt fájlban (<tt>i*.json</tt>) a következőket lehet megadni: | ||
+ | * bemenet | ||
+ | ** argv | ||
+ | *** például: <tt>"argv": ["a", "-h", "file.txt"]</tt> | ||
+ | *** ezek a futtatandó parancs mögé append-álódnak | ||
+ | ** bemeneti fájlok listája (amiket olvashat a beküldő program) | ||
+ | *** ezek odamásolódnak a beküldött program mellé a teszt előtt. | ||
+ | ** stdin, mit kapjon a standard bemeneten, egy string | ||
+ | *** például: <tt>"stdin": "a\nb\n10\n"</tt> | ||
+ | * kimenet | ||
+ | ** stdout, egy string, hogy mit várunk az stdout-ra. | ||
+ | *** ha nincsen megadva, akkor mindegy, hogy mit írt ki az stdout-ra. | ||
+ | ** stderr, egy string, hogy mit várunk az stderr-ra. | ||
+ | *** ha nincsen megadva, akkor mindegy, hogy mit írt ki az stderr-re. | ||
+ | ** returncode: milyen return code-al kell hogy megálljon a program | ||
+ | *** ha nincsen megadva, akkor mindegy, hogy milyen hibakóddal lépett ki. | ||
+ | |||
+ | Opcionálisan megadhatunk egy test.py ellenőrző script-et, ami mindezen információk birtokában eldöntheti, hogy elfogadja-e a megoldást. | ||
+ | <python> | ||
+ | def _eval(_input, stdout, stderr, returncode): | ||
+ | </python> | ||
+ | Ennek True/False-t kell visszaadnia. Bemenete: | ||
+ | * <tt>_input</tt>, lényegében a teszt json fájl, python dictionary-ként. | ||
+ | * stdout: a program által írt kimenet, string | ||
+ | * stderr: a program által írt hiba-kimenet, string | ||
+ | * returncode: a program által visszaadott hibakód, egész (0-123) | ||
=== python3_class === | === python3_class === | ||
+ | Akkor használjunk ilyen faladattípust, ha | ||
+ | * python3 kódot szeretnénk kérni a megoldásban | ||
+ | * és nem csak egy függvényhívás a megoldás | ||
+ | ** bár ez a feladattípus speciális esetként magában foglalja a <tt>python3_function</tt> típust is. | ||
+ | * Kérhetjük egy osztály és tagfüggvényeinek megírását | ||
+ | * megkövetelhetjük a példányosítás és a tagfüggvények meghívásának adott sorrendjét. | ||
+ | |||
+ | A <tt>manifest.json</tt> fájl tartalma | ||
+ | * <tt>"type": "python3_class"</tt> | ||
+ | * <tt>"class": "MyClass"</tt> a megírandó osztály neve | ||
+ | ** ha nincs megadva, akkor megegyezik a feladat nevével, de nem is muszáj használni ennek az értékét. | ||
+ | * <tt>"code"</tt> ez a lefuttatandó kódot tartalmazza, ha nincs megadva, akkor a következő: | ||
+ | <python> | ||
+ | def _code(_input): | ||
+ | return Class(*_input) | ||
+ | </python> | ||
+ | ahol <tt>Class</tt> helyett a megírandó osztály neve lesz. Pontosabban a json formátum miatt ez a default: | ||
+ | |||
+ | "code": "def _code(_input):\n return Class(*_input)" | ||
+ | |||
+ | Ahogy eddig is, a feladat mappájába egy <tt>test.py</tt> fájlba rakhatunk saját ellenőrző kódot. | ||
+ | Viszont a függvénnyel ellentétben itt muszáj megadnunk '''a megírni kívánt osztályt'''. Ez a következőt jelenti: | ||
+ | <python> | ||
+ | def Class: | ||
+ | pass | ||
+ | |||
+ | def _eval(_input, _output, _expected_output, _exception, _expected_exception): | ||
+ | return type(_output) == type(_expected_output) and \ | ||
+ | _output.__dict__ == _expected_output.__dict__ and \ | ||
+ | _exception == _expected_exception | ||
+ | </python> | ||
+ | Ahol <tt>Class</tt> helyett megintcsak a megírandó osztályt írjuk. | ||
+ | |||
+ | Az <tt>_eval</tt> függvény paraméterei: | ||
+ | * '''_input''' a bemeneti pickle fájlból betöltött objektum | ||
+ | * '''_output''' a <tt>_code</tt> függvény által return-ölt objektum | ||
+ | ** vagy <tt>None</tt>, ha a függvény időközben Exception-t dobott | ||
+ | * '''_expected_output''' a tesz által elvárt kimenet (az adott <tt>"o*.pkl"</tt> fájlból betöltött objektum) | ||
+ | * '''_exception''' a függvény futása közben dobott kivétel | ||
+ | ** vagy <tt>None</tt> ha nem volt Exception | ||
+ | * '''_expected_exception''' a teszt által elvárt kivétel, ha a teszt lényege az, hogy kivétel dobódjon | ||
+ | ** Ezt egy '''<tt>e</tt>''' betűvel kezdődő pickle fájlban tudjuk megadni, aminek a nevének többi része megegyezik a hozzá tartozó bemenetével. | ||
+ | ** Ha nincs ilyen fájl, akkor ez <tt>None</tt> lesz. | ||
+ | |||
+ | ==== a megírandó osztály prototípusa ==== | ||
+ | Figyelem, a <tt>test.py</tt>-ba ne oldjuk meg a feladatot, viszont érdemes ide egy '''konstruktort és egy <tt>__repr__</tt> metódust''' tenni. | ||
+ | Technikailag a tesztek és a beküldések működni fognak akkor is, ha csak egy <tt>pass</tt> van az osztályban, | ||
+ | mert ha a beküldő megírta rendesen az osztályt és a <tt>_code</tt>-ban az példányosult, akkor az már elég. | ||
+ | |||
+ | Figyelem, az <tt>_eval</tt> függvénybe ne tegyünk tagfüggvény hívást, se lépdányosítást! Ha ezt megtesszük, akkor annak az lesz a következménye, | ||
+ | hogy a <tt>test.py</tt>-ban megírt kód fog lefutni, nem pedig a beküldő kódja! | ||
+ | |||
+ | Viszont a <tt>_code</tt> függvényben a beküldő kódja fog futni, viszont ide se tegyük be a megoldást, mert ezt kvázi láthatja a beküldő (stack-trace-el). | ||
== build == | == build == | ||
+ | Ezzel a paranccsal lehet ''élesbe'' helyezni a rendszert. | ||
cd ~ && docker build -f hazijavitorendszer/Dockerfile -t hazicp hazijavitorendszer | cd ~ && docker build -f hazijavitorendszer/Dockerfile -t hazicp hazijavitorendszer | ||
+ | |||
+ | * Ha ezt megtesszük, akkor a legközelebbi beküldés a build-elés pillanatában meglévő állapotokat fogja látni. | ||
+ | * Amíg ezt nem tesszük meg, addig bármi lehet a <tt>hazijavitorendszer</tt> mappában, nem lesz hatással a hallgatók beküldéseire. | ||
+ | * Figyelem, a <tt>hazijavitorendszer</tt> mappán kívüli fájlok/mappák módosítása ellen ez nem véd! | ||
== definiálás == | == definiálás == | ||
253. sor: | 370. sor: | ||
** Csak "expert"-eknek! | ** Csak "expert"-eknek! | ||
* Ezalatt értem a home-mappa tartalmát, illetve a HW mappában a script-eket. | * Ezalatt értem a home-mappa tartalmát, illetve a HW mappában a script-eket. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
= Logs = | = Logs = | ||
283. sor: | 387. sor: | ||
[2020-02-17 13:42:48 UTC] INVALID submission from "borbely@math.bme.hu" exercise "koszones" returned 0 | [2020-02-17 13:42:48 UTC] INVALID submission from "borbely@math.bme.hu" exercise "koszones" returned 0 | ||
− | Ezekből a log-okból | + | Ezekből a log-okból bizonyos [[#Pontok|segéd script]]-ekkel tudjuk kinyerni a pontokat. |
== archivált == | == archivált == | ||
− | Minden feladatról, minden beküldőről számon van tartva az ''' | + | Minden feladatról, minden beküldőről számon van tartva az ''adott pontszámot elért legutolsó'' beküldése. |
+ | Ez úgy történik, hogy a rendszer (egészen pontosan az <tt>archive.sh</tt> script) mindig eltárolja a legutolsó beküldést, olyan fájlnévvel, ami tartalmazza a feladatot, a beküldőt és az elért pontszámot. | ||
Ezt megnézhetjük az '''<tt>archive</tt>''' mappában. | Ezt megnézhetjük az '''<tt>archive</tt>''' mappában. | ||
+ | |||
+ | [archive] | ||
+ | ├───[feladat1] | ||
+ | │ ├───jozsi~1.py | ||
+ | │ ├───sanyi~2.py | ||
+ | │ ├───jozsi~2.py | ||
+ | │ ... | ||
+ | │ | ||
+ | ├───[feladat2] | ||
+ | ├───[feladat3] | ||
+ | ... | ||
+ | |||
+ | Így mindig megkereshetjük a legutolsó beküldést, vagy a legjobb beküldést is. Vagy mondjuk csak a maximális pontot elért legutolsó beküldést. | ||
+ | |||
+ | = Segéd script-ek = | ||
+ | == checksum == | ||
+ | Mivel az egész rendszer igen érzékeny minden benne lévő script-re, ezért van egy md5 checksum, ami teszteli, hogy nem írtunk-e bele véletlenül valamelyik fontos fájlba. | ||
+ | |||
+ | Ez a checksum kiszámolódik minden (interaktív) belépésnél, ezt látjuk itt: | ||
+ | Using username "hazi". | ||
+ | hazi@leibniz.math.bme.hu's password: | ||
+ | |||
+ | Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent | ||
+ | permitted by applicable law. | ||
+ | Last login: Thu Feb 20 14:30:24 2020 from 152.66.166.49 | ||
+ | '''f30a1e390a073b0f650ecfc6f0233ebf''' - | ||
+ | hazi@leibniz:~$ | ||
+ | |||
+ | Build-elés előtt érdemes leellenőrizni, hogy még mindig ugyan az-e ez a checksum, mint ami a belépésnél volt. | ||
+ | Ezt megtehetjük úgy is, hogy meghívjuk a <tt>checksums.sh</tt> bash script-et: | ||
+ | |||
+ | hazi@leibniz:~$ bash checksums.sh | ||
+ | f30a1e390a073b0f650ecfc6f0233ebf - | ||
+ | hazi@leibniz:~$ bash checksums.sh -l | ||
+ | |||
+ | * Ennek a script-nek van egy <tt>-l</tt> opciója, ami nemcsak az összes fájlra vett MD5 hash-t írja ki, hanem fájlonként is. | ||
+ | * A szükséges fájlokról Borbély Gábornak (<tt>borbely</tt>) van egy mentése. Ha valamit elrontunk, akkor tőle vissza lehet nyerni a helyes fájlokat. | ||
+ | * Ez a hash érték nem érzékeny (többek között) az alábbiakra: | ||
+ | ** a feladatokra és a lefuttatandó tesztekre, azokat mindenki elronthatja saját felelősségére (és kárára). | ||
+ | ** A log-okra és korábbi beküldésekre, pontok állására | ||
+ | ** a <tt>.tsv</tt> fájlok tartalmára, amiben a felhasználók vannak | ||
+ | ** A fentebb felsorol adatokról nincs is ''hivatalos'' mentés, ezeket minden feladatkitűzőnek magának kell megőriznie. | ||
+ | |||
+ | == Pontok == | ||
+ | bash checkpoints.sh "email" "feladat" | ||
+ | Ez kiírja az adott (email címmel definiált) felhasználó adott feladatának pontszámát. | ||
+ | |||
+ | * Ha a felhasználó üres (<tt>""</tt>), akkor az adott feladat összes beküldőjének a pontját írja ki | ||
+ | * Ha a feladat üres (<tt>""</tt>), akkor az adott ember összes beküldésének pontját írja ki | ||
+ | * Ha mindegyik üres, akkor minden ember minden beküldésének max pontját írja ki. | ||
+ | * Ez a script nem nézi az elkésett vagy érvénytelen beküldéseket, csak a minden beküldési feltételnek megfelelő feladatok pontszámát veszi figyelembe. | ||
+ | * Lehet a pontokat az utolsó vagy a maximum szerint nézni | ||
+ | ** <tt>-m</tt> vagy <tt>--max</tt>, ez a default: legjobb érvényes beküldés | ||
+ | ** <tt>-l</tt> vagy <tt>--latest</tt>, a legutolsó, de még időben beküldött, eredményt nézi | ||
+ | |||
+ | == run.sh == | ||
+ | Ha a home mappából futtatjuk a '''run.sh''' script-et, akkor lehet szimulálni egy beküldést. | ||
+ | * Ehhez meg kell adni a script-nek, hogy mely fájlokat és milyen levelet küldött egy (fiktív) beküldő. | ||
+ | bash run.sh -h | ||
+ | * Ez a script fut le akkor is, amikor valaki egy valódi levelet küld. | ||
+ | * Ha megadjuk a <tt>--test</tt> kapcsolót az elején, akkor hasonló történik, csak | ||
+ | ** a válaszlevél nem elküldődik, hanem kiíródik a konzolra | ||
+ | ** Nincsen log-olás | ||
+ | ** Nincsen a megoldás kitörölve az ellenőrzés után (azért hogy újra lehessen tesztelni ugyanazzal a fájllal) | ||
+ | ** Nincsen archiválás | ||
+ | ** Mindig újra <tt>build</tt>-elődik a <tt>docker image</tt>, vagyis nem kell manuálisan megtennünk és nincs is hatással az ''éles'' beküldésekre | ||
+ | * Ezzel érdemes kísérletezgetni, ha valaki új feladatokon dolgozik | ||
+ | ===kapcsolók=== | ||
+ | * teszt mód | ||
+ | ** <tt>--test</tt> | ||
+ | ** <tt>-t</tt> | ||
+ | * help | ||
+ | ** <tt>--help</tt> | ||
+ | ** <tt>-h</tt> | ||
+ | * a további argumentumok vagy egyetlen mappanév, vagy (egy vagy több) fájlnév | ||
+ | ** Ha mappanevet adunk meg, akkor a mappában lévő összes fájlt a beküldés részének tekinti. | ||
+ | ** Ha fájlt vagy fájlokat, akkor azon fájlokat tekinti a beküldés csatolmányainak | ||
+ | ===beküldés=== | ||
+ | * Figyelem, muszáj a beküldéshez legalább egy, kiterjesztés nélküli <tt>info</tt> nevű fájlt megadni, ami az (imitált) email adatait tartalmazza | ||
+ | * Ha egy tényleges levél érkezik, akkor ezt a fájlt a levelezőrendszernek kell szolgáltatnia (ahogyan a csatolmányok letöltéséről is gondoskodik). | ||
+ | * Az <tt>info</tt> fájl formátuma: legalább három soros utf8 kódolású szövegfájl | ||
+ | *# sora a levél beküldője | ||
+ | *#* Figyelem, a '''tényleges beküldő''' nem feltétlenül a levél '''From''' mezője, azt könnyű meghamisítani | ||
+ | *# sora a levél tárgya | ||
+ | *# sora a levél megérkezésének dátuma | ||
+ | *#* Figyelem, '''nem a levél elküldésének dátuma'''! | ||
+ | *#* Ennek '''kell időzóna''' információt is tartalmaznia, még ha UTC+0 is | ||
+ | *#* a python <tt>dateutil.parser.parse</tt> függvényének fel kell tudnia olvasni | ||
+ | *# további sorai a levél teste nyers szövegként | ||
+ | |||
+ | == pickle == | ||
+ | A python függvény típusú beküldéseknek a teszt fájljai bináris [https://docs.python.org/3/library/pickle.html pickle] fájlok. | ||
+ | Ezeket kicsit körülményes szerkeszteni, ezért erre van egy segéd script. | ||
+ | python3 makepickle.py -h | ||
+ | Ez a parancssorban kapott argumentumokat pickle-özi. | ||
+ | === Kapcsolók === | ||
+ | *A kimenet (<tt>-o --output</tt>) | ||
+ | ** lehet stdout (ha üresen hagyjuk) | ||
+ | ** vagy egy fájlnév | ||
+ | ** vagy egy mappa, ekkor ebbe a mappába egy <tt>i[0-9]+.pkl</tt> nevű fájl lesz, olyan sorszámmal, ami még nincsen. Ez az új teszt bemenetekhez javasolt! | ||
+ | * lista vagy egy elem (<tt>-l --list</tt>) | ||
+ | ** Ha ez a kapcsoló nincsen bekapcsolva, akkor egyszerűen az első parancssori argumentum lesz kimentve. | ||
+ | ** Ha be van kapcsolva, akkor az összes argumentum, mint lista lesz kimentve. A teszt bemenetekhez javasolt! |
A lap 2020. március 31., 14:35-kori változata
Tartalomjegyzék |
Tájékoztató
Ez az oldal a Házifeladat Ellenőrző rendszer használatát írja le, hogy hogyan lehet feladatokat feladni, felhasználókat és kurzusokat kezelni.
Belépés
A rendszer maga egy leibniz-es felhasználón keresztül érhető el:
hazi@leibniz.math.bme.hu
Ide be lehet ssh-zni, scp-zni vagy a webmail-jébe belépni.
Honlap
Mivel a hazi egy sima felhasználó a leibniz-en, van honlapja is:
http://math.bme.hu/~hazi
Ide lehet közérdekű (publikus) infókat kitenni, de lehet jelszóval védett al-oldalakat is csinálni, mondjuk azok biztonsága elég enyhe.
Érdekesség, hogy vannak terhelési grafikonok is.
Mappaszerkezet
Ennek a felhasználónak a home mappájában a következőket találjuk:
[~] ├───[hazijavitorendszer] │ ├───[HW] │ │ ├───[feladat] │ │ . │ │ . (többi feladat) │ │ . │ │ ├───main │ │ ├───validate │ │ ├───userinfo.tsv │ │ ├───auxiliary.py │ │ ├───off │ │ ├───feladattipus1 │ │ ├───feladattipus2 │ │ ├───... │ │ └───getsenderinfo │ ├───mailsend-go_1.0.7_linux-64bit.deb │ ├───Dockerfile │ └───Dockerfile.test ├───[solution] ├───[logs] ├───[archive] ├───digest_logs.sh ├───checkpoints.sh ├───... ├───archive.sh └───run.sh
Mit csináljunk
Hallgatók kezelése
Ezt lényegében egy hazijavitorendszer/HW/*.tsv (tab-separated-values) fájl szerkesztésével tehetjük meg.
Formátuma:
email name course borbely@math.bme.hu Gábor Borbély info2,lecturer
- Több ilyen fájl is lehet, ekkor a rendszer lényegében uniózza ezen fájlokat.
- Minden sora egy felhasználó
- A felhasználók az email-címükkel vannak azonosítva, a nevük csak tájékoztató jellegű.
- Egy felhasználóhoz megadhatunk course-t, ami az általa látogatott kurzusok listája: egy vesszővel elválasztott lista.
- Egy kurzus neve csak latin alfanumerikus karakterekből állhat (szóköz, vessző, ékezetes karakter nem lehet benne) vagy alulvonásból (azaz regex \w+).
- A példában meg van adva egy lecturer csoport is a tanároknak.
- Ha több kurzusra is jár egy hallgató, akkor a kurzust egy vesszővel vagy szóközzel elválasztott listával adjuk meg.
- Ha egy hallgató többször szerepel a listában, akkor csak a legelső előfordulását vesszük figyelembe.
- Régi hallgatókat nem érdemes kitörölni, ha a kurzusa évszámmal is meg van jelölve.
- például a kurzus info2_2019 nem fog összeakadni az info2_2020 kurzussal
- de ha valaki másodszorra hallgatja az info2-t, akkor lehet két kurzusa: info2_2019,info2_2020
Ha a felhasználók .tsv fájlját változtatjuk, akkor a módosítások csak akkor jutnak érvényre, ha újra build-eljük a docker image-et
Feladatok felvétele
Egy feladatot a hazijavitorendszer/HW/ mappában lévő mappa definiál.
- a mappa neve a feladat neve
- almappákat nem vesz figyelembe a rendszer, ezen belül nem lehet más almappa
- kell legyen egy manifest.json fájl a feladat mappájában
- kellenek tesztek a mappában
- minden teszt neve i betűvel kell kezdődjön
- a tesztek formátuma feladattípustól függ. lásd lentebb
Például a fahrenheit nevű feladat felvételéhez hozzuk létre az alábbiakat:
HW └───fahrenheit ├───manifest.json ├───i1.json ├───i2.json └───i3.json
Egy feladathoz 123-nál több teszt esetet nem adhatunk meg!
Ha egy új feladatot felveszünk vagy régit módosítunk, vagy kitörlünk, akkor a módosítások csak akkor jutnak érvényre, ha újra build-eljük a docker image-et
manifest.json
A feladatot egy json dictionary írja le, az alábbi kulcsokkal:
- "type" a feladat típusa, kell legyen egy, a típussal egyező nevű, futtatható fájl a HW mappában
- "description" HTML source, json escaped, opcionális
- "course" mely csoportok küldhetnek be (lecturer-t mindig érdemes belevenni)
- ez lehet egy sztring, amiben vesszővel elválasztva vannak a kurzusok
- vagy lehet egy json lista:
"course": ["info2", "lecturer"]
ha nem adunk meg kurzust, akkor senkitől nem fogad el beküldéseket
- "visible" true vagy false
- ha false akkor ez a feladat nem fogad el beküldéseket
- "deadline" egy sztring, ami a határidőt írja le
- például "2020-02-14 01:00:00 UTC+1"
- Muszáj időzónát megadni (UTC+1 a budapesti)
- a python dateutils.parser.parse függvénye számára értelmezhető formátumban kell legyen
- Ha nincsen egyáltalán "deadline" kulcs a szótárban, akkor bármikor be lehet küldeni a feladatot.
- "disclaimer"
- Ezzel megkövetelhetünk egy adott formátumú levéltörzset a beküldőtől.
- Használható arra, hogy muszáj legyen beírni a hallgatónak azt, hogy ő készítette a feladatot és nem másolt.
- Az értéke egy sztring kell legyen, amiben az alábbi behelyettesítéseket is megkövetelhetjük:
- {name}
- {email}
- {course}
- Például:
"disclaimer": "Én, {name}, felelősségem teljes tudatában kijelentem, hogy a mellékelt kód az én szellemi termékem, azt mással meg nem osztottam."
- A disclaimer többnyelvű is lehet, ha egy json listában több ilyen sztringet is megadunk.
- Ekkor az számít helyes beküldésnek, ha a levél törzse a megadott disclaimer-ek legalább egyikével megegyezik.
- De egyszerűen megadhatunk üres disclaimer-t is, aminek a következménye, hogy csak üres levelet fogad el feladat.
- Ha nincsen disclaimer kulcs a szótárban, akkor a levél törzse irreleváns.
Feladattípusok
Egy feladat típusa határozza meg, hogy milyen programnyelvet várunk el a beküldőtől. Akkor tekinthető valami egy értelmes feladattípusnak, ha van egy olyan nevű futtatható fájl a HW mappában. Például ha programozási feladatból python3 programokat akarunk feladni, akkor kell legyen egy python3_program nevű futtatható állomány. A továbbiakban ez ellenőrzi le azt a feladatot, aminek a "type" mezőjében a "python3_program"-ot adtuk meg.
Lentebb részletezzünk, hogy milyen feladattípusok vannak és hogy melyik milyen sajátosságokkal rendelkezik. De definiálhatunk saját feladattípust is.
python3_program
Akkor használjunk ilyen faladattípust, ha
- azt akarjuk, hogy a beküldés egy .py kiterjesztésű szövegfájl legyen,
- amit le lehet futtatni a python3 feladatneve.py paranccsal.
Ahhoz hogy egy ilyen programot teszteljünk, az alábbiakat tudjuk bemenetként megadni:
- parancssori argumentumok
- bemeneti fájlok (amiket olvashat a beküldő)
- stdin
A program lefutásának sikereségét úgy tudjuk tesztelni, hogy megnézzük hogy
- mit írt a program az stdout-ra,
- mit írt az stderr-re,
- milyen return code-al ált meg.
- Opcionálisan megadhatunk egy test.py ellenőrző script-et, ami mindezen információk birtokában eldöntheti, hogy elfogadja-e a megoldást.
- a default ellenőrző script azt csinálja, hogy megnézi hogy a return code 0-e és hogy az stdout megegyezik-e az elvárt stdout-al.
Egy teszt esethez egy i betűvel kezdődő nevű json fájlt kell berakni a feladat mappájába. Például ioverscrupulous.json:
{ "argv": ["1.text", "overscrupulous"], "files": ["1.text"], "returncode": 0, "stdout": "314\n240\1729" }
A tesztfájlok kulcsai:
- "argv" sztringek listája, a parancssori argumentumok (a script neve után)
- ha nem adjuk meg, akkor nem kap plusz parancssori argumentumot a megoldás
- "stdin" egy utf-8 sztring, amit stdin-ről megkap a beküldött program (több soros is lehet "\n")
- ha nem adjuk meg, vagy üreset adunk meg, akkor nem tud stdin-ről olvasni a program
- "files" fájlok listája, amikel olvashat a beküldött program
- ha nem adjuk meg, akkor nem fog fájlokat találni a beküldött program
- "stdout" az elvárt kimenet (utf-8 sztring)
- "stderr" az elvárt error kimenet, ha figyelembe akarjuk venni azt is, hogy mit írt a program az stderr-re.
- "returncode" a lefutás utáni visszatérési érték (0-128) közti szám
- Ezzel lehet tesztelni, hogy helyesen állt-e le a program.
- De megkövetelhetünk egy hibakódot is.
- Ha nem adjuk meg, akkor az elvárt hibakód a 0
Ha egyedi tesztelő feltételt akarunk megadni, akkor egy test.py fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk:
def _eval(_input, stdout, stderr, returncode):
A függvénynek True vagy False-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga Server error! hibaüzenettel.
Az _eval függvény paraméterei:
- _input az adott test json fájljának tartalma
- stdout a program által kiadott kimenet (sztring)
- stderr a program által kiadott hiba-kimenet (sztring)
- returncode a program hibakódja, egész szám
Ha új tesztesetet veszünk fel, vagy régit módosítunk vagy törlünk, akkor a módosítások csak akkor jutnak érvényre, ha újra build-eljük a docker image-et
python3_function
Akkor használjunk ilyen faladattípust, ha
- azt akarjuk, hogy a beküldés egy python3 nyelven írt függvény legyen
- azaz tartalmazzon legalább egy, megadott szignatúrájú függvényt
- Ehhez definiálni kell a manifest fájlban a megírni kívánt függvény nevét.
"function": "fibonacci"
- Ha ezt nem definiáljuk, akkor a feladat nevével megegyező függvény lesz tesztelve.
- A megoldás más függvényt és tetszőleges module-t is használhat segítségnek, akár beépítettet, akár a beküldő saját szerzeményét.
Ahhoz hogy egy ilyen függvényt teszteljünk, a függvény argumentumait kell megadni, pickle formátumban.
- Egy teszt egy pickle-özött lista, ami az előre (binárisan) lementett bemeneti argumentumok listája.
- Ezt egy i betűvel kezdődő nevű .pkl kiterjesztésű fájlba kell megadni a feladat mappájában.
A pickle fájlok előállításához segédlet itt.
Például, ha a descartes_szorzat függvényt akarom feladni, ami listák Descartes szorzatát adja vissza, akkor ez lenne a szignatúrája:
def descartes_szorzat(x, y): # return the list of pairs of x and y
És egy tesz fájlt így lehetne előállítani:
python3 -c "import pickle; pickle.dump([[1, 2], ['a', 'b']], open('i1.pkl', 'wb'))"
Ekkor az alábbi hívódik meg
descartes_szorzat([1, 2], ['a', 'b'])
Az ehhez tartozó kimenet pedig szintén egy pickle fájl, aminek a neve o betűvel kezdődik, a nevének többi része pedig megegyezik a hozzá tartozó bemenetével.
python3 -c "import pickle; pickle.dump([(1, 'a'), (2, 'a'), (1, 'b'), (2, 'b')], open('o1.pkl', 'wb'))"
Ha egyedi tesztelő feltételt akarunk megadni, akkor egy test.py fájlt kell tennünk a feladat mappájába, amiben az alábbi függvényt definiáljuk:
def _eval(_input, _output, _expected_output, _exception, _expected_exception):
A függvénynek True vagy False-al kell visszatérnie annak függvényében, hogy elfogadjuk-e az eredményt. Ha ez a függvény Exception-t dob, akkor a megoldás 0 pontos lesz egy narancssárga Server error! hibaüzenettel.
Az _eval függvény paraméterei:
- _input a bemeneti pickle fájlból betöltött objektum, mindig lista, akkor is ha egy hosszú (egy változós függvény bemenete)
- _output a return-ölt objektum
- vagy None, ha a függvény időközben Exception-t dobott
- _expected_output a tesz által elvárt kimenet (az adott "o*.pkl" fájlból betöltött objektum)
- _exception a függvény futása közben dobott kivétel
- vagy None ha nem volt Exception
- _expected_exception a teszt által elvárt kivétel, ha a teszt lényege az, hogy kivétel dobódjon
- Ezt egy e betűvel kezdődő pickle fájlban tudjuk megadni, aminek a nevének többi része megegyezik a hozzá tartozó bemenetével.
- Ha nincs ilyen fájl, akkor ez None lesz.
Ha új tesztesetet veszünk fel, vagy régit módosítunk vagy törlünk, akkor a módosítások csak akkor jutnak érvényre, ha újra build-eljük a docker image-et
program
Általában bármilyen parancssorból hívható programot tesztelhetünk ezzel. Akkor is ha interpretált vagy ha fordított nyelven van írva. Persze ehhez installálva kell legyen a szükséges fordító és/vagy interpreter a Docker image-ben.
Akkor használjunk ilyen faladattípust, ha
- azt akarjuk, hogy a beküldés egy interpreter által futtatott vagy fordító által fordított fájl legyen, ami parancssorból működik
Ahhoz hogy egy ilyen programot teszteljünk, az alábbiakat kell megadnunk a manifest.json fájlban:
- "type": "program"
- "compile" ennek az értéke egyetlen parancs, vagy listában megadott parancs plusz paraméterek lehetnek
- ez fog lefutni a beküldött program előtt
- például: ["gcc", "-o", "myprogram", "myprogram.c"]
- ha ez nincsen megadva, vagy üres lista van megadva, akkor nincsen fordítási lépés.
- "command" ez fog lefutni tesztenként
- lehet egy string vagy string-ek listája
- például: "./myprogram", ha előzőleg lefordítottuk
- vagy ["python3", "fahrenheit.py"]
- vagy ["wolfram", "-script", "calculate.m"]
- Ezek a parancsok mind a beküldő felhasználójának home-mappájában (/home/dummy) fognak lefutni és a dummy felhasználó nevében (és jogosultságaival).
- Ezen kívül a szokásos description, deadline, visible és course mezők is lehetnek.
Egy teszt fájlban (i*.json) a következőket lehet megadni:
- bemenet
- argv
- például: "argv": ["a", "-h", "file.txt"]
- ezek a futtatandó parancs mögé append-álódnak
- bemeneti fájlok listája (amiket olvashat a beküldő program)
- ezek odamásolódnak a beküldött program mellé a teszt előtt.
- stdin, mit kapjon a standard bemeneten, egy string
- például: "stdin": "a\nb\n10\n"
- argv
- kimenet
- stdout, egy string, hogy mit várunk az stdout-ra.
- ha nincsen megadva, akkor mindegy, hogy mit írt ki az stdout-ra.
- stderr, egy string, hogy mit várunk az stderr-ra.
- ha nincsen megadva, akkor mindegy, hogy mit írt ki az stderr-re.
- returncode: milyen return code-al kell hogy megálljon a program
- ha nincsen megadva, akkor mindegy, hogy milyen hibakóddal lépett ki.
- stdout, egy string, hogy mit várunk az stdout-ra.
Opcionálisan megadhatunk egy test.py ellenőrző script-et, ami mindezen információk birtokában eldöntheti, hogy elfogadja-e a megoldást.
def _eval(_input, stdout, stderr, returncode):
Ennek True/False-t kell visszaadnia. Bemenete:
- _input, lényegében a teszt json fájl, python dictionary-ként.
- stdout: a program által írt kimenet, string
- stderr: a program által írt hiba-kimenet, string
- returncode: a program által visszaadott hibakód, egész (0-123)
python3_class
Akkor használjunk ilyen faladattípust, ha
- python3 kódot szeretnénk kérni a megoldásban
- és nem csak egy függvényhívás a megoldás
- bár ez a feladattípus speciális esetként magában foglalja a python3_function típust is.
- Kérhetjük egy osztály és tagfüggvényeinek megírását
- megkövetelhetjük a példányosítás és a tagfüggvények meghívásának adott sorrendjét.
A manifest.json fájl tartalma
- "type": "python3_class"
- "class": "MyClass" a megírandó osztály neve
- ha nincs megadva, akkor megegyezik a feladat nevével, de nem is muszáj használni ennek az értékét.
- "code" ez a lefuttatandó kódot tartalmazza, ha nincs megadva, akkor a következő:
def _code(_input): return Class(*_input)
ahol Class helyett a megírandó osztály neve lesz. Pontosabban a json formátum miatt ez a default:
"code": "def _code(_input):\n return Class(*_input)"
Ahogy eddig is, a feladat mappájába egy test.py fájlba rakhatunk saját ellenőrző kódot. Viszont a függvénnyel ellentétben itt muszáj megadnunk a megírni kívánt osztályt. Ez a következőt jelenti:
def Class: pass def _eval(_input, _output, _expected_output, _exception, _expected_exception): return type(_output) == type(_expected_output) and \ _output.__dict__ == _expected_output.__dict__ and \ _exception == _expected_exception
Ahol Class helyett megintcsak a megírandó osztályt írjuk.
Az _eval függvény paraméterei:
- _input a bemeneti pickle fájlból betöltött objektum
- _output a _code függvény által return-ölt objektum
- vagy None, ha a függvény időközben Exception-t dobott
- _expected_output a tesz által elvárt kimenet (az adott "o*.pkl" fájlból betöltött objektum)
- _exception a függvény futása közben dobott kivétel
- vagy None ha nem volt Exception
- _expected_exception a teszt által elvárt kivétel, ha a teszt lényege az, hogy kivétel dobódjon
- Ezt egy e betűvel kezdődő pickle fájlban tudjuk megadni, aminek a nevének többi része megegyezik a hozzá tartozó bemenetével.
- Ha nincs ilyen fájl, akkor ez None lesz.
a megírandó osztály prototípusa
Figyelem, a test.py-ba ne oldjuk meg a feladatot, viszont érdemes ide egy konstruktort és egy __repr__ metódust tenni. Technikailag a tesztek és a beküldések működni fognak akkor is, ha csak egy pass van az osztályban, mert ha a beküldő megírta rendesen az osztályt és a _code-ban az példányosult, akkor az már elég.
Figyelem, az _eval függvénybe ne tegyünk tagfüggvény hívást, se lépdányosítást! Ha ezt megtesszük, akkor annak az lesz a következménye, hogy a test.py-ban megírt kód fog lefutni, nem pedig a beküldő kódja!
Viszont a _code függvényben a beküldő kódja fog futni, viszont ide se tegyük be a megoldást, mert ezt kvázi láthatja a beküldő (stack-trace-el).
build
Ezzel a paranccsal lehet élesbe helyezni a rendszert.
cd ~ && docker build -f hazijavitorendszer/Dockerfile -t hazicp hazijavitorendszer
- Ha ezt megtesszük, akkor a legközelebbi beküldés a build-elés pillanatában meglévő állapotokat fogja látni.
- Amíg ezt nem tesszük meg, addig bármi lehet a hazijavitorendszer mappában, nem lesz hatással a hallgatók beküldéseire.
- Figyelem, a hazijavitorendszer mappán kívüli fájlok/mappák módosítása ellen ez nem véd!
definiálás
Hogyan definiálhatunk új feladattípust? (Csak "expert"-eknek!)
Mit NE csináljunk
chmod
- A hazi felhasználó umask-ja 027, ezt ne változtassuk!
- Minden mappa és fájl jogosultsága olyan, hogy other felhasználók ne lássák, ezt ne változtassuk!
script-ek
- A felhasználók táblázat és a feladatok mappáinak kivételével semmihez ne nyúljunk.
- Csak "expert"-eknek!
- Ezalatt értem a home-mappa tartalmát, illetve a HW mappában a script-eket.
Logs
A rendszer folyamatosan megőrzi és eltárolja a beküldések legfontosabb adatait. De magát az eredeti beküldést csak a levelezőrendszer INBOX-ában tudjuk megnézni.
log fájlok
A logs mappában van minden log, amit a rendszer generált, ezek csak a beküldések kivonatai:
- ki, mikor küldött be, melyik feladatot
- Ha valid volt a beküldés (kurzus, határidő, beküldő mind rendben volt), akkor a pontszámát is
- Ha egy beküldés nem volt valid, de bizonyos gyenge követelményeket teljesített (pl. csak elkésett a beküldéssel), akkor a rendszer kiértékeli a feladatát, de nem ad rá pontot. Ezeket is láthatjuk a log-okban.
A log fájlok-ból ha valaki csak a pontokra kíváncsi, akkor a <SUCCESS> szóra kell grep-elni és az elért maximumot kikeresni.
Itt a második példában a beküldést kiértékelte a rendszer, de INVALID címkével (mondjuk úgyis 0 pontos lett volna):
[2020-02-17 13:42:47 UTC] <SUCCESS> submission from "borbely@math.bme.hu" exercise "greeting" returned 4 [2020-02-17 13:42:48 UTC] INVALID submission from "borbely@math.bme.hu" exercise "koszones" returned 0
Ezekből a log-okból bizonyos segéd script-ekkel tudjuk kinyerni a pontokat.
archivált
Minden feladatról, minden beküldőről számon van tartva az adott pontszámot elért legutolsó beküldése. Ez úgy történik, hogy a rendszer (egészen pontosan az archive.sh script) mindig eltárolja a legutolsó beküldést, olyan fájlnévvel, ami tartalmazza a feladatot, a beküldőt és az elért pontszámot.
Ezt megnézhetjük az archive mappában.
[archive] ├───[feladat1] │ ├───jozsi~1.py │ ├───sanyi~2.py │ ├───jozsi~2.py │ ... │ ├───[feladat2] ├───[feladat3] ...
Így mindig megkereshetjük a legutolsó beküldést, vagy a legjobb beküldést is. Vagy mondjuk csak a maximális pontot elért legutolsó beküldést.
Segéd script-ek
checksum
Mivel az egész rendszer igen érzékeny minden benne lévő script-re, ezért van egy md5 checksum, ami teszteli, hogy nem írtunk-e bele véletlenül valamelyik fontos fájlba.
Ez a checksum kiszámolódik minden (interaktív) belépésnél, ezt látjuk itt:
Using username "hazi". hazi@leibniz.math.bme.hu's password: Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Thu Feb 20 14:30:24 2020 from 152.66.166.49 f30a1e390a073b0f650ecfc6f0233ebf - hazi@leibniz:~$
Build-elés előtt érdemes leellenőrizni, hogy még mindig ugyan az-e ez a checksum, mint ami a belépésnél volt. Ezt megtehetjük úgy is, hogy meghívjuk a checksums.sh bash script-et:
hazi@leibniz:~$ bash checksums.sh f30a1e390a073b0f650ecfc6f0233ebf - hazi@leibniz:~$ bash checksums.sh -l
- Ennek a script-nek van egy -l opciója, ami nemcsak az összes fájlra vett MD5 hash-t írja ki, hanem fájlonként is.
- A szükséges fájlokról Borbély Gábornak (borbely) van egy mentése. Ha valamit elrontunk, akkor tőle vissza lehet nyerni a helyes fájlokat.
- Ez a hash érték nem érzékeny (többek között) az alábbiakra:
- a feladatokra és a lefuttatandó tesztekre, azokat mindenki elronthatja saját felelősségére (és kárára).
- A log-okra és korábbi beküldésekre, pontok állására
- a .tsv fájlok tartalmára, amiben a felhasználók vannak
- A fentebb felsorol adatokról nincs is hivatalos mentés, ezeket minden feladatkitűzőnek magának kell megőriznie.
Pontok
bash checkpoints.sh "email" "feladat"
Ez kiírja az adott (email címmel definiált) felhasználó adott feladatának pontszámát.
- Ha a felhasználó üres (""), akkor az adott feladat összes beküldőjének a pontját írja ki
- Ha a feladat üres (""), akkor az adott ember összes beküldésének pontját írja ki
- Ha mindegyik üres, akkor minden ember minden beküldésének max pontját írja ki.
- Ez a script nem nézi az elkésett vagy érvénytelen beküldéseket, csak a minden beküldési feltételnek megfelelő feladatok pontszámát veszi figyelembe.
- Lehet a pontokat az utolsó vagy a maximum szerint nézni
- -m vagy --max, ez a default: legjobb érvényes beküldés
- -l vagy --latest, a legutolsó, de még időben beküldött, eredményt nézi
run.sh
Ha a home mappából futtatjuk a run.sh script-et, akkor lehet szimulálni egy beküldést.
- Ehhez meg kell adni a script-nek, hogy mely fájlokat és milyen levelet küldött egy (fiktív) beküldő.
bash run.sh -h
- Ez a script fut le akkor is, amikor valaki egy valódi levelet küld.
- Ha megadjuk a --test kapcsolót az elején, akkor hasonló történik, csak
- a válaszlevél nem elküldődik, hanem kiíródik a konzolra
- Nincsen log-olás
- Nincsen a megoldás kitörölve az ellenőrzés után (azért hogy újra lehessen tesztelni ugyanazzal a fájllal)
- Nincsen archiválás
- Mindig újra build-elődik a docker image, vagyis nem kell manuálisan megtennünk és nincs is hatással az éles beküldésekre
- Ezzel érdemes kísérletezgetni, ha valaki új feladatokon dolgozik
kapcsolók
- teszt mód
- --test
- -t
- help
- --help
- -h
- a további argumentumok vagy egyetlen mappanév, vagy (egy vagy több) fájlnév
- Ha mappanevet adunk meg, akkor a mappában lévő összes fájlt a beküldés részének tekinti.
- Ha fájlt vagy fájlokat, akkor azon fájlokat tekinti a beküldés csatolmányainak
beküldés
- Figyelem, muszáj a beküldéshez legalább egy, kiterjesztés nélküli info nevű fájlt megadni, ami az (imitált) email adatait tartalmazza
- Ha egy tényleges levél érkezik, akkor ezt a fájlt a levelezőrendszernek kell szolgáltatnia (ahogyan a csatolmányok letöltéséről is gondoskodik).
- Az info fájl formátuma: legalább három soros utf8 kódolású szövegfájl
- sora a levél beküldője
- Figyelem, a tényleges beküldő nem feltétlenül a levél From mezője, azt könnyű meghamisítani
- sora a levél tárgya
- sora a levél megérkezésének dátuma
- Figyelem, nem a levél elküldésének dátuma!
- Ennek kell időzóna információt is tartalmaznia, még ha UTC+0 is
- a python dateutil.parser.parse függvényének fel kell tudnia olvasni
- további sorai a levél teste nyers szövegként
- sora a levél beküldője
pickle
A python függvény típusú beküldéseknek a teszt fájljai bináris pickle fájlok. Ezeket kicsit körülményes szerkeszteni, ezért erre van egy segéd script.
python3 makepickle.py -h
Ez a parancssorban kapott argumentumokat pickle-özi.
Kapcsolók
- A kimenet (-o --output)
- lehet stdout (ha üresen hagyjuk)
- vagy egy fájlnév
- vagy egy mappa, ekkor ebbe a mappába egy i[0-9]+.pkl nevű fájl lesz, olyan sorszámmal, ami még nincsen. Ez az új teszt bemenetekhez javasolt!
- lista vagy egy elem (-l --list)
- Ha ez a kapcsoló nincsen bekapcsolva, akkor egyszerűen az első parancssori argumentum lesz kimentve.
- Ha be van kapcsolva, akkor az összes argumentum, mint lista lesz kimentve. A teszt bemenetekhez javasolt!