Testing Files Generator
Deutsch

Dokumentation

Alles, was das Tool kann, geordnet nach den Fragen, mit denen die Leute tatsächlich kommen. Die README im Repository ist die vollständige Referenz und passt immer zu dem Build, den du heruntergeladen hast.

Welche Befehle gibt es?

Jeder tut genau eine Sache:

tfg generate    Dateien erzeugen, aus einem Rezept oder über Flags
tfg validate    ein Rezept prüfen und nichts schreiben
tfg verify      ein Verzeichnis gegen ein Manifest prüfen
tfg cleanup     die Dateien entfernen, die ein Manifest auflistet
tfg recipe fmt  ein Rezept in seiner festen Form ausgeben
tfg preset      ein Set von Dateien aus einer benannten Testfrage erzeugen
tfg formats     die Formate dieses Builds auflisten
tfg damage      die Arten auflisten, wie dieser Build eine Datei absichtlich beschädigen kann
tfg tool        kleine Helfer für Dateien, die du schon hast
tfg version     die Version des Tools ausgeben
tfg license     die Lizenz ausgeben und was sie für erzeugte Dateien bedeutet

Wie erzeuge ich eine einzelne Datei in exakter Größe?

Nenne das Format, die Größe und das Ziel. Größen zählen in 1024ern, 2mb sind also 2097152 Bytes. Eine einfache Byte-Zahl funktioniert auch, --size 10485761 verlangt also genau so viele.

tfg generate --format png --size 2mb --out ./out

Die nützlichen Flags von generate:

FlagWas es tut
--format <id>Format der Dateien, zum Beispiel txt
--size <size>exakte Größe jeder Datei, etwa 10mb oder eine einfache Byte-Zahl
--size-range <a-b>eine Größe, die pro Datei aus einem Bereich gezogen wird, etwa 1kb-8kb. Die Ziehung kommt aus dem Seed
--boundary <size>drei Dateien rund um ein Limit: ein Byte darunter, das Limit, ein Byte darüber
--count <n>wie viele Dateien erzeugt werden. Standard 1
--name <template>Namensvorlage, zum Beispiel invoice_{index:04}.txt
--out <dir>Verzeichnis, in das geschrieben wird
--seed <n>Seed des Laufs. Derselbe Seed ergibt dieselben Bytes
--set <k>=<v>eine Formateinstellung, wiederholbar
--damage <name>Dateien absichtlich beschädigen, wiederholbar und der Reihe nach angewendet. Mit tfg damage bekommst du die Liste
--expected <outcome>accept, reject, sanitize oder unspecified
--dry-runzählen und anzeigen, überhaupt nichts schreiben
--jsondas Manifest auf die Standardausgabe schreiben

Wie erzeuge ich eine absichtlich kaputte Datei?

Jede andere Datei, die dieses Tool schreibt, ist per Konstruktion korrekt, was zwei der drei Fragen beantwortet, die ein Upload-Validator stellt. --damage beantwortet die dritte - lässt sich die Datei überhaupt öffnen. Die Datei wird normal erzeugt und dann beschädigt, sie hat also weiterhin die verlangte Größe.

tfg generate --format png --size 2mb --damage zero-head --out ./out
tfg generate --format png --size 2mb --damage zero-head:bytes=16 --out ./out

Einstellungen stehen hinter einem Doppelpunkt. Das Flag lässt sich wiederholen, und die Reihenfolge, in der du sie schreibst, ist die Reihenfolge, in der sie angewendet werden. tfg damage listet auf, was dieser Build kann und was jede Variante annimmt.

In einem Rezept ist der Schlüssel eine Liste, aus Namen oder aus Einstellungen:

targets:
  - id: broken
    format: png
    count: 5
    size: 2mb
    damage:
      - zero-head
      - type: zero-head
        bytes: 16

Eine beschädigte Datei bekommt im Manifest expected: reject, mit der daneben festgehaltenen Beschädigung. Zwei Dinge werden abgelehnt, bevor etwas geschrieben wird, weil sonst jeweils eine Datei auf die Platte käme, die das Manifest falsch beschreibt:

Eine dritte lässt sich nicht im Voraus wissen. Wenn eine Beschädigung läuft und kein Byte verändert, wird diese Datei verworfen statt geschrieben - der Lauf macht weiter, sagt, um welche Datei es ging, und endet mit dem Exit-Code für teilweise erfolgreiche Läufe.

Schritt für Schritt, mit einem Test, der das Manifest liest: So erzeugen Sie eine beschädigte Datei zum Testen.

Wie sieht ein Rezept aus?

Ein Rezept ist eine YAML-Datei, die einen ganzen Lauf beschreibt. Checke sie neben deinen Tests ein, und die Fixtures sind keine Binärdateien mehr in deinem Repository - jeder kann sie Byte für Byte aus einer Datei von wenigen hundert Zeichen neu erzeugen.

