Two new "hooks" were made available in pmtest: genhook is called after everything is generated (just before the snapshot is taken), and snaphook is called just after the snapshot. A hook is a list of strings. Calling a hook is "exec"ing each string in the list. Some helper functions were added to pmtest, notably pacmanrun and addrule__pacman_warned. pacmanrun and rootjoin are used in a hook to install a package and then intentionally mess it up, which then allows the "-Qk" test output to be verified (with addrule__pacman_warned). Signed-off-by: Jeremy Heiner <ScalaProtractor at gmail.com> --- test/pacman/pmpkg.py | 47 ++++++++++++++++++++++++++++ test/pacman/pmtest.py | 63 +++++++++++++++++++++++++++----------- test/pacman/tests/querycheck001.py | 18 +++++++++++ test/pacman/tests/querycheck002.py | 24 +++++++++++++++ test/pacman/tests/querycheck003.py | 24 +++++++++++++++ test/pacman/tests/querycheck004.py | 27 ++++++++++++++++ test/pacman/tests/querycheck005.py | 33 ++++++++++++++++++++ test/pacman/tests/querycheck006.py | 20 ++++++++++++ test/pacman/tests/querycheck007.py | 20 ++++++++++++ test/pacman/tests/querycheck008.py | 33 ++++++++++++++++++++ test/pacman/tests/querycheck009.py | 30 ++++++++++++++++++ test/pacman/tests/querycheck010.py | 27 ++++++++++++++++ test/pacman/util.py | 1 + 13 files changed, 349 insertions(+), 18 deletions(-) create mode 100644 test/pacman/tests/querycheck001.py create mode 100644 test/pacman/tests/querycheck002.py create mode 100644 test/pacman/tests/querycheck003.py create mode 100644 test/pacman/tests/querycheck004.py create mode 100644 test/pacman/tests/querycheck005.py create mode 100644 test/pacman/tests/querycheck006.py create mode 100644 test/pacman/tests/querycheck007.py create mode 100644 test/pacman/tests/querycheck008.py create mode 100644 test/pacman/tests/querycheck009.py create mode 100644 test/pacman/tests/querycheck010.py diff --git a/test/pacman/pmpkg.py b/test/pacman/pmpkg.py index 9c9e447..bb0facf 100644 --- a/test/pacman/pmpkg.py +++ b/test/pacman/pmpkg.py @@ -23,6 +23,8 @@ import shutil from StringIO import StringIO import tarfile +import hashlib +import zlib import util @@ -59,6 +61,7 @@ def __init__(self, name, version = "1.0-1"): # files self.files = [] self.backup = [] + self.mtree = False # install self.install = { "pre_install": "", @@ -104,6 +107,33 @@ def parse_filename(name): filename, extra = filename.split("|") return filename + def mtreefile(self, info, data): + if self.mtree: + self.mtree.append( + "./%s uid=%d gid=%d mode=%o time=%d.%d" + " type=file size=%d md5digest=%s sha256digest=%s" + % (info.name, info.uid, info.gid, info.mode, + info.mtime >> 32, info.mtime & 0xFFFFFFFF, + info.size, hashlib.md5(data).hexdigest(), + hashlib.sha256(data).hexdigest())) + + def mtreedir(self, info): + if self.mtree: + self.mtree.append( + "./%s uid=%d gid=%d mode=%o time=%d.%d" + " type=dir" + % (info.name, info.uid, info.gid, info.mode, + info.mtime >> 32, info.mtime & 0xFFFFFFFF)) + + def mtreelink(self, info): + if self.mtree: + self.mtree.append( + "./%s uid=%d gid=%d mode=%o time=%d.%d" + " type=link link=%s" + % (info.name, info.uid, info.gid, info.mode, + info.mtime >> 32, info.mtime & 0xFFFFFFFF, + info.linkname)) + def makepkg(self, path): """Creates an Arch Linux package archive. @@ -148,11 +178,14 @@ def makepkg(self, path): util.mkdir(os.path.dirname(self.path)) # Generate package metadata + if self.mtree: + self.mtree = ["#mtree"] tar = tarfile.open(self.path, "w:gz") for name, data in archive_files: info = tarfile.TarInfo(name) info.size = len(data) tar.addfile(info, StringIO(data)) + self.mtreefile(info, data) # Generate package file system for name in self.files: @@ -162,18 +195,32 @@ def makepkg(self, path): info.mode = fileinfo["perms"] elif fileinfo["isdir"]: info.mode = 0755 + elif fileinfo["islink"]: + info.mode = 0777 if fileinfo["isdir"]: info.type = tarfile.DIRTYPE tar.addfile(info) + self.mtreedir(info) elif fileinfo["islink"]: info.type = tarfile.SYMTYPE info.linkname = fileinfo["link"] tar.addfile(info) + self.mtreelink(info) else: # TODO wow what a hack, adding a newline to match mkfile? filedata = name + "\n" info.size = len(filedata) tar.addfile(info, StringIO(filedata)) + self.mtreefile(info, filedata) + + # .MTREE + if self.mtree: + filedata = "\n".join(self.mtree)+"\n" # zlib.compress(filedata)? + # but that causes "Unrecognized archive format" error, and this + # seems to work anyway (libalpm is happy with uncompressed file) + info = tarfile.TarInfo(".MTREE") + info.size = len(filedata) + tar.addfile(info, StringIO(filedata)) tar.close() diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index a0a1455..0afb222 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -18,6 +18,7 @@ import os +import re import shlex import shutil import stat @@ -99,6 +100,9 @@ def load(self): self.localpkgs = [] self.createlocalpkgs = False self.filesystem = [] + self.rundir = self.rootjoin(util.TMPDIR) + self.genhook = [] + self.snaphook = [] self.description = "" self.option = {} @@ -198,12 +202,50 @@ def generate(self): # Done. vprint(" Taking a snapshot of the file system") + for cmd in self.genhook: + vprint("\texec "+cmd) + exec cmd for roots, dirs, files in os.walk(root): for i in files: filename = os.path.join(roots, i) f = pmfile.PacmanFile(root, self.rootremove(filename)) self.files.append(f) vprint("\t%s" % f.name) + for cmd in self.snaphook: + vprint("\texec "+cmd) + exec cmd + + def pacmanbin(self): + return([self.env.pacman["bin"], + "--config", self.rootjoin(util.PACCONF), + "--root", self.env.root, + "--dbpath", self.rootjoin(util.PM_DBPATH), + "--cachedir", self.rootjoin(util.PM_CACHEDIR)]) + + def pacmansub(self, cmd, output): + vprint("\trunning: %s" % " ".join(cmd)) + time_start = time.time() + retcode = subprocess.call(cmd, stdout=output, stderr=output, + cwd=self.rundir, env={'LC_ALL': 'C'}) + time_end = time.time() + vprint("\ttime elapsed: %.2fs" % (time_end - time_start)) + vprint("\tretcode = %s" % retcode) + return retcode + + def pacmanrun(self, args): + cmd = self.env.cmdroot() + cmd.extend(self.pacmanbin()) + cmd.append("--noconfirm") + cmd.extend(shlex.split(args)) + output = open(self.rootjoin(util.GENFILE), 'a') + self.pacmansub(cmd, output) + output.close() + + def addrule__pacman_warned(self, pkg, path, msg): + if not path.startswith(self.env.root): + path = self.rootjoin(path) + match = re.escape("warning: %s: %s (%s)" % (pkg.name, path, msg)) + self.addrule("PACMAN_OUTPUT=^"+match+"$") def run(self): if os.path.isfile(util.PM_LOCK): @@ -226,11 +268,7 @@ def run(self): "--tool=memcheck", "--leak-check=full", "--show-reachable=yes", "--suppressions=%s" % suppfile]) - cmd.extend([pacman["bin"], - "--config", self.rootjoin(util.PACCONF), - "--root", root, - "--dbpath", self.rootjoin(util.PM_DBPATH), - "--cachedir", self.rootjoin(util.PM_CACHEDIR)]) + cmd.extend(self.pacmanbin()) if not pacman["manual-confirm"]: cmd.append("--noconfirm") if pacman["debug"]: @@ -240,28 +278,17 @@ def run(self): output = open(self.rootjoin(util.LOGFILE), 'w') else: output = None - vprint("\trunning: %s" % " ".join(cmd)) - - # Change to the tmp dir before running pacman, so that local package - # archives are made available more easily. - tmpdir = self.rootjoin(util.TMPDIR) - time_start = time.time() - self.retcode = subprocess.call(cmd, stdout=output, stderr=output, - cwd=tmpdir, env={'LC_ALL': 'C'}) - time_end = time.time() - vprint("\ttime elapsed: %.2fs" % (time_end - time_start)) + self.retcode = self.pacmansub(cmd, output) if output: output.close() - vprint("\tretcode = %s" % self.retcode) - # Check if the lock is still there if os.path.isfile(util.PM_LOCK): tap.diag("\tERROR: %s not removed" % util.PM_LOCK) os.unlink(util.PM_LOCK) # Look for a core file - if os.path.isfile(os.path.join(tmpdir, "core")): + if os.path.isfile(os.path.join(self.rundir, "core")): tap.diag("\tERROR: pacman dumped a core file") def check(self): diff --git a/test/pacman/tests/querycheck001.py b/test/pacman/tests/querycheck001.py new file mode 100644 index 0000000..8587b50 --- /dev/null +++ b/test/pacman/tests/querycheck001.py @@ -0,0 +1,18 @@ +self.description = "Query--check files, all there" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] + +self.addpkg(pkg) +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename()]) + +self.args = "-Qk" + +self.addrule("PACMAN_RETCODE=0") diff --git a/test/pacman/tests/querycheck002.py b/test/pacman/tests/querycheck002.py new file mode 100644 index 0000000..c385893 --- /dev/null +++ b/test/pacman/tests/querycheck002.py @@ -0,0 +1,24 @@ +self.description = "Query--check files, missing a file" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] + +self.addpkg(pkg) + +zap = pkg.files[1] + +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename(), + "os.unlink(self.rootjoin('%s'))" % zap]) + +self.args = "-Qk" + +self.addrule("PACMAN_RETCODE=1") +self.addrule__pacman_warned(pkg, zap, "No such file or directory") +self.addrule("PACMAN_OUTPUT= 1 missing file$") diff --git a/test/pacman/tests/querycheck003.py b/test/pacman/tests/querycheck003.py new file mode 100644 index 0000000..07e1bc9 --- /dev/null +++ b/test/pacman/tests/querycheck003.py @@ -0,0 +1,24 @@ +self.description = "Query--check files, missing a link" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] + +self.addpkg(pkg) + +zap = pkg.files[2].split(' -> ')[0] + +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename(), + "os.unlink(self.rootjoin('%s'))" % zap]) + +self.args = "-Qk" + +self.addrule("PACMAN_RETCODE=1") +self.addrule__pacman_warned(pkg, zap, "No such file or directory") +self.addrule("PACMAN_OUTPUT= 1 missing file$") diff --git a/test/pacman/tests/querycheck004.py b/test/pacman/tests/querycheck004.py new file mode 100644 index 0000000..dcf8173 --- /dev/null +++ b/test/pacman/tests/querycheck004.py @@ -0,0 +1,27 @@ +self.description = "Query--check files, missing a dir" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] + +self.addpkg(pkg) + +zap = pkg.files[3] + +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename(), + "shutil.rmtree(self.rootjoin('%s'))" % zap]) + +self.args = "-Qk" + +missing = "No such file or directory" + +self.addrule("PACMAN_RETCODE=1") +self.addrule__pacman_warned(pkg, zap, missing) +self.addrule__pacman_warned(pkg, pkg.files[4], missing) +self.addrule("PACMAN_OUTPUT= 2 missing files$") diff --git a/test/pacman/tests/querycheck005.py b/test/pacman/tests/querycheck005.py new file mode 100644 index 0000000..e673cc6 --- /dev/null +++ b/test/pacman/tests/querycheck005.py @@ -0,0 +1,33 @@ +self.description = "Query--check files, missing several" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] + +self.addpkg(pkg) + +zap1 = pkg.files[1] +zap2 = pkg.files[2].split(' -> ')[0] +zap3 = pkg.files[3] + +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename(), + "os.unlink(self.rootjoin('%s'))" % zap1, + "os.unlink(self.rootjoin('%s'))" % zap2, + "shutil.rmtree(self.rootjoin('%s'))" % zap3]) + +self.args = "-Qk" + +missing = "No such file or directory" + +self.addrule("PACMAN_RETCODE=1") +self.addrule__pacman_warned(pkg, zap1, missing) +self.addrule__pacman_warned(pkg, zap2, missing) +self.addrule__pacman_warned(pkg, zap3, missing) +self.addrule__pacman_warned(pkg, pkg.files[4], missing) +self.addrule("PACMAN_OUTPUT= 4 missing files$") diff --git a/test/pacman/tests/querycheck006.py b/test/pacman/tests/querycheck006.py new file mode 100644 index 0000000..cf178f4 --- /dev/null +++ b/test/pacman/tests/querycheck006.py @@ -0,0 +1,20 @@ +self.description = "Query--check mtree, no mtree" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] +pkg.mtree = False + +self.addpkg(pkg) +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename()]) + +self.args = "-Qkk" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("PACMAN_OUTPUT=dummy: no mtree file") diff --git a/test/pacman/tests/querycheck007.py b/test/pacman/tests/querycheck007.py new file mode 100644 index 0000000..d43d846 --- /dev/null +++ b/test/pacman/tests/querycheck007.py @@ -0,0 +1,20 @@ +self.description = "Query--check mtree, all there" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] +pkg.mtree = True + +self.addpkg(pkg) +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename()]) + +self.args = "-Qkk" + +self.addrule("PACMAN_RETCODE=0") +self.addrule("!PACMAN_OUTPUT=dummy: no mtree file") diff --git a/test/pacman/tests/querycheck008.py b/test/pacman/tests/querycheck008.py new file mode 100644 index 0000000..ddf9260 --- /dev/null +++ b/test/pacman/tests/querycheck008.py @@ -0,0 +1,33 @@ +self.description = "Query--check mtree, missing several" + +pkg = pmpkg("dummy") +pkg.files = [ + "etc/dummy.conf", + "lib/libdummy.so.0", + "lib/libdummy.so -> ./libdummy.so.0", + "lib/dummy/", + "lib/dummy/stuff", + "bin/dummy"] +pkg.mtree = True + +zap1 = pkg.files[1] +zap2 = pkg.files[2].split(' -> ')[0] +zap3 = pkg.files[3] + +self.addpkg(pkg) +self.genhook.extend([ + "self.pacmanrun('-U %s')" % pkg.filename(), + "os.unlink(self.rootjoin('%s'))" % zap1, + "os.unlink(self.rootjoin('%s'))" % zap2, + "shutil.rmtree(self.rootjoin('%s'))" % zap3]) + +self.args = "-Qkk" + +missing = "No such file or directory" + +self.addrule("PACMAN_RETCODE=1") +self.addrule__pacman_warned(pkg, zap1, missing) +self.addrule__pacman_warned(pkg, zap2, missing) +self.addrule__pacman_warned(pkg, zap3, missing) +self.addrule__pacman_warned(pkg, pkg.files[4], missing) +self.addrule("PACMAN_OUTPUT= 4 altered files$") diff --git a/test/pacman/tests/querycheck009.py b/test/pacman/tests/querycheck009.py new file mode 100644 index 0000000..6caf733 --- /dev/null +++ b/test/pacman/tests/querycheck009.py @@ -0,0 +1,30 @@ +self.description = "Query--check mtree, bad types" + +pkg = pmpkg("dummy") +pkg.mtree = True +pkg.files = ["dummy/z"] +for file, mangle in [ + ("dummy/f1", ["os.unlink('%s')", "os.mkdir('%s')"]), + ("dummy/f2", ["os.unlink('%s')", "os.symlink('z','%s')"]), + ("dummy/l1 -> z", ["os.unlink('%s')", "os.mkdir('%s')"]), + ("dummy/l2 -> z", ["os.unlink('%s')", "util.mkfile('%s')"]), + ("dummy/d1/", ["os.rmdir('%s')", "util.mkfile('%s')"]), + ("dummy/d2/", ["os.rmdir('%s')", "os.symlink('z','%s')"])]: + pkg.files.append(file) + parsed = pkg.parse_filename(file) + noslash = parsed.rstrip('/') + err = "File type mismatch" + if noslash != parsed: + err = "Not a directory" + rooted = self.rootjoin(noslash) + for cmd in mangle: + self.genhook.append(cmd % rooted) + self.addrule__pacman_warned(pkg, parsed, err) + +self.addpkg(pkg) +self.genhook.insert(0, "self.pacmanrun('-U %s')" % pkg.filename()) + +self.args = "-Qkk" + +self.addrule("PACMAN_RETCODE=1") +self.addrule("PACMAN_OUTPUT= 6 altered files$") diff --git a/test/pacman/tests/querycheck010.py b/test/pacman/tests/querycheck010.py new file mode 100644 index 0000000..caba37b --- /dev/null +++ b/test/pacman/tests/querycheck010.py @@ -0,0 +1,27 @@ +self.description = "Query--check mtree, bad perms+time+size+link" + +pkg = pmpkg("dummy") +pkg.mtree = True +pkg.files = ["dummy/"] +for file, err, mangle in [ + #("dummy/uid", "UID mismatch", ["os.chown('%s',?,-1)"]),#needs root + #("dummy/gid", "GID mismatch", ["os.chown('%s',-1,?)"]),#needs root + ("dummy/mode", "Permissions mismatch", ["os.chmod('%s',0400)"]), + ("dummy/mtime", "Modification time mismatch", ["os.utime('%s',None)"]), + ("dummy/size", "Size mismatch", ["util.mkfile('%s')"]), + ("dummy/link -> size", "Symlink path mismatch", [ + "os.unlink('%s')", "os.symlink('z','%s')"])]: + pkg.files.append(file) + parsed = pkg.parse_filename(file) + rooted = self.rootjoin(parsed) + for cmd in mangle: + self.genhook.append(cmd % rooted) + self.addrule__pacman_warned(pkg, parsed, err) + +self.addpkg(pkg) +self.genhook.insert(0, "self.pacmanrun('-U %s')" % pkg.filename()) + +self.args = "-Qkk" + +self.addrule("PACMAN_RETCODE=1") +self.addrule("PACMAN_OUTPUT= 4 altered files$") diff --git a/test/pacman/util.py b/test/pacman/util.py index 5c9a0c0..d2df8dc 100644 --- a/test/pacman/util.py +++ b/test/pacman/util.py @@ -40,6 +40,7 @@ TMPDIR = "tmp" SYNCREPO = "var/pub" LOGFILE = "var/log/pactest.log" +GENFILE = "var/log/testgen.log" verbose = 0 -- 1.8.4