From 87114da6c7c127c599409840e0e1d96c0db1f3d9 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 13 Jul 2026 01:47:34 -0700 Subject: [PATCH 1/5] Add GitHub Actions workflow for building and testing C API bindings --- .github/workflows/build-c-api-bindings.yml | 71 ++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/build-c-api-bindings.yml diff --git a/.github/workflows/build-c-api-bindings.yml b/.github/workflows/build-c-api-bindings.yml new file mode 100644 index 00000000..26fe2abf --- /dev/null +++ b/.github/workflows/build-c-api-bindings.yml @@ -0,0 +1,71 @@ +# 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. + +name: Build and test C API bindings + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +permissions: + contents: read + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + name: ${{ matrix.cxx }}, ${{ matrix.build_type }} + runs-on: ubuntu-22.04 + strategy: + matrix: + build_type: [RelWithDebInfo] + cxx: [g++-11, g++-12, clang++-15] + include: + - cxx: g++-11 + cc: gcc-11 + - cxx: g++-12 + cc: gcc-12 + - cxx: clang++-15 + cc: clang-15 + fail-fast: false + + steps: + - uses: actions/checkout@v6 + + - name: Configure build + working-directory: ${{ runner.temp }} + env: + CXX: ${{ matrix.cxx }} + CC: ${{ matrix.cc }} + TEMP_WORKSPACE: ${{ runner.temp }} + run: | + cmake -B${TEMP_WORKSPACE}/build -S${GITHUB_WORKSPACE}/bindings/c \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DSVS_BUILD_C_API_TESTS=ON + + - name: Build C API, tests and samples + working-directory: ${{ runner.temp }}/build + run: make -j$(nproc) + + - name: Run C API tests + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{ runner.temp }}/build + run: ctest -C ${{ matrix.build_type }} --output-on-failure From a3259ecd8f594c2cab94b52eca01e411bf41904f Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 13 Jul 2026 03:28:26 -0700 Subject: [PATCH 2/5] Enable testing for C API by adding CTest support --- bindings/c/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/c/CMakeLists.txt b/bindings/c/CMakeLists.txt index dd9847d2..b88ab589 100644 --- a/bindings/c/CMakeLists.txt +++ b/bindings/c/CMakeLists.txt @@ -201,6 +201,9 @@ endif() if(SVS_BUILD_C_API_TESTS) + # Add test to CTest + include(CTest) + enable_testing() if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(${TARGET_NAME} PRIVATE --coverage) target_link_options(${TARGET_NAME} PRIVATE --coverage) From d83b4daef35b892a8670f07e01d4808b74f819ab Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 13 Jul 2026 05:56:58 -0700 Subject: [PATCH 3/5] Enhance C API tests by improving storage support checks and adding OpenMP runtime installation --- .github/workflows/build-c-api-bindings.yml | 11 +++++++++++ bindings/c/tests/c_api_index.cpp | 6 +++--- bindings/c/tests/c_api_storage.cpp | 16 ++++++++-------- bindings/c/tests/c_api_test_utils.h | 13 +++++++++---- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build-c-api-bindings.yml b/.github/workflows/build-c-api-bindings.yml index 26fe2abf..782c06e1 100644 --- a/.github/workflows/build-c-api-bindings.yml +++ b/.github/workflows/build-c-api-bindings.yml @@ -49,6 +49,17 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Install OpenMP runtime + env: + CXX: ${{ matrix.cxx }} + run: | + sudo apt-get update + # The default libgomp shipped with GCC does not match the clang + # toolchain, so install the LLVM OpenMP runtime (libomp) for clang. + if [[ "${CXX}" == clang* ]]; then + sudo apt-get install -y libomp-15-dev + fi + - name: Configure build working-directory: ${{ runner.temp }} env: diff --git a/bindings/c/tests/c_api_index.cpp b/bindings/c/tests/c_api_index.cpp index 07dce2c4..efadbca2 100644 --- a/bindings/c/tests/c_api_index.cpp +++ b/bindings/c/tests/c_api_index.cpp @@ -230,17 +230,17 @@ CATCH_TEST_CASE("C API Index Build and Search", "[c_api][index][build][search]") svs_storage_h storage = svs_storage_create_leanvec( DIMENSION / 2, SVS_DATA_TYPE_INT4, SVS_DATA_TYPE_INT8, error ); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); run_build_and_search(storage); // LVQ: primary = int4, residual = int8 storage = svs_storage_create_lvq(SVS_DATA_TYPE_INT4, SVS_DATA_TYPE_INT8, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); run_build_and_search(storage); // Scalar Quantization: int8 storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); run_build_and_search(storage); svs_error_free(error); diff --git a/bindings/c/tests/c_api_storage.cpp b/bindings/c/tests/c_api_storage.cpp index 22953e44..60d4aca1 100644 --- a/bindings/c/tests/c_api_storage.cpp +++ b/bindings/c/tests/c_api_storage.cpp @@ -77,7 +77,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_leanvec( leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error ); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -90,7 +90,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_leanvec( leanvec_dims, SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error ); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -101,7 +101,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -112,7 +112,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -123,7 +123,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -133,7 +133,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_error_h error = svs_error_create(); svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -143,7 +143,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_error_h error = svs_error_create(); svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); - CATCH_REQUIRE(check_storage_support(storage, error) == true); + check_storage_support(storage, error); svs_storage_free(storage); svs_error_free(error); @@ -170,7 +170,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { CATCH_REQUIRE(storage1 != nullptr); CATCH_REQUIRE(storage2 != nullptr); - CATCH_REQUIRE(check_storage_support(storage3, error) == true); + check_storage_support(storage3, error); svs_storage_free(storage1); svs_storage_free(storage2); diff --git a/bindings/c/tests/c_api_test_utils.h b/bindings/c/tests/c_api_test_utils.h index c1f488f4..c4a54a33 100644 --- a/bindings/c/tests/c_api_test_utils.h +++ b/bindings/c/tests/c_api_test_utils.h @@ -133,11 +133,16 @@ inline float cosine_distance(const float* a, const float* b, size_t dim) { return dot_product / (std::sqrt(norm_a) * std::sqrt(norm_b)); } -inline bool check_storage_support(svs_storage_h storage, svs_error_h error) { +inline void check_storage_support(svs_storage_h storage, svs_error_h error) { if (storage == nullptr) { auto code = svs_error_get_code(error); - return code == SVS_ERROR_NOT_IMPLEMENTED || code == SVS_ERROR_UNSUPPORTED_HW; - } else { - return svs_error_ok(error) == true; + if (code == SVS_ERROR_NOT_IMPLEMENTED) { + CATCH_SKIP("Storage kind is not implemented, skipping test."); + return; // Skip the test + } else if (code == SVS_ERROR_UNSUPPORTED_HW) { + CATCH_SKIP("Storage kind is not supported, skipping test."); + return; // Skip the test + } } + CATCH_REQUIRE(svs_error_ok(error) == true); // Fail the test for other errors } From 0bc7be8669efe7540652c202fc1183ddf20fac6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:12:36 +0000 Subject: [PATCH 4/5] Fix clang-15 name lookup bug: rename Allocator to Alloc in DispatchConverter specializations In namespace svs, partial specializations of lib::DispatchConverter with a template parameter named 'Allocator' caused clang-15 (and clang-16) to incorrectly resolve the name to the class template svs::lib::Allocator, resulting in 'use of class template requires template arguments' errors. Rename the template parameter from 'Allocator' to 'Alloc' in: - bindings/c/src/data_builder/simple.hpp - bindings/c/src/data_builder/sq.hpp - bindings/c/src/data_builder/lvq.hpp - bindings/c/src/data_builder/leanvec.hpp --- bindings/c/src/data_builder/leanvec.hpp | 6 +++--- bindings/c/src/data_builder/lvq.hpp | 6 +++--- bindings/c/src/data_builder/simple.hpp | 6 +++--- bindings/c/src/data_builder/sq.hpp | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bindings/c/src/data_builder/leanvec.hpp b/bindings/c/src/data_builder/leanvec.hpp index 87588c7e..13ba58a5 100644 --- a/bindings/c/src/data_builder/leanvec.hpp +++ b/bindings/c/src/data_builder/leanvec.hpp @@ -77,11 +77,11 @@ class LeanVecDataBuilder { } }; -template +template struct lib:: - DispatchConverter> { + DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = LeanVecDataBuilder; + using To = LeanVecDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_LEANVEC) { diff --git a/bindings/c/src/data_builder/lvq.hpp b/bindings/c/src/data_builder/lvq.hpp index 7a9e8ca5..d7d5912a 100644 --- a/bindings/c/src/data_builder/lvq.hpp +++ b/bindings/c/src/data_builder/lvq.hpp @@ -75,12 +75,12 @@ class LVQDataBuilder { } }; -template +template struct lib::DispatchConverter< const c_runtime::Storage*, - LVQDataBuilder> { + LVQDataBuilder> { using From = const svs::c_runtime::Storage*; - using To = LVQDataBuilder; + using To = LVQDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_LVQ) { diff --git a/bindings/c/src/data_builder/simple.hpp b/bindings/c/src/data_builder/simple.hpp index 36482909..1f016241 100644 --- a/bindings/c/src/data_builder/simple.hpp +++ b/bindings/c/src/data_builder/simple.hpp @@ -61,10 +61,10 @@ class SimpleDataBuilder { } }; -template -struct lib::DispatchConverter> { +template +struct lib::DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = SimpleDataBuilder; + using To = SimpleDataBuilder; static int64_t match(From from) { if constexpr (svs::is_arithmetic_v) { diff --git a/bindings/c/src/data_builder/sq.hpp b/bindings/c/src/data_builder/sq.hpp index a062ff7b..f2fd9559 100644 --- a/bindings/c/src/data_builder/sq.hpp +++ b/bindings/c/src/data_builder/sq.hpp @@ -59,10 +59,10 @@ template > class SQDat } }; -template -struct lib::DispatchConverter> { +template +struct lib::DispatchConverter> { using From = const svs::c_runtime::Storage*; - using To = SQDataBuilder; + using To = SQDataBuilder; static int64_t match(From from) { if (from->kind == SVS_STORAGE_KIND_SQ) { From 7d50c07ea2a10317a1d7e8713a7d4f1ced5a31e1 Mon Sep 17 00:00:00 2001 From: Rafik Saliev Date: Mon, 13 Jul 2026 08:33:35 -0700 Subject: [PATCH 5/5] Revert check_storage_support --- bindings/c/tests/c_api_index.cpp | 18 ++++++++++++------ bindings/c/tests/c_api_storage.cpp | 16 ++++++++-------- bindings/c/tests/c_api_test_utils.h | 13 ++++--------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/bindings/c/tests/c_api_index.cpp b/bindings/c/tests/c_api_index.cpp index efadbca2..6bd19d29 100644 --- a/bindings/c/tests/c_api_index.cpp +++ b/bindings/c/tests/c_api_index.cpp @@ -230,18 +230,24 @@ CATCH_TEST_CASE("C API Index Build and Search", "[c_api][index][build][search]") svs_storage_h storage = svs_storage_create_leanvec( DIMENSION / 2, SVS_DATA_TYPE_INT4, SVS_DATA_TYPE_INT8, error ); - check_storage_support(storage, error); - run_build_and_search(storage); + CATCH_REQUIRE(check_storage_support(storage, error) == true); + if (storage != nullptr) { + run_build_and_search(storage); + } // LVQ: primary = int4, residual = int8 storage = svs_storage_create_lvq(SVS_DATA_TYPE_INT4, SVS_DATA_TYPE_INT8, error); - check_storage_support(storage, error); - run_build_and_search(storage); + CATCH_REQUIRE(check_storage_support(storage, error) == true); + if (storage != nullptr) { + run_build_and_search(storage); + } // Scalar Quantization: int8 storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); - check_storage_support(storage, error); - run_build_and_search(storage); + CATCH_REQUIRE(check_storage_support(storage, error) == true); + if (storage != nullptr) { + run_build_and_search(storage); + } svs_error_free(error); } diff --git a/bindings/c/tests/c_api_storage.cpp b/bindings/c/tests/c_api_storage.cpp index 60d4aca1..22953e44 100644 --- a/bindings/c/tests/c_api_storage.cpp +++ b/bindings/c/tests/c_api_storage.cpp @@ -77,7 +77,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_leanvec( leanvec_dims, SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_UINT8, error ); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -90,7 +90,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_leanvec( leanvec_dims, SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT4, error ); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -101,7 +101,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_VOID, error); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -112,7 +112,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT8, SVS_DATA_TYPE_VOID, error); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -123,7 +123,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_storage_h storage = svs_storage_create_lvq(SVS_DATA_TYPE_UINT4, SVS_DATA_TYPE_UINT8, error); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -133,7 +133,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_error_h error = svs_error_create(); svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_UINT8, error); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -143,7 +143,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { svs_error_h error = svs_error_create(); svs_storage_h storage = svs_storage_create_sq(SVS_DATA_TYPE_INT8, error); - check_storage_support(storage, error); + CATCH_REQUIRE(check_storage_support(storage, error) == true); svs_storage_free(storage); svs_error_free(error); @@ -170,7 +170,7 @@ CATCH_TEST_CASE("C API Storage", "[c_api][storage]") { CATCH_REQUIRE(storage1 != nullptr); CATCH_REQUIRE(storage2 != nullptr); - check_storage_support(storage3, error); + CATCH_REQUIRE(check_storage_support(storage3, error) == true); svs_storage_free(storage1); svs_storage_free(storage2); diff --git a/bindings/c/tests/c_api_test_utils.h b/bindings/c/tests/c_api_test_utils.h index c4a54a33..c1f488f4 100644 --- a/bindings/c/tests/c_api_test_utils.h +++ b/bindings/c/tests/c_api_test_utils.h @@ -133,16 +133,11 @@ inline float cosine_distance(const float* a, const float* b, size_t dim) { return dot_product / (std::sqrt(norm_a) * std::sqrt(norm_b)); } -inline void check_storage_support(svs_storage_h storage, svs_error_h error) { +inline bool check_storage_support(svs_storage_h storage, svs_error_h error) { if (storage == nullptr) { auto code = svs_error_get_code(error); - if (code == SVS_ERROR_NOT_IMPLEMENTED) { - CATCH_SKIP("Storage kind is not implemented, skipping test."); - return; // Skip the test - } else if (code == SVS_ERROR_UNSUPPORTED_HW) { - CATCH_SKIP("Storage kind is not supported, skipping test."); - return; // Skip the test - } + return code == SVS_ERROR_NOT_IMPLEMENTED || code == SVS_ERROR_UNSUPPORTED_HW; + } else { + return svs_error_ok(error) == true; } - CATCH_REQUIRE(svs_error_ok(error) == true); // Fail the test for other errors }