关于开源:如何为-Databend-添加新的测试

8次阅读

共计 7203 个字符,预计需要花费 19 分钟才能阅读完成。

测试是进步软件健壮性、减速迭代过程的不二法宝。本文将会介绍如何为 Databend 增加不同品种的测试。

单元测试

Databend 的单元测试组织模式有别于个别的 Rust 我的项目,是间接一股脑放在 tests/it 目录下的。同时,在各个 crate 的 Cargo.toml 中,也针对性地禁用了 doctest 和 bin/lib test。
长处:

  • 缩小须要构建的测试指标,进步测试编译 / 链接速度。
  • 当须要增加新单元测试时(不批改 src),只须要编译对应的 it(test),节省时间。

毛病:

  • tests/it 会把须要测试的 crate 当作一个内部对象,所有待测试的内容都须要被设定为 pub。不利于软件设计上的分层,整个我的项目构造会迅速的被毁坏,须要引入编码标准并更加依赖开发者的被动保护。

编写

能够简略地将单元测试分为两类,一类是不须要内部文件染指的纯 Rust 测试,一类是 Golden Files 测试。
Rust 测试
与平时编写 Rust 单元测试雷同,只是待测试的内容须要设为 pub,且援用待测试 crate 须要应用该 crate 的名字。
Databend 提供一些用于模仿全局状态的函数,如 create_query_context 等,可能会有助于编写测试。

#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
async fn test_credits_table() -> Result<()> {let ctx = crate::tests::create_query_context().await?;

    let table = CreditsTable::create(1);
    let source_plan = table.read_plan(ctx.clone(), None).await?;

    let stream = table.read(ctx, &source_plan).await?;
    let result = stream.try_collect::<Vec<_>>().await?;
    let block = &result[0];
    assert_eq!(block.num_columns(), 3);
    Ok(())
}

下面示例来自 credits_table 的测试,先构建 read_plan 读取新建的 CreditsTable 表,再对列数进行断言。
Golden Files 测试

Golden File Testing are like unit tests, except the expected output is stored in a separate file. — Max Grigorev at ZuriHac

Golden Files 测试是一种罕用的测试伎俩,相当于是一类快照测试,如果执行状况和预期后果存在差别则认为测试失败。
Databend 应用 goldenfile 这个 crate 来编写 Golden Files 测试。目前 Databend 有打算用此代替 assert_blocks 系列断言

#[test]
fn test_expr_error() {let mut mint = Mint::new("tests/it/testdata");
    let mut file = mint.new_goldenfile("expr-error.txt").unwrap();

    let cases = &[r#"5 * (a and) 1"#,
        r#"a + +"#,
        r#"CAST(col1 AS foo)"#,
        r#"1 a"#,
        r#"CAST(col1)"#,
        r#"G.E.B IS NOT NULL AND
            col1 NOT BETWEEN col2 AND
                AND 1 + col3 DIV sum(col4)"#,
    ];

    for case in cases {run_parser!(file, expr, case);
    }
}

编写 Golden Files 测试时须要指定挂载的目录和对应预期后果的文件。

在执行测试的主体局部(如下面示例中的 run_parser! 宏),除了封装运行测试的必要逻辑外,还须要定义输入时的格局。

测试文件必须按指定格局编写。或者,应用 REGENERATE_GOLDENFILES=1 生成。

上面 Golden File 的例子节选自 common/ast 模块测试的 testdata/expr-error.txt,Output 对应解析 5 * (a and) 1 的预期后果。

---------- Input ----------
5 * (a and) 1
---------- Output ---------
error: 
  --> SQL:1:12
  |
1 | 5 * (a and) 1
  | -          ^ expected more tokens for expression
  | |           
  | while parsing expression

运行

单元测试的运行能够运行 make unit-test 或者是 cargo test –workspace。\
二者的区别在于 make unit-test 封装了 ulimit 命令管制最大文件数和栈的大小以确保测试可能顺利运行,如果应用 MacOS 则更倡议应用 make unit-test。\
通过过滤机制,能够轻松指定运行名字中具备特定内容的测试,例如 cargo test test_expr_error。

排查

Rust 测试
同其余我的项目中的 Rust 测试一样,能够依据敌对的谬误提醒轻松定位呈现故障的测试。如果须要具体的 Backtrace,能够在运行测试命令时增加环境变量 RUST_BACKTRACE=1。

failures:

---- buffer::buffer_read_number_ext::test_read_number_ext stdout ----
Error: Code: 1046, displayText = Cannot parse value:[] to number type, cause: lexical parse error: 'the string to parse was empty' at index 0.

<Backtrace disabled by default. Please use RUST_BACKTRACE=1 to enable> 
thread 'buffer::buffer_read_number_ext::test_read_number_ext' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/cd282d7f75da9080fda0f1740a729516e7fbec68/library/test/src/lib.rs:185:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Golden Files 测试
Golden Files 测试的执行命令与 Rust 测试雷同,但在谬误提醒方面有所差别。得益于 goldenfiles 引入了 similar-assert,能够轻松辨认 diff:

Differences (-left|+right):
 ---------- Output ---------
 'I'm who I'm.'
 ---------- AST ------------
 Literal {
     span: [-        QuotedString(0..18),
+        QuotedString(0..16),
     ],
     lit: String("I'm who I'm.",),
 }
.cargo/git/checkouts/rust-goldenfile-6352648ef139d984/16c5783/src/differs.rs:15:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
下面示例中,+ 对应测试理论后果,- 对应测试预期后果,其余为相干的上下文。

