tests: Test concurrent operations
authorColin Walters <walters@verbum.org>
Sat, 14 Oct 2017 00:50:28 +0000 (19:50 -0500)
committerAtomic Bot <atomic-devel@projectatomic.io>
Tue, 5 Dec 2017 02:32:47 +0000 (02:32 +0000)
Test that concurrent commits and prunes can succeed. Mostly this is a
check that the new locking works correctly and the concurrent processes
will properly wait until they've acquired the appropriate repository
lock.

Closes: #1343
Approved by: cgwalters

Makefile-tests.am
tests/test-concurrency.py [new file with mode: 0755]

index 6d0e0865d90e14d0cce2c5c3adda561634cfe57b..2b335556141b5854b7d6d4010eabfbc0ccc6fc77 100644 (file)
@@ -108,6 +108,7 @@ _installed_or_uninstalled_test_scripts = \
        tests/test-xattrs.sh \
        tests/test-auto-summary.sh \
        tests/test-prune.sh \
+       tests/test-concurrency.py \
        tests/test-refs.sh \
        tests/test-demo-buildsystem.sh \
        tests/test-switchroot.sh \
diff --git a/tests/test-concurrency.py b/tests/test-concurrency.py
new file mode 100755 (executable)
index 0000000..6aa8f26
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2017 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+from __future__ import print_function
+import os
+import sys
+import shutil
+import subprocess
+from multiprocessing import cpu_count
+
+def fatal(msg):
+    sys.stderr.write(msg)
+    sys.stderr.write('\n')
+    sys.exit(1)
+
+# Create 20 files with content based on @dname + a serial, basically to have
+# different files with different checksums.
+def mktree(dname, serial=0):
+    print('Creating tree', dname, file=sys.stderr)
+    os.mkdir(dname, 0755)
+    for v in xrange(20):
+        with open('{}/{}'.format(dname, v), 'w') as f:
+            f.write('{} {} {}\n'.format(dname, serial, v))
+
+subprocess.check_call(['ostree', '--repo=repo', 'init', '--mode=bare'])
+# like the bit in libtest, but let's do it unconditionally since it's simpler,
+# and we don't need xattr coverage for this
+with open('repo/config', 'a') as f:
+    f.write('disable-xattrs=true\n')
+
+def commit(v):
+    tdir='tree{}'.format(v)
+    cmd = ['ostree', '--repo=repo', 'commit', '--fsync=0', '-b', tdir, '--tree=dir='+tdir]
+    proc = subprocess.Popen(cmd)
+    print('PID {}'.format(proc.pid), *cmd, file=sys.stderr)
+    return proc
+def prune():
+    cmd = ['ostree', '--repo=repo', 'prune', '--refs-only']
+    proc = subprocess.Popen(cmd)
+    print('PID {}:'.format(proc.pid), *cmd, file=sys.stderr)
+    return proc
+
+def wait_check(proc):
+    pid = proc.pid
+    proc.wait()
+    if proc.returncode != 0:
+        sys.stderr.write("process {} exited with code {}\n".format(proc.pid, proc.returncode))
+        return False
+    else:
+        sys.stderr.write('PID {} exited OK\n'.format(pid))
+        return True
+
+print("1..2")
+
+def run(n_committers, n_pruners):
+    # The number of committers needs to be even since we only create half as
+    # many trees
+    n_committers += n_committers % 2
+
+    committers = set()
+    pruners = set()
+
+    print('n_committers', n_committers, 'n_pruners', n_pruners, file=sys.stderr)
+    n_trees = n_committers / 2
+    for v in xrange(n_trees):
+        mktree('tree{}'.format(v))
+
+    for v in xrange(n_committers):
+        committers.add(commit(v / 2))
+    for v in xrange(n_pruners):
+        pruners.add(prune())
+
+    failed = False
+    for committer in committers:
+        if not wait_check(committer):
+            failed = True
+    for pruner in pruners:
+        if not wait_check(pruner):
+            failed = True
+    if failed:
+        fatal('A child process exited abnormally')
+
+    for v in xrange(n_trees):
+        shutil.rmtree('tree{}'.format(v))
+
+# No concurrent pruning
+run(cpu_count()/2 + 2, 0)
+print("ok no concurrent prunes")
+
+run(cpu_count()/2 + 4, 3)
+print("ok concurrent prunes")