libwebsockets 持续集成(CI)测试接入指南:从 Travis 配置到多模式测试矩阵

原创2026-09-28 22:33:381,604 阅读
文章标签:人工智能AI AgentAgent 框架多模态语音音频后端

libwebsockets 持续集成(CI)测试接入指南:从 Travis 配置到多模式测试矩阵

libwebsockets 的 CI 接入并非简单的"构建一下、跑个测试"就能了事——它需要把依赖安装、构建选项、协议一致性测试、负载压测与安全攻击测试完整串联起来。本文基于 third_party/libwebsockets/READMEs/README.ci.md 的骨架,结合仓库内真实存在的 scripts/travis_install.sh、scripts/travis_control.sh 及配套测试脚本,完整讲解 libwebsockets 的 CI 测试体系:为什么要为每个新功能补 CI 用例、三个接入点在源码中的具体落点,以及 lwsws / mbedtls / smp / libev / libuv 等测试模式各自跑哪些测试。读完本文,你将掌握 libwebsockets 的 CI 测试设计思路,并能在自己的集成流程中复用它现成的测试脚本与构建参数约定。

说明:libwebsockets 以第三方依赖形式内置于本仓库(TEN-framework / ten-framework)的 third_party/libwebsockets 目录,作为底层 HTTP(S)/WebSocket 传输库使用;其完整的 README 系列文档与 CI 脚本一并随源码树保留,因此本文所引用的脚本路径均以仓库根目录为基准。

为什么要给 libwebsockets 加 CI

原文档(README.ci.md)开篇就点明了 CI 的必要性,这段论述本身就是接入 CI 的第一条原则:

  • 凡是"应当长期可用"的功能,就必须在 CI 中持续被锻炼(原话是至少要在 Travis 上跑通)。新增非平凡代码时,尽量带上对应的 CI 测试。
  • 冷门功能最容易悄悄坏掉:如果一个功能使用者很少,那么重构或其它大改动随时可能把它破坏到不可用,而且"没有任何人注意到,直到很久以后"。经验表明这是常态,而不是例外。
  • 作者也直言,维护 CI 测试是"非产出性的苦差事"(nonproductive PITA),从来没人为此感谢过他;但要想让"各种功能可用的承诺"继续保持下去,为新代码补 CI 测试就是必须付出的代价。

这条原则直接决定了本仓库 third_party/libwebsockets/scripts/ 下那套测试脚本的存在形态:它们不是为了演示而写的玩具,而是被设计成可在 CI 里按模式批量驱动、可判定成败的"真测试"。

CI 的三个接入点(Integration points)

原文档把 CI 接入归纳为三个明确的集成点,每个点对应仓库里的一个具体文件:

1. cmake:测试活动与构建选项的映射

.travis.yml maps the various test activities to CMake options needed.

即在 CI 配置中,不同测试活动(如 TLS 栈选择、事件循环选择、是否启用 minimal-examples)被映射成对应的 CMake 选项。从 scripts/travis_control.sh 的实际内容可以看到,这些 CMake 选项包括:

  • LWS_OPENSSL_LIBRARIES / LWS_OPENSSL_INCLUDE_DIRS:指定 OpenSSL 库路径(如 /usr/local/lib/libssl.so;/usr/local/lib/libcrypto.so 与 /usr/local/include/openssl),供 lwsws、lwsws2、smp、mbedtls、ss+mbedtls 等模式使用;
  • OPENSSL_ROOT_DIR:macOS 上定位 Homebrew 安装的 OpenSSL(/usr/local/opt/openssl);
  • $CMAKE_ARGS:CI 注入的通用 CMake 参数透传变量,所有模式都会把它追加到 cmake ... 命令末尾;
  • 由测试脚本额外约定的构建选项,例如 attack.sh 头部注释要求用 cmake .. -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_MINIMAL_EXAMPLES=1 构建,而 autobahn-test-server.sh 同样要求 -DLWS_WITH_MINIMAL_EXAMPLES=1,因为测试依赖 minimal examples 生成的可执行文件(如 bin/lws-minimal-http-server-tls)。

需要说明的是:本仓库快照中并未包含 .travis.yml 本体(Travis CI 已停止服务,上游也未随源码树携带该文件),但原文档描述的"由 CMake 选项驱动测试活动"的机制,通过 travis_control.sh 中按 LWS_METHOD 分支传递 $CMAKE_ARGS 的代码可以完整印证。

2. 依赖安装:scripts/travis_install.sh

See ./scripts/travis_install.sh

该脚本(scripts/travis_install.sh)负责在 CI 环境中按测试模式安装依赖,逻辑按"平台 × 测试模式(LWS_METHOD)"交叉展开:

Linux(TRAVIS_OS_NAME == linux,使用 apt-get):

