0%

Eindproject: een live scorebord · eindproject

Eindproject: Draai de hele quizavond

De server en de client hebben nog nooit samen gedraaid. Deze les laat ze een hele quizavond doorlopen, voegt de test toe die vastlegt hoe een quiz eindigt, en controleert het hele project in één keer. Eén scenario van pl_ws.py zet je twee programma’s op het podium:

python pl_ws.py quiz-night

Het roept de main van je server aan in één thread en de main van je client voor kijker A en voor elke scorebijhouder, speelt twee eigen kijkers, B en S, en vertraagt S met opzet. Een afgemaakte versie, op de site uitgevoerd, drukte het transcript hieronder af; lees het blok voor blok. Regels met | zijn de standard output van een programma, regels met ! zijn standard error, en PORT staat voor het dat het systeem koos.

scenario quiz-night: your server and your client through a whole quiz night
viewer A is your watch; B is this program's; S is this program's and is slowed down on purpose
part 1: a quiz of three updates; first a scorekeeper with the wrong token, then three scores
your server's main() is called with --port 0 --max-updates 3 --deadline 6
viewer B joined; its first message was a snapshot with version 0
viewer S joined; its first message was a snapshot with version 0
scorekeeper (wrong token) ! the scoreboard refused the connection: HTTP 401
scorekeeper (wrong token) exited with 1
scorekeeper | version 1: Owls 3, Foxes 0, Herons 0
scorekeeper exited with 0
scorekeeper | version 2: Owls 3, Foxes 2, Herons 0
scorekeeper exited with 0
scorekeeper | version 3: Owls 3, Foxes 2, Herons 5
scorekeeper exited with 0

Je server weigerde het verkeerde token vóór de upgrade, en je score maakte van de 401 één regel op standard error en exitstatus 1. Daarna drie scores, elk beantwoord met zijn snapshot.

viewer A (your client) | version 0: Owls 0, Foxes 0, Herons 0
viewer A (your client) | version 1: Owls 3, Foxes 0, Herons 0
viewer A (your client) | version 2: Owls 3, Foxes 2, Herons 0
viewer A (your client) | version 3: Owls 3, Foxes 2, Herons 5
viewer A (your client) exited with 0
viewer B received versions [0, 1, 2, 3], then the server closed it with 1000 'the quiz is over'
viewer S received versions [0, 1, 3], then the server closed it with 1000 'the quiz is over'
your server | listening on 127.0.0.1:PORT
your server | the quiz is over at version 3; every viewer was closed with 1000
your main() returned 0

Je watch drukte elke versie van 0 tot en met 3 af en vertrok met 0 bij de close van de server. Kijker S zag versie 2 nooit: terwijl zijn thread nog bezig was met het sturen van versie 1, verving versie 3 versie 2 in zijn outbox. Hij ontving toch de laatste versie vóór zijn close met 1000, en dat is precies de regel uit les 1. Welke versies S ontvangt, hangt af van timing, dus je run kan een andere tonen; wat nooit verandert, is dat zijn laatste versie de laatste versie van de quiz is, gevolgd door 1000.

part 2: a quiz of ten updates with a deadline of 1.5 seconds; one score
your server's main() is called with --port 0 --max-updates 10 --deadline 1.5
viewer B joined; its first message was a snapshot with version 0
viewer S joined; its first message was a snapshot with version 0
scorekeeper | version 1: Owls 2, Foxes 0, Herons 0
scorekeeper exited with 0
viewer A (your client) | version 0: Owls 0, Foxes 0, Herons 0
viewer A (your client) | version 1: Owls 2, Foxes 0, Herons 0
viewer A (your client) exited with 0
viewer B received versions [0, 1], then the server closed it with 1001 'the scoreboard is closing'
viewer S received versions [0, 1], then the server closed it with 1001 'the scoreboard is closing'
your server | listening on 127.0.0.1:PORT
your server | the deadline passed at version 1; every connection was closed with 1001
your main() returned 0
the token from token.txt appears in no line of this transcript