# 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

Jedes Target braucht genau eines von size, size-range, boundary oder contains. Zwei davon sind ein Fehler, und keines ebenfalls. Ein ungültiges Rezept schreibt überhaupt keine Dateien und meldet alle Probleme auf einmal statt nur das erste, jedes mit der Einstellung, um die es geht.

Wie lege ich fest, was mein System mit einer Datei tun soll?

Kurzform, wenn das Ergebnis reicht, Langform, wenn der Grund zählt:

expected: accept
expected:
  outcome: reject
  reason: size_limit

Die Ergebnisse sind accept, reject, sanitize und unspecified. Die Gründe sind eine geschlossene Liste, damit ein Report danach gruppieren kann: 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 und size_zero.

Ein Grund benennt die Regel, um die es geht, nicht das Urteil. Deshalb kann derselbe Grund unter beiden Ergebnissen stehen - eine Datei ein Byte unter einem Limit ist accept, und die Regel, um die es geht, ist trotzdem size_limit.

Was steht im Manifest?

Es wird am Ende jedes Laufs neben die Dateien geschrieben, auch bei einem unterbrochenen Lauf. Ein Eintrag pro Datei:

{
  "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"
      }
    }
  ]
}

Ein recipe_hash kommt hinzu, wenn der Lauf aus einem Rezept stammt, und preset mit overrides, wenn er aus einem Preset stammt, sodass sich ein Manifest immer auf das zurückführen lässt, was es erzeugt hat.

Jeder Eintrag trägt außerdem target_id, die ID des Targets im Rezept, das die Datei erzeugt hat, und summary.by_target zählt, auf wie viele Dateien jedes Target kam. Ein Rezept mit mehreren Targets lässt sich so Target für Target prüfen, ohne Dateinamen zu lesen.

Was ist ein Preset?

Ein fertiges Set aus Dateien, das eine gängige Testfrage beantwortet, damit du das Set nicht selbst entwerfen musst. Presets sind darunter ganz normale Rezepte, und eject gibt das Rezept aus, damit du es von dort aus bearbeiten kannst. Jedes Preset hat eine eigene Seite mit dem, was es meist findet, was im Set steckt und welche Einstellungen es kennt.

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 sagt dir, was das Set kosten würde, bevor du es baust, und sagt unmissverständlich, wenn eine Zahl ein Platzhalter von uns statt ein Limit von dir ist.

Was bedeuten die Exit-Codes?

Jedes Ende hat einen eigenen Code, maschinenlesbare Ausgabe geht auf die Standardausgabe, und ein fehlgeschlagener Lauf gibt dort nichts aus. Die Tabelle ist ein eingefrorener Vertrag - die Bedeutung eines Codes zu ändern erfordert einen Major-Versionssprung.

Code Bedeutung
0 Alles hat funktioniert.
1 Ein unerwarteter Fehler im Tool.
2 Falscher Befehl oder falsches Flag.
3 Das Rezept ist ungültig.
4 Das Format kann nicht, was verlangt wurde.
5 Lesen oder Schreiben ist fehlgeschlagen.
6 Nicht genug Speicherplatz.
7 verify hat eine Abweichung gefunden.
8 Der Lauf ist beendet, aber nicht alles wurde erzeugt.
130 Mit Strg+C abgebrochen.
143 Durch ein Signal beendet, so sieht ein CI-Timeout aus.
- 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

Ein mit Strg+C gestoppter Lauf hinterlässt trotzdem ein Manifest und nie eine halb geschriebene Datei, sodass ein abgebrochener Job vom nächsten noch aufgeräumt werden kann.

Fertige Workflows für GitHub Actions und GitLab CI: So erzeugen Sie Testdateien in einer CI-Pipeline.

Gibt es ein Desktop-Fenster?

Ja, dieselbe Engine mit einem Fenster davor, für das Testen, das nicht skriptbar ist. Es ist keine abgespeckte Version: Ein Test vergleicht die beiden Oberflächen Fähigkeit für Fähigkeit, und alles, was nur eine von beiden kann, muss deklariert und begründet werden, statt unbemerkt auseinanderzudriften.

Die Bildschirme sind ein Stapel, Presets, mehrere Stapel gleichzeitig und Info. Es zeigt, was ein Lauf kosten würde, bevor etwas geschrieben wird, meldet den Fortschritt während des Laufs und lässt sich mittendrin abbrechen, ohne eine halb geschriebene Datei zu hinterlassen. Eine Rezeptdatei öffnet es noch nicht - Rezepte gibt es vorerst nur auf der Kommandozeile, und das Fenster baut seine Stapel im Formular.