Skip to content

Commit 03f514b

Browse files
authored
Add C API tests for error handling, indexing, and search parameters (#306)
This pull request introduces a comprehensive C API test suite for the SVS project, leveraging the Catch2 testing framework. It adds new test files covering all major C API functionalities, integrates automated test building and execution into the CMake build system, and improves error handling and testability for dynamic index operations. **C API Test Infrastructure and Test Coverage:** * Added a new directory of C API tests using Catch2, with individual test files for error handling, algorithm configuration, storage, search parameters, index building, and dynamic index operations. **Dynamic Index Error Handling:** * Refactored `svs_index_dynamic_delete_points` to improve error handling.
1 parent 66a0549 commit 03f514b

12 files changed

Lines changed: 2296 additions & 14 deletions

bindings/c/CMakeLists.txt

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
cmake_minimum_required(VERSION 3.21)
16-
project(svs_c_api VERSION 0.1.0 LANGUAGES CXX C)
16+
project(svs_c_api VERSION 0.4.0 LANGUAGES CXX C)
1717
set(TARGET_NAME svs_c_api)
1818

1919
set(SVS_C_API_HEADERS
@@ -60,18 +60,18 @@ if(UNIX AND NOT APPLE)
6060
endif()
6161

6262
target_compile_features(${TARGET_NAME} INTERFACE cxx_std_20)
63+
if (NOT DEFINED SVS_CXX_STANDARD OR SVS_CXX_STANDARD STREQUAL "")
64+
set(SVS_CXX_STANDARD 20)
65+
endif()
6366
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_C_API_HEADERS}")
64-
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 20)
67+
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD ${SVS_CXX_STANDARD})
6568
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
6669
set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
6770
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )
6871

6972
target_link_libraries(${TARGET_NAME} PRIVATE
7073
svs::svs
7174
)
72-
if (SVS_EXPERIMENTAL_LINK_STATIC_MKL)
73-
link_mkl_static(${TARGET_NAME})
74-
endif()
7575

7676
if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
7777
message(STATUS "Enabling LVQ/LeanVec support in C API")
@@ -95,7 +95,9 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
9595
svs::svs
9696
svs_compile_options
9797
)
98-
link_mkl_static(${TARGET_NAME})
98+
if(SVS_EXPERIMENTAL_LINK_STATIC_MKL)
99+
link_mkl_static(${TARGET_NAME})
100+
endif()
99101
elseif(TARGET svs::svs)
100102
message(FATAL_ERROR
101103
"Pre-built LVQ/LeanVec SVS library cannot be used in SVS main build. "
@@ -191,8 +193,35 @@ install(FILES
191193
)
192194

193195
# Build tests if requested
194-
# if(SVS_BUILD_C_API_TESTS)
195-
# add_subdirectory(tests)
196-
# endif()
196+
if(DEFINED SVS_BUILD_TESTS)
197+
option(SVS_BUILD_C_API_TESTS "Build C API tests" ${SVS_BUILD_TESTS})
198+
else()
199+
option(SVS_BUILD_C_API_TESTS "Build C API tests" OFF)
200+
endif()
201+
202+
203+
if(SVS_BUILD_C_API_TESTS)
204+
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
205+
target_compile_options(${TARGET_NAME} PRIVATE --coverage)
206+
target_link_options(${TARGET_NAME} PRIVATE --coverage)
207+
# add coverage target
208+
add_custom_target(clean_coverage
209+
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning coverage data..."
210+
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} find . -name "*.gcda" -delete
211+
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.info
212+
COMMENT "Cleaning coverage data..."
213+
)
214+
add_custom_target(coverage
215+
COMMAND ${CMAKE_COMMAND} -E echo "Generating coverage report..."
216+
COMMAND ${CMAKE_COMMAND} -E env GCOV_PREFIX=${CMAKE_BINARY_DIR}/coverage GCOV_PREFIX_STRIP=1 ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage
217+
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --capture --directory . --output-file coverage.info
218+
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '/usr/*' --output-file coverage.info
219+
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '*/_deps/*' --output-file coverage.info
220+
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --list coverage.info
221+
COMMENT "Generating code coverage report..."
222+
)
223+
endif()
224+
add_subdirectory(tests)
225+
endif()
197226

198227
add_subdirectory(samples)

bindings/c/src/index.hpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@
2323
#include <svs/concepts/data.h>
2424
#include <svs/core/distance.h>
2525
#include <svs/core/query_result.h>
26+
#include <svs/lib/misc.h>
2627
#include <svs/orchestrators/dynamic_vamana.h>
2728
#include <svs/orchestrators/vamana.h>
2829

2930
#include <filesystem>
3031
#include <memory>
3132
#include <span>
33+
#include <vector>
3234

