Testing Files Generator
Русский

Документация

Всё, что делает инструмент, разложено по вопросам, с которыми люди действительно приходят. README в репозитории - полный справочник, и он всегда соответствует скачанной вами сборке.

Какие есть команды?

Каждая делает одно дело:

tfg generate    создать файлы по рецепту или по флагам
tfg validate    проверить рецепт, ничего не записывая
tfg verify      сверить каталог с манифестом
tfg cleanup     удалить файлы, перечисленные в манифесте
tfg recipe fmt  вывести рецепт в устоявшемся виде
tfg preset      собрать набор файлов по именованному тестовому вопросу
tfg formats     перечислить форматы этой сборки
tfg damage      перечислить способы, которыми эта сборка может намеренно испортить файл
tfg tool        небольшие инструменты для уже имеющихся файлов
tfg version     вывести версию инструмента
tfg license     вывести лицензию и что она означает для созданных файлов

Как создать один файл точного размера?

Укажите формат, размер и место назначения. Размеры считаются по 1024, поэтому 2mb - это 2097152 байта. Подойдёт и простое число байт, так что --size 10485761 запрашивает ровно столько.

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

Полезные флаги команды generate:

ФлагЧто делает
--format <id>формат файлов, например txt
--size <size>точный размер каждого файла, например 10mb или простое число байт
--size-range <a-b>размер, выбираемый для каждого файла из диапазона, например 1kb-8kb. Выбор идёт от seed
--boundary <size>три файла вокруг лимита: на байт меньше, сам лимит, на байт больше
--count <n>сколько файлов создать. По умолчанию 1
--name <template>шаблон имени, например invoice_{index:04}.txt
--out <dir>каталог, в который записывать
--seed <n>seed запуска. Один и тот же seed даёт те же байты
--set <k>=<v>настройка формата, можно повторять
--damage <name>намеренно испортить файлы, можно повторять, применяется по порядку. Список выводит tfg damage
--expected <outcome>accept, reject, sanitize или unspecified
--dry-runпосчитать и показать, ничего не записывая
--jsonзаписать манифест в стандартный вывод

Как сделать намеренно сломанный файл?

Любой другой файл, который записывает этот инструмент, корректен по построению, и это отвечает на два из трёх вопросов, которые задаёт проверка загрузки. --damage отвечает на третий - открывается ли файл вообще. Файл создаётся как обычно, а затем портится, поэтому у него остаётся запрошенный размер.

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

Настройки указываются после двоеточия. Флаг можно повторять, и порядок записи - это порядок применения. tfg damage перечисляет, что умеет эта сборка и что принимает каждый вид повреждения.

В рецепте ключ - это список имён или настроек:

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

Повреждённый файл получает в манифесте expected: reject с записанным рядом повреждением. Две вещи отклоняются до записи чего-либо, потому что каждая оставила бы на диске файл, неверно описанный манифестом:

Третье заранее узнать нельзя. Если повреждение выполняется и не меняет ни одного байта, такой файл отбрасывается, а не записывается - запуск продолжается, сообщает, что это был за файл, и завершается кодом частичного завершения.

Шаг за шагом, с тестом, который читает манифест: как сделать повреждённый файл для тестов.

Как выглядит рецепт?

Рецепт - это файл YAML, описывающий целый запуск. Закоммитьте его рядом с тестами, и фикстуры перестанут быть бинарными файлами в вашем репозитории - любой сможет пересоздать их байт в байт из файла в несколько сотен символов.

# 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

Каждой цели нужен ровно один из ключей size, size-range, boundary или contains. Два - это ошибка, и ни одного - тоже. Недопустимый рецепт записывает ни одного файла и сообщает обо всех проблемах сразу, а не только о первой, называя каждый раз настройку, к которой она относится.

Как объявить, что моя система должна делать с файлом?

Краткая форма, когда достаточно исхода, и длинная, когда важна причина:

expected: accept
expected:
  outcome: reject
  reason: size_limit

Исходы - accept, reject, sanitize и unspecified. Причины образуют закрытый список, чтобы отчёт мог по ним группировать: 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 и size_zero.

Причина называет действующее правило, а не вердикт. Поэтому одна и та же причина может стоять под любым исходом - файл на байт меньше лимита получает accept, а правило, о котором речь, всё равно size_limit.

Что в манифесте?

Он записывается рядом с файлами в конце каждого запуска, в том числе прерванного. Одна запись на файл:

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

recipe_hash добавляется, если запуск был по рецепту, а preset с overrides - если по пресету, так что манифест всегда можно отследить до того, что его создало.

Каждая запись также содержит target_id - id цели рецепта, которая создала файл, а summary.by_target считает файлы каждой цели. Рецепт с несколькими целями можно поэтому проверить цель за целью, не читая имена файлов.

Что такое пресет?

Готовый набор файлов, отвечающий на распространённый тестовый вопрос, чтобы вам не приходилось проектировать набор самостоятельно. Пресеты - обычные рецепты внутри, а eject выводит рецепт, чтобы вы могли отредактировать его. У каждого пресета есть отдельная страница о том, что он обычно находит, что входит в набор и какие настройки принимает.

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 сообщает, чего стоил бы набор, прежде чем вы его соберёте, и прямо говорит, когда число - наша временная подстановка, а не ваш лимит.

Что означают коды завершения?

У каждого исхода свой код, машиночитаемый вывод идёт в стандартный вывод, а неудачный запуск ничего туда не печатает. Таблица - замороженный контракт: смена значения кода требует повышения мажорной версии.

Код Значение
0 Всё сработало.
1 Непредвиденная ошибка внутри инструмента.
2 Неверная команда или флаг.
3 Рецепт недопустим.
4 Формат не может сделать то, что запрошено.
5 Не удалось прочитать или записать.
6 Недостаточно места на диске.
7 verify обнаружил расхождение.
8 Запуск завершён, но создано не всё.
130 Прерван с помощью Ctrl+C.
143 Остановлен сигналом, так выглядит тайм-аут в 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

Запуск, остановленный с помощью Ctrl+C, всё равно оставляет манифест и никогда не оставляет наполовину записанный файл, поэтому отменённое задание может быть убрано следующим.

Готовые workflow для GitHub Actions и GitLab CI: как генерировать тестовые файлы в конвейере CI.

Есть ли десктопное окно?

Да, тот же движок с окном сверху, для тестирования, которое не автоматизируется. Это не урезанная версия: тест сравнивает два интерфейса возможность за возможностью, и всё, что умеет только один из них, должно быть объявлено и обосновано, а не тихо расходиться.

Экраны: одна партия, пресеты, несколько партий одновременно и о программе. Окно показывает, чего стоил бы запуск, прежде чем что-либо записать, отображает ход работы и может быть отменено на полпути без наполовину записанного файла. Файл рецепта оно пока не открывает - рецепты пока дело командной строки, а окно собирает свои партии в форме.