Initial commit

Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai>
Co-Authored-By: Laure Hugo <laure.hugo@mistral.ai>
Co-Authored-By: Benjamin Trom <benjamin.trom@mistral.ai>
Co-Authored-By: Mathias Gesbert <mathias.gesbert@ext.mistral.ai>
Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai>
Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai>
Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-Authored-By: Valentin Berard <val@mistral.ai>
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Quentin Torroba 2025-12-09 13:13:22 +01:00 committed by Quentin Torroba
commit fa15fc977b
200 changed files with 30484 additions and 0 deletions

View file

@ -0,0 +1,231 @@
from __future__ import annotations
from collections.abc import Callable, Generator
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import time
import pytest
from vibe.core.autocompletion.file_indexer import FileIndexer
# This suite runs against the real filesystem and watcher. A faked store/watcher
# split would be faster to unit-test, but given time constraints and the low churn
# expected for this feature, integration coverage was chosen as a trade-off.
@pytest.fixture
def file_indexer() -> Generator[FileIndexer]:
indexer = FileIndexer()
yield indexer
indexer.shutdown()
def _wait_for(condition: Callable[[], bool], timeout=3.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if condition():
return True
time.sleep(0.05)
return False
def test_updates_index_on_file_creation(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
file_indexer.get_index(Path("."))
target = tmp_path / "new_file.py"
target.write_text("", encoding="utf-8")
assert _wait_for(
lambda: any(
entry.rel == target.name for entry in file_indexer.get_index(Path("."))
)
)
def test_updates_index_on_file_deletion(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
target = tmp_path / "new_file.py"
target.write_text("", encoding="utf-8")
file_indexer.get_index(Path("."))
target.unlink()
assert _wait_for(
lambda: all(
entry.rel != target.name for entry in file_indexer.get_index(Path("."))
)
)
def test_updates_index_on_file_rename(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
old_file = tmp_path / "old_name.py"
old_file.write_text("", encoding="utf-8")
file_indexer.get_index(Path("."))
new_file = tmp_path / "new_name.py"
old_file.rename(new_file)
assert _wait_for(
lambda: all(
entry.rel != old_file.name for entry in file_indexer.get_index(Path("."))
)
and any(
entry.rel == new_file.name for entry in file_indexer.get_index(Path("."))
)
)
def test_updates_index_on_folder_rename(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
old_folder = tmp_path / "old_folder"
old_folder.mkdir()
number_of_files = 5
file_names = [f"file{i}.py" for i in range(1, number_of_files + 1)]
old_file_paths = [old_folder / name for name in file_names]
for file_path in old_file_paths:
file_path.write_text("", encoding="utf-8")
file_indexer.get_index(Path("."))
new_folder = tmp_path / "new_folder"
old_folder.rename(new_folder)
assert _wait_for(
lambda: (
entries := file_indexer.get_index(Path(".")),
all(not entry.rel.startswith("old_folder/") for entry in entries)
and all(
any(entry.rel == f"new_folder/{name}" for entry in entries)
for name in file_names
),
)[1]
)
def test_updates_index_incrementally_by_default(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
file_indexer.get_index(Path("."))
rebuilds_before = file_indexer.stats.rebuilds
incremental_before = file_indexer.stats.incremental_updates
target = tmp_path / "stats_file.py"
target.write_text("", encoding="utf-8")
assert _wait_for(
lambda: any(
entry.rel == target.name for entry in file_indexer.get_index(Path("."))
)
)
assert file_indexer.stats.rebuilds == rebuilds_before
assert file_indexer.stats.incremental_updates >= incremental_before + 1
def test_rebuilds_index_when_mass_change_threshold_is_exceeded(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
mass_change_threshold = 5
# in an ideal world, we would use "threshold + 1", but in reality, we need to test with a
# number of files important enough to MAKE SURE that a batch of >= threshold events will be
# detected by the watcher
number_of_files = mass_change_threshold * 3
monkeypatch.chdir(tmp_path)
indexer = FileIndexer(mass_change_threshold=mass_change_threshold)
try:
indexer.get_index(Path("."))
rebuilds_before = indexer.stats.rebuilds
ThreadPoolExecutor(max_workers=number_of_files).map(
lambda i: (tmp_path / f"bulk{i}.py").write_text("", encoding="utf-8"),
range(number_of_files),
)
assert _wait_for(lambda: len(indexer.get_index(Path("."))) == number_of_files)
# we do not assert that "incremental_updates" did not change,
# as the watcher potentially reported some batches of events that were
# smaller than the threshold
assert indexer.stats.rebuilds >= rebuilds_before + 1
finally:
indexer.shutdown()
def test_switching_between_roots_restarts_index(
tmp_path: Path,
tmp_path_factory: pytest.TempPathFactory,
monkeypatch: pytest.MonkeyPatch,
file_indexer: FileIndexer,
) -> None:
first_root = tmp_path
second_root = tmp_path_factory.mktemp("second-root")
(first_root / "first.py").write_text("", encoding="utf-8")
(second_root / "second.py").write_text("", encoding="utf-8")
monkeypatch.chdir(first_root)
assert _wait_for(
lambda: any(
entry.rel == "first.py" for entry in file_indexer.get_index(Path("."))
)
)
monkeypatch.chdir(second_root)
assert _wait_for(
lambda: all(
entry.rel != "first.py" for entry in file_indexer.get_index(Path("."))
)
)
assert _wait_for(
lambda: any(
entry.rel == "second.py" for entry in file_indexer.get_index(Path("."))
)
)
def test_watcher_failure_does_not_break_existing_index(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, file_indexer: FileIndexer
) -> None:
monkeypatch.chdir(tmp_path)
seed = tmp_path / "seed.py"
seed.write_text("", encoding="utf-8")
file_indexer.get_index(Path("."))
def boom(*_: object, **__: object) -> None:
raise RuntimeError("boom")
monkeypatch.setattr(file_indexer._store, "apply_changes", boom)
(tmp_path / "new_file.py").write_text("", encoding="utf-8")
assert _wait_for(
lambda: (
entries := file_indexer.get_index(Path(".")),
# new file was not added: watcher failed
all(entry.rel != "new_file.py" for entry in entries)
# but the existing index is still intact
and all(entry.rel == "seed.py" for entry in entries),
)[1]
)
def test_shutdown_cleans_up_resources(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.chdir(tmp_path)
(tmp_path / "test.txt").write_text("", encoding="utf-8")
file_indexer = FileIndexer()
file_indexer.get_index(Path("."))
file_indexer.shutdown()
assert file_indexer.get_index(Path(".")) == []