HazifeladatEllenorzoTeacher

A MathWikiből
(Változatok közti eltérés)
a
(Feladattípusok)
(egy szerkesztő 20 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 =  
 
Ennek a felhasználónak a <tt>home</tt> mappájában a következőket találjuk:
 
Ennek a felhasználónak a <tt>home</tt> mappájában a következőket találjuk:
  
     ~
+
     [~]
     ├───hazijavitorendszer
+
     ├───[hazijavitorendszer]
     │  ├───HW
+
     │  ├───[HW]
     │  │  ├───feladat
+
     │  │  ├───[feladat]
 
     │  │  .
 
     │  │  .
 
     │  │  . (többi feladat)
 
     │  │  . (többi feladat)
28. sor: 37. sor:
 
     │  │  ├───...
 
     │  │  ├───...
 
     │  │  └───getsenderinfo
 
     │  │  └───getsenderinfo
     │  └───mailsend-go_1.0.6_linux-64bit.deb
+
     │  ├───mailsend-go_1.0.7_linux-64bit.deb
     ├───solution
+
     │  ├───Dockerfile
     ├───logs
+
     │  └───Dockerfile.test
     ├───archive
+
     ├───[solution]
     ├───test
+
     ├───[logs]
     │  └───(itt lényegében a felette lévőnek egy másolata van)
+
     ├───[archive]
 
     ├───digest_logs.sh
 
     ├───digest_logs.sh
 +
    ├───checkpoints.sh
 +
    ├───...
 
     ├───archive.sh
 
     ├───archive.sh
    ├───Dockerfile
 
 
     └───run.sh
 
     └───run.sh
  
 
= Mit csináljunk =
 
= Mit csináljunk =
 
== Hallgatók kezelése ==
 
== Hallgatók kezelése ==
Ezt lényegében a <tt>userinfo.tsv</tt> (tab-separated-values) fájl szerkesztésével tehetjük meg.
+
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:
48. 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ű.
59. 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>userinfo.tsv</tt> fájlt változtatjuk, akkor a módosítások csak akkor jutnak érvényre, ha '''újra <tt>build</tt>-eljük a <tt>docker image</tt>-et'''
+
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 ==
169. 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.
196. 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):
+
  def descartes_szorzat(x, y):
     ''# return the list of pairs of x and 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:
  
207. 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.
  
215. 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.
233. 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 ==
252. 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.
 
== test ==
 
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 akar
 
  
 
= Logs =
 
= Logs =
282. 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 ki-<tt>grep</tt>-elhetjük a pontokat.
+
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 '''eddigi legjobb (és a legjobbak közül a legfrissebb) megoldása'''.
+
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"
  • 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.

 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
    1. 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
    2. sora a levél tárgya
    3. 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
    4. 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 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!
Személyes eszközök