☰
coremltools 仓库内嵌 protobuf 的 conformance 测试体系:架构、协议与多语言运行指南
2026/10/13 1:41:56 网站建设 项目流程
  • AI 应用
  • 机器学习
  • 模型优化

【免费下载链接】coremltools

Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.

项目地址:https://gitcode.com/gh_mirrors/co/coremltools
点击查看免费下载

本文以当前仓库内嵌的 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 明确说明测试可有两种运行方式:

  1. 进程内(in-process):通过 conformance_test.h 定义的ConformanceTestRunner接口在同一个进程内执行测试,适合 C/C++;
  2. 子进程 + 管道:通过 conformance_test_runner.cc 中ForkPipeRunner的实现,fork 出被测进程并通过 stdin/stdout 管道通信。

两者的取舍在协议文件注释中写得很清楚:子进程方式对 C/C++ 以外的语言简单得多;而子进程方式在 iOS 应用等缺少 fork/stdin/stdout 的非常规环境中可能更棘手,此时应改用进程内方式。

2.3 管道上的线协议

每个测试用例在管道上表现为一对ConformanceRequest/ConformanceResponse消息,其线协议非常简单(见 conformance_test_runner.cc):

  1. tester 发送 4 字节小端长度 N;
  2. tester 发送 N 字节的序列化ConformanceRequest;
  3. testee 发送 4 字节小端长度 M;
  4. 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 格式
JSONJSON 格式
JSPBGoogle 内部专用,开源 testee 跳过
TEXT_FORMATtext format

TestCategory(conformance.proto):

值含义
UNSPECIFIED_TEST未指定
BINARY_TEST测试二进制 wire format
JSON_TEST测试 JSON wire format
JSON_IGNORE_UNKNOWN_PARSING_TEST解析 JSON 时忽略未知字段(可选特性)
JSPB_TESTJSPB 格式,开源 testee 跳过
TEXT_FORMAT_TESTtext 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_cpp

JavaScript(Node.js):确保node在 PATH 中:

$ cd conformance && make test_nodejs

Ruby(MRI):先构建 C 扩展,再运行测试:

$ cd ruby && rake $ cd conformance && make test_ruby

4.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_cppmake test_cpp运行 C++ 实现(conformance-cpp)
test_javamake test_javaJava 完整实现(conformance-java)
test_java_litemake test_java_liteJava Lite 实现(conformance-java-lite)
test_csharpmake test_csharpC# 实现(conformance-csharp,基于 dotnet)
test_rubymake test_rubyRuby 实现(conformance_ruby.rb)
test_jrubymake test_jrubyJRuby 实现(同一 conformance_ruby.rb)
test_phpmake test_phpPHP 纯实现(conformance_php.php)
test_php_cmake test_php_cPHP + C 扩展(conformance_php.php)
test_pythonmake test_pythonPython 纯实现(conformance_python.py)
test_python_cppmake test_python_cppPython 使用 C++ 后端(同一脚本)
test_nodejsmake test_nodejsNode.js(conformance_nodejs.js)
test_objcmake test_objcObjective-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()揭示了底层机制:

  1. 调用两次pipe()创建toproc_pipe(父→子)与fromproc_pipe(子→父)两条管道;
  2. fork()产生子进程;
  3. 子进程close(STDIN_FILENO)、close(STDOUT_FILENO)后,用dup2将管道读端映射到 stdin、写端映射到 stdout,关闭其余管道描述符;
  4. 通过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 需要:

  1. 读入 4 字节小端长度前缀 +ConformanceRequest;
  2. 解析ConformanceRequest(这一步应当总是成功);
  3. 解析其中的protobuf_payload/json_payload/text_payload(这一步可能失败,失败则回填parse_error);
  4. 解析成功后,按requested_output_format将消息序列化进ConformanceResponse返回;
  5. 若被测实现不支持某格式/特性,返回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.

项目地址:https://gitcode.com/gh_mirrors/co/coremltools
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询