Apache Gluten单元测试指南:如何为原生引擎编写可靠测试用例
【免费下载链接】glutenGluten is a middle layer responsible for offloading JVM-based SQL engines' execution to native engines.项目地址: https://gitcode.com/GitHub_Trending/glu/gluten
Apache Gluten作为JVM-based SQL引擎与原生执行引擎之间的中间层,其单元测试对于确保查询执行的正确性和性能至关重要。本文将详细介绍如何为Gluten原生引擎编写可靠的测试用例,涵盖测试环境搭建、测试框架使用、关键测试类型及最佳实践,帮助开发者快速上手单元测试开发。
单元测试在Gluten中的重要性
Gluten通过Substrait协议将Spark执行计划转换为原生引擎(如Velox、ClickHouse)可执行的计划,单元测试需验证这一转换过程的准确性、数据处理的正确性以及性能表现。可靠的单元测试能够:
- 确保原生引擎正确执行SQL算子逻辑
- 提前发现跨引擎数据类型转换问题
- 验证内存管理和资源释放的有效性
- 保障不同Spark版本和原生后端的兼容性
Gluten的测试代码主要分布在以下目录:
- gluten-ut/:包含针对不同Spark版本的单元测试
- backends-clickhouse/src/test/:ClickHouse后端测试
- backends-velox/src/test/:Velox后端测试
- cpp/velox/tests/:C++层原生执行测试
测试环境搭建
1. 基础环境准备
首先需要构建Gluten项目及测试相关组件:
# 克隆仓库 git clone https://gitcode.com/GitHub_Trending/glu/gluten cd gluten # 构建Velox后端并开启测试选项 ./dev/buildbundle-veloxbe.sh --build_tests=ON --build_benchmarks=ON # 生成测试所需的Parquet数据和Substrait计划文件 mvn test -Pspark-3.5 -Pbackends-velox -pl backends-velox \ -am -DtagsToInclude="org.apache.gluten.tags.GenerateExample" \ -Dtest=none -DfailIfNoTests=false -Dexec.skip生成的测试文件位于backends-velox/generated-native-benchmark/目录,包含:
plan_{stageId}_{partitionId}_{vId}.json:Substrait执行计划data_{stageId}_{partitionId}_{vId}_{iteratorIdx}.parquet:输入测试数据conf_{stageId}_{partitionId}_{vId}.ini:测试配置文件
2. 测试配置与依赖
Gluten提供了统一的测试基类GlutenSQLTestsBaseTrait,位于gluten-ut/common/src/test/scala/org/apache/spark/sql/GlutenSQLTestsBaseTrait.scala,该类预设了测试所需的Spark配置:
// 关键测试配置 .set("spark.plugins", "org.apache.gluten.GlutenPlugin") .set("spark.shuffle.manager", "org.apache.spark.shuffle.sort.ColumnarShuffleManager") .set("spark.sql.adaptive.enabled", "true") .set("spark.memory.offHeap.enabled", "true") .set("spark.memory.offHeap.size", "1024MB")通过继承该Trait,可以快速搭建测试环境并获取预配置的SparkSession。
核心测试类型与实现方法
1. SQL查询结果验证测试
这类测试验证Gluten执行SQL查询的结果是否与Spark原生执行一致,是最基础也最重要的测试类型。典型实现如下:
class GlutenDataFrameAggregateSuite extends GlutenSQLTestsTrait { test("test aggregate function sum") { val df = spark.createDataFrame(Seq((1, "a"), (2, "b"), (3, "a"))) .toDF("id", "name") // Gluten执行结果 val glutenResult = df.groupBy("name").sum("id").orderBy("name").collect() // 禁用Gluten的基准结果 withSQLConf(GlutenConfig.GLUTEN_ENABLED.key -> "false") { val sparkResult = df.groupBy("name").sum("id").orderBy("name").collect() assert(glutenResult sameElements sparkResult) } } }Gluten提供了GlutenSQLTestsTrait测试特质,自动处理测试前后的环境切换,确保测试的隔离性。
2. 执行计划验证测试
验证Spark计划是否被正确转换为原生执行计划,确保算子下推和优化的正确性:
test("validate columnar execution plan") { val df = spark.sql("SELECT id, count(*) FROM test_table GROUP BY id") val executedPlans = getExecutedPlan(df) // 验证是否生成了ColumnarShuffleExchange assert(executedPlans.exists(_.nodeName.contains("ColumnarShuffleExchange"))) // 验证Aggregate算子是否下推到原生引擎 assert(executedPlans.exists(_.nodeName.contains("ColumnarAggregate"))) }getExecutedPlan方法(定义于GlutenSQLTestsBaseTrait)可提取DataFrame的执行计划树,便于验证计划转换结果。
3. 原生引擎C++层测试
对于C++实现的核心逻辑,可使用Google Test框架直接测试,例如Velox后端的内存管理测试:
TEST(MemoryManagerTest, TestMemoryAllocation) { auto memoryManager = std::make_shared<VeloxMemoryManager>(); auto pool = memoryManager->getPool(); // 测试内存分配 auto buffer = pool->allocate<uint8_t>(1024); ASSERT_NE(buffer.get(), nullptr); ASSERT_EQ(buffer->size(), 1024); // 测试内存限制 memoryManager->setMemoryLimit(2048); ASSERT_THROW(pool->allocate<uint8_t>(3072), std::bad_alloc); }C++测试可通过以下命令单独运行:
cd cpp/build/velox/tests ./velox_tests --gtest_filter=MemoryManagerTest.*4. 性能基准测试
使用Gluten提供的基准测试框架验证原生执行性能,位于cpp/velox/benchmarks/目录。以TPCH Q5查询为例:
# 运行TPCH Q5基准测试 ./generic_benchmark \ --plan ../data/generic_q5/q5_first_stage_0.json \ --split ../data/generic_q5/q5_first_stage_0_split.json \ --with-shuffle --threads 8 --iterations 10基准测试会输出详细的性能指标,包括执行时间、内存使用和算子耗时,帮助评估优化效果。
图:TPCH Q5查询的第一阶段执行计划,展示了Gluten如何将SQL转换为原生执行算子
测试结果分析与调试
1. 测试报告解读
Gluten使用Surefire插件生成详细的测试报告,包含测试用例执行情况、失败原因和性能数据。典型的测试报告界面如下:
图:Gluten单元测试报告示例,显示测试总数、跳过数和失败数,以及详细的失败原因
报告中会高亮显示失败的测试用例,并提供完整的错误堆栈信息,便于定位问题。
2. 调试技巧
(1)Java/Scala层调试
使用IntelliJ IDEA远程调试Spark测试:
export SPARK_SUBMIT_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8008" mvn test -Pspark-3.5 -Pbackends-velox -DwildcardSuites=GlutenDataFrameAggregateSuite在IDEA中配置远程调试,连接到8008端口即可断点调试。
(2)C++层调试
使用GDB调试原生执行逻辑:
gdb cpp/build/velox/benchmarks/generic_benchmark (gdb) b VeloxBackend.cc:50 # 在Velox后端初始化处设置断点 (gdb) r --plan /path/to/plan.json --data /path/to/data.parquet通过bt命令查看调用栈,p命令打印变量值,定位C++代码中的问题。
(3)执行计划调试
开启调试模式输出Substrait计划:
mvn test -Pspark-3.5 -Pbackends-velox -Dspark.gluten.sql.debug=true生成的JSON格式计划可通过Substrait可视化工具进行分析,验证计划转换的正确性。
测试最佳实践
1. 测试用例设计原则
- 原子性:每个测试用例只验证一个功能点,便于定位问题
- 可重复性:测试数据固定,确保每次运行结果一致
- 覆盖全面:覆盖不同数据类型、边界条件和异常场景
- 性能平衡:单元测试应快速执行,避免过度复杂的数据集
2. 常用测试工具与框架
- ScalaTest:用于Scala层测试,提供丰富的断言和测试风格
- Google Test:C++层单元测试框架,支持参数化测试和测试夹具
- Benchmark:Google Benchmark框架,用于性能基准测试
- Surefire:Maven插件,生成统一的测试报告
3. CI集成
Gluten的CI流程会自动运行所有单元测试,确保代码质量。开发者提交PR前应本地运行:
# 运行所有单元测试 mvn test -Pspark-3.5 -Pbackends-velox -Pspark-ut # 运行特定模块测试 mvn test -Pspark-3.5 -pl gluten-ut/spark35总结
编写可靠的单元测试是保障Gluten原生引擎质量的关键环节。本文介绍了Gluten单元测试的环境搭建、核心测试类型、实现方法和调试技巧,涵盖从SQL层到C++原生层的全栈测试。通过遵循本文所述的最佳实践,开发者可以有效验证Gluten的功能正确性和性能表现,为SQL引擎的原生加速提供坚实保障。
Gluten的测试框架持续演进,更多测试工具和用例可参考官方测试文档和测试示例代码。
【免费下载链接】glutenGluten is a middle layer responsible for offloading JVM-based SQL engines' execution to native engines.项目地址: https://gitcode.com/GitHub_Trending/glu/gluten
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考