Skip to content
Open

Dev #37

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clangd
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
CompileFlags:
CompilationDatabase: ./build/linux
CompilationDatabase: build/win32-debug
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.15)

project(ZeroErr LANGUAGES C CXX)

if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
endif()

if(NOT DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
Expand Down
59 changes: 44 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
.PHONY: all linux windows test linux-test windows-test doc clean

# Tool overrides (optional): CLANGXX=... CLANG=... CMAKE=...
CLANGXX ?= clang++
CLANG ?= clang
CMAKE ?= cmake

# Discover clang compiler-rt / libFuzzer link directory.
# Prefer explicit override, then clang++ -print-runtime-dir, then dirname of fuzzer_no_main.
CLANG_RUNTIME_DIR ?= $(shell $(CLANGXX) -print-runtime-dir 2>/dev/null)
ifeq ($(strip $(CLANG_RUNTIME_DIR)),)
CLANG_RUNTIME_DIR := $(shell dirname $$($(CLANGXX) -print-file-name=libclang_rt.fuzzer_no_main-x86_64.a 2>/dev/null) 2>/dev/null)
endif

# Only pass -L when the directory really exists.
ifneq ($(wildcard $(CLANG_RUNTIME_DIR)/.),)
FUZZ_LINK_FLAGS := -L$(CLANG_RUNTIME_DIR)
ENABLE_FUZZING ?= ON
else
FUZZ_LINK_FLAGS :=
ENABLE_FUZZING ?= OFF
$(info [zeroerr] clang runtime dir not found; ENABLE_FUZZING=$(ENABLE_FUZZING))
endif

# Linker flags for fuzzing builds (override with FUZZ_LINK_FLAGS=...).
LINUX_FUZZ_CMAKE_FLAGS := -DENABLE_FUZZING=$(ENABLE_FUZZING)
ifneq ($(strip $(FUZZ_LINK_FLAGS)),)
LINUX_FUZZ_CMAKE_FLAGS += -DCMAKE_EXE_LINKER_FLAGS=$(FUZZ_LINK_FLAGS) -DCMAKE_CXX_FLAGS=$(FUZZ_LINK_FLAGS)
endif

all: linux windows

build/linux/Makefile: Makefile
mkdir -p build/linux
cmake -B build/linux -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \
-DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON -DDISABLE_CUDA_BUILD=OFF -DENABLE_FUZZING=ON \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang \
$(CMAKE) -B build/linux -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \
-DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=OFF -DDISABLE_CUDA_BUILD=OFF \
-DCMAKE_CXX_COMPILER=$(CLANGXX) -DCMAKE_C_COMPILER=$(CLANG) \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_CXX_FLAGS=-L/home/utils/llvm-20.1.8/lib/clang/20/lib/linux
$(LINUX_FUZZ_CMAKE_FLAGS)

linux: build/linux/Makefile
cmake --build build/linux -j `nproc`
$(CMAKE) --build build/linux -j `nproc`

build/windows/ZeroErr.sln: Makefile
mkdir -p build/windows
Expand All @@ -23,12 +51,12 @@ windows: build/windows/ZeroErr.sln

build/macosx/Makefile: Makefile
mkdir -p build/macosx
cmake -B build/macosx -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \
$(CMAKE) -B build/macosx -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \
-DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON -DDISABLE_CUDA_BUILD=OFF -DENABLE_FUZZING=OFF \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=$(CLANGXX) -DCMAKE_C_COMPILER=$(CLANG)

macosx: build/macosx/Makefile
cmake --build build/macosx -j 4
$(CMAKE) --build build/macosx -j 4

test: linux-test windows-test fuzz-test

Expand All @@ -55,21 +83,22 @@ macosx-test: macosx

build/linux-release/Makefile: Makefile
mkdir -p build/linux-release
cmake -B build/linux-release -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_STANDARD=11 \
-DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON -DENABLE_FUZZING=ON \
-DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang
$(CMAKE) -B build/linux-release -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=11 \
-DBUILD_EXAMPLES=ON -DBUILD_TEST=ON -DUSE_MOLD=ON \
-DCMAKE_CXX_COMPILER=$(CLANGXX) -DCMAKE_C_COMPILER=$(CLANG) \
$(LINUX_FUZZ_CMAKE_FLAGS)

linux-release: build/linux-release/Makefile
cmake --build build/linux-release -j `nproc`
$(CMAKE) --build build/linux-release -j `nproc`

bench: linux-release
cd build/linux-release/test && ./unittest -b --testcase=speedtest

doc:
mkdir -p build-linux-doc
cd build-linux-doc && cmake .. -DCMAKE_BUILD_TYPE=Debug \
cd build-linux-doc && $(CMAKE) .. -DCMAKE_BUILD_TYPE=Debug \
-DBUILD_EXAMPLES=ON -DBUILD_DOC=ON && \
cmake --build . --target doxy -j `nproc`
$(CMAKE) --build . --target doxy -j `nproc`

clean:
rm -rf build
rm -rf build
85 changes: 83 additions & 2 deletions docs/pages/zh/3.assertion.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,90 @@
断言(Assertion)
================
[TOC]

断言是一个表达式,用来约束当前的程序变量符合一定的条件。如果断言失败,程序会抛出一个异常。断言通常用于调试和测试,可以在程序中加入一些检查点,确保程序的正确性。
我们提供了高级的断言库,可以用来检查变量的类型、值、长度等。

## 断言级别

我们提供了高级的断言库,可以用来检查变量的类型、值、长度等。
ZeroErr提供三种不同级别的断言:

* CHECK: 警告级别(WARN)- 当断言失败时,会输出警告信息,但程序继续执行
* REQUIRE: 错误级别(ERROR)- 当断言失败时,会抛出异常并中断当前执行流
* ASSERT: 致命级别(FATAL)- 当断言失败时,会抛出异常并应该终止程序

## 断言类型

### 基本断言
最基本的断言用于检查条件是否为真:

```c++
CHECK(条件); // 警告级别断言
REQUIRE(条件); // 错误级别断言
ASSERT(条件); // 致命级别断言
```

### 反向断言
检查条件是否为假:
```c++
CHECK_NOT(条件); // 检查条件为假,否则显示警告
REQUIRE_NOT(条件); // 检查条件为假,否则抛出错误
ASSERT_NOT(条件); // 检查条件为假,否则抛出致命错误
```

### 异常断言
检查代码是否抛出异常:
```c++
CHECK_THROWS(可能抛出异常的表达式); // 检查是否抛出异常,否则显示警告
REQUIRE_THROWS(可能抛出异常的表达式); // 检查是否抛出异常,否则抛出错误
ASSERT_THROWS(可能抛出异常的表达式); // 检查是否抛出异常,否则抛出致命错误
```

### 比较断言
提供各种比较操作的断言:
```c++
// 相等比较 (EQ)
CHECK_EQ(左值, 右值); // 检查左值 == 右值
REQUIRE_EQ(左值, 右值); // 检查左值 == 右值
ASSERT_EQ(左值, 右值); // 检查左值 == 右值
// 不等比较 (NE)
CHECK_NE(左值, 右值); // 检查左值 != 右值
REQUIRE_NE(左值, 右值); // 检查左值 != 右值
ASSERT_NE(左值, 右值); // 检查左值 != 右值
// 大于比较 (GT)
CHECK_GT(左值, 右值); // 检查左值 > 右值
REQUIRE_GT(左值, 右值); // 检查左值 > 右值
ASSERT_GT(左值, 右值); // 检查左值 > 右值
// 大于等于比较 (GE)
CHECK_GE(左值, 右值); // 检查左值 >= 右值
REQUIRE_GE(左值, 右值); // 检查左值 >= 右值
ASSERT_GE(左值, 右值); // 检查左值 >= 右值
// 小于比较 (LT)
CHECK_LT(左值, 右值); // 检查左值 < 右值
REQUIRE_LT(左值, 右值); // 检查左值 < 右值
ASSERT_LT(左值, 右值); // 检查左值 < 右值
// 小于等于比较 (LE)
CHECK_LE(左值, 右值); // 检查左值 <= 右值
REQUIRE_LE(左值, 右值); // 检查左值 <= 右值
ASSERT_LE(左值, 右值); // 检查左值 <= 右值
```

### 自定义消息
所有断言宏都支持添加自定义消息,将在断言失败时显示:
```c++
CHECK(x > 0, "x必须为正数,当前值:{}", x);
REQUIRE_EQ(result, expected, "计算结果不匹配,期望:{},实际:{}", expected, result);
```

### 禁用断言
如果希望在发布模式下禁用所有断言,可以定义ZEROERR_NO_ASSERT宏:
```c++
#define ZEROERR_NO_ASSERT
#include <zeroerr/assert.h>
```

### 断言结果输出
当断言失败时,ZeroErr会自动输出详细的错误信息,包括:
* 断言级别(WARN/ERROR/FATAL)
* 断言表达式及其展开结果
* 源文件位置和行号
* 自定义错误消息(如果提供)
101 changes: 100 additions & 1 deletion docs/pages/zh/6.unittest.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,102 @@
单元测试(Unit Test)
====================
[TOC]
[TOC]

单元测试是一种用于验证代码正确性的方法。ZeroErr提供了一个简单易用的单元测试框架。

### 基本用法

最简单的测试用例如下:

```c++
TEST_CASE("my first test") {
REQUIRE(1 + 1 == 2);
}
```

TEST_CASE 宏用于定义一个测试用例。它接受一个字符串参数作为测试名称。
在测试用例中,你可以使用各种断言宏来验证代码的行为。断言宏的使用,请参考[断言](./3.assertion.md)。

### 装饰器

ZeroErr提供了一些装饰器,可以用来修饰测试用例。

* `skip()`: 跳过当前测试用例
* `timeout()`: 设置当前测试用例的超时时间
* `may_fail()`: 定义一个可能会失败的测试用例
* `should_fail()`: 定义一个应该失败的测试用例

这些装饰器可以插入到测试定义的宏中使用:

```c++
TEST_CASE("my first test", should_fail(), timeout(0.01)) {
REQUIRE(1 + 1 != 2);
}
```


### 测试夹具(Test Fixture)

如果你需要在多个测试之间共享一些设置代码,可以使用测试夹具:

```c++
class MyFixture {
protected:
std::vector<int> numbers;

void SetUp() {
numbers = {1, 2, 3, 4, 5};
}
};

TEST_CASE_FIXTURE(MyFixture, "test with fixture") {
SetUp();
REQUIRE(numbers.size() == 5);
}
```

### BDD 风格测试

ZeroErr支持行为驱动开发(BDD)风格的测试:

```c++
SCENARIO("用户登录") {
GIVEN("一个已注册用户") {
User user("test", "password");

WHEN("使用正确的密码登录") {
bool result = user.login("password");

THEN("登录应该成功") {
REQUIRE(result == true);
}
}
}
}
```

### 测试矩阵

测试结果会被分为以下几类:
- passed: 通过的测试数量
- warning: 带警告通过的测试数量
- failed: 失败的测试数量
- skipped: 跳过的测试数量

### 配置选项

UnitTest提供了多个命令行配置选项:
- verbose: 输出测试结果
- quiet: 不输出测试结果
- bench: 启用定义的基准测试
- fuzz: 启用定义的模糊测试
- list-test-cases: 列出所有测试用例
- no-color: 不使用彩色输出
- log-to-report: 将测试结果记录到报告中
- correct_output_path: 存储黄金文件的路径
- reporters: 用于报告测试结果的报告器名称(支持console, xml两种)
- testcase: 运行指定名称的测试用例
- testcase-exclude: 排除指定名称的测试用例
- file: 运行指定文件的测试用例
- file-exclude: 排除指定文件的测试用例

13 changes: 5 additions & 8 deletions include/zeroerr/assert.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,14 @@ ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH
} while (0)
#endif

