DataFusion sqllogictest 实战:SQL 快照测试体系的运行机制与完整操作指南

发布时间:2026/9/25 11:13:22
DataFusion sqllogictest 实战:SQL 快照测试体系的运行机制与完整操作指南 大数据数据分析后端【免费下载链接】datafusionApache DataFusion SQL Query Engine项目地址https://gitcode.com/gh_mirrors/datafu/datafusion点击查看免费下载本文以 DataFusion 仓库中的datafusion/sqllogictestcrate 官方 README 为主线系统讲解如何用 sqllogictest.slt文件对 SQL 引擎做快照式正确性测试包括测试文件的编写与补全、子串/行号过滤、Postgres 兼容对比、TPC-H 与 SQLite 大数据量套件、configMatrix配置扫描、scratch 临时目录规范以及.slt文件格式的细节。读完本文后你可以独立为 DataFusion 编写可运行的.slt测试理解测试运行器runner的底层过滤、校验与计时实现并掌握各类环境变量与命令行参数的用法。1. 什么是 datafusion-sqllogictestdatafusion/sqllogictest是 Apache DataFusion它内嵌了 sqllogictest-rs 库当前固定版本sqllogictest 0.29.1负责解析并执行.slt测试文件。sqllogictest 这套格式最初为 SQLite 设计目标是验证 SQL 查询在不同引擎上的行为一致性格式本身与具体引擎无关。运行器扫描两处目录本 crate 下的test_files/目录当前共有 200 余个.slt文件与若干子目录独立仓库 datafusion-testing 中的data/sqlite/目录通过 git 子模块挂载在../../datafusion-testing/data/运行器常量DATAFUSION_TESTING_TEST_DIRECTORY即指向该路径见 bin/sqllogictests.rs。[[test]]段配置了harness false测试入口是一个独立的自定义二进制 bin/sqllogictests.rs这意味着它不依赖 Rust 内建的 test harness而是自行实现了多线程并行调度、进度条、过滤、计时与失败聚合逻辑。2. 测试环境准备按 README 的 Testing setup 一节准备流程为# 1. DataFusion 使用最新 stable 版本的 Rust rustup update stable # 2. 初始化并拉取子模块datafusion-testing 等 git submodule init git submodule update --init --remote --recursive子模块尤其重要不开启子模块的话INCLUDE_SQLITEtrue相关的 SQLite 套件就没有数据源。3..slt文件格式详解这是整套测试体系的核心。测试由一系列 query record 组成通常先以CREATE语句建表灌数据再执行后续查询验证数据。每个.slt文件都在一个独立的SessionContext中运行这样测试设置是显式的且文件间可以并行执行——因此测试应避免产生外部可见的副作用例如写入/tmp/这类全局位置。Query record 的通用格式如下test_name注释为 DataFusion 扩展# test_name query type_string sort_mode sql_query ---- expected_result各字段含义test_name唯一标识测试名的注释行DataFusion 专有type_string结果列的类型串每一列对应一个字符B—BooleanD—DatetimeI—IntegerP— timestamPR— 浮点数结果T—Text?— 其他任意类型sort_mode可选取值为nosort默认、rowsort、valuesortnosort结果严格按引擎返回顺序比较只应用于带ORDER BY或单行结果的查询否则行序在不同引擎间是未定义的rowsort先收集全部输出再在客户端按行排序对值的文本表示做sort_unstable因此9排在10之后valuesort与rowsort类似但不按行分组每个值独立排序。expected_result期望输出区其中的值会按统一规则转换浮点值四舍五入到 12 位小数Spark 兼容路径为 15 位NULL渲染为NULL空字符串渲染为(empty)布尔值渲染为true/false。建议没有显式ORDER BY的查询要么补上order by要么使用rowsort。3.1 类型转换规则的源码实现上述渲染规则并非纸面约定而是运行器注入的 normalizer 实现的。src/engines/conversion.rs 中可以看到关键实现NULL_STR NULL常量bool_to_str输出true/falsevarchar_to_str将空字符串转为(empty)并把 NUL 字节转义为\0见 conversion.rs#L35-L42FLOAT_ROUND_DIGITS: i64 12与SPARK_FLOAT_ROUND_DIGITS: i64 15两个常量分别对应 DataFusion 默认与 Spark 兼容的浮点舍入位数float_decimal_to_str使用BigDecimal::round完成舍入conversion.rs#L44-L88文件内的单元测试也断言了f64_to_str(0.12345678901234567) 0.123456789012这类 12 位舍入行为。运行器在驱动引擎前会依次注册严格列校验器、该 normalizer 以及值校验器见 bin/sqllogictests.rs#L694-L697runner.add_label(self.engine.label()); runner.with_column_validator(strict_column_validator); runner.with_normalizer(value_normalizer); runner.with_validator(self.validator);3.2 完整示例# group_by_distinct query TTI SELECT a, b, COUNT(DISTINCT c) FROM my_table GROUP BY a, b ORDER BY a, b ---- foo bar 10 foo baz 5 foo 4 34. 运行测试TLDR 快速命令README 给出的最小命令集均可直接复制执行# 运行全部测试 cargo test --test sqllogictests # 开启 debug 日志运行全部测试 RUST_LOGdebug cargo test --test sqllogictests # 只运行 information_schema.slt 中的测试子串匹配 cargo test --test sqllogictests -- information_schema # 自动用实际输出更新 ddl.slt cargo test --test sqllogictests -- ddl --complete # 运行 ddl.slt 并把 debug 日志打到 stdout RUST_LOGdebug cargo test --test sqllogictests -- ddl日志由env_logger::init()初始化因此RUST_LOGdebug即可看到各文件执行的调试信息bin/sqllogictests.rs#L124-L126。4.1 文件过滤子串匹配与file:line语法除了上面的子串匹配文件名运行器还支持定位到文件内某一行的测试。过滤器由 src/filters.rs 实现// filters.rs#L37-L63节选 pub struct Filter { file_substring: String, line_number: Optionu32, }Filter从形如file_name:line_number的字符串解析例如# 运行 information.slt 第 709 行声明的测试以及所有前置准备语句 cargo test --test sqllogictests -- information:709一个值得注意的设计细节在should_skip_record中CREATE TABLE、INSERT INTO、DROP、SELECT * INTO等语句永远不会被跳过因为它们是后续测试建表灌数所必需的只有SELECT与EXPLAIN语句在行号不匹配时才会被跳过见 filters.rs#L115-L158 的statement_is_skippable。这解释了为什么只跑一行测试时仍需执行全部准备语句。5. 编写新测试Cookbook新增一个测试的标准三步流程第 1 步写查询骨架。把 setup 与查询写入新的.slt文件本例为my_awesome_test.sltquery CREATE TABLE foo AS VALUES (1); query SELECT * from foo;第 2 步用--complete模式自动填充期望输出cargo test --test sqllogictests -- my_awesome_test --complete第 3 步人工核对内容。完成后文件应类似statement ok CREATE TABLE foo AS VALUES (1); query I SELECT * from foo; ---- 1确认无误后即可提交。--complete在源码中对应run_complete_file它调用 sqllogictest 的update_test_file把实际执行结果按空格分隔符写回文件bin/sqllogictests.rs#L841-L910。6. 运行模式详解Reference6.1 Validation Mode校验模式默认模式逐条执行.slt中的 statement 与 query把文件中的期望输出与实际输出比对。运行全部套件cargo test --test sqllogictests支持cargo test风格的文件名子串匹配来缩小范围# 因为子串 information 命中 information_schema.slt 而被执行 cargo test --test sqllogictests -- information也支持前文提到的file:line语法精确执行单条测试。6.2 Completion Mode补全模式补全模式下运行器读取原型脚本、对引擎执行其中的语句与查询然后把结果插回脚本生成完整文件——即第 5 节使用的--complete# 用实际运行结果更新 ddl.slt cargo test --test sqllogictests -- ddl --complete对 SQLite 套件datafusion-testing/data/sqlite/的批量重新生成请使用 regenerate_sqlite_files.sh。警告regenerate_sqlite_files.sh是实验性脚本需谨慎理解后再运行。它会在本地克隆远程仓库、把某个依赖的 location 替换为自定义 git 版本、用 GitHub gist 中的文件替换现有.rs文件并执行一系列本地命令README 原文提示。另外从源码可见一个约束声明了# configMatrix:指令的文件不允许使用--complete因为补全只会按单一组合的输出覆写整个文件bin/sqllogictests.rs#L855-L866。6.3 Postgres 兼容模式以pg_compat_为前缀的测试文件用于验证 DataFusion 与 Postgres 的行为一致性同一份脚本分别在 DataFusion 和 Postgres 上执行并比对。运行器中该前缀是常量PG_COMPAT_FILE_PREFIX pg_compat_Postgres runner 只会执行这类文件或INCLUDE_SQLITE打开时的 sqlite 目录。要对一个已在运行的 Postgres 实例执行兼容测试PG_COMPATtrue PG_URIpostgresql://postgres127.0.0.1/postgres cargo test --featurespostgres --test sqllogictests环境变量说明PG_COMPAT指示 sqllogictest 使用 Postgres runner 而非 DataFusion对应 clap 选项--postgres-runnerenv PG_COMPATPG_URIlibpq 风格连接串格式见tokio-postgres的Config文档。用 Docker 起一个合规的 Postgres 容器官方 postgres 镜像docker run \ -p5432:5432 \ -e POSTGRES_INITDB_ARGS--encodingUTF-8 --lc-collateC --lc-ctypeC \ -e POSTGRES_HOST_AUTH_METHODtrust \ postgres注意排序规则collation必须设为C否则ORDER BY的结果顺序会与 DataFusion 不一致导致 diff。如果本机已安装 Docker也可以不提供PG_URI——运行器会自动创建一个临时 Postgres 容器testcontainers实现见 bin/postgres_container.rsPG_COMPATtrue cargo test --featurespostgres --test sqllogictests6.4 TPC-H 测试test_files/tpch/目录下的测试基于 TPC-H 数据集SF 0.1需要先生成数据。在仓库根目录执行mkdir -p datafusion/sqllogictest/test_files/tpch/data docker run -it \ -v $(realpath datafusion/sqllogictest/test_files/tpch/data):/data \ ghcr.io/scalytics/tpch-docker:main -vf -s 0.1然后加INCLUDE_TPCHtrue等价于--include-tpch才会纳入 tpch 文件INCLUDE_TPCHtrue cargo test --test sqllogictests从 bin/sqllogictests.rs#L1005 可以看到过滤逻辑!f.relative_path_starts_with(TPCH_PREFIX) || options.include_tpch——即默认跳过所有tpch前缀路径。6.5 SQLite 测试datafusion-testing 仓库data/sqlite目录中的测试文件源自 SQLite 官方测试套件经清理与更新后可在 DataFusion 的 sqllogictest runner 中运行。运行前需调大 Rust 栈大小并设置INCLUDE_SQLITEtrueexport RUST_MIN_STACK30485760; INCLUDE_SQLITEtrue cargo test --test sqllogictests这些测试包含 500 万条以上查询全量执行耗时很长README 建议使用release-nonltoprofile 加速INCLUDE_SQLITEtrue cargo test --profile release-nonlto --test sqllogictestsSQLite 测试也可以交给 Postgres runner 执行以验证兼容性export RUST_MIN_STACK30485760; PG_COMPATtrue INCLUDE_SQLITEtrue cargo test --featurespostgres --test sqllogictests更新 SQLite 期望答案使用 regenerate_sqlite_files.sh注意必须在空的 postgres 实例上运行PG_URIpostgresql://postgreslocalhost:5432/postgres bash datafusion/sqllogictest/regenerate_sqlite_files.sh6.6 scratch 临时目录scratchdirDataFusion 的 sqllogictest runner 会自动创建名为test_files/scratch/filename的目录不存在则创建存在则清空其内容。例如test_files/copy.slt应使用test_files/scratch/copy作为临时目录。需要写临时文件的测试只能写在这个目录下以避免与并发运行的其他测试互相干扰。这一点不是口头约定运行前有强制静态检查scratch_file_checkbin/sqllogictests.rs#L1202-L1252用正则scratch/([^/])/扫描每个.slt文件只要出现的 scratch 目标名与文件名去扩展名不一致测试在开始执行前就整体失败。6.7 Substrait 往返模式Substrait round-trip该模式在 validation 模式下为每条生成的 DataFusion 逻辑计划追加一次 Substrait 转换往返SQL → DF logical → Substrait → DF logical → DF physical → 执行并非所有语句都会往返CREATE、INSERT、SET、EXPLAIN等会原样下发其余语句都走 Substrait 往返。警告此模式在substraitfeature 之后全量套件仍有失败项目 Issues 中有汇总。CI 因此只通过cargo xtask ci step test substrait对单个文件limit.slt执行该模式。在默认套件上运行cargo test --test sqllogictests --features substrait -- --substrait-round-trip用file:line过滤器聚焦某个失败测试cargo test --test sqllogictests --features substrait -- --substrait-round-trip binary.slt:237. Cookbook测试末尾空白trailing whitespacesqllogictest runner 会自动剥离行尾空白因此直接断言很难区分Andrew和带尾随空格的Andrew。例如以下测试无法分辨两者query T select substr(Andrew Lamb, 1, 7) ---- Andrew技巧是在目标列右侧再投影一列非空白字符。比如在选择|之后就能区分Andrew与Andrew注意两空格与一空格的区别# 注意 Andrew 和 | 之间是两个空格 query TT select substr(Andrew Lamb, 1, 7), | ---- Andrew | # 注意 Andrew 和 | 之间只有一个空格 query TT select substr(Andrew Lamb, 1, 6), | ---- Andrew |8. Cookbook忽略不稳定输出volatile output有些结果片段每次运行都会变化时间戳、计数器等。为保留其余快照内容可在期望块中用slt:ignore标记替换这些片段。校验时该标记充当通配符只要求周围文本匹配query TT EXPLAIN ANALYZE SELECT * FROM generate_series(100); ---- Plan with Metrics LazyMemoryExec: partitions1, batch_generators[generate_series: start0, end100, batch_size8192], metrics[output_rows101, elapsed_computeslt:ignore, output_bytesslt:ignore]这样EXPLAIN ANALYZE这类带耗时/字节计数的输出就可以安全地纳入快照测试而不必为每次运行的抖动反复修文件。9. Cookbook用configMatrix扫描配置configMatrix让同一个.slt文件在每一组配置值组合上各运行一次。每条指令都是一行注释# configMatrix: keyv1,v2[,...]行为要点README 与 src/config_matrix.rs 源码一致重复该指令即可嵌套多个 key取值做笛卡尔积值会 trim 空白并去重保留首次出现顺序同一 key 重复声明时合并值列表union 而非新增维度未知 key 或非法取值会快速失败错误信息包含文件名、key 与取值每个覆写都按SET key value的语义应用因此datafusion.runtime.*如datafusion.runtime.memory_limit以及依赖配置的 UDF 行为与文件内直接SET完全一致默认 runner 与--substrait-round-trip都支持--complete会拒绝声明了该指令的文件Postgres runner 则忽略这些指令即使前面的组合失败所有组合都会继续运行每个失败都会前缀标注产生它的组合多个失败组合会在N configMatrix combinations failed:头之下全部汇报。2 × 2 4 次运行的嵌套示例# configMatrix: datafusion.execution.parquet.coerce_int96ms,us # configMatrix: datafusion.execution.parquet.coerce_int96_tzUTC,America/New_York statement ok CREATE EXTERNAL TABLE int96_from_spark STORED AS PARQUET LOCATION ../../parquet-testing/data/int96_from_spark.parquet; query I select count(*) from int96_from_spark ---- 6失败输出形如[configMatrix: datafusion.execution.parquet.coerce_int96ms, datafusion.execution.parquet.coerce_int96_tzUTC] caused by External error: 1 errors in file .../parquet_int96_matrix.slt9.1 源码印证解析与失败归因config_matrix.rs#L136-L192 的parse_configurations展示了完整解析链路识别注释行中的configMatrix:标记大小写不敏感容忍##banner→ 按拆分 key 与逗号分隔的值列表 → trim、过滤空值、unique()去重 → 用multi_cartesian_product()展开成TestConfiguration列表。没有指令的文件会得到一个空配置因此调用方永远保持单一循环形态。失败归因由run_each_configuration完成每个组合跑完后若失败则用TestConfiguration::attribute_failure给错误加上[configMatrix: k1, ...]前缀combine_configuration_failures在恰好一个失败时原样返回无矩阵文件的错误形态不变多个失败时聚合为N configMatrix combinations failed:报告config_matrix.rs#L101-L133。这些行为均有单元测试覆盖如同名 key 合并、重复值去重、失败归因等 20 余个用例。仓库中 test_files/config_matrix.slt 即为该特性的实际测试文件。10. 按文件耗时统计Per-file timing summary运行器可以输出确定性的按文件耗时统计帮助定位慢测试文件。默认关闭用--timing-summary或环境变量SLT_TIMING_SUMMARYtrue开启。开启后为保持输出稳定周期性的Progress:行默认被抑制bin/sqllogictests.rs#L186-L190 中print_periodic_progress is_ci !options.timing_summary。# 输出确定性的按文件耗时最慢的排最前 cargo test --test sqllogictests -- --timing-summary # 配合标准 shell 工具只保留前 10 行 cargo test --test sqllogictests -- --timing-summary | head -n 10 # 通过环境变量开启 SLT_TIMING_SUMMARY1 cargo test --test sqllogictests # 可选的调试日志打印耗时超过 30s 的文件默认关闭 SLT_TIMING_DEBUG_SLOW_FILES1 cargo test --test sqllogictests实现上print_timing_summary把每个文件的FileTiming按耗时降序并列时按路径排序输出为序号 耗时s 相对路径bin/sqllogictests.rs#L403-L422慢文件调试则由SLT_TIMING_DEBUG_SLOW_FILES环境变量在每文件任务收尾时判断elapsed 30s并向 stderr 打印。11. 命令行与环境变量速查综合 bin/sqllogictests.rs 中的Options结构clap 定义刻意模仿 Rust 内建 test runner 的参数风格以便 IDE 兼容关键开关如下参数 / 环境变量默认作用--completeoff补全模式用实际运行结果覆写期望输出--postgres-runner/PG_COMPAToff使用 Postgres runner仅执行pg_compat_*文件与 sqlite 套件--substrait-round-tripoffSubstrait 逻辑计划往返模式需substraitfeature与--complete、--postgres-runner互斥--include-sqlite/INCLUDE_SQLITEoff纳入 datafusion-testing 的 sqlite 套件建议配RUST_MIN_STACK--include-tpch/INCLUDE_TPCHoff纳入test_files/tpch测试需先生成 SF0.1 数据filter[:line]-文件名子串过滤可加:行号精确定位单条测试--test-threadsCPU 并行度并行文件数buffer_unordered的窗口--timing-summary/SLT_TIMING_SUMMARYoff输出确定性按文件耗时统计SLT_TIMING_DEBUG_SLOW_FILESoff打印超过 30s 的慢文件RUST_LOG-env_logger日志级别如debug--color/NO_COLOR/CARGO_TERM_COLORauto彩色输出控制NO_COLOR优先级最高--list-直接退出兼容 nextest 的列举行为此外还有若干为兼容 Rust 内建 test runner 而被显式忽略的参数--format、-Z、--show-output、--ignored、--nocapture传入时仅打印 WARNING。[features]中可用的 featureCargo.tomlpostgres、substrait、parquet_encryption启用后才会跑encrypted_parquet.slt、avro、backtrace。12. 并行执行与失败聚合机制从源码结构看运行器的调度模型值得了解因为它决定了多文件测试的隔离性与可诊断性文件级并行所有测试文件以buffer_unordered(test_threads)的方式并行跑每个文件一个独立SessionContextCI 无 TTY 时按 10% 间隔或每 50 个文件打印进度bin/sqllogictests.rs#L193-L325文件内错误截断单文件最多展示 10 条错误ERRS_PER_FILE_LIMIT 10超出部分折叠为... other N errors in file not shown ...避免一个坏文件刷屏失败 SQL 追踪CurrentlyExecutingSqlTracker记录当前正在执行的 SQL最终错误信息会自动带上failure in for sql ...上下文配置漂移检测run_datafusion会收集测试文件运行期间被改动但未被还原的 DataFusion 配置并在文件结束后报出防止测试污染全局配置状态bin/sqllogictests.rs#L631-L660。13. 关键文件索引路径说明datafusion/sqllogictest/README.md本文依据的官方文档datafusion/sqllogictest/bin/sqllogictests.rs测试入口与运行器主逻辑过滤、并行、计时、scratch 检查datafusion/sqllogictest/src/filters.rsfile:line过滤器与可跳过语句规则datafusion/sqllogictest/src/config_matrix.rs# configMatrix:指令解析、笛卡尔积展开与失败归因datafusion/sqllogictest/src/engines/conversion.rs值渲染规则NULL、空串、浮点舍入、Decimaldatafusion/sqllogictest/bin/postgres_container.rsPostgres 自动容器管理postgresfeaturedatafusion/sqllogictest/regenerate_sqlite_files.shSQLite 期望答案重新生成脚本实验性datafusion/sqllogictest/test_files/200 余个.slt测试文件含config_matrix.slt、tpch/等datafusion/sqllogictest/Cargo.toml依赖、features 与[[test]]配置14. 小结DataFusion 的 sqllogictest 体系把SQL 快照测试做成了一个可独立编译的测试驱动.slt文件定义查询与期望输出自定义 runner 负责文件级并行、file:line过滤、类型与值规范化、scratch 目录隔离与配置漂移检测在此之上叠加了 Postgres 兼容对比、TPC-H / SQLite 大数据量套件、Substrait 往返验证与configMatrix配置扫描等扩展模式。对贡献者而言最常用路径是在test_files/下新增.slt→cargo test --test sqllogictests -- name --complete生成期望 → 人工核对后提交对排查者而言--timing-summary、SLT_TIMING_DEBUG_SLOW_FILES与RUST_LOGdebug提供了从宏观耗时到微观 SQL 的完整观测手段。赞分享大数据数据分析后端【免费下载链接】datafusionApache DataFusion SQL Query Engine项目地址https://gitcode.com/gh_mirrors/datafu/datafusion点击查看免费下载相关推荐Dart SDK 测试体系实战指南tests/ 测试格式与 test_runner 运行机制全解析Dart SDK 测试体系实战指南tests/ 测试格式与 test_runner 运行机制全解析 Dart SDK 位于开发者技术栈的最底层语言与核心库的编程语言编译器语言运行时标准库开发工具Streamlit E2E 测试实战基于 Playwright 的运行、调试、外部托管与快照更新完整指南Streamlit E2E 测试实战基于 Playwright 的运行、调试、外部托管与快照更新完整指南 Streamlit 仓库使用 Playwright数据可视化后端前端pyrefly stubgen 快照测试体系完全指南测试夹具结构与快照更新工作流pyrefly stubgen 快照测试体系完全指南测试夹具结构与快照更新工作流 导读 本文聚焦 pyrefly 中 stubgen从 Python 源码开发工具静态分析IDE代码质量上一篇5分钟打造你的Obsidian个性化首页从零开始构建高效知识管理系统下一篇electron-builder v26 到 v27 迁移实战指南migrate-schema 自动迁移、Node.js 22.12 门槛与全部破坏性变更创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考