3335
namespace svs::c_runtime {
3436
struct Index {
@@ -155,11 +157,19 @@ struct DynamicIndexVamana : public DynamicIndex {
155157
}
156158

157159
size_t delete_points(std::span<const size_t> ids) override {
158-
auto old_size = index.size();
159-
index.delete_points(ids);
160-
// TODO: This is a bit of a hack - we should ideally return the number of points
161-
// actually deleted, but for now we can just return index size change.
162-
return old_size - index.size();
160+
std::vector<size_t> ids_to_delete;
161+
ids_to_delete.reserve(ids.size());
162+
163+
for (auto id : ids) {
164+
if (index.has_id(id)) {
165+
ids_to_delete.push_back(id);
166+
}
167+
}
168+
169+
if (!ids_to_delete.empty()) {
170+
index.delete_points(svs::lib::as_const_span(ids_to_delete));
171+
}
172+
return ids_to_delete.size();
163173
}
164174

165175
bool has_id(size_t id) const override { return index.has_id(id); }

bindings/c/tests/CMakeLists.txt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright 2026 Intel Corporation
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
set(TARGET_NAME svs_c_api_test)
16+
17+
# Check if Catch2 is available
18+
find_package(Catch2 3 QUIET)
19+
20+
if(NOT Catch2_FOUND)
21+
message(STATUS "Catch2 not found, fetching from GitHub...")
22+
include(FetchContent)
23+
24+
# Do wide printing for the console logger for Catch2
25+
set(CATCH_CONFIG_CONSOLE_WIDTH "100" CACHE STRING "" FORCE)
26+
set(CATCH_BUILD_TESTING OFF CACHE BOOL "" FORCE)
27+
set(CATCH_CONFIG_ENABLE_BENCHMARKING OFF CACHE BOOL "" FORCE)
28+
set(CATCH_CONFIG_FAST_COMPILE OFF CACHE BOOL "" FORCE)
29+
set(CATCH_CONFIG_PREFIX_ALL ON CACHE BOOL "" FORCE)
30+
31+
set(PRESET_CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD})
32+
if(DEFINED SVS_CXX_STANDARD)
33+
set(CMAKE_CXX_STANDARD ${SVS_CXX_STANDARD})
34+
endif()
35+
FetchContent_Declare(
36+
Catch2
37+
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
38+
GIT_TAG v3.4.0
39+
)
40+
FetchContent_MakeAvailable(Catch2)
41+
set(CMAKE_CXX_STANDARD ${PRESET_CMAKE_CXX_STANDARD})
42+
endif()
43+
44+
# Define test sources
45+
set(C_API_TEST_SOURCES
46+
c_api_error.cpp
47+
c_api_algorithm.cpp
48+
c_api_storage.cpp
49+
c_api_search_params.cpp
50+
c_api_index_builder.cpp
51+
c_api_index.cpp
52+
c_api_dynamic_index.cpp
53+
)
54+
55+
# Create test executable
56+
add_executable(${TARGET_NAME} ${C_API_TEST_SOURCES})
57+
58+
# Link with C API library and Catch2
59+
target_link_libraries(${TARGET_NAME} PRIVATE
60+
svs_c_api
61+
Catch2::Catch2WithMain
62+
)
63+
64+
# Set C++ standard
65+
target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)
66+
set_target_properties(${TARGET_NAME} PROPERTIES
67+
CXX_STANDARD 20
68+
CXX_STANDARD_REQUIRED ON
69+
CXX_EXTENSIONS OFF
70+
)
71+
72+
# Include directories
73+
target_include_directories(${TARGET_NAME} PRIVATE
74+
${CMAKE_CURRENT_SOURCE_DIR}/../include
75+
)
76+
77+
# Add test to CTest
78+
include(CTest)
79+
enable_testing()
80+
81+
# Add Catch2 CMake module path
82+
if(NOT Catch2_FOUND)
83+
# Catch2 was fetched, use its source directory
84+
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
85+
else()
86+
# Catch2 was found via find_package, use its module directory
87+
list(APPEND CMAKE_MODULE_PATH ${Catch2_DIR})
88+
endif()
89+
90+
include(Catch)
91+
catch_discover_tests(${TARGET_NAME} PROPERTIES LABELS "c_api")
92+
93+
# Add a custom target to run tests
94+
add_custom_target(run_c_api_test
95+
COMMAND ${TARGET_NAME}
96+
DEPENDS ${TARGET_NAME}
97+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
98+
COMMENT "Running C API tests..."
99+
)

