Last Updated: December 29, 2025
- Overview
- Test Suite Summary
- Building Tests
- Running Tests
- Test Organization
- Test Coverage by Component
- Code Coverage Metrics
- Test Design Principles
- Adding New Tests
- Common Testing Patterns
- Code Coverage Analysis
- Advanced Testing Features
- Performance Benchmarks
- Implementation History
- Continuous Integration
- Troubleshooting
- Best Practices
- Future Improvements
- Conclusion
The libcvc library uses Google Test (gtest) for comprehensive unit testing. The test infrastructure is modern, well-documented, and integrated seamlessly with CMake. Tests are enabled by default and cover core functionality, multithreaded operations, async programming, CUDA GPU acceleration, and real-world scenarios.
Key Features:
- 577 comprehensive tests with 100% pass rate
- Automatic Google Test integration via CMake FetchContent
- Code coverage analysis with lcov/gcov
- Multithreaded concurrency testing
- Futures API async programming tests
- CUDA GPU acceleration tests
- Real-world stress tests (SDF convergence, state_object patterns)
Last Updated: April 2026
| Component | Tests | Focus Areas |
|---|---|---|
| App | 72 | Application framework, data/property management, threading |
| State | 158 | State tree, hierarchies, signals, async operations, state_object pattern |
| Voxels | 133 | Volume data, algorithms, CUDA GPU paths |
| Volume | 29 | Spatial coordinates, interpolation, subvolumes |
| Geometry | 119 | Mesh operations, normals, I/O, SDF computation, isosurface extraction |
| Volume-Ops | 42 | Filtering, contrast enhancement, anisotropic diffusion, GDTV |
| HDF5 | 24 | .cvc file format round-trip and metadata |
| Procedural Geometry | typed fixtures | Templated parameterized tests over generated meshes |
Key Metrics:
- Success Rate: 100%
- Execution Time: ~280 seconds (includes intensive SDF convergence test)
- Coverage: Target 64.6% overall, 94.1% on core components
Known Issue: StateTest.StateObjectStateName passes its assertions but encounters a segfault during test cleanup. This is a timing-related destructor issue that doesn't affect functionality.
| Test Suite | Duration | Notable Tests |
|---|---|---|
| app_test | ~0.5s | 114 tests, fast unit tests |
| state_test | ~13s | 105 tests, includes multithreaded + futures + async |
| voxels_test | ~33s | 130 tests, includes CUDA tests (22) with performance benchmarks |
| volume_test | ~0.3s | 29 tests, spatial operations |
| geometry_test | ~0.4s | 50 tests, Stanford Bunny I/O, deep copy |
| algorithm_test | ~265s | 6 tests, BunnyVolumeConvergence is ~228s |
Required:
- CMake 3.15 or higher
- C++20 compatible compiler (GCC 13+, Clang 17+, MSVC 19.29+); C++23 selectable via
-DCMAKE_CXX_STANDARD=23 - Boost libraries (>= 1.58): thread, date_time, regex, filesystem, system, chrono
Optional (for coverage):
- lcov (for coverage report generation)
- gcov (usually included with GCC)
Tests are controlled by the CVC_BUILD_TESTS CMake option, which is ON by default.
# Disable tests
cmake -DCVC_BUILD_TESTS=OFF /path/to/libcvc
# Enable tests (default)
cmake -DCVC_BUILD_TESTS=ON /path/to/libcvc# Configure with tests enabled (default)
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release
# Build the project including tests
cmake --build build -j$(nproc)
# Or build only specific test executables
cmake --build build --target app_test state_testThe tests use GoogleTest 1.17.0, resolved by CMake/SetupGTest.cmake. No manual installation of Google Test is required.
- An installed package, if there is one.
find_package(GTest 1.17.0 CONFIG)searchesCMAKE_PREFIX_PATH. The cvcpkg catalog'sgoogletestis a build dependency ofcvcpkg/recipes/libcvc, so a prefix made withcvcpkg install-deps cvcpkg/recipes/libcvc --prefix <p>already has it, and configuring against<p>needs no network. - Otherwise, built from source. FetchContent downloads the same release tarball from GitHub (SHA-256 pinned) and builds it in the build tree.
The configure log names the one it used (-- GoogleTest 1.17.0: installed package (...) or ... building from source with FetchContent -- <reason>). Pass -DCMAKE_DISABLE_FIND_PACKAGE_GTest=ON to always build from source. With MSVC, an installed GoogleTest is used only when it matches the build's C runtime: a Release-only package is skipped for a Debug build, for instance, rather than mixing /MD and /MDd.
CTest is the standard way to run tests in CMake projects:
# Run all tests
cd build && ctest
# Run with verbose output on failure
ctest --output-on-failure
# Run tests in parallel
ctest -j$(nproc)
# Run specific test suite
ctest -R app_test
ctest -R state_test
ctest -R voxels_testA convenient check target is provided:
cmake --build build --target checkThis runs all tests and displays output on failure.
For more control and Google Test features:
# Run entire test suite
./build/bin/app_test
./build/bin/state_test
./build/bin/voxels_test
./build/bin/volume_test
./build/bin/geometry_test
./build/bin/algorithm_test
# Run with filters
./build/bin/app_test --gtest_filter=AppTest.DataSetAndGet
./build/bin/state_test --gtest_filter="*Futures*"
./build/bin/voxels_test --gtest_filter=VoxelsTest.BilateralFilter*
# List all tests
./build/bin/app_test --gtest_list_tests
./build/bin/state_test --gtest_list_tests
# Run tests multiple times (detect flakiness)
./build/bin/app_test --gtest_repeat=100
# Brief output (just pass/fail)
./build/bin/state_test --gtest_brief=1| File | Component | Lines | Tests |
|---|---|---|---|
src/cvc/tests/app_test.cpp |
cvc::app | ~1,400 | 114 |
src/cvc/tests/state_test.cpp |
cvc::state | ~3,400 | 105 |
src/cvc/tests/voxels_test.cpp |
cvc::voxels | ~3,520 | 130 |
src/cvc/tests/volume_test.cpp |
cvc::volume | ~800 | 29 |
src/cvc/tests/geometry_test.cpp |
cvc::geometry | ~900 | 50 |
src/cvc/tests/algorithm_test.cpp |
algorithms | ~600 | 6 |
src/cvc/tests/CMakeLists.txt |
Build config | ~60 | - |
Tests follow the Google Test pattern:
TEST(TestSuiteName, TestName) {
// Test implementation
}Naming Guidelines:
- Test suite name matches the class being tested (e.g.,
AppTest,StateTest) - Test name describes the specific behavior (e.g.,
DataSetAndGet,ConcurrentValueReads) - Use descriptive names that explain intent
Focus: Application singleton, data/property management, threading, utilities
Click to expand App test categories
- SingletonInstance
- DataSetAndGet (int, double, string, bool)
- DataRemove
- DataTypeInfo
- DataTypeName
- DataTypes (enumeration)
- DataTypeRegistry
- TypeRegistrationUniqueness
- RegisterDataType
- PropertySetAndGet
- PropertyRemove
- PropertyMap
- PropertyList operations (append, remove, unique)
- PropertyListAppendUnique
- MultipleProperties
- ThreadKeyGeneration
- ThreadKeyUniqueness
- ThreadInfo
- ThreadProgress
- ThreadProgressMultiple
- ThreadList
- MutexCreation
- MutexReuse
- MutexInfo
- Listify (vector ↔ string conversion)
- Sleep function
- ListifyRoundTrip
Coverage: 89.82% (441/491 lines in app.cpp)
Focus: Hierarchical state system, signals, async futures, state_object pattern
Click to expand State test categories
- SingletonInstance
- Value storage (string, int, double, bool)
- ValueCommaList, ValueConversion
- ValueListUnique
- DataSetAndGet
- DataTypeInt, DataTypeDouble
- Type-safe retrieval
- ChildCreation
- FullName, ParentName
- ChildrenListing, NumChildren
- ChildrenWithRegex
- DeepHierarchyNavigation
- OperatorChaining
- EmptyKeyHandling
- CommentSetAndGet
- HiddenFlag
- InitializedFlag
- LastModification
- Touch operations
- InitializedFlagBehavior
- PropertyTreeConversion
- PropertyTreeRoundTrip
- JsonConversion, JsonRoundtrip
- SaveAndRestore
- Traverse with callbacks
- TraverseWithRegex
- TraversalEnterExitSignals
- ValueChangedSignal
- DataChangedSignal
- ChildChangedSignal
- ConcurrentValueReads (10 threads, 1000 reads)
- ConcurrentValueWrites (10 threads)
- ConcurrentWritesToSameNode (20 threads)
- ConcurrentDataOperations (8 threads)
- ConcurrentSignalHandling
- ConcurrentHierarchyCreation
- ConcurrentTraversal
- ConcurrentResetOperations
- ConcurrentPropertyTreeOperations
- DeadlockDetectionValueAndSignal
- StateObjectMultithreaded (8 threads)
- StressTestCombinedOperations (2,162 operations/sec)
- ValueWithCallback
- WaitForValue (blocking)
- WaitForValueWithTimeout
- ValueFutureGet
- ValueFutureWaitFor
- ValueFutureGetFor
- DataWithCallback
- WaitForData
- WaitForDataWithTimeout
- MultipleFuturesOnSameState
- FutureProducerConsumerPattern
- StateObjectMultithreaded
- StateObjectConfiguration
- StateObjectDataProcessor
- StateObjectRenderer
- StateObjectAppSettings
- StateObjectExternalAccess
- StateObjectNestedPaths
- StateObjectStateName
- StateObjectMultipleInstances
- StateObjectWithData
- StateObjectAsyncHandlers
Coverage: 92.75% (243/262 lines in state.cpp)
Focus: Volume data structure, image processing algorithms, CUDA GPU acceleration
Click to expand Voxels test categories
- Default, dimension-based, type-specified, copy, pointer-based
- Linear indexing, 3D coordinates, bounds checking
- Expansion, reduction, type conversion, precision
- Auto-calculation, manual setting, all data types, large volumes
- Assignment, equality, fill, map, sub, resize
- Histogram, copy-on-write, composite, raw data access
- Shallow copy (default, shared data)
- Deep copy (independent allocation)
- Copy-on-write behavior
- All data types, large volumes
- All 30 conversion paths (6 types × 5)
- Precision loss handling
- Negative value handling
- Upsample, downsample, trilinear interpolation, non-uniform
- Range expansion/shrinking, negative ranges, identity
- Center, corner, single slice
- Bilateral filter (edge-preserving smoothing)
- Contrast enhancement (resistor propagation)
- Anisotropic diffusion (Perona-Malik model)
- GDTV filter (gradient-domain total variation)
- Add, subtract, partial overlap handling
- Out-of-bounds exceptions, uninitialized states, large volumes
- Device management (availability, info, selection, enable/disable)
- Memory operations (migration to/from GPU, multiple cycles, device switching)
- Operations on GPU (modify, fill, map, subvolume)
- Algorithms on GPU (bilateral filter, GDTV filter, min/max calculation)
- Integration tests (copy with CUDA, different data types, large volumes)
- Performance benchmarks (bilateral filter, GDTV filter with multiple configurations)
Coverage: 92.17% (306/332 lines in voxels.cpp)
Focus: Spatial coordinate system, trilinear interpolation, subvolumes
- Construction & Properties (8 tests)
- Interpolation (7 tests)
- Subvolumes (8 tests)
- Operations (6 tests)
Coverage: 91.18% (124/136 lines in volume.cpp)
Focus: Triangle mesh operations, normals, quality improvement, deep copy semantics, I/O
Click to expand Geometry test categories
- Default construction
- Copy construction and assignment
- Triangle and vertex counting
- Bounding box computation
- Normal vector computation
- Per-vertex normal averaging
- Empty geometry handling
- Extents calculation
- ShallowCopyDefault - Copy-on-write behavior (default)
- DeepCopy - Independent memory allocation with
copy(geom, true) - DeepCopyIndependence - Verify modifications don't affect original
Deep Copy Implementation:
// Shallow copy (default) - shares data via boost::shared_ptr
geometry g2;
g2.copy(g1); // or g2.copy(g1, false)
// Deep copy - independent allocation
geometry g2;
g2.copy(g1, true); // Creates new shared_ptr instancesWhen to Use Deep Copy:
- Parallel processing: Multiple threads need independent meshes
- Benchmarking: Avoid copy-on-write overhead in timing
- Known modifications: Data will diverge immediately
- Memory tracking: Explicit control over allocation timing
- Laplacian smoothing (vertex movement validation)
- Boundary-fixed smoothing
- Topology preservation during smoothing
- Triangle quality improvement (aspect ratio)
- Multiple smoothing iterations
- Surface projection after modification
- Merge operations (index reindexing)
- Triangle surface extraction
- Wire interior generation
- Normal inversion
- Topology validation
- OFF format read/write
- RAW format read/write
- Geometry serialization
- Stanford Bunny loading (real-world test: 34,835 vertices, 69,473 triangles)
Coverage: 79.00% (252/319 lines in geometry.cpp)
Focus: High-level algorithms (SDF, isosurfacing, convergence)
- SDFBasic - Basic SDF computation
- IsoBasic - Basic isosurface extraction
- IsoWithDifferentIsovalues - Multiple isovalue testing
- SDFThenIsoRoundtrip - Pipeline validation
- BunnySDF_IsoRoundtrip - Real-world test (Stanford Bunny, ~5.5s)
- BunnyVolumeConvergence - Stress test (32³→256³, ~239s)
Full Project (December 12, 2025):
- Lines: 64.6% (10,272 of 15,903 lines)
- Functions: 68.1% (6,848 of 10,056 functions)
Core Components Only:
- Lines: 94.1% (429 of 456 lines in headers)
- Functions: 91.2% (218/239 functions in headers)
| File | Line Coverage | Function Coverage | Status |
|---|---|---|---|
| state.cpp | 92.75% (243/262) | 94.7% (355/375) | ✅ Excellent |
| voxels.cpp | 92.17% (306/332) | 94.44% (17/18) | ✅ Excellent |
| volume.cpp | 91.18% (124/136) | - | ✅ Excellent |
| app.cpp | 89.82% (441/491) | - | ✅ Good |
| geometry.cpp | 79.00% (252/319) | - | ✅ Good |
Header Files (Template instantiations):
- geometry.h: 100%
- volume.h: 83.3%
- state.h: 64.0% (many template paths)
- app.h: 57.6%
- voxels.h: 26.5% (legacy code paths)
✅ Well Covered Areas:
- Core state management (value/data operations)
- Tree navigation and hierarchy
- Multithreaded concurrent access
- Futures API (blocking waits, callbacks, timeouts)
- State object pattern (11 comprehensive tests)
- Volume data structure (all 6 data types)
- Image processing algorithms (bilateral, diffusion, GDTV, contrast)
- CUDA GPU operations (20 dedicated tests)
- Geometry mesh operations
- SDF computation and convergence
- Complex template instantiation paths
- Error recovery scenarios
- I/O edge cases (corrupt files, invalid formats)
- Legacy compatibility code
- Some specialized algorithms
Each test is independent and cleans up after itself. The examples
below use app as a cvc::app instance owned by the test (typically
a fixture member or a file-scope test object):
TEST(StateTest, ValueSetAndGet) {
cvc::app app;
// Arrange
state::instance(app)("test.value").value("test");
// Act & Assert
EXPECT_EQ(state::instance(app)("test.value").value(), "test");
// Cleanup
state::instance(app)("test").reset();
}Tests use unique namespaces to avoid conflicts:
test.value.*- Value-related teststest.data.*- Data storage teststest.concurrent.*- Concurrency teststest.future.*- Futures API tests
The app and state classes are thread-safe. Tests include:
- Single-threaded behavior validation
- Concurrent access tests (12 tests)
- Deadlock detection
- Signal handling under load
Tests use realistic scenarios:
- Stanford Bunny mesh (34,834 triangles)
- Large volumes (256³ = 16.7M voxels)
- Stress tests (thousands of operations)
app_test.cpp- Forcvc::appfunctionalitystate_test.cpp- Forcvc::statefunctionalityvoxels_test.cpp- Forcvc::voxelsfunctionalityvolume_test.cpp- Forcvc::volumefunctionalitygeometry_test.cpp- Forcvc::geometryfunctionality- Create new file for new components
TEST(TestSuiteName, TestName) {
// Arrange: Set up test conditions
cvc::volume vol(dimension(10, 10, 10));
vol.fill(5.0);
// Act: Perform the operation
double value = vol(5, 5, 5);
// Assert: Verify results
EXPECT_DOUBLE_EQ(value, 5.0);
// Cleanup (if needed)
state::instance(app)("test").reset();
}Google Test provides various assertion macros:
EXPECT_EQ(a, b)- Values are equalEXPECT_NE(a, b)- Values are not equalEXPECT_TRUE(condition)- Condition is trueEXPECT_FALSE(condition)- Condition is falseEXPECT_LT/LE/GT/GE(a, b)- Comparison assertionsEXPECT_DOUBLE_EQ(a, b)- Floating point equalityEXPECT_THROW(statement, exception)- Exception testingASSERT_*versions - Stop test on failure
cmake --build build --target state_test
./build/bin/state_test --gtest_filter="*YourNewTest*"
ctest --output-on-failureTEST(StateTest, TypeConversionError) {
state::instance(app)("test").value(42);
EXPECT_THROW({
state::instance(app)("test").data<std::vector<int>>();
}, cvc::type_conversion_error);
}For tests requiring common setup:
class VoxelsTestFixture : public ::testing::Test {
protected:
void SetUp() override {
v = cvc::voxels(cvc::dimension(10, 10, 10), cvc::Float);
v.fill(0.0);
}
void TearDown() override {
// Cleanup if needed
}
cvc::voxels v;
};
TEST_F(VoxelsTestFixture, FillOperation) {
v.fill(42.0);
EXPECT_DOUBLE_EQ(v(5, 5, 5), 42.0);
}For testing multiple inputs:
class DataTypeTest : public ::testing::TestWithParam<cvc::DataType> {};
TEST_P(DataTypeTest, AllTypes) {
cvc::DataType type = GetParam();
cvc::voxels v(cvc::dimension(5, 5, 5), type);
EXPECT_EQ(v.voxelType(), type);
}
INSTANTIATE_TEST_SUITE_P(
AllDataTypes,
DataTypeTest,
::testing::Values(
cvc::UChar, cvc::UShort, cvc::UInt,
cvc::Float, cvc::Double, cvc::UInt64
)
);TEST(StateTest, ConcurrentValueReads) {
state::instance(app)("test.concurrent").value("initial");
std::atomic<int> success_count(0);
std::vector<boost::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&success_count]() {
for (int j = 0; j < 100; ++j) {
std::string val = state::instance(app)("test.concurrent").value();
if (!val.empty()) success_count++;
}
});
}
for (auto& t : threads) t.join();
EXPECT_EQ(success_count.load(), 1000);
state::instance(app)("test").reset();
}The easiest way to generate coverage:
./generate_coverage.shThis script automatically:
- Configures build with coverage enabled
- Builds the project
- Runs all tests
- Generates HTML coverage report
- Shows coverage summary
The report will be in build-coverage/coverage_html/index.html.
lcov comes from cvcpkg, like everything else libcvc builds with: lcov 2.0 on cvcpkg's perl, with the Perl modules it needs. gcov comes with the compiler.
cvcpkg install lcov --prefix ~/cvc-tools --config release --link shared
export PATH="$HOME/cvc-tools/bin:$PATH" # or pass -DLCOV_PATH/-DGENHTML_PATH# Step 1: Configure with coverage enabled
cmake -B build-coverage -S . \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage" \
-DCVC_ENABLE_COVERAGE=ON \
-DCVC_BUILD_TESTS=ON
# Step 2: Build the project
cmake --build build-coverage -j$(nproc)
# Step 3: Generate coverage report
cmake --build build-coverage --target coverage
# Step 4: View the report
xdg-open build-coverage/coverage_html/index.htmlcoverage- Runs tests and generates HTML reportcoverage-view- Opens the coverage report in a browser
The HTML report shows:
- Line Coverage: Percentage of code lines executed
- Function Coverage: Percentage of functions called
- Branch Coverage: Percentage of conditional branches taken
Color coding:
- Green: Code executed by tests
- Red: Code not executed
- Orange: Partially executed (branches)
- Initial Coverage: Run coverage on current tests
- Identify Gaps: Look for red (uncovered) sections
- Add Tests: Write tests for uncovered code
- Re-run Coverage: Verify improvement
- Iterate: Repeat until target coverage reached
CI enforces a hard line-coverage gate: the package-linux / libcvc-debug job fails when line coverage of the supported in-repo
surface drops below COVERAGE_MIN (80%), set at the top of
.github/workflows/ci.yml. A PR that adds enough untested code to
drag the project below the gate cannot merge without also adding
tests.
Additional (advisory) targets:
- Critical Paths: 100% coverage
- New code in a PR: cover what you add; don't rely on headroom
The gate and the reports measure the same, repo-only view. The filter
is an allowlist of src/ + inc/ (a /usr/* blocklist would still
count Boost/CGAL/HDF5 headers from the libcvc-deps prefix that are
inlined into instrumented TUs), minus:
- Test code (
*/test/*,*/tests/*,*_test.cpp) - Vendored XmlRpc++ (
src/xmlrpc/) - Vendored contour-spectrum code (
src/cvc/geometry/cvc-mesher/contour/), perdocs/roadmap/COVERAGE_IMPROVEMENT_PLAN.md§4 Option A - Vendored stb single-file headers (
src/cvc/image/third_party/)
The local coverage CMake target applies the identical filter, so
numbers from ./generate_coverage.sh are directly comparable to the
CI gate.
cd build-coverage
# Clear previous data
lcov --directory . --zerocounters
# Run specific tests
./bin/state_test --gtest_filter="*Futures*"
# Capture and generate report
# --ignore-errors negative,gcov,mismatch: geninfo can emit a spurious
# negative count for inlined STL symbols (a gcov quirk) and gcov can fail
# on toolchain version skew; neither should abort the capture.
lcov --directory . --capture --output-file coverage.info --ignore-errors negative,gcov,mismatch
lcov --remove coverage.info '/usr/*' '*/test/*' -o coverage_filtered.info
genhtml coverage_filtered.info -o coverage_htmlThe test suite includes comprehensive multithreaded tests to validate concurrent access:
./bin/state_test --gtest_filter="*Concurrent*:*Deadlock*:*Stress*"Test Coverage:
- ConcurrentValueReads - 10 threads, 1,000 reads each
- ConcurrentValueWrites - 10 threads writing different nodes
- ConcurrentWritesToSameNode - 20 threads, high contention
- ConcurrentDataOperations - 8 reader/writer threads
- ConcurrentSignalHandling - Signal propagation under load
- ConcurrentHierarchyCreation - Parallel tree building
- ConcurrentTraversal - Tree traversal during modifications
- ConcurrentResetOperations - Reset mixed with read/write
- ConcurrentPropertyTreeOperations - Serialization under modifications
- DeadlockDetectionValueAndSignal - Reentrancy testing
- StateObjectMultithreaded - CRTP pattern with 8 threads
- StressTestCombinedOperations - 2,162 operations in 1 second
Async programming patterns with the futures API:
./bin/state_test --gtest_filter="*Futures*:*Future*"Features Tested:
- Blocking waits for values/data
- Timeout exceptions (
cvc::timeout_error) - Callback registration and firing
state_future<T>template class- Producer-consumer patterns
- Multiple waiters on same state
Example:
TEST(StateTest, WaitForValue) {
boost::thread writer([]() {
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
state::instance(app)("test.future.wait").value(12345);
});
int val = state::instance(app)("test.future.wait").wait_for_value<int>();
EXPECT_EQ(val, 12345);
writer.join();
}CRTP-based state object pattern for automatic state management:
./bin/state_test --gtest_filter="*StateObject*"Test Coverage:
- Configuration management (window size, fullscreen)
- Data processing (status tracking, error counting)
- Renderer state (camera, render mode)
- Application settings (theme, language)
- External state access via per-app
cvc::state::instance(app) - Nested state paths
- Multiple instances with unique state paths
- Boost::any data storage
- Async handler execution
GPU acceleration testing (requires CUDA-capable GPU):
./bin/voxels_test --gtest_filter="*CUDA*"Test Categories:
- Device Management (4 tests)
- Memory Operations (4 tests)
- Operations on GPU (4 tests)
- Algorithms on GPU (2 tests)
- Integration Tests (3 tests)
- Large Volume Performance (3 tests)
BunnyVolumeConvergence - Most intensive test:
- Progressive resolutions: 32³ → 64³ → 128³ → 256³
- Volume estimation via interior voxel counting
- Convergence verification across resolutions
- ~239 seconds execution time
- Real-world performance benchmark
Thread-Safe Refactoring (v2.0):
- Before optimization: ~2600 seconds (43+ minutes) at 256³
- After optimization: ~234 seconds (3.9 minutes) at 256³
- Performance gain: 11x speedup 🚀
Optimization Techniques:
- Cell reference caching in octree building
- Cell reference caching in ray casting
- Unconditional BOOST_DISABLE_ASSERTS
| Resolution | Voxels | Time | Memory | Performance |
|---|---|---|---|---|
| 32³ | 32,768 | ~0.05s | ~10 MB | Excellent |
| 64³ | 262,144 | ~0.2s | ~50 MB | Excellent |
| 128³ | 2,097,152 | ~1.5s | ~250 MB | Good |
| 256³ | 16,777,216 | ~15s | ~1.5 GB | Acceptable |
Performance scales approximately O(n) for fixed triangle count with optimizations.
| Build Type | Optimization | SDF 256³ Time | Speedup |
|---|---|---|---|
| Release | -O3 -DNDEBUG |
~220s | 1.0x (baseline) |
| RelWithDebInfo | -O2 -g -DNDEBUG |
~234s | 0.94x |
| Debug | -g (no opt) |
~3027s | 0.07x |
| Debug + Coverage | -g --coverage |
~3030s | 0.07x |
Recommendation: Use RelWithDebInfo for development/testing.
GDTV Filter CUDA Acceleration:
The GDTV (Gradient-Domain Total Variation) filter shows exceptional GPU speedup due to its fully parallelizable Jacobi iteration pattern:
| Configuration | CPU Time (ms) | GPU Time (ms) | Speedup | Max Diff | Status |
|---|---|---|---|---|---|
| 32³, 5 iter | 229.6 | 12.8 | 17.9x | 3.05e-05 | ✅ |
| 64³, 5 iter | 917.0 | 15.1 | 60.9x | 3.05e-05 | ✅ |
| 32³, 20 iter | 352.7 | 12.5 | 28.2x | 3.05e-05 | ✅ |
| 64³, 20 iter | 3055.6 | 36.1 | 84.7x | 3.05e-05 | ✅ |
| 128³, 5 iter | 8301.8 | 138.3 | 60.0x | 3.05e-05 | ✅ |
| 128³, 10 iter | 18741.2 | 217.8 | 86.1x | 3.05e-05 | ✅ |
Key Insights:
- Speedup scales with iterations: More iterations = higher speedup (20 iterations shows 28-85x vs 5 iterations shows 18-61x)
- Speedup scales with volume size: Larger volumes = better GPU utilization (128³ > 64³ > 32³)
- Perfect numerical accuracy: GPU results match CPU within 3×10⁻⁵ (negligible floating-point differences)
- GPU timing includes memory transfer: Speedup would be even higher for pure computation
- Best-in-class GPU candidate: GDTV is the most GPU-friendly algorithm in the voxels library
Why GDTV Shows Exceptional Speedup:
- Both gradient computation AND filtering are GPU-accelerated
- Each iteration is fully parallelizable (Jacobi pattern - no data dependencies)
- Iteration count multiplies benefit (20 iterations = 20× parallelizable work)
- Memory-bound operation benefits from GPU memory bandwidth (350+ GB/s vs ~50 GB/s CPU)
- Thousands of independent computations per iteration
Bilateral Filter CUDA Acceleration:
Bilateral filter also shows excellent GPU performance:
- Small volumes (32³): ~17x speedup
- Medium volumes (64³): ~20x speedup
- Large volumes (128³): ~25x speedup
- Numerical accuracy: perfect match with CPU implementation
Contrast Enhancement: NOT CUDA-accelerated
- Reason: 2/3 of algorithm (propagation phase) is inherently sequential
- Only 1/3 (stretching phase) is parallelizable - not worth the complexity
- Documentation explains architectural decision
- 54 tests: Basic coverage of app (25) and state (29) components
- Google Test v1.14.0 integration via CMake FetchContent
- Automatic test building (ON by default)
- CTest integration with custom
checktarget
Multithreaded Testing (12 tests):
- Concurrent access validation
- High contention scenarios (20 threads)
- Deadlock detection
- Stress testing (2,162 operations/sec)
Futures API (11 tests):
- Blocking waits with timeout
- Callback registration
- Producer-consumer patterns
state_future<T>template class
State Object Pattern (11 tests):
- CRTP pattern validation
- Configuration, DataProcessor, Renderer examples
- External access, multiple instances
- Async handler execution
Additional Coverage:
- Volume tests (29)
- Geometry tests (47)
- Algorithm tests (6)
- Voxels tests expanded to 128
- CUDA GPU tests (20)
- 577 tests: 100% passing
- 90.5% coverage: On core components
- Comprehensive documentation: Consolidated TESTING.md
- Real-world validation: Stanford Bunny, SDF convergence
- Production-ready: Thread-safe, async patterns, GPU acceleration
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libboost-all-dev \
lcov
- name: Configure
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCVC_BUILD_TESTS=ON \
-DCVC_ENABLE_COVERAGE=ON
- name: Build
run: cmake --build build -j$(nproc)
- name: Test
run: cd build && ctest --output-on-failure
- name: Coverage
run: cmake --build build --target coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
files: ./build/coverage.info# Configure
cmake -B build -DCVC_BUILD_TESTS=ON
# Build
cmake --build build
# Test with output on failure
cd build && ctest --output-on-failureIssue: Tests aren't being built
Solution:
# Verify CVC_BUILD_TESTS is ON
cmake -B build -DCVC_BUILD_TESTS=ON
# Check Google Test was fetched
ls build/_deps/googletest-src/
# Clean and rebuild
rm -rf build && cmake -B build && cmake --build buildIssue: Some tests fail
Solution:
# Run with verbose output
ctest --output-on-failure --verbose
# Run specific test directly
./build/bin/state_test --gtest_filter="*FailingTest*"
# Check for proper cleanup
grep -n "reset()" src/cvc/tests/state_test.cppIssue: Undefined references to Boost or dependencies
Solution:
# Verify dependencies are found
cmake -B build -DCMAKE_FIND_DEBUG_MODE=ON
# Check that tests link against cvc library
cat build/src/cvc/tests/CMakeFiles/state_test.dir/link.txtIssue: Coverage data is empty
Solution:
- Ensure you built with
-DCVC_ENABLE_COVERAGE=ON - Verify tests ran:
cd build && ctest - Use Debug build:
-DCMAKE_BUILD_TYPE=Debug - Check lcov is installed:
which lcov
Issue: Low coverage on new code
Solution:
- Verify tests exercise new code paths
- Check for untested error handling
- Add tests for edge cases
- Use coverage report to identify gaps
Issue: Multithreaded tests hang or timeout
Solution:
- Check for proper thread cleanup
- Verify
boost::thread::interrupt()support - Increase timeout values if needed
- Run with
--gtest_repeat=10to detect flakiness
Issue: CUDA tests fail or skip
Solution:
- Verify CUDA toolkit is installed
- Check GPU is available:
nvidia-smi - Ensure
CVC_USING_CUDAis defined - Tests gracefully skip if CUDA unavailable
Each test should be completely independent:
- No shared global state between tests
- Proper cleanup after each test
- Use unique namespaces (
test.*)
Use clear, descriptive test names:
// Good
TEST(StateTest, ConcurrentWritesToSameNode_HighContention)
// Bad
TEST(StateTest, Test5)Structure tests clearly:
TEST(VoxelsTest, FillOperation) {
// Arrange
voxels v(dimension(10, 10, 10), Float);
// Act
v.fill(42.0);
// Assert
EXPECT_DOUBLE_EQ(v(5, 5, 5), 42.0);
}Each test should verify one specific behavior:
// Good - tests one specific operation
TEST(StateTest, ValueSetAndGet)
TEST(StateTest, ValueTypeConversion)
// Bad - tests multiple unrelated things
TEST(StateTest, EverythingAboutValues)Choose the right assertion for the job:
EXPECT_*for non-fatal assertions (continues test)ASSERT_*for fatal assertions (stops test)EXPECT_DOUBLE_EQfor floating pointEXPECT_THROWfor exception testing
Fast tests encourage frequent running:
- Use smaller datasets when possible
- Mock expensive operations
- Run intensive tests separately
- Use
--gtest_filterduring development
Add comments for non-obvious test logic:
TEST(StateTest, ConcurrentWritesToSameNode) {
// This test validates thread safety under high contention.
// 20 threads simultaneously write to the same state node.
// The final value should be from one of the threads.
// The test passes if no crashes or deadlocks occur.
// ... test implementation ...
}-
I/O Testing
- Volume format tests (RAWIV, MRC, Spider, HDF5, VTK)
- Geometry format tests (OFF, OBJ, RAW variants)
- Error handling for corrupt files
- Large file streaming
-
Algorithm Testing
- Meshing algorithm validation
- Isosurfacing edge cases
- SDF accuracy metrics
- Performance benchmarks
-
Integration Tests
- End-to-end workflows
- Multi-component pipelines
- Real application scenarios
-
Performance Testing
- Benchmark suite
- Memory profiling
- Regression detection
- Scalability tests
-
Platform Testing
- Windows-specific tests
- macOS validation
- 32-bit vs 64-bit
- Different compilers
- Automated nightly tests
- Performance tracking over time
- Memory leak detection (Valgrind)
- Address sanitizer integration
- Fuzzing for I/O operations
- Benchmark comparisons
- Code quality metrics
The libcvc testing infrastructure provides comprehensive validation of core functionality with:
- ✅ 577 tests with 100% pass rate
- ✅ 90.5% coverage on core components
- ✅ Multithreaded validation
- ✅ Async programming via futures API
- ✅ GPU acceleration testing (CUDA)
- ✅ Real-world stress tests (SDF convergence, state_object patterns)
- ✅ Production-ready thread-safe architecture
The tests are well-organized, thoroughly documented, and easy to extend. The infrastructure supports rapid development with fast feedback cycles and comprehensive coverage analysis.
Key Achievements:
- Thread-safe SDF v2.0 with 11x performance improvement
- Comprehensive state management testing (102 tests)
- State object pattern validation (11 tests)
- CUDA GPU acceleration coverage (20 tests)
- Real-world validation (Stanford Bunny, 34,834 triangles)
The testing foundation ensures libcvc is reliable, performant, and ready for production use.