|
1 | 1 | import os |
| 2 | +import shutil |
2 | 3 | import sqlite3 |
3 | 4 | from pathlib import Path |
4 | 5 |
|
@@ -206,6 +207,52 @@ def test_query_empty_preserves_embeddings_outer_shape_when_requested(): |
206 | 207 | assert not_requested.embeddings is None |
207 | 208 |
|
208 | 209 |
|
| 210 | +def test_chroma_close_palace_releases_sqlite_lock_for_reopen(tmp_path): |
| 211 | + """close_palace must release chromadb's rust-side SQLite file lock so |
| 212 | + a fresh PersistentClient on the same path after shutil.rmtree can |
| 213 | + write without hitting SQLITE_READONLY_DBMOVED.""" |
| 214 | + backend = ChromaBackend() |
| 215 | + palace_path = tmp_path / "palace-a" |
| 216 | + ref = PalaceRef(id=str(palace_path), local_path=str(palace_path)) |
| 217 | + |
| 218 | + col = backend.get_collection(palace=ref, collection_name="mempalace_drawers", create=True) |
| 219 | + col.upsert(documents=["hello"], ids=["a"], metadatas=[{"k": "v"}]) |
| 220 | + |
| 221 | + backend.close_palace(ref) |
| 222 | + shutil.rmtree(palace_path) |
| 223 | + |
| 224 | + col = backend.get_collection(palace=ref, collection_name="mempalace_drawers", create=True) |
| 225 | + col.upsert(documents=["world"], ids=["b"], metadatas=[{"k": "v2"}]) |
| 226 | + assert col.count() == 1 |
| 227 | + |
| 228 | + |
| 229 | +def test_chroma_close_releases_all_cached_clients(tmp_path): |
| 230 | + """close() must release every cached client's SQLite file lock so any |
| 231 | + of their palace paths can be reopened by a fresh backend in the same |
| 232 | + process.""" |
| 233 | + backend = ChromaBackend() |
| 234 | + palace_a = tmp_path / "palace-a" |
| 235 | + palace_b = tmp_path / "palace-b" |
| 236 | + ref_a = PalaceRef(id=str(palace_a), local_path=str(palace_a)) |
| 237 | + ref_b = PalaceRef(id=str(palace_b), local_path=str(palace_b)) |
| 238 | + |
| 239 | + for ref in (ref_a, ref_b): |
| 240 | + backend.get_collection(palace=ref, collection_name="mempalace_drawers", create=True).upsert( |
| 241 | + documents=["x"], ids=["x"], metadatas=[{"k": "v"}] |
| 242 | + ) |
| 243 | + |
| 244 | + backend.close() |
| 245 | + |
| 246 | + for path in (palace_a, palace_b): |
| 247 | + shutil.rmtree(path) |
| 248 | + ref = PalaceRef(id=str(path), local_path=str(path)) |
| 249 | + fresh = ChromaBackend() |
| 250 | + col = fresh.get_collection(palace=ref, collection_name="mempalace_drawers", create=True) |
| 251 | + col.upsert(documents=["y"], ids=["y"], metadatas=[{"k": "v2"}]) |
| 252 | + assert col.count() == 1 |
| 253 | + fresh.close() |
| 254 | + |
| 255 | + |
209 | 256 | def test_chroma_cache_invalidates_when_db_file_missing(tmp_path): |
210 | 257 | """A palace rebuild that removes chroma.sqlite3 must drop the stale cache. |
211 | 258 |
|
|
0 commit comments