Блог ·
Зелений, бо не міг туди дістатися
Наш контрактний тест відповідав на найзвичайніший наказ кодом «не підтримується». Кожен тест, що залежав від нього, був зеленим не з тієї причини.
Автор: Provibr
Ми тримаємо агента-заглушку. Він говорить тим самим протоколом, що й справжній, він працює поруч зі шлюзом, і він існує для того, щоб платформний бік кожного наказу можна було випробувати, не чіпаючи гіпервізора клієнта. Це є контрактним тестом із процесом навколо нього.
До цього тижня та заглушка відповідала на найзвичайніший наказ, який лише буває, вимкни ту машину, кодом COMMAND_STATUS_UNSUPPORTED.
Ніхто цього не помітив, і причина, чому ніхто не помітив, і є суттю цієї статті. Кожен тест, що надсилав заглушці наказ живлення, був зеленим. Вони були зеленими тому, що наказ відхиляли, платформа правильно опрацьовувала відмову, і твердження в кінці справджувалося. Тести не брехали. Вони описували шлях, яким ніхто не збирався ходити.
Що робить відсутня гілка з тестами навколо неї
У рушія потоків є вузол дії: виконати наказ проти сервера, а тоді піти дротом ok або дротом error залежно від того, як усе минуло. Той вузол був у продукті від раунду, що його запровадив, і мав тести.
Ті тести могли дістатися лише дроту error. Не через ваду в них, а тому, що єдиний доступний їм агент відхиляв кожен наказ, який вони могли надіслати. Тест, що стверджує «взято гілку помилки», чудово проходить, коли ту помилку виготовив випробувальний стенд, а не річ, яку випробовують.
Це є саме тією формою, про яку застерігають наші власні нотатки, реченням, що стоїть у настанові проєкту вже місяцями: тест може бути зеленим тому, що не може дістатися доброго шляху. Різновид, що вкусив нас тут, є гіршим за звичайний, бо річ не в тім, що гілки ніколи не написали. Гілку написали, вона працює, і поруч із нею весь цей час стояв тест, що проходив.
Заглушка мусила тримати стан, а не лише відповідати
Додати пʼять наказів живлення означало не просто повертати успіх. Платформа вирішує, що наказові дозволено робити, читаючи поточний стан сервера: не можна поновити машину, яка працює, не можна запустити ту, що вже піднята. Це рішення живе в платформі, і воно читає стовпець.
Агент, що відповідає «зроблено», нічого не змінюючи, лишає той стовпець там, де він був. Тож тест може надіслати рівно один наказ, а далі він застряг, бо другий наказ відхиляє власний автомат станів платформи, правильно, на статусі, який нікуди не зрушив.
Тому заглушка тримає стан живлення на кожну машину й надсилає спостереження стану назад через сесію, так само як це робить справжній агент. Саме цього насправді бракувало. Не відповіді, а наслідку.
Коли це стало на місце, потік, що йде циклом по списку і видає два накази за прохід, уперше вдалося прогнати проти заглушки. Шість наказів, усі дійшли до завершення, і цикл, що дістався другого проходу, бо перший прохід справді щось змінив.
Дві речі, яких заглушка навмисно не робить
Вона не відхиляє неможливих переходів вигаданим кодом помилки. Межа між дозволеним і недозволеним живе в платформі, а коди причин агента є сталим списком, вкомпільованим у джерело справжнього агента. Вигадати код, якого справжній агент не має, означало б випробовувати контракт, якого не існує. Заглушка, суворіша за річ, яку вона заступає, навчає вас речей, що не є правдою.
Вона також не вдає тривалості. Завершення роботи справжнього гостя чекає на відповідь операційної системи і після спливу строку відступає до жорсткої зупинки. У заглушці все стається всередині такту. Але сплив строку й прапорець відступу протягнуто у вивід, тож тест може побачити, що вони надійшли, хоч ніщо на них і не чекало. Протягнути параметр і зважити на нього є різними твердженнями, і робиться лише одне з них.
Другий будильник робить вимірювання випадковим
Друге виправлення того самого раунду стосується вимірювання, а не продукту.
Виконання на паузі будить робітник у шлюзі, що опитує чергу раз на десять секунд. Наш наскрізний скрипт будить їх сам, усередині процесу, щоб мати змогу стверджувати точно, що сталося. Окремо ці двоє є нормальними, а разом катастрофічними: якщо проти тієї самої бази даних розробки працює шлюз із увімкненим будильником, він зʼїдає рядки, щойно минає їхній час пробудження, і скрипт міряє того з двох, хто дістався туди першим.
Це сталося. Одна гілка скрипта одного разу зазнала невдачі, хоч із нею все було гаразд, а це є найдорожчим різновидом невдачі, бо природною відповіддю є піти шукати ваду.
Виправленням є не коментар зі словами «переконайтеся, що жоден робітник не працює». Ним є функція, що це міряє. Найперше скрипт створює одноразове очікування з уже минулим часом пробудження, стежить за ним вісім секунд і перевіряє, чи не торкнувся його хтось інший. Якщо торкнувся, скрипт відмовляється працювати й каже чому.
Це є малою кількістю коду заради великої кількості довіри. Припущення, записане в коментарі, є припущенням; те саме припущення, записане як перевірка, що йде першою, є передумовою.
Повторам потрібні стеля і зростання паузи
Поки добрі шляхи нарешті стали досяжними, шляхи невдач теж як слід зміряли.
Очікування, що кидає помилку при поновленні, бо шаблон вилучили або сервера більше немає, інакше повторювали б на кожному такті вічно, у таблиці черги, що ніколи не порожніє. Є стеля у пʼять спроб із паузою, що подвоюється: шістдесят, сто двадцять, двісті сорок, чотириста вісімдесят секунд. Ці чотири проміжки вичитали зі справжнього запуску, а не з функції, що їх обчислює. На пʼятій спробі очікування скасовують, а виконання позначають як невдале.
Зустрічним доказом є та частина, що має значення. З вимкненою сторожею вичерпання те саме виконання лишається в стані очікування нескінченно. Сторожа є доведеною лише тоді, коли ситуацію, якій вона запобігає, справді відтворили.
Під цим є другий різновид невдачі. Робітник заявляє очікування, виводячи його зі стану очікування перед тим, як рушити обходом, бо побічні наслідки потоку стаються під час обходу, а не після нього. Якщо процес, що його заявив, помирає посеред обходу, рядок сидить у стані заявки вічно, а запит, що шукає очікування, які настали, його не бачить: він питає про рядки в стані очікування. Замітання повертає назад заявки, старші за пʼятнадцять хвилин, і один такт проти шлюзу, що справді працює, звільнив одне, розбудив одне й поновив одне, без жодної невдачі.
Дрібниці, які можна знайти лише запуском
Затримка на три секунди не робить недійсним вузол. Вона робить недійсним увесь граф, бо мінімумом є пʼять, і виконавець не повертає геть нічого замість виконання зі зламаним вузлом. Це є правильним і водночас збиває з пантелику першого разу, бо ви очікували помилки про вузол, а дістали мовчання про граф.
Порти шлюзу названо за шлюзом. Виставлення змінних без того префікса лишає його слухати на власних усталених значеннях, тихо, а виглядає це точно як негаразд у мережі.
Зупинка записує вузли, до яких так і не дійшла, у журнал як пропущені. Це є навмисною відмінністю від того, щоб їх не вписувати: виконання, що завершилося раніше, і виконання, що пішло іншим шляхом, виглядають однаково, якщо невідвідані вузли просто відсутні.
А вузол погодження лишав по собі осиротілі рядки в таблиці сповіщень, шістнадцять із них у базі даних розробки. Прибирання за цим є прибиранням на кожне виконання і йде за ключем усунення повторів, а ніколи не за збігом зі взірцем на префіксі, бо збіг зі взірцем викосив би рядки, що належать чужому раунду.
Що ми б записали
- Тест, що не може дістатися доброго шляху, є зеленим не з тієї причини. Спитайте, що робив би цей тест, якби можливість вилучили.
- Заглушка, що відхиляє все, є не заглушкою, а стіною. Кожен тест за нею міряє відмову.
- Підробка мусить тримати стан, а не лише відповідати. Якщо платформа вирішує за стовпцем, щось мусить той стовпець рухати.
- Не робіть заглушку суворішою за справжню річ. Вигаданий код помилки випробовує контракт, якого ніхто не втілює.
- Протягуйте параметр навіть тоді, коли не можете на нього зважити, щоб тест міг довести, що він надійшов.
- Два будильники на одній базі даних роблять вимірювання випадковим. Один із них перемагає, і ви не можете сказати який.
- Перетворіть передумову з коментаря на перевірку, що йде першою. Тоді вона є фактом, а не сподіванням.
- Доведіть сторожу, вимкнувши її. Стеля повторів, якої ніхто не сягав, є твердженням.
- Заявлений рядок є невидимим для запиту, що шукає роботу, яка настала. Щось мусить повертати назад застарілі заявки.
- Невідвідані вузли належать до журналу як пропущені, інакше ранній вихід не відрізнити від іншого маршруту.
Для ясності про те, чим це є: сім раніше не доведених гілок пройшли наскрізно, кожну зі зустрічним червоним доказом; перевірка злиття пройшла 16 із 16 кроків без жодного рядка невдачі, набір вебу був зеленим на 4583, пакунки, шлюз і інструменти разом на 7287, власний набір агента-заглушки на 481, наскрізний скрипт на 89, гілка будильника на 13, гілка листа про погодження на 22, а цикл зі справжніми діями на 18. Не доведено: нічого не запускали проти справжнього Proxmox, нечитний граф на самому рядку очікування, партія, більша за розмір партії, і справжня доставка пошти через SMTP.
Із платформної роботи над Provibr. Як обхід навчився спинятися і тривати завтра, описано в Кожен виклик самого себе стояв у хвостовій позиції, а де пролягає межа між паузою, яку може ввібрати запит, і паузою, що призупиняє виконання, описано в Пауза, що тримала замовлення відкритим.