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
7 changes: 7 additions & 0 deletions .github/scripts/test_ci_aggregates.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ def test_matrix_verdicts_and_dependencies(self):
"python_build",
"PYTHON_SELECTED",
),
(
"python.yml",
"faiss-comparison",
"python-build",
"python_build",
"PYTHON_SELECTED",
),
("docs.yml", "docs-result", "build", "docs", "DOCS_SELECTED"),
):
workflow = (WORKFLOWS / filename).read_text()
Expand Down
46 changes: 46 additions & 0 deletions .github/scripts/test_ci_workflow_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,52 @@ def expand(match):
group(template, 0, "b", filename),
)

def test_windows_consumers_cover_each_component_and_openmp_mode(self):
workflow = (ROOT / ".github/workflows/test.yaml").read_text()
block = workflow.split(" core-consumer:\n", 1)[1].split(
" core-consumer-result:\n", 1
)[0]
variants = re.findall(
r'- os: windows-2025-vs2026\n\s+openmp: "(ON|OFF)"'
r"\n\s+component: (core|fastscan)",
block,
)
self.assertCountEqual(
variants,
[
(mode, component)
for mode in ("ON", "OFF")
for component in ("core", "fastscan")
],
)
windows = block.split(
" - name: Test external core-only integrations (Windows)\n", 1
)[1].split(" - name: Report compiler cache statistics", 1)[0]
self.assertIn("-DRABITQ_COMPONENT=${{ matrix.component }}", windows)

def test_component_partition_rejects_unknown_or_empty_selection(self):
with tempfile.TemporaryDirectory() as directory:
for component in ("unknown", ""):
with self.subTest(component=component):
result = subprocess.run(
[
"cmake",
f"-DRABITQ_SOURCE_DIR={ROOT}",
f"-DWORK_DIR={directory}",
f"-DRABITQ_COMPONENT={component}",
"-P",
str(ROOT / "tests/core_consumer/run.cmake"),
],
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(result.returncode, 0)
self.assertIn(
"RABITQ_COMPONENT must be core or fastscan", result.stderr
)
self.assertFalse(list(Path(directory).iterdir()))

def test_documentation_summary_is_available_for_non_doc_prs(self):
text = (ROOT / ".github/workflows/docs.yml").read_text()
triggers = text.split("concurrency:", 1)[0]
Expand Down
39 changes: 23 additions & 16 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ jobs:
tests/python/test_symqg.py::test_save_load_roundtrip
tests/python/test_qgkmeans.py::test_train_populates_faiss_style_outputs
tests/python/test_rabitqkmeans.py::test_flat_parameters_and_outputs
- name: Install FAISS comparison dependency
if: matrix.python-version == '3.11'
run: python -m pip install faiss-cpu
- name: Test comparison script
if: matrix.python-version == '3.11'
env:
OMP_NUM_THREADS: "2"
run: python -m pytest tests/python/test_compare_with_faiss.py --tb=short -q

python-result:
name: Python validation
Expand Down Expand Up @@ -123,22 +131,21 @@ jobs:
tests/python/test_rabitqkmeans.py::test_flat_parameters_and_outputs

faiss-comparison:
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.python_build == 'true') }}
# Keep the required check name; FAISS runs against the 3.11 source installation.
name: FAISS comparison script
needs: [changes, python-build]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 20
env:
OMP_NUM_THREADS: "2"
timeout-minutes: 5
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.11"
cache: pip
- name: Verify runner CMake
run: cmake --version
- name: Install comparison dependencies
run: python -m pip install ".[test]" faiss-cpu
- name: Test comparison script
run: python -m pytest tests/python/test_compare_with_faiss.py --tb=short -q
- name: Require selected comparison tests
env:
CHANGES_RESULT: ${{ needs.changes.result }}
PYTHON_SELECTED: ${{ needs.changes.outputs.python_build }}
MATRIX_RESULT: ${{ needs.python-build.result }}
run: |
if [ "$CHANGES_RESULT" = success ] && [ "$PYTHON_SELECTED" = false ]; then
test "$MATRIX_RESULT" = skipped || test "$MATRIX_RESULT" = success
else
test "$MATRIX_RESULT" = success
fi
24 changes: 21 additions & 3 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ jobs:
run: |
sudo apt-get install -y ccache
echo "CCACHE_DIR=$RUNNER_TEMP/ccache" >> "$GITHUB_ENV"
# OpenMP OFF runs the full core/FastScan suites in the integrations below.
- name: Configure portable build
if: matrix.openmp == 'ON'
run: >-
cmake -S . -B build-clang -G Ninja
-DCMAKE_BUILD_TYPE=Release
Expand All @@ -210,6 +212,7 @@ jobs:
-DRABITQ_USE_OPENMP=${{ matrix.openmp }}
-DRABITQ_BUILD_INDEX=${{ matrix.openmp }}
- name: Build and test
if: matrix.openmp == 'ON'
run: |
cmake --build build-clang --parallel 2
ctest --test-dir build-clang --output-on-failure --no-tests=error
Expand Down Expand Up @@ -306,7 +309,8 @@ jobs:
env:
ASAN_OPTIONS: detect_leaks=1
UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1
run: ctest --test-dir build-sanitizers --output-on-failure
# Each case is a separate process; bound concurrent sanitizer memory use.
run: ctest --test-dir build-sanitizers --output-on-failure --no-tests=error --parallel 2
- name: Test installed sanitizer usage requirements
env:
ASAN_OPTIONS: detect_leaks=1
Expand Down Expand Up @@ -378,14 +382,27 @@ jobs:
run: ctest --test-dir build-consumer --output-on-failure

