Apache Arrow 跨语言集成测试完全指南:JSON 测试数据格式与 archery integration 实战 📅 发布时间:2026/9/14 5:23:00 👁 浏览次数: Apache Arrow 跨语言集成测试完全指南JSON 测试数据格式与 archery integration 实战【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow本篇技术指南围绕 Apache Arrow 仓库中的 Integration.rst 文档系统讲解 Arrow 如何在不同语言实现C、Java、Go、C#、Rust 等之间保证互操作性包括基于 producer/consumer 的集成测试策略、archery integration命令的完整用法、以及专为跨语言验证设计的 JSON 测试数据格式Schema、Field、Type、RecordBatch、DictionaryBatch 与各类缓冲区的编码规范。读完本文你将掌握如何安装 Archery 集成测试组件、如何构建并启用各语言的测试入口、如何阅读与编写 Arrow 集成测试 JSON 文件以及如何借助数据生成器与 gold 文件理解 Arrow 列式格式与 IPC/Flight/C Data Interface 三大规范的兼容性边界。为什么需要跨语言集成测试Apache Arrow 是一个多语言工具箱同一份列式内存数据可以被 C、Java、Go、C#、Rust、Ruby、JavaScript 等实现读写。为了保证这些实现彼此互通仅靠各自单元测试远远不够——不同实现可能对同一规范产生不同的理解。因此 Arrow 项目专门维护了一套跨语言集成测试并作为 CI持续集成任务定期运行。这套集成测试覆盖了 Arrow 的三大规范相关规范文档与源码均在本仓库中IPC 格式Arrow 文件File与流Stream格式见 format/File.fbs 与 format/Message.fbsFlight RPC 协议跨语言远程数据交换协议见 format/Flight.protoC Data Interface以 C 结构体ArrowArray、ArrowSchema实现零拷贝跨语言数据交换的 ABI 接口。集成测试的核心目标是验证各实现对这些规范的遵循程度而不是验证某一语言内部的正确性。测试策略producer / consumer 配对模型Arrow 的集成测试策略可以概括为四个要点测试数据集使用专门的 JSON 格式描述。这种格式是自定义的、人类可读的、为 Arrow 集成测试专门设计的后文详述它不是 Arrow 的规范格式non-canonical但足够精确地描述任意 Arrow 列式数据。JSON 文件由测试框架Archery生成。不同的文件覆盖不同的数据类型与特性——数值、列表、字典编码等——从而在出现不兼容时能快速定位到具体类型而不是混杂在单个大文件里难以排查。每种语言实现提供入口点能够完成JSON ↔ Arrow 内存表示的转换并能够把 Arrow 内存数据以目标格式IPC / Flight / C Data Interface暴露出来。每种格式都对所有支持的 (producer, consumer) 实现配对进行测试。producer 读取 JSON 文件 → 转换为内存中的 Arrow 数据 → 用被测格式导出consumer 以该格式读回数据 → 转回 Arrow 内存表示同时读取同一份 JSON 文件并校验两份数据集完全一致。在 runner.py 中可以看到这一策略的实现IntegrationRunner通过itertools.product对启用的 producer 集合与 consumer 集合求笛卡尔积为每一对组合运行测试用例。其文档注释见 cli.py 中integration命令的 docstring给出了一个直观的例子若启用 C、Java、Rust 三个实现则测试 9 种组合C→C、C→Java、C→Rust、Java→C……Rust→Rust。示例一IPC 格式的配对测试假设正在测试Arrow C 作为 producer、Arrow Java 作为 consumer的 IPC 格式对一个 JSON 文件的测试流程如下C 可执行程序读取 JSON 文件转换为 Arrow 内存数据写出一个 Arrow IPC 文件文件路径通常通过命令行参数给出Java 可执行程序读取同一 JSON 文件转换为 Arrow 内存数据同时读取 C 生成的 IPC 文件最后校验两份 Arrow 内存数据集是否相等。对应到 runner.py 的_produce_consume方法其调用链为producer.json_to_file(json_path, file_path)生成 IPC 文件 →consumer.validate(json_path, file_path)校验 →producer.file_to_stream()把文件转成流 →consumer.stream_to_file()读回流并再写回文件 → 再次validate校验。即每个测试用例实际上验证了JSON→文件文件→流流→文件三条路径的一致性。示例二C Data Interface 的配对测试假设正在测试Arrow Go 作为 producer、Arrow C# 作为 consumer的 C Data Interface流程如下测试框架在堆上分配一个 C 的ArrowArray结构体Go 的进程内入口例如一个 C 兼容的函数调用读取 JSON 文件将其中的一个 record batch 导出到该ArrowArray结构体中C# 的进程内入口读取同一 JSON 文件把同一个 record batch 转为 Arrow 内存数据同时从ArrowArray结构体中导入 Go 导出的 record batch校验两者相等然后释放导入的 record batch视实现语言的能力测试框架还可能断言内存消耗保持一致即导出的 record batch 没有泄漏最后测试框架释放ArrowArray结构体。在源码中这一步通过 cdata.py 的 FFI 层完成_run_c_schema_test_case使用ffi.new(struct ArrowSchema*)分配结构体_run_c_array_test_cases则逐 batch 使用ffi.new(struct ArrowArray*)导出/导入并比较见 runner.py。值得注意的实现细节是C Data Interface 测试强制串行执行serial True因为只有串行才能做准确的内存账目核对。运行集成测试安装 Archery 与构建各语言组件集成测试的数据生成器与执行器都实现在 Archery 工具中源码位于 dev/archery。首先需要安装 archery 的integration组件$ pip install -e dev/archery[integration]安装完成后可用archery integration命令查看全部可用选项$ archery integration --help构建被测组件运行集成测试之前需要先构建希望纳入测试的各个语言组件C、Java 等具体构建方式参见各语言的开发者文档。部分语言需要额外的构建选项才能启用集成测试入口例如C 需要在 cmake 命令中加入-DARROW_BUILD_INTEGRATIONON从 cli.py 的命令定义看archery integration支持通过--with-cpp、--with-java、--with-js、--with-dotnet、--with-go、--with-nanoarrow、--with-ruby、--with-rust分别启用各实现这些选项也可通过环境变量ARCHERY_INTEGRATION_WITH_*注入--with-all则一次性启用所有已知实现。常用命令示例只启用 C运行 IPC 集成测试archery integration --run-ipc --with-cpp1启用 C 与 Java 运行 IPC 测试Java 需要先构建出带依赖的集成 JAR 并导出路径VERSION14.0.0-SNAPSHOT export ARROW_JAVA_INTEGRATION_JAR$JAVA_DIR/tools/target/arrow-tools-$VERSION-jar-with-dependencies.jar archery integration --run-ipc --with-cpp1 --with-java1运行全部测试包括 IPC、Flight 与 C Data Interfacearchery integration --with-all --run-flight --run-ipc --run-c-data需要说明的是Arrow 项目本身就在 CI 中运行这些测试且 CI 任务基于 Docker Compose参见仓库根目录的 compose.yaml。你可以直接在本地运行该 Docker Compose 任务或参考它了解如何构建其他语言、启用特定测试。此外archery integration还有几个实用选项--random-seed默认 12345用于固定数据生成的随机种子保证测试可复现见 cli.py 中的np.random.seed(random_seed)--serial强制串行执行-x/--stop-on-error遇到首个错误即停止-k/--match只运行名称包含指定子串的用例。JSON 测试数据格式规范JSON 是 Arrow 列式数据的跨语言集成测试表示。官方明确说明这一表示不是规范格式not canonical但它提供了验证各语言实现的可读途径。仓库中带有两个可直接阅读的示例文件docs/source/format/integration_json_examples/simple.json含 int32、double、utf8 三种字段与 docs/source/format/integration_json_examples/struct.json。文件顶层结构Data File{ schema: /*Schema*/, batches: [ /*RecordBatch*/ ], dictionaries: [ /*DictionaryBatch*/ ], }所有文件都包含schema与batchesdictionaries仅当 schema 中存在字典类型字段时才出现。Schema{ fields : [ /* Field */ ], metadata : /* Metadata */ }Field{ name : name_of_the_field, nullable : /* boolean */, type : /* Type */, children : [ /* Field */ ], dictionary: { id: /* integer */, indexType: /* Type */, isOrdered: /* boolean */ }, metadata : /* Metadata */ }关键规则dictionary属性当且仅当该 Field 对应字典类型时出现其id映射到DictionaryBatch中的某一列。此时type属性描述的是字典的值类型。对基本类型primitivechildren为空数组。Metadatanull | [ { key: /* string */, value: /* string */ } ]即自定义元数据的键值对数组。可以省略或为 null此时等价于[]无元数据。规范不禁止出现重复的 key。Type 及其各变体Type 的name取值如下具体字段定义与 format/Schema.fbs 保持一致null|struct|list|largelist|listview|largelistview|fixedsizelist|union|int|floatingpoint|utf8|largeutf8|binary|largebinary|utf8view|binaryview|fixedsizebinary|bool|decimal|date|time|timestamp|interval|duration|map|runendencodedInt{ name : int, bitWidth : /* integer */, isSigned : /* boolean */ }FloatingPoint{ name : floatingpoint, precision : HALF|SINGLE|DOUBLE }FixedSizeBinary{ name : fixedsizebinary, byteWidth : /* byte width */ }Decimal{ name : decimal, precision : /* integer */, scale : /* integer */ }Timestamp{ name : timestamp, unit : $TIME_UNIT, timezone: $timezone }其中$TIME_UNIT取值为SECOND|MILLISECOND|MICROSECOND|NANOSECONDtimezone为可选字符串。Duration{ name : duration, unit : $TIME_UNIT }Date{ name : date, unit : DAY|MILLISECOND }Time{ name : time, unit : $TIME_UNIT, bitWidth: /* integer: 32 or 64 */ }Interval{ name : interval, unit : YEAR_MONTH|DAY_TIME }Union{ name : union, mode : SPARSE|DENSE, typeIds : [ /* integer */ ] }注意Union 的typeIds是各数组槽位中用于标记激活哪个成员的判别码discriminant一般情况下这些判别码并不等于对应 child 数组的下标。List{ name: list }list 元素是什么类型 由 Field 的children中唯一的那个 Field 描述。例如一个int32列表{ name: list_nullable, type: { name: list }, nullable: true, children: [ { name: item, type: { name: int, isSigned: true, bitWidth: 32 }, nullable: true, children: [] } ] }FixedSizeList{ name: fixedsizelist, listSize: /* integer */ }同样带有长度为 1 的children数组。Struct{ name: struct }Field 的children是带有有意义名称与类型的 Field 数组。Map{ name: map, keysSorted: /* boolean */ }Field 的children只含一个struct字段该 struct 自身又包含两个名为 key 和 value 的子字段。从 datagen.py 的MapField实现看key 字段不可为空assert not key_field.nullable且 entries 的 struct 字段名默认为 entries非规范 map 用例可自定义该名称。Null{ name: null }RunEndEncoded{ name: runendencoded }Field 的children必须恰好两个子字段第一个必须名为 run_ends、不可为空、且类型为int16/int32/int64之一第二个必须名为 values类型不限。对应源码可见 datagen.py 中的RunEndsField断言 bit_width ∈ {16, 32, 64} 且不可为空。扩展类型Extension Types的表示与 IPC 格式一致扩展类型用其底层存储类型storage type加上专用字段元数据来表示以便重建扩展类型。假设一个由structnumer: int32, denom: int32存储支撑的 rational 扩展类型其字段表示为{ name : name_of_the_field, nullable : /* boolean */, type : { name : struct }, children : [ { name: numer, type: { name: int, bitWidth: 32, isSigned: true } }, { name: denom, type: { name: int, bitWidth: 32, isSigned: true } } ], metadata : [ {key: ARROW:extension:name, value: rational}, {key: ARROW:extension:metadata, value: rational-serialized} ] }在 datagen.py 的ExtensionField中可以看到扩展类型就是通过在存储字段的 metadata 上追加ARROW:extension:name与ARROW:extension:metadata两对键值实现的其 type 与 children 完全委托给 storage field。RecordBatch{ count: /* integer number of rows */, columns: [ /* FieldData */ ] }DictionaryBatch{ id: /* integer */, data: [ /* RecordBatch */ ] }FieldData 与缓冲区编码{ name: field_name, count: field_length, $BUFFER_TYPE: /* BufferData */ ... $BUFFER_TYPE: /* BufferData */ children: [ /* FieldData */ ] }命名对应规则Schema 中 Field 的name与 RecordBatch 的columns中 FieldData 的name一一对应对嵌套类型list、struct 等Field 的children中每个 Field 的name对应其 FieldData 的children中对应 FieldData 的name而DictionaryBatch 内部的 FieldData其name不与任何东西对应。$BUFFER_TYPE取值为以下之一VALIDITY有效位图validity bitmapOFFSET偏移量用于 string、list 等变长类型TYPE_ID类型判别码用于 unionDATA数据BufferData 的编码方式VALIDITYJSON 数组元素为 1有效或 0null。即使 Field 不可为空也仍然要有VALIDITY数组——只是所有值都是 1。OFFSET32 位偏移用整数 JSON 数组64 位偏移用字符串格式化的整数数组。TYPE_ID整数 JSON 数组。DATA按逻辑类型编码的值数组见下。VARIADIC_DATA_BUFFERS以十六进制字符串表示的多个数据缓冲区组成的 JSON 数组用于 view 类型。VIEWS编码后的 view 对象 JSON 数组每个 view 包含SIZEview 的大小整数INLINED内联的编码值当SIZE小于 12 时出现PREFIX_HEXview 前四个字节的十六进制编码非内联时出现BUFFER_INDEX在VARIADIC_DATA_BUFFERS中索引非内联时出现OFFSET在被查看缓冲区中的偏移非内联时出现。DATA 值按逻辑类型的编码规则布尔类型1true/ 0false数组整数类类型含 timestampJSON 数字数组64 位整数为避免精度丢失用 JSON 字符串格式的整数数组浮点类型JSON 数字数组数值限制到 3 位小数以避免精度丢失datagen.py 中FloatingPointField.generate_column即用np.round(values, 3)实现二进制类型大写十六进制编码的字符串数组用以表示任意二进制数据UTF-8 字符串类型JSON 字符串数组。嵌套与边界情况对 list 与 largelistFieldData 有VALIDITY与OFFSET其余数据在children中child FieldData 拥有与父级相同的全部属性例如int32列表的 child 数据就有VALIDITY与DATA。对 fixedsizelist没有OFFSET成员因为偏移已由listSize隐含。注意 child 数据的count可能与父count不一致例如一个listSize为 4 的FixedSizeList、RecordBatch 有 7 行则其 FieldData 的children中数据 count 为 28。对 null 类型FieldData 不含任何缓冲区。simple.json示例中bazutf8列的OFFSET: [0, 2, 2, 2, 5, 9]、DATA: [aa, , , bbb, cccc]正是变长类型编码的直观体现第三个 batch 全部 VALIDITY 为 0 则展示了全 null 数据的表示方式。Archery 集成测试用例清单了解自动化测试到底覆盖了哪些用例有助于判断未来 Arrow 格式变更时需要进行哪些手工测试。集成测试用例分两类一类由 Archery 中的数据生成器即时生成另一类是存放在 arrow-testing 仓库data/arrow-ipc-stream/integration目录中的gold 文件。数据生成器测试Data Generator Tests这些用例由archery integration命令即时生成并测试生成逻辑集中在 datagen.py 的get_generated_json_files函数中。覆盖范围包括基本类型Primitive Types无 batch、各种基本值、零长度 batch、string/binary 的大偏移large offset情形对应generate_primitive_case、generate_large_binary_case等Null 类型平凡的 null batchDecimal128 / Decimal256另有 decimal32、decimal64各自对不同实现有 skip 标注例如 decimal256 跳过 JSdecimal32/64 跳过 Java/JS/Ruby/GoDateTime各种时间单位对应generate_datetime_case覆盖 date/time/timestamp 的全部分辨率含时区与非时区变体Durations各种单位IntervalsYearMonth、DayTime其中 MonthDayNano 单独作为一个用例Map 类型含非规范 Mapnon-canonical maps嵌套类型Lists、Structs、带大偏移的 Lists以及递归嵌套generate_recursive_nested_caseUnions稀疏与稠密 uniongenerate_unions_case中稀疏/稠密各两组typeIds 非连续如 [5,7]、[10,20]、[42,43,44]用于验证判别码与 child 下标不一致的情形自定义元数据generate_custom_metadata_case含未注册扩展类型的元数据含重复字段名的 Schemagenerate_duplicate_fieldnames_case字典类型有符号索引、无符号索引、嵌套字典generate_dictionary_case、generate_dictionary_unsigned_case、generate_nested_dictionary_caseRun end encodedgenerate_run_end_encoded_case覆盖 int16/int32/int64 三种 run_ends 宽度与多种 values 类型Binary view 与 string viewgenerate_binary_view_case含内联与非内联两种 view 形态BINARY_VIEW_INLINE_SIZE为 12List view 与 large list viewgenerate_list_view_case扩展类型generate_extension_case另含generate_extension_wrapped_union_case验证扩展类型包裹 union 的场景。从get_generated_json_files的代码可以看到每个生成的 JSON 文件命名形如generated_name.json且大量用例通过.skip_tester(...)/.skip_format(...)标注了特定实现暂不支持的跳过项例如 nanoarrow 跳过字典与 view 系列Ruby 跳过 run-end-encoded 与 view 系列等这些跳过机制定义在 datagen.py 的File.should_skip中。Gold 文件集成测试Gold File Integration Tests预生成的 JSON 与 Arrow IPC 文件同时含文件与流两种格式存放在 arrow-testing 仓库的data/arrow-ipc-stream/integration目录被 runner.py 引用见_gold_tests其按前缀识别 gold 目录版本并设置相应 skip 与 quirks。这些文件被视为正确的基准用于测试。覆盖的用例包括向后兼容Backwards Compatibility基于0.14.1 格式测试datetime、decimals、dictionaries、intervals、maps、nested types (list, struct)、primitives、primitive with no batches、primitive with zero length batches基于0.17.1 格式测试unions字节序Endianness以下用例同时提供 Little Endian 与 Big Endian 版本用于自动转换测试——custom metadata、datetime、decimals、decimal256、dictionaries、dictionaries with unsigned indices、duplicate fieldnames 的 record batch、extension types、interval types、map types、non-canonical map data、nested types (lists, structs)、nested dictionaries、nested large offset types、nulls、primitive data、large offset binary and strings、primitives with no batches、primitive batches with zero length、recursive nested types、union types压缩测试CompressionLZ4 与 ZSTD含共享字典的 BatchesBatches with Shared Dictionaries。值得留意的实现细节_gold_tests对旧版本 gold 文件设置了quirks如no_decimal_validate、no_date64_validate、no_times_validate因为 0.14.1/0.17.1/1.0.0 等旧版本生成的 decimal 值可能超出给定 precision 的范围ARROW-13558校验时需放行。生成新的 Gold 文件当列式格式或 IPC 规范更新时往往需要新增 gold 文件。Archery 提供了专门的选项。官方建议使用某个众所周知的 Arrow 实现版本来生成 gold 文件。例如若在./build/release/有 Arrow C 的构建可用以下命令在/tmp/gold-files目录生成新 gold 文件export ARROW_CPP_EXE_PATH./build/release/ archery integration --with-cpp 1 --write-gold-files/tmp/gold-files从 cli.py 的实现看--write-gold-files要求恰好启用一个实现len(testers) ! 1时直接报错随后调用 datagen.py 的generate_gold_files它先生成全部generated_*.json再用该实现的json_to_file/file_to_stream分别产出.arrow_file与.stream文件同时把 JSON 用 gzip 压缩为.json.gzgold 测试读取时再解压见_gold_tests。C 实现的可执行文件路径默认由环境变量ARROW_CPP_EXE_PATH指定默认为cpp/build/debug见 tester_cpp.py因此上例中把它指向./build/release/。结合源码理解测试执行流程将上述内容串联起来一次典型的archery integration --run-ipc --with-cpp1执行流程是cli.py 的integration命令解析参数调用np.random.seed(random_seed)固定随机数select_testers()依据--with-*参数实例化各语言 Tester如CppTester、JavaTester、GoTester等定义于 tester_*.py 系列文件run_all_tests()汇总静态 JSONintegration/data/*.json与datagen.get_generated_json_files()即时生成的 JSON构造 Flight 场景列表runner.py 中定义了 auth:basic_proto、middleware、ordered、flight_sql 等场景并创建IntegrationRunner按--run-ipc/--run-flight/--run-c-data分别触发run_ipc()/run_flight()/run_c_data()对每个 (producer, consumer) 配对执行用例IPC 与 C Data 用例的失败被记录为FailureFlight 用例则先启动 server 再让 client 上传下载比对最后汇总输出若干 failures, 若干 skips只要存在失败即以退出码 1 结束sys.exit(1)便于 CI 捕获。这套机制保证了 Arrow 的每一次格式演进都有跨语言回归兜底任何一端的实现只要对 Schema.fbs、IPC 或 Flight 规范的理解出现偏差都会在某一对 producer/consumer 组合的比对中被暴露出来。小结Apache Arrow 的跨语言集成测试体系由三块基石构成人类可读的 JSON 测试数据格式精确描述 Schema、Type、RecordBatch 与各缓冲区编码、producer/consumer 配对执行器runner.py datagen.py 覆盖 IPC、Flight、C Data Interface 三大规范、以及gold 文件0.14.1/0.17.1 兼容性与大小端/压缩等边界场景。对于希望为 Arrow 贡献新语言实现或修改列式格式的开发者掌握archery integration的用法与 JSON 格式规范是确保改动不破坏跨语言互操作性的前提。【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考