De tweede quiz kon niet af: er werden tien updates verwacht en er kwam er maar één. Bij de deadline sloot je server iedereen met 1001, je watch zag dat als een normaal einde, en main gaf nog steeds 0 terug. De laatste regel is een controle die pl_ws.py op zijn eigen uitvoer doet; nog een regel, die niet getoond wordt, telt threads en verschilt van run tot run.

In het transcript ontving de vertraagde kijker S versie 0, 1 en 3, en daarna een close met 1000. Is dat een fout?

De laatste test

Het einde is de regel die dit hoofdstuk toevoegde, en nog niets in je testsuite legt hem vast. Vervang de laatste stub in tests/test_scoreboard.py, test_the_last_update_is_sent_before_the_close. Maak een bord met max_updates=1 en verwerk één score, zodat de quiz voorbij is op versie 1. Bouw een stand-in-kijker met types.SimpleNamespace: een id, een send die elk bericht, geparset, vastlegt, en een close(code, reason) die de code vastlegt. Roep scoreboard_server.viewer_handler(viewer, board) aan. Die keert meteen terug, omdat de laatste snapshot al de snapshot bij binnenkomst is: er hoeft niets te wachten en er is geen recv nodig. Controleer daarna dat het laatst verstuurde bericht versie 1 heeft, dat de vastgelegde closes precies [1000] zijn, en dat board["finished"].is_set().

Wat Check my work controleert

Dit is de hele acceptatiecontrole van het eindproject, in woorden. Het importeren van de drukt niets af, en de enveloppen komen nog steeds heelhuids heen en terug en weigeren nog steeds. Bij elke verweving die de controle kan regelen, krijgt een kijker zijn snapshot bij binnenkomst en daarna alleen nieuwere, hele borden, nooit van onder een lock vandaan; drie snelle scores laten één snapshot wachten; en aan het eind van een quiz krijgt elke kijker de laatste snapshot toegestuurd, wordt hij daarna één keer gesloten met 1000, en gaat finished pas omhoog als niemand meer wacht. Een mislukte, een dode en een crashende kijker verlaten elk het register. De antwoorden aan de scorebijhouder, de tokencontrole, de ruimtelimiet, het adres, de grenzen, de eindes en de exitstatussen van de server, de uitvoer en de exitstatussen van de client, en de feiten van je contract worden gecontroleerd zoals eerder, en het token verschijnt nergens. Daarna draaien je tests tegen negen versies die elk één realistische fout maken: de acht van les 4 en een kijker die sluit met 1000 zonder de laatste snapshot te sturen.

Optioneel: draai het op je eigen computer

Dit deel wordt niet beoordeeld, en niets op de site ziet het: geen enkele les vraagt er een resultaat van, en er wordt niets teruggestuurd. Als je je scorebord buiten de browser wilt zien werken, klik je op Download project, pak je de ZIP uit in een nieuwe, lege map, en open je een in die map. Je hebt Python 3.11 of nieuwer nodig, omdat websockets 17.4 dat vereist; elk tabblad begint met een controle. Is het nummer lager, installeer dan een nieuwere Python van python.org en gebruik de naam daarvan, zoals python3.13 (of py -3.13 op Windows), overal waar de opdrachten python3 (of py) zeggen. De README.md van het project bevat dezelfde opdrachten.

Controleer de versie, richt eenmalig alles in, en start daarna de server in de eerste terminal:

python3 --version
python3 -m venv .venv
./.venv/bin/python -m pip install -r requirements.txt
./.venv/bin/python -m pytest -q
./.venv/bin/python scoreboard_server.py --max-updates 2 --deadline 60

Kijk mee in een tweede terminal in dezelfde map; scoor in een derde twee keer:

./.venv/bin/python scoreboard_client.py watch --url ws://127.0.0.1:8765/watch --max-messages 3 --wait 60
./.venv/bin/python scoreboard_client.py score --url ws://127.0.0.1:8765/score --team Owls --points 3
./.venv/bin/python scoreboard_client.py score --url ws://127.0.0.1:8765/score --team Foxes --points 2

