测试百科全书

报告问题 查看源代码 每夜版 · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

测试执行环境的详尽规范。

背景

Bazel BUILD 语言包含一些规则,可用于以多种语言定义自动化测试程序。

测试使用 bazel test 运行。

用户还可以直接执行测试二进制文件。这是允许的,但不建议这样做,因为此类调用不符合下文所述的要求。

测试应是密封的:也就是说,它们应仅访问已声明依赖项的资源。如果测试不是完全封闭的,那么它们就无法给出历史上可重现的结果。这可能会给以下方面带来严重问题:查找问题代码(确定哪个更改导致测试失败)、发布工程可审核性以及测试的资源隔离(自动化测试框架不应因某些测试恰好与某个服务器通信而对该服务器发起 DDOS 攻击)。

目标

本页面的目标是正式确定 Bazel 测试的运行时环境和预期行为。它还会对测试运行程序和构建系统施加要求。

测试环境规范有助于测试作者避免依赖未指定的行为,从而使测试基础架构能够更自由地进行实现更改。该规范填补了一些漏洞,这些漏洞目前允许许多测试通过,尽管这些测试并非完全密封、确定性和可重入。

本页面旨在提供规范性且权威性的信息。如果此规范与测试运行程序的实现行为不一致,则以规范为准。

建议的规范

关键字“必须”“不得”“必需”“会”“不会”“应”“不应”“建议”“可以”和“可选”应按照 IETF RFC 2119 中的描述进行解释。

测试目的

Bazel 测试的目的是确认签入代码库的源文件的某些属性。(在此页面上,“源文件”包括测试数据、标准输出以及在版本控制下保留的任何其他内容。)一位用户编写了一项测试,用于断言他们希望保持的不变性。其他用户稍后执行测试,以检查不变量是否已被打破。如果测试依赖于源文件以外的任何变量(非密封),那么它的价值就会降低,因为当测试不再通过时,后来的用户无法确定是否是自己的更改导致了问题。

因此,测试结果必须仅取决于以下因素:

  • 测试具有声明依赖关系的源文件
  • 测试声明了依赖关系的 build 系统产品
  • 测试运行程序保证其行为保持不变的资源

目前,我们不会强制执行此类行为。不过,测试运行程序保留将来添加此类强制执行的权利。

构建系统的作用

测试规则与二进制规则类似,每条规则都必须生成一个可执行程序。对于某些语言,这是一个将特定于语言的框架与测试代码相结合的桩程序。测试规则还必须生成其他输出。除了主要测试可执行文件之外,测试运行程序还需要 runfiles 的清单(应在运行时提供给测试的输入文件),并且可能需要有关测试的类型、大小和标记的信息。

构建系统可以使用 runfile 来交付代码和数据。(这可能用作一种优化,通过在测试之间共享文件(例如通过使用动态链接)来减小每个测试二进制文件的大小。)构建系统应确保生成的执行文件通过测试运行程序提供的 runfiles 映像加载这些文件,而不是通过对源代码或输出树中绝对位置的硬编码引用来加载。

测试运行程序的角色

从测试运行程序的角度来看,每个测试都是一个可以使用 execve() 调用的程序。可能还有其他执行测试的方法;例如,IDE 可能允许在进程内执行 Java 测试。不过,将测试作为独立进程运行的结果必须被视为权威结果。如果测试进程运行完毕并正常终止(退出代码为零),则表示测试已通过。任何其他结果均视为测试失败。特别是,将任何字符串 PASSFAIL 写入标准输出对测试运行程序没有任何意义。

如果测试执行时间过长、超出某些资源限制,或者测试运行程序检测到禁止的行为,则可能会选择终止测试,并将运行视为失败。在向测试进程或其任何子进程发送信号后,运行程序不得报告测试通过。

整个测试目标(而非单个方法或测试)在有限的时间内运行完成。测试的时限取决于其 timeout 属性,具体如下表所示:

超时 时间限制(秒)
短片 60
适中 300
long 900
eternal 3600

未明确指定超时时间的测试会根据测试的 size 隐含一个超时时间,如下所示:

size 隐含的超时标签
短片
适中
large long
特大 eternal

未明确设置超时时间的“大型”测试将分配 900 秒的运行时间。超时时间为“短”的“中等”测试将分配 60 秒。

timeout 不同,size 还会确定在本地运行测试时其他资源(如 RAM)的假设峰值使用量,如常见定义中所述。

sizetimeout 标签的所有组合都是合法的,因此可以声明“巨大”测试的超时时间为“短”。大概会很快做一些非常可怕的事情。

无论超时时间如何,测试都可能会以任意速度返回。测试不会因超时时间过长而受到惩罚,但可能会收到警告:您应尽可能将超时时间设置得短一些,以免出现任何不稳定性。

在已知运行速度较慢的条件下手动运行时,可以使用 --test_timeout bazel 标志替换测试超时时间。--test_timeout 值以秒为单位。例如,--test_timeout=120 将测试超时时间设置为 2 分钟。

此外,我们还建议了测试超时时间的下限,如下所示:

超时 时间最小值(秒)
短片 0
适中 30
long 300
eternal 900

