- AI 应用
- 机器学习
- 模型优化
【免费下载链接】coremltools
Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.
本文以当前仓库内嵌的 protobuf 源码树中的 conformance 目录 及其 README 为核心,系统讲解 Protocol Buffers 一致性测试(conformance test)的设计动机、tester/testee 双进程管道架构、
conformance.proto测试协议,以及 C++、Node.js、Ruby 等语言的构建运行方法。读完本文,你将掌握如何运行仓库内置的 conformance 测试套件,并能够依照协议为任意语言的 protobuf 实现编写一个可被测试器驱动的被测程序(testee)。
一、conformance 测试是什么:为什么需要一套跨语言的一致性测试
Protocol Buffers 是 Google 设计的数据交换格式,围绕它存在 C++、Java、Python、Ruby、PHP、Objective-C、C#、JavaScript 等多语言官方实现,以及大量第三方实现。不同实现即使遵循同一份.proto规范,也可能在二进制 wire format 解析、JSON 映射、text format 输出等细节上出现偏差。因此需要一套与具体语言无关、易于接入任意实现的测试,来统一检验各实现的"完备性(completeness)"与"正确性(correctness)"——这正是 conformance 目录的职责。
在 coremltools 仓库中,该目录位于 deps/protobuf/conformance,与deps/protobuf/src、deps/protobuf/python等构成一套完整的 protobuf 源码树。它本身是 coremltools 构建依赖的一部分,其价值在于:任何被用于 coremltools 的 protobuf 实现,都可以通过这套测试验证其行为是否符合规范。
二、总体架构:tester 与 testee 的管道通信模型
2.1 双进程分工
conformance 测试采用清晰的两端分工:
- tester(测试器):即
conformance-test(仓库中对应 conformance_test_runner.cc 编译出的conformance-test-runner),包含全部测试用例及其期望输出,负责驱动整个测试流程并判定结果; - testee(被测程序):用你想要测试的语言编写的独立小程序,实现 conformance.proto 定义的测试协议,通过管道与 tester 通信。
conformance_test_runner.cc源码开头的注释用一张 ASCII 图直观描述了这一模型:
+--------+ pipe +----------+ | tester | <------> | testee | | | | | | C++ | | any lang | +--------+ +----------+其中 tester 固定由 C++ 实现,testee 可以是任意语言。
2.2 两种运行方式
conformance.proto 明确说明测试可有两种运行方式:
- 进程内(in-process):通过 conformance_test.h 定义的
ConformanceTestRunner接口在同一个进程内执行测试,适合 C/C++; - 子进程 + 管道:通过 conformance_test_runner.cc 中
ForkPipeRunner的实现,fork 出被测进程并通过 stdin/stdout 管道通信。
两者的取舍在协议文件注释中写得很清楚:子进程方式对 C/C++ 以外的语言简单得多;而子进程方式在 iOS 应用等缺少 fork/stdin/stdout 的非常规环境中可能更棘手,此时应改用进程内方式。
2.3 管道上的线协议
每个测试用例在管道上表现为一对ConformanceRequest/ConformanceResponse消息,其线协议非常简单(见 conformance_test_runner.cc):
- tester 发送 4 字节小端长度 N;
- tester 发送 N 字节的序列化
ConformanceRequest; - testee 发送 4 字节小端长度 M;
- testee 发送 M 字节的序列化
ConformanceResponse。
任何语言的 testee 只需要做到"能从 stdin 读、能向 stdout 写"即可参与测试,这正是 README 强调"程序只需要能够从 stdin 读取、向 stdout 写入"的底层含义。
三、测试协议详解:conformance.proto
3.1 请求/响应消息结构
conformance.proto 定义了核心消息:
ConformanceRequest:单个测试用例的输入。其oneof payload承载四种载荷之一:protobuf_payload(bytes):二进制 protobuf 载荷;json_payload(string):JSON 载荷;jspb_payload(string):仅 Google 内部使用,开源 testee 直接跳过;text_payload(string):text format 载荷。
此外还携带
requested_output_format(要求 testee 以何种格式序列化输出)、message_type(测试消息全名,目前为protobuf_test_messages.proto3.TestAllTypesProto3或protobuf_test_messages.google.protobuf.TestAllTypesProto2)、test_category(测试类别)与可选的print_unknown_fields(JSON/text 输出时是否打印未知字段)。ConformanceResponse:单个测试用例的输出,oneof result包含:parse_error:解析失败(注意:部分测试用例本就是故意构造的非法输入,设置该字段不必然意味着测试失败);serialize_error:解析成功但序列化到请求的输出格式时出错;runtime_error:其他运行时错误,该字段一旦设置即表示测试失败;protobuf_payload/json_payload/text_payload:成功解析后按请求格式序列化的结果;jspb_payload:JSPB 输出(开源 testee 可跳过);skipped:testee 跳过该测试(如不支持 JSON 输入输出)。
3.2 枚举定义
WireFormat(conformance.proto):
| 值 | 含义 |
|---|---|
UNSPECIFIED | 未指定 |
PROTOBUF | 二进制 protobuf 格式 |
JSON | JSON 格式 |
JSPB | Google 内部专用,开源 testee 跳过 |
TEXT_FORMAT | text format |
TestCategory(conformance.proto):
| 值 | 含义 |
|---|---|
UNSPECIFIED_TEST | 未指定 |
BINARY_TEST | 测试二进制 wire format |
JSON_TEST | 测试 JSON wire format |
JSON_IGNORE_UNKNOWN_PARSING_TEST | 解析 JSON 时忽略未知字段(可选特性) |
JSPB_TEST | JSPB 格式,开源 testee 跳过 |
TEXT_FORMAT_TEST | text format,C++/Java/Python 已支持,其他语言可跳过 |
FailureSet(conformance.proto):runner 会以message_type == "conformance.FailureSet"的请求作为第一个请求,要求 testee 返回其已知失败的测试名列表(序列化在protobuf_payload中)。
3.3 测试命名规范
从 conformance_test.cc 的GetTestName()可以看出,每个测试用例都有一个结构化全名,格式为:
<级别>.<Proto3|Proto2>.<输入格式>.<用例名>.<输出格式>例如Required.Proto3.ProtobufInput.PrematureEofInPackedField.BOOL、Recommended.Proto3.JsonInput.RepeatedFieldTrailingComma,这些名字既出现在 failure_list_cpp.txt 等失败列表中,也被 conformance_python.py 用于上报已知失败。级别(ConformanceLevel)分Required(必需)与Recommended(推荐)两级,详见下文的--enforce_recommended说明。
四、构建与运行:各语言测试命令
4.1 前置条件:先构建 protoc
所有测试都依赖protoc生成的代码,因此在运行任何 conformance 测试前,必须在 protobuf 源码树根目录执行make(对应仓库中的 deps/protobuf):
$ make这一步骤由 Makefile.am 中的protoc_middleman目标承接:它调用protoc以--cpp_out、--java_out、--ruby_out、--objc_out、--python_out、--php_out、--js_out等插件一次性为 conformance.proto、test_messages_proto2.proto、test_messages_proto3.proto以及 Any/Duration/FieldMask/Struct/Timestamp/Wrappers 等 well-known types 生成各语言绑定,并 touchprotoc_middleman作为增量标记。
4.2 README 明确给出的运行命令
以下命令均在 protobuf 源码树内执行(本文示例路径以仓库根目录为起点,实际操作时先cd deps/protobuf):
C++:
$ cd conformance && make test_cppJavaScript(Node.js):确保node在 PATH 中:
$ cd conformance && make test_nodejsRuby(MRI):先构建 C 扩展,再运行测试:
$ cd ruby && rake $ cd conformance && make test_ruby4.3 其余语言:从 Makefile 与 CI 脚本看全貌
README 说明"protobuf 源码树中的大多数语言都已配置好 conformance 测试,其中一些配置比较棘手,可参考仓库根目录的tests.sh查看 CI(Travis/kokoro)的运行方式"。当前仓库的 tests.sh 正是这一脚本,其build_cpp()中执行cd conformance && make test_cpp(tests.sh),build_csharp()中执行cd conformance && make test_csharp(tests.sh)。
而 Makefile.am 完整列出了全部可用的测试目标:
| 目标 | 命令 | 说明 |
|---|---|---|
test_cpp | make test_cpp | 运行 C++ 实现(conformance-cpp) |
test_java | make test_java | Java 完整实现(conformance-java) |
test_java_lite | make test_java_lite | Java Lite 实现(conformance-java-lite) |
test_csharp | make test_csharp | C# 实现(conformance-csharp,基于 dotnet) |
test_ruby | make test_ruby | Ruby 实现(conformance_ruby.rb) |
test_jruby | make test_jruby | JRuby 实现(同一 conformance_ruby.rb) |
test_php | make test_php | PHP 纯实现(conformance_php.php) |
test_php_c | make test_php_c | PHP + C 扩展(conformance_php.php) |
test_python | make test_python | Python 纯实现(conformance_python.py) |
test_python_cpp | make test_python_cpp | Python 使用 C++ 后端(同一脚本) |
test_nodejs | make test_nodejs | Node.js(conformance_nodejs.js) |
test_objc | make test_objc | Objective-C(需OBJC_CONFORMANCE_TEST条件) |
可以看出每个目标都统一携带--enforce_recommended标志,并为其实现指定对应的失败列表(如failure_list_cpp.txt、text_format_failure_list_cpp.txt)。仓库还提供了 Bazel 版本的一键运行脚本 conformance_test_runner.sh,它通过--testee、--failure_list、--text_format_failure_list三个参数定位被测程序与失败列表,并在内部为 runner 追加--enforce_recommended。
五、测试运行器源码剖析:ForkPipeRunner 是如何工作的
5.1 进程派生与管道建立
conformance_test_runner.cc 的ForkPipeRunner::SpawnTestProgram()揭示了底层机制:
- 调用两次
pipe()创建toproc_pipe(父→子)与fromproc_pipe(子→父)两条管道; fork()产生子进程;- 子进程
close(STDIN_FILENO)、close(STDOUT_FILENO)后,用dup2将管道读端映射到 stdin、写端映射到 stdout,关闭其余管道描述符; - 通过
execv启动被测程序(execv成功则永不返回)。
值得注意的是代码注释中的设计权衡(conformance_test_runner.cc):不能为每个测试重新 fork 一个进程——数千个用例加上 Java 这类启动耗时可达 100ms 以上的子进程会慢到无法接受;也不能把所有测试打包成一条大消息——那样会丧失"定位是哪个用例导致崩溃"的可观测性。因此选择了长驻子进程 + 逐用例消息轮询的折中方案。
5.2 崩溃检测与恢复
RunTest()(conformance_test_runner.cc)在写入请求后尝试读取响应,若读取失败则假定被测进程崩溃,立即waitpid收割子进程,将"child exited, status=N"或"child killed by signal N"写入runtime_error字段,从而把崩溃转化为一个可判定的测试失败结果,而不是让整个套件挂死。
5.3 runner 命令行参数
UsageError()(conformance_test_runner.cc)列出了全部参数:
| 参数 | 作用 |
|---|---|
--failure_list <filename> | 指定预期失败的测试列表文件,每行一个测试名,#开头为注释;文件由 ParseFailureList 解析(去掉空白、剥离注释、忽略空行) |
--text_format_failure_list <filename> | 指定 text format 测试套件中预期失败列表 |
--enforce_recommended | 强制Recommended级别的用例也必须通过,用于严格遵循 protobuf 规范 |
--verbose | 输出详细日志 |
命令行最后一个非选项参数即被测程序及其参数。入口 conformance_test_main.cc 注册了两个测试套件——BinaryAndJsonConformanceSuite(binary_json_conformance_suite.h)与TextFormatConformanceTestSuite——并交由ForkPipeRunner::Run依次执行。从 binary_json_conformance_suite.h 的方法清单可以看到 JSON 侧测试的覆盖面:字段命名约定、非重复类型、重复类型、null 类型、wrapper 类型、FieldMask、Struct、Value、Any 等专项测试。
5.4 结果判定逻辑
conformance_test.cc 展示了判定核心:ReportSuccess与ReportFailure会对照预期失败集合处理——若某个在失败列表中的测试反而成功,会输出 "test is in the failure list, but test succeeded. Remove it from the failure list." 的错误提示;若Recommended级别用例失败但未开启--enforce_recommended,则只输出 WARNING 而不计入失败。最终对比由MessageDifferencer完成,确保序列化结果与期望逐字段一致。
六、failure list:已知失败清单机制
各语言的failure_list_*.txt文件是 conformance 体系的重要组成。以 failure_list_cpp.txt 为例,其头部注释说明了用途:
这是当前 C++ 实现已知失败的 conformance 测试清单。通过列在这里,我们可以持续跟踪哪些用例失败、并确保不引入其他用例的回归。
文件格式为每行一个完整测试名,#起头的行为注释,例如Recommended.Proto3.JsonInput.RepeatedFieldTrailingComma、Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput等。仓库为每种语言都维护了对应的二进制/JSON 失败清单(failure_list_cpp.txt、failure_list_java.txt、failure_list_python.txt、failure_list_js.txt、failure_list_ruby.txt、failure_list_php.txt、failure_list_csharp.txt、failure_list_objc.txt等)以及独立的text_format_failure_list_*.txt。此外还有 update_failure_list.py 这类维护工具,便于在实现修复后同步更新清单。上文已说明,失败列表不仅"容忍"已知失败,还反向监督:被列入清单的用例一旦通过,runner 会明确报错要求移除。
七、为新的 protobuf 实现编写 conformance 测试程序
7.1 最小实现要求
README 明确指出:要测试一个新的 protobuf 实现,只需用该语言编写一个实现 conformance.proto 测试协议的独立程序,该程序只需能读写 stdin/stdout。具体而言,testee 需要:
- 读入 4 字节小端长度前缀 +
ConformanceRequest; - 解析
ConformanceRequest(这一步应当总是成功); - 解析其中的
protobuf_payload/json_payload/text_payload(这一步可能失败,失败则回填parse_error); - 解析成功后,按
requested_output_format将消息序列化进ConformanceResponse返回; - 若被测实现不支持某格式/特性,返回
skipped。
7.2 以 C++ 参考实现为模板
README 推荐参考 C++ 实现(对应 conformance_cpp.cc,README 描述其为约 150 行的精简易读参考)。该文件的DoTest()函数(conformance_cpp.cc)完整示范了上述流程:
- 通过
DescriptorPool::generated_pool()->FindMessageTypeByName(request.message_type())按请求中的消息类型名动态查找到TestAllTypesProto2/TestAllTypesProto3的消息描述,再经MessageFactory构造实例——这正是message_type字段的用途; - 按 payload 分支处理:二进制载荷直接
ParseFromString;JSON 载荷经JsonToBinaryString转换(若test_category == JSON_IGNORE_UNKNOWN_PARSING_TEST则开启ignore_unknown_fields);text 载荷经TextFormat::ParseFromString; - 特殊处理
conformance.FailureSet请求:若被测实现存在已知失败,此时应返回序列化后的FailureSet(C++ 示例通过kFailures数组维护); - 输出阶段按
requested_output_format分别走SerializeToString(protobuf)、BinaryToJsonString(JSON)、TextFormat::Printer(text format)。
仓库为每种语言都提供了对应参考实现:conformance_python.py、conformance_nodejs.js、conformance_ruby.rb、conformance_php.php、conformance_objc.m、ConformanceJava.java 与 ConformanceJavaLite.java。其中 Python 实现(conformance_python.py)在收到FailureSet请求时会先用一个特殊的畸形载荷探测所用 C++ 后端是否为"旧解析器",据此上报对应的一组已知失败测试名——展示了 FailureSet 机制在实际实现中的灵活运用。
编写完成后,通过 runner 以被测程序路径作为参数启动即可:
./conformance-test-runner --enforce_recommended --failure_list <你的失败列表> ./你的被测程序(对应仓库内各make test_*目标实际执行的命令形态,例如 Makefile.am 中的test_cpp。)
八、可移植性说明与适用前提
README 末尾明确提示:测试运行器目前不支持 Windows(其实现依赖fork、pipe、dup2、execv、waitpid等 UNIX 系统调用,conformance_test_runner.cc 的注释也承认使用了 UNIX 专用 API,并注明src/google/protobuf/compiler/subprocess.h虽有跨平台 API,但只支持向子进程发送单条消息,无法满足逐用例交互的需求),欢迎提交补丁修复。
此外需要强调的是,以上全部命令的适用前提是已在 deps/protobuf 根目录成功构建protoc(make),且各语言运行时(node、ruby、dotnet、php、javac 等)按需就位——这与 README 的说明以及 tests.sh 中internal_build_cpp()先构建 C++ 主构建(protoc 与 libprotobuf)再运行 conformance 的顺序完全一致。
九、小结
conformance 测试体系的价值在于"一份测试、任意实现":tester 用 C++ 集中承载全部用例与期望,testee 只需实现 conformance.proto 定义的 stdin/stdout 管道协议即可被驱动。本文覆盖了从架构模型、协议字段、构建运行命令到 runner 源码、failure list 机制与新实现接入的完整链路。若要在 coremltools 的 protobuf 依赖上进一步实践,可依次阅读 README、conformance.proto、conformance_test_runner.cc 与 conformance_cpp.cc,再对照 Makefile.am 中的各test_*目标逐语言验证。
- AI 应用
- 机器学习
- 模型优化
【免费下载链接】coremltools
Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.
相关推荐
ProxySQL 仓库开发指南:构建体系、Feature Tier、TAP 测试与双协议架构详解
ProxySQL 仓库开发指南:构建体系、Feature Tier、TAP 测试与双协议架构详解 ProxySQL 是一个用 C++17 编写的高性能、协议感知
后端数据库负载均衡Ruby 多语言一致性测试(Conformance)全解析:以 Go 为基准的跨语言协议校验
Ruby 多语言一致性测试(Conformance)全解析:以 Go 为基准的跨语言协议校验 Ruby 版本的 riverqueue 采用"Go 生成测试夹具(
任务调度后端libssh2 源码解读与构建指南:Nmap 仓库内嵌的 SSH2 协议库
libssh2 源码解读与构建指南:Nmap 仓库内嵌的 SSH2 协议库 libssh2 是一个以 C 语言实现的 SSH2 协议客户端库,本仓库在 libs
网络安全网络漏洞扫描
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考