Elke opdracht noemt de Python binnen .venv, dus er hoeft niets geactiveerd te worden. Drie terminals, omdat watch zijn terminal bezet houdt tot zijn laatste snapshot.

terminal 1   server: listening on 127.0.0.1:8765 ... closing line, exit 0
terminal 2   watch: version 0, version 1, version 2, exit 0
terminal 3   score Owls 3: version 1      score Foxes 2: version 2
all three    talk over 127.0.0.1, which never leaves your computer

In woorden: de ene terminal houdt de server, een andere de kijker en een derde de scorebijhouder, en alle drie praten ze alleen op je eigen computer met elkaar. Met een afgemaakte versie op een Mac gaven deze opdrachten deze run: de server drukte listening on 127.0.0.1:8765 af; de twee scores drukten version 1: Owls 3, Foxes 0, Herons 0 en version 2: Owls 3, Foxes 2, Herons 0 af, elk met exitstatus 0; watch drukte versie 0 tot en met 2 af en stopte met 0; en de server drukte zijn afsluitende regel af en stopte met 0. Een watch die daarna werd gestart, drukte could not connect to ws://127.0.0.1:8765/watch af en stopte met 1, omdat er niets meer luisterde.

Waarom heeft de run op je eigen computer drie terminals nodig, en niet twee?

Wat weet Python Land na je run op je eigen computer daarover?

Het afgemaakte scorebord

Je hebt een server met een geschreven contract, grenzen en twee nette eindes, een client die elke weigering en close omzet in een exitstatus, en tests die realistische fouten opmerken. Veel -servers die je zult tegenkomen, zien er wel anders uit dan de jouwe: ze wachten op veel verbindingen met één in plaats van met een thread per verbinding. Hoofdstuk 11 geeft je daar een voorproefje van.

Opdracht

Vervang in tests/test_scoreboard.py de laatste stub, test_the_last_update_is_sent_before_the_close, en houd zijn naam: roep op een bord dat is gemaakt met scoreboard_server.make_board([...], max_updates=1) en dat één score heeft gekregen scoreboard_server.viewer_handler aan met een stand-in waarvan send elk bericht vastlegt en waarvan close(code, reason) de code vastlegt, en controleer daarna dat het laatst verstuurde bericht versie 1 heeft, dat de vastgelegde codes [1000] zijn, en dat board["finished"].is_set(). Houd elke andere test, en houd de code van elke eerdere les correct.

Draai python -m pytest -q totdat elke test slaagt, en draai python pl_ws.py quiz-night. Klik daarna op Check my work. Die controleert het hele eindproject, met stand-ins en zonder thread of : de imports drukken niets af; de enveloppen komen heelhuids heen en terug en weigeren; de kijker bij elke verweving die de controle kan regelen, in een quiz die nog lang niet voorbij is en aan het eind ervan; coalescing; een mislukte, een dode en een crashende kijker; de antwoorden aan de scorebijhouder; de tokencontrole vóór 101 met hmac.compare_digest; de ruimtelimiet; serve_scoreboard en main; watch, score en de meegeleverde client-main; de feiten in SCOREBOARD_PROTOCOL.md; en het token op geen enkele stream, in geen enkele en in geen enkel bericht, allemaal zoals in les 1 tot en met 3. Daarna draait ze je tests in dit proces: één keer tegen je eigen code, waarbij minstens 11 tests moeten worden verzameld en ze allemaal moeten slagen, en daarna één keer met elk van negen vervangen door een versie die één realistische fout maakt, de acht van les 4 en een viewer_handler die sluit met 1000 zonder de laatste snapshot te sturen. Tegen elk ervan moet minstens één test falen. Een run die na 3 seconden nog loopt, wordt gestopt, en tegen een foutieve versie telt dat als opgemerkt; er start geen nieuwe run meer zodra er 8 seconden voorbij zijn. Extra tests, hulpfuncties en aanpassingen aan README.md zijn prima. De optionele run op je eigen computer wordt niet gecontroleerd.