bindings/c/tests/README.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# C API Tests
2+
3+
This directory contains comprehensive tests for the SVS C API using the Catch2 testing framework.
4+
5+
## Test Structure
6+
7+
The tests are organized into separate files by functionality:
8+
9+
- **c_api_error.cpp**: Tests for error handling functionality
10+
- **c_api_algorithm.cpp**: Tests for algorithm creation and configuration (Vamana)
11+
- **c_api_storage.cpp**: Tests for storage configurations (Simple, LeanVec, LVQ, SQ)
12+
- **c_api_search_params.cpp**: Tests for search parameter creation and configuration
13+
- **c_api_index_builder.cpp**: Tests for index builder creation and configuration
14+
- **c_api_index.cpp**: Tests for index building, searching, and basic operations
15+
- **c_api_dynamic_index.cpp**: Tests for dynamic index operations (add, delete, consolidate, compact)
16+
17+
Note: The main() function is provided by Catch2::Catch2WithMain automatically.
18+
19+
## Building the Tests
20+
21+
The tests are built as part of the C API build process. To build them:
22+
23+
```bash
24+
# From the build directory
25+
cmake -DSVS_BUILD_C_API_TESTS=ON ..
26+
make svs_c_api_test
27+
```
28+
29+
To disable building tests:
30+
31+
```bash
32+
cmake -DSVS_BUILD_C_API_TESTS=OFF ..
33+
```
34+
35+
## Running the Tests
36+
37+
### Run all tests
38+
39+
```bash
40+
./svs_c_api_test
41+
```
42+
43+
### Run specific test cases
44+
45+
```bash
46+
# Run error handling tests only
47+
./svs_c_api_test "[c_api][error]"
48+
49+
# Run algorithm tests only
50+
./svs_c_api_test "[c_api][algorithm]"
51+
52+
# Run all index tests
53+
./svs_c_api_test "[c_api][index]"
54+
55+
# Run dynamic index tests
56+
./svs_c_api_test "[c_api][dynamic]"
57+
```
58+
59+
### Run with verbose output
60+
61+
```bash
62+
./svs_c_api_test -s
63+
```
64+
65+
### List all available tests
66+
67+
```bash
68+
./svs_c_api_test --list-tests
69+
```
70+
71+
### Run with CTest
72+
73+
```bash
74+
ctest -R svs_c_api_test
75+
```
76+
77+
## Test Coverage
78+
79+
The tests cover the following aspects of the C API:
80+
81+
### Error Handling
82+
83+
- Error handle creation and cleanup
84+
- Error state checking
85+
- Error codes and messages
86+
- NULL error handle support
87+
88+
### Algorithm Configuration
89+
90+
- Vamana algorithm creation
91+
- Parameter getters and setters (graph_degree, build_window_size, alpha, search_history)
92+
- Invalid parameter handling
93+
94+
### Storage Configuration
95+
96+
- Simple storage (Float32, Float16, Int8, Uint8)
97+
- LeanVec storage (various primary/secondary combinations)
98+
- LVQ storage (with and without residual)
99+
- Scalar Quantization storage
100+
101+
### Search Parameters
102+
103+
- Vamana search parameter creation
104+
- Various window sizes
105+
106+
### Index Builder
107+
108+
- Index builder creation with different metrics (Euclidean, Cosine, Dot Product)
109+
- Storage configuration
110+
- Thread pool configuration (Native, OMP, Custom)
111+
112+
### Index Operations
113+
114+
- Index building from data
115+
- Searching with queries
116+
- Different K values
117+
- Distance calculation
118+
- Vector reconstruction
119+
- Thread count management
120+
121+
### Dynamic Index Operations
122+
123+
- Dynamic index building with/without explicit IDs
124+
- Adding points
125+
- Deleting points
126+
- ID existence checking
127+
- Index consolidation
128+
- Index compaction
129+
- Search after modifications
130+
131+
## Test Patterns
132+
133+
The tests follow the patterns established in the SVS project:
134+
135+
1. Use `CATCH_TEST_CASE` for test case definitions
136+
2. Use `CATCH_SECTION` for test subsections
137+
3. Use `CATCH_REQUIRE` for assertions
138+
4. Clean up all resources (free handles) after each test
139+
5. Test both success and error paths
140+
6. Test with and without NULL error handles
141+
142+
## Adding New Tests
143+
144+
When adding new tests:
145+
146+
1. Create a new `.cpp` file or add to an existing one
147+
2. Follow the existing structure and naming conventions
148+
3. Include proper copyright header
149+
4. Use appropriate test tags: `[c_api][functionality]`
150+
5. Add the new test file to `CMakeLists.txt` if needed
151+
6. Clean up all allocated resources
152+
7. Test both success and error conditions
153+
154+
## Dependencies
155+
156+
- Catch2 v3.x (automatically fetched if not found)
157+
- SVS C API library
158+
- C++20 or later compiler
159+
160+
## Notes
161+
162+
- Tests use a simple sequential thread pool for deterministic behavior
163+
- Test data is generated programmatically for repeatability
164+
- Some tests may be skipped if optional features are not enabled (e.g., LVQ/LeanVec)

0 commit comments

Comments
 (0)