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.
EZTest
tests do not link against the C++ runtime library (libc++.so
/libstdc++.so
). This makes compiling the tests with-fsanitizer=memory
trivial.EZTest
runs 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.EZTest
is 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 install
Then 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.h
in 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
./test
Tests 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
arg
istrue
- Checks the
{ASSERT|EXPECT}_FALSE(arg:bool)
- Checks the
arg
isfalse
- 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:
bool
is an type that supports the!
operator.anyT
is any type that the specified operator is valid for.str
is one of the following:char *
std::string
(if usingC++
)std::string_view
(if usingC++17
or newer)
fp
is one of the following:float
double
Failure messages that print the variables exist in the following cases:
- You are using
C++
- You are using
C11
or 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_F
TEST_P
SCOPED_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
Fixture
to inherit from inEZTest
. -
EZTest
usesC-style
printing as opposed toC++-style
operator<<
printing. -
*There are no
DeathTests
inEZTest
. -
*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_PRECISION
default:4
- This will change the
ULP
for both{ASSERT|EXPECT}_{FLOAT|DOUBLE}_EQ
EZTEST_FLOAT_ULP_PRECISION
default:EZTEST_ULP_PRECISION
- This will change the
ULP
for both{ASSERT|EXPECT}_FLOAT_EQ
EZTEST_DOUBLE_ULP_PRECISION
default:EZTEST_ULP_PRECISION
- This will change the
ULP
for both{ASSERT|EXPECT}_DOUBLE_EQ
EZTEST_C_PRINT_ARGS
default:1
- If set to
0
and using theC
language,eztest
will stop trying to print arguments on failure.
EZTEST_VERBOSITY
default:0
- Increase value to include more internal printouts (mostly just non-fatal warnings).
The following macros can be defined to change compilation.
-
EZTEST_DISABLE_WARNINGS
default:1
- If set,
eztest
will enablePragmas
to disable known, inevitable, warnings. These warnings mostly only show up if compiling withClang's -Weverything
or an excessive amount ofGCC
warnings. If compiling with-Wall -Wextra -Wpedantic
the only warning that may show up is-Wunused-function
depending on the set ofASSERT
/EXPECT
checks 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
Pragmas
are used minimally and will never apply to your own code.
-
EZTEST_STRICT_NAMESPACE
default: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
C
C99
or newer- Support for
__attribute__((constructor))
- If
C++
C++11
or 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-tidy
The 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`.