Testing Files Generator
简体中文

文档

工具的全部功能,按人们实际会问的问题来组织。仓库中的 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。抽取结果来自种子
--boundary <size>围绕一个限制的三个文件:小一字节、恰好等于限制、大一字节
--count <n>生成多少个文件。默认 1
--name <template>文件名模板,例如 invoice_{index:04}.txt
--out <dir>写入的目录
--seed <n>本次运行的种子。相同的种子得到相同的字节
--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,并在旁边记录所做的破坏。有两种情况会在写入任何内容之前被拒绝,因为各自都会在磁盘上留下清单描述有误的文件:

第三种无法提前得知。如果某个破坏运行后没有改动任何字节,该文件会被丢弃而不是写出,运行会继续,指出是哪个文件,并以部分完成的退出码结束。

一步一步来,附带一个读取清单的测试:如何制作用于测试的损坏文件。

配方是什么样子?

配方是一个描述整次运行的 YAML 文件。把它与测试放在一起提交,fixture 就不再是仓库里的二进制文件,任何人都可以用一个只有几百个字符的文件逐字节重建它们。

# 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

每个 target 必须恰好有 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,即配方中生成该文件的 target 的 id,summary.by_target 则统计每个 target 生成的文件数。因此有多个 target 的配方可以逐个 target 检查,无需阅读文件名。

什么是预设?

预设是回答常见测试问题的现成文件集,你不必自己设计。预设底层就是普通配方,eject 会把配方打印出来,供你从那里开始编辑。每个预设都有自己的页面,说明它通常能发现什么、集合里有什么,以及它接受的每项设置。

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 停止的运行仍会留下清单,也绝不会留下写了一半的文件,所以被取消的任务仍可由下一次运行清理。

适用于 GitHub Actions 和 GitLab CI 的现成工作流:如何在 CI 流水线中生成测试文件。

有桌面窗口吗?

有,它就是在同一个引擎上加了一个窗口,用于不走脚本的测试。它不是缩水版:有测试逐项对比这两种界面,只有其中一方能做的事必须被声明并说明理由,而不是悄悄地渐行渐远。

界面有单批生成、预设、同时多批和关于。它会在写入任何内容之前显示一次运行的开销,运行时报告进度,并且可以在中途取消而不会留下写了一半的文件。它目前还不能打开配方文件,配方暂时只属于命令行,窗口通过表单来构建批次。