LWS_METHOD 安装内容
lwsws / lwsws2 realpath libjemalloc1 libev4 libuv-dev libdbus-1-dev valgrind mosquitto;并安装 Python 依赖 six>=1.9、Twisted==16.0.0、pyopenssl>=0.14、autobahntestsuite;再解压 openssl-1.1.0-trusty.tar.bz2 到 / 并执行 ldconfig、update-ca-certificates
mbedtls / ss+mbedtls realpath libjemalloc1 libev4 libuv-dev valgrind + 同样的 OpenSSL 预编译包
smp realpath libjemalloc1 libev4 + OpenSSL 预编译包
libev libev-dev
libuv libuv-dev(lwsws/lwsws2 分支也会顺带安装)

macOS(TRAVIS_OS_NAME == osx,使用 brew):

  • lwsws / lwsws2:brew install dbus;
  • libev:brew install libev;
  • libuv(及 lwsws/lwsws2):brew install libuv。

特殊开关:当 COVERITY_SCAN_BRANCH == 1(Coverity Scan 分支)时直接退出,不安装任何依赖——因为该分支只做静态扫描,不需要运行测试环境。

从依赖清单可以看出各测试模式的能力边界:libev/libuv 是事件循环后端(分别对应 LWS_EVLIB_EVENT/LWS_EVLIB_UV 测试面),mosquitto 是 MQTT broker(供 dbus/MQTT 相关测试用),autobahntestsuite 则是 WebSocket 协议一致性测试工具。

3. 执行预编排的测试动作:scripts/travis_control.sh

See ./scripts/travis_control.sh

该脚本(scripts/travis_control.sh)是 CI 的核心调度器,负责"构建 → 安装 → 按模式跑测试"。它的分支结构与 travis_install.sh 一一对应,整理如下:

  • COVERITY_SCAN_BRANCH == 1:跳过全部构建与测试。
  • macOS:mkdir build && cd build,执行 cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. && cmake --build .;但 mbedtls / ss+mbedtls 模式跳过(因为 OpenSSL 路径参数对 mbedTLS 无意义)。
  • Linux 各模式:
    • lwsws:cmake(带 LWS_OPENSSL_LIBRARIES/LWS_OPENSSL_INCLUDE_DIRS)→ 构建 → sudo make install → 依次执行 minimal-examples/selftests.sh、scripts/h2spec.sh、scripts/attack.sh、scripts/h2load.sh、scripts/autobahn-test-server.sh、scripts/autobahn-test-client.sh,即"全套测试";
    • lwsws2:构建 + 安装后只跑 scripts/autobahn-test-server.sh(侧重 WebSocket 一致性);
    • smp:构建后只跑 scripts/h2load-smp.sh(多核并发压测);
    • mbedtls / ss+mbedtls:cmake → 构建 → 安装 → 跑 selftests.sh、h2spec.sh、h2load.sh、attack.sh(与 lwsws 相比少了 Autobahn 与 h2load-smp);
    • 其它(默认路径):只做 cmake $CMAKE_ARGS .. && cmake --build .,即普通编译验证。

这种"按模式裁剪测试集合"的设计,正是原文档所说"把各种测试活动映射到 CMake 选项"的运行时体现。

测试矩阵里的各个测试脚本

scripts/ 目录(third_party/libwebsockets/scripts)里除了 travis_install.sh / travis_control.sh 两个调度脚本,还有一批被调用的专项测试脚本,它们的职责与判定方式如下。

HTTP/2 协议一致性:h2spec.sh

h2spec.sh 从 h2spec v2.1.0 的 linux_amd64 发布包解压出 h2spec 工具,后台启动 minimal-http-server-tls 示例服务器(监听 127.0.0.1:7681),然后执行:

../../../build/h2spec -h 127.0.0.1 -p 7681 -t -k -S > /tmp/hlog

其中 -t 表示 TLS 模式、-k 忽略证书校验、-S 跳过对同一连接上多个 stream 的要求。判定逻辑很直接:若输出中出现 Failures:,则打印失败段并 exit 1,否则 exit 0。这是把"HTTP/2 协议规范符合性"固化成 CI 门槛的典型做法。

HTTP/1.1 与 HTTP/2 负载压测:h2load.sh 与 h2load-smp.sh

h2load.sh 与 h2load-smp.sh 结构几乎相同,差别仅在于前者打的是单进程的 lws-minimal-http-server-tls,后者打的是 SMP 模式的 lws-minimal-http-server-smp -s。压测命令为:

  • HTTP/1.1:h2load -n 10000 -c 1 --h1、-n 10000 -c 10 --h1、-n 100000 -c 100 --h1;
  • HTTP/2:去掉 --h1,同样按 1 / 10 / 100 并发、1 万到 10 万请求递增。

任何一档压测命令返回非零即终止并退出对应错误码。这套组合同时覆盖了协议版本与并发规模两个维度,用于防止回归把吞吐或并发能力打回原形。

安全与健壮性攻击测试:attack.sh

