Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions bindings/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

cmake_minimum_required(VERSION 3.21)
project(svs_c_api VERSION 0.1.0 LANGUAGES CXX C)
project(svs_c_api VERSION 0.4.0 LANGUAGES CXX C)
set(TARGET_NAME svs_c_api)

set(SVS_C_API_HEADERS
Expand Down Expand Up @@ -60,18 +60,18 @@ if(UNIX AND NOT APPLE)
endif()

target_compile_features(${TARGET_NAME} INTERFACE cxx_std_20)
if (NOT DEFINED SVS_CXX_STANDARD OR SVS_CXX_STANDARD STREQUAL "")
set(SVS_CXX_STANDARD 20)
endif()
set_target_properties(${TARGET_NAME} PROPERTIES PUBLIC_HEADER "${SVS_C_API_HEADERS}")
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD 20)
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD ${SVS_CXX_STANDARD})
set_target_properties(${TARGET_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
set_target_properties(${TARGET_NAME} PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} )

target_link_libraries(${TARGET_NAME} PRIVATE
svs::svs
)
if (SVS_EXPERIMENTAL_LINK_STATIC_MKL)
link_mkl_static(${TARGET_NAME})
endif()

if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
message(STATUS "Enabling LVQ/LeanVec support in C API")
Expand All @@ -95,7 +95,9 @@ if (SVS_RUNTIME_ENABLE_LVQ_LEANVEC)
svs::svs
svs_compile_options
)
link_mkl_static(${TARGET_NAME})
if(SVS_EXPERIMENTAL_LINK_STATIC_MKL)
link_mkl_static(${TARGET_NAME})
endif()
elseif(TARGET svs::svs)
message(FATAL_ERROR
"Pre-built LVQ/LeanVec SVS library cannot be used in SVS main build. "
Expand Down Expand Up @@ -191,8 +193,35 @@ install(FILES
)

# Build tests if requested
# if(SVS_BUILD_C_API_TESTS)
# add_subdirectory(tests)
# endif()
if(DEFINED SVS_BUILD_TESTS)
option(SVS_BUILD_C_API_TESTS "Build C API tests" ${SVS_BUILD_TESTS})
else()
option(SVS_BUILD_C_API_TESTS "Build C API tests" OFF)
endif()


if(SVS_BUILD_C_API_TESTS)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(${TARGET_NAME} PRIVATE --coverage)
target_link_options(${TARGET_NAME} PRIVATE --coverage)
# add coverage target
add_custom_target(clean_coverage
COMMAND ${CMAKE_COMMAND} -E echo "Cleaning coverage data..."
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} find . -name "*.gcda" -delete
COMMAND ${CMAKE_COMMAND} -E remove -f ${CMAKE_BINARY_DIR}/coverage.info
COMMENT "Cleaning coverage data..."
)
add_custom_target(coverage
COMMAND ${CMAKE_COMMAND} -E echo "Generating coverage report..."
COMMAND ${CMAKE_COMMAND} -E env GCOV_PREFIX=${CMAKE_BINARY_DIR}/coverage GCOV_PREFIX_STRIP=1 ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/coverage
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --capture --directory . --output-file coverage.info
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '/usr/*' --output-file coverage.info
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --remove coverage.info '*/_deps/*' --output-file coverage.info
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_BINARY_DIR} lcov --list coverage.info
COMMENT "Generating code coverage report..."
)
endif()
add_subdirectory(tests)
endif()

add_subdirectory(samples)
20 changes: 15 additions & 5 deletions bindings/c/src/index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
#include <svs/concepts/data.h>
#include <svs/core/distance.h>
#include <svs/core/query_result.h>
#include <svs/lib/misc.h>
#include <svs/orchestrators/dynamic_vamana.h>
#include <svs/orchestrators/vamana.h>

#include <filesystem>
#include <memory>
#include <span>
#include <vector>

namespace svs::c_runtime {
struct Index {
Expand Down Expand Up @@ -155,11 +157,19 @@ struct DynamicIndexVamana : public DynamicIndex {
}

size_t delete_points(std::span<const size_t> ids) override {
auto old_size = index.size();
index.delete_points(ids);
// TODO: This is a bit of a hack - we should ideally return the number of points
// actually deleted, but for now we can just return index size change.
return old_size - index.size();
std::vector<size_t> ids_to_delete;
ids_to_delete.reserve(ids.size());

for (auto id : ids) {
if (index.has_id(id)) {
ids_to_delete.push_back(id);
}
}

if (!ids_to_delete.empty()) {
index.delete_points(svs::lib::as_const_span(ids_to_delete));
}
return ids_to_delete.size();
}

bool has_id(size_t id) const override { return index.has_id(id); }
Expand Down
99 changes: 99 additions & 0 deletions bindings/c/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2026 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set(TARGET_NAME svs_c_api_test)
Comment thread
rfsaliev marked this conversation as resolved.

# Check if Catch2 is available
find_package(Catch2 3 QUIET)

if(NOT Catch2_FOUND)
message(STATUS "Catch2 not found, fetching from GitHub...")
include(FetchContent)

# Do wide printing for the console logger for Catch2
set(CATCH_CONFIG_CONSOLE_WIDTH "100" CACHE STRING "" FORCE)
set(CATCH_BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(CATCH_CONFIG_ENABLE_BENCHMARKING OFF CACHE BOOL "" FORCE)
set(CATCH_CONFIG_FAST_COMPILE OFF CACHE BOOL "" FORCE)
set(CATCH_CONFIG_PREFIX_ALL ON CACHE BOOL "" FORCE)

Comment thread
rfsaliev marked this conversation as resolved.
set(PRESET_CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD})
if(DEFINED SVS_CXX_STANDARD)
set(CMAKE_CXX_STANDARD ${SVS_CXX_STANDARD})
endif()
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.4.0
)
FetchContent_MakeAvailable(Catch2)
set(CMAKE_CXX_STANDARD ${PRESET_CMAKE_CXX_STANDARD})
endif()

# Define test sources
set(C_API_TEST_SOURCES
c_api_error.cpp
c_api_algorithm.cpp
c_api_storage.cpp
c_api_search_params.cpp
c_api_index_builder.cpp
c_api_index.cpp
c_api_dynamic_index.cpp
)

# Create test executable
add_executable(${TARGET_NAME} ${C_API_TEST_SOURCES})

# Link with C API library and Catch2
target_link_libraries(${TARGET_NAME} PRIVATE
svs_c_api
Catch2::Catch2WithMain
)

# Set C++ standard
target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20)
set_target_properties(${TARGET_NAME} PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS OFF
)

