# pybsv - Backup, Synchronization, Versioning. # Copyright (C) 2025 Simon Boyé # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . """Tests for the `VirtualFileSystem` class and related stuff.""" from __future__ import annotations from datetime import UTC, datetime from io import BytesIO from pathlib import Path, PurePosixPath import pytest from bsv.vfs import FileMetadata, FsError, Permissions, VirtualFileSystem @pytest.fixture def fs(tmp_path: Path) -> VirtualFileSystem: """Fixture that returns a `VirtualFileSystem`.""" return VirtualFileSystem(tmp_path) ######################################################################################## # Permissions def test_permissions(): perm0 = Permissions(0o1234) assert perm0.unix_perms == 0o1234 perm1 = Permissions("752") assert perm1.unix_perms == 0o752 assert perm0 == perm0 assert perm0 != perm1 assert repr(perm0) == "Permissions(0o1234)" assert repr(perm1) == "Permissions(0o0752)" assert str(perm0) == "-w--wxr-T" assert str(perm1) == "rwxr-x-w-" ######################################################################################## # FileMetadata def test_file_metadata(): path = PurePosixPath("/some_dir/some_file") permissions = Permissions(0o1234) mod_time = datetime(2025, 7, 12, 12, 34, 56, tzinfo=UTC) file_md = FileMetadata( path, type="file", permissions=permissions, modification_time=mod_time, byte_size=123, ) assert file_md.path == path assert file_md.type == "file" assert file_md.permissions == permissions assert file_md.modification_time == mod_time assert file_md.byte_size == 123 assert file_md.unix_mode == "--w--wxr-T" assert not file_md.is_hidden assert file_md.is_file assert not file_md.is_dir assert not file_md.is_symlink assert not file_md.is_other dir_md = FileMetadata( path, type="dir", permissions=permissions, modification_time=mod_time, byte_size=123, ) assert dir_md.type == "dir" assert not dir_md.is_file assert dir_md.is_dir assert not dir_md.is_symlink assert not dir_md.is_other symlink_md = FileMetadata( path, type="symlink", permissions=permissions, modification_time=mod_time, byte_size=123, ) assert symlink_md.type == "symlink" assert not symlink_md.is_file assert not symlink_md.is_dir assert symlink_md.is_symlink assert not symlink_md.is_other other_md = FileMetadata( path, type="other", permissions=permissions, modification_time=mod_time, byte_size=123, ) assert other_md.type == "other" assert not other_md.is_file assert not other_md.is_dir assert not other_md.is_symlink assert other_md.is_other assert FileMetadata( PurePosixPath("/some_dir/.some_file"), type="file", permissions=permissions, modification_time=mod_time, byte_size=123, ).is_hidden def test_file_metadata_eq(): path = PurePosixPath("/some_dir/some_file") permissions = Permissions(0o1234) mod_time = datetime(2025, 7, 12, 12, 34, 56, tzinfo=UTC) md = FileMetadata( path, type="file", permissions=permissions, modification_time=mod_time, byte_size=123, ) assert ( FileMetadata( path, type="file", permissions=permissions, modification_time=mod_time, byte_size=123, ) == md ) assert ( FileMetadata( PurePosixPath("/some_dir/some_other_file"), type="file", permissions=permissions, modification_time=mod_time, byte_size=123, ) != md ) assert ( FileMetadata( path, type="dir", permissions=permissions, modification_time=mod_time, byte_size=123, ) != md ) assert ( FileMetadata( path, type="file", permissions=Permissions(0o0752), modification_time=mod_time, byte_size=123, ) != md ) assert ( FileMetadata( path, type="file", permissions=permissions, modification_time=datetime(2025, 1, 2, 3, 4, 5), byte_size=123, ) != md ) assert ( FileMetadata( path, type="file", permissions=permissions, modification_time=mod_time, byte_size=124, ) != md ) ######################################################################################## # mkdir def test_mkdir_fails_with_relative_path(fs: VirtualFileSystem): with pytest.raises(FsError): fs.mkdir("test") def test_mkdir_default(fs: VirtualFileSystem): assert not fs.exists("/test") fs.mkdir("/test") assert fs.is_dir("/test") def test_mkdir_nested_fails_without_parents(fs: VirtualFileSystem): assert not fs.exists("/foo") with pytest.raises(FsError): fs.mkdir("/foo/bar") def test_mkdir_nested(fs: VirtualFileSystem): assert not fs.exists("/test") fs.mkdir("/test/foobar", parents=True) assert fs.is_dir("/test/foobar") def test_mkdir_fails_if_exists(fs: VirtualFileSystem): assert not fs.exists("/foo") fs.mkdir("/foo") assert fs.is_dir("/foo") with pytest.raises(FsError): fs.mkdir("/foo") def test_mkdir_exists_ok(fs: VirtualFileSystem): assert not fs.exists("/test") fs.mkdir("/test") assert fs.is_dir("/test") fs.mkdir("/test", exist_ok=True) def test_mkdir_exists_ok_fail_if_file(fs: VirtualFileSystem): fs.write_bytes("/test", b"test") assert fs.is_file("/test") with pytest.raises(FsError): fs.mkdir("/test", exist_ok=True) def test_mkdir_mode(fs: VirtualFileSystem): assert not fs.exists("/test") permissions = Permissions(0o741) fs.mkdir("/test", mode=permissions) assert fs.is_dir("/test") assert fs.metadata("/test").permissions == permissions ######################################################################################## # read_bytes / write_bytes def test_read_write_bytes(fs: VirtualFileSystem): assert not fs.exists("/test") fs.write_bytes("/test", b"This is a test.") assert fs.read_bytes("/test") == b"This is a test." stream = BytesIO(b"Another test.") fs.write_bytes("/test", stream) assert fs.read_bytes("/test") == b"Another test." with pytest.raises(FsError): fs.read_bytes("/does_not_exist") with pytest.raises(FsError): fs.write_bytes("/does_not_exist/foobar", b"") def test_open_read_write(fs: VirtualFileSystem): assert not fs.exists("/test") with fs.open_write("/test") as stream: stream.write(b"foo") stream.write(b"bar") assert fs.exists("/test") with fs.open_read("/test") as stream: assert stream.read(3) == b"foo" assert stream.read(3) == b"bar" assert stream.read() == b"" # Test overwrite with fs.open_write("/test") as stream: stream.write(b"baz") with fs.open_read("/test") as stream: assert stream.read() == b"baz" with pytest.raises(FsError): fs.open_read("/does_not_exist") with pytest.raises(FsError): fs.open_write("/does_not_exist/foobar") ######################################################################################## # metadata def test_metadata(fs: VirtualFileSystem): file_permissions = Permissions(0o754) file_time = datetime(2025, 5, 17, 13, 57, 32, tzinfo=UTC) file_content = b"This is a test\n" fs.write_bytes("/test_file", file_content) fs.set_permissions("/test_file", file_permissions) fs.set_modification_time("/test_file", file_time) md = fs.metadata("/test_file") assert md.path == PurePosixPath("/test_file") assert md.permissions == file_permissions assert md.type == "file" assert md.modification_time == file_time assert md.byte_size == len(file_content) assert not md.is_hidden assert fs.metadata("/test_file") == md assert fs.is_file("/test_file") assert not fs.is_dir("/test_file") assert not fs.is_symlink("/test_file") assert not fs.is_other("/test_file") fs.set_permissions("/test_file", Permissions(0o644)) assert fs.metadata("/test_file") != md fs.mkdir("/.test_dir") md = fs.metadata("/.test_dir") assert md.type == "dir" assert fs.metadata("/.test_dir").is_hidden assert not fs.is_file("/.test_dir") assert fs.is_dir("/.test_dir") assert not fs.is_symlink("/.test_dir") assert not fs.is_other("/.test_dir") fs.make_link("/test_link", "/link_target") md = fs.metadata("/test_link") assert md.type == "symlink" assert not fs.is_file("/test_link") assert not fs.is_dir("/test_link") assert fs.is_symlink("/test_link") assert not fs.is_other("/test_link") assert fs.metadata_or_none("/does_not_exist") is None with pytest.raises(FsError): fs.metadata("/does_not_exist") ######################################################################################## # iter_dir def test_iter_dir(fs: VirtualFileSystem): expected = [ (PurePosixPath("/dir"), "dir"), (PurePosixPath("/file"), "file"), (PurePosixPath("/link"), "symlink"), ] for path, file_type in expected: if file_type == "dir": fs.mkdir(path) elif file_type == "file": fs.write_bytes(path, b"") elif file_type == "symlink": fs.make_link(path, "/foobar") items_metadata = sorted(fs.iter_dir("/")) for md, [path, file_type] in zip(items_metadata, expected, strict=True): assert md.path == path assert md.type == file_type def test_iter_dir_failure(fs: VirtualFileSystem): with pytest.raises(FsError): list(fs.iter_dir("/test")) fs.write_bytes("/test", b"") with pytest.raises(FsError): list(fs.iter_dir("/test"))