Dokumentacja
Wszystko, co narzędzie robi, ułożone jako pytania, z którymi ludzie naprawdę przychodzą. README w repozytorium jest pełną dokumentacją i zawsze odpowiada wersji, którą pobrałeś.
Jakie są komendy?
Każda robi jedną rzecz:
tfg generate tworzy pliki, z przepisu albo z flag
tfg validate sprawdza przepis i nic nie zapisuje
tfg verify sprawdza katalog względem manifestu
tfg cleanup usuwa pliki wypisane w manifeście
tfg recipe fmt wypisuje przepis w postaci uporządkowanej
tfg preset buduje zestaw plików z nazwanego pytania testowego
tfg formats wypisuje formaty, które ta wersja obsługuje
tfg damage wypisuje sposoby, którymi ta wersja umie celowo zepsuć plik
tfg tool drobne czynności na plikach, które już masz
tfg version wypisuje wersję narzędzia
tfg license wypisuje licencję i to, co znaczy dla wygenerowanych plików
Jak wygenerować jeden plik o dokładnym rozmiarze?
Podaj format, rozmiar i katalog. Rozmiary liczą się po 1024, więc 2mb to 2097152
bajty. Zwykła liczba bajtów też zadziała, więc --size 10485761 prosi dokładnie
o tyle.
tfg generate --format png --size 2mb --out ./out
Przydatne flagi komendy generate:
| Flaga | Co robi |
|---|---|
--format <id> | format plików, na przykład txt |
--size <rozmiar> | dokładny rozmiar każdego pliku, na przykład 10mb albo liczba bajtów |
--size-range <a-b> | rozmiar losowany dla każdego pliku z zakresu, na przykład 1kb-8kb. Losowanie idzie z ziarna |
--boundary <rozmiar> | trzy pliki wokół limitu: bajt pod, dokładnie limit, bajt nad |
--count <n> | ile plików zapisać. Domyślnie 1 |
--name <szablon> | szablon nazwy, na przykład invoice_{index:04}.txt |
--out <katalog> | katalog, do którego trafiają pliki |
--seed <n> | ziarno przebiegu. To samo ziarno daje te same bajty |
--set <k>=<v> | ustawienie formatu, można powtarzać |
--damage <nazwa> | celowo psuje pliki, można powtarzać, stosowane po kolei. Listę wypisuje tfg damage |
--expected <wynik> | accept, reject, sanitize albo unspecified |
--dry-run | policz i pokaż, nie zapisuj niczego |
--json | wypisz manifest na standardowe wyjście |
Jak zrobić plik celowo zepsuty?
Każdy inny plik, który to narzędzie zapisuje, jest poprawny z definicji, co odpowiada na dwa
z trzech pytań walidatora uploadu. --damage odpowiada na trzecie - czy plik w ogóle
się otwiera. Plik powstaje normalnie i dopiero potem zostaje zepsuty, więc dalej ma zamówiony
rozmiar.
tfg generate --format png --size 2mb --damage zero-head --out ./out
tfg generate --format png --size 2mb --damage zero-head:bytes=16 --out ./out
Ustawienia idą po dwukropku. Flagę można powtarzać, a kolejność zapisu jest kolejnością
stosowania. tfg damage wypisuje, co ta wersja umie i co każde uszkodzenie
przyjmuje.
W przepisie klucz jest listą, nazw albo ustawień:
targets:
- id: broken
format: png
count: 5
size: 2mb
damage:
- zero-head
- type: zero-head
bytes: 16
Uszkodzony plik dostaje w manifeście expected: reject, a obok niego zapisane
uszkodzenie. Dwie rzeczy są odmawiane, zanim cokolwiek powstanie, bo każda zostawiłaby na
dysku plik, który manifest opisuje nieprawdziwie:
- plik mniejszy niż potrzebuje uszkodzenie, bo wyszedłby nietknięty
-
expected: acceptobok uszkodzenia, bo nic nie mogłoby tego spełnić. Napiszsanitize, jeśli system pod testem ma plik naprawić, albounspecified, jeśli właśnie o to pytasz
Trzeciej rzeczy nie da się wiedzieć z góry. Jeśli uszkodzenie przebiegnie i nie ruszy ani jednego bajtu, taki plik zostaje odrzucony zamiast zapisany - przebieg idzie dalej, mówi, którego pliku to dotyczyło, i kończy się kodem częściowego wyniku.
Krok po kroku, z testem czytającym manifest: jak zrobić uszkodzony plik do testów.
Jak wygląda przepis?
Przepis to plik YAML opisujący cały przebieg. Commitujesz go obok testów i dane testowe przestają być binariami w repozytorium - każdy może je odtworzyć, co do bajta, z pliku o długości kilkuset znaków.
# fixtures.yaml
version: 1
seed: 7741
defaults:
label: true
targets:
- id: invoices
format: pdf
count: 25
size: 300kb
name: invoice_{index:04}.pdf
properties:
pages: 3
page_size: a4
expected: accept
- id: over_the_limit
format: png
count: 2
size: 12mb
expected:
outcome: reject
reason: size_limit
- id: bundle
format: zip
contains:
- format: txt
count: 200
size: 4kb
output:
dir: ./fixtures
manifest: manifest.json
tfg validate fixtures.yaml
tfg generate fixtures.yaml
Każdy cel potrzebuje dokładnie jednego z: size, size-range,
boundary albo contains. Dwa naraz to błąd, brak też. Niepoprawny przepis
nie tworzy ani jednego pliku i zgłasza wszystkie problemy naraz, a nie pierwszy
z brzegu, przy czym każdy nazywa ustawienie, o które chodzi.
Jak zadeklarować, co system ma zrobić z plikiem?
Krótka forma, gdy wystarczy sam wynik, i długa, gdy liczy się powód:
expected: accept
expected:
outcome: reject
reason: size_limit
Wyniki to accept, reject, sanitize i
unspecified. Powody są zamkniętą listą, żeby raport mógł po nich grupować:
content_malformed, count_limit, dimensions_limit,
duplicate, encoding_invalid, extension_rule,
filename_invalid, filename_too_long, filename_traversal,
malware_signature, mime_mismatch, nesting_depth,
none, size_limit i size_zero.
Powód nazywa regułę, o którą chodzi, a nie werdykt. Dlatego ten sam powód może
stać pod jednym i pod drugim wynikiem - plik bajt pod limitem jest accept, a reguła,
której dotyczy, to dalej size_limit.
Co zawiera manifest?
Powstaje obok plików na koniec każdego przebiegu, także takiego, który został przerwany. Jedna pozycja na plik:
{
"manifest_version": "1.0",
"tool": { "name": "testing-files-generator", "version": "0.4.0" },
"run": {
"id": "run_b359aa8d94",
"seed": 0,
"command": "tfg generate --format png --size 2mb --out ./out",
"platform": { "os": "windows", "arch": "amd64" },
"complete": true
},
"summary": {
"file_count": 1,
"total_bytes": 2097152,
"by_format": { "png": 1 },
"by_expected": { "unspecified": 1 }
},
"files": [
{
"path": "files_0001.png",
"bytes": 2097152,
"format": "png",
"fidelity": "full",
"determinism": "byte",
"seed": "8dc2d18c",
"hashes": { "sha256": "1a1f7c..." },
"properties": { "width": 640, "height": 480 },
"expected": {
"outcome": "unspecified",
"detail": "No expectation was declared for this file.",
"confidence": "policy_dependent"
}
}
]
}
Do tego dochodzi recipe_hash, gdy przebieg pochodził z przepisu, oraz
preset i overrides, gdy pochodził z presetu - żeby manifest zawsze dało
się prześledzić z powrotem do tego, co go wyprodukowało.
Każdy wpis niesie też target_id, czyli id celu z przepisu, który
wyprodukował ten plik, a summary.by_target liczy pliki, które dał każdy cel. Przepis
z kilkoma celami da się dzięki temu sprawdzać cel po celu, bez czytania nazw plików.
Czym jest preset?
To gotowy zestaw plików odpowiadający na częste pytanie testowe, żebyś nie musiał projektować
zestawu samodzielnie. Presety są pod spodem zwykłymi przepisami, a eject wypisuje ten
przepis, więc możesz go od tego miejsca edytować. Każdy preset ma
własną stronę: co zwykle znajduje, co jest w zestawie i jakie ustawienia
przyjmuje.
-
Czy plik poprawny i najmniejszy, na jaki format pozwala, przechodzi?
empty-and-minimal -
Czy mój system zapisze, pokaże i odda nazwę pliku, której się nie spodziewał?
filename-handling -
Czy limit rozmiaru działa dokładnie tam, gdzie jest zadeklarowany?
size-boundaries -
Czy import tabeli poradzi sobie z tym, co eksportują prawdziwe narzędzia?
tabular-import -
Czy mój czytnik wie, w jakim kodowaniu jest plik, czy zgaduje?
text-encoding -
Czy mój formularz przesyłania plików przyjmuje to, co powinien, i odrzuca resztę?
upload-validation
tfg preset list
tfg preset show size-boundaries
tfg generate --preset size-boundaries --limit 10mb --out ./edges
tfg preset eject size-boundaries > my.yaml
show mówi, ile zestaw będzie kosztował, zanim go zbudujesz, i wprost zaznacza, kiedy
liczba jest naszą wartością zastępczą, a nie Twoim limitem.
Co znaczą kody wyjścia?
Każde zakończenie ma własny kod, wyjście czytelne dla maszyny idzie na standardowe wyjście, a nieudany przebieg nie pisze tam nic. Tabela jest zamrożonym kontraktem - zmiana znaczenia kodu wymaga podniesienia wersji głównej.
| Kod | Znaczenie |
|---|---|
0 |
Wszystko się udało. |
1 |
Nieoczekiwany błąd wewnątrz narzędzia. |
2 |
Zła komenda albo flaga. |
3 |
Przepis jest niepoprawny. |
4 |
Format nie potrafi tego, o co poproszono. |
5 |
Odczyt albo zapis się nie powiódł. |
6 |
Za mało miejsca na dysku. |
7 |
verify znalazło niezgodność. |
8 |
Przebieg się zakończył, ale nie wszystko powstało. |
130 |
Przerwane przez Ctrl+C. |
143 |
Zatrzymane sygnałem - tak wygląda przekroczony czas w CI. |
- name: build the fixtures
run: tfg generate fixtures.yaml --out ./fixtures
- name: run the tests
run: pytest tests/
- name: nothing moved
run: tfg verify ./fixtures/manifest.json
Przebieg zatrzymany przez Ctrl+C i tak zostawia manifest i nigdy nie zostawia pliku zapisanego w połowie, więc anulowane zadanie da się posprzątać następnym.
Gotowe workflow dla GitHub Actions i GitLab CI: jak generować pliki testowe w potoku CI.
Czy jest okno?
Tak, ten sam silnik z oknem, do testowania, które nie jest skryptowane. Nie jest okrojoną wersją: test porównuje obie powierzchnie możliwość po możliwości, a wszystko, co potrafi tylko jedna z nich, musi być zadeklarowane i uzasadnione, zamiast po cichu się rozjeżdżać.
Ekrany to pojedynczy wsad, presety, kilka wsadów naraz i informacje o programie. Okno pokazuje, ile przebieg będzie kosztował, zanim cokolwiek zapisze, melduje postęp w trakcie i da się je przerwać w połowie bez zostawiania pliku zapisanego do połowy. Nie wczytuje jeszcze przepisu z pliku - przepisy są na razie sprawą wiersza poleceń, a okno buduje swoje wsady w formularzu.