# Include directories
target_include_directories(${TARGET_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../include
)

# Add test to CTest
include(CTest)
enable_testing()

# Add Catch2 CMake module path
if(NOT Catch2_FOUND)
# Catch2 was fetched, use its source directory
list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
else()
# Catch2 was found via find_package, use its module directory
list(APPEND CMAKE_MODULE_PATH ${Catch2_DIR})
endif()

include(Catch)
catch_discover_tests(${TARGET_NAME} PROPERTIES LABELS "c_api")

# Add a custom target to run tests
add_custom_target(run_c_api_test
COMMAND ${TARGET_NAME}
DEPENDS ${TARGET_NAME}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Running C API tests..."
)
164 changes: 164 additions & 0 deletions bindings/c/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# C API Tests

This directory contains comprehensive tests for the SVS C API using the Catch2 testing framework.

## Test Structure

The tests are organized into separate files by functionality:

- **c_api_error.cpp**: Tests for error handling functionality
- **c_api_algorithm.cpp**: Tests for algorithm creation and configuration (Vamana)
- **c_api_storage.cpp**: Tests for storage configurations (Simple, LeanVec, LVQ, SQ)
- **c_api_search_params.cpp**: Tests for search parameter creation and configuration
- **c_api_index_builder.cpp**: Tests for index builder creation and configuration
- **c_api_index.cpp**: Tests for index building, searching, and basic operations
- **c_api_dynamic_index.cpp**: Tests for dynamic index operations (add, delete, consolidate, compact)

Note: The main() function is provided by Catch2::Catch2WithMain automatically.

## Building the Tests

The tests are built as part of the C API build process. To build them:

```bash
# From the build directory
cmake -DSVS_BUILD_C_API_TESTS=ON ..
make svs_c_api_test
```
Comment thread
rfsaliev marked this conversation as resolved.

To disable building tests:

```bash
cmake -DSVS_BUILD_C_API_TESTS=OFF ..
```

## Running the Tests

### Run all tests

```bash
./svs_c_api_test
```

### Run specific test cases

```bash
# Run error handling tests only
./svs_c_api_test "[c_api][error]"

# Run algorithm tests only
./svs_c_api_test "[c_api][algorithm]"

# Run all index tests
./svs_c_api_test "[c_api][index]"

# Run dynamic index tests
./svs_c_api_test "[c_api][dynamic]"
```

### Run with verbose output

```bash
./svs_c_api_test -s
```

### List all available tests

```bash
./svs_c_api_test --list-tests
```

### Run with CTest

```bash
ctest -R svs_c_api_test
```

## Test Coverage

The tests cover the following aspects of the C API:

### Error Handling

- Error handle creation and cleanup
- Error state checking
- Error codes and messages
- NULL error handle support

### Algorithm Configuration

- Vamana algorithm creation
- Parameter getters and setters (graph_degree, build_window_size, alpha, search_history)
- Invalid parameter handling

### Storage Configuration

- Simple storage (Float32, Float16, Int8, Uint8)
- LeanVec storage (various primary/secondary combinations)
- LVQ storage (with and without residual)
- Scalar Quantization storage

### Search Parameters

- Vamana search parameter creation
- Various window sizes

### Index Builder

- Index builder creation with different metrics (Euclidean, Cosine, Dot Product)
- Storage configuration
- Thread pool configuration (Native, OMP, Custom)

### Index Operations

- Index building from data
- Searching with queries
- Different K values
- Distance calculation
- Vector reconstruction
- Thread count management

### Dynamic Index Operations

- Dynamic index building with/without explicit IDs
- Adding points
- Deleting points
- ID existence checking
- Index consolidation
- Index compaction
- Search after modifications

## Test Patterns

The tests follow the patterns established in the SVS project:

1. Use `CATCH_TEST_CASE` for test case definitions
2. Use `CATCH_SECTION` for test subsections
3. Use `CATCH_REQUIRE` for assertions
4. Clean up all resources (free handles) after each test
5. Test both success and error paths
6. Test with and without NULL error handles

## Adding New Tests

When adding new tests:

1. Create a new `.cpp` file or add to an existing one
2. Follow the existing structure and naming conventions
3. Include proper copyright header
4. Use appropriate test tags: `[c_api][functionality]`
5. Add the new test file to `CMakeLists.txt` if needed
6. Clean up all allocated resources
7. Test both success and error conditions

## Dependencies

- Catch2 v3.x (automatically fetched if not found)
- SVS C API library
- C++20 or later compiler

## Notes

- Tests use a simple sequential thread pool for deterministic behavior
- Test data is generated programmatically for repeatability
- Some tests may be skipped if optional features are not enabled (e.g., LVQ/LeanVec)
Loading
Loading