Qiti is a lightweight C++20 library that brings profiling and instrumentation directly into your unit tests.
By integrating seamlessly with your test framework of choice, Qiti lets you track custom metrics and gather performance insights without ever leaving your test suite.
Qiti also provides optional Thread Sanitizer wrapper functionality: when enabled, tests can be run in isolation under TSan, automatically detecting data races and other thread-safety issues. You can even enforce custom thread-safety behavior right from your test code, catching concurrency bugs early in CI.
- Clang or Apple Clang (no other compiler is supported)*
- macOS (14 or 15, x86_64 and/or arm64)*
- Linux (tested on Ubuntu, Debian, and Fedora; x86_64)*
- Windows (experimental/work-in-progress; x86_64, ThreadSanitizer features not supported)*
- C++20
- CMake**
*see "Platform & Compiler Test Matrix" below **see "Manual Integration" below for instructions to integrate without CMake
Note: Qiti should not be linked in your final release builds. You will likely need to add a flag to your CMake invocation (e.g. -DQiti=1) to only link into your project when building your unit test executable, independent of your regular builds.
Qiti provides the qiti::ThreadSanitizer class with multiple detection capabilities:
createFunctionsCalledInParallelDetector()- Always available, uses function call trackingcreatePotentialDeadlockDetector()- Uses custom lock-order tracking on macOS, requires Clang ThreadSanitizer on LinuxcreateDataRaceDetector()- Requires Clang ThreadSanitizer, uses TSan for data race detection
To enable TSan-dependent functionality (createDataRaceDetector() and createPotentialDeadlockDetector() on Linux), add -DQITI_ENABLE_CLANG_THREAD_SANITIZER=ON to your CMake configuration:
cmake -B build . -DQITI_ENABLE_CLANG_THREAD_SANITIZER=ONWhen enabled, this adds ThreadSanitizer compiler flags (-fsanitize=thread, -fno-inline) and makes TSan-dependent functionality available.
Note: This option is not supported on Windows.
When building on macOS with QITI_ENABLE_CLANG_THREAD_SANITIZER=ON, do not build universal binaries (arm64 + x86_64). ThreadSanitizer is incompatible with universal binaries. Build for your target architecture only.
To integrate Qiti into your CMake-based project, add Qiti as a subdirectory and link against the qiti_lib target provided by the library:
# Add Qiti to your project
add_subdirectory(path/to/qiti)
# Create your unit test executable (e.g. Catch2)
add_executable(my_tests
tests/test_my_component.cpp
# etc.
)
# Link any intermediary libraries you build (and wish to profile/instrument) with qiti_lib
target_link_libraries(my_lib
PRIVATE
qiti_lib # General Qiti library target
Catch2::Catch2WithMain # (or your chosen test framework)
# etc.
)
# Link your final unit test executable with qiti_lib and qiti_tests_client
target_link_libraries(my_tests
PRIVATE
qiti_lib # General Qiti library target
qiti_tests_client # Qiti target specific to final executable
Catch2::Catch2WithMain # (or your chosen test framework)
# etc.
)By linking against qiti_lib, Qiti automatically propagates:
- Include directories:
./include - Core compiler flags (via
INTERFACE):-finstrument-functions(enable function instrumentation)-fno-omit-frame-pointer(preserve frame pointers)-g(generate debug symbols)
- ThreadSanitizer flags (when
QITI_ENABLE_CLANG_THREAD_SANITIZER=ON):-fsanitize=thread(enable Thread Sanitizer)-fno-inline(prevent inlining for TSan accuracy)
- ThreadSanitizer linker flags (when enabled):
-fsanitize=thread
You do not need to add these flags yourself—just ensure you are using Clang with C++20.
In addition, by linking your unit test executable with qiti_tests_client, Qiti automatically propagates:
- Object File:
./source/client/qiti_client_executable.cpp - Linker flags (via
INTERFACE):-rdynamic
For projects not using CMake, you'll need to manually build the Qiti library and configure your build system:
# Build qiti_lib shared library from all source/qiti_*.cpp files
clang++ -std=c++20 -shared -fPIC \
-finstrument-functions -fno-omit-frame-pointer -g \
-I./include -I./source \
source/qiti_*.cpp \
-o libqiti.so # or .dylib on macOSInclude in your project:
- Add
source/client/qiti_client_executable.cppto your test executable's source files
Required compiler flags:
-std=c++20-finstrument-functions -fno-omit-frame-pointer -g-I./path/to/qiti/include(forqiti_include.hpp)
Required linker flags:
-L./path/to/qiti -lqiti(link the Qiti library)-rdynamic
Optional ThreadSanitizer support:
- Add
-fsanitize=thread -fno-inline -DQITI_ENABLE_CLANG_THREAD_SANITIZER=1to both compiler and linker flags
- Clang compiler (Apple Clang or LLVM Clang)
- C++20 standard
- Include qiti_client_executable.cpp in your test executable
- Link against libqiti shared library
- Apply required compiler/linker flags above
For complex projects, consider using CMake's FetchContent to automatically handle Qiti integration.
- When compiling on macOS, the deployment target must be 10.15 (Catalina) or later.
Qiti is continuously tested across a comprehensive matrix of platforms, compilers, and configurations to ensure reliability and compatibility:
| Platform | OS Version | Compiler | Build System | ThreadSanitizer | Test Frameworks |
|---|---|---|---|---|---|
| macOS | 14 | Apple Clang (Xcode 15.4) | Xcode | ✅ Enabled | Catch2 + GTest |
| macOS | 15 | Apple Clang (Xcode 16.2) | Xcode | ✅ Enabled | Catch2 + GTest |
| macOS | 15 | LLVM Clang 16 | Ninja | ✅ Enabled | Catch2 + GTest |
| Ubuntu | Latest | LLVM Clang 16 | Ninja | ✅ Enabled | Catch2 + GTest |
| Ubuntu | Latest | LLVM Clang 16 | Ninja | ❌ Disabled | Catch2 + GTest |
| Debian | Stable | LLVM Clang 17 | Ninja | ✅ Enabled | Catch2 + GTest |
| Fedora | Latest | LLVM Clang (latest) | Ninja | ✅ Enabled | Catch2 + GTest |
| Windows | Latest | LLVM Clang 16 | Ninja | ❌ Disabled | Catch2 |
Additional CI Validations:
- Code Quality: CPPLint validation with custom filters
- Architecture: x86_64 (macOS: also arm64)
- Build Type: Release builds across all configurations
- C++ Standard: C++20 compliance validation
This comprehensive testing matrix ensures Qiti works reliably across the development environments and deployment targets most commonly used for C++ unit testing and profiling workflows.
📖 API Documentation
Complete API documentation is included in this repository. To view it:
- Clone or download this repository
- Open
docs/html/index.htmlin your browser - Browse the full API reference, including all classes, functions, and usage examples
In your unit test file, add
#include "qiti_include.hpp"Then in each unit test you wish to use Qiti, add a qiti::ScopedQitiTest at the top of the test. For example:
TEST_CASE("Example Test")
{
qiti::ScopedQitiTest test;
// your test code here
}Even if you don't call any functions of ScopedQitiTest directly, instantiating it still enables most of the functionality of Qiti and cleans up the state once the test ends.
Number of times called.
TEST_CASE("Example Test")
{
qiti::ScopedQitiTest test;
// Profile one of your functions
auto funcData = qiti::FunctionData::getFunctionData<&myFunc>();
REQUIRE(funcData != nullptr);
// Call twice
myFunc();
myFunc();
// Enforce that it was indeed called twice
CHECK(funcData->getNumTimesCalled() == 2);
}Enforce number of heap allocations.
TEST_CASE("Example Test")
{
qiti::ScopedQitiTest test;
// Profile one of your functions
auto funcData = qiti::FunctionData::getFunctionData<&myFunc>();
REQUIRE(funcData != nullptr);
// Call function
funcData();
// Get information on last function call
auto lastFunctionCall = funcData->getLastFunctionCall();
// Enforce that this function does not heap allocate
REQUIRE(lastFunctionCall.getNumHeapAllocations() == 0);
}Enforce length of test.
TEST_CASE("Example Test")
{
qiti::ScopedQitiTest test;
// test code here
// Ensure that the test does not take too long
REQUIRE(test.getLengthOfTest_ms() < 10);
}Detect data races (requires QITI_ENABLE_CLANG_THREAD_SANITIZER=ON).
#ifdef QITI_ENABLE_CLANG_THREAD_SANITIZER
TEST_CASE("Example Test")
{
qiti::ScopedQitiTest test;
auto dataRaceDetector = qiti::ThreadSanitizer::createDataRaceDetector();
auto codeToTest = []()
{
std::thread t([]
{
functionToRunOnThread0();
});
functionToRunOnThread1();
t.join();
};
dataRaceDetector->run(codeToTest);
REQUIRE(dataRaceDetector->passed()); // No data races detected
}
#endifPlease refer to the documentation for a full overview of all available features.
Qiti is licensed under the MIT License.