HazifeladatEllenorzoTeacher
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.
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.6_linux-64bit.deb ├───solution ├───logs ├───archive ├───test │ └───(itt lényegében a felette lévőnek egy másolata van) ├───digest_logs.sh ├───archive.sh ├───Dockerfile └───run.sh
Mit csináljunk
Hallgatók kezelése
Ezt lényegében a userinfo.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
- 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 userinfo.tsv fájlt változtatjuk, akkor a módosítások csak akkor jutnak érvényre, ha újra build-eljük a docker image-et
cd ~ && docker build -f Dockerfile -t hazicp hazijavitorendszer
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
cd ~ && docker build -f Dockerfile -t hazicp hazijavitorendszer
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
cd ~ && docker build -f Dockerfile -t hazicp hazijavitorendszer
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.
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
cd ~ && docker build -f Dockerfile -t hazicp hazijavitorendszer
python3_class
definiálás
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.
test
Van egy test mappa ahol lényegében az egész rendszer le van másolva, egy-két különbséggel.
- Ha a test mappából futtatjuk a run.sh script-et, akkor nem történik log-olás és nem küld a rendszer válaszlevelet a beküldőknek.
- Itt lehet kísérletezni, ha valaki akar.