You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
7.3 KiB
254 lines
7.3 KiB
# 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 <https://www.gnu.org/licenses/>.
|
|
"""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 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-"
|
|
|
|
|
|
########################################################################################
|
|
# 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.is_file
|
|
assert not md.is_dir
|
|
assert not md.is_symlink
|
|
assert not md.is_other
|
|
assert md.modification_time == file_time
|
|
assert md.byte_size == len(file_content)
|
|
assert not md.is_hidden_files
|
|
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 not md.is_file
|
|
assert md.is_dir
|
|
assert not md.is_symlink
|
|
assert not md.is_other
|
|
assert fs.metadata("/.test_dir").is_hidden_files
|
|
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 md.is_file
|
|
assert not md.is_dir
|
|
assert md.is_symlink
|
|
assert not md.is_other
|
|
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"))
|
|
|