libwebsockets 持续集成(CI)测试接入指南:从 Travis 配置到多模式测试矩阵
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.ymlmaps 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 体系添加新功能测试可以遵循如下流程(本仓库的脚本即为现成模板):
- 先回答"值不值得":该功能是否承诺长期可用?若是,就有义务进 CI;越是冷门、使用者少的功能,越需要 CI 兜底,否则下一次重构就会让它无声坏死。
- 确认测试可被 CMake 选项驱动:新增功能需要对应的构建开关(类似
LWS_WITH_MINIMAL_EXAMPLES),并把它通过$CMAKE_ARGS传入,使测试活动与构建选项的映射成立。 - 补依赖安装分支:在 travis_install.sh 中,按
LWS_METHOD与平台(Linux 的 apt-get / macOS 的 brew)把新功能所需依赖加上,同时保留COVERITY_SCAN_BRANCH的短路出口。 - 注册测试动作:在 travis_control.sh 中,决定该功能适合哪种模式(
lwsws全量、smp压测、mbedtls轻量 TLS 面等),把对应的测试脚本挂到该模式的执行链上。 - 用可判定成败的方式写测试:参考 h2spec.sh 的"失败关键字 + exit 1"、h2load.sh 的"非零退出即失败"、autobahn 脚本的"行为归类统计"以及 attack.sh 的"md5 整体比对",让测试结果能机械地被 CI 判定。
在本仓库中的位置与延伸阅读
本仓库将 libwebsockets 以第三方依赖形式置于 third_party/libwebsockets,全部 README 与测试脚本随树保留,可直接查阅:
- 本文主依据:README.ci.md
- 依赖安装:scripts/travis_install.sh
- 测试调度:scripts/travis_control.sh
- 专项测试:h2spec.sh、h2load.sh、h2load-smp.sh、attack.sh、autobahn-test-server.sh、autobahn-test-client.sh
若想进一步了解与 CI 相关的构建与测试机制,可继续阅读同目录下的 README.cmake.md(CMake 选项体系)与 README.ctest.md(CTest 集成方式),它们与本文的 CI 脚本互为补充,共同构成 libwebsockets 的"构建—测试—持续集成"全链路。