Comment fabriquer un fichier corrompu pour les tests
Un validateur à qui l'on n'a jamais montré que des fichiers sains n'a pas vraiment été testé. Voici comment obtenir un fichier cassé volontairement, qui sort à exactement la taille demandée et qui porte un manifeste disant ce que votre système doit en faire.
La réponse courte
tfg generate --format png --size 2mb --damage zero-head --out ./out écrit un PNG
d'exactement 2097152 octets dont les premiers octets sont des zéros, et le manifeste à côté note
que votre système doit le rejeter.
La méthode habituelle
Pourquoi un fichier corrompu à la main fait un mauvais test
Les moyens habituels sont un éditeur hexadécimal, un script qui inverse quelques octets au hasard,
ou un fichier raccourci avec head ou truncate. Ça marche une fois,
puis ça coûte :
- C'est différent à chaque fois. Un octet tiré au hasard tombe ailleurs à chaque exécution, donc un échec du mardi peut ne pas revenir le mercredi.
- Ça change la taille. Un fichier coupé est plus petit que la limite sous laquelle il devait se trouver, si bien que le contrôle de taille répond avant le contrôle du contenu et que le test passe pour la mauvaise raison.
- Ça passe souvent inaperçu. Un texte brut reste lisible avec un octet modifié au milieu, et un lecteur d'images indulgent le dessine simplement, de sorte que le fichier censé être cassé est accepté.
- Ça ne dit rien de ce qui doit se passer. Le fichier n'est que des octets, et celui qui lira le test ensuite doit deviner si l'acceptation ou le rejet était voulu.
Ce que vous obtenez
Un fichier abîmé garde la taille demandée
Le fichier est généré normalement puis cassé, en route vers le disque. Il garde la taille demandée, et la même commande écrit de nouveau les mêmes octets.
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
Les réglages se placent après deux-points. L'option peut être répétée, et les dommages s'appliquent dans l'ordre où vous les écrivez. Ça marche avec chacun des 26 formats.
Ce qu'il sait faire
Quels dommages existe-t-il ?
Voici la liste que le programme affiche, lue dans le programme au moment de construire cette page.
tfg damage affiche la même, et tfg damage <id> dit ce que prend
l'un d'eux.
| Dommage | Ce qu'il fait aux octets | Plus petit fichier | Réglages |
|---|---|---|---|
zero-head |
Écrase les premiers octets du fichier avec des zéros, sans toucher à sa longueur. La plupart des lecteurs regardent d'abord là, donc presque tout remarque ce dommage. | 8 | bytes |
zero-head écrit des zéros sur le début du fichier. La plupart des lecteurs regardent
d'abord là, la signature et l'en-tête qui disent ce qu'est le fichier, donc presque tout lecteur
le remarque. Le texte brut et les journaux n'ont pas de signature et sont refusés eux aussi, car
une suite d'octets nuls n'est pas du texte. En dessous de quatre octets, certains formats
sortent avec un dommage dont aucun lecteur ne se plaint, c'est pourquoi le réglage commence à
quatre.
Ce que dit le manifeste
Un manifeste qui dit ce qui doit se passer
Chaque fichier abîmé reçoit une entrée disant que votre système doit le rejeter, avec le dommage noté à côté :
"expected": {
"outcome": "reject",
"reason": "content_malformed",
"confidence": "certain"
},
"damage": [
{
"type": "zero-head",
"settings": {
"bytes": "8"
}
}
]
Deux demandes sont refusées avant que rien ne soit écrit, car chacune laisserait sur le disque un fichier que le manifeste décrit mal :
- un fichier plus petit que ce dont le dommage a besoin, qui sortirait intact
-
expected: acceptà côté d'un dommage, car rien ne pourrait le satisfaire. Écrivezsanitizesi votre système doit réparer le fichier, ouunspecifiedsi c'est justement la question que vous posez
Dans une recette
Fichiers sains et cassés dans une même exécution
Mettez les deux dans une seule recette, et le manifeste porte l'attente de chaque fichier, de sorte que le test n'a pas besoin d'une liste disant lequel est lequel :
version: 1
targets:
- id: healthy
format: pdf
size: 1mb
expected: accept
- id: broken
format: pdf
size: 1mb
damage:
- zero-head
Dans un test
En faire un test
Le test lit le manifeste et vérifie que ce qui s'est passé est ce qui a été déclaré. Il n'a pas besoin d'une liste de noms de fichiers :
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 bon refus est un refus net. Un message qui dit ce qui n'allait pas est la réponse voulue. Une erreur serveur, un blocage ou un fichier à moitié enregistré est le défaut que ce test existe pour trouver.
Suite
Où aller ensuite
- Le preset upload-validation pose à un formulaire les deux autres questions, celle de la taille et celle du type.
- Fichiers de test en CI exécute une recette comme celle-ci dans un pipeline.
-
La documentation donne chaque option de
tfg generate.