详尽的测试执行环境规范。
背景
Bazel BUILD 语言包含的规则可用于以多种语言定义自动化测试程序。
使用 bazel test
运行测试。
用户还可以直接执行测试二进制文件。我们允许这种调用,但不予认可,因为此类调用不符合下述强制性要求。
测试应该是封闭的:也就是说,测试应仅访问已声明依赖项的资源。如果测试未正确封闭,则测试不会提供过去可重现的结果。这可能是一个重大问题,用于找出罪魁祸首(确定哪些更改破坏了测试)、发布工程可审核性以及测试的资源隔离(自动化测试框架不应将 DDOS 用作服务器,因为某些测试恰巧与此服务器通信)。
目标
本页面的目标是正式建立 Bazel 测试的运行时环境和预期行为。此测试还会对测试运行程序和构建系统提出要求。
测试环境规范有助于测试作者避免依赖未指定的行为,从而使测试基础架构可以更自由地更改实现。该规范缩小了目前允许许多测试通过(尽管没有适当封闭、确定和可重入)的一些漏洞。
本页内容既具有规范性,又具有权威性。如果此规范与测试运行程序的实现的行为不一致,应以该规范为准。
建议的规范
关键字“必须”“不得”“必需”“会”“不会”“应”“不应”“建议”“可以”和“可选”应按 IETF RFC 2119 中的说明进行解释。
测试目的
Bazel 测试的目的是确认签入代码库中的源文件的某些属性。(在本页面上,“源文件”包括测试数据、黄金输出以及受版本控制的任何其他内容。)一位用户编写了一个测试,以断言他们希望维护的不变量。其他用户稍后会执行该测试,以检查不可变是否已损坏。如果测试依赖于除源文件(非封闭文件)以外的任何变量,则其值会减小,因为当测试停止通过时,后期用户无法确定他们的更改是错误的。
因此,测试的结果仅取决于:
- 测试所声明的依赖项的源文件
- 测试所声明的依赖项的构建系统的产品
- 测试运行程序保证其行为保持不变的资源
我们目前没有强制实施此类行为。不过,测试运行程序保留将来添加此类强制执行的权利。
构建系统的作用
测试规则与二进制规则类似,因为每条规则都必须生成一个可执行程序。对于某些语言,这是一个存根程序,可将特定于语言的自动化测试框架与测试代码结合在一起。测试规则还必须生成其他输出。除了主要测试可执行文件之外,测试运行程序还需要一个 runfiles 清单,这是在运行时应可供测试使用的输入文件,并且可能需要有关测试的类型、大小和标记的信息。
构建系统可能会使用 runfile 传递代码和数据。(可用于通过在测试之间共享文件(例如通过使用动态链接)来缩减每个测试二进制文件的大小。)构建系统应确保生成的可执行文件通过测试运行程序提供的 runfiles 映像加载这些文件,而不是通过硬编码的方式引用源代码树或输出树中的绝对位置。
测试运行程序的角色
从测试运行程序的角度来看,每个测试都是一个可通过 execve()
调用的程序。也可以通过其他方式执行测试;例如,IDE 可能允许在进程中执行 Java 测试。不过,作为独立进程运行测试的结果必须被视为权威。如果某个测试进程运行完成,且正常终止并显示退出代码为零,则表示测试已通过。任何其他结果都会被视为测试失败。具体而言,将任何字符串 PASS
或 FAIL
写入 stdout 对测试运行程序没有意义。
如果测试的执行时间太长、超出某些资源限制,或者测试运行程序检测到被禁止的行为,则可能会选择终止测试并将运行视为失败。在向测试进程或其任何子进程发送信号后,运行程序不得将测试报告为通过。
整个测试目标(而不是单个方法或测试)有一定的运行时间,直到完成为止。测试的时间限制取决于测试的 timeout
属性,如下表所示:
超时 | 时间限制(秒) |
---|---|
短片 | 60 |
适中 | 300 |
long | 900 |
永恒 | 3600 |
对于未明确指定超时的测试,系统会根据测试的 size
隐式指定超时,如下所示:
大小 | 隐式超时标签 |
---|---|
small | 短片 |
medium | 适中 |
大 | long |
巨大的 | 永恒 |
没有明确超时设置的“大型”测试将被分配 900 秒的运行时间。超时为“short”的“中等”测试将分配 60 秒。
与 timeout
不同,size
还会另外确定在本地运行测试时假定其他资源(如 RAM)的峰值使用量,如常见定义中所述。
size
和 timeout
标签的所有组合均合法,因此“超大型”测试可能会被声明为“短”超时。按理说,这个工具很快会做一些非常糟糕的事情
无论超时情况如何,测试都可能会以任意速度返回。虽然可能会发出警告,但测试不会因超时而受到处罚:通常,您应尽可能将超时设置得尽可能紧,以免导致任何不稳定问题。
在已知速度缓慢的条件下手动运行时,可以使用 --test_timeout
bazel 标志替换测试超时时间。--test_timeout
值以秒为单位。例如,--test_timeout=120
将测试超时设置为两分钟。
此外,还有一个建议的测试超时下限,如下所示:
超时 | 最短时间(秒) |
---|---|
短片 | 0 |
适中 | 30 |
long | 300 |
永恒 | 900 |
例如,如果“中等”测试在 5.5 秒内完成,请考虑设置 timeout =
"short"
或 size = "small"
。使用 bazel --test_verbose_timeout_warnings
命令行选项时,系统会显示指定大小过大的测试。
测试大小和超时时间是根据此处的规范在 BUILD 文件中指定的。如果未指定,测试的大小将默认为“中”。
如果测试的主进程退出,但其部分子进程仍在运行,则测试运行程序应认为运行已完成,并根据从主进程中观察到的退出代码将其统计为成功或失败。测试运行程序可能会终止任何零散进程。测试不应以这种方式泄露进程。
测试分片
您可以通过测试分片来并行处理测试。请参阅 --test_sharding_strategy
和 shard_count
以启用测试分片。启用分片后,每个分片都会启动测试运行程序一次。环境变量 TEST_TOTAL_SHARDS
表示分片数量,TEST_SHARD_INDEX
表示分片索引(从 0 开始)。运行程序会使用此信息来选择要运行哪些测试,例如使用轮循策略。并非所有测试运行程序都支持分片。如果运行程序支持分片,则必须创建或更新 TEST_SHARD_STATUS_FILE
指定的文件的最后修改日期。否则,如果 --incompatible_check_sharding_support
已启用,Bazel 会在分片时无法通过测试。
初始条件
执行测试时,测试运行程序必须建立某些初始条件。
测试运行程序必须使用 argv[0]
中测试可执行文件的路径调用每项测试。此路径必须是相对路径,且位于测试当前目录(位于 runfiles 树中,见下文)下方。除非用户明确请求,否则测试运行程序不应将任何其他参数传递给测试。
初始环境代码块应由以下部分组成:
变量 | 值 | 状态 |
---|---|---|
HOME |
“$TEST_TMPDIR ”的值 |
推荐 |
LANG |
unset | 必选 |
LANGUAGE |
unset | 必选 |
LC_ALL |
unset | 必选 |
LC_COLLATE |
unset | 必选 |
LC_CTYPE |
unset | 必选 |
LC_MESSAGES |
unset | 必选 |
LC_MONETARY |
unset | 必选 |
LC_NUMERIC |
unset | 必选 |
LC_TIME |
unset | 必选 |
LD_LIBRARY_PATH |
以冒号分隔的目录列表,其中包含共享库 | 可选 |
JAVA_RUNFILES |
“$TEST_SRCDIR ”的值 |
已弃用 |
LOGNAME |
“$USER ”的值 |
必选 |
PATH |
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:. |
推荐 |
PWD |
$TEST_SRCDIR/workspace-name |
推荐 |
SHLVL |
2 |
推荐 |
TEST_INFRASTRUCTURE_FAILURE_FILE |
可写目录中的私有文件的绝对路径(此文件只能用于报告源自测试基础架构的故障,而不能作为报告不稳定的测试失败的通用机制。在这种情况下,测试基础架构是指并非专用于测试,但可能会因故障而导致测试失败的系统或库。第一行是导致故障的测试基础架构组件的名称,第二行是直观易懂的故障说明。其他行会被忽略。) | 可选 |
TEST_LOGSPLITTER_OUTPUT_FILE |
可写目录中某个私有文件的绝对路径(用于写入 Logsplitter protobuffer 日志) | 可选 |
TEST_PREMATURE_EXIT_FILE |
可写目录中某个私有文件的绝对路径(用于捕获对 exit() 的调用) |
可选 |
TEST_RANDOM_SEED |
如果使用 --runs_per_test 选项,则对于每次测试运行,TEST_RANDOM_SEED 会设置为 run number(以 1 开头)。 |
可选 |
TEST_RUN_NUMBER |
如果使用 --runs_per_test 选项,则对于每次测试运行,TEST_RUN_NUMBER 会设置为 run number(以 1 开头)。 |
可选 |
TEST_TARGET |
要测试的目标的名称 | 可选 |
TEST_SIZE |
测试 size |
可选 |
TEST_TIMEOUT |
测试 timeout (以秒为单位) |
可选 |
TEST_SHARD_INDEX |
分片索引(如果使用 sharding ) |
可选 |
TEST_SHARD_STATUS_FILE |
触摸的文件的路径,表明支持 sharding |
可选 |
TEST_SRCDIR |
runfiles 树底部的绝对路径 | 必选 |
TEST_TOTAL_SHARDS |
如果使用 sharding ,则总值为 shard count |
可选 |
TEST_TMPDIR |
私有可写目录的绝对路径 | 必选 |
TEST_WORKSPACE |
本地代码库的工作区名称 | 可选 |
TEST_UNDECLARED_OUTPUTS_DIR |
私有可写目录(用于写入未声明的测试输出)的绝对路径。写入 TEST_UNDECLARED_OUTPUTS_DIR 目录的所有文件都将被压缩,并添加到 bazel-testlogs 下的 outputs.zip 文件中。 |
可选 |
TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR |
私有可写目录的绝对路径(用于写入未声明的测试输出注解 .part 和 .pb 文件)。 |
可选 |
TEST_WARNINGS_OUTPUT_FILE |
可写目录中某个私有文件的绝对路径(用于写入测试目标警告) | 可选 |
TESTBRIDGE_TEST_ONLY |
值 --test_filter (如果已指定) |
可选 |
TZ |
UTC |
必选 |
USER |
“getpwuid(getuid())->pw_name ”的值 |
必选 |
XML_OUTPUT_FILE |
测试操作应将测试结果 XML 输出文件写入到该位置。 否则,Bazel 会生成默认的 XML 输出文件,该文件会将测试日志作为测试操作的一部分包含在内。XML 架构基于 JUnit 测试结果架构。 | 可选 |
BAZEL_TEST |
表示测试可执行文件由 bazel test 驱动 |
必选 |
该环境可能包含其他条目。测试不应依赖于上面未列出的任何环境变量是否存在、不存在或值。
初始工作目录应为 $TEST_SRCDIR/$TEST_WORKSPACE
。
未指定当前进程 ID、进程组 ID、会话 ID 和父级进程 ID。该进程不一定是进程组领导者或会话领导者。该进程不一定有控制终端。进程可能具有零个或零个以上正在运行或尚未回收的子进程。当测试代码获得控制时,该进程不应具有多个线程。
文件描述符 0 (stdin
) 应处于打开状态以供读取,但其附加的内容未指定。测试不得从该数据库读取数据。文件描述符 1 (stdout
) 和 2 (stderr
) 应可以打开以供写入,但其附加了什么内容并未指定。它可以是终端、管道、常规文件,或其他任何可以写入字符的内容。它们可以共享打开文件表中的条目(这意味着它们无法独立跳转)。测试不应继承任何其他打开的文件描述符。
初始 umask 应为 022
或 027
。
不得有闹钟或间隔计时器等待处理。
被屏蔽信号的初始掩码应为空。所有信号都应设置为其默认操作。
初始资源限制(包括软资源限制和硬资源限制)应按如下方式设置:
资源 | 限值 |
---|---|
RLIMIT_AS |
无限制 |
RLIMIT_CORE |
未指定 |
RLIMIT_CPU |
无限制 |
RLIMIT_DATA |
无限制 |
RLIMIT_FSIZE |
无限制 |
RLIMIT_LOCKS |
无限制 |
RLIMIT_MEMLOCK |
无限制 |
RLIMIT_MSGQUEUE |
未指定 |
RLIMIT_NICE |
未指定 |
RLIMIT_NOFILE |
至少 1024 |
RLIMIT_NPROC |
未指定 |
RLIMIT_RSS |
无限制 |
RLIMIT_RTPRIO |
未指定 |
RLIMIT_SIGPENDING |
未指定 |
RLIMIT_STACK |
无限制,即 2044 KB <= rlim <= 8192 KB |
初始处理时间(由 times()
返回)和资源利用率(由 getrusage()
返回)未指定。
未指定初始调度政策和优先级。
主机系统的角色
除了由测试运行程序直接控制的用户上下文的各个方面外,执行测试的操作系统还必须满足某些属性,测试运行才有效。
文件系统
测试观察到的根目录不一定是真正的根目录。
应装载 /proc
。
所有构建工具都应位于本地安装使用的 /usr
下的绝对路径下。
以 /home
开头的路径可能不可用。测试不应访问任何此类路径。
/tmp
应是可写的,但测试应避免使用这些路径。
测试不得假设任何常量路径可供其专用。
测试不得假设任何已装载的文件系统都启用了时间。
用户和群组
users root、nobody 和 unittest 必须存在。群组 root、nobody 和 eng 必须存在。
必须以非根用户身份执行测试。实际用户 ID 和有效用户 ID 必须相等;群组 ID 也是如此。除此之外,当前用户 ID、群组 ID、用户名和群组名称均未指定。未指定补充组 ID 的集合。
当前用户 ID 和群组 ID 必须具有对应的名称,这些名称可通过 getpwuid()
和 getgrgid()
检索。对于补充组 ID,可能并非如此。
当前用户必须有主目录。它可能无法写入。测试不得尝试向其写入数据。
网络
未指定主机名。可能包含英文句点,也可能不包含。解析主机名必须提供当前主机的 IP 地址。解析第一个点之后的主机名也必须有效。必须解析主机名 localhost。
其他资源
至少会授予一个 CPU 核心的测试。或许还可以使用其他方式,但不保证一定如此。该核心的其他性能方面未指定。您可以通过在测试规则中添加“cpu:n”(其中 n 为正数)标记,将预留增加到更多 CPU 核心数。即使机器的 CPU 核心总数少于所请求的 CPU 核心数,Bazel 仍会运行测试。如果测试使用分片,则每个分片将保留此处指定的 CPU 核心数量。
测试可以创建子进程,但不能创建进程组或会话。
测试可以使用的输入文件数量存在限制。此限制随时可能更改,但目前在数万个输入的范围内。
日期和时间
未指定当前时间和日期。未指定系统时区。
X Windows 可能无法使用。需要 X 服务器的测试应启动 Xvfb。
测试与文件系统的交互
除非另有指定,否则测试环境变量中指定的所有文件路径均指向本地文件系统上的某个位置。
测试应仅在 $TEST_TMPDIR
和 $TEST_UNDECLARED_OUTPUTS_DIR
(如果已设置)指定的目录中创建文件。
这些目录最初是空的。
测试不得尝试移除、chmod 或以其他方式更改这些目录。
这些目录可能是符号链接。
$TEST_TMPDIR/.
的文件系统类型仍未指定。
测试还可以将 .part 文件写入 $TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR
,为未声明的输出文件添加注解。
在极少数情况下,系统可能会强制在 /tmp
中创建文件。例如,Unix 网域套接字的路径长度限制通常需要在 /tmp
下创建套接字。Bazel 无法跟踪此类文件;测试本身必须保持封闭状态,使用唯一路径以避免与其他同时运行测试和非测试进程发生冲突的情况,并清理它在 /tmp
中创建的文件。
一些常用的测试框架(如 JUnit4 TemporaryFolder
或 Go TempDir
)有自己的方式,可以在 /tmp
下创建临时目录。这些测试框架包含清理 /tmp
中文件的功能,因此即使它们在 TEST_TMPDIR
之外创建文件,您也可以使用它们。
测试必须通过 runfiles 机制或执行环境中其他专用于提供输入文件的部分访问输入。
测试不得在根据其自身可执行文件的位置推断的路径访问构建系统的其他输出。
未指定 runfiles 树是包含常规文件、符号链接还是二者的混合。runfiles 树可能包含指向目录的符号链接。测试应避免使用 runfiles 树中包含 ..
组件的路径。
runfiles 树中的任何目录、文件或符号链接(包括遍历符号链接的路径)都不应是可写的。(由此可见,初始工作目录不应是可写的。)测试不得假定运行文件的任何部分可写入或归当前用户所有(例如,chmod
和 chgrp
可能会失败)。
在测试执行期间,Runfiles 树(包括遍历符号链接的路径)不得更改。父目录和文件系统装载不得以任何方式更改,以影响解析 runfiles 树中某个路径的结果。
为了捕获提前退出,测试可以在启动时在 TEST_PREMATURE_EXIT_FILE
指定的路径上创建一个文件,并在退出时将其移除。如果 Bazel 在测试完成后看到此文件,则会假定测试过早退出并将其标记为失败。
代码规范
测试规则中的某些标记具有特殊含义。另请参阅关于 tags
属性的 Bazel build 百科全书。
添加链接 | 含义 |
---|---|
exclusive |
不要同时运行其他测试 |
external |
测试具有外部依赖项;停用测试缓存 |
large |
test_suite 规范;大型测试套件 |
manual * |
不在 :... 、:* 或 :all 等通配符目标格式中包含测试目标 |
medium |
test_suite 规范;中型测试套件的套件 |
small |
test_suite 规范;小型测试套件 |
smoke |
test_suite 惯例;表示它应该在将代码更改提交到版本控制系统之前运行 |
Runfiles
在以下情形中,假设有一条 *_binary() 规则,其标签为 //foo/bar:unittest
,其运行时依赖项为标签为 //deps/server:server
的规则。
位置
目标 //foo/bar:unittest
的 runfiles 目录是目录 $(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles
。此路径称为 runfiles_dir
。
依赖项
runfiles 目录声明为 *_binary()
规则的编译时依赖项。runfiles 目录本身依赖于会影响 *_binary()
规则或其任何编译时或运行时依赖项的一组 BUILD 文件。修改源文件不会影响 runfiles 目录的结构,因此不会触发任何重新构建。
目录
runfiles 目录包含以下内容:
- 运行时依赖项的符号链接:作为
*_binary()
规则的运行时依赖项的每个 OutputFile 和 CommandRule 都由 runfiles 目录中的一个符号链接表示。符号链接的名称为$(WORKSPACE)/package_name/rule_name
。例如,服务器的符号链接将命名为$(WORKSPACE)/deps/server/server
,完整路径将为$(WORKSPACE)/foo/bar/unittest.runfiles/$(WORKSPACE)/deps/server/server
。符号链接的目的地是 OutputFile 或 CommandRule 的 OutputFileName(),表示为绝对路径。因此,符号链接的目的地可能是$(WORKSPACE)/linux-dbg/deps/server/42/server
。 - 指向子运行文件的符号链接:对于每个属于
*_binary()
C 的运行时依赖项的*_binary()
Z,在 C 的 runfiles 目录中还有指向 Z 的运行文件的第二个链接。符号链接的名称为$(WORKSPACE)/package_name/rule_name.runfiles
。符号链接的目标是 runfiles 目录。例如,所有子程序都共享一个通用的 runfiles 目录。