From d7547eff18236cff0617c2070896af0eb5a0f79a Mon Sep 17 00:00:00 2001 From: gouyt13clear Date: Wed, 28 Oct 2026 00:02:48 +0800 Subject: [PATCH] test: reduce redundant validation and stabilize GIL checks --- .github/scripts/ci_changes.py | 2 + .github/scripts/test_ci_workflow_contracts.py | 53 +++++++------- .github/workflows/python.yml | 2 +- pyproject.toml | 13 +++- scripts/check-python.sh | 2 +- tests/README.md | 10 ++- tests/common/test_data.cpp | 73 ------------------- tests/common/test_data.hpp | 29 -------- tests/common/test_helpers.hpp | 64 ---------------- tests/python/test_concurrent_indexes.py | 33 ++++++--- tests/python/test_editor_support.py | 54 -------------- tests/python/test_hnsw.py | 20 ++--- tests/python/test_index_counts.py | 5 +- tests/python/test_ivf_initializers.py | 13 +++- tests/test_mypy.py | 58 +++++++++++++++ tests/unit/rabitqlib/utils/rotator_test.cpp | 14 +--- 16 files changed, 150 insertions(+), 295 deletions(-) create mode 100644 tests/test_mypy.py diff --git a/.github/scripts/ci_changes.py b/.github/scripts/ci_changes.py index d82cfe15..d05596a3 100644 --- a/.github/scripts/ci_changes.py +++ b/.github/scripts/ci_changes.py @@ -154,6 +154,8 @@ def classify(paths): selected.add("python_quality") elif path == "tests/test_pyright.py": selected.update(("python_quality", "python_build")) + elif path == "tests/test_mypy.py": + selected.update(("python_quality", "python_build", "wheels")) elif path.startswith("tests/python/"): selected.update(("python_build", "wheels")) if path == "tests/python/conftest.py": diff --git a/.github/scripts/test_ci_workflow_contracts.py b/.github/scripts/test_ci_workflow_contracts.py index c3ba2038..274b3001 100644 --- a/.github/scripts/test_ci_workflow_contracts.py +++ b/.github/scripts/test_ci_workflow_contracts.py @@ -1,12 +1,12 @@ """Check lightweight CI coverage and workflow scheduling contracts.""" +import fnmatch import json import os import re import subprocess import sys import tempfile -import textwrap import tomllib import unittest from pathlib import Path @@ -23,7 +23,28 @@ def test_installed_typing_checks_are_required(self): wheel_dependencies = config["tool"]["cibuildwheel"]["test-requires"] checker = [value for value in test_dependencies if value.startswith("mypy==")] self.assertEqual(len(checker), 1) - self.assertIn(checker[0], wheel_dependencies) + self.assertFalse(any(value.startswith("mypy") for value in wheel_dependencies)) + wheels = config["tool"]["cibuildwheel"] + typing_overrides = [ + o for o in wheels["overrides"] if checker[0] in o.get("test-requires", []) + ] + self.assertEqual(len(typing_overrides), 1) + override = typing_overrides[0] + self.assertEqual(override["inherit"]["test-requires"], "append") + self.assertEqual(override["inherit"]["test-command"], "append") + self.assertIn("pytest {project}/tests/test_mypy.py", override["test-command"]) + self.assertEqual( + { + identifier + for identifier in wheels["build"].split() + if fnmatch.fnmatchcase(identifier, override["select"]) + }, + {f"cp{abi}-manylinux_x86_64" for abi in (311, 312, 313, 314)}, + ) + self.assertIn( + "--ignore={project}/tests/python/test_benchmark_protocol.py", + wheels["test-command"], + ) self.assertFalse( any(value.startswith("mypy") for value in config["project"]["dependencies"]) ) @@ -32,7 +53,10 @@ def test_installed_typing_checks_are_required(self): " python-result:\n", 1 )[0] self.assertIn('pip install ".[test,typing]"', build) - self.assertIn("python -m pytest tests/python/test_editor_support.py", build) + self.assertIn( + "python -m pytest tests/python/test_editor_support.py tests/test_mypy.py", + build, + ) self.assertNotIn("continue-on-error", build) self.assertIn( "pytest {project}/tests/python", @@ -45,33 +69,12 @@ def test_installed_typing_checks_are_required(self): for path in ( "python_bindings/__init__.pyi", "tests/python/typing/public_api.py", + "tests/test_mypy.py", ): with self.subTest(path=path): self.assertLessEqual( {"python_build", "python_quality", "wheels"}, classify([path]) ) - # Exercise the actual aggregator script, so a failed selected matrix - # cannot be reported as a successful Python validation check. - result = workflow.split(" python-result:\n", 1)[1].split( - " python-windows:\n", 1 - )[0] - self.assertIn("needs: [changes, python-build]", result) - script = textwrap.dedent(result.split(" run: |\n", 1)[1]) - for matrix_result in ("success", "failure", "cancelled", "skipped"): - with self.subTest(matrix_result=matrix_result): - checked = subprocess.run( - ["bash", "-e", "-c", script], - env=dict( - os.environ, - CHANGES_RESULT="success", - PYTHON_SELECTED="true", - MATRIX_RESULT=matrix_result, - ), - capture_output=True, - text=True, - check=False, - ) - self.assertEqual(checked.returncode == 0, matrix_result == "success") def test_pyright_checks_are_required_and_scoped(self): config = tomllib.loads((ROOT / "pyproject.toml").read_text()) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 39c751da..361d8f64 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -76,7 +76,7 @@ jobs: - name: Check installed Python types and editor metadata env: OMP_NUM_THREADS: "2" - run: python -m pytest tests/python/test_editor_support.py --tb=short -q + run: python -m pytest tests/python/test_editor_support.py tests/test_mypy.py --tb=short -q - name: Check Pyright public API and test code run: python -m pytest tests/test_pyright.py --tb=short -q diff --git a/pyproject.toml b/pyproject.toml index e5a77228..46a246da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,17 @@ cmake.args = [ build = "cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp314-manylinux_x86_64 cp311-manylinux_aarch64 cp312-manylinux_aarch64 cp313-manylinux_aarch64 cp314-manylinux_aarch64 cp311-win_amd64 cp312-win_amd64 cp313-win_amd64 cp314-win_amd64 cp311-macosx_arm64 cp312-macosx_arm64 cp313-macosx_arm64 cp314-macosx_arm64" build-verbosity = 1 environment = { CMAKE_ARGS = "-DRABITQ_ENABLE_NATIVE_OPTIMIZATION=OFF" } -test-requires = ["pytest", "mypy==2.4.0"] -test-command = "pytest {project}/tests/python --ignore={project}/tests/python/test_compare_with_faiss.py --ignore={project}/tests/python/test_cpp_clustering_examples.py --tb=short -q" +test-requires = ["pytest"] +test-command = "pytest {project}/tests/python --ignore={project}/tests/python/test_compare_with_faiss.py --ignore={project}/tests/python/test_cpp_clustering_examples.py --ignore={project}/tests/python/test_benchmark_protocol.py --tb=short -q" + +# Static typing runs once per wheel ABI. Every platform still checks its installed +# declarations, runtime signatures, documentation and package metadata. +[[tool.cibuildwheel.overrides]] +select = "*-manylinux_x86_64" +inherit.test-requires = "append" +test-requires = ["mypy==2.4.0"] +inherit.test-command = "append" +test-command = "pytest {project}/tests/test_mypy.py --tb=short -q" [tool.cibuildwheel.linux] manylinux-aarch64-image = "manylinux_2_28" diff --git a/scripts/check-python.sh b/scripts/check-python.sh index 59092f4e..aed701a4 100755 --- a/scripts/check-python.sh +++ b/scripts/check-python.sh @@ -18,6 +18,6 @@ if [[ "$($ruff --version)" != "ruff 0.16.1" ]]; then fi cd "$repo_root" -paths=(python_bindings sample/python tests/python tests/test_pyright.py benchmarks) +paths=(python_bindings sample/python tests/python tests/test_mypy.py tests/test_pyright.py benchmarks) "$ruff" format --check "${paths[@]}" "$ruff" check "${paths[@]}" diff --git a/tests/README.md b/tests/README.md index 70375d20..9bc31251 100644 --- a/tests/README.md +++ b/tests/README.md @@ -332,12 +332,16 @@ Array shapes and value ranges remain runtime checks. After rebuilding and installing with the `test` extra, run: ```bash -python -m pytest tests/python/test_editor_support.py -q +python -m pytest tests/python/test_editor_support.py tests/test_mypy.py -q ``` The suite checks installed declarations, signatures, documentation, examples, and -valid/invalid calls with strict mypy. CI requires it; missing mypy fails the test. -Keep the mypy pins in the `test` extra and wheel test requirements synchronized. +runtime metadata on every wheel. Strict mypy valid/invalid-call checks live in +`tests/test_mypy.py`, run for source installs and each CPython ABI on Linux x86-64 +wheels. Other wheel platforms do not repeat static analysis. CI requires mypy +for these selected checks; missing mypy fails the test. Keep its pins in the +`test` extra and Linux x86-64 wheel override synchronized. +Benchmark protocol tests run in Python quality CI, separately from wheel tests. CI also checks the public type examples and test code with Pyright. With Node 24 and the `test,typing` extras installed in the same environment, run: diff --git a/tests/common/test_data.cpp b/tests/common/test_data.cpp index b2f55679..d5938467 100644 --- a/tests/common/test_data.cpp +++ b/tests/common/test_data.cpp @@ -1,6 +1,5 @@ #include "test_data.hpp" -#include #include #include #include @@ -20,76 +19,4 @@ std::vector TestDataGenerator::GenerateRandomVector( return vec; } -std::vector TestDataGenerator::GenerateNormalizedVector( - size_t dim, unsigned int seed -) { - auto vec = GenerateRandomVector(dim, -1.0f, 1.0f, seed); - - // Calculate L2 norm - float norm = 0.0f; - for (float val : vec) { - norm += val * val; - } - norm = std::sqrt(norm); - - // Normalize - if (norm > 1e-10f) { - for (float& val : vec) { - val /= norm; - } - } - - return vec; -} - -std::vector> TestDataGenerator::GenerateRandomVectors( - size_t num_vectors, size_t dim, float min, float max, unsigned int seed -) { - std::vector> vectors; - vectors.reserve(num_vectors); - - for (size_t i = 0; i < num_vectors; ++i) { - vectors.push_back(GenerateRandomVector(dim, min, max, seed + i)); - } - - return vectors; -} - -std::vector TestDataGenerator::GenerateGaussianVector( - size_t dim, float mean, float stddev, unsigned int seed -) { - std::mt19937 rng(seed); - std::normal_distribution dist(mean, stddev); - - std::vector vec(dim); - for (size_t i = 0; i < dim; ++i) { - vec[i] = dist(rng); - } - return vec; -} - -std::vector TestDataGenerator::GenerateSimpleVector(size_t dim) { - std::vector vec(dim); - for (size_t i = 0; i < dim; ++i) { - vec[i] = static_cast(i % 10) / 10.0f; - } - return vec; -} - -std::vector TestDataGenerator::GenerateZeroVector(size_t dim) { - return std::vector(dim, 0.0f); -} - -std::vector TestDataGenerator::GenerateOnesVector(size_t dim) { - return std::vector(dim, 1.0f); -} - -std::vector TestDataGenerator::GenerateIncrementalVector(size_t dim) { - std::vector vec(dim); - for (size_t i = 0; i < dim; ++i) { - vec[i] = static_cast(i); - } - return vec; -} - } // namespace rabitq_test diff --git a/tests/common/test_data.hpp b/tests/common/test_data.hpp index 673e8b8c..955e7028 100644 --- a/tests/common/test_data.hpp +++ b/tests/common/test_data.hpp @@ -12,35 +12,6 @@ class TestDataGenerator { static std::vector GenerateRandomVector( size_t dim, float min = -1.0f, float max = 1.0f, unsigned int seed = 42 ); - - // Generate random normalized vector (unit length) - static std::vector GenerateNormalizedVector(size_t dim, unsigned int seed = 42); - - // Generate multiple random vectors - static std::vector> GenerateRandomVectors( - size_t num_vectors, - size_t dim, - float min = -1.0f, - float max = 1.0f, - unsigned int seed = 42 - ); - - // Generate Gaussian distributed vector - static std::vector GenerateGaussianVector( - size_t dim, float mean = 0.0f, float stddev = 1.0f, unsigned int seed = 42 - ); - - // Generate a simple test vector with known values - static std::vector GenerateSimpleVector(size_t dim); - - // Generate zero vector - static std::vector GenerateZeroVector(size_t dim); - - // Generate vector with all ones - static std::vector GenerateOnesVector(size_t dim); - - // Generate vector with incremental values [0, 1, 2, 3, ...] - static std::vector GenerateIncrementalVector(size_t dim); }; } // namespace rabitq_test diff --git a/tests/common/test_helpers.hpp b/tests/common/test_helpers.hpp index df442360..4a68ed10 100644 --- a/tests/common/test_helpers.hpp +++ b/tests/common/test_helpers.hpp @@ -4,7 +4,6 @@ #include #include -#include namespace rabitq_test { @@ -13,74 +12,11 @@ inline bool FloatNearlyEqual(float a, float b, float epsilon = 1e-5f) { return std::abs(a - b) < epsilon; } -inline bool DoubleNearlyEqual(double a, double b, double epsilon = 1e-10) { - return std::abs(a - b) < epsilon; -} - -// Vector comparison -inline bool VectorsNearlyEqual( - const float* a, const float* b, size_t size, float epsilon = 1e-5f -) { - for (size_t i = 0; i < size; ++i) { - if (!FloatNearlyEqual(a[i], b[i], epsilon)) { - return false; - } - } - return true; -} - -// Calculate relative error -inline float RelativeError(float actual, float expected) { - if (std::abs(expected) < 1e-10f) { - return std::abs(actual - expected); - } - return std::abs((actual - expected) / expected); -} - -// Calculate mean squared error -inline float MeanSquaredError(const float* a, const float* b, size_t size) { - float mse = 0.0f; - for (size_t i = 0; i < size; ++i) { - float diff = a[i] - b[i]; - mse += diff * diff; - } - return mse / static_cast(size); -} - -// Calculate dot product -inline float DotProduct(const float* a, const float* b, size_t size) { - float result = 0.0f; - for (size_t i = 0; i < size; ++i) { - result += a[i] * b[i]; - } - return result; -} - -// Calculate L2 distance -inline float L2Distance(const float* a, const float* b, size_t size) { - float sum = 0.0f; - for (size_t i = 0; i < size; ++i) { - float diff = a[i] - b[i]; - sum += diff * diff; - } - return std::sqrt(sum); -} - -// Custom assertion macros -#define ASSERT_FLOAT_NEARLY_EQUAL(a, b, epsilon) \ - ASSERT_TRUE(rabitq_test::FloatNearlyEqual((a), (b), (epsilon))) \ - << "Expected: " << (a) << " to be nearly equal to " << (b) \ - << " (epsilon: " << (epsilon) << "), but difference was " << std::abs((a) - (b)) - #define EXPECT_FLOAT_NEARLY_EQUAL(a, b, epsilon) \ EXPECT_TRUE(rabitq_test::FloatNearlyEqual((a), (b), (epsilon))) \ << "Expected: " << (a) << " to be nearly equal to " << (b) \ << " (epsilon: " << (epsilon) << "), but difference was " << std::abs((a) - (b)) -#define ASSERT_VECTORS_NEARLY_EQUAL(a, b, size, epsilon) \ - ASSERT_TRUE(rabitq_test::VectorsNearlyEqual((a), (b), (size), (epsilon))) \ - << "Vectors are not nearly equal (epsilon: " << (epsilon) << ")" - } // namespace rabitq_test #endif // RABITQ_TEST_HELPERS_HPP diff --git a/tests/python/test_concurrent_indexes.py b/tests/python/test_concurrent_indexes.py index e4e2eb52..ab5f3372 100644 --- a/tests/python/test_concurrent_indexes.py +++ b/tests/python/test_concurrent_indexes.py @@ -3,6 +3,7 @@ import sys from concurrent.futures import ThreadPoolExecutor from threading import Barrier, Event, Thread +from time import monotonic, sleep import numpy as np import pytest @@ -171,29 +172,39 @@ def test_active_mutation_rejects_reads_and_other_mutations(kind, tmp_path): def test_search_releases_gil(kind): index, data, _ = make_index(kind) queries = np.tile(data, (32, 1)) - go, finished = Event(), Event() - observed = [] + ready, progress = Event(), Event() + active = stopped = False + options = parameters(kind, True) def python_worker(): - go.wait(timeout=10) - observed.append(not finished.is_set()) + ready.set() + while not active and not stopped: + sleep(0.001) + if active: + progress.set() worker = Thread(target=python_worker) worker.start() previous = sys.getswitchinterval() try: - # Prevent an interpreter timeslice between go.set() and the native call. - # The worker must run while that call has released the GIL. + assert ready.wait(timeout=10) + # Suppress interpreter timeslices across the whole probe. Repeated native + # calls allow a delayed worker to run; one short search is not a deadline. sys.setswitchinterval(100) - go.set() - index.search(queries, 5, num_threads=1, **parameters(kind, True)) - finished.set() + deadline = monotonic() + 5 + # Arm with a Python assignment: signaling an Event here could itself + # release the GIL on a contended lock and falsely satisfy the probe. + active = True + while not progress.is_set() and monotonic() < deadline: + index.search(queries, 5, num_threads=1, **options) + # Capture before restoring normal scheduling or joining the worker. + released = progress.is_set() finally: + stopped = True sys.setswitchinterval(previous) - finished.set() worker.join(timeout=10) assert not worker.is_alive() - assert observed == [True] + assert released, "Python worker made no progress during native searches" def test_native_exception_reacquires_gil_and_releases_access(): diff --git a/tests/python/test_editor_support.py b/tests/python/test_editor_support.py index df23aef5..0e2d0e91 100644 --- a/tests/python/test_editor_support.py +++ b/tests/python/test_editor_support.py @@ -3,8 +3,6 @@ import ast import doctest import re -import subprocess -import sys from pathlib import Path import pytest @@ -145,58 +143,6 @@ def test_documentation_examples(): assert runner.tries > 0 -def test_static_public_usage(tmp_path): - fixture = Path(__file__).parent / "typing" / "public_api.py" - result = subprocess.run( - [ - sys.executable, - "-m", - "mypy", - "--strict", - "--no-incremental", - "--cache-dir", - str(tmp_path / "cache"), - str(fixture), - ], - text=True, - capture_output=True, - check=False, - ) - assert result.returncode == 0, result.stdout + result.stderr - # Removing ignores must expose the deliberately invalid calls, proving the - # package is typed rather than silently treated as Any. - invalid = tmp_path / "invalid.py" - invalid.write_text(re.sub(r" # type: ignore\[[^]]+\]", "", fixture.read_text())) - result = subprocess.run( - [ - sys.executable, - "-m", - "mypy", - "--strict", - "--no-incremental", - "--cache-dir", - str(tmp_path / "cache"), - str(invalid), - ], - text=True, - capture_output=True, - check=False, - ) - assert result.returncode == 1, result.stdout + result.stderr - expected_lines = { - number - for number, line in enumerate(fixture.read_text().splitlines(), start=1) - if "# type: ignore[" in line - } - error_lines = { - int(number) - for number in re.findall( - rf"{re.escape(str(invalid))}:(\d+): error:", result.stdout - ) - } - assert error_lines == expected_lines, result.stdout - - @pytest.mark.parametrize("kind", ["ivf", "hnsw", "symqg"]) def test_documented_search_result_types(kind): import numpy as np diff --git a/tests/python/test_hnsw.py b/tests/python/test_hnsw.py index fc8efb72..1b4bd5e2 100644 --- a/tests/python/test_hnsw.py +++ b/tests/python/test_hnsw.py @@ -245,23 +245,23 @@ def partial_hnsw(base_data, clusters): return idx -def test_add_returns_dense_labels(partial_hnsw, base_data, clusters): +def test_add_returns_dense_labels_and_preserves_search( + partial_hnsw, base_data, clusters +): _, cluster_ids = clusters assert partial_hnsw.num_points == 400 + before, _ = partial_hnsw.search(base_data[:10], k=1, ef=N_VECTORS) ids = partial_hnsw.add(base_data[400:], cluster_ids=cluster_ids[400:]) np.testing.assert_array_equal(ids, np.arange(400, N_VECTORS)) assert partial_hnsw.num_points == N_VECTORS - - -def test_add_makes_points_searchable(partial_hnsw, base_data, clusters): # A sparse graph can isolate the odd point, as it can after build, so this # asks for near-perfect self-retrieval rather than exact. - _, cluster_ids = clusters - partial_hnsw.add(base_data[400:], cluster_ids=cluster_ids[400:]) ids, distances = partial_hnsw.search(base_data[400:], k=1, ef=N_VECTORS) found = np.count_nonzero(ids[:, 0] == np.arange(400, N_VECTORS)) assert found >= 0.95 * (N_VECTORS - 400) assert np.isfinite(distances).all() + after, _ = partial_hnsw.search(base_data[:10], k=1, ef=N_VECTORS) + np.testing.assert_array_equal(before[:, 0], after[:, 0]) def test_add_into_a_dense_graph_retrieves_every_point(base_data, clusters): @@ -284,14 +284,6 @@ def test_add_without_cluster_ids_preserves_self_retrieval(partial_hnsw, base_dat assert np.count_nonzero(found[:, 0] == np.arange(400, N_VECTORS)) >= 0.95 * 100 -def test_add_preserves_earlier_points(partial_hnsw, base_data, clusters): - _, cluster_ids = clusters - before, _ = partial_hnsw.search(base_data[:10], k=1, ef=N_VECTORS) - partial_hnsw.add(base_data[400:], cluster_ids=cluster_ids[400:]) - after, _ = partial_hnsw.search(base_data[:10], k=1, ef=N_VECTORS) - np.testing.assert_array_equal(before[:, 0], after[:, 0]) - - def test_add_beyond_capacity_raises(partial_hnsw, base_data): with pytest.raises(ValueError, match="capacity"): partial_hnsw.add(base_data) diff --git a/tests/python/test_index_counts.py b/tests/python/test_index_counts.py index 8dba7061..a9448d9a 100644 --- a/tests/python/test_index_counts.py +++ b/tests/python/test_index_counts.py @@ -14,13 +14,14 @@ def check_counts(index, capacity, stored, active): assert 0 <= index.num_points <= index.num_stored_points <= index.max_elements -@pytest.mark.parametrize("metric", ["l2", "ip"]) +# Metric forwarding is covered by portable-index tests; counts are metric-independent. @pytest.mark.parametrize( "kind,bits", [("ivf", 1), ("ivf", 4), ("ivf", 32), ("hnsw", 1), ("hnsw", 9)] + [("symqg", bits) for bits in (0, 4, 8)], ) -def test_count_contract_through_lifecycle(tmp_path, kind, bits, metric): +def test_count_contract_through_lifecycle(tmp_path, kind, bits): + metric = "l2" count, dim = 40, 65 data = np.random.default_rng(42).normal(size=(count + 4, dim)).astype(np.float32) data /= np.linalg.norm(data, axis=1, keepdims=True) diff --git a/tests/python/test_ivf_initializers.py b/tests/python/test_ivf_initializers.py index 44916408..78e28cf2 100644 --- a/tests/python/test_ivf_initializers.py +++ b/tests/python/test_ivf_initializers.py @@ -94,10 +94,17 @@ def test_hnsw_rejects_corrupt_sidecar(tmp_path, metric, damage): np.testing.assert_array_equal(left, right) -@pytest.mark.parametrize("metric", ["l2", "ip"]) # Routing is independent of bit width; retain each distinct reranking storage path. -@pytest.mark.parametrize("bits", [1, 4, 32]) -@pytest.mark.parametrize("initializer", ["auto", "flat", "flat_rabitq", "hnsw"]) +@pytest.mark.parametrize( + "initializer,bits,metric", + [ + (mode, bits, metric) + for mode in ("flat", "flat_rabitq", "hnsw") + for bits in (1, 4, 32) + for metric in ("l2", "ip") + ] + + [("auto", 4, "l2")], +) def test_initializer_roundtrip_and_updates(tmp_path, metric, bits, initializer): rng = np.random.default_rng(773) dim, count = 65, 33 diff --git a/tests/test_mypy.py b/tests/test_mypy.py new file mode 100644 index 00000000..224de9a0 --- /dev/null +++ b/tests/test_mypy.py @@ -0,0 +1,58 @@ +"""Static public usage checked against an installed package with mypy.""" + +import re +import subprocess +import sys +from pathlib import Path + + +def test_static_public_usage(tmp_path): + fixture = Path(__file__).parent / "python" / "typing" / "public_api.py" + result = subprocess.run( + [ + sys.executable, + "-m", + "mypy", + "--strict", + "--no-incremental", + "--cache-dir", + str(tmp_path / "cache"), + str(fixture), + ], + text=True, + capture_output=True, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr + # Removing ignores must expose the deliberately invalid calls, proving the + # package is typed rather than silently treated as Any. + invalid = tmp_path / "invalid.py" + invalid.write_text(re.sub(r" # type: ignore\[[^]]+\]", "", fixture.read_text())) + result = subprocess.run( + [ + sys.executable, + "-m", + "mypy", + "--strict", + "--no-incremental", + "--cache-dir", + str(tmp_path / "cache"), + str(invalid), + ], + text=True, + capture_output=True, + check=False, + ) + assert result.returncode == 1, result.stdout + result.stderr + expected_lines = { + number + for number, line in enumerate(fixture.read_text().splitlines(), start=1) + if "# type: ignore[" in line + } + error_lines = { + int(number) + for number in re.findall( + rf"{re.escape(str(invalid))}:(\d+): error:", result.stdout + ) + } + assert error_lines == expected_lines, result.stdout diff --git a/tests/unit/rabitqlib/utils/rotator_test.cpp b/tests/unit/rabitqlib/utils/rotator_test.cpp index 3e7550ea..110d1fe2 100644 --- a/tests/unit/rabitqlib/utils/rotator_test.cpp +++ b/tests/unit/rabitqlib/utils/rotator_test.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -23,18 +22,7 @@ using namespace rabitq_test; class RotatorTest : public ::testing::Test { protected: - void SetUp() override { - dim = 128; - test_data = TestDataGenerator::GenerateRandomVector(dim, -1.0f, 1.0f, 42); - } - - void TearDown() override { - // Clean up any temporary files - std::remove("test_rotator.bin"); - } - - size_t dim; - std::vector test_data; + size_t dim = 128; }; // Test that FhtKacRotator is chosen by default