Как сделать повреждённый файл для тестов
Валидатор, которому показывали только здоровые файлы, на самом деле не проверен. Вот как получить файл, намеренно испорченный, выходящий точно того размера, который вы просите, и несущий манифест с указанием, что ваша система должна с ним сделать.
Короткий ответ
tfg generate --format png --size 2mb --damage zero-head --out ./out записывает PNG
ровно в 2097152 байта, первые байты которого нули, а манифест рядом фиксирует, что ваша система
должна его отклонить.
Обычный путь
Почему файл, испорченный вручную, - плохой тест
Обычно берут шестнадцатеричный редактор, скрипт, переворачивающий несколько случайных байтов, или
укорачивают файл через head либо truncate. Один раз это работает, а
потом обходится дорого:
- Каждый раз по-разному. Случайный байт при каждом запуске попадает в новое место, поэтому сбой во вторник в среду может не повториться.
- Меняется размер. Обрезанный файл меньше лимита, под которым он должен был оставаться, поэтому проверка размера отвечает раньше проверки содержимого, и тест проходит по неверной причине.
- Это часто остаётся незамеченным. Простой текст читается и с изменённым байтом посередине, а снисходительная программа чтения изображений просто рисует его, так что файл, который должен быть испорчен, принимается.
- Не сказано, что должно произойти. Файл - это просто байты, и тому, кто будет читать тест позже, придётся гадать, имелось в виду принятие или отклонение.
Что вы получаете
Повреждённый файл остаётся нужного размера
Файл создаётся как обычно и портится потом, по пути на диск. Он сохраняет заданный размер, а та же команда снова записывает те же байты.
tfg generate --format png --size 2mb --damage zero-head --out ./out
tfg generate --format pdf --size 1mb --count 5 --damage zero-head:bytes=16 --out ./broken
Настройки пишутся после двоеточия. Параметр можно повторять, а повреждения применяются в том порядке, в каком вы их записали. Это работает с каждым из 26 форматов.
Что он умеет
Какие бывают повреждения?
Это список, который печатает программа, прочитанный из неё при сборке этой страницы. tfg
damage печатает тот же список, а tfg damage <id> говорит, что
принимает одно из них.
| Повреждение | Что оно делает с байтами | Наименьший файл | Настройки |
|---|---|---|---|
zero-head |
Перезаписывает первые байты файла нулями, не меняя его длину. Большинство программ чтения смотрят сначала туда, поэтому это повреждение замечает почти всё. | 8 | bytes |
zero-head записывает нули поверх начала файла. Большинство программ чтения смотрят
сначала туда, на сигнатуру и заголовок, которые говорят, что это за файл, поэтому замечает почти
любая. У простого текста и журналов сигнатуры нет, и их тоже отклоняют, потому что
последовательность нулевых байтов не является текстом. Меньше четырёх байтов у некоторых
форматов получается повреждение, на которое не жалуется ни одна программа чтения, поэтому
настройка начинается с четырёх.
Что говорит манифест
Манифест, который говорит, что должно произойти
Каждый повреждённый файл получает запись о том, что ваша система должна его отклонить, а рядом записано повреждение:
"expected": {
"outcome": "reject",
"reason": "content_malformed",
"confidence": "certain"
},
"damage": [
{
"type": "zero-head",
"settings": {
"bytes": "8"
}
}
]
Два запроса отклоняются до того, как что-либо записано, потому что каждый оставил бы на диске файл, который манифест описывает неверно:
- файл меньше, чем нужно повреждению, который вышел бы нетронутым
-
expected: acceptрядом с повреждением, потому что ничто не могло бы этого выполнить. Напишитеsanitize, если ваша система должна починить файл, илиunspecified, если именно это вы и проверяете
В рецепте
Здоровые и сломанные файлы за один запуск
Положите оба вида в один рецепт, и манифест несёт ожидание для каждого файла, так что тесту не нужен список, какой файл какой:
version: 1
targets:
- id: healthy
format: pdf
size: 1mb
expected: accept
- id: broken
format: pdf
size: 1mb
damage:
- zero-head
В тесте
Превращаем это в тест
Тест читает манифест и проверяет, что произошедшее совпадает с заявленным. Список имён файлов ему не нужен:
import json, os
directory = "healthy-and-broken"
manifest = json.load(open(os.path.join(directory, "manifest.json")))
for entry in manifest["files"]:
response = upload(os.path.join(directory, entry["path"]))
if entry["expected"]["outcome"] == "reject":
assert not response.ok
else:
assert response.ok
Хороший отказ - это чистый отказ. Сообщение, говорящее, что было не так, - тот ответ, который вам нужен. Ошибка сервера, зависание или наполовину сохранённый файл - тот дефект, ради которого этот тест и существует.
Дальше
Куда идти отсюда
- Пресет upload-validation задаёт форме два других вопроса, о размере и о типе.
- Тестовые файлы в CI запускают такой рецепт в конвейере.
-
Документация содержит каждый параметр
tfg generate.