Cómo crear un archivo corrupto para pruebas
Un validador al que solo se le han mostrado archivos sanos no está realmente probado. Así se consigue un archivo roto a propósito, que sale con exactamente el tamaño que pides y trae un manifiesto que dice qué debe hacer tu sistema con él.
La respuesta corta
tfg generate --format png --size 2mb --damage zero-head --out ./out escribe un PNG de
exactamente 2097152 bytes cuyos primeros bytes son ceros, y el manifiesto que lo acompaña
registra que tu sistema debe rechazarlo.
La forma habitual
Por qué un archivo corrompido a mano es una mala prueba
Lo habitual es un editor hexadecimal, un script que cambia unos cuantos bytes al azar o cortar un
archivo con head o truncate. Funciona una vez y después sale caro:
- Es distinto cada vez. Un byte al azar cae en un sitio nuevo en cada ejecución, así que un fallo del martes puede no volver el miércoles.
- Cambia el tamaño. Un archivo cortado es más pequeño que el límite bajo el que debía quedar, de modo que la comprobación de tamaño responde antes que la del contenido y la prueba pasa por el motivo equivocado.
- A menudo pasa inadvertido. El texto plano se sigue leyendo con un byte cambiado en medio, y un lector de imágenes indulgente simplemente lo dibuja, así que el archivo que debía estar roto se acepta.
- No dice nada de lo que debe ocurrir. El archivo son solo bytes, y quien lea la prueba después tiene que adivinar si se quería la aceptación o el rechazo.
Lo que obtienes
Un archivo dañado conserva el tamaño que pediste
El archivo se genera con normalidad y se rompe después, de camino al disco. Conserva el tamaño que pediste, y el mismo comando vuelve a escribir los mismos bytes.
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
Los ajustes van después de dos puntos. La opción se puede repetir, y los daños se aplican en el orden en que los escribes. Funciona con cada uno de los 26 formatos.
Lo que puede hacer
¿Qué daños hay?
Esta es la lista que imprime el programa, leída de él al construir esta página. tfg
damage imprime la misma, y tfg damage <id> dice qué admite cada uno.
| Daño | Qué hace con los bytes | Archivo más pequeño | Ajustes |
|---|---|---|---|
zero-head |
Sobrescribe los primeros bytes del archivo con ceros sin tocar su longitud. La mayoría de los lectores miran ahí primero, así que casi todo nota este daño. | 8 | bytes |
zero-head escribe ceros sobre el comienzo del archivo. La mayoría de los lectores miran
ahí primero, la firma y la cabecera que dicen qué es el archivo, así que casi cualquier lector
lo nota. El texto plano y los registros no tienen firma y también se rechazan, porque una serie
de bytes nulos no es texto. Por debajo de cuatro bytes algunos formatos salen con un daño del
que ningún lector se queja, por eso el ajuste empieza en cuatro.
Lo que dice el manifiesto
Un manifiesto que dice lo que debe ocurrir
Cada archivo dañado recibe una entrada que dice que tu sistema debe rechazarlo, con el daño anotado al lado:
"expected": {
"outcome": "reject",
"reason": "content_malformed",
"confidence": "certain"
},
"damage": [
{
"type": "zero-head",
"settings": {
"bytes": "8"
}
}
]
Se rechazan dos peticiones antes de escribir nada, porque cada una dejaría en el disco un archivo que el manifiesto describe mal:
- un archivo más pequeño de lo que el daño necesita, que saldría intacto
-
expected: acceptjunto a un daño, porque nada podría cumplirlo. Escribesanitizesi tu sistema debe reparar el archivo, ounspecifiedsi esa es justo la pregunta que haces
En una receta
Archivos sanos y rotos en una sola ejecución
Pon ambos en una receta y el manifiesto lleva lo esperado de cada archivo, así que la prueba no necesita una lista de cuál es cuál:
version: 1
targets:
- id: healthy
format: pdf
size: 1mb
expected: accept
- id: broken
format: pdf
size: 1mb
damage:
- zero-head
En una prueba
Convertirlo en una prueba
La prueba lee el manifiesto y comprueba que lo ocurrido es lo declarado. No necesita una lista de nombres de archivo:
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
Un buen rechazo es un rechazo limpio. Un mensaje que dice qué estaba mal es la respuesta que quieres. Un error de servidor, un bloqueo o un archivo guardado a medias es el defecto que esta prueba existe para encontrar.
Siguiente
A dónde ir desde aquí
- El preset upload-validation hace a un formulario las otras dos preguntas, la del tamaño y la del tipo.
- Archivos de prueba en CI ejecuta una receta como esta en un pipeline.
-
La documentación recoge cada opción de
tfg generate.