Testing Files Generator
简体中文

如何制作用于测试的损坏文件

一个只见过完好文件的校验器,并没有真正被测试过。下面介绍如何得到一个故意弄坏的文件:它的大小恰好就是你要求的大小,并附带一份清单,说明你的系统该如何处理它。

简短回答

tfg generate --format png --size 2mb --damage zero-head --out ./out 会写出一个恰好 2097152 字节的 PNG,它的开头几个字节是零,旁边的清单则记录你的系统应该拒绝它。

常见做法

为什么手工弄坏的文件不是好测试

常见做法是用十六进制编辑器、用脚本翻转几个随机字节,或用 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"
    }
  }
]

有两种请求会在写入任何内容之前被拒绝,因为各自都会在磁盘上留下一个清单描述有误的文件:

在配方中

一次运行中的完好文件和损坏文件

把两者放进同一个配方,清单就带有每个文件的预期,测试因此不需要一份说明哪个是哪个的列表:

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

好的拒绝是干净的拒绝。一条说明错在哪里的消息,就是你想要的答案。服务器错误、卡死或只保存了一半的文件,正是这个测试要找出来的缺陷。

接下来

从这里去哪里