Skip to main content
某些扩展在测试期间需要与外部 HTTP 服务器或其他网络监听器通信——例如,一个发起出站 HTTP 请求的扩展需要一个本地回显服务器来接收这些请求。本页介绍如何构建这类测试,使其能够在 --parallel=auto 下并跨操作系统可靠地运行。 这里的模式同样适用于 C++ 和 Rust 扩展——两者都通过 cargo vsql test 使用 MTR 作为测试运行器。

为什么硬编码端口会失败

有两种失败模式会使硬编码端口在 MTR 中变得不可靠: --parallel 下的端口冲突。 MTR 为每个 worker 分配其 @@port 值附近的一段端口范围。位于该范围之外的固定端口(比如 18777)可能与另一个 worker 保留的端口块或某个系统服务发生冲突——从而导致间歇性的 EADDRINUSE,且只在高并行度下才会重现。 操作系统级别的变化。 新的运行器镜像和操作系统版本会不时保留或限制此前可用的端口。今天在本地和 CI 中都能通过的端口,明天可能会悄无声息地无法绑定。 在这两种情况下,解决方案都是相同的:绑定到端口 0,让操作系统分配一个可用端口,然后将实际端口回传给测试。

模式 A:服务器集成式监听器

当网络监听器运行在 MySQL 进程内部时使用此模式——例如,一个内嵌 HTTP 服务器的扩展。 在扩展中,在绑定后将已绑定的端口作为状态变量暴露出来:
在测试中,使用 MTR 的 wait_condition.inc 轮询该状态变量,直到它非零,然后将端口捕获到一个 MTR 变量中。在任何 UNINSTALL EXTENSION / INSTALL EXTENSION 循环之后都要重新读取——临时端口会在每次绑定时发生变化。

模式 B:外部进程监听器

当监听器是一个独立进程时使用此模式——例如 Python 辅助服务器、模拟 API,或任何 MTR 无法直接控制的外部程序。
此模式要求 PATH 上有 python3。GitHub 托管的运行器已包含它;在自托管运行器上使用之前请先确认其可用性。

前台启动器

与其用 --exec ... & 将服务器放到后台并轮询端口,不如使用一个前台启动器脚本,它会:
  1. start_new_session=True 在子进程中启动服务器
  2. 阻塞,直到服务器已完成绑定并写入就绪文件
  3. 退出,让 MTR 继续执行
MTR 会在前台 --exec 调用上阻塞,因此这保证了服务器在任何 SQL 运行之前就已就绪——无需单独的轮询器或 sleep。 launcher.py — 用 --write_file 写入此内容:
echo_server.py — 实际的服务器,在开始服务之前写出其端口:

在测试中

启动启动器(前台——MTR 会阻塞直到它退出),然后 source 端口文件:

让端口不出现在结果文件中

结果文件按照编写的内容记录 SQL。如果 URL 中包含临时端口号,那么结果文件在每次运行时都会包含不同的值,测试在记录/重放时就会始终失败。 --disable_query_log 下一并 source 端口文件并设置 MySQL 会话变量,这样 let 赋值和临时端口都不会出现在输出中。在任何需要 URL 的地方都使用该会话变量:

进程管理

记录辅助进程的输出——切勿丢弃它。 上面的启动器模式将服务器的 stderr 写入 echo_server.log,并在失败时打印出来。丢弃辅助进程的输出会让 CI 工件中的偶发故障无法解释。 按 PID 杀进程,而非按名称。 在 Linux 上,pkill -f echo_server.py 可能会通过 /proc/cmdline 匹配到 MTR 进程本身。请使用启动器写入的 PID 文件:
在测试结束前移除所有临时文件。 MTR 的 check-testcase 会将留在 $MYSQLTEST_VARDIR/tmp/ 中的任何文件标记为脏状态并使测试失败:
关于模式 B 的一份完整、经过测试的实现,请参阅 vsql-http/mysql-test/t/vsql_http_requests.test——它涵盖了从 --write_file 到清理的完整测试生命周期。

另请参阅