下面示例中,+ 对应测试理论后果,- 对应测试预期后果,其余为相干的上下文。
goldenfiles 的报错可能会波及多个测试文件,受限于长文本反对和空格显示,排查仍可能存在不便。
这里提供一个绝对敌对的排查思路:

  1. 确保之前的更改都曾经提交,而后运行 REGENERATE_GOLDENFILES=1 cargo test -p <package> –test it 从新生成对应的测试。
  2. 执行 git diff 来显示前后 goldenfiles 文件的差别。
  3. 认真分别问题呈现起因,确定是否存在预期外的问题。

功能测试

功能测试临时呈现两种计划并行的状况,除了旧有的 stateless/stateful 测试计划外,还引入了全新的 SQL 逻辑测试,后续 stateless 测试会过渡到 SQL 逻辑测试上。从实质上讲,这两类功能测试流程雷同:

  • 启动 databend 实例。
  • 应用对应的客户端 / 驱动执行查问。
  • 比照查问状况和预期行为之间的差别,判断测试是否通过。

然而,在设计上,SQL 逻辑测试能够提供更全面的能力:

  • 拓展比拟后果文件的形式到其余协定(涵盖 http handler)。
  • 提醒每个语句的后果。
  • 提供错误处理的能力。
  • 反对排序、重试等测试逻辑。

编写

stateless/stateful 测试

stateless/stateful 测试放在 tests/suites 目录下:

  • 输出是一系列 sql 语句,对应目录中的 *.sql 文件。
SELECT '==Array(Int32)==';

CREATE TABLE IF NOT EXISTS t2(id Int null, arr Array(Int32) null) Engine = Fuse;

INSERT INTO t2 VALUES(1, [1,2,3]);
INSERT INTO t2 VALUES(2, [1,2,4]);
INSERT INTO t2 VALUES(3, [3,4,5]);
SELECT max(arr), min(arr) FROM t2;
SELECT arg_max(id, arr), arg_min(id, arr) FROM (SELECT id, arr FROM t2);
  • 输入对应查问后果(含报错),如果没有输入则须要置空,对应目录中的 *.result 文件。
==Array(Int32)==
[3, 4, 5] [1, 2, 3]
3 1

测试能够笼罩 SQL 执行过程中遇到预期谬误的状况,有两种形式:

  • 沿用下面的办法,在 result 文件中标注具体报错信息。
  • 也能够采纳 ErrorCode 正文的形式,此时无需在 result 文件中增加对应内容。
SELECT INET_ATON('hello');-- {ErrorCode 1060}

SQL 逻辑测试
SQL 逻辑测试放在 tests/logictest 目录下。语句标准在 sqlite sqllogictest 的根底上进行拓展,能够分成以下几类:

  • statement ok:SQL 语句正确,且胜利执行。
  • statement error <error regex>:SQL 语句输入冀望的谬误。
  • statement query <desired_query_schema_type> <options> <labels>:SQL 语句胜利执行并输入预期后果。
statement query B label(mysql,http)
select count(1) > 1 from information_schema.columns;

----  mysql
1

----  http
true

下面的例子展现了如何对 mysql 和 http 别离设计对应的输入后果。其中 B 示意后果为布尔类型,label 用来标记协定。SQL 逻辑测试同样反对测试集生成 python3 gen_suites.py。

运行

因为 stateless/stateful 测试和 sqllogictest 测试均由 Python 编写,在运行前请确保你曾经装置全副的依赖。

这几类测试都有对应的 make 命令,并反对集群模式测试:

  • stateless 测试:make stateless-test & make stateless-cluster-test。
  • stateful 测试:make stateful-test & make stateful-cluster-test。(个别在 CI 中运行,本地须要正确配置 MINIO 环境)。
  • sqllogictest 测试:make sqllogic-test & make sqllogic-cluster-test。

排查

stateless/stateful 测试

目前 stateless/stateful 测试可能提供文件级的报错和 diff,但无奈确定报错是由哪一条语句产生。

02_0057_function_nullif:                                                [FAIL] - result differs with:
--- /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.result
+++ /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.stdout
@@ -3,7 +3,7 @@
 1
 1
 NULL
-a
+b
 b
 a
 NULL

Having 1 errors! 207 tests passed.                     0 tests skipped.
The failure tests:
    /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.sql

sqllogictest 测试
sqllogictest 测试能提供精准到语句的报错,并提供更多无效的上下文帮忙排查问题。

AssertionError: Expected:
INFORMATION_SCHEMA
default
 Actual:
  INFORMATION_SCHEMA
          db_12_0003
             default
 Statement:
Parsed Statement
    at_line: 77,
    s_type: Statement: query, type: T, query_type: T, retry: False,
    suite_name: gen/02_function/02_0005_function_compare,
    text:
        select * from system.databases where name not like '%sys%' order by name;
    results: [(<re.Match object; span=(0, 4), match='----'>, 83, 'INFORMATION_SCHEMA\ndefault')],
    runs_on: {'mysql', 'clickhouse', 'http'},
 Start Line: 83, Result Label: 
make: *** [Makefile:82: sqllogic-test] Error 1

提醒

  • stateless/stateful 超时类谬误(Timeout!)的默认工夫限度为 10 分钟。为不便排查,能够将 databend-test 文件中的 timeout 改短。
  • 移除 databend-query-standalone-embedded-meta.sh 等脚本中的 nohup 有助于在测试时同时输入日志到终端,可能同样有助于排查。

对于 Databend

Databend 是一款开源、弹性、低成本,基于对象存储也能够做实时剖析的旧式数仓。期待您的关注,一起摸索云原生数仓解决方案,打造新一代开源 Data Cloud。

  • Databend 文档:https://databend.rs/
  • Twitter:https://twitter.com/Datafuse_…
  • Slack:https://datafusecloud.slack.com/
  • Wechat:Databend
  • GitHub:https://github.com/datafusela…


文章首发于公众号:Databend

正文完
 0