attack.sh 是最"狠"的一个脚本,其注释明确要求以 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_MINIMAL_EXAMPLES=1 构建。它启动 libwebsockets-test-server -d15(调试级别日志),用 lws-minimal-raw-netcat 作为原始 TCP 客户端,逐项攻击 HTTP 解析层:

  • URI 参数解析:/cgi-bin/settingsjs?UPDATE_SETTINGS=1&... 等请求,校验 URI Arg 的切分结果;
  • 百分号编码与路径归一化:/t%3dest?key1%3d2=value1、%2f%2e%2e%2f%2e./xxtest.html 等,验证解码与路径重写;
  • 目录穿越攻击:/../../../../etc/passwd、/%2e%2e%2f../../../etc/passwd、%2f%2e%2e%2f%2e./.%2e/.%2e%2fetc/passwd 等,必须被归一化为 404/403 而非泄露文件;
  • 畸形请求:缺失 URI、重复方法、超长 header 名、超长 URI 内容、随机字节垃圾(80 字节、10MB)等,服务器必须存活且正确拒绝;
  • HTTP/1.1 管线化:用 wget 连续请求 8 个 /test.html,对响应内容做 md5 一致性校验;
  • URI 变体大矩阵:脚本内置了 200 余条 /.../、/../、/./、/% 等组合变体,逐条请求并对比预期状态码表(403/404/200),最后用 md5sum 比对整体结果。

attack.sh 的本质是"把 HTTP 解析器的攻击面系统性锤一遍",确保路径归一化、编码解码与错误处理在每次 CI 中都被重新验证。

WebSocket 协议一致性:Autobahn TestSuite

Autobahn 是 WebSocket 社区标准的协议一致性测试套件,libwebsockets 同时用它测"服务器侧"与"客户端侧"两个方向:

  • autobahn-test-server.sh:生成 fuzzingclient.json,以 lws-minimal-ws-server-echo(端口 9001)为被测服务器,wstest -m fuzzingclient 全量跑测试用例(cases: ["*"]),仅排除 2.10 / 2.11(RFC6455 不要求单连接内多 PING/PONG 并发,lws 出于内存效率也不支持);随后解析 reports/servers/index.json,只要出现非 OK / NON-STRICT / INFORMATIONAL 的 behavior 即记为失败。
  • autobahn-test-client.sh:反过来由 wstest -m fuzzingserver 扮演服务器,lws-minimal-ws-client-echo 循环请求 /runCase?case=N&agent=libwebsockets 逐个用例,最后用 /updateReports 生成报告;排除项除 2.10/2.11 外,还排除了 12.3.1、12.3.2、12.4.*、12.5.*(脚本注释指出 Autobahn 自 2017 年 8 月起对这些用例本身就有缺陷)。注意它用 PYTHONHASHSEED=0 启动 wstest,保证用例执行顺序可复现。

两个脚本最后都以 exit $RESULT 返回汇总状态,因此 CI 可以直接把退出码当作通过/失败依据。

如何把一个新功能接入 CI:可复用的接入步骤

把原文档的"为什么"与脚本的"怎么做"合起来,向 libwebsockets 的 CI 体系添加新功能测试可以遵循如下流程(本仓库的脚本即为现成模板):

  1. 先回答"值不值得":该功能是否承诺长期可用?若是,就有义务进 CI;越是冷门、使用者少的功能,越需要 CI 兜底,否则下一次重构就会让它无声坏死。
  2. 确认测试可被 CMake 选项驱动:新增功能需要对应的构建开关(类似 LWS_WITH_MINIMAL_EXAMPLES),并把它通过 $CMAKE_ARGS 传入,使测试活动与构建选项的映射成立。
  3. 补依赖安装分支:在 travis_install.sh 中,按 LWS_METHOD 与平台(Linux 的 apt-get / macOS 的 brew)把新功能所需依赖加上,同时保留 COVERITY_SCAN_BRANCH 的短路出口。
  4. 注册测试动作:在 travis_control.sh 中,决定该功能适合哪种模式(lwsws 全量、smp 压测、mbedtls 轻量 TLS 面等),把对应的测试脚本挂到该模式的执行链上。
  5. 用可判定成败的方式写测试:参考 h2spec.sh 的"失败关键字 + exit 1"、h2load.sh 的"非零退出即失败"、autobahn 脚本的"行为归类统计"以及 attack.sh 的"md5 整体比对",让测试结果能机械地被 CI 判定。

在本仓库中的位置与延伸阅读

本仓库将 libwebsockets 以第三方依赖形式置于 third_party/libwebsockets,全部 README 与测试脚本随树保留,可直接查阅:

若想进一步了解与 CI 相关的构建与测试机制,可继续阅读同目录下的 README.cmake.md(CMake 选项体系)与 README.ctest.md(CTest 集成方式),它们与本文的 CI 脚本互为补充,共同构成 libwebsockets 的"构建—测试—持续集成"全链路。

登录后查看全文
ten-framework