#ifdef ZEROERR_OS_WINDOWS
#define ZEROERR_PRINT_ASSERT(cond, level, pattern, ...) \
ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, " Assertion Failed:\n{msg}" pattern, \
assertion_data.log(), __VA_ARGS__)
#else
// pattern is optional: call sites pass "" __VA_ARGS__ so empty message args still
// provide a pattern token. ##__VA_ARGS__ drops the trailing comma when there are no
// extra format arguments (required for clang/clangd; MSVC accepts the extension too).
ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments")
#define ZEROERR_PRINT_ASSERT(cond, level, pattern, ...) \
ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, " Assertion Failed:\n{msg}" pattern, \
assertion_data.log(), ##__VA_ARGS__)
ZEROERR_CLANG_SUPPRESS_WARNING_POP
#endif

#define ZEROERR_ASSERT_EXP(cond, level, expect_throw, is_false, ...) \
ZEROERR_FUNC_SCOPE_BEGIN { \
Expand All @@ -78,7 +75,7 @@ ZEROERR_CLANG_SUPPRESS_WARNING_POP
decltype(_ZEROERR_TEST_CONTEXT), \
std::is_same<decltype(_ZEROERR_TEST_CONTEXT), \
const bool>::value>::setContext(assertion_data, _ZEROERR_TEST_CONTEXT); \
ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, __VA_ARGS__); \
ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, "" __VA_ARGS__); \
if (false) debug_break(); \
assertion_data(); \
ZEROERR_FUNC_SCOPE_RET(assertion_data.passed); \
Expand All @@ -103,7 +100,7 @@ ZEROERR_CLANG_SUPPRESS_WARNING_POP
decltype(_ZEROERR_TEST_CONTEXT), \
std::is_same<decltype(_ZEROERR_TEST_CONTEXT), \
const bool>::value>::setContext(assertion_data, _ZEROERR_TEST_CONTEXT); \
ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, __VA_ARGS__); \
ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, "" __VA_ARGS__); \
if (false) debug_break(); \
assertion_data(); \
ZEROERR_FUNC_SCOPE_RET(assertion_data.passed); \
Expand Down
Loading
Loading