From 03436ff3d9f26bcbaec16ea34e025210c26907dc Mon Sep 17 00:00:00 2001 From: lochhh Date: Thu, 23 Jul 2026 14:51:52 +0100 Subject: [PATCH 1/4] Test `StorageBackend._full_path` with with native Windows separators (backslash) --- tests/unit/test_storage_adapter.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/test_storage_adapter.py b/tests/unit/test_storage_adapter.py index a8ef4a99a..e554a78f0 100644 --- a/tests/unit/test_storage_adapter.py +++ b/tests/unit/test_storage_adapter.py @@ -193,6 +193,16 @@ def test_unsupported_protocol_get_url_raises(self): with pytest.raises(DataJointError, match="Unsupported storage protocol"): backend.get_url("schema/file.dat") + def test_file_protocol_full_path_uses_forward_slashes(self): + """`_full_path` must return POSIX-style separators to match fsspec's + LocalFileSystem.walk() output, even on Windows, so that callers doing + string-prefix stripping (e.g. gc.py) can match paths correctly.""" + backend = StorageBackend.__new__(StorageBackend) + backend.spec = {"protocol": "file", "location": "data\\blobs"} + backend.protocol = "file" + result = backend._full_path("schema/ab/cd/hash123") + assert result == "data/blobs/schema/ab/cd/hash123" + class TestGetStoreSpecPluginDelegation: """Tests for plugin protocol handling in Config.get_store_spec().""" From 9e8f675dac415783a725c9e845d45b07024e3065 Mon Sep 17 00:00:00 2001 From: lochhh Date: Thu, 23 Jul 2026 15:02:54 +0100 Subject: [PATCH 2/4] Fix Windows backslash bug in file-protocol storage paths --- src/datajoint/storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datajoint/storage.py b/src/datajoint/storage.py index 39a21ce46..4069212fb 100644 --- a/src/datajoint/storage.py +++ b/src/datajoint/storage.py @@ -418,7 +418,7 @@ def _full_path(self, path: str | PurePosixPath) -> str: elif self.protocol == "file": location = self.spec.get("location", "") if location: - return str(Path(location) / path) + return (Path(location) / path).as_posix() return path else: return self._require_adapter().full_path(self.spec, path) From db07c10473e290920e070c09939dccb26a5ac92c Mon Sep 17 00:00:00 2001 From: lochhh Date: Thu, 23 Jul 2026 15:20:07 +0100 Subject: [PATCH 3/4] Make `_full_path` test platform-independent --- tests/unit/test_storage_adapter.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/unit/test_storage_adapter.py b/tests/unit/test_storage_adapter.py index e554a78f0..38a9d31f7 100644 --- a/tests/unit/test_storage_adapter.py +++ b/tests/unit/test_storage_adapter.py @@ -1,14 +1,17 @@ """Tests for the StorageAdapter plugin system.""" +from pathlib import PureWindowsPath + import pytest import datajoint as dj +from datajoint import storage from datajoint.errors import DataJointError from datajoint.storage import StorageBackend from datajoint.storage_adapter import ( + _COMMON_STORE_KEYS, StorageAdapter, _adapter_registry, - _COMMON_STORE_KEYS, get_storage_adapter, ) @@ -193,10 +196,11 @@ def test_unsupported_protocol_get_url_raises(self): with pytest.raises(DataJointError, match="Unsupported storage protocol"): backend.get_url("schema/file.dat") - def test_file_protocol_full_path_uses_forward_slashes(self): - """`_full_path` must return POSIX-style separators to match fsspec's - LocalFileSystem.walk() output, even on Windows, so that callers doing - string-prefix stripping (e.g. gc.py) can match paths correctly.""" + def test_file_protocol_full_path_uses_forward_slashes(self, monkeypatch): + """`_full_path` must return forward slashes to match fsspec's walk() + output (gc.py relies on this for string-prefix stripping).""" + # monkeypatch to PureWindowsPath so that the test is platform-independent + monkeypatch.setattr(storage, "Path", PureWindowsPath) backend = StorageBackend.__new__(StorageBackend) backend.spec = {"protocol": "file", "location": "data\\blobs"} backend.protocol = "file" From 67df7815e0c1265117c50fcccc2f33888c5d980f Mon Sep 17 00:00:00 2001 From: lochhh Date: Fri, 24 Jul 2026 10:45:31 +0100 Subject: [PATCH 4/4] Test `delete_schema_path` parent dir pruning --- tests/unit/test_gc.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/unit/test_gc.py diff --git a/tests/unit/test_gc.py b/tests/unit/test_gc.py new file mode 100644 index 000000000..d81762ccc --- /dev/null +++ b/tests/unit/test_gc.py @@ -0,0 +1,35 @@ +"""Tests for garbage collection module.""" + +from pathlib import PurePosixPath, PureWindowsPath +from unittest.mock import MagicMock + +import pytest + +from datajoint import storage +from datajoint.gc import GarbageCollector +from datajoint.storage import StorageBackend + + +@pytest.mark.parametrize( + ("path_cls", "location"), + [ + pytest.param(PureWindowsPath, "data\\blobs", id="windows"), + pytest.param(PurePosixPath, "data/blobs", id="posix"), + ], +) +def test_delete_schema_path_prunes_parent_dir(monkeypatch, path_cls, location): + """Pruning's `rsplit("/", 1)` needs forward slashes; `windows` param catches + a regression to `str(Path(...))`, `posix` pins the already-correct case.""" + monkeypatch.setattr(storage, "Path", path_cls) + backend = StorageBackend.__new__(StorageBackend) + backend.spec = {"protocol": "file", "location": location} + backend.protocol = "file" + backend._fs = MagicMock() + backend.fs.exists.return_value = True + # Ensure rmdir called only once: non-empty dir stops the walk one level up + backend.fs.ls.side_effect = lambda p: [] if p == "data/blobs/schema/ab/cd" else ["sibling"] + collector = GarbageCollector.__new__(GarbageCollector) + collector.backend = backend + assert collector.delete_schema_path("schema/ab/cd/hash123") is True + backend.fs.rm.assert_called_once_with("data/blobs/schema/ab/cd/hash123") + backend.fs.rmdir.assert_called_once_with("data/blobs/schema/ab/cd")