Документация
Всё, что делает инструмент, разложено по вопросам, с которыми люди действительно приходят. 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 с записанным рядом
повреждением. Две вещи отклоняются до записи чего-либо, потому что каждая оставила бы на диске
файл, неверно описанный манифестом:
- файл меньше, чем нужно повреждению, потому что он вышел бы без изменений
-
expected: acceptрядом с повреждением, потому что этому не мог бы соответствовать ни один файл. Пишитеsanitize, если тестируемая система должна починить файл, илиunspecified, если именно этот вопрос вы и задаёте
Третье заранее узнать нельзя. Если повреждение выполняется и не меняет ни одного байта, такой файл отбрасывается, а не записывается - запуск продолжается, сообщает, что это был за файл, и завершается кодом частичного завершения.
Шаг за шагом, с тестом, который читает манифест: как сделать повреждённый файл для тестов.
Как выглядит рецепт?
Рецепт - это файл 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
выводит рецепт, чтобы вы могли отредактировать его. У каждого пресета есть
отдельная страница о том, что он обычно находит, что входит в набор и
какие настройки принимает.
-
Пройдёт ли корректный файл, настолько маленький, насколько позволяет формат?
empty-and-minimal -
Сохранит, покажет и вернёт ли моя система имя файла, которого не ожидала?
filename-handling -
Применяется ли лимит размера ровно там, где он объявлен?
size-boundaries -
Переживёт ли мой импорт таблиц то, что экспортируют настоящие инструменты?
tabular-import -
Знает ли мой читатель, в какой кодировке файл, или угадывает?
text-encoding -
Принимает ли моя форма загрузки то, что должна, и отклоняет ли остальное?
upload-validation
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.
Есть ли десктопное окно?
Да, тот же движок с окном сверху, для тестирования, которое не автоматизируется. Это не урезанная версия: тест сравнивает два интерфейса возможность за возможностью, и всё, что умеет только один из них, должно быть объявлено и обосновано, а не тихо расходиться.
Экраны: одна партия, пресеты, несколько партий одновременно и о программе. Окно показывает, чего стоил бы запуск, прежде чем что-либо записать, отображает ход работы и может быть отменено на полпути без наполовину записанного файла. Файл рецепта оно пока не открывает - рецепты пока дело командной строки, а окно собирает свои партии в форме.