tests: copy on write checks for objectstore.Object

Verify the copy on write semantics of `objectstore.Object`, i.e.
content will only be copied at the moment a client wants to write
to `Object`. This also checks that `Object.base` works.

Modify the CI to execute the unit tests in a privileged container
because `Object.read()` works internally by bind mounting a path.
The mount operation needs at least CAP_SYS_ADMIN and overwriting
the file permissions CAP_DAC_OVERRIDE.
This commit is contained in:
Christian Kellner 2020-02-27 10:55:45 +01:00 committed by Tom Gundersen
parent 6b167abf5d
commit a2cbed0ceb
2 changed files with 58 additions and 0 deletions

View file

@ -35,6 +35,7 @@ jobs:
runs-on: ubuntu-latest
container:
image: docker.io/library/python:3.7
options: --privileged # Needed for bind mounts in unit tests
steps:
- name: Clone repository
uses: actions/checkout@v2

View file

@ -85,6 +85,63 @@ class TestObjectStore(unittest.TestCase):
assert len(os.listdir(f"{object_store.refs}/a/")) == 1
assert len(os.listdir(f"{object_store.refs}/b/")) == 1
def test_object_copy_on_write(self):
# operate with a clean object store
with tempfile.TemporaryDirectory(dir="/var/tmp") as tmp:
# sample data to be used for read, write checks
data = "23"
object_store = objectstore.ObjectStore(tmp)
assert len(os.listdir(object_store.refs)) == 0
with object_store.new() as tree:
path = tree.write()
with open(f"{path}/data", "w") as f:
f.write(data)
st = os.fstat(f.fileno())
data_inode = st.st_ino
# commit the object as "x"
x_hash = object_store.commit(tree, "x")
# after the commit, "x" is now the base
# of "tree"
self.assertEqual(tree.base, "x")
# check that "data" is still the very
# same file after committing
with tree.read() as path:
with open(f"{path}/data", "r") as f:
st = os.fstat(f.fileno())
self.assertEqual(st.st_ino, data_inode)
data_read = f.read()
self.assertEqual(data, data_read)
# the object referenced by "x" should act as
# the base of a new object. As long as the
# new one is not modified, it should have
# the very same content
with object_store.new(base_id="x") as tree:
self.assertEqual(tree.base, "x")
self.assertEqual(tree.treesum, x_hash)
with tree.read() as path:
with open(f"{path}/data", "r") as f:
# copy-on-write: since we have not written
# to the tree yet, "data" should be the
# very same file as that one of object "x"
st = os.fstat(f.fileno())
self.assertEqual(st.st_ino, data_inode)
data_read = f.read()
self.assertEqual(data, data_read)
path = tree.write()
# "data" must of course still be present
assert os.path.exists(f"{path}/data")
# but since it is a copy, have a different inode
st = os.stat(f"{path}/data")
self.assertNotEqual(st.st_ino, data_inode)
p = Path(f"{path}/other_data")
p.touch()
# now that we have written, the treesum
# should have changed
self.assertNotEqual(tree.treesum, x_hash)
def test_snapshot(self):
object_store = objectstore.ObjectStore(self.store)
with object_store.new() as tree: