Blog ·
Verde fiindcă nu putea ajunge acolo
Testul nostru de contract răspundea la cea mai obișnuită comandă care există cu „neacceptat”. Fiecare test care ținea de el era verde din motivul greșit.
De Provibr
Ținem un agent surogat. Vorbește același protocol ca cel adevărat, rulează lângă poartă și există pentru ca partea de platformă a fiecărei comenzi să poată fi testată fără să atingem hipervizorul unui client. Este un test de contract cu un proces în jurul lui.
Până săptămâna aceasta, acel surogat răspundea la cea mai obișnuită comandă care există, stinge mașina aceea, cu COMMAND_STATUS_UNSUPPORTED.
Nu observase nimeni, iar motivul pentru care nu observase nimeni este rostul articolului acestuia. Fiecare test care trimitea o comandă de alimentare către surogat era verde. Erau verzi fiindcă comanda era refuzată, platforma trata refuzul corect, iar afirmația de la sfârșit se ținea. Testele nu mințeau. Descriau o cale pe care nu voise nimeni să o parcurgă.
Ce face o ramură lipsă testelor din jurul ei
Motorul de fluxuri are un nod de acțiune: rulează o comandă față de un server, apoi urmează firul ok sau firul error, după cum a mers. Nodul acela era în produs de la runda care l-a introdus și avea teste.
Acele teste puteau ajunge numai la firul error. Nu din pricina unei greșeli din ele, ci fiindcă singurul agent de care dispuneau refuza fiecare comandă pe care o puteau trimite. Un test care afirmă „s-a luat ramura de eroare” trece foarte bine atunci când eroarea a fost fabricată de bancul de test și nu de lucrul aflat sub test.
Aceasta este forma despre care ne previn propriile noastre notițe, într-o propoziție care stă în manualul proiectului de luni de zile: un test poate fi verde fiindcă nu poate ajunge la calea bună. Varianta care ne-a mușcat aici este mai rea decât cea obișnuită, fiindcă nu este cazul unei ramuri care nu a fost scrisă niciodată. Ramura fusese scrisă, funcționează și a avut tot timpul un test trecut lângă ea.
Surogatul trebuia să țină o stare, nu doar să răspundă
Adăugarea celor cinci comenzi de alimentare nu a fost o chestiune de a întoarce succes. Platforma hotărăște ce are voie să facă o comandă citind starea de acum a serverului: nu puteți relua o mașină care rulează, nu puteți porni una care este deja pornită. Hotărârea aceea trăiește în platformă și citește o coloană.
Un agent care răspunde „gata” fără să schimbe nimic lasă coloana aceea unde era. Așa că un test poate trimite exact o comandă și după aceea se blochează, fiindcă a doua comandă este refuzată de mașina de stări a platformei, pe drept, pe o stare care nu s-a mișcat niciodată.
Prin urmare, surogatul ține o stare de alimentare pentru fiecare mașină și trimite observația de stare înapoi peste sesiune, în același fel în care o face un agent adevărat. Asta lipsea de fapt. Nu un răspuns, ci o urmare.
Cu asta la locul ei, un flux care parcurge o listă în buclă și dă câte două comenzi la fiecare trecere a putut fi rulat pentru prima oară față de surogat. Șase comenzi, toate ajunse la starea de încheiat, și o buclă care a ajuns la a doua trecere fiindcă prima chiar schimbase ceva.
Două lucruri pe care surogatul dinadins nu le face
Nu refuză tranzițiile imposibile cu un cod de eroare inventat. Granița dintre îngăduit și neîngăduit trăiește în platformă, iar codurile de motiv ale agentului sunt o listă fixă, compilată în sursa agentului adevărat. Să născocim un cod pe care agentul adevărat nu îl are ar însemna să testăm un contract care nu există. Un surogat mai strict decât lucrul în locul căruia stă vă învață lucruri care nu sunt adevărate.
Nici durata nu o simulează. O oprire pe un oaspete adevărat așteaptă ca sistemul de operare să răspundă și cade înapoi pe o oprire forțată după un timp de așteptare. În surogat totul se petrece înăuntrul tactului. Dar timpul de așteptare și indicatorul de oprire forțată sunt purtate mai departe în ieșire, deci un test poate vedea că au sosit, chiar dacă nu le-a așteptat nimeni. A trece un parametru mai departe și a-l respecta sunt afirmații diferite, iar aici se face numai una dintre ele.
Un al doilea ceas deșteptător face măsurătoarea întâmplătoare
Cealaltă reparație din aceeași rundă ține de măsurătoare, nu de produs.
Rulările puse în pauză sunt trezite de un lucrător din poartă, care interoghează o coadă la fiecare zece secunde. Scriptul nostru cap la cap face el însuși trezirea, în proces, ca să poată afirma exact ce s-a întâmplat. Cele două sunt în regulă separat și dezastruoase împreună: dacă o poartă cu trezitorul pornit rulează față de aceeași bază de date de dezvoltare, ea înghite rândurile de îndată ce le trece ora de trezire, iar scriptul îl măsoară pe cel dintre cei doi care a ajuns primul acolo.
S-a și întâmplat. O ramură a scriptului a eșuat o dată fără să aibă nimic în neregulă, ceea ce este cel mai scump fel de eșec, fiindcă răspunsul firesc este să plecați în căutarea unei greșeli.
Reparația nu este un comentariu care spune „aveți grijă să nu ruleze niciun lucrător”. Este o funcție care măsoară lucrul acesta. Înainte de orice altceva, scriptul creează o așteptare de unică folosință cu o oră de trezire deja trecută, o urmărește vreme de opt secunde și verifică dacă a atins-o altcineva. Dacă a atins-o cineva, scriptul refuză să ruleze și spune de ce.
Este puțin cod pentru multă încredere. O presupunere scrisă într-un comentariu este o presupunere; aceeași presupunere scrisă ca o verificare care rulează prima este o precondiție.
Reîncercările au nevoie de un plafon și de o pauză care crește
Acum că, în sfârșit, se putea ajunge la căile bune, au fost măsurate ca lumea și căile de eșec.
O așteptare care aruncă o eroare la reluare, fiindcă un șablon a fost șters sau un server nu mai există, ar fi altfel reîncercată la fiecare tact la nesfârșit, într-un tabel de coadă care nu se golește niciodată. Există un plafon de cinci încercări, cu o pauză care se dublează: șaizeci, o sută douăzeci, două sute patruzeci, patru sute optzeci de secunde. Cele patru intervale au fost citite dintr-o rulare adevărată și nu din funcția care le calculează. La a cincea încercare, așteptarea este anulată, iar rularea este însemnată ca eșuată.
Contraproba este partea care contează. Cu garda de epuizare oprită, aceeași rulare rămâne în starea de așteptare la nesfârșit. O gardă este dovedită abia atunci când situația pe care o împiedică a fost chiar produsă.
Sub ea se află un al doilea fel de eșec. Un lucrător revendică o așteptare mutând-o din starea de așteptare înainte să înceapă să parcurgă, fiindcă efectele secundare ale unui flux se petrec în timpul parcurgerii și nu după ea. Dacă procesul care a revendicat-o moare la jumătatea parcurgerii, rândul stă în starea de revendicare pentru totdeauna, iar interogarea care caută așteptări scadente nu îl vede: ea cere rânduri aflate în starea de așteptare. Un baleiaj pune înapoi revendicările mai vechi de cincisprezece minute, iar un singur tact față de o poartă care chiar rula a eliberat una, a trezit una și a reluat una, fără niciun eșec.
Câteva lucruri mărunte care se pot găsi numai rulându-le
O întârziere de trei secunde nu face un nod nevalid. Face nevalid întregul graf, fiindcă minimul este cinci, iar executorul nu întoarce absolut nimic în loc să întoarcă o rulare cu un nod stricat. Asta este corect și este totodată derutant prima oară, fiindcă vă așteptați la o eroare despre un nod și primiți tăcere despre un graf.
Porturile porții sunt numite după poartă. Dacă puneți variabilele fără prefixul acela, ea rămâne să asculte pe valorile ei implicite, în tăcere, iar asta arată exact ca o problemă de rețea.
O oprire scrie în registru nodurile la care nu a ajuns niciodată, ca sărite. Aceasta este o deosebire dinadinsă față de a le lăsa pe dinafară: o rulare care s-a încheiat devreme și o rulare care a luat altă cale arată la fel dacă nodurile neatinse pur și simplu lipsesc.
Iar un nod de aprobare lăsa rânduri orfane în tabelul de notificări, șaisprezece la număr pe baza de date de dezvoltare. Curățenia pentru asta se face pentru fiecare rulare în parte și pe cheia de deduplicare, niciodată printr-o potrivire de tipar pe un prefix, fiindcă o potrivire de tipar ar scoate rânduri care aparțin rundei altcuiva.
Ce am consemna
- Un test care nu poate ajunge la calea bună este verde din motivul greșit. Întrebați-vă ce ar face testul dacă funcția ar fi ștearsă.
- Un surogat care refuză totul nu este un surogat, este un zid. Fiecare test din spatele lui măsoară refuzul.
- O dublură trebuie să țină o stare, nu doar să răspundă. Dacă platforma hotărăște pe o coloană, ceva trebuie să miște coloana aceea.
- Nu faceți un surogat mai strict decât lucrul adevărat. Un cod de eroare inventat testează un contract pe care nu îl are nimeni.
- Treceți un parametru mai departe chiar și atunci când nu îl puteți respecta, ca un test să poată dovedi că a sosit.
- Doi trezitori pe o singură bază de date fac măsurătoarea întâmplătoare. Unul dintre ei câștigă și nu puteți spune care.
- Prefaceți precondiția din comentariu într-o verificare care rulează prima. Atunci este un fapt și nu o nădejde.
- Dovediți o gardă oprind-o. Un plafon de reîncercări pe care nu l-a atins nimeni este o afirmație.
- Un rând revendicat este invizibil pentru interogarea care caută lucru scadent. Ceva trebuie să pună înapoi revendicările învechite.
- Nodurile neatinse își au locul în registru ca sărite, altfel o ieșire timpurie nu se deosebește de un alt traseu.
Ca să fie limpede ce este aceasta: șapte ramuri nedovedite până acum au fost parcurse cap la cap, fiecare cu o contraprobă roșie; verificarea de îmbinare a rulat 16 din 16 pași fără nicio linie de eșec, suita web a fost 4583 verde, pachetele, poarta și uneltele împreună 7287 verde, suita proprie a agentului surogat 481 verde, scriptul cap la cap 89 verde, ramura trezitorului 13 verde, ramura de e-mail a aprobării 22 verde, iar bucla cu acțiuni adevărate 18 verde. Nedovedite: nu s-a rulat nimic față de un Proxmox adevărat, un graf care nu se poate citi chiar pe rândul de așteptare, un lot mai mare decât dimensiunea lotului și livrarea adevărată de e-mail prin SMTP.
Din munca de platformă la Provibr. Cum a învățat parcurgerea să se oprească și să continue mâine se află în fiecare autoapel era în poziție de coadă, iar unde stă granița dintre o pauză pe care o cerere o poate absorbi și una care suspendă rularea se află în o pauză care ținea comanda deschisă.