core-consumer:
name: Core and FastScan consumers (${{ matrix.os }}, OpenMP ${{ matrix.openmp }})
name: Core and FastScan consumers (${{ matrix.os }}, OpenMP ${{ matrix.openmp }}${{ matrix.component && format(', {0}', matrix.component) || '' }})
needs: changes
if: ${{ !cancelled() && (needs.changes.result != 'success' || needs.changes.outputs.cpp == 'true') }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15, windows-2025-vs2026]
os: [ubuntu-latest, ubuntu-24.04-arm, macos-15]
openmp: ["ON", "OFF"]
include:
- os: windows-2025-vs2026
openmp: "ON"
component: core
- os: windows-2025-vs2026
openmp: "OFF"
component: core
- os: windows-2025-vs2026
openmp: "ON"
component: fastscan
- os: windows-2025-vs2026
openmp: "OFF"
component: fastscan
runs-on: ${{ matrix.os }}
timeout-minutes: 45
steps:
Expand Down Expand Up @@ -429,6 +446,7 @@ jobs:
run: >-
cmake "-DRABITQ_SOURCE_DIR=$PWD"
"-DWORK_DIR=$env:RUNNER_TEMP/rabitq-core"
-DRABITQ_COMPONENT=${{ matrix.component }}
-DRABITQ_USE_OPENMP=${{ matrix.openmp }}
-P tests/core_consumer/run.cmake
- name: Report compiler cache statistics
Expand Down
19 changes: 17 additions & 2 deletions tests/core_consumer/run.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Run with -DRABITQ_SOURCE_DIR=<checkout> -DWORK_DIR=<external directory> -P run.cmake.
# Optional -DRABITQ_COMPONENT=core|fastscan selects one independent partition.
cmake_minimum_required(VERSION 3.20)
if(NOT RABITQ_SOURCE_DIR OR NOT WORK_DIR)
message(FATAL_ERROR "RABITQ_SOURCE_DIR and WORK_DIR are required")
Expand Down Expand Up @@ -26,7 +27,15 @@ endif()
if(DEFINED OpenMP_ROOT)
list(APPEND configure_options "-DOpenMP_ROOT=${OpenMP_ROOT}")
endif()
foreach(component IN ITEMS core fastscan)
# Windows CI runs the two independent component checks on separate runners.
set(components core fastscan)
if(DEFINED RABITQ_COMPONENT)
if(NOT RABITQ_COMPONENT IN_LIST components)
message(FATAL_ERROR "RABITQ_COMPONENT must be core or fastscan")
endif()
set(components "${RABITQ_COMPONENT}")
endif()
foreach(component IN LISTS components)
set(scan OFF)
if(component STREQUAL "fastscan")
set(scan ON)
Expand Down Expand Up @@ -87,6 +96,12 @@ foreach(component IN ITEMS core fastscan)
endforeach()
endforeach()

# The core partition owns the core-only rejection checks and core benchmark.
if(NOT "core" IN_LIST components)
message(STATUS "FastScan external consumers passed")
return()
endif()

# Configuration must fail early for combinations that cannot provide full functionality.
execute_process(COMMAND "${CMAKE_COMMAND}" -S "${RABITQ_SOURCE_DIR}"
-B "${WORK_DIR}/invalid-python" -DRABITQ_BUILD_INDEX=OFF
Expand Down Expand Up @@ -138,4 +153,4 @@ run("${CMAKE_COMMAND}" -S "${RABITQ_SOURCE_DIR}/benchmarks/core"
run("${CMAKE_COMMAND}" --build "${WORK_DIR}/benchmark" --config Release --parallel 2)
run("${CMAKE_CTEST_COMMAND}" --test-dir "${WORK_DIR}/benchmark" -C Release
--output-on-failure)
message(STATUS "Core and FastScan external consumers and core benchmark passed")
message(STATUS "External consumers and core benchmark passed for: ${components}")
Loading