1# Copyright (c) 2006 by Aurelien Foret <orelien@chez.com> 2# Copyright (c) 2006-2018 Pacman Development Team <pacman-dev@archlinux.org> 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License 15# along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 18import os 19import shlex 20import shutil 21import stat 22import subprocess 23import time 24 25import pmrule 26import pmdb 27import pmfile 28import tap 29import util 30from util import vprint 31 32class pmtest(object): 33 """Test object 34 """ 35 36 def __init__(self, name, root): 37 self.name = name 38 self.testname = os.path.basename(name).replace('.py', '') 39 self.root = root 40 self.dbver = 9 41 self.cachepkgs = True 42 self.cmd = ["pacman", "--noconfirm", 43 "--config", self.configfile(), 44 "--root", self.rootdir(), 45 "--dbpath", self.dbdir(), 46 "--hookdir", self.hookdir(), 47 "--cachedir", self.cachedir()] 48 49 def __str__(self): 50 return "name = %s\n" \ 51 "testname = %s\n" \ 52 "root = %s" % (self.name, self.testname, self.root) 53 54 def addpkg2db(self, treename, pkg): 55 if not treename in self.db: 56 self.db[treename] = pmdb.pmdb(treename, self.root) 57 self.db[treename].pkgs.append(pkg) 58 59 def addpkg(self, pkg): 60 self.localpkgs.append(pkg) 61 62 def findpkg(self, name, version, allow_local=False): 63 """Find a package object matching the name and version specified in 64 either sync databases or the local package collection. The local database 65 is allowed to match if allow_local is True.""" 66 for db in self.db.values(): 67 if db.is_local and not allow_local: 68 continue 69 pkg = db.getpkg(name) 70 if pkg and pkg.version == version: 71 return pkg 72 for pkg in self.localpkgs: 73 if pkg.name == name and pkg.version == version: 74 return pkg 75 76 return None 77 78 def addrule(self, rulename): 79 rule = pmrule.pmrule(rulename) 80 self.rules.append(rule) 81 82 def load(self): 83 # Reset test parameters 84 self.result = { 85 "success": 0, 86 "fail": 0 87 } 88 self.args = "" 89 self.retcode = 0 90 self.db = { 91 "local": pmdb.pmdb("local", self.root) 92 } 93 self.localpkgs = [] 94 self.createlocalpkgs = False 95 self.filesystem = [] 96 97 self.description = "" 98 self.option = {} 99 100 # Test rules 101 self.rules = [] 102 self.files = [] 103 self.expectfailure = False 104 105 if os.path.isfile(self.name): 106 # all tests expect this to be available 107 from pmpkg import pmpkg 108 with open(self.name) as input: 109 exec(input.read(),locals()) 110 else: 111 raise IOError("file %s does not exist!" % self.name) 112 113 def generate(self, pacman): 114 tap.diag("==> Generating test environment") 115 116 # Cleanup leftover files from a previous test session 117 if os.path.isdir(self.root): 118 shutil.rmtree(self.root) 119 vprint("\t%s" % self.root) 120 121 # Create directory structure 122 vprint(" Creating directory structure:") 123 dbdir = os.path.join(self.root, util.PM_SYNCDBPATH) 124 cachedir = os.path.join(self.root, util.PM_CACHEDIR) 125 syncdir = os.path.join(self.root, util.SYNCREPO) 126 tmpdir = os.path.join(self.root, util.TMPDIR) 127 logdir = os.path.join(self.root, os.path.dirname(util.LOGFILE)) 128 etcdir = os.path.join(self.root, os.path.dirname(util.PACCONF)) 129 bindir = os.path.join(self.root, "bin") 130 ldconfig = os.path.basename(pacman["ldconfig"]) 131 ldconfigdir = os.path.join(self.root, os.path.dirname(pacman["ldconfig"][1:])) 132 shell = pacman["scriptlet-shell"][1:] 133 shelldir = os.path.join(self.root, os.path.dirname(shell)) 134 sys_dirs = [dbdir, cachedir, syncdir, tmpdir, logdir, etcdir, bindir, 135 ldconfigdir, shelldir] 136 for sys_dir in sys_dirs: 137 if not os.path.isdir(sys_dir): 138 vprint("\t%s" % sys_dir[len(self.root)+1:]) 139 os.makedirs(sys_dir, 0o755) 140 # Only the dynamically linked binary is needed for fakechroot 141 shutil.copy("/bin/sh", bindir) 142 if shell != "bin/sh": 143 shutil.copy("/bin/sh", os.path.join(self.root, shell)) 144 shutil.copy(os.path.join(util.SELFPATH, "ldconfig.stub"), 145 os.path.join(ldconfigdir, ldconfig)) 146 ld_so_conf = open(os.path.join(etcdir, "ld.so.conf"), "w") 147 ld_so_conf.close() 148 149 # Configuration file 150 vprint(" Creating configuration file") 151 util.mkcfgfile(util.PACCONF, self.root, self.option, self.db) 152 153 # Creating packages 154 vprint(" Creating package archives") 155 for pkg in self.localpkgs: 156 vprint("\t%s" % os.path.join(util.TMPDIR, pkg.filename())) 157 pkg.finalize() 158 pkg.makepkg(tmpdir) 159 for key, value in self.db.items(): 160 for pkg in value.pkgs: 161 pkg.finalize() 162 if key == "local" and not self.createlocalpkgs: 163 continue 164 for pkg in value.pkgs: 165 vprint("\t%s" % os.path.join(util.PM_CACHEDIR, pkg.filename())) 166 if self.cachepkgs: 167 pkg.makepkg(cachedir) 168 else: 169 pkg.makepkg(os.path.join(syncdir, value.treename)) 170 pkg.md5sum = util.getmd5sum(pkg.path) 171 pkg.csize = os.stat(pkg.path)[stat.ST_SIZE] 172 173 # Creating sync database archives 174 vprint(" Creating databases") 175 for key, value in self.db.items(): 176 vprint("\t" + value.treename) 177 value.generate() 178 179 # Filesystem 180 vprint(" Populating file system") 181 for f in self.filesystem: 182 if type(f) is pmfile.pmfile: 183 vprint("\t%s" % f.path) 184 f.mkfile(self.root); 185 else: 186 vprint("\t%s" % f) 187 path = util.mkfile(self.root, f, f) 188 if os.path.isfile(path): 189 os.utime(path, (355, 355)) 190 for pkg in self.db["local"].pkgs: 191 vprint("\tinstalling %s" % pkg.fullname()) 192 pkg.install_package(self.root) 193 if self.db["local"].pkgs and self.dbver >= 9: 194 path = os.path.join(self.root, util.PM_DBPATH, "local") 195 util.mkfile(path, "ALPM_DB_VERSION", str(self.dbver)) 196 197 # Done. 198 vprint(" Taking a snapshot of the file system") 199 for filename in self.snapshots_needed(): 200 f = pmfile.snapshot(self.root, filename) 201 self.files.append(f) 202 vprint("\t%s" % f.name) 203 204 def add_hook(self, name, content): 205 if not name.endswith(".hook"): 206 name = name + ".hook" 207 path = os.path.join("etc/pacman.d/hooks/", name) 208 self.filesystem.append(pmfile.pmfile(path, content)) 209 210 def add_script(self, name, content): 211 if not content.startswith("#!"): 212 content = "#!/bin/sh\n" + content 213 path = os.path.join("bin/", name) 214 self.filesystem.append(pmfile.pmfile(path, content, mode=0o755)) 215 216 def snapshots_needed(self): 217 files = set() 218 for r in self.rules: 219 files.update(r.snapshots_needed()) 220 return files 221 222 def run(self, pacman): 223 if os.path.isfile(util.PM_LOCK): 224 tap.bail("\tERROR: another pacman session is on-going -- skipping") 225 return 226 227 tap.diag("==> Running test") 228 vprint("\tpacman %s" % self.args) 229 230 cmd = [] 231 if os.geteuid() != 0: 232 fakeroot = util.which("fakeroot") 233 if not fakeroot: 234 tap.diag("WARNING: fakeroot not found!") 235 else: 236 cmd.append("fakeroot") 237 238 fakechroot = util.which("fakechroot") 239 if not fakechroot: 240 tap.diag("WARNING: fakechroot not found!") 241 else: 242 cmd.append("fakechroot") 243 244 if pacman["gdb"]: 245 cmd.extend(["libtool", "execute", "gdb", "--args"]) 246 if pacman["valgrind"]: 247 suppfile = os.path.join(os.path.dirname(__file__), 248 '..', '..', 'valgrind.supp') 249 cmd.extend(["libtool", "execute", "valgrind", "-q", 250 "--tool=memcheck", "--leak-check=full", 251 "--show-reachable=yes", 252 "--gen-suppressions=all", 253 "--child-silent-after-fork=yes", 254 "--log-file=%s" % os.path.join(self.root, "var/log/valgrind"), 255 "--suppressions=%s" % suppfile]) 256 self.addrule("FILE_EMPTY=var/log/valgrind") 257 258 # replace program name with absolute path 259 prog = pacman["bin"] 260 if not prog: 261 prog = util.which(self.cmd[0], pacman["bindir"]) 262 if not prog or not os.access(prog, os.X_OK): 263 if not prog: 264 tap.bail("could not locate '%s' binary" % (self.cmd[0])) 265 return 266 267 cmd.append(os.path.abspath(prog)) 268 cmd.extend(self.cmd[1:]) 269 if pacman["manual-confirm"]: 270 cmd.append("--confirm") 271 if pacman["debug"]: 272 cmd.append("--debug=%s" % pacman["debug"]) 273 cmd.extend(shlex.split(self.args)) 274 275 if not (pacman["gdb"] or pacman["nolog"]): 276 output = open(os.path.join(self.root, util.LOGFILE), 'w') 277 else: 278 output = None 279 vprint("\trunning: %s" % " ".join(cmd)) 280 281 # Change to the tmp dir before running pacman, so that local package 282 # archives are made available more easily. 283 time_start = time.time() 284 self.retcode = subprocess.call(cmd, stdout=output, stderr=output, 285 cwd=os.path.join(self.root, util.TMPDIR), env={'LC_ALL': 'C'}) 286 time_end = time.time() 287 vprint("\ttime elapsed: %.2fs" % (time_end - time_start)) 288 289 if output: 290 output.close() 291 292 vprint("\tretcode = %s" % self.retcode) 293 294 # Check if the lock is still there 295 if os.path.isfile(util.PM_LOCK): 296 tap.diag("\tERROR: %s not removed" % util.PM_LOCK) 297 os.unlink(util.PM_LOCK) 298 # Look for a core file 299 if os.path.isfile(os.path.join(self.root, util.TMPDIR, "core")): 300 tap.diag("\tERROR: pacman dumped a core file") 301 302 def check(self): 303 tap.plan(len(self.rules)) 304 for i in self.rules: 305 success = i.check(self) 306 if success == 1: 307 self.result["success"] += 1 308 else: 309 self.result["fail"] += 1 310 tap.ok(success, i) 311 312 def configfile(self): 313 return os.path.join(self.root, util.PACCONF) 314 315 def dbdir(self): 316 return os.path.join(self.root, util.PM_DBPATH) 317 318 def rootdir(self): 319 return self.root + '/' 320 321 def cachedir(self): 322 return os.path.join(self.root, util.PM_CACHEDIR) 323 324 def hookdir(self): 325 return os.path.join(self.root, util.PM_HOOKDIR) 326