An easy to use and lightweight unit test framework for C/C++ on
Linux exported as a single header file
eztest.h
The API is made to mostly match
GoogleTest. For some
projects it can be a drop-in replacements, for others it won't work.
The key selling point, and primary motivating factor for writing
EZTest, is that it will work out of the box with
-fsanitizer=memory,
as opposed to GoogleTest
which requires re-building from source along with an
-fsanitizer=memory
enabled libc++ to work.
- 1. Overview and Motivation
- 2. Installation
- 3. Usage
- 4. Advanced Usage and Toggles
- 5. System Requirements
- 6. Developer Info
The general motivating factor for EZTest is to be an alternative to
GoogleTest with a few
important distinguishing characteristics.
EZTesttests do not link against the C++ runtime library (libc++.so/libstdc++.so). This makes compiling the tests with-fsanitizer=memorytrivial.EZTestruns each test as its own process (usingfork). This allows for signal handling of buggy tests with clear error messages. Further it isolates tests from one another preventing potential corruption carrying over from one test to another.EZTestis packages as just a single header file and requires no extra link/compilation steps to get working.
The default installation location is /usr/local.
Keep in mind, the only file necessary to get started is
eztest.h,
so feel free to just copy and include it as you see fit.
Clone the repository and run these commands in the cloned folder:
mkdir build && cd build
cmake .. # Use -DCMAKE_INSTALL_PREFIX=<your_path> to control the destination
cmake --build . --target installThen to use the installed library, add the following to your cmake project:
find_package(eztest REQUIRED)
target_link_libraries(your_test_target eztest::eztest)Clone the repository and add the following to your cmake project
add_subdirectory(path/to/your/cloned/eztest)
target_link_libraries(your_test_target eztest)There are several toggles for modify how the
eztest.h
is generated/installed.
These include
option(
EZTEST_ULP_PRECISION
"Int: Set float/double compare ULP bound"
OFF)
option(
EZTEST_FLOAT_ULP_PRECISION
"Int: Set float compare ULP bound"
OFF)
option(
EZTEST_DOUBLE_ULP_PRECISION
"Int: Set double compare ULP bound"
OFF)
option(
EZTEST_DISABLE_WARNINGS
"Bool: Set/unset to configure whether warnings are supressed with pragmas"
OFF)
option(
EZTEST_DISABLE_LINTS
"Bool: Set/unset to configure whether lints are supressed with comments"
OFF)
option(
EZTEST_STRICT_NAMESPACE
"Bool: Set/unset to configure whether generic TEST/ASSERT/EXPECT macros are defined"
OFF)and can be set during the installation setup. All of these other than
EZTEST_DISABLE_LINTS have corresponding macros (see 4. Advanced
Usage and Toggles). The
EZTEST_DISABLE_LINTS option will determine whether /* NOLINT* */
directives are transferred from the source code to the header during
generation. If you want to ensure certain code characteristics using
clang-tidy including in the EZTest header, set
-DEZTEST_DISABLE_LINTS=ON during installation.
The API is similiar to that of
GoogleTest but there are
some key distinction.
All symbols and macros defined in
eztest.h
are prefixed eztest or EZTEST. Furthermore if compiling with C++
all symbols are inside the eztest:: namespace. Assuming your code
has no symbols/macros begining with eztest or EZTEST and/or no
symbols in the eztest:: namespace, there should be no symbol
conflicts when using EZTest.
- Include
eztest.hin your test file - Build your test executable
- Run the resulting executable.
For example take the following test.cc
/* eztest.h contains `main`. */
#include "eztest/eztest.h"
TEST(foo, bar) {
ASSERT_EQ(0, 0);
}To run the test(s) we would do the following:
clang++ test.cc -O3 -o test
./testTests are created with the following macros:
TEST(suite, name)TEST_TIMED(suite, name, timeout_in_milliseconds)
The usage is identical to
GoogleTest i.e:
TEST(my_suite, my_test) {
/* Test code goes here... */
}There are assertions / expect statements a variety of different
checks. The difference between an ASSERT check and EXPECT check,
is that if an ASSERT check fails, the test will end
immediately. Alternatively if an EXPECT check fails, the test will
continue running but will fail on termination.
{ASSERT|EXPECT}_TRUE(arg:bool)- Checks the
argistrue
- Checks the
{ASSERT|EXPECT}_FALSE(arg:bool)- Checks the
argisfalse
- Checks the
{ASSERT|EXPECT}_EQ(lhs:anyT, rhs:anyT)- Checks that
lhs == rhs
- Checks that
{ASSERT|EXPECT}_NE(lhs:anyT, rhs:anyT)- Checks that
lhs != rhs
- Checks that
{ASSERT|EXPECT}_LE(lhs:anyT, rhs:anyT)- Checks that
lhs <= rhs
- Checks that
{ASSERT|EXPECT}_LT(lhs:anyT, rhs:anyT)- Checks that
lhs < rhs
- Checks that
{ASSERT|EXPECT}_GE(lhs:anyT, rhs:anyT)- Checks that
lhs >= rhs
- Checks that
{ASSERT|EXPECT}_GT(lhs:anyT, rhs:anyT)- Checks that
lhs > rhs
- Checks that
{ASSERT|EXPECT}_STREQ(lhs:str, rhs:str)- Checks that
strcmp(lhs, rhs) == 0
- Checks that
{ASSERT|EXPECT}_STRNE(lhs:str, rhs:str)- Checks that
strcmp(lhs, rhs) != 0
- Checks that
{ASSERT|EXPECT}_STRCASEEQ(lhs:str, rhs:str)- Checks that
strcasecmp(lhs, rhs) == 0
- Checks that
{ASSERT|EXPECT}_STRCASENE(lhs:str, rhs:str)- Checks that
strcasecmp(lhs, rhs) != 0
- Checks that
{ASSERT|EXPECT}_FLOAT_EQ(lhs:float, rhs:float)- Checks that
ULP_difference(lhs, rhs) < Threshold- See more on
ULP.
- See more on
- Checks that
{ASSERT|EXPECT}_DOUBLE_EQ(lhs:double, rhs:double)- Checks that
ULP_difference(lhs, rhs) < Threshold
- Checks that
{ASSERT|EXPECT}_NEAR(lhs:fp, rhs:fp, bound:fp)- Checks that
abs(lhs - rhs) <= bound
- Checks that
In the above:
boolis an type that supports the!operator.anyTis any type that the specified operator is valid for.stris one of the following:char *std::string(if usingC++)std::string_view(if usingC++17or newer)
fpis one of the following:floatdouble
Failure messages that print the variables exist in the following cases:
- You are using
C++ - You are using
C11or newer with a compiler that supports the__typeof__extension.
Otherwise, the failure message will only indicate the line number / variables names that failed.
In additional to default failure messages, you can also optionally
include printf as
optional additional arguments to any ASSERT/EXPECT macro that will
be printed only on failure. For example:
/* int a, b, c; */
int d = a + b;
ASSERT_EQ(c, d, "Something something %d + %d\n", a, b);Tests can be disabled from running (but still built) by prefixing the
test name with DISABLED_.
This behaves exactly the same as
GoogleTest.
There are several key distinctions between the EZTest API and that
of GoogleTest.
In no particularly the notable differences are:
*Support/fix is planned.
-
*The following macros are unimplemented in
EZTest:TEST_FTEST_PSCOPED_TRACE{ASSERT|EXPECT}_THROW{ASSERT|EXPECT}_ANY_THROW{ASSERT|EXPECT}_NO_THROW{ASSERT|EXPECT}_NO_FATAL_FAILURE{ASSERT|EXPECT}_PRED1{ASSERT|EXPECT}_PRED2{ASSERT|EXPECT}_PRED3{ASSERT|EXPECT}_PRED4{ASSERT|EXPECT}_PRED5
-
There is no test
Fixtureto inherit from inEZTest. -
EZTestusesC-styleprinting as opposed toC++-styleoperator<<printing. -
*There are no
DeathTestsinEZTest. -
*There is an environment variable / commandline support in
EZTest. This is relevant to- Test filtering.
- Test repeating
- Test shuffling
- Test listing
- etc...
-
*There are no toggles for modify test printouts in
EZTest. -
*There is no support for generating an XML/JSON files for the test results in
EZTest. -
*There are slight differences in the printout format.
-
*No global variables for changing behavior
- Instead there are some macros.
There are some macros that can be defined which will change the
behavior/compilation of
eztest.h.
For the most part, the defaults should be fine.
The following macros can be defined to change behavior.
EZTEST_ULP_PRECISIONdefault:4- This will change the
ULPfor both{ASSERT|EXPECT}_{FLOAT|DOUBLE}_EQ
EZTEST_FLOAT_ULP_PRECISIONdefault:EZTEST_ULP_PRECISION- This will change the
ULPfor both{ASSERT|EXPECT}_FLOAT_EQ
EZTEST_DOUBLE_ULP_PRECISIONdefault:EZTEST_ULP_PRECISION- This will change the
ULPfor both{ASSERT|EXPECT}_DOUBLE_EQ
EZTEST_C_PRINT_ARGSdefault:1- If set to
0and using theClanguage,eztestwill stop trying to print arguments on failure.
EZTEST_VERBOSITYdefault:0- Increase value to include more internal printouts (mostly just non-fatal warnings).
The following macros can be defined to change compilation.
-
EZTEST_DISABLE_WARNINGSdefault:1- If set,
eztestwill enablePragmasto disable known, inevitable, warnings. These warnings mostly only show up if compiling withClang's -Weverythingor an excessive amount ofGCCwarnings. If compiling with-Wall -Wextra -Wpedanticthe only warning that may show up is-Wunused-functiondepending on the set ofASSERT/EXPECTchecks used. The total set of disabled warnings are:-Waggregate-returns-Wcxx98-compat-pedantic-Wcxx98-compat-Wdouble-promotion-Wfloat-equal-Wformat-nonliteral-Wglobal-constructors-Wpadded-Wunsafe-buffer-usage-Wunsafe-buffer-usage-in-libc-call-Wunused-function-Wunused-member-function-Wunused-result-Wunused-template
- Note the
Pragmasare used minimally and will never apply to your own code.
-
EZTEST_STRICT_NAMESPACEdefault:0- If set, then general
{ASSERT|EXPECT}_*andTEST*macros will be prefixed withEZTEST_
There are some system requirements. Some of these may not be hard-requirements, but at the very least they are untested.
Linux is the only tested OS at the moment. It's probably that any
unix based system would work.
GCC/Clang are the only tested compilers are the moment.
- If
CC99or newer- Support for
__attribute__((constructor))
- If
C++C++11or newer
If compiling without posix specification
- x86-64
- x86-32
- arm
- aarch64
- riscv64
- riscv32
Otherwise any target should work.
The source code is located in
src/eztest.
The source code and includable
eztest.h
files seperate. The actual
eztest.h
header is autogenerated. This is a convenience to make development
simpler.
To generate the
eztest.h
header file, we use the
scripts/freeze.py
script.
Generally there is no need to invoke the script directly. It will
either be invoked by cmake as needed for tests/installation, or by
the wrapper script
quick-regen.py.
Tests are located in the
tests
directory.
To build the tests you can run:
mkdir build && cd build
cmake .. -DEZTEST_BUILD_TESTS=ON
make check-all # Run "unit tests"
make check-external # Run "integration tests"
make run-static-analysis # Run clang-tidyThe general cmake compiler/flags/language arguments will also
apply. As well there are toolchain files are testing cross
compilation.
To see some examples of the internal tests usage see .github/workflows/ci.yaml`.