This guide provides comprehensive instructions for building qtty-cpp, running tests, and troubleshooting common issues.
Table of Contents
- Prerequisites
- Quick Start
- Build Process Details
- Running Tests
- Testing Strategy
- Troubleshooting
- Advanced Build Options
Prerequisites
Required Tools
| Tool | Minimum Version | Purpose |
| CMake | 3.15+ | Build system orchestration |
| C++ Compiler | C++17 support | Compiling C++ code (GCC 7+, Clang 5+, MSVC 2017+) |
| Cargo/Rust | Latest stable | Building the qtty-ffi Rust library |
| Git | Any recent | Cloning repository and submodules |
Operating System Support
- Linux: Fully supported (tested on Ubuntu, Fedora, Arch)
- macOS: Fully supported (tested on macOS 10.15+)
- Windows: Supported with MSVC (MinGW/Cygwin may work but untested)
Internet Connection
First build requires internet access to:
- Fetch Google Test framework (via CMake FetchContent)
- Download Rust dependencies (via Cargo)
Subsequent builds use cached dependencies.
Quick Start
1. Clone Repository
git clone --recurse-submodules <repository-url>
cd qtty-cpp
Important: The --recurse-submodules flag is required to fetch the qtty-ffi Rust library.
If you already cloned without submodules:
git submodule update --init --recursive
2. Build
cmake -S . -B build
cmake --build build --parallel
On Windows with MSVC:
cmake -S . -B build
cmake --build build --config Release
3. Run Tests
# Using CTest (recommended)
ctest --test-dir build --output-on-failure
# Or run test executable directly
./build/test_ffi
4. Run Demo
Expected output:
=== qtty-cpp Demo ===
FFI ABI Version: 1
1. Distance Conversion:
Distance: 1500 m
Distance: 1.5 km
2. Using Literals:
Height: 10.5 m
Width: 5 m
Sum: 15.5 m
...
Build Process Details
The build orchestrates Cargo and CMake. Understanding this flow helps diagnose issues.
Build Pipeline
Step 1: Header Generation
- Cargo runs the Rust `gen_cpp_units` binary
- Parses qtty/qtty-ffi/units.csv
- Generates include/qtty/units/*.hpp
- Generates include/qtty/literals.hpp
Step 2: Rust Library Compilation
- Cargo builds qtty-ffi crate
- Output: qtty/target/release/libqtty_ffi.{so,dylib,dll}
- Depends on Step 1 (CMake dependency)
Step 3: C++ Integration
- CMake creates interface library qtty_cpp
- Links against qtty_ffi
- Sets include paths
- Configures RPATH for runtime library location
Step 4: Build Executables
- demo executable
- test_ffi executable (links Google Test)
CMake Targets
Run individual build steps:
# Generate C++ headers only
cmake --build . --target gen_cpp_units
# Build Rust library only
cmake --build . --target build_qtty_ffi
# Build demo only
cmake --build . --target demo
# Build tests only
cmake --build . --target test_ffi
What Gets Built
After a successful build, the build/ directory contains:
build/
demo # Demo executable
test_ffi # Test executable
lib/ # (empty, INTERFACE library)
_deps/googletest-build/ # Google Test build artifacts
The Rust library is built in the submodule:
qtty/target/release/
libqtty_ffi.so (Linux)
libqtty_ffi.dylib (macOS)
qtty_ffi.dll (Windows)
Running Tests
Using CTest (Recommended)
CTest integrates with Google Test for flexible test execution:
cd build
# Run all tests
ctest
# Verbose output
ctest -V
# Show test output only on failure
ctest --output-on-failure
# Run tests matching a pattern
ctest -R Length
# Run tests in parallel
ctest -j8
Direct Test Execution
Run the test binary directly for more Google Test options:
cd build
# Run all tests
./test_ffi
# List available tests
./test_ffi --gtest_list_tests
# Run specific test suite
./test_ffi --gtest_filter=LengthDimensionTest.*
# Run specific test
./test_ffi --gtest_filter=LengthDimensionTest.MeterConstruction
# Run multiple patterns
./test_ffi --gtest_filter=*Conversion*:*Comparison*
# Exclude tests
./test_ffi --gtest_filter=-*Precision*
# Repeat tests (useful for flaky test detection)
./test_ffi --gtest_repeat=100
# Show detailed output
./test_ffi --gtest_verbose
# Run with color output
./test_ffi --gtest_color=yes
Test Output Format
Successful test run:
[==========] Running 37 tests from 9 test suites.
[----------] Global test environment set-up.
[----------] 5 tests from LengthDimensionTest
[ RUN ] LengthDimensionTest.MeterConstruction
[ OK ] LengthDimensionTest.MeterConstruction (0 ms)
[ RUN ] LengthDimensionTest.MeterArithmetic
[ OK ] LengthDimensionTest.MeterArithmetic (0 ms)
...
[----------] 5 tests from LengthDimensionTest (1 ms total)
...
[==========] 37 tests from 9 test suites ran. (12 ms total)
[ PASSED ] 37 tests.
Failed test example:
[ RUN ] LengthDimensionTest.MeterArithmetic
/path/to/test_length.cpp:17: Failure
Expected equality of these values:
sum.value()
Which is: 12
13.0
[ FAILED ] LengthDimensionTest.MeterArithmetic (1 ms)
Testing Strategy
The qtty-cpp test suite is organized into nine test suites covering different aspects of the library.
Test File Organization
Location: tests/
Test Suite Descriptions
LengthDimensionTest
Tests length units including metric, imperial, and astronomical scales.
Key Tests:
- MeterConstruction - Constructor and literal creation
- MeterArithmetic - Addition, subtraction, multiplication, division
- MeterComparison - All comparison operators
- LengthUnitConversions - Meter <-> Kilometer, millimeter, inch, mile
- AstronomicalLengthUnits - AU, light-year, parsec conversions
- SmallLengthUnits - Nanometer, micrometer conversions
Example Test (from test_length.cpp):
TEST_F(LengthDimensionTest, LengthUnitConversions) {
Meter m(1000.0);
Kilometer km = m.
to<Kilometer>();
EXPECT_NEAR(km.value(), 1.0, 1e-12);
Meter m2 = km.
to<Meter>();
EXPECT_NEAR(m2.value(), 1000.0, 1e-9);
auto inch_val = 12.0_in;
Centimeter cm = inch_val.
to<Centimeter>();
EXPECT_NEAR(cm.value(), 30.48, 1e-9);
}
Quantity< typename ExtractTag< TargetType >::type > to() const
TimeDimensionTest
Tests time units from nanoseconds to days.
Key Tests:
- SecondConstruction - Constructor and literal creation
- TimeUnitConversions - Second <-> Minute, Hour, Day
- SmallTimeUnits - Millisecond, microsecond, nanosecond
- CompoundTimeOperations - Arithmetic operations
AngularDimensionTest
Tests angular units including degrees, radians, arcminutes, and arcseconds.
Key Tests:
- RadianConstruction - Constructor and literal creation
- AngularConversions - Radian <-> Degree conversions
- ArcminArcSecConversions - Arcminute and arcsecond conversions
- AngularArithmetic - Addition and scalar operations
MassDimensionTest
Tests mass units in metric and imperial systems.
Key Tests:
- KilogramConstruction - Constructor and literal creation
- MassUnitConversions - Kilogram <-> Gram, Ton, Pound
- SmallMassUnits - Milligram conversions
- MassComparison - Comparison operators
PowerDimensionTest
Tests power units including watts and horsepower.
Key Tests:
- WattConstruction - Constructor and literal creation
- PowerUnitConversions - Watt <-> Kilowatt, Megawatt, Horsepower
- PowerArithmetic - Arithmetic operations
- PowerComparison - Comparison operators
DerivedQuantityTest
Tests compound units created by dividing quantities.
Key Tests:
- VelocityCreation - Creating velocity from length/time division
Example (from test_derived.cpp):
TEST_F(DerivedQuantityTest, VelocityCreation) {
Meter distance(100.0);
Second time(20.0);
auto velocity = distance / time;
EXPECT_NEAR(velocity.value(), 5.0, 1e-12);
Velocity<Meter, Second> v1(10.0);
EXPECT_EQ(v1.value(), 10.0);
}
QuantityOperationsTest
Tests arithmetic, utility, and compound assignment operations across all unit types.
Key Tests:
- Addition - Same-unit addition
- Subtraction - Same-unit subtraction
- ScalarMultiplication - Quantity * scalar
- ScalarDivision - Quantity / scalar
- CompoundAssignment - +=, -=, *=, /=
- UnaryMinus - Negation operator
- AbsoluteValue - abs() method
- Comparison - All comparison operators
DimensionSafetyTest
Tests that the library properly throws exceptions for invalid operations.
Key Tests:
- ExceptionHandling - Verifies IncompatibleDimensionsError is thrown for dimension mismatches
Example (from test_dimension_safety.cpp):
TEST_F(DimensionSafetyTest, ExceptionHandling) {
Meter m(100.0);
EXPECT_THROW({
qtty_quantity_t qty;
qtty_quantity_make(100.0, UNIT_ID_METER, &qty);
qtty_quantity_t result;
int32_t status = qtty_quantity_convert(qty, UNIT_ID_SECOND, &result);
check_status(status, "Invalid conversion");
}, IncompatibleDimensionsError);
}
PrecisionEdgeCaseTest
Tests numerical precision and edge cases.
Key Tests:
- VerySmallValues - Nanometer-scale values
- VeryLargeValues - Light-year and parsec scales
- NearZeroOperations - Operations near zero
- PrecisionMaintenance - Multiple conversion roundtrips
- NegativeValues - Negative quantity operations
- ZeroComparison - Comparisons with zero
Test Fixtures
Location: tests/fixtures.hpp
All test suites derive from QttyTest base class:
class QttyTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
};
class LengthDimensionTest : public QttyTest {};
class TimeDimensionTest : public QttyTest {};
The fixtures use the Google Test framework and import qtty symbols:
Testing Philosophy
- Comprehensive Coverage: Every unit type has tests for construction, conversion, and operations
- Precision Testing: Use EXPECT_NEAR with appropriate tolerance for floating-point comparisons
- Edge Cases: Test boundary conditions (very large/small values, zero, negative)
- Type Safety: Verify compile-time type safety prevents invalid operations
- Error Handling: Test exception throwing for invalid operations
- Real-World Scenarios: Use realistic conversion factors (e.g., 1 inch = 2.54 cm exactly)
Adding New Tests
To add a test:
- Choose appropriate test file (or create new one)
- Add test using Google Test TEST_F macro
- Update CMakeLists.txt if adding new file (lines 108-118)
- Run tests to verify
Example:
TEST_F(LengthDimensionTest, MyNewTest) {
EXPECT_NEAR(km.
value(), 0.1, 1e-12);
}
constexpr double value() const
Quantity< MeterTag > Meter
Quantity< KilometerTag > Kilometer
Troubleshooting
Issue: Submodule Not Found
Error:
CMake Error: qtty/qtty-ffi/units.csv not found
Solution:
git submodule update --init --recursive
cd qtty
cargo build -p qtty-ffi --release
cd ..
Issue: Cargo Not Found
Error:
CMake Error: Could not find cargo
Solution:
Install Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
Or specify Cargo path:
cmake -DCARGO_BIN=/path/to/cargo ..
Issue: C++17 Not Supported
Error:
error: This file requires compiler and library support for the ISO C++ 2017 standard.
Solution:
Update compiler:
# Ubuntu
sudo apt install g++-9
export CXX=g++-9
# macOS
brew install llvm
export CXX=/usr/local/opt/llvm/bin/clang++
Or specify compiler:
cmake -DCMAKE_CXX_COMPILER=g++-9 ..
Issue: Shared Library Not Found at Runtime
Error:
./demo: error while loading shared libraries: libqtty_ffi.so: cannot open shared object file
Solution:
The build should automatically set RPATH, but if it fails:
Linux:
export LD_LIBRARY_PATH=$PWD/../qtty/target/release:$LD_LIBRARY_PATH
./demo
macOS:
export DYLD_LIBRARY_PATH=$PWD/../qtty/target/release:$DYLD_LIBRARY_PATH
./demo
Or rebuild with explicit RPATH:
cmake -DCMAKE_BUILD_RPATH=$PWD/qtty/target/release ..
make
Issue: Generated Headers Missing
Error:
fatal error: qtty/units/length.hpp: No such file or directory
Solution:
Manually run generator:
cargo run --manifest-path gen_cpp_units/Cargo.toml --release -- \
qtty/qtty-ffi/units.csv include/qtty
Or rebuild the generation target:
cmake --build build --target gen_cpp_units
Check that the unit table exists:
ls qtty/qtty-ffi/units.csv
If missing, build the FFI library:
cd qtty
cargo build -p qtty-ffi --release
cd ..
Issue: Test Failures
Error:
Expected equality of these values:
km.value()
Which is: 1.0000000001
1.0
Diagnosis:
Floating-point precision issue. Tests use EXPECT_NEAR with tolerance:
EXPECT_NEAR(km.value(), 1.0, 1e-12);
Solution:
If tests fail due to precision, check:
- Platform differences (x86 vs ARM, 32-bit vs 64-bit)
- Compiler optimization levels
- Math library implementations
Adjust tolerance if necessary (but investigate root cause first).
Issue: Google Test Download Fails
Error:
CMake Error at build/_deps/googletest-subbuild/CMakeLists.txt:
Failed to download https://github.com/google/googletest/...
Solution:
Check internet connection. If behind proxy:
export http_proxy=http://proxy:port
export https_proxy=http://proxy:port
cmake ..
Or download Google Test manually and use FetchContent_Declare with local path.
Issue: Build Artifacts from Previous Build
Symptom: Strange build errors or outdated behavior
Solution:
Clean build:
rm -rf build
mkdir build && cd build
cmake ..
make -j$(nproc)
Clean Cargo artifacts:
cd qtty
cargo clean
cd ..
rm -rf build
Advanced Build Options
Build Types
# Debug build (default)
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Release build (optimized)
cmake -DCMAKE_BUILD_TYPE=Release ..
# Release with debug info
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
# Minimum size release
cmake -DCMAKE_BUILD_TYPE=MinSizeRel ..
Compiler Selection
# Use Clang
cmake -DCMAKE_CXX_COMPILER=clang++ ..
# Use specific GCC version
cmake -DCMAKE_CXX_COMPILER=g++-11 ..
# Windows MSVC
cmake -G "Visual Studio 16 2019" ..
Parallel Builds
# Make (Linux/macOS)
make -j$(nproc) # Linux: use all cores
make -j$(sysctl -n hw.ncpu) # macOS: use all cores
make -j8 # Use 8 jobs
# CMake (cross-platform)
cmake --build . --parallel 8
# Ninja (if using Ninja generator)
ninja -j8
Installation
# Install to /usr/local (requires sudo)
sudo cmake --install .
# Install to custom prefix
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local ..
cmake --build .
cmake --install .
Installed files:
<prefix>/include/qtty/
qtty.hpp
ffi_core.hpp
literals.hpp
units/*.hpp
<prefix>/include/qtty_ffi.h
Verbose Build Output
# CMake verbose
cmake --build . --verbose
# Make verbose
make VERBOSE=1
# See exact commands
cmake --build . -- VERBOSE=1
Custom Generator Invocation
Run generator manually with custom output:
cargo run --manifest-path gen_cpp_units/Cargo.toml --release -- \
qtty/qtty-ffi/units.csv include/qtty
The generator looks for:
Using qtty-cpp in Your Project
Method 1: CMake subdirectory (recommended)
# In your CMakeLists.txt
add_subdirectory(path/to/qtty-cpp)
target_link_libraries(your_target PRIVATE qtty_cpp)
Method 2: CMake install + find_package
# After installing qtty-cpp
find_package(qtty_cpp REQUIRED)
target_link_libraries(your_target PRIVATE qtty_cpp)
Method 3: Manual integration
Copy installed headers to your include path and link against libqtty_ffi:
g++ -std=c++17 -I/path/to/qtty-cpp/include \
-I/path/to/qtty-ffi/include \
-L/path/to/qtty/target/release \
-lqtty_ffi \
mycode.cpp -o myapp
See Also