Skip to content

Commit c7257ca

Browse files
authored
Merge pull request #25 from sunxfancy/dev
v0.2.1
2 parents 5ae88b1 + 2076173 commit c7257ca

23 files changed

Lines changed: 2586 additions & 2288 deletions

.clang-format

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ BraceWrapping:
1515
AfterUnion: false
1616
BeforeCatch: false
1717
BeforeElse: false
18-
18+
19+
AlignConsecutiveShortCaseStatements:
20+
Enabled: true
21+
AcrossEmptyLines: true
22+
AcrossComments: true
23+
AlignCaseColons: false
1924

2025
CompactNamespaces: false
2126
AlignConsecutiveAssignments: true

Readme.en.md

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Hope you get 0 errors and 0 warnings everyday!
88
![](./docs/fig/zeroerr.jpg)
99

1010

11-
ZeroErr is a smart assert library, a lightweight unit test framework and a quick logging framework. It integrates those features and provided an unite and clear interface for seperate using or joint using.
11+
ZeroErr is a smart assertion library, a lightweight unit testing framework and a structure logging framework. It integrates those features and provided an unite and clear interface for separated usage or combined usage.
1212

1313
[English Documentation](https://sunxfancy.github.io/zeroerr/en/) | [项目文档](https://sunxfancy.github.io/zeroerr/zh/)
1414

@@ -21,7 +21,7 @@ The current popular unit testing frameworks, e.g. Catch2, doctest, Boost.Test an
2121

2222
### 1. Generic Printing
2323

24-
Most unit testing frameworks and logger libraries can not provide a generic printing for user customized type. Especially, when using containers, struct and pointers (including smart pointers), user have to manualy write code to generate the log message or print those information during unit testing failed cases.
24+
Most unit testing frameworks and logger libraries can not provide a generic printing for user customized types. Especially, when using containers, structures and pointers (including smart pointers), user have to manually write code to generate the log message or print those information during unit testing failed cases.
2525

2626
This library `zeroerr` gives you an ability to print generically for all types:
2727

@@ -36,7 +36,7 @@ Similar to other C++ unit testing frameworks, `zeroerr` will convert this piece
3636
3737
![case1](docs/fig/case1.png)
3838
39-
For the custom struct type with override `std::ostream& operator<<(std::ostream&, Type)` stream output, you can use it not only for this type but also all contains using this type, including multiple recurisve contains:
39+
For the custom struct type with override `std::ostream& operator<<(std::ostream&, Type)` stream output, you can use it not only for this type but also all contains using this type, including multiple recursive contains:
4040
4141
```c++
4242
struct Node {
@@ -94,9 +94,9 @@ This functin `PrintExt` will match all the class who's base class is `Value` and
9494
9595
![case3-llvm](./docs/fig/case3.png)
9696
97-
### 2. Joint using of assert, log and unit testing
97+
### 2. Combined usage of assert, log and unit testing
9898
99-
If you use a logger, an unit testing framework and a smart assert libary, you can joint use them and some macros may conflict. In `zeroerr`, if an assertion is failed, the logger will recevie an event and stored in your log file. If you are using an assertion in unit testing, the assertion can be recorded and reported in the end.
99+
If you use one logging framework, an unit testing framework and an assertion library, it's not a easy work to combine them together. There is a lot of benefits to use assertion, logging and unit testing together. In `zeroerr`, if an assertion is failed, the logger will receive an event and stored the event in your log file. If you are using an assertion in unit testing, the assertion failure, logged fatal events can be recorded and reported.
100100
101101
```c++
102102
int fib(int n) {
@@ -121,13 +121,33 @@ TEST_CASE("fib function test") {
121121

122122
![joint1](docs/fig/joint1.png)
123123

124-
Further more, the unit testing can check the log result matches the previous running result to avoid writing code to check it.
124+
For the logging system, the unit testing can access the log data to ensure that the function has executed the expected logic and results.
125+
126+
```c++
127+
118 static void function() {
128+
119 int k = system_call();
129+
120 LOG_IF(k != 0, "System call failed, error code = {k}", k);
130+
121 }
131+
...
132+
133+
TEST_CASE("access log in Test case") {
134+
zeroerr::suspendLog();
135+
function();
136+
CHECK(LOG_GET(function, 120, k, int) == ERROR_CODE);
137+
zeroerr::resumeLog();
138+
}
139+
```
140+
141+
In order to access the log, we need to pause the log system first, to avoid the data being output to the file, then call the function, access the data in the log through the `LOG_GET` macro, and finally resume the log system. (Currently experimental, only the first call of each log point can be accessed)
142+
143+
144+
Further more, the unit testing can check the logged result if it matches the previous running result (a golden file) to avoid writing any code in the test case.
125145

126146
```c++
127147
TEST_CASE("match ostream") {
128148
// match output can be done in the following workflow
129-
// 1. user mark the test case which are comparing output use 'have_same_output'
130-
// 2. If the output is not exist, the result has been used as a correct verifier.
149+
// 1. user mark the test case which are comparing output use 'ZEROERR_HAVE_SAME_OUTPUT'
150+
// 2. If the output is not exist, the result will be store to the disk.
131151
// 3. If the output is exist, compare with it and report error if output is not match.
132152
std::cerr << "a = 100" << std::endl;
133153

@@ -138,54 +158,63 @@ TEST_CASE("match ostream") {
138158
Once you set `ZEROERR_HAVE_SAME_OUTPUT` marco, the system will check the output stream and save the first run result into a file. Then, the next run will compare the result to see if it the same. (Currently experimental)
139159
140160
141-
Finally, for the log system, the unit testing can access the log data to ensure that the function has executed the expected logic and results.
161+
## 3. Fuzzing Support
142162
143-
```c++
144-
118 static void function() {
145-
119 LOG("function log {i}", 1);
146-
120 LOG("function log {sum}, {i}", 10, 1);
147-
121 }
148-
...
163+
Most Unit Testing frameworks do not support fuzzing. However, it's a powerful feature to automatically detect faults in the software and can greatly reduce the work to write test cases.
149164
150-
TEST_CASE("access log in Test case") {
151-
zeroerr::suspendLog();
152-
function();
153-
CHECK(LOG_GET(function, 119, i, int) == 1);
154-
CHECK(LOG_GET(function, 120, sum, int) == 10);
155-
CHECK(LOG_GET(function, 120, i, int) == 1);
156-
zeroerr::resumeLog();
165+
Different than other fuzzing framework, `zeroerr` can also support logging and assertion in the code, so the fuzzing result not only contains corpus but also with the logging and assertion information.
166+
167+
Here is an example of using `zeroerr` to do structured fuzzing:
168+
169+
```c++
170+
FUZZ_TEST_CASE("fuzz_test") {
171+
LOG("Run fuzz_test");
172+
FUZZ_FUNC([=](int k, std::string num) {
173+
int t = atoi(num.c_str());
174+
LOG("k: {k}, num:{num}, t: {t}", k, num, t);
175+
REQUIRE(k == t);
176+
})
177+
.WithDomains(InRange<int>(0, 10), Arbitrary<std::string>())
178+
.WithSeeds({{5, "Foo"}, {10, "Bar"}})
179+
.Run(10);
157180
}
158181
```
159182

160-
In order to access the log, we need to pause the log system first, to avoid the data being output to the file, then call the function, access the data in the log through the `LOG_GET` macro, and finally resume the log system. (Currently experimental, only the first call of each log point can be accessed)
183+
Inspired by [fuzztest](https://github.com/google/fuzztest), Domain is a concept to specify the input data range (or patterns) for the target function. Here, we use `InRange` to specify the range of `k` is 0 to 10, and `Arbitrary` to specify the data of `num` can be any random string. Then, we use `WithSeeds` to specify the initial seeds for the fuzzing.
161184

185+
The macro `FUZZ_TEST_CASE` will generate a test case which can connect with `libFuzzer` to run the fuzzing. Finally, we use `Run(10)` to call `libFuzzer` to run the target for 10 times.
186+
187+
To build the test case with fuzzing, you need to use `clang++` to compile the code and with `-fsanitize=fuzzer-no-link` and link the `-lclang_rt.fuzzer_no_main-x86_64` which is a version of libFuzzer without main function. You can find this runtime library by calling `clang++ -print-runtime-dir`. Here is the complete command to build the test case with fuzzing support:
188+
189+
```bash
190+
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
191+
```
162192

163193

164-
## Features
194+
## Other Good Features
165195

166196

167-
Using ZeroErr, you can catch your assert error, log fatal event in the unit testing.
168-
The fatal condition will be recorded and printed. Here are a list of features we provided:
197+
Here are a list of features we provided:
169198

170-
1. Minimal Requirement
171-
You can only include what you need. If you need assert but no unit testing, no problem.
199+
1. Partially include
200+
You can only include what you need. If you need only assertion but no unit testing, no problem.
172201

173202
2. Optional thread safety
174-
You can choose to build with/without thread safety. For some simple single thread program, log is no need to be multithread safed.
203+
You can choose to build with/without thread safety.
175204

176205
3. Fastest log
177-
Using a lock-free queue for logging and multiple level of log writing policies. You can choose to only write to disk with the most important events.
206+
Multiple level of log writing policies. You can choose to only write to disk with the most important events.
178207

179208
4. Customized print / log / assert printing format
180-
You can customize your printing format for everything. There is a callback function for the printing.
209+
You can customize your printing format for everything. There is a templated callback function for the printing.
181210

182211
5. Quickly debug something
183-
You can use dbg macro to quickly see the output, it can print the expression also.
212+
You can use dbg macro to quickly see the output, it can be applied to any expression.
184213

185214
6. Colorful output
186-
You can have default colorful output to terminal and no color for file
215+
You can have default colorful output to terminal and no color for file output
187216

188-
7. Print struct/stl/special library data structure
217+
7. Print struct/stl/pointers without any extra code
189218

190219
8. Doctest like assertion and unit test feature
191220
You can use your unit test as a documentation of function behavior. The output of unittest can be a documented report.
@@ -194,13 +223,13 @@ You can use your unit test as a documentation of function behavior. The output o
194223
After assertion failed, the logging result will print automatically even if you didn't redirect to your error stream
195224

196225
10. Logging Category
197-
Logging information can have customized category and only display one categroy based on your assertion or configuration
226+
Logging information can have customized category and only display one category based on your assertion or configuration
198227

199228
11. Logging for Unit Testing
200-
You can use a correct logging result as your unit testing comparsion. So you just need to manually verify your log once and setup it as baseline comparsion. The unit testing framework will use that as the result to verify unit testing
229+
You can use a correct logging result as your unit testing golden file. So you just need to manually verify your log once and save it. The unit testing framework will use the golden file to verify your unit testing result.
201230

202231
12. Structured Logging
203-
We can support output structured information directly into plain text, json, logfmt, or other custom format
232+
We can support output structured information directly into plain text or lisp format (json, logfmt, or other custom format should be the next step to support)
204233

205234
13. Automatic Tracing with logging
206235
While logging at the end, we can record the time consuming for this function.
@@ -210,7 +239,7 @@ While logging at the end, we can record the time consuming for this function.
210239
* dbg
211240
* print (without use extern functions)
212241
* assert
213-
* color (if always enable)
242+
* color (if always enabled)
214243

215244

216245
## The logo generation

Readme.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,38 @@ TEST_CASE("access log in Test case") {
161161

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

164+
## 3. Fuzzing的支持
165+
166+
大多数单元测试框架不支持fuzzing。然而,Fuzzing功能强大,可以自动检测软件中的错误,并且可以大大减少编写测试用例的工作量。
167+
168+
不同于其他fuzzing框架,`zeroerr`可以支持在代码中使用日志和断言,因此fuzzing的结果不仅包含了输入数据,还包含了日志和断言的信息。
169+
170+
使用方法:
171+
172+
```c++
173+
FUZZ_TEST_CASE("fuzz_test") {
174+
LOG("Run fuzz_test");
175+
FUZZ_FUNC([=](int k, std::string num) {
176+
int t = atoi(num.c_str());
177+
LOG("k: {k}, num:{num}, t: {t}", k, num, t);
178+
REQUIRE(k == t);
179+
})
180+
.WithDomains(InRange<int>(0, 10), Arbitrary<std::string>())
181+
.WithSeeds({{5, "Foo"}, {10, "Bar"}})
182+
.Run(10);
183+
}
184+
```
185+
186+
受到 [fuzztest](https://github.com/google/fuzztest)的启发,我们使用Domain这个概念,用于指定目标函数的输入数据范围(或模式)。在这里,我们使用 `InRange` 来指定 `k` 的范围是0到10,使用 `Arbitrary` 来指定 `num` 的数据可以是任意随机字符串。然后,我们使用 `WithSeeds` 来指定fuzzing的初始种子。最后,我们使用 `Run` 来指定fuzzing的次数。
187+
188+
宏 `FUZZ_TEST_CASE` 会生成一个测试用例,可以连接到 `libFuzzer` 来运行fuzzing。最后,我们使用 `Run(10)` 来调用 `libFuzzer` 来运行目标10次。
189+
190+
为了构建带有fuzzing的测试用例,您需要使用 `clang++` 编译代码,并使用 `-fsanitize=fuzzer-no-link` 并链接 `-lclang_rt.fuzzer_no_main-x86_64`,这是一个没有main函数的libFuzzer版本。您可以通过调用 `clang++ -print-runtime-dir` 来找到这个运行时库。以下是带有fuzzing支持的测试用例的完整构建命令:
191+
192+
```bash
193+
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
194+
```
195+
164196

165197
## 项目构建
166198

include/zeroerr/assert.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ struct context_helper<T, false> {
280280
switch (data.info.level) {
281281
case assert_level::ZEROERR_FATAL_l:
282282
case assert_level::ZEROERR_ERROR_l: ctx->failed_as++; break;
283-
case assert_level::ZEROERR_WARN_l: ctx->warning_as++; break;
283+
case assert_level::ZEROERR_WARN_l: ctx->warning_as++; break;
284284
}
285285
}
286286
};

include/zeroerr/benchmark.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH
1616

17-
#define ZEROERR_CREATE_BENCHMARK_FUNC(function, name) \
18-
static void function(zeroerr::TestContext*); \
19-
static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \
17+
#define ZEROERR_CREATE_BENCHMARK_FUNC(function, name) \
18+
static void function(zeroerr::TestContext*); \
19+
static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \
2020
{name, __FILE__, __LINE__, function}, zeroerr::TestType::bench); \
2121
static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT))
2222

@@ -47,7 +47,8 @@ using Clock = std::conditional<std::chrono::high_resolution_clock::is_steady,
4747

4848
namespace detail {
4949
struct LinuxPerformanceCounter;
50-
}
50+
struct WindowsPerformanceCounter;
51+
} // namespace detail
5152

5253
/**
5354
* @brief PerformanceCounter is a class to measure the performance of a function.
@@ -72,7 +73,8 @@ struct PerformanceCounter {
7273
PerfCountSet<uint64_t> _val;
7374
PerfCountSet<bool> _has;
7475

75-
detail::LinuxPerformanceCounter* _perf = nullptr;
76+
detail::LinuxPerformanceCounter* _perf = nullptr;
77+
detail::WindowsPerformanceCounter* win_perf = nullptr;
7678
};
7779

7880
/**

include/zeroerr/domains/arbitrary.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class Arbitrary<
8484
T, typename std::enable_if<detail::is_specialization<T, std::basic_string>::value>::type>
8585
: public Domain<T, std::vector<typename T::value_type>> {
8686
Arbitrary<std::vector<typename T::value_type>> impl;
87+
8788
public:
8889
using ValueType = T;
8990
using CorpusType = std::vector<typename T::value_type>;
@@ -93,9 +94,7 @@ class Arbitrary<
9394
return CorpusType(v.begin(), v.end());
9495
}
9596

96-
CorpusType GetRandomCorpus(Rng& rng) const override {
97-
return impl.GetRandomCorpus(rng);
98-
}
97+
CorpusType GetRandomCorpus(Rng& rng) const override { return impl.GetRandomCorpus(rng); }
9998

10099
void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override {
101100
impl.Mutate(rng, v, only_shrink);
@@ -126,11 +125,13 @@ class Arbitrary<
126125

127126
template <typename T, typename U>
128127
class Arbitrary<std::pair<T, U>>
129-
: public AggregateOf<std::pair<typename std::remove_const<T>::type, typename std::remove_const<U>::type>> {};
128+
: public AggregateOf<
129+
std::pair<typename std::remove_const<T>::type, typename std::remove_const<U>::type>> {};
130130

131131

132132
template <typename... T>
133-
class Arbitrary<std::tuple<T...>> : public AggregateOf<std::tuple<typename std::remove_const<T>::type...>> {};
133+
class Arbitrary<std::tuple<T...>>
134+
: public AggregateOf<std::tuple<typename std::remove_const<T>::type...>> {};
134135

135136
template <typename T>
136137
class Arbitrary<const T> : public Arbitrary<T> {};

include/zeroerr/domains/element_of.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ class ElementOf : public Domain<T, uint64_t> {
2323

2424
CorpusType FromValue(const ValueType& v) const override {
2525
for (size_t i = 0; i < elements.size(); i++) {
26-
if (elements[i] == v) {
27-
return i;
28-
}
26+
if (elements[i] == v) return i;
2927
}
3028
return 0;
3129
}

include/zeroerr/format.h

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
11
#pragma once
22
#include "zeroerr/print.h"
33

4-
#include <string>
54
#include <sstream>
5+
#include <string>
66

7-
namespace zeroerr
8-
{
7+
namespace zeroerr {
98

109
template <typename... T>
1110
std::string format(const char* fmt, T... args) {
1211
std::stringstream ss;
13-
bool parse_name = false;
14-
Printer print;
15-
print.isQuoted = false; print.isCompact = true;
16-
print.line_break = "";
12+
bool parse_name = false;
13+
Printer print;
14+
15+
print.isQuoted = false;
16+
print.isCompact = true;
17+
print.line_break = "";
1718
std::string str_args[] = {print(args)...};
19+
1820
int j = 0;
1921
for (const char* i = fmt; *i != '\0'; i++) {
20-
switch (*i)
21-
{
22-
case '{':
23-
parse_name = true;
24-
break;
25-
case '}':
26-
parse_name = false;
27-
ss << str_args[j++];
28-
break;
29-
default:
30-
if (!parse_name) ss << *i;
31-
break;
22+
switch (*i) {
23+
case '{': parse_name = true; break;
24+
case '}':
25+
parse_name = false;
26+
ss << str_args[j++];
27+
break;
28+
default:
29+
if (!parse_name) ss << *i;
30+
break;
3231
}
3332
}
3433
return ss.str();
3534
}
3635

37-
} // namespace zeroerr
36+
} // namespace zeroerr

0 commit comments

Comments
 (0)