GithubHelp home page GithubHelp logo

sunxfancy / zeroerr Goto Github PK

View Code? Open in Web Editor NEW
19.0 3.0 1.0 4.43 MB

A powerful C++ unit testing/assert/log utils

Home Page: https://sunxfancy.github.io/zeroerr/zh/html/

License: MIT License

CMake 2.97% C++ 92.10% C 3.84% Cuda 0.42% Shell 0.38% Makefile 0.28%
assert benchmarking cpp logging unit-testing assertion c-plus-plus-11 logger unit-test header-only

zeroerr's Introduction

ZeroErr 零误框架

Standard download Eng-Readme

Hope you get 0 errors and 0 warnings everyday!

ZeroErr 零误框架是一款轻量级C++单元测试框架,同时也集成了断言库,日志库,打印调试等诸多功能,实现了以上功能的整合与协同工作。既可以选择整体使用,也可以单独使用其中的部分功能。

项目文档 | English Documentation

注:目前项目处于实验阶段,API可能会有较大变动,不建议在生产环境中使用。

为何要开发一款新的测试框架

目前业界主流的测试框架,Catch2, doctest, Boost.Test, cpputest 功能完善成熟,基本满足大多数应用场景。glog,spdlog等日志库使用也很简便。但这其中还存在一些问题:

1. 泛型打印支持

以上主流框架对任意类型数据的打印支持不够,大多数测试框架,断言库,日志库,大多缺乏泛型打印支持,对于容器和用户自定义类型缺乏直接打印的能力,ZeroErr解决了这一问题。

TEST_CASE("Try logging") {
    std::vector<int> data = {1, 2, 3};
    LOG_IF(1 == 1, "data = {data}", data);
}

类似于其他C++单元测试框架,ZeroErr可以将这段宏注册的单元测试代码编译成自动运行的函数,执行后结果如下,这里我们无需定义任何规则,就可以使用LOG宏打印vector模板:

case1

对于带有 std::ostream& operator<<(std::ostream&, Type) 流输出重载的自定义类型,可以不加修改直接打印。并且还支持容器类型的嵌套。

struct Node {
    std::string name;
    int id;
};

std::ostream& operator<<(std::ostream& out, Node n) {
    out << n.id << '.' << n.name;
    return out;
}

TEST_CASE("Try logging with custom type") {
    std::map<std::string, std::vector<Node>> data = {
        {"user1", {{"a",1}, {"b",2}}}, {"user2", {{"c",3}, {"d",4}}}
    };
    LOG("data = {data}", data);
}

case2

当然,很多情况下,第三方库并没有重载我们预期的<<操作符。对于较复杂的情况,我们可以编写泛型打印函数来处理,这大大增强了系统对不同接口的处置能力。例如,我们对这个LLVM的 llvm::Function* 类型,可以使用如下方式用dbg函数打印,这里dbg类似于rust的dbg宏,用来快速打印检查任意类型,并且可以嵌套使用:

namespace zeroerr { // must defined in namespace zeroerr

template <typename T>
typename std::enable_if<
    std::is_base_of<llvm::Value, T>::value || std::is_base_of<llvm::Type, T>::value, void>::type
PrinterExt(Printer& P, T* s, unsigned level, const char* lb, rank<2>) {
    if (s == nullptr) {
        P.os << P.tab(level) << "nullptr" << lb;
    } else {
        llvm::raw_os_ostream os(P.os);
        s->print(os);
    }
}
}

TEST_CASE("customize printing of LLVM pointers") {
    llvm::LLVMContext        context;
    std::vector<llvm::Type*> args   = {llvm::Type::getInt32Ty(context)};
    llvm::Module*            module = new llvm::Module("test_module", context);

    auto* f =
        llvm::Function::Create(llvm::FunctionType::get(llvm::Type::getVoidTy(context), args, false),
                               llvm::GlobalValue::ExternalLinkage, "test", module);
    dbg(dbg(f)->getType());
}

这个泛型函数会匹配所有基类为ValueType的llvm类,然后打印时创建一个llvm::raw_os_ostream输出流,并对其进行调用print方法打印。

case3-llvm

2. 断言、日志、单元测试的联合使用

对于使用多个不同的库实现上述功能,断言、日志、单元测试的各种功能无法协同使用。而在ZeroErr中,断言出错时,可以被日志系统捕获,可以输出到文件中保存,断言在单元测试中,可以被报告系统记录,并在最终输出中统计共有哪些断言失败。上述功能可以联合使用,也可以单独使用某一项,用法非常灵活。

int fib(int n) {
    REQUIRE(n >= 0, "n must be non-negative");
    REQUIRE(n < 20, "n must be less than 20");
    if (n <= 2) {
        return 1;
    }
    return fib(n - 1) + fib(n - 2);
}

TEST_CASE("fib function test") {
    CHECK(fib(0) == 0);
    CHECK(fib(1) == 1);
    CHECK(fib(2) == 1);
    CHECK(fib(3) == 2);
    CHECK(fib(4) == 3);
    CHECK(fib(5) == 5);
    CHECK(fib(20) == 6765);
}

joint1

更进一步,单元测试甚至可以通过比较log结果是否与之前正确的结果相同,从而避免很多复杂的单元测试编写,粗略检查代码的正确性。

TEST_CASE("match ostream") {
    // match output can be done in the following workflow
    // 1. user mark the test case which are comparing output use 'have_same_output'
    // 2. If the output is not exist, the result has been used as a correct verifier.
    // 3. If the output is exist, compare with it and report error if output is not match.
    std::cerr << "a = 100" << std::endl;

    ZEROERR_HAVE_SAME_OUTPUT;
}

通过设置 ZEROERR_HAVE_SAME_OUTPUT 宏,系统会自动检查该测试点的output stream输出,第一次执行时的结果会自动保存起来,而之后每次执行,都会将输出与第一次输出进行对比,相同则正确,否则该点错误。用户可以第一次手动观察输出是否符合预期,若是修改了实现后,想清除保存的结果,只需要将测试目录下的 output.txt 缓存文件删除即可。(目前仍是实验功能)

最后,对于日志系统,单元测试不但能够访问日志数据,以确保函数按照预期逻辑执行出来了结果。 还可以在逻辑出错时,自动捕获函数中的断言和相关打印信息,以便于后续的调试。

118 static void function() {
119    LOG("function log {i}", 1);  
120    LOG("function log {sum}, {i}", 10, 1);
121 }
...

TEST_CASE("access log in Test case") {
    zeroerr::suspendLog();
    function();
    CHECK(LOG_GET(function, 119, i, int) == 1);
    CHECK(LOG_GET(function, 120, sum, int) == 10);
    CHECK(LOG_GET(function, 120, i, int) == 1);
    zeroerr::resumeLog();
}

为了访问log,我们首先要暂停log系统,避免数据被输出到文件中,然后调用函数,通过LOG_GET宏访问log中的数据,最后再恢复log系统的运行。(目前,暂时仅能获取到每个Log点第一次调用的数据,仍是实验功能)。

3. Fuzzing的支持

大多数单元测试框架不支持fuzzing。然而,Fuzzing功能强大,可以自动检测软件中的错误,并且可以大大减少编写测试用例的工作量。

不同于其他fuzzing框架,zeroerr可以支持在代码中使用日志和断言,因此fuzzing的结果不仅包含了输入数据,还包含了日志和断言的信息。

使用方法:

FUZZ_TEST_CASE("fuzz_test") {
    LOG("Run fuzz_test");
    FUZZ_FUNC([=](int k, std::string num) {
        int t = atoi(num.c_str());
        LOG("k: {k}, num:{num}, t: {t}", k, num, t);
        REQUIRE(k == t);
    })
        .WithDomains(InRange<int>(0, 10), Arbitrary<std::string>())
        .WithSeeds({{5, "Foo"}, {10, "Bar"}})
        .Run(10);
}

受到 fuzztest的启发,我们使用Domain这个概念,用于指定目标函数的输入数据范围(或模式)。在这里,我们使用 InRange 来指定 k 的范围是0到10,使用 Arbitrary 来指定 num 的数据可以是任意随机字符串。然后,我们使用 WithSeeds 来指定fuzzing的初始种子。最后,我们使用 Run 来指定fuzzing的次数。

FUZZ_TEST_CASE 会生成一个测试用例,可以连接到 libFuzzer 来运行fuzzing。最后,我们使用 Run(10) 来调用 libFuzzer 来运行目标10次。

为了构建带有fuzzing的测试用例,您需要使用 clang++ 编译代码,并使用 -fsanitize=fuzzer-no-link 并链接 -lclang_rt.fuzzer_no_main-x86_64,这是一个没有main函数的libFuzzer版本。您可以通过调用 clang++ -print-runtime-dir 来找到这个运行时库。以下是带有fuzzing支持的测试用例的完整构建命令:

clang++ -std=c++11 -fsanitize=fuzzer-no-link -L=`clang++ -print-runtime-dir` -lclang_rt.fuzzer_no_main-x86_64  -o test_fuzz test_fuzz.cpp 

项目构建

本项目使用CMake构建,您可以直接将整个目录引入为子项目,也可以选择下载我们提前打包好的整合文件 zeroerr.hpp。项目的构建可以在项目根目录下使用如下指令:

mkdir build
cmake -DCMAKE_BUILD_TYPE=Release -B ./build -S .
cmake --build ./build

可选的构建参数

构建参数 选项 含义
COLORFUL_OUTPUT AUTO, ON, OFF 采用彩色输出,此功能依赖特定操作系统
ENABLE_THREAD_SAFE ON, OFF 启用线程安全支持
ENABLE_AUTO_INIT ON, OFF 自动在进程启动时初始化一些环境检测
USE_MOLD ON, OFF 使用 mold linker 链接
BUILD_EXAMPLES ON, OFF 构建示例代码

zeroerr's People

Contributors

sunxfancy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

xuesongtap

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.