Testing Files Generator
Polski

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:

FlagaCo 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-runpolicz i pokaż, nie zapisuj niczego
--jsonwypisz 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:

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.

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.