Jak zrobić uszkodzony plik do testów
Walidator, któremu pokazywano tylko zdrowe pliki, nie został naprawdę przetestowany. Oto jak dostać plik zepsuty celowo, o dokładnie takim rozmiarze, jaki podasz, z manifestem mówiącym, co system ma z nim zrobić.
Krótka odpowiedź
tfg generate --format png --size 2mb --damage zero-head --out ./out zapisuje PNG o
dokładnie 2097152 bajtach, którego pierwsze bajty są zerami, a manifest obok zapisuje, że system
powinien go odrzucić.
Zwykła droga
Dlaczego plik uszkodzony ręcznie to kiepski test
Zwykle używa się edytora szesnastkowego, skryptu zmieniającego kilka losowych bajtów albo ucięcia
pliku przez head lub truncate. Za pierwszym razem działa, a potem
kosztuje:
- Za każdym razem jest inaczej. Losowy bajt trafia w nowe miejsce przy każdym przebiegu, więc błąd z wtorku może nie wrócić w środę.
- Zmienia rozmiar. Plik ucięty jest mniejszy niż limit, pod którym miał się mieścić, więc kontrola rozmiaru odpowiada przed kontrolą zawartości, a test przechodzi z niewłaściwego powodu.
- Często nikt tego nie zauważa. Zwykły tekst da się czytać mimo zmienionego bajtu w środku, a pobłażliwy czytnik obrazów po prostu go narysuje, więc plik, który miał być zepsuty, zostaje przyjęty.
- Nic nie mówi o tym, co ma się stać. Plik to same bajty, a ten, kto później czyta test, musi zgadywać, czy chodziło o przyjęcie, czy o odrzucenie.
Co dostajesz
Uszkodzony plik ma nadal rozmiar, o który prosiłeś
Plik jest generowany normalnie, a psuty dopiero w drodze na dysk. Zachowuje rozmiar, który podałeś, a to samo polecenie zapisuje znowu te same bajty.
tfg generate --format png --size 2mb --damage zero-head --out ./out
tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken
Ustawienia podaje się po dwukropku. Flagę można powtarzać, a uszkodzenia są stosowane w kolejności, w jakiej je zapiszesz. Działa z każdym z 26 formatów.
Co potrafi
Jakie są uszkodzenia?
To lista, którą wypisuje program, odczytana z niego w chwili budowania tej strony. tfg
damage wypisuje to samo, a tfg damage <id> mówi, co przyjmuje jedno z
nich.
| Uszkodzenie | Co robi z bajtami | Najmniejszy plik | Ustawienia |
|---|---|---|---|
zero-head |
Nadpisuje pierwsze bajty pliku zerami, nie zmieniając jego długości. Większość czytników zagląda tam najpierw, więc to uszkodzenie zauważy prawie wszystko. | 8 | bytes |
zero-head zapisuje zera na początku pliku. Większość czytników zagląda tam najpierw, w
sygnaturę i nagłówek mówiące, czym jest plik, więc zauważy to prawie każdy czytnik. Zwykły tekst
i logi nie mają sygnatury i też są odrzucane, bo ciąg zerowych bajtów nie jest tekstem. Poniżej
czterech bajtów niektóre formaty wychodzą z uszkodzeniem, na które nie skarży się żaden czytnik,
dlatego ustawienie zaczyna się od czterech.
Co mówi manifest
Manifest, który mówi, co ma się stać
Każdy uszkodzony plik dostaje wpis mówiący, że system powinien go odrzucić, a obok zapisane jest uszkodzenie:
"expected": {
"outcome": "reject",
"reason": "content_malformed",
"confidence": "certain"
},
"damage": [
{
"type": "zero-head",
"settings": {
"bytes": "8"
}
}
]
Dwie prośby są odrzucane, zanim cokolwiek powstanie, bo każda zostawiłaby na dysku plik, który manifest opisuje błędnie:
- 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 ma plik naprawić, albounspecified, jeśli właśnie o to pytasz
W przepisie
Zdrowe i zepsute pliki w jednym przebiegu
Włóż jedno i drugie do jednego przepisu, a manifest niesie oczekiwanie każdego pliku, więc test nie potrzebuje listy, który jest który:
version: 1
targets:
- id: healthy
format: pdf
size: 1mb
expected: accept
- id: broken
format: pdf
size: 1mb
damage:
- zero-head
W teście
Zamiana tego w test
Test czyta manifest i sprawdza, czy to, co się stało, jest tym, co zadeklarowano. Nie potrzebuje listy nazw plików:
import json, os
directory = "healthy-and-broken"
manifest = json.load(open(os.path.join(directory, "manifest.json")))
for entry in manifest["files"]:
response = upload(os.path.join(directory, entry["path"]))
if entry["expected"]["outcome"] == "reject":
assert not response.ok
else:
assert response.ok
Dobre odrzucenie jest czyste. Komunikat mówiący, co było nie tak, to odpowiedź, o którą chodzi. Błąd serwera, zawieszenie albo plik zapisany do połowy to wada, którą ten test ma znaleźć.
Dalej
Dokąd pójść stąd
- Preset upload-validation zadaje formularzowi pozostałe dwa pytania, o rozmiar i o typ.
- Pliki testowe w CI uruchamiają taki przepis w potoku.
-
Dokumentacja ma każdą flagę
tfg generate.