debian-forge/test/mod/test_objectstore.py

328 lines
8.3 KiB
Python

#
# Tests for the 'osbuild.objectstore' module.
#
import contextlib
import os
import tempfile
from pathlib import Path
import pytest
from osbuild import objectstore
from .. import test
def store_path(store: objectstore.ObjectStore, ref: str, path: str) -> bool:
obj = store.get(ref)
if not obj:
return False
return os.path.exists(os.path.join(obj, path))
@pytest.fixture(name="tmpdir")
def tmpdir_fixture():
with tempfile.TemporaryDirectory() as tmp:
yield tmp
@pytest.fixture(name="object_store")
def store_fixture():
with tempfile.TemporaryDirectory(
prefix="osbuild-test-",
dir="/var/tmp",
) as tmp:
with objectstore.ObjectStore(tmp) as store:
yield store
def test_basic(object_store):
object_store.maximum_size = 1024 * 1024 * 1024
# No objects or references should be in the store
assert len(os.listdir(object_store.objects)) == 0
tree = object_store.new("a")
# new object should be in write mode
assert tree.mode == objectstore.Object.Mode.WRITE
p = Path(tree, "A")
p.touch()
tree.finalize() # put the object into READ mode
assert tree.mode == objectstore.Object.Mode.READ
# commit makes a copy, if space
object_store.commit(tree, "a")
assert store_path(object_store, "a", "A")
# second object, based on the first one
obj2 = object_store.new("b")
obj2.init(tree)
p = Path(obj2, "B")
p.touch()
obj2.finalize() # put the object into READ mode
assert obj2.mode == objectstore.Object.Mode.READ
# commit always makes a copy, if space
object_store.commit(tree, "b")
assert object_store.contains("b")
assert store_path(object_store, "b", "A")
assert store_path(object_store, "b", "B")
assert len(os.listdir(object_store.objects)) == 2
# object should exist and should be in read mode
tree = object_store.get("b")
assert tree is not None
assert tree.mode == objectstore.Object.Mode.READ
def test_cleanup(tmpdir):
with objectstore.ObjectStore(tmpdir) as object_store:
object_store.maximum_size = 1024 * 1024 * 1024
stage = os.path.join(object_store, "stage")
tree = object_store.new("a")
assert len(os.listdir(stage)) == 1
p = Path(tree, "A")
p.touch()
# there should be no temporary Objects dirs anymore
with objectstore.ObjectStore(tmpdir) as object_store:
assert object_store.get("A") is None
def test_metadata(tmpdir):
# test metadata object directly first
with tempfile.TemporaryDirectory() as tmp:
md = objectstore.Object.Metadata(tmp)
assert os.fspath(md) == tmp
with pytest.raises(KeyError):
with md.read("a"):
pass
# do not write anything to the file, it should not get stored
with md.write("a"):
pass
assert len(list(os.scandir(tmp))) == 0
# also we should not write anything if an exception was raised
with pytest.raises(AssertionError):
with md.write("a") as f:
f.write("{}")
raise AssertionError
with md.write("a") as f:
f.write("{}")
assert len(list(os.scandir(tmp))) == 1
with md.read("a") as f:
assert f.read() == "{}"
data = {
"boolean": True,
"number": 42,
"string": "yes, please"
}
extra = {
"extra": "data"
}
with tempfile.TemporaryDirectory() as tmp:
md = objectstore.Object.Metadata(tmp)
d = md.get("a")
assert d is None
md.set("a", None)
with pytest.raises(KeyError):
with md.read("a"):
pass
md.set("a", data)
assert md.get("a") == data
# use tmpdir fixture from here on
with objectstore.ObjectStore(tmpdir) as store:
store.maximum_size = 1024 * 1024 * 1024
obj = store.new("a")
p = Path(obj, "A")
p.touch()
obj.meta.set("md", data)
assert obj.meta.get("md") == data
store.commit(obj, "x")
obj.meta.set("extra", extra)
assert obj.meta.get("extra") == extra
store.commit(obj, "a")
with objectstore.ObjectStore(tmpdir) as store:
obj = store.get("a")
assert obj.meta.get("md") == data
assert obj.meta.get("extra") == extra
ext = store.get("x")
assert ext.meta.get("md") == data
assert ext.meta.get("extra") is None
@pytest.mark.skipif(not test.TestBase.can_bind_mount(), reason="Need root for bind mount")
def test_host_tree(tmpdir):
with objectstore.ObjectStore(tmpdir) as store:
host = store.host_tree
assert host.tree
assert os.fspath(host)
# check we actually cannot write to the path
p = Path(host.tree, "osbuild-test-file")
with pytest.raises(OSError):
p.touch()
print("FOO")
# We cannot access the tree property after cleanup
with pytest.raises(AssertionError):
_ = host.tree
def test_source_epoch(object_store):
tree = object_store.new("a")
tree.source_epoch = 946688461
t = Path(tree)
a = Path(tree, "A")
a.touch()
d = Path(tree, "d")
d.mkdir()
l = Path(tree, "l")
l.symlink_to(d, target_is_directory=True)
for i in (a, d, l, t):
si = os.stat(i, follow_symlinks=False)
before = int(si.st_mtime)
assert before != tree.source_epoch
# FINALIZING
tree.finalize()
for i in (a, d, l, t):
si = os.stat(i, follow_symlinks=False)
after = int(si.st_mtime)
assert after != before, f"mtime not changed for {i}"
assert after == tree.source_epoch
baum = object_store.new("b")
baum.init(tree)
assert baum.created == tree.created
b = Path(tree, "B")
b.touch()
si = os.stat(b, follow_symlinks=False)
before = int(si.st_mtime)
assert before != tree.source_epoch
# FINALIZING
baum.finalize()
si = os.stat(a, follow_symlinks=False)
after = int(si.st_mtime)
assert after != before
assert after == tree.source_epoch
# pylint: disable=too-many-statements
@pytest.mark.skipif(not test.TestBase.can_bind_mount(), reason="Need root for bind mount")
def test_store_server(tmpdir):
with contextlib.ExitStack() as stack:
store = objectstore.ObjectStore(tmpdir)
stack.enter_context(store)
tmp = tempfile.TemporaryDirectory()
tmp = stack.enter_context(tmp)
server = objectstore.StoreServer(store)
stack.enter_context(server)
client = objectstore.StoreClient(server.socket_address)
have = client.source("org.osbuild.files")
want = os.path.join(tmpdir, "sources")
assert have.startswith(want)
tmp = client.mkdtemp(suffix="suffix", prefix="prefix")
assert tmp.startswith(store.tmp)
name = os.path.basename(tmp)
assert name.startswith("prefix")
assert name.endswith("suffix")
obj = store.new("42")
p = Path(obj, "file.txt")
p.write_text("osbuild")
p = Path(obj, "directory")
p.mkdir()
obj.finalize()
mountpoint = Path(tmpdir, "mountpoint")
mountpoint.mkdir()
assert store.contains("42")
path = client.read_tree_at("42", mountpoint)
assert Path(path) == mountpoint
filepath = Path(mountpoint, "file.txt")
assert filepath.exists()
txt = filepath.read_text(encoding="utf8")
assert txt == "osbuild"
# check we can mount subtrees via `read_tree_at`
filemount = Path(tmpdir, "file")
filemount.touch()
path = client.read_tree_at("42", filemount, "/file.txt")
filepath = Path(path)
assert filepath.is_file()
txt = filepath.read_text(encoding="utf8")
assert txt == "osbuild"
dirmount = Path(tmpdir, "dir")
dirmount.mkdir()
path = client.read_tree_at("42", dirmount, "/directory")
dirpath = Path(path)
assert dirpath.is_dir()
# check proper exceptions are raised for non existent
# mount points and sub-trees
with pytest.raises(RuntimeError):
nonexistent = os.path.join(tmpdir, "nonexistent")
_ = client.read_tree_at("42", nonexistent)
with pytest.raises(RuntimeError):
_ = client.read_tree_at("42", tmpdir, "/nonexistent")