Testing Files Generator
Português (Brasil)

Documentação

Tudo o que a ferramenta faz, organizado como as perguntas com que as pessoas realmente chegam. O README do repositório é a referência completa e sempre corresponde à versão que você baixou.

Quais comandos existem?

Cada um faz uma única coisa:

tfg generate    produzir arquivos, a partir de uma receita ou de opções
tfg validate    verificar uma receita sem escrever nada
tfg verify      verificar um diretório contra um manifesto
tfg cleanup     remover os arquivos que um manifesto lista
tfg recipe fmt  imprimir uma receita na sua forma normalizada
tfg preset      montar um conjunto de arquivos a partir de uma pergunta de teste nomeada
tfg formats     listar os formatos que esta versão suporta
tfg damage      listar as formas como esta versão pode quebrar um arquivo de propósito
tfg tool        pequenas utilidades para arquivos que você já tem
tfg version     imprimir a versão da ferramenta
tfg license     imprimir a licença e o que ela significa para os arquivos gerados

Como gero um único arquivo de tamanho exato?

Informe o formato, o tamanho e o destino. Os tamanhos contam de 1024 em 1024, então 2mb são 2097152 bytes. Uma contagem simples de bytes também funciona, então --size 10485761 pede exatamente essa quantidade.

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

As opções úteis de generate:

OpçãoO que faz
--format <id>formato dos arquivos, por exemplo txt
--size <size>tamanho exato de cada arquivo, como 10mb ou uma contagem simples de bytes
--size-range <a-b>um tamanho sorteado por arquivo dentro de um intervalo, como 1kb-8kb. O sorteio vem do seed
--boundary <size>três arquivos ao redor de um limite: um byte abaixo, o limite, um byte acima
--count <n>quantos arquivos produzir. Padrão 1
--name <template>modelo de nome, por exemplo invoice_{index:04}.txt
--out <dir>diretório onde escrever
--seed <n>seed da execução. O mesmo seed dá os mesmos bytes
--set <k>=<v>uma configuração de formato, repetível
--damage <name>quebrar os arquivos de propósito, repetível e aplicado em ordem. Rode tfg damage para ver a lista
--expected <outcome>accept, reject, sanitize ou unspecified
--dry-runcontar e mostrar, sem escrever absolutamente nada
--jsonescrever o manifesto na saída padrão

Como faço um arquivo quebrado de propósito?

Todo outro arquivo que esta ferramenta escreve é correto por construção, o que responde a duas das três perguntas que um validador de upload faz. --damage responde à terceira - se o arquivo abre, afinal. O arquivo é produzido normalmente e depois quebrado, então continua com o tamanho que você pediu.

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

As configurações vão depois de dois-pontos. A opção se repete, e a ordem em que você as escreve é a ordem em que são aplicadas. tfg damage lista o que esta versão pode fazer e o que cada uma aceita.

Em uma receita a chave é uma lista, de nomes ou de configurações:

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

Um arquivo danificado recebe expected: reject no manifesto, com o dano registrado ao lado. Duas coisas são recusadas antes de escrever qualquer coisa, porque cada uma deixaria no disco um arquivo que o manifesto descreve errado:

Uma terceira não pode ser conhecida de antemão. Se um dano roda e não move nenhum byte, esse arquivo é descartado em vez de escrito - a execução continua, diz qual arquivo foi e termina com o código de saída parcial.

Passo a passo, com um teste que lê o manifesto: como criar um arquivo corrompido para testes.

Como é uma receita?

Uma receita é um arquivo YAML que descreve uma execução inteira. Versione-a ao lado dos seus testes e as fixtures deixam de ser binários no seu repositório - qualquer pessoa pode reconstruí-las, byte a byte, a partir de um arquivo de algumas centenas de caracteres.

# 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

Cada target precisa de exatamente uma destas chaves: size, size-range, boundary ou contains. Duas é um erro e nenhuma também. Uma receita inválida escreve nenhum arquivo e relata todos os problemas de uma vez em vez de só o primeiro, cada um nomeando a configuração a que se refere.

Como declaro o que meu sistema deve fazer com um arquivo?

Forma curta quando o resultado basta, forma longa quando o motivo importa:

expected: accept
expected:
  outcome: reject
  reason: size_limit

Os resultados são accept, reject, sanitize e unspecified. Os motivos são uma lista fechada para que um relatório possa agrupar por eles: 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 e size_zero.

Um motivo nomeia a regra em jogo, não o veredito. Por isso o mesmo motivo pode ficar sob qualquer um dos resultados - um arquivo um byte abaixo de um limite é accept, e a regra de que se trata continua sendo size_limit.

O que há no manifesto?

Ele é escrito ao lado dos arquivos no fim de cada execução, inclusive de uma execução interrompida. Uma entrada por arquivo:

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

Um recipe_hash é adicionado quando a execução veio de uma receita, e preset com overrides quando veio de um preset, de modo que um manifesto sempre pode ser rastreado até o que o produziu.

Cada entrada também traz target_id, o id do target da receita que produziu o arquivo, e summary.by_target conta os arquivos a que cada target chegou. Uma receita com vários targets pode assim ser verificada target por target sem ler nomes de arquivo.

O que é um preset?

Um conjunto de arquivos pronto que responde a uma pergunta de teste comum, para que você não precise desenhar o conjunto. Presets são receitas comuns por baixo, e eject imprime a receita para você editá-la a partir dali. Cada preset tem uma página própria com o que costuma encontrar, o que há no conjunto e cada configuração que aceita.

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 diz quanto o conjunto custaria antes de você montá-lo, e diz abertamente quando um número é um valor provisório nosso, e não um limite seu.

O que significam os códigos de saída?

Cada término tem seu próprio código, a saída legível por máquina vai para a saída padrão, e uma execução com falha não imprime nada lá. A tabela é um contrato congelado - mudar o que um código significa exige uma versão maior.

Código Significado
0 Tudo funcionou.
1 Um erro inesperado dentro da ferramenta.
2 Comando ou opção incorretos.
3 A receita não é válida.
4 O formato não consegue fazer o que foi pedido.
5 Uma leitura ou escrita falhou.
6 Espaço em disco insuficiente.
7 verify encontrou uma divergência.
8 A execução terminou, mas nem tudo foi produzido.
130 Interrompido com Ctrl+C.
143 Encerrado por um sinal, que é a cara de um timeout de 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

Uma execução parada com Ctrl+C ainda deixa um manifesto e nunca deixa um arquivo pela metade, então um job cancelado ainda pode ser limpo pelo seguinte.

Workflows prontos para GitHub Actions e GitLab CI: como gerar arquivos de teste em um pipeline de CI.

Existe uma janela de desktop?

Sim, o mesmo motor com uma janela por cima, para o teste que não é automatizado. Não é uma versão reduzida: um teste compara as duas interfaces capacidade por capacidade, e tudo que só uma delas pode fazer precisa ser declarado e justificado em vez de divergir em silêncio.

As telas são um lote, presets, vários lotes de uma vez e Sobre. Ela mostra quanto uma execução custaria antes de escrever qualquer coisa, informa o progresso enquanto roda e pode ser cancelada no meio sem deixar um arquivo pela metade. Ainda não abre um arquivo de receita - por enquanto receitas são coisa de linha de comando, e a janela monta seus lotes no formulário.