例如,如果“中等”测试在 5.5 秒内完成,请考虑设置 timeout = "short"size = "small"。使用 bazel --test_verbose_timeout_warnings 命令行选项将显示指定大小过大的测试。

测试规模和超时时间在 BUILD 文件中指定,具体请参阅此处的规范。如果未指定,测试规模将默认为“中等”。

如果测试的主进程退出,但其某些子进程仍在运行,测试运行程序应将该运行视为已完成,并根据从主进程观察到的退出代码将其计为成功或失败。测试运行程序可能会终止任何无关的进程。测试不应以这种方式泄漏进程。

测试分片

可以通过测试分片实现测试并行化。请参阅 --test_sharding_strategyshard_count 以启用测试分片。启用分片后,测试运行程序会针对每个分片启动一次。环境变量 TEST_TOTAL_SHARDS 是分片数,TEST_SHARD_INDEX 是分片索引,从 0 开始。运行程序会使用此信息来选择要运行的测试,例如使用轮询策略。并非所有测试运行程序都支持分片。如果 runner 支持分片,则必须创建或更新 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 运行文件树根目录的绝对路径 必填
TEST_TOTAL_SHARDS 总计 shard count,如果使用 sharding 可选
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 应为 022027

不得有任何待处理的闹钟或间隔计时器。

被屏蔽信号的初始掩码应为空。所有信号都应设置为其默认操作。

初始资源限制(包括软限制和硬限制)应按如下方式设置:

资源 限制
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 无限制,或 2044KB <= rlim <= 8192KB

初始进程时间(由 times() 返回)和资源利用率(由 getrusage() 返回)未指定。

初始调度政策和优先级未指定。

宿主系统的作用

除了受测试运行程序直接控制的用户上下文方面之外,测试运行所依赖的操作系统还必须满足某些属性,才能使测试运行有效。

文件系统

测试观察到的根目录可能是真实根目录,也可能不是。

/proc 应装载。

所有 build 工具都应位于本地安装所使用的 /usr 下的绝对路径中。

/home 开头的路径可能无法使用。测试不应访问任何此类路径。

/tmp 应该是可写入的,但测试应避免使用这些路径。

测试不得假定任何常量路径可供其独占使用。

测试不得假设任何已装载的文件系统都已启用 atime。

用户和群组

用户 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 核心数少于请求的数量,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 TemporaryFolderGo TempDir)有自己的方法在 /tmp 下创建临时目录。这些测试框架包含用于清理 /tmp 中文件的功能,因此即使它们在 TEST_TMPDIR 之外创建文件,您也可以使用它们。

测试必须通过 runfiles 机制或执行环境的其他部分(专门用于提供输入文件)来访问输入。

测试不得访问构建系统的其他输出(通过从其自身可执行文件的位置推断出的路径)。

未指定 runfiles 树是否包含常规文件、符号链接或两者兼有。runfiles 树可能包含指向目录的符号链接。测试应避免在 runfiles 树中使用包含 .. 组件的路径。

运行文件树中的任何目录、文件或符号链接(包括遍历符号链接的路径)都不应可写入。(因此,初始工作目录不应可写。)测试不得假定 runfiles 的任何部分是可写入的,或归当前用户所有(例如,chmodchgrp 可能会失败)。

在测试执行期间,runfiles 树(包括遍历符号链接的路径)不得更改。父目录和文件系统装载不得以任何方式更改,以免影响解析 runfiles 树中路径的结果。

为了捕获过早退出,测试可能会在启动时在 TEST_PREMATURE_EXIT_FILE 指定的路径中创建一个文件,并在退出时将其移除。如果 Bazel 在测试结束时看到该文件,则会假定测试过早退出,并将其标记为失败。

标记惯例

测试规则中的某些标记具有特殊含义。另请参阅 Bazel 构建百科全书中的 tags 属性

标记 含义
exclusive 同时不运行其他测试
external 测试具有外部依赖项;停用测试缓存
large test_suite 大会;大型测试套件
manual * 请勿在通配符目标模式(例如 :...:*:all)中包含测试目标
medium test_suite 惯例;中等测试套件
small test_suite 惯例;一套小型测试
smoke test_suite 惯例;表示应在将代码更改提交到版本控制系统之前运行

Runfiles

在下文中,假设有一个标记为 //foo/bar:unittest 的 *_binary() 规则,该规则对标记为 //deps/server:server 的规则具有运行时依赖关系。

位置

目标 //foo/bar:unittest 的 runfiles 目录是 $(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles 目录。此路径称为 runfiles_dir

依赖项

运行文件目录被声明为 *_binary() 规则的编译时依赖项。运行文件目录本身取决于影响 *_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
  • 指向子 runfile 的符号链接:对于作为 *_binary() C 的运行时依赖项的每个 *_binary() Z,C 的 runfile 目录中都有一个指向 Z 的 runfile 的第二个链接。符号链接的名称为 $(WORKSPACE)/package_name/rule_name.runfiles。符号链接的目标是 runfiles 目录。例如,所有子程序共享一个通用的 runfiles 目录。