diff options
Diffstat (limited to 'meta/lib')
28 files changed, 2058 insertions, 479 deletions
diff --git a/meta/lib/oe/copy_buildsystem.py b/meta/lib/oe/copy_buildsystem.py new file mode 100644 index 0000000000..cf7fada7f0 --- /dev/null +++ b/meta/lib/oe/copy_buildsystem.py @@ -0,0 +1,102 @@ +# This class should provide easy access to the different aspects of the +# buildsystem such as layers, bitbake location, etc. +import stat +import shutil + +def _smart_copy(src, dest): + # smart_copy will choose the correct function depending on whether the + # source is a file or a directory. + mode = os.stat(src).st_mode + if stat.S_ISDIR(mode): + shutil.copytree(src, dest, symlinks=True) + else: + shutil.copyfile(src, dest) + shutil.copymode(src, dest) + +class BuildSystem(object): + def __init__(self, d): + self.d = d + self.layerdirs = d.getVar('BBLAYERS', True).split() + + def copy_bitbake_and_layers(self, destdir): + # Copy in all metadata layers + bitbake (as repositories) + layers_copied = [] + bb.utils.mkdirhier(destdir) + layers = list(self.layerdirs) + + corebase = self.d.getVar('COREBASE', True) + layers.append(corebase) + + corebase_files = self.d.getVar('COREBASE_FILES', True).split() + + # bitbake belongs in corebase so make sure it goes there + if "bitbake" not in corebase_files: + corebase_files.append("bitbake") + corebase_files = [corebase + '/' +x for x in corebase_files] + + for layer in layers: + layerconf = os.path.join(layer, 'conf', 'layer.conf') + if os.path.exists(layerconf): + with open(layerconf, 'r') as f: + if f.readline().startswith("# ### workspace layer auto-generated by devtool ###"): + bb.warn("Skipping local workspace layer %s" % layer) + continue + + # If the layer was already under corebase, leave it there + # since layers such as meta have issues when moved. + layerdestpath = destdir + if corebase == os.path.dirname(layer): + layerdestpath += '/' + os.path.basename(corebase) + layerdestpath += '/' + os.path.basename(layer) + + layer_relative = os.path.relpath(layerdestpath, + destdir) + layers_copied.append(layer_relative) + + # Treat corebase as special since it typically will contain + # build directories or other custom items. + if corebase == layer: + bb.utils.mkdirhier(layerdestpath) + for f in corebase_files: + f_basename = os.path.basename(f) + destname = os.path.join(layerdestpath, f_basename) + _smart_copy(f, destname) + else: + if os.path.exists(layerdestpath): + bb.note("Skipping layer %s, already handled" % layer) + else: + _smart_copy(layer, layerdestpath) + + return layers_copied + +def generate_locked_sigs(sigfile, d): + bb.utils.mkdirhier(os.path.dirname(sigfile)) + depd = d.getVar('BB_TASKDEPDATA', True) + tasks = ['%s.%s' % (v[2], v[1]) for v in depd.itervalues()] + bb.parse.siggen.dump_lockedsigs(sigfile, tasks) + +def prune_lockedsigs(allowed_tasks, excluded_targets, lockedsigs, pruned_output): + with open(lockedsigs, 'r') as infile: + bb.utils.mkdirhier(os.path.dirname(pruned_output)) + with open(pruned_output, 'w') as f: + invalue = False + for line in infile: + if invalue: + if line.endswith('\\\n'): + splitval = line.strip().split(':') + if splitval[1] in allowed_tasks and not splitval[0] in excluded_targets: + f.write(line) + else: + f.write(line) + invalue = False + elif line.startswith('SIGGEN_LOCKEDSIGS'): + invalue = True + f.write(line) + +def create_locked_sstate_cache(lockedsigs, input_sstate_cache, output_sstate_cache, d, fixedlsbstring=""): + bb.note('Generating sstate-cache...') + + bb.process.run("gen-lockedsig-cache %s %s %s" % (lockedsigs, input_sstate_cache, output_sstate_cache)) + if fixedlsbstring: + os.rename(output_sstate_cache + '/' + d.getVar('NATIVELSBSTRING', True), + output_sstate_cache + '/' + fixedlsbstring) diff --git a/meta/lib/oe/image.py b/meta/lib/oe/image.py index 7e080b00dd..0ce303d570 100644 --- a/meta/lib/oe/image.py +++ b/meta/lib/oe/image.py @@ -48,11 +48,13 @@ class ImageDepGraph(object): graph = dict() def add_node(node): + base_type = self._image_base_type(node) deps = (self.d.getVar('IMAGE_TYPEDEP_' + node, True) or "") - if deps != "": + base_deps = (self.d.getVar('IMAGE_TYPEDEP_' + base_type, True) or "") + if deps != "" or base_deps != "": graph[node] = deps - for dep in deps.split(): + for dep in deps.split() + base_deps.split(): if not dep in graph: add_node(dep) else: @@ -72,6 +74,18 @@ class ImageDepGraph(object): for item in remove_list: self.graph.pop(item, None) + def _image_base_type(self, type): + ctypes = self.d.getVar('COMPRESSIONTYPES', True).split() + if type in ["vmdk", "live", "iso", "hddimg"]: + type = "ext3" + basetype = type + for ctype in ctypes: + if type.endswith("." + ctype): + basetype = type[:-len("." + ctype)] + break + + return basetype + def _compute_dependencies(self): """ returns dict object of nodes with [no_of_depends_on, no_of_depended_by] @@ -282,7 +296,11 @@ class Image(ImageDepGraph): bb.data.update_data(localdata) localdata.setVar('type', type) - cmds.append("\t" + localdata.getVar("IMAGE_CMD", True)) + image_cmd = localdata.getVar("IMAGE_CMD", True) + if image_cmd: + cmds.append("\t" + image_cmd) + else: + bb.fatal("No IMAGE_CMD defined for IMAGE_FSTYPES entry '%s' - possibly invalid type name or missing support class" % type) cmds.append(localdata.expand("\tcd ${DEPLOY_DIR_IMAGE}")) if type in cimages: diff --git a/meta/lib/oe/license.py b/meta/lib/oe/license.py index 340da61102..31ca15b574 100644 --- a/meta/lib/oe/license.py +++ b/meta/lib/oe/license.py @@ -25,7 +25,8 @@ class InvalidLicense(LicenseError): def __str__(self): return "invalid characters in license '%s'" % self.license -license_operator = re.compile('([&|() ])') +license_operator_chars = '&|() ' +license_operator = re.compile('([' + license_operator_chars + '])') license_pattern = re.compile('[a-zA-Z0-9.+_\-]+$') class LicenseVisitor(ast.NodeVisitor): diff --git a/meta/lib/oe/lsb.py b/meta/lib/oe/lsb.py index b53f361035..50c1d4720b 100644 --- a/meta/lib/oe/lsb.py +++ b/meta/lib/oe/lsb.py @@ -9,6 +9,7 @@ def release_dict(): data = {} for line in output.splitlines(): + if line.startswith("-e"): line = line[3:] try: key, value = line.split(":\t", 1) except ValueError: diff --git a/meta/lib/oe/package.py b/meta/lib/oe/package.py index f8b532220a..8bc56c6e88 100644 --- a/meta/lib/oe/package.py +++ b/meta/lib/oe/package.py @@ -30,7 +30,8 @@ def runstrip(arg): elif elftype & 8 or elftype & 4: extraflags = "--remove-section=.comment --remove-section=.note" - stripcmd = "'%s' %s '%s'" % (strip, extraflags, file) + # Use mv to break hardlinks + stripcmd = "'%s' %s '%s' -o '%s.tmp' && chown --reference='%s' '%s.tmp' && mv '%s.tmp' '%s'" % (strip, extraflags, file, file, file, file, file, file) bb.debug(1, "runstrip: %s" % stripcmd) ret = subprocess.call(stripcmd, shell=True) @@ -97,3 +98,29 @@ def filedeprunner(arg): raise e return (pkg, provides, requires) + + +def read_shlib_providers(d): + import re + + shlib_provider = {} + shlibs_dirs = d.getVar('SHLIBSDIRS', True).split() + list_re = re.compile('^(.*)\.list$') + # Go from least to most specific since the last one found wins + for dir in reversed(shlibs_dirs): + bb.debug(2, "Reading shlib providers in %s" % (dir)) + if not os.path.exists(dir): + continue + for file in os.listdir(dir): + m = list_re.match(file) + if m: + dep_pkg = m.group(1) + fd = open(os.path.join(dir, file)) + lines = fd.readlines() + fd.close() + for l in lines: + s = l.strip().split(":") + if s[0] not in shlib_provider: + shlib_provider[s[0]] = {} + shlib_provider[s[0]][s[1]] = (dep_pkg, s[2]) + return shlib_provider diff --git a/meta/lib/oe/package_manager.py b/meta/lib/oe/package_manager.py index 8609156ca2..c9a8084bf2 100644 --- a/meta/lib/oe/package_manager.py +++ b/meta/lib/oe/package_manager.py @@ -529,8 +529,11 @@ class PackageManager(object): return cmd = [bb.utils.which(os.getenv('PATH'), "oe-pkgdata-util"), - "glob", self.d.getVar('PKGDATA_DIR', True), installed_pkgs_file, + "-p", self.d.getVar('PKGDATA_DIR', True), "glob", installed_pkgs_file, globs] + exclude = self.d.getVar('PACKAGE_EXCLUDE_COMPLEMENTARY', True) + if exclude: + cmd.extend(['-x', exclude]) try: bb.note("Installing complementary packages ...") complementary_pkgs = subprocess.check_output(cmd, stderr=subprocess.STDOUT) @@ -575,7 +578,8 @@ class RpmPM(PackageManager): self.fullpkglist = list() self.deploy_dir = self.d.getVar('DEPLOY_DIR_RPM', True) self.etcrpm_dir = os.path.join(self.target_rootfs, "etc/rpm") - self.install_dir = os.path.join(self.target_rootfs, "install") + self.install_dir_name = "oe_install" + self.install_dir_path = os.path.join(self.target_rootfs, self.install_dir_name) self.rpm_cmd = bb.utils.which(os.getenv('PATH'), "rpm") self.smart_cmd = bb.utils.which(os.getenv('PATH'), "smart") self.smart_opt = "--quiet --data-dir=" + os.path.join(target_rootfs, @@ -676,10 +680,10 @@ class RpmPM(PackageManager): def _search_pkg_name_in_feeds(self, pkg, feed_archs): for arch in feed_archs: arch = arch.replace('-', '_') + regex_match = re.compile(r"^%s-[^-]*-[^-]*@%s$" % \ + (re.escape(pkg), re.escape(arch))) for p in self.fullpkglist: - regex_match = r"^%s-[^-]*-[^-]*@%s$" % \ - (re.escape(pkg), re.escape(arch)) - if re.match(regex_match, p) is not None: + if regex_match.match(p) is not None: # First found is best match # bb.note('%s -> %s' % (pkg, pkg + '@' + arch)) return pkg + '@' + arch @@ -746,9 +750,9 @@ class RpmPM(PackageManager): bb.utils.mkdirhier(self.etcrpm_dir) # Setup temporary directory -- install... - if os.path.exists(self.install_dir): - bb.utils.remove(self.install_dir, True) - bb.utils.mkdirhier(os.path.join(self.install_dir, 'tmp')) + if os.path.exists(self.install_dir_path): + bb.utils.remove(self.install_dir_path, True) + bb.utils.mkdirhier(os.path.join(self.install_dir_path, 'tmp')) channel_priority = 5 platform_dir = os.path.join(self.etcrpm_dir, "platform") @@ -835,7 +839,7 @@ class RpmPM(PackageManager): self._invoke_smart('config --set rpm-dbpath=/var/lib/rpm') self._invoke_smart('config --set rpm-extra-macros._var=%s' % self.d.getVar('localstatedir', True)) - cmd = 'config --set rpm-extra-macros._tmppath=/install/tmp' + cmd = "config --set rpm-extra-macros._tmppath=/%s/tmp" % (self.install_dir_name) prefer_color = self.d.getVar('RPM_PREFER_ELF_ARCH', True) if prefer_color: @@ -853,6 +857,7 @@ class RpmPM(PackageManager): % prefer_color) self._invoke_smart(cmd) + self._invoke_smart('config --set rpm-ignoresize=1') # Write common configuration for host and target usage self._invoke_smart('config --set rpm-nolinktos=1') @@ -988,7 +993,7 @@ class RpmPM(PackageManager): cmd += "--dbpath=/var/lib/rpm " cmd += "--define='_cross_scriptlet_wrapper %s' " % \ self.scriptlet_wrapper - cmd += "--define='_tmppath /install/tmp' %s" % ' '.join(pkgs) + cmd += "--define='_tmppath /%s/tmp' %s" % (self.install_dir_name, ' '.join(pkgs)) else: # for pkg in pkgs: # bb.note('Debug: What required: %s' % pkg) @@ -1023,7 +1028,7 @@ class RpmPM(PackageManager): bb.utils.remove(os.path.join(self.target_rootfs, 'var/lib/opkg'), True) # remove temp directory - bb.utils.remove(self.d.expand('${IMAGE_ROOTFS}/install'), True) + bb.utils.remove(self.install_dir_path, True) def backup_packaging_data(self): # Save the rpmlib for increment rpm image generation @@ -1627,10 +1632,10 @@ class DpkgPM(PackageManager): def remove(self, pkgs, with_dependencies=True): if with_dependencies: os.environ['APT_CONFIG'] = self.apt_conf_file - cmd = "%s remove %s" % (self.apt_get_cmd, ' '.join(pkgs)) + cmd = "%s purge %s" % (self.apt_get_cmd, ' '.join(pkgs)) else: cmd = "%s --admindir=%s/var/lib/dpkg --instdir=%s" \ - " -r --force-depends %s" % \ + " -P --force-depends %s" % \ (bb.utils.which(os.getenv('PATH'), "dpkg"), self.target_rootfs, self.target_rootfs, ' '.join(pkgs)) @@ -1722,9 +1727,12 @@ class DpkgPM(PackageManager): with open(self.d.expand("${STAGING_ETCDIR_NATIVE}/apt/apt.conf.sample")) as apt_conf_sample: for line in apt_conf_sample.read().split("\n"): match_arch = re.match(" Architecture \".*\";$", line) + architectures = "" if match_arch: for base_arch in base_arch_list: - apt_conf.write(" Architecture \"%s\";\n" % base_arch) + architectures += "\"%s\";" % base_arch + apt_conf.write(" Architectures {%s};\n" % architectures); + apt_conf.write(" Architecture \"%s\";\n" % base_archs) else: line = re.sub("#ROOTFS#", self.target_rootfs, line) line = re.sub("#APTCONF#", self.apt_conf_dir, line) diff --git a/meta/lib/oe/patch.py b/meta/lib/oe/patch.py index b085c9d6b5..f68d40f8c8 100644 --- a/meta/lib/oe/patch.py +++ b/meta/lib/oe/patch.py @@ -199,10 +199,108 @@ class PatchTree(PatchSet): self.Pop(all=True) class GitApplyTree(PatchTree): + patch_line_prefix = '%% original patch' + def __init__(self, dir, d): PatchTree.__init__(self, dir, d) + @staticmethod + def extractPatchHeader(patchfile): + """ + Extract just the header lines from the top of a patch file + """ + lines = [] + with open(patchfile, 'r') as f: + for line in f.readlines(): + if line.startswith('Index: ') or line.startswith('diff -') or line.startswith('---'): + break + lines.append(line) + return lines + + @staticmethod + def prepareCommit(patchfile): + """ + Prepare a git commit command line based on the header from a patch file + (typically this is useful for patches that cannot be applied with "git am" due to formatting) + """ + import tempfile + import re + author_re = re.compile('[\S ]+ <\S+@\S+\.\S+>') + # Process patch header and extract useful information + lines = GitApplyTree.extractPatchHeader(patchfile) + outlines = [] + author = None + date = None + for line in lines: + if line.startswith('Subject: '): + subject = line.split(':', 1)[1] + # Remove any [PATCH][oe-core] etc. + subject = re.sub(r'\[.+?\]\s*', '', subject) + outlines.insert(0, '%s\n\n' % subject.strip()) + continue + if line.startswith('From: ') or line.startswith('Author: '): + authorval = line.split(':', 1)[1].strip().replace('"', '') + # git is fussy about author formatting i.e. it must be Name <email@domain> + if author_re.match(authorval): + author = authorval + continue + if line.startswith('Date: '): + if date is None: + dateval = line.split(':', 1)[1].strip() + # Very crude check for date format, since git will blow up if it's not in the right + # format. Without e.g. a python-dateutils dependency we can't do a whole lot more + if len(dateval) > 12: + date = dateval + continue + if line.startswith('Signed-off-by: '): + authorval = line.split(':', 1)[1].strip().replace('"', '') + # git is fussy about author formatting i.e. it must be Name <email@domain> + if author_re.match(authorval): + author = authorval + outlines.append(line) + # Write out commit message to a file + with tempfile.NamedTemporaryFile('w', delete=False) as tf: + tmpfile = tf.name + for line in outlines: + tf.write(line) + # Prepare git command + cmd = ["git", "commit", "-F", tmpfile] + # git doesn't like plain email addresses as authors + if author and '<' in author: + cmd.append('--author="%s"' % author) + if date: + cmd.append('--date="%s"' % date) + return (tmpfile, cmd) + + @staticmethod + def extractPatches(tree, startcommit, outdir): + import tempfile + import shutil + tempdir = tempfile.mkdtemp(prefix='oepatch') + try: + shellcmd = ["git", "format-patch", startcommit, "-o", tempdir] + out = runcmd(["sh", "-c", " ".join(shellcmd)], tree) + if out: + for srcfile in out.split(): + patchlines = [] + outfile = None + with open(srcfile, 'r') as f: + for line in f: + if line.startswith(GitApplyTree.patch_line_prefix): + outfile = line.split()[-1].strip() + continue + patchlines.append(line) + if not outfile: + outfile = os.path.basename(srcfile) + with open(os.path.join(outdir, outfile), 'w') as of: + for line in patchlines: + of.write(line) + finally: + shutil.rmtree(tempdir) + def _applypatch(self, patch, force = False, reverse = False, run = True): + import shutil + def _applypatchhelper(shellcmd, patch, force = False, reverse = False, run = True): if reverse: shellcmd.append('-R') @@ -214,12 +312,65 @@ class GitApplyTree(PatchTree): return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Add hooks which add a pointer to the original patch file name in the commit message + reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() + if not reporoot: + raise Exception("Cannot get repository root for directory %s" % self.dir) + commithook = os.path.join(reporoot, '.git', 'hooks', 'commit-msg') + commithook_backup = commithook + '.devtool-orig' + applyhook = os.path.join(reporoot, '.git', 'hooks', 'applypatch-msg') + applyhook_backup = applyhook + '.devtool-orig' + if os.path.exists(commithook): + shutil.move(commithook, commithook_backup) + if os.path.exists(applyhook): + shutil.move(applyhook, applyhook_backup) + with open(commithook, 'w') as f: + # NOTE: the formatting here is significant; if you change it you'll also need to + # change other places which read it back + f.write('echo >> $1\n') + f.write('echo "%s: $PATCHFILE" >> $1\n' % GitApplyTree.patch_line_prefix) + os.chmod(commithook, 0755) + shutil.copy2(commithook, applyhook) try: - shellcmd = ["git", "--work-tree=.", "am", "-3", "-p%s" % patch['strippath']] - return _applypatchhelper(shellcmd, patch, force, reverse, run) - except CmdError: - shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']] - return _applypatchhelper(shellcmd, patch, force, reverse, run) + patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) + try: + shellcmd = [patchfilevar, "git", "--work-tree=.", "am", "-3", "--keep-cr", "-p%s" % patch['strippath']] + return _applypatchhelper(shellcmd, patch, force, reverse, run) + except CmdError: + # Need to abort the git am, or we'll still be within it at the end + try: + shellcmd = ["git", "--work-tree=.", "am", "--abort"] + runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + except CmdError: + pass + # Fall back to git apply + shellcmd = ["git", "--git-dir=.", "apply", "-p%s" % patch['strippath']] + try: + output = _applypatchhelper(shellcmd, patch, force, reverse, run) + except CmdError: + # Fall back to patch + output = PatchTree._applypatch(self, patch, force, reverse, run) + # Add all files + shellcmd = ["git", "add", "-f", "."] + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Exclude the patches directory + shellcmd = ["git", "reset", "HEAD", self.patchdir] + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + # Commit the result + (tmpfile, shellcmd) = self.prepareCommit(patch['file']) + try: + shellcmd.insert(0, patchfilevar) + output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) + finally: + os.remove(tmpfile) + return output + finally: + os.remove(commithook) + os.remove(applyhook) + if os.path.exists(commithook_backup): + shutil.move(commithook_backup, commithook) + if os.path.exists(applyhook_backup): + shutil.move(applyhook_backup, applyhook) class QuiltTree(PatchSet): diff --git a/meta/lib/oe/recipeutils.py b/meta/lib/oe/recipeutils.py new file mode 100644 index 0000000000..159a103719 --- /dev/null +++ b/meta/lib/oe/recipeutils.py @@ -0,0 +1,279 @@ +# Utility functions for reading and modifying recipes +# +# Some code borrowed from the OE layer index +# +# Copyright (C) 2013-2014 Intel Corporation +# + +import sys +import os +import os.path +import tempfile +import textwrap +import difflib +import utils +import shutil +import re +from collections import OrderedDict, defaultdict + + +# Help us to find places to insert values +recipe_progression = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION', 'LICENSE', 'LIC_FILES_CHKSUM', 'PROVIDES', 'DEPENDS', 'PR', 'PV', 'SRCREV', 'SRC_URI', 'S', 'do_fetch', 'do_unpack', 'do_patch', 'EXTRA_OECONF', 'do_configure', 'EXTRA_OEMAKE', 'do_compile', 'do_install', 'do_populate_sysroot', 'INITSCRIPT', 'USERADD', 'GROUPADD', 'PACKAGES', 'FILES', 'RDEPENDS', 'RRECOMMENDS', 'RSUGGESTS', 'RPROVIDES', 'RREPLACES', 'RCONFLICTS', 'ALLOW_EMPTY', 'do_package', 'do_deploy'] +# Variables that sometimes are a bit long but shouldn't be wrapped +nowrap_vars = ['SUMMARY', 'HOMEPAGE', 'BUGTRACKER'] +list_vars = ['SRC_URI', 'LIC_FILES_CHKSUM'] +meta_vars = ['SUMMARY', 'DESCRIPTION', 'HOMEPAGE', 'BUGTRACKER', 'SECTION'] + + +def pn_to_recipe(cooker, pn): + """Convert a recipe name (PN) to the path to the recipe file""" + import bb.providers + + if pn in cooker.recipecache.pkg_pn: + filenames = cooker.recipecache.pkg_pn[pn] + best = bb.providers.findBestProvider(pn, cooker.data, cooker.recipecache, cooker.recipecache.pkg_pn) + return best[3] + else: + return None + + +def get_unavailable_reasons(cooker, pn): + """If a recipe could not be found, find out why if possible""" + import bb.taskdata + taskdata = bb.taskdata.TaskData(None, skiplist=cooker.skiplist) + return taskdata.get_reasons(pn) + + +def parse_recipe(fn, d): + """Parse an individual recipe""" + import bb.cache + envdata = bb.cache.Cache.loadDataFull(fn, [], d) + return envdata + + +def get_var_files(fn, varlist, d): + """Find the file in which each of a list of variables is set. + Note: requires variable history to be enabled when parsing. + """ + envdata = parse_recipe(fn, d) + varfiles = {} + for v in varlist: + history = envdata.varhistory.variable(v) + files = [] + for event in history: + if 'file' in event and not 'flag' in event: + files.append(event['file']) + if files: + actualfile = files[-1] + else: + actualfile = None + varfiles[v] = actualfile + + return varfiles + + +def patch_recipe_file(fn, values, patch=False, relpath=''): + """Update or insert variable values into a recipe file (assuming you + have already identified the exact file you want to update.) + Note that some manual inspection/intervention may be required + since this cannot handle all situations. + """ + remainingnames = {} + for k in values.keys(): + remainingnames[k] = recipe_progression.index(k) if k in recipe_progression else -1 + remainingnames = OrderedDict(sorted(remainingnames.iteritems(), key=lambda x: x[1])) + + with tempfile.NamedTemporaryFile('w', delete=False) as tf: + def outputvalue(name): + rawtext = '%s = "%s"\n' % (name, values[name]) + if name in nowrap_vars: + tf.write(rawtext) + elif name in list_vars: + splitvalue = values[name].split() + if len(splitvalue) > 1: + linesplit = ' \\\n' + (' ' * (len(name) + 4)) + tf.write('%s = "%s%s"\n' % (name, linesplit.join(splitvalue), linesplit)) + else: + tf.write(rawtext) + else: + wrapped = textwrap.wrap(rawtext) + for wrapline in wrapped[:-1]: + tf.write('%s \\\n' % wrapline) + tf.write('%s\n' % wrapped[-1]) + + tfn = tf.name + with open(fn, 'r') as f: + # First runthrough - find existing names (so we know not to insert based on recipe_progression) + # Second runthrough - make the changes + existingnames = [] + for runthrough in [1, 2]: + currname = None + for line in f: + if not currname: + insert = False + for k in remainingnames.keys(): + for p in recipe_progression: + if re.match('^%s(_prepend|_append)*[ ?:=(]' % p, line): + if remainingnames[k] > -1 and recipe_progression.index(p) > remainingnames[k] and runthrough > 1 and not k in existingnames: + outputvalue(k) + del remainingnames[k] + break + for k in remainingnames.keys(): + if re.match('^%s[ ?:=]' % k, line): + currname = k + if runthrough == 1: + existingnames.append(k) + else: + del remainingnames[k] + break + if currname and runthrough > 1: + outputvalue(currname) + + if currname: + sline = line.rstrip() + if not sline.endswith('\\'): + currname = None + continue + if runthrough > 1: + tf.write(line) + f.seek(0) + if remainingnames: + tf.write('\n') + for k in remainingnames.keys(): + outputvalue(k) + + with open(tfn, 'U') as f: + tolines = f.readlines() + if patch: + with open(fn, 'U') as f: + fromlines = f.readlines() + relfn = os.path.relpath(fn, relpath) + diff = difflib.unified_diff(fromlines, tolines, 'a/%s' % relfn, 'b/%s' % relfn) + os.remove(tfn) + return diff + else: + with open(fn, 'w') as f: + f.writelines(tolines) + os.remove(tfn) + return None + +def localise_file_vars(fn, varfiles, varlist): + """Given a list of variables and variable history (fetched with get_var_files()) + find where each variable should be set/changed. This handles for example where a + recipe includes an inc file where variables might be changed - in most cases + we want to update the inc file when changing the variable value rather than adding + it to the recipe itself. + """ + fndir = os.path.dirname(fn) + os.sep + + first_meta_file = None + for v in meta_vars: + f = varfiles.get(v, None) + if f: + actualdir = os.path.dirname(f) + os.sep + if actualdir.startswith(fndir): + first_meta_file = f + break + + filevars = defaultdict(list) + for v in varlist: + f = varfiles[v] + # Only return files that are in the same directory as the recipe or in some directory below there + # (this excludes bbclass files and common inc files that wouldn't be appropriate to set the variable + # in if we were going to set a value specific to this recipe) + if f: + actualfile = f + else: + # Variable isn't in a file, if it's one of the "meta" vars, use the first file with a meta var in it + if first_meta_file: + actualfile = first_meta_file + else: + actualfile = fn + + actualdir = os.path.dirname(actualfile) + os.sep + if not actualdir.startswith(fndir): + actualfile = fn + filevars[actualfile].append(v) + + return filevars + +def patch_recipe(d, fn, varvalues, patch=False, relpath=''): + """Modify a list of variable values in the specified recipe. Handles inc files if + used by the recipe. + """ + varlist = varvalues.keys() + varfiles = get_var_files(fn, varlist, d) + locs = localise_file_vars(fn, varfiles, varlist) + patches = [] + for f,v in locs.iteritems(): + vals = {k: varvalues[k] for k in v} + patchdata = patch_recipe_file(f, vals, patch, relpath) + if patch: + patches.append(patchdata) + + if patch: + return patches + else: + return None + + + +def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): + """Copy (local) recipe files, including both files included via include/require, + and files referred to in the SRC_URI variable.""" + import bb.fetch2 + import oe.path + + # FIXME need a warning if the unexpanded SRC_URI value contains variable references + + uris = (d.getVar('SRC_URI', True) or "").split() + fetch = bb.fetch2.Fetch(uris, d) + if download: + fetch.download() + + # Copy local files to target directory and gather any remote files + bb_dir = os.path.dirname(d.getVar('FILE', True)) + os.sep + remotes = [] + includes = [path for path in d.getVar('BBINCLUDED', True).split() if + path.startswith(bb_dir) and os.path.exists(path)] + for path in fetch.localpaths() + includes: + # Only import files that are under the meta directory + if path.startswith(bb_dir): + if not whole_dir: + relpath = os.path.relpath(path, bb_dir) + subdir = os.path.join(tgt_dir, os.path.dirname(relpath)) + if not os.path.exists(subdir): + os.makedirs(subdir) + shutil.copy2(path, os.path.join(tgt_dir, relpath)) + else: + remotes.append(path) + # Simply copy whole meta dir, if requested + if whole_dir: + shutil.copytree(bb_dir, tgt_dir) + + return remotes + + +def get_recipe_patches(d): + """Get a list of the patches included in SRC_URI within a recipe.""" + patchfiles = [] + # Execute src_patches() defined in patch.bbclass - this works since that class + # is inherited globally + patches = bb.utils.exec_flat_python_func('src_patches', d) + for patch in patches: + _, _, local, _, _, parm = bb.fetch.decodeurl(patch) + patchfiles.append(local) + return patchfiles + + +def validate_pn(pn): + """Perform validation on a recipe name (PN) for a new recipe.""" + reserved_names = ['forcevariable', 'append', 'prepend', 'remove'] + if not re.match('[0-9a-z-]+', pn): + return 'Recipe name "%s" is invalid: only characters 0-9, a-z and - are allowed' % pn + elif pn in reserved_names: + return 'Recipe name "%s" is invalid: is a reserved keyword' % pn + elif pn.startswith('pn-'): + return 'Recipe name "%s" is invalid: names starting with "pn-" are reserved' % pn + return '' + diff --git a/meta/lib/oe/rootfs.py b/meta/lib/oe/rootfs.py index c4735f2755..a18472aeeb 100644 --- a/meta/lib/oe/rootfs.py +++ b/meta/lib/oe/rootfs.py @@ -118,17 +118,18 @@ class Rootfs(object): if self.d.getVar('USE_DEVFS', True) != "1": self._create_devfs() - self._uninstall_uneeded() + self._uninstall_unneeded() self._insert_feed_uris() self._run_ldconfig() - self._generate_kernel_module_deps() + if self.d.getVar('USE_DEPMOD', True) != "0": + self._generate_kernel_module_deps() self._cleanup() - def _uninstall_uneeded(self): + def _uninstall_unneeded(self): # Remove unneeded init script symlinks delayed_postinsts = self._get_delayed_postinsts() if delayed_postinsts is None: @@ -137,34 +138,39 @@ class Rootfs(object): self.d.getVar('IMAGE_ROOTFS', True), "run-postinsts", "remove"]) - # Remove unneeded package-management related components - if bb.utils.contains("IMAGE_FEATURES", "package-management", - True, False, self.d): - return + runtime_pkgmanage = bb.utils.contains("IMAGE_FEATURES", "package-management", + True, False, self.d) + if not runtime_pkgmanage: + # Remove components that we don't need if we're not going to install + # additional packages at runtime + if delayed_postinsts is None: + installed_pkgs_dir = self.d.expand('${WORKDIR}/installed_pkgs.txt') + pkgs_to_remove = list() + with open(installed_pkgs_dir, "r+") as installed_pkgs: + pkgs_installed = installed_pkgs.read().split('\n') + for pkg_installed in pkgs_installed[:]: + pkg = pkg_installed.split()[0] + if pkg in ["update-rc.d", + "base-passwd", + self.d.getVar("ROOTFS_BOOTSTRAP_INSTALL", True) + ]: + pkgs_to_remove.append(pkg) + pkgs_installed.remove(pkg_installed) + + if len(pkgs_to_remove) > 0: + self.pm.remove(pkgs_to_remove, False) + # Update installed_pkgs.txt + open(installed_pkgs_dir, "w+").write('\n'.join(pkgs_installed)) - if delayed_postinsts is None: - installed_pkgs_dir = self.d.expand('${WORKDIR}/installed_pkgs.txt') - pkgs_to_remove = list() - with open(installed_pkgs_dir, "r+") as installed_pkgs: - pkgs_installed = installed_pkgs.read().split('\n') - for pkg_installed in pkgs_installed[:]: - pkg = pkg_installed.split()[0] - if pkg in ["update-rc.d", - "base-passwd", - self.d.getVar("ROOTFS_BOOTSTRAP_INSTALL", True) - ]: - pkgs_to_remove.append(pkg) - pkgs_installed.remove(pkg_installed) - - if len(pkgs_to_remove) > 0: - self.pm.remove(pkgs_to_remove, False) - # Update installed_pkgs.txt - open(installed_pkgs_dir, "w+").write('\n'.join(pkgs_installed)) + else: + self._save_postinsts() - else: - self._save_postinsts() + post_uninstall_cmds = self.d.getVar("ROOTFS_POSTUNINSTALL_COMMAND", True) + execute_pre_post_process(self.d, post_uninstall_cmds) - self.pm.remove_packaging_data() + if not runtime_pkgmanage: + # Remove the package manager data files + self.pm.remove_packaging_data() def _run_intercepts(self): intercepts_dir = os.path.join(self.d.getVar('WORKDIR', True), @@ -209,16 +215,17 @@ class Rootfs(object): 'new', '-v']) def _generate_kernel_module_deps(self): - kernel_abi_ver_file = os.path.join(self.d.getVar('STAGING_KERNEL_DIR', True), + kernel_abi_ver_file = oe.path.join(self.d.getVar('PKGDATA_DIR', True), "kernel-depmod", 'kernel-abiversion') - if os.path.exists(kernel_abi_ver_file): - kernel_ver = open(kernel_abi_ver_file).read().strip(' \n') - modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules', kernel_ver) + if not os.path.exists(kernel_abi_ver_file): + bb.fatal("No kernel-abiversion file found (%s), cannot run depmod, aborting" % kernel_abi_ver_file) + + kernel_ver = open(kernel_abi_ver_file).read().strip(' \n') + modules_dir = os.path.join(self.image_rootfs, 'lib', 'modules', kernel_ver) - bb.utils.mkdirhier(modules_dir) + bb.utils.mkdirhier(modules_dir) - self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, - kernel_ver]) + self._exec_shell_cmd(['depmodwrapper', '-a', '-b', self.image_rootfs, kernel_ver]) """ Create devfs: @@ -348,11 +355,11 @@ class RpmRootfs(Rootfs): pass def _log_check_warn(self): - r = re.compile('(warn|Warn)') + r = re.compile('^(warn|Warn|NOTE: warn|NOTE: Warn)') log_path = self.d.expand("${T}/log.do_rootfs") with open(log_path, 'r') as log: for line in log.read().split('\n'): - if 'log_check' in line: + if 'log_check' in line or 'NOTE:' in line: continue m = r.search(line) @@ -402,7 +409,10 @@ class RpmRootfs(Rootfs): # __db.00* (Berkeley DB files that hold locks, rpm specific environment # settings, etc.), that should not get into the final rootfs self.pm.unlock_rpm_db() - bb.utils.remove(self.image_rootfs + "/install", True) + if os.path.isdir(self.pm.install_dir_path + "/tmp") and not os.listdir(self.pm.install_dir_path + "/tmp"): + bb.utils.remove(self.pm.install_dir_path + "/tmp", True) + if os.path.isdir(self.pm.install_dir_path) and not os.listdir(self.pm.install_dir_path): + bb.utils.remove(self.pm.install_dir_path, True) class DpkgRootfs(Rootfs): diff --git a/meta/lib/oe/sdk.py b/meta/lib/oe/sdk.py index c57a441941..a6767412c7 100644 --- a/meta/lib/oe/sdk.py +++ b/meta/lib/oe/sdk.py @@ -269,6 +269,8 @@ class DpkgSdk(Sdk): bb.note("Installing TARGET packages") self._populate_sysroot(self.target_pm, self.target_manifest) + self.target_pm.install_complementary(self.d.getVar('SDKIMAGE_INSTALL_COMPLEMENTARY', True)) + execute_pre_post_process(self.d, self.d.getVar("POPULATE_SDK_POST_TARGET_COMMAND", True)) self._copy_apt_dir_to(os.path.join(self.sdk_target_sysroot, "etc", "apt")) diff --git a/meta/lib/oe/sstatesig.py b/meta/lib/oe/sstatesig.py index af7617ee61..62e75c25ca 100644 --- a/meta/lib/oe/sstatesig.py +++ b/meta/lib/oe/sstatesig.py @@ -137,13 +137,16 @@ class SignatureGeneratorOEBasicHash(bb.siggen.SignatureGeneratorBasicHash): return super(bb.siggen.SignatureGeneratorBasicHash, self).dump_sigtask(fn, task, stampbase, runtime) - def dump_lockedsigs(self, sigfile=None): + def dump_lockedsigs(self, sigfile=None, taskfilter=None): if not sigfile: sigfile = os.getcwd() + "/locked-sigs.inc" bb.plain("Writing locked sigs to %s" % sigfile) types = {} for k in self.runtaskdeps: + if taskfilter: + if not k in taskfilter: + continue fn = k.rsplit(".",1)[0] t = self.lockedhashfn[fn].split(" ")[1].split(":")[5] t = 't-' + t.replace('_', '-') diff --git a/meta/lib/oe/terminal.py b/meta/lib/oe/terminal.py index 0a623c75b1..4f5c611615 100644 --- a/meta/lib/oe/terminal.py +++ b/meta/lib/oe/terminal.py @@ -2,6 +2,7 @@ import logging import oe.classutils import shlex from bb.process import Popen, ExecutionError +from distutils.version import LooseVersion logger = logging.getLogger('BitBake.OE.Terminal') @@ -52,9 +53,17 @@ class XTerminal(Terminal): raise UnsupportedTerminal(self.name) class Gnome(XTerminal): - command = 'gnome-terminal -t "{title}" -x {command}' + command = 'gnome-terminal -t "{title}" --disable-factory -x {command}' priority = 2 + def __init__(self, sh_cmd, title=None, env=None, d=None): + # Check version + vernum = check_terminal_version("gnome-terminal") + if vernum and LooseVersion(vernum) >= '3.10': + logger.debug(1, 'Gnome-Terminal 3.10 or later does not support --disable-factory') + self.command = 'gnome-terminal -t "{title}" -x {command}' + XTerminal.__init__(self, sh_cmd, title, env, d) + class Mate(XTerminal): command = 'mate-terminal -t "{title}" -x {command}' priority = 2 @@ -63,17 +72,20 @@ class Xfce(XTerminal): command = 'xfce4-terminal -T "{title}" -e "{command}"' priority = 2 +class Terminology(XTerminal): + command = 'terminology -T="{title}" -e {command}' + priority = 2 + class Konsole(XTerminal): - command = 'konsole -T "{title}" -e {command}' + command = 'konsole --nofork -p tabtitle="{title}" -e {command}' priority = 2 def __init__(self, sh_cmd, title=None, env=None, d=None): # Check version - vernum = check_konsole_version("konsole") - if vernum: - if vernum.split('.')[0] == "2": - logger.debug(1, 'Konsole from KDE 4.x will not work as devshell, skipping') - raise UnsupportedTerminal(self.name) + vernum = check_terminal_version("konsole") + if vernum and LooseVersion(vernum) < '2.0.0': + # Konsole from KDE 3.x + self.command = 'konsole -T "{title}" -e {command}' XTerminal.__init__(self, sh_cmd, title, env, d) class XTerm(XTerminal): @@ -112,6 +124,24 @@ class TmuxRunning(Terminal): if not os.getenv('TMUX'): raise UnsupportedTerminal('tmux is not running') + if not check_tmux_pane_size('tmux'): + raise UnsupportedTerminal('tmux pane too small') + + Terminal.__init__(self, sh_cmd, title, env, d) + +class TmuxNewWindow(Terminal): + """Open a new window in the current running tmux session""" + name = 'tmux-new-window' + command = 'tmux new-window -n "{title}" "{command}"' + priority = 2.70 + + def __init__(self, sh_cmd, title=None, env=None, d=None): + if not bb.utils.which(os.getenv('PATH'), 'tmux'): + raise UnsupportedTerminal('tmux is not installed') + + if not os.getenv('TMUX'): + raise UnsupportedTerminal('tmux is not running') + Terminal.__init__(self, sh_cmd, title, env, d) class Tmux(Terminal): @@ -180,10 +210,27 @@ def spawn(name, sh_cmd, title=None, env=None, d=None): if pipe.returncode != 0: raise ExecutionError(sh_cmd, pipe.returncode, output) -def check_konsole_version(konsole): +def check_tmux_pane_size(tmux): + import subprocess as sub + try: + p = sub.Popen('%s list-panes -F "#{?pane_active,#{pane_height},}"' % tmux, + shell=True,stdout=sub.PIPE,stderr=sub.PIPE) + out, err = p.communicate() + size = int(out.strip()) + except OSError as exc: + import errno + if exc.errno == errno.ENOENT: + return None + else: + raise + if size/2 >= 19: + return True + return False + +def check_terminal_version(terminalName): import subprocess as sub try: - p = sub.Popen(['sh', '-c', '%s --version' % konsole],stdout=sub.PIPE,stderr=sub.PIPE) + p = sub.Popen(['sh', '-c', '%s --version' % terminalName],stdout=sub.PIPE,stderr=sub.PIPE) out, err = p.communicate() ver_info = out.rstrip().split('\n') except OSError as exc: @@ -196,6 +243,8 @@ def check_konsole_version(konsole): for ver in ver_info: if ver.startswith('Konsole'): vernum = ver.split(' ')[-1] + if ver.startswith('GNOME Terminal'): + vernum = ver.split(' ')[-1] return vernum def distro_name(): diff --git a/meta/lib/oe/utils.py b/meta/lib/oe/utils.py index 35442568e2..bedade292b 100644 --- a/meta/lib/oe/utils.py +++ b/meta/lib/oe/utils.py @@ -42,7 +42,15 @@ def version_less_or_equal(variable, checkvalue, truevalue, falsevalue, d): return falsevalue def both_contain(variable1, variable2, checkvalue, d): - if d.getVar(variable1,1).find(checkvalue) != -1 and d.getVar(variable2,1).find(checkvalue) != -1: + val1 = d.getVar(variable1, True) + val2 = d.getVar(variable2, True) + val1 = set(val1.split()) + val2 = set(val2.split()) + if isinstance(checkvalue, basestring): + checkvalue = set(checkvalue.split()) + else: + checkvalue = set(checkvalue) + if checkvalue.issubset(val1) and checkvalue.issubset(val2): return checkvalue else: return "" @@ -180,3 +188,7 @@ def multiprocess_exec(commands, function): pool.terminate() pool.join() raise + +def squashspaces(string): + import re + return re.sub("\s+", " ", string).strip() diff --git a/meta/lib/oeqa/runtime/_ptest.py b/meta/lib/oeqa/runtime/_ptest.py index 4c58dc1d7f..81c9c43862 100644 --- a/meta/lib/oeqa/runtime/_ptest.py +++ b/meta/lib/oeqa/runtime/_ptest.py @@ -86,7 +86,7 @@ class PtestRunnerTest(oeRuntimeTest): installed_pkgs.write(self.pkgs_list.list("arch")) cmd = [bb.utils.which(os.getenv('PATH'), "oe-pkgdata-util"), - "glob", oeRuntimeTest.tc.d.getVar('PKGDATA_DIR', True), installed_pkgs_file, + "-p", oeRuntimeTest.tc.d.getVar('PKGDATA_DIR', True), "glob", installed_pkgs_file, globs] try: bb.note("Installing complementary packages ...") @@ -99,26 +99,27 @@ class PtestRunnerTest(oeRuntimeTest): return complementary_pkgs.split() def setUp(self): - self.buildhist_dir = oeRuntimeTest.tc.d.getVar("BUILDHISTORY_DIR_IMAGE", True) - self.assertTrue(os.path.exists(self.buildhist_dir)) self.ptest_log = os.path.join(oeRuntimeTest.tc.d.getVar("TEST_LOG_DIR",True), "ptest-%s.log" % oeRuntimeTest.tc.d.getVar('DATETIME', True)) @skipUnlessPassed('test_ssh') def test_ptestrunner(self): self.add_smart_channel() - cond = oeRuntimeTest.hasPackage("ptest-runner") and oeRuntimeTest.hasFeature("ptest") and oeRuntimeTest.hasPackage("-ptest") - if not cond: + (runnerstatus, result) = self.target.run('which ptest-runner', 0) + cond = oeRuntimeTest.hasPackage("ptest-runner") and oeRuntimeTest.hasFeature("ptest") and oeRuntimeTest.hasPackage("-ptest") and (runnerstatus != 0) + if cond: self.install_packages(self.install_complementary("*-ptest")) self.install_packages(['ptest-runner']) - self.target.run('/usr/bin/ptest-runner > /tmp/ptest.log 2>&1', 0) + (runnerstatus, result) = self.target.run('/usr/bin/ptest-runner > /tmp/ptest.log 2>&1', 0) + #exit code is !=0 even if ptest-runner executes because some ptest tests fail. + self.assertTrue(runnerstatus != 127, msg="Cannot execute ptest-runner!") self.target.copy_from('/tmp/ptest.log', self.ptest_log) - shutil.copyfile(self.ptest_log, os.path.join(self.buildhist_dir, "ptest.log")) + shutil.copyfile(self.ptest_log, "ptest.log") - result = self.parse_ptest(os.path.join(self.buildhist_dir, "ptest.log")) + result = self.parse_ptest("ptest.log") log_results_to_location = "./results" if os.path.exists(log_results_to_location): shutil.rmtree(log_results_to_location) os.makedirs(log_results_to_location) - result.log_as_files(log_results_to_location, test_status = ['fail']) + result.log_as_files(log_results_to_location, test_status = ['pass','fail']) diff --git a/meta/lib/oeqa/runtime/parselogs.py b/meta/lib/oeqa/runtime/parselogs.py index 42cb1b5e6f..7721912a23 100644 --- a/meta/lib/oeqa/runtime/parselogs.py +++ b/meta/lib/oeqa/runtime/parselogs.py @@ -7,10 +7,26 @@ from oeqa.utils.decorators import * errors = ["error", "cannot", "can\'t", "failed"] common_errors = [ - '(WW) warning, (EE) error, (NI) not implemented, (??) unknown.', - 'dma timeout', - 'can\'t add hid device:', - 'usbhid: probe of ', + "(WW) warning, (EE) error, (NI) not implemented, (??) unknown.", + "dma timeout", + "can\'t add hid device:", + "usbhid: probe of ", + "_OSC failed (AE_ERROR)", + "_OSC failed (AE_SUPPORT)", + "AE_ALREADY_EXISTS", + "ACPI _OSC request failed (AE_SUPPORT)", + "can\'t disable ASPM", + "Failed to load module \"vesa\"", + "Failed to load module vesa", + "Failed to load module \"modesetting\"", + "Failed to load module modesetting", + "Failed to load module \"glx\"", + "Failed to load module glx", + "[drm] Cannot find any crtc or sizes - going 1024x768", + "_OSC failed (AE_NOT_FOUND); disabling ASPM", + "Open ACPI failed (/var/run/acpid.socket) (No such file or directory)", + "NX (Execute Disable) protection cannot be enabled: non-PAE kernel!", + "hd.: possibly failed opcode" ] x86_common = [ @@ -22,11 +38,6 @@ x86_common = [ qemux86_common = [ 'Fast TSC calibration', - '_OSC failed (AE_NOT_FOUND); disabling ASPM', - 'Open ACPI failed (/var/run/acpid.socket) (No such file or directory)', - 'Failed to load module "vesa"', - 'Failed to load module "modesetting"', - 'Failed to load module "glx"', 'wrong ELF class', ] + common_errors @@ -39,9 +50,11 @@ ignore_errors = { 'qemux86-64' : qemux86_common, 'qemumips' : [ 'Failed to load module "glx"', + 'pci 0000:00:00.0: [Firmware Bug]: reg 0x..: invalid BAR (can\'t size)', ] + common_errors, 'qemuppc' : [ 'PCI 0000:00 Cannot reserve Legacy IO [io 0x0000-0x0fff]', + 'host side 80-wire cable detection failed, limiting max speed', 'mode "640x480" test failed', 'Failed to load module "glx"', ] + common_errors, @@ -54,6 +67,19 @@ ignore_errors = { 'crownbay' : x86_common, 'genericx86' : x86_common, 'genericx86-64' : x86_common, + 'edgerouter' : [ + 'Fatal server error:', + ] + common_errors, + 'minnow' : [ + 'netlink init failed', + 'NETLINK INITIALIZATION FAILED', + ] + common_errors, + 'jasperforest' : [ + 'Activated service \'org.bluez\' failed:', + 'Unable to find NFC netlink family', + 'netlink init failed', + 'NETLINK INITIALIZATION FAILED', + ] + common_errors, } log_locations = ["/var/log/","/var/log/dmesg", "/tmp/dmesg_output.log"] @@ -100,9 +126,10 @@ class ParseLogsTest(oeRuntimeTest): (status, output) = self.target.run("test -d "+str(location)) if (status == 0): (status, output) = self.target.run("find "+str(location)+"/*.log -maxdepth 1 -type f") - output = output.splitlines() - for logfile in output: - logs.append(os.path.join(location,str(logfile))) + if (status == 0): + output = output.splitlines() + for logfile in output: + logs.append(os.path.join(location,str(logfile))) return logs #build the grep command to be used with filters and exclusions diff --git a/meta/lib/oeqa/runtime/rpm.py b/meta/lib/oeqa/runtime/rpm.py index b17e8b46a8..0529992cbe 100644 --- a/meta/lib/oeqa/runtime/rpm.py +++ b/meta/lib/oeqa/runtime/rpm.py @@ -47,6 +47,16 @@ class RpmInstallRemoveTest(oeRuntimeTest): (status,output) = self.target.run('rpm -e rpm-doc') self.assertEqual(status, 0, msg="Failed to remove rpm-doc package: %s" % output) + @testcase(1096) + @skipUnlessPassed('test_ssh') + def test_rpm_query_nonroot(self): + (status, output) = self.target.run('useradd test1') + self.assertTrue(status == 0, msg="Failed to create new user") + (status, output) = self.target.run('sudo -u test1 id') + self.assertTrue('(test1)' in output, msg="Failed to execute as new user") + (status, output) = self.target.run('sudo -u test1 rpm -qa') + self.assertEqual(status, 0, msg="status: %s. Cannot run rpm -qa" % status) + @classmethod def tearDownClass(self): oeRuntimeTest.tc.target.run('rm -f /tmp/rpm-doc.rpm') diff --git a/meta/lib/oeqa/runtime/smart.py b/meta/lib/oeqa/runtime/smart.py index 3b49314df7..dba3bd60d7 100644 --- a/meta/lib/oeqa/runtime/smart.py +++ b/meta/lib/oeqa/runtime/smart.py @@ -53,12 +53,15 @@ class SmartRepoTest(SmartTest): @classmethod def setUpClass(self): + self.repolist = [] self.repo_server = HTTPService(oeRuntimeTest.tc.d.getVar('DEPLOY_DIR', True), oeRuntimeTest.tc.target.server_ip) self.repo_server.start() @classmethod def tearDownClass(self): self.repo_server.stop() + for i in self.repolist: + oeRuntimeTest.tc.target.run('smart channel -y --remove '+str(i)) def test_smart_channel(self): self.smart('channel', 1) @@ -71,6 +74,7 @@ class SmartRepoTest(SmartTest): for arch in os.listdir('%s/%s' % (self.repo_server.root_dir, image_pkgtype)): if arch in pkgarchs: self.smart('channel -y --add {a} type=rpm-md baseurl={u}/{a}'.format(a=arch, u=deploy_url)) + self.repolist.append(arch) self.smart('update') def test_smart_channel_help(self): @@ -119,3 +123,47 @@ class SmartRepoTest(SmartTest): @skipUnlessPassed('test_smart_install') def test_smart_reinstall(self): self.smart('reinstall -y psplash-default') + + @testcase(727) + @skipUnlessPassed('test_smart_channel_add') + def test_smart_remote_repo(self): + self.smart('update') + self.smart('install -y psplash') + self.smart('remove -y psplash') + + @testcase(726) + def test_smart_local_dir(self): + self.target.run('mkdir /tmp/myrpmdir') + self.smart('channel --add myrpmdir type=rpm-dir path=/tmp/myrpmdir -y') + self.target.run('cd /tmp/myrpmdir') + self.smart('download psplash') + output = self.smart('channel --list') + for i in output.split("\n"): + if ("rpmsys" != str(i)) and ("myrpmdir" != str(i)): + self.smart('channel --disable '+str(i)) + self.target.run('cd /home/root') + self.smart('install psplash') + for i in output.split("\n"): + if ("rpmsys" != str(i)) and ("myrpmdir" != str(i)): + self.smart('channel --enable '+str(i)) + self.smart('channel --remove myrpmdir -y') + self.target.run("rm -rf /tmp/myrpmdir") + + @testcase(718) + def test_smart_add_rpmdir(self): + self.target.run('mkdir /tmp/myrpmdir') + self.smart('channel --add myrpmdir type=rpm-dir path=/tmp/myrpmdir -y') + self.smart('channel --disable myrpmdir -y') + output = self.smart('channel --show myrpmdir') + self.assertTrue("disabled = yes" in output, msg="Failed to disable rpm dir") + self.smart('channel --enable myrpmdir -y') + output = self.smart('channel --show myrpmdir') + self.assertFalse("disabled = yes" in output, msg="Failed to enable rpm dir") + self.smart('channel --remove myrpmdir -y') + self.target.run("rm -rf /tmp/myrpmdir") + + @testcase(731) + @skipUnlessPassed('test_smart_channel_add') + def test_smart_remove_package(self): + self.smart('install -y psplash') + self.smart('remove -y psplash')
\ No newline at end of file diff --git a/meta/lib/oeqa/runtime/systemd.py b/meta/lib/oeqa/runtime/systemd.py index 1451698bb3..5935edd549 100644 --- a/meta/lib/oeqa/runtime/systemd.py +++ b/meta/lib/oeqa/runtime/systemd.py @@ -86,3 +86,9 @@ class SystemdServiceTests(SystemdTest): self.systemctl('is-enabled', 'avahi-daemon.service', expected=1) self.systemctl('enable', 'avahi-daemon.service') self.systemctl('is-enabled', 'avahi-daemon.service') + +class SystemdJournalTests(SystemdTest): + @skipUnlessPassed('test_ssh') + def test_systemd_journal(self): + (status, output) = self.target.run('journalctl') + self.assertEqual(status, 0, output) diff --git a/meta/lib/oeqa/selftest/_toaster.py b/meta/lib/oeqa/selftest/_toaster.py index 1cf28a0144..2ed08d24e6 100644 --- a/meta/lib/oeqa/selftest/_toaster.py +++ b/meta/lib/oeqa/selftest/_toaster.py @@ -19,89 +19,89 @@ from oeqa.utils.decorators import testcase class ToasterSetup(oeSelfTest): def recipe_parse(self, file_path, var): - for line in open(file_path,'r'): - if line.find(var) > -1: - val = line.split(" = ")[1].replace("\"", "").strip() - return val + for line in open(file_path,'r'): + if line.find(var) > -1: + val = line.split(" = ")[1].replace("\"", "").strip() + return val def fix_file_path(self, file_path): - if ":" in file_path: - file_path=file_path.split(":")[2] - return file_path + if ":" in file_path: + file_path=file_path.split(":")[2] + return file_path class Toaster_DB_Tests(ToasterSetup): # Check if build name is unique - tc_id=795 @testcase(795) def test_Build_Unique_Name(self): - all_builds = Build.objects.all().count() - distinct_builds = Build.objects.values('id').distinct().count() - self.assertEqual(distinct_builds, all_builds, msg = 'Build name is not unique') + all_builds = Build.objects.all().count() + distinct_builds = Build.objects.values('id').distinct().count() + self.assertEqual(distinct_builds, all_builds, msg = 'Build name is not unique') # Check if build coocker log path is unique - tc_id=819 @testcase(819) def test_Build_Unique_Cooker_Log_Path(self): - distinct_path = Build.objects.values('cooker_log_path').distinct().count() - total_builds = Build.objects.values('id').count() - self.assertEqual(distinct_path, total_builds, msg = 'Build coocker log path is not unique') + distinct_path = Build.objects.values('cooker_log_path').distinct().count() + total_builds = Build.objects.values('id').count() + self.assertEqual(distinct_path, total_builds, msg = 'Build coocker log path is not unique') # Check if the number of errors matches the number of orm_logmessage.level entries with value 2 - tc_id=820 @testcase(820) def test_Build_Errors_No(self): - builds = Build.objects.values('id', 'errors_no') - cnt_err = [] - for build in builds: - log_mess_err_no = LogMessage.objects.filter(build = build['id'], level = 2).count() - if (build['errors_no'] != log_mess_err_no): - cnt_err.append(build['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + builds = Build.objects.values('id', 'errors_no') + cnt_err = [] + for build in builds: + log_mess_err_no = LogMessage.objects.filter(build = build['id'], level = 2).count() + if (build['errors_no'] != log_mess_err_no): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) # Check if the number of warnings matches the number of orm_logmessage.level entries with value 1 - tc=821 @testcase(821) def test_Build_Warnings_No(self): - builds = Build.objects.values('id', 'warnings_no') - cnt_err = [] - for build in builds: - log_mess_warn_no = LogMessage.objects.filter(build = build['id'], level = 1).count() - if (build['warnings_no'] != log_mess_warn_no): - cnt_err.append(build['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + builds = Build.objects.values('id', 'warnings_no') + cnt_err = [] + for build in builds: + log_mess_warn_no = LogMessage.objects.filter(build = build['id'], level = 1).count() + if (build['warnings_no'] != log_mess_warn_no): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) # Check if the build succeeded then the errors_no is 0 - tc_id=822 @testcase(822) def test_Build_Suceeded_Errors_No(self): - builds = Build.objects.filter(outcome = 0).values('id', 'errors_no') - cnt_err = [] - for build in builds: - if (build['errors_no'] != 0): - cnt_err.append(build['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + builds = Build.objects.filter(outcome = 0).values('id', 'errors_no') + cnt_err = [] + for build in builds: + if (build['errors_no'] != 0): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) # Check if task order is unique for one build - tc=824 - @testcase(824) + @testcase(824) def test_Task_Unique_Order(self): - builds = Build.objects.values('id') - cnt_err = [] - for build in builds: - total_task_order = Task.objects.filter(build = build['id']).values('order').count() - distinct_task_order = Task.objects.filter(build = build['id']).values('order').distinct().count() - if (total_task_order != distinct_task_order): - cnt_err.append(build['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + builds = Build.objects.values('id') + cnt_err = [] + for build in builds: + total_task_order = Task.objects.filter(build = build['id']).values('order').count() + distinct_task_order = Task.objects.filter(build = build['id']).values('order').distinct().count() + if (total_task_order != distinct_task_order): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) # Check task order sequence for one build - tc=825 @testcase(825) def test_Task_Order_Sequence(self): - builds = builds = Build.objects.values('id') - cnt_err = [] - for build in builds: - tasks = Task.objects.filter(Q(build = build['id']), ~Q(order = None), ~Q(task_name__contains = '_setscene')).values('id', 'order').order_by("order") - cnt_tasks = 0 - for task in tasks: - cnt_tasks += 1 - if (task['order'] != cnt_tasks): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + builds = builds = Build.objects.values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter(Q(build = build['id']), ~Q(order = None), ~Q(task_name__contains = '_setscene')).values('id', 'order').order_by("order") + cnt_tasks = 0 + for task in tasks: + cnt_tasks += 1 + if (task['order'] != cnt_tasks): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if disk_io matches the difference between EndTimeIO and StartTimeIO in build stats - tc=828 ### this needs to be updated ### @@ -110,336 +110,336 @@ class Toaster_DB_Tests(ToasterSetup): # Check if outcome = 2 (SSTATE) then sstate_result must be 3 (RESTORED) - tc=832 @testcase(832) def test_Task_If_Outcome_2_Sstate_Result_Must_Be_3(self): - tasks = Task.objects.filter(outcome = 2).values('id', 'sstate_result') - cnt_err = [] - for task in tasks: - if (row['sstate_result'] != 3): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(outcome = 2).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (row['sstate_result'] != 3): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if outcome = 1 (COVERED) or 3 (EXISTING) then sstate_result must be 0 (SSTATE_NA) - tc=833 @testcase(833) def test_Task_If_Outcome_1_3_Sstate_Result_Must_Be_0(self): - tasks = Task.objects.filter(outcome__in = (1, 3)).values('id', 'sstate_result') - cnt_err = [] - for task in tasks: - if (task['sstate_result'] != 0): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(outcome__in = (1, 3)).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (task['sstate_result'] != 0): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if outcome is 0 (SUCCESS) or 4 (FAILED) then sstate_result must be 0 (NA), 1 (MISS) or 2 (FAILED) - tc=834 @testcase(834) def test_Task_If_Outcome_0_4_Sstate_Result_Must_Be_0_1_2(self): - tasks = Task.objects.filter(outcome__in = (0, 4)).values('id', 'sstate_result') - cnt_err = [] - for task in tasks: - if (task['sstate_result'] not in [0, 1, 2]): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(outcome__in = (0, 4)).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (task['sstate_result'] not in [0, 1, 2]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if task_executed = TRUE (1), script_type must be 0 (CODING_NA), 2 (CODING_PYTHON), 3 (CODING_SHELL) - tc=891 @testcase(891) def test_Task_If_Task_Executed_True_Script_Type_0_2_3(self): - tasks = Task.objects.filter(task_executed = 1).values('id', 'script_type') - cnt_err = [] - for task in tasks: - if (task['script_type'] not in [0, 2, 3]): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(task_executed = 1).values('id', 'script_type') + cnt_err = [] + for task in tasks: + if (task['script_type'] not in [0, 2, 3]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if task_executed = TRUE (1), outcome must be 0 (SUCCESS) or 4 (FAILED) - tc=836 @testcase(836) def test_Task_If_Task_Executed_True_Outcome_0_4(self): - tasks = Task.objects.filter(task_executed = 1).values('id', 'outcome') - cnt_err = [] - for task in tasks: - if (task['outcome'] not in [0, 4]): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(task_executed = 1).values('id', 'outcome') + cnt_err = [] + for task in tasks: + if (task['outcome'] not in [0, 4]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if task_executed = FALSE (0), script_type must be 0 - tc=890 @testcase(890) def test_Task_If_Task_Executed_False_Script_Type_0(self): - tasks = Task.objects.filter(task_executed = 0).values('id', 'script_type') - cnt_err = [] - for task in tasks: - if (task['script_type'] != 0): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + tasks = Task.objects.filter(task_executed = 0).values('id', 'script_type') + cnt_err = [] + for task in tasks: + if (task['script_type'] != 0): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Check if task_executed = FALSE (0) and build outcome = SUCCEEDED (0), task outcome must be 1 (COVERED), 2 (CACHED), 3 (PREBUILT), 5 (EMPTY) - tc=837 @testcase(837) def test_Task_If_Task_Executed_False_Outcome_1_2_3_5(self): - builds = Build.objects.filter(outcome = 0).values('id') - cnt_err = [] - for build in builds: - tasks = Task.objects.filter(build = build['id'], task_executed = 0).values('id', 'outcome') - for task in tasks: - if (task['outcome'] not in [1, 2, 3, 5]): - cnt_err.append(task['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + builds = Build.objects.filter(outcome = 0).values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter(build = build['id'], task_executed = 0).values('id', 'outcome') + for task in tasks: + if (task['outcome'] not in [1, 2, 3, 5]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) # Key verification - tc=888 @testcase(888) def test_Target_Installed_Package(self): - rows = Target_Installed_Package.objects.values('id', 'target_id', 'package_id') - cnt_err = [] - for row in rows: - target = Target.objects.filter(id = row['target_id']).values('id') - package = Package.objects.filter(id = row['package_id']).values('id') - if (not target or not package): - cnt_err.append(row['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for target installed package id: %s' % cnt_err) + rows = Target_Installed_Package.objects.values('id', 'target_id', 'package_id') + cnt_err = [] + for row in rows: + target = Target.objects.filter(id = row['target_id']).values('id') + package = Package.objects.filter(id = row['package_id']).values('id') + if (not target or not package): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for target installed package id: %s' % cnt_err) # Key verification - tc=889 @testcase(889) def test_Task_Dependency(self): - rows = Task_Dependency.objects.values('id', 'task_id', 'depends_on_id') - cnt_err = [] - for row in rows: - task_id = Task.objects.filter(id = row['task_id']).values('id') - depends_on_id = Task.objects.filter(id = row['depends_on_id']).values('id') - if (not task_id or not depends_on_id): - cnt_err.append(row['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for task dependency id: %s' % cnt_err) + rows = Task_Dependency.objects.values('id', 'task_id', 'depends_on_id') + cnt_err = [] + for row in rows: + task_id = Task.objects.filter(id = row['task_id']).values('id') + depends_on_id = Task.objects.filter(id = row['depends_on_id']).values('id') + if (not task_id or not depends_on_id): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task dependency id: %s' % cnt_err) # Check if build target file_name is populated only if is_image=true AND orm_build.outcome=0 then if the file exists and its size matches the file_size value ### Need to add the tc in the test run @testcase(1037) def test_Target_File_Name_Populated(self): - builds = Build.objects.filter(outcome = 0).values('id') - for build in builds: - targets = Target.objects.filter(build_id = build['id'], is_image = 1).values('id') - for target in targets: - target_files = Target_Image_File.objects.filter(target_id = target['id']).values('id', 'file_name', 'file_size') - cnt_err = [] - for file_info in target_files: - target_id = file_info['id'] - target_file_name = file_info['file_name'] - target_file_size = file_info['file_size'] - if (not target_file_name or not target_file_size): - cnt_err.append(target_id) - else: - if (not os.path.exists(target_file_name)): - cnt_err.append(target_id) - else: - if (os.path.getsize(target_file_name) != target_file_size): - cnt_err.append(target_id) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for target image file id: %s' % cnt_err) + builds = Build.objects.filter(outcome = 0).values('id') + for build in builds: + targets = Target.objects.filter(build_id = build['id'], is_image = 1).values('id') + for target in targets: + target_files = Target_Image_File.objects.filter(target_id = target['id']).values('id', 'file_name', 'file_size') + cnt_err = [] + for file_info in target_files: + target_id = file_info['id'] + target_file_name = file_info['file_name'] + target_file_size = file_info['file_size'] + if (not target_file_name or not target_file_size): + cnt_err.append(target_id) + else: + if (not os.path.exists(target_file_name)): + cnt_err.append(target_id) + else: + if (os.path.getsize(target_file_name) != target_file_size): + cnt_err.append(target_id) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for target image file id: %s' % cnt_err) # Key verification - tc=884 @testcase(884) def test_Package_Dependency(self): - cnt_err = [] - deps = Package_Dependency.objects.values('id', 'package_id', 'depends_on_id') - for dep in deps: - if (dep['package_id'] == dep['depends_on_id']): - cnt_err.append(dep['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package dependency id: %s' % cnt_err) + cnt_err = [] + deps = Package_Dependency.objects.values('id', 'package_id', 'depends_on_id') + for dep in deps: + if (dep['package_id'] == dep['depends_on_id']): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package dependency id: %s' % cnt_err) # Check if recipe name does not start with a number (0-9) - tc=838 @testcase(838) def test_Recipe_Name(self): - recipes = Recipe.objects.values('id', 'name') - cnt_err = [] - for recipe in recipes: - if (recipe['name'][0].isdigit() is True): - cnt_err.append(recipe['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + recipes = Recipe.objects.values('id', 'name') + cnt_err = [] + for recipe in recipes: + if (recipe['name'][0].isdigit() is True): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) # Check if recipe section matches the content of the SECTION variable (if set) in file_path - tc=839 @testcase(839) def test_Recipe_DB_Section_Match_Recipe_File_Section(self): - recipes = Recipe.objects.values('id', 'section', 'file_path') - cnt_err = [] - for recipe in recipes: - file_path = self.fix_file_path(recipe['file_path']) - file_exists = os.path.isfile(file_path) - if (not file_path or (file_exists is False)): - cnt_err.append(recipe['id']) - else: - file_section = self.recipe_parse(file_path, "SECTION = ") - db_section = recipe['section'] - if file_section: - if (db_section != file_section): - cnt_err.append(recipe['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + recipes = Recipe.objects.values('id', 'section', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_section = self.recipe_parse(file_path, "SECTION = ") + db_section = recipe['section'] + if file_section: + if (db_section != file_section): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) # Check if recipe license matches the content of the LICENSE variable (if set) in file_path - tc=840 @testcase(840) def test_Recipe_DB_License_Match_Recipe_File_License(self): - recipes = Recipe.objects.values('id', 'license', 'file_path') - cnt_err = [] - for recipe in recipes: - file_path = self.fix_file_path(recipe['file_path']) - file_exists = os.path.isfile(file_path) - if (not file_path or (file_exists is False)): - cnt_err.append(recipe['id']) - else: - file_license = self.recipe_parse(file_path, "LICENSE = ") - db_license = recipe['license'] - if file_license: - if (db_license != file_license): - cnt_err.append(recipe['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + recipes = Recipe.objects.values('id', 'license', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_license = self.recipe_parse(file_path, "LICENSE = ") + db_license = recipe['license'] + if file_license: + if (db_license != file_license): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) # Check if recipe homepage matches the content of the HOMEPAGE variable (if set) in file_path - tc=841 @testcase(841) def test_Recipe_DB_Homepage_Match_Recipe_File_Homepage(self): - recipes = Recipe.objects.values('id', 'homepage', 'file_path') - cnt_err = [] - for recipe in recipes: - file_path = self.fix_file_path(recipe['file_path']) - file_exists = os.path.isfile(file_path) - if (not file_path or (file_exists is False)): - cnt_err.append(recipe['id']) - else: - file_homepage = self.recipe_parse(file_path, "HOMEPAGE = ") - db_homepage = recipe['homepage'] - if file_homepage: - if (db_homepage != file_homepage): - cnt_err.append(recipe['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + recipes = Recipe.objects.values('id', 'homepage', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_homepage = self.recipe_parse(file_path, "HOMEPAGE = ") + db_homepage = recipe['homepage'] + if file_homepage: + if (db_homepage != file_homepage): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) # Check if recipe bugtracker matches the content of the BUGTRACKER variable (if set) in file_path - tc=842 @testcase(842) def test_Recipe_DB_Bugtracker_Match_Recipe_File_Bugtracker(self): - recipes = Recipe.objects.values('id', 'bugtracker', 'file_path') - cnt_err = [] - for recipe in recipes: - file_path = self.fix_file_path(recipe['file_path']) - file_exists = os.path.isfile(file_path) - if (not file_path or (file_exists is False)): - cnt_err.append(recipe['id']) - else: - file_bugtracker = self.recipe_parse(file_path, "BUGTRACKER = ") - db_bugtracker = recipe['bugtracker'] - if file_bugtracker: - if (db_bugtracker != file_bugtracker): - cnt_err.append(recipe['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + recipes = Recipe.objects.values('id', 'bugtracker', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_bugtracker = self.recipe_parse(file_path, "BUGTRACKER = ") + db_bugtracker = recipe['bugtracker'] + if file_bugtracker: + if (db_bugtracker != file_bugtracker): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) # Recipe key verification, recipe name does not depends on a recipe having the same name - tc=883 @testcase(883) def test_Recipe_Dependency(self): - deps = Recipe_Dependency.objects.values('id', 'recipe_id', 'depends_on_id') - cnt_err = [] - for dep in deps: - if (not dep['recipe_id'] or not dep['depends_on_id']): - cnt_err.append(dep['id']) - else: - name = Recipe.objects.filter(id = dep['recipe_id']).values('name') - dep_name = Recipe.objects.filter(id = dep['depends_on_id']).values('name') - if (name == dep_name): - cnt_err.append(dep['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe dependency id: %s' % cnt_err) + deps = Recipe_Dependency.objects.values('id', 'recipe_id', 'depends_on_id') + cnt_err = [] + for dep in deps: + if (not dep['recipe_id'] or not dep['depends_on_id']): + cnt_err.append(dep['id']) + else: + name = Recipe.objects.filter(id = dep['recipe_id']).values('name') + dep_name = Recipe.objects.filter(id = dep['depends_on_id']).values('name') + if (name == dep_name): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe dependency id: %s' % cnt_err) # Check if package name does not start with a number (0-9) - tc=846 @testcase(846) def test_Package_Name_For_Number(self): - packages = Package.objects.filter(~Q(size = -1)).values('id', 'name') - cnt_err = [] - for package in packages: - if (package['name'][0].isdigit() is True): - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(~Q(size = -1)).values('id', 'name') + cnt_err = [] + for package in packages: + if (package['name'][0].isdigit() is True): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check if package version starts with a number (0-9) - tc=847 @testcase(847) def test_Package_Version_Starts_With_Number(self): - packages = Package.objects.filter(~Q(size = -1)).values('id', 'version') - cnt_err = [] - for package in packages: - if (package['version'][0].isdigit() is False): - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(~Q(size = -1)).values('id', 'version') + cnt_err = [] + for package in packages: + if (package['version'][0].isdigit() is False): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check if package revision starts with 'r' - tc=848 @testcase(848) def test_Package_Revision_Starts_With_r(self): - packages = Package.objects.filter(~Q(size = -1)).values('id', 'revision') - cnt_err = [] - for package in packages: - if (package['revision'][0].startswith("r") is False): - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(~Q(size = -1)).values('id', 'revision') + cnt_err = [] + for package in packages: + if (package['revision'][0].startswith("r") is False): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check the validity of the package build_id ### TC must be added in test run @testcase(1038) def test_Package_Build_Id(self): - packages = Package.objects.filter(~Q(size = -1)).values('id', 'build_id') - cnt_err = [] - for package in packages: - build_id = Build.objects.filter(id = package['build_id']).values('id') - if (not build_id): - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(~Q(size = -1)).values('id', 'build_id') + cnt_err = [] + for package in packages: + build_id = Build.objects.filter(id = package['build_id']).values('id') + if (not build_id): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check the validity of package recipe_id ### TC must be added in test run @testcase(1039) def test_Package_Recipe_Id(self): - packages = Package.objects.filter(~Q(size = -1)).values('id', 'recipe_id') - cnt_err = [] - for package in packages: - recipe_id = Recipe.objects.filter(id = package['recipe_id']).values('id') - if (not recipe_id): - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(~Q(size = -1)).values('id', 'recipe_id') + cnt_err = [] + for package in packages: + recipe_id = Recipe.objects.filter(id = package['recipe_id']).values('id') + if (not recipe_id): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check if package installed_size field is not null ### TC must be aded in test run @testcase(1040) def test_Package_Installed_Size_Not_NULL(self): - packages = Package.objects.filter(installed_size__isnull = True).values('id') - cnt_err = [] - for package in packages: - cnt_err.append(package['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + packages = Package.objects.filter(installed_size__isnull = True).values('id') + cnt_err = [] + for package in packages: + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) # Check if all layers requests return exit code is 200 - tc=843 @testcase(843) def test_Layers_Requests_Exit_Code(self): - layers = Layer.objects.values('id', 'layer_index_url') - cnt_err = [] - for layer in layers: - resp = urllib.urlopen(layer['layer_index_url']) - if (resp.getcode() != 200): - cnt_err.append(layer['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) + layers = Layer.objects.values('id', 'layer_index_url') + cnt_err = [] + for layer in layers: + resp = urllib.urlopen(layer['layer_index_url']) + if (resp.getcode() != 200): + cnt_err.append(layer['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) # Check if the output of bitbake-layers show_layers matches the info from database - tc=895 @testcase(895) def test_Layers_Show_Layers(self): - layers = Layer.objects.values('id', 'name', 'local_path') - cmd = commands.getoutput('bitbake-layers show_layers') - cnt_err = [] - for layer in layers: - if (layer['name'] or layer['local_path']) not in cmd: - cnt_err.append(layer['id']) - self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) + layers = Layer.objects.values('id', 'name', 'local_path') + cmd = commands.getoutput('bitbake-layers show_layers') + cnt_err = [] + for layer in layers: + if (layer['name'] or layer['local_path']) not in cmd: + cnt_err.append(layer['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) # Check if django server starts regardless of the timezone set on the machine - tc=905 @testcase(905) def test_Start_Django_Timezone(self): current_path = os.getcwd() - zonefilelist = [] + zonefilelist = [] ZONEINFOPATH = '/usr/share/zoneinfo/' - os.chdir("../bitbake/lib/toaster/") - cnt_err = 0 - for filename in os.listdir(ZONEINFOPATH): - if os.path.isfile(os.path.join(ZONEINFOPATH, filename)): - zonefilelist.append(filename) - for k in range(len(zonefilelist)): - if k <= 5: - files = zonefilelist[k] - os.system("export TZ="+str(files)+"; python manage.py runserver > /dev/null 2>&1 &") - time.sleep(3) - pid = subprocess.check_output("ps aux | grep '[/u]sr/bin/python manage.py runserver' | awk '{print $2}'", shell = True) - if pid: - os.system("kill -9 "+str(pid)) - else: - cnt_err.append(zonefilelist[k]) - self.assertEqual(cnt_err, 0, msg = 'Errors django server does not start with timezone: %s' % cnt_err) + os.chdir("../bitbake/lib/toaster/") + cnt_err = 0 + for filename in os.listdir(ZONEINFOPATH): + if os.path.isfile(os.path.join(ZONEINFOPATH, filename)): + zonefilelist.append(filename) + for k in range(len(zonefilelist)): + if k <= 5: + files = zonefilelist[k] + os.system("export TZ="+str(files)+"; python manage.py runserver > /dev/null 2>&1 &") + time.sleep(3) + pid = subprocess.check_output("ps aux | grep '[/u]sr/bin/python manage.py runserver' | awk '{print $2}'", shell = True) + if pid: + os.system("kill -9 "+str(pid)) + else: + cnt_err.append(zonefilelist[k]) + self.assertEqual(cnt_err, 0, msg = 'Errors django server does not start with timezone: %s' % cnt_err) os.chdir(current_path) diff --git a/meta/lib/oeqa/selftest/bblayers.py b/meta/lib/oeqa/selftest/bblayers.py index 1ead8e8671..5b046d06e1 100644 --- a/meta/lib/oeqa/selftest/bblayers.py +++ b/meta/lib/oeqa/selftest/bblayers.py @@ -18,26 +18,43 @@ class BitbakeLayers(oeSelfTest): @testcase(83) def test_bitbakelayers_showlayers(self): - result = runCmd('bitbake-layers show_layers') + result = runCmd('bitbake-layers show-layers') self.assertTrue('meta-selftest' in result.output) @testcase(93) def test_bitbakelayers_showappends(self): - result = runCmd('bitbake-layers show_appends') + result = runCmd('bitbake-layers show-appends') self.assertTrue('xcursor-transparent-theme_0.1.1.bbappend' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') @testcase(90) def test_bitbakelayers_showoverlayed(self): - result = runCmd('bitbake-layers show_overlayed') - self.assertTrue('aspell' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') + result = runCmd('bitbake-layers show-overlayed') + self.assertTrue('aspell' in result.output, msg='aspell overlayed recipe was not recognised') @testcase(95) def test_bitbakelayers_flatten(self): - self.assertFalse(os.path.isdir(os.path.join(self.builddir, 'test'))) - result = runCmd('bitbake-layers flatten test') - bb_file = os.path.join(self.builddir, 'test/recipes-graphics/xcursor-transparent-theme/xcursor-transparent-theme_0.1.1.bb') + testoutdir = os.path.join(self.builddir, 'test_bitbakelayers_flatten') + self.assertFalse(os.path.isdir(testoutdir)) + self.track_for_cleanup(testoutdir) + result = runCmd('bitbake-layers flatten %s' % testoutdir) + bb_file = os.path.join(testoutdir, 'recipes-graphics/xcursor-transparent-theme/xcursor-transparent-theme_0.1.1.bb') self.assertTrue(os.path.isfile(bb_file)) contents = ftools.read_file(bb_file) find_in_contents = re.search("##### bbappended from meta-selftest #####\n(.*\n)*include test_recipe.inc", contents) - shutil.rmtree(os.path.join(self.builddir, 'test')) self.assertTrue(find_in_contents) + + def test_bitbakelayers_add_remove(self): + result = runCmd('bitbake-layers show-layers') + self.assertNotIn('meta-skeleton', result.output, 'This test cannot run with meta-skeleton in bblayers.conf') + result = runCmd('bitbake-layers add-layer ../meta-skeleton') + result = runCmd('bitbake-layers show-layers') + self.assertIn('meta-skeleton', result.output) + result = runCmd('bitbake-layers remove-layer ../meta-skeleton') + result = runCmd('bitbake-layers show-layers') + self.assertNotIn('meta-skeleton', result.output) + result = runCmd('bitbake-layers add-layer ../meta-skeleton') + result = runCmd('bitbake-layers show-layers') + self.assertIn('meta-skeleton', result.output) + result = runCmd('bitbake-layers remove-layer */meta-skeleton') + result = runCmd('bitbake-layers show-layers') + self.assertNotIn('meta-skeleton', result.output) diff --git a/meta/lib/oeqa/selftest/bbtests.py b/meta/lib/oeqa/selftest/bbtests.py index 68f97bd8e3..b301d8f819 100644 --- a/meta/lib/oeqa/selftest/bbtests.py +++ b/meta/lib/oeqa/selftest/bbtests.py @@ -27,7 +27,7 @@ class BitbakeTests(oeSelfTest): def test_event_handler(self): self.write_config("INHERIT += \"test_events\"") result = bitbake('m4-native') - find_build_started = re.search("NOTE: Test for bb\.event\.BuildStarted(\n.*)*NOTE: Preparing runqueue", result.output) + find_build_started = re.search("NOTE: Test for bb\.event\.BuildStarted(\n.*)*NOTE: Preparing RunQueue", result.output) find_build_completed = re.search("Tasks Summary:.*(\n.*)*NOTE: Test for bb\.event\.BuildCompleted", result.output) self.assertTrue(find_build_started, msg = "Match failed in:\n%s" % result.output) self.assertTrue(find_build_completed, msg = "Match failed in:\n%s" % result.output) @@ -71,6 +71,7 @@ class BitbakeTests(oeSelfTest): @testcase(163) def test_force_task(self): bitbake('m4-native') + self.add_command_to_tearDown('bitbake -c clean m4-native') result = bitbake('-C compile m4-native') look_for_tasks = ['do_compile', 'do_install', 'do_populate_sysroot'] for task in look_for_tasks: @@ -119,60 +120,71 @@ class BitbakeTests(oeSelfTest): @testcase(1028) def test_environment(self): - self.append_config("TEST_ENV=\"localconf\"") - result = runCmd('bitbake -e | grep TEST_ENV=') - self.assertTrue('localconf' in result.output) - self.remove_config("TEST_ENV=\"localconf\"") + self.append_config("TEST_ENV=\"localconf\"") + result = runCmd('bitbake -e | grep TEST_ENV=') + self.assertTrue('localconf' in result.output) + self.remove_config("TEST_ENV=\"localconf\"") @testcase(1029) def test_dry_run(self): - result = runCmd('bitbake -n m4-native') - self.assertEqual(0, result.status) + result = runCmd('bitbake -n m4-native') + self.assertEqual(0, result.status) @testcase(1030) def test_just_parse(self): - result = runCmd('bitbake -p') - self.assertEqual(0, result.status) + result = runCmd('bitbake -p') + self.assertEqual(0, result.status) @testcase(1031) def test_version(self): - result = runCmd('bitbake -s | grep wget') - find = re.search("wget *:([0-9a-zA-Z\.\-]+)", result.output) - self.assertTrue(find) + result = runCmd('bitbake -s | grep wget') + find = re.search("wget *:([0-9a-zA-Z\.\-]+)", result.output) + self.assertTrue(find) @testcase(1032) def test_prefile(self): - preconf = os.path.join(self.builddir, 'conf/prefile.conf') - self.track_for_cleanup(preconf) - ftools.write_file(preconf ,"TEST_PREFILE=\"prefile\"") - result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') - self.assertTrue('prefile' in result.output) - self.append_config("TEST_PREFILE=\"localconf\"") - result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') - self.assertTrue('localconf' in result.output) - self.remove_config("TEST_PREFILE=\"localconf\"") + preconf = os.path.join(self.builddir, 'conf/prefile.conf') + self.track_for_cleanup(preconf) + ftools.write_file(preconf ,"TEST_PREFILE=\"prefile\"") + result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') + self.assertTrue('prefile' in result.output) + self.append_config("TEST_PREFILE=\"localconf\"") + result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') + self.assertTrue('localconf' in result.output) + self.remove_config("TEST_PREFILE=\"localconf\"") @testcase(1033) def test_postfile(self): - postconf = os.path.join(self.builddir, 'conf/postfile.conf') - self.track_for_cleanup(postconf) - ftools.write_file(postconf , "TEST_POSTFILE=\"postfile\"") - self.append_config("TEST_POSTFILE=\"localconf\"") - result = runCmd('bitbake -R conf/postfile.conf -e | grep TEST_POSTFILE=') - self.assertTrue('postfile' in result.output) - self.remove_config("TEST_POSTFILE=\"localconf\"") + postconf = os.path.join(self.builddir, 'conf/postfile.conf') + self.track_for_cleanup(postconf) + ftools.write_file(postconf , "TEST_POSTFILE=\"postfile\"") + self.append_config("TEST_POSTFILE=\"localconf\"") + result = runCmd('bitbake -R conf/postfile.conf -e | grep TEST_POSTFILE=') + self.assertTrue('postfile' in result.output) + self.remove_config("TEST_POSTFILE=\"localconf\"") @testcase(1034) def test_checkuri(self): - result = runCmd('bitbake -c checkuri m4') - self.assertEqual(0, result.status) + result = runCmd('bitbake -c checkuri m4') + self.assertEqual(0, result.status) @testcase(1035) def test_continue(self): - self.write_recipeinc('man',"\ndo_fail_task () {\nexit 1 \n}\n\naddtask do_fail_task before do_fetch\n" ) - runCmd('bitbake -c cleanall man xcursor-transparent-theme') - result = runCmd('bitbake man xcursor-transparent-theme -k', ignore_status=True) - errorpos = result.output.find('ERROR: Function failed: do_fail_task') - manver = re.search("NOTE: recipe xcursor-transparent-theme-(.*?): task do_unpack: Started", result.output) - continuepos = result.output.find('NOTE: recipe xcursor-transparent-theme-%s: task do_unpack: Started' % manver.group(1)) - self.assertLess(errorpos,continuepos) + self.write_recipeinc('man',"\ndo_fail_task () {\nexit 1 \n}\n\naddtask do_fail_task before do_fetch\n" ) + runCmd('bitbake -c cleanall man xcursor-transparent-theme') + result = runCmd('bitbake man xcursor-transparent-theme -k', ignore_status=True) + errorpos = result.output.find('ERROR: Function failed: do_fail_task') + manver = re.search("NOTE: recipe xcursor-transparent-theme-(.*?): task do_unpack: Started", result.output) + continuepos = result.output.find('NOTE: recipe xcursor-transparent-theme-%s: task do_unpack: Started' % manver.group(1)) + self.assertLess(errorpos,continuepos) + + @testcase(1119) + def test_non_gplv3(self): + data = 'INCOMPATIBLE_LICENSE = "GPLv3"' + conf = os.path.join(self.builddir, 'conf/local.conf') + ftools.append_file(conf ,data) + result = bitbake('readline', ignore_status=True) + self.assertEqual(result.status, 0) + self.assertFalse(os.path.isfile(os.path.join(self.builddir, 'tmp/deploy/licenses/readline/generic_GPLv3'))) + self.assertTrue(os.path.isfile(os.path.join(self.builddir, 'tmp/deploy/licenses/readline/generic_GPLv2'))) + ftools.remove_from_file(conf ,data)
\ No newline at end of file diff --git a/meta/lib/oeqa/selftest/buildoptions.py b/meta/lib/oeqa/selftest/buildoptions.py index a250cae0e1..926ffe9993 100644 --- a/meta/lib/oeqa/selftest/buildoptions.py +++ b/meta/lib/oeqa/selftest/buildoptions.py @@ -13,16 +13,22 @@ class ImageOptionsTests(oeSelfTest): @testcase(761) def test_incremental_image_generation(self): + image_pkgtype = get_bb_var("IMAGE_PKGTYPE") + if image_pkgtype != 'rpm': + self.skipTest('Not using RPM as main package format') bitbake("-c cleanall core-image-minimal") self.write_config('INC_RPM_IMAGE_GEN = "1"') self.append_config('IMAGE_FEATURES += "ssh-server-openssh"') bitbake("core-image-minimal") - res = runCmd("grep 'Installing openssh-sshd' %s" % (os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs")), ignore_status=True) + log_data_file = os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs") + log_data_created = ftools.read_file(log_data_file) + incremental_created = re.search("NOTE: load old install solution for incremental install\nNOTE: old install solution not exist\nNOTE: creating new install solution for incremental install(\n.*)*NOTE: Installing the following packages:.*packagegroup-core-ssh-openssh", log_data_created) self.remove_config('IMAGE_FEATURES += "ssh-server-openssh"') - self.assertEqual(0, res.status, msg="No match for openssh-sshd in log.do_rootfs") + self.assertTrue(incremental_created, msg = "Match failed in:\n%s" % log_data_created) bitbake("core-image-minimal") - res = runCmd("grep 'Removing openssh-sshd' %s" %(os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs")),ignore_status=True) - self.assertEqual(0, res.status, msg="openssh-sshd was not removed from image") + log_data_removed = ftools.read_file(log_data_file) + incremental_removed = re.search("NOTE: load old install solution for incremental install\nNOTE: creating new install solution for incremental install(\n.*)*NOTE: incremental removed:.*openssh-sshd-.*", log_data_removed) + self.assertTrue(incremental_removed, msg = "Match failed in:\n%s" % log_data_removed) @testcase(925) def test_rm_old_image(self): diff --git a/meta/lib/oeqa/selftest/devtool.py b/meta/lib/oeqa/selftest/devtool.py new file mode 100644 index 0000000000..dc1cf21064 --- /dev/null +++ b/meta/lib/oeqa/selftest/devtool.py @@ -0,0 +1,527 @@ +import unittest +import os +import logging +import re +import shutil +import tempfile +import glob + +import oeqa.utils.ftools as ftools +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd, bitbake, get_bb_var +from oeqa.utils.decorators import testcase + +class DevtoolTests(oeSelfTest): + + def test_create_workspace(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + result = runCmd('bitbake-layers show-layers') + self.assertTrue('/workspace' not in result.output, 'This test cannot be run with a workspace layer in bblayers.conf') + # Try creating a workspace layer with a specific path + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + result = runCmd('devtool create-workspace %s' % tempdir) + self.assertTrue(os.path.isfile(os.path.join(tempdir, 'conf', 'layer.conf'))) + result = runCmd('bitbake-layers show-layers') + self.assertIn(tempdir, result.output) + # Try creating a workspace layer with the default path + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool create-workspace') + self.assertTrue(os.path.isfile(os.path.join(workspacedir, 'conf', 'layer.conf'))) + result = runCmd('bitbake-layers show-layers') + self.assertNotIn(tempdir, result.output) + self.assertIn(workspacedir, result.output) + + def test_recipetool_create(self): + # Try adding a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + tempsrc = os.path.join(tempdir, 'srctree') + os.makedirs(tempsrc) + recipefile = os.path.join(tempdir, 'logrotate_3.8.7.bb') + srcuri = 'https://fedorahosted.org/releases/l/o/logrotate/logrotate-3.8.7.tar.gz' + result = runCmd('recipetool create -o %s %s -x %s' % (recipefile, srcuri, tempsrc)) + self.assertTrue(os.path.isfile(recipefile)) + checkvars = {} + checkvars['LICENSE'] = 'GPLv2' + checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=18810669f13b87348459e611d31ab760' + checkvars['SRC_URI'] = 'https://fedorahosted.org/releases/l/o/logrotate/logrotate-${PV}.tar.gz' + checkvars['SRC_URI[md5sum]'] = '99e08503ef24c3e2e3ff74cc5f3be213' + checkvars['SRC_URI[sha256sum]'] = 'f6ba691f40e30e640efa2752c1f9499a3f9738257660994de70a45fe00d12b64' + with open(recipefile, 'r') as f: + for line in f: + if '=' in line: + splitline = line.split('=', 1) + var = splitline[0].rstrip() + value = splitline[1].strip().strip('"') + if var in checkvars: + needvalue = checkvars.pop(var) + self.assertEqual(value, needvalue) + if line.startswith('inherit '): + inherits = line.split()[1:] + + self.assertEqual(checkvars, {}, 'Some variables not found') + + def test_recipetool_create_git(self): + # Ensure we have the right data in shlibs/pkgdata + bitbake('libpng pango libx11 libxext jpeg') + # Try adding a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + tempsrc = os.path.join(tempdir, 'srctree') + os.makedirs(tempsrc) + recipefile = os.path.join(tempdir, 'libmatchbox.bb') + srcuri = 'git://git.yoctoproject.org/libmatchbox' + result = runCmd('recipetool create -o %s %s -x %s' % (recipefile, srcuri, tempsrc)) + self.assertTrue(os.path.isfile(recipefile), 'recipetool did not create recipe file; output:\n%s' % result.output) + checkvars = {} + checkvars['LICENSE'] = 'LGPLv2.1' + checkvars['LIC_FILES_CHKSUM'] = 'file://COPYING;md5=7fbc338309ac38fefcd64b04bb903e34' + checkvars['S'] = '${WORKDIR}/git' + checkvars['PV'] = '1.0+git${SRCPV}' + checkvars['SRC_URI'] = srcuri + checkvars['DEPENDS'] = 'libpng pango libx11 libxext jpeg' + inherits = [] + with open(recipefile, 'r') as f: + for line in f: + if '=' in line: + splitline = line.split('=', 1) + var = splitline[0].rstrip() + value = splitline[1].strip().strip('"') + if var in checkvars: + needvalue = checkvars.pop(var) + self.assertEqual(value, needvalue) + if line.startswith('inherit '): + inherits = line.split()[1:] + + self.assertEqual(checkvars, {}, 'Some variables not found') + + self.assertIn('autotools', inherits, 'Missing inherit of autotools') + self.assertIn('pkgconfig', inherits, 'Missing inherit of pkgconfig') + + def test_devtool_add(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + # Fetch source + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + url = 'http://www.ivarch.com/programs/sources/pv-1.5.3.tar.bz2' + result = runCmd('wget %s' % url, cwd=tempdir) + result = runCmd('tar xfv pv-1.5.3.tar.bz2', cwd=tempdir) + srcdir = os.path.join(tempdir, 'pv-1.5.3') + self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory') + # Test devtool add + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake -c cleansstate pv') + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool add pv %s' % srcdir) + self.assertTrue(os.path.exists(os.path.join(workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn('pv', result.output) + self.assertIn(srcdir, result.output) + # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then) + bitbake('pv -c cleansstate') + # Test devtool build + result = runCmd('devtool build pv') + installdir = get_bb_var('D', 'pv') + self.assertTrue(installdir, 'Could not query installdir variable') + bindir = get_bb_var('bindir', 'pv') + self.assertTrue(bindir, 'Could not query bindir variable') + if bindir[0] == '/': + bindir = bindir[1:] + self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D') + + def test_devtool_add_library(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + # We don't have the ability to pick up this dependency automatically yet... + bitbake('libusb1') + # Fetch source + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + url = 'http://www.intra2net.com/en/developer/libftdi/download/libftdi1-1.1.tar.bz2' + result = runCmd('wget %s' % url, cwd=tempdir) + result = runCmd('tar xfv libftdi1-1.1.tar.bz2', cwd=tempdir) + srcdir = os.path.join(tempdir, 'libftdi1-1.1') + self.assertTrue(os.path.isfile(os.path.join(srcdir, 'CMakeLists.txt')), 'Unable to find CMakeLists.txt in source directory') + # Test devtool add + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool add libftdi %s' % srcdir) + self.assertTrue(os.path.exists(os.path.join(workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn('libftdi', result.output) + self.assertIn(srcdir, result.output) + # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then) + bitbake('libftdi -c cleansstate') + # Test devtool build + result = runCmd('devtool build libftdi') + staging_libdir = get_bb_var('STAGING_LIBDIR', 'libftdi') + self.assertTrue(staging_libdir, 'Could not query STAGING_LIBDIR variable') + self.assertTrue(os.path.isfile(os.path.join(staging_libdir, 'libftdi1.so.2.1.0')), 'libftdi binary not found in STAGING_LIBDIR') + # Test devtool reset + stampprefix = get_bb_var('STAMP', 'libftdi') + result = runCmd('devtool reset libftdi') + result = runCmd('devtool status') + self.assertNotIn('libftdi', result.output) + self.assertTrue(stampprefix, 'Unable to get STAMP value for recipe libftdi') + matches = glob.glob(stampprefix + '*') + self.assertFalse(matches, 'Stamp files exist for recipe libftdi that should have been cleaned') + self.assertFalse(os.path.isfile(os.path.join(staging_libdir, 'libftdi1.so.2.1.0')), 'libftdi binary still found in STAGING_LIBDIR after cleaning') + + def test_devtool_modify(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + # Clean up anything in the workdir/sysroot/sstate cache + bitbake('mdadm -c cleansstate') + # Try modifying a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + self.add_command_to_tearDown('bitbake -c clean mdadm') + result = runCmd('devtool modify mdadm -x %s' % tempdir) + self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found') + self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found') + self.assertTrue(os.path.exists(os.path.join(workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + matches = glob.glob(os.path.join(workspacedir, 'appends', 'mdadm_*.bbappend')) + self.assertTrue(matches, 'bbappend not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn('mdadm', result.output) + self.assertIn(tempdir, result.output) + # Check git repo + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertEqual(result.output.strip(), "", 'Created git repo is not clean') + result = runCmd('git symbolic-ref HEAD', cwd=tempdir) + self.assertEqual(result.output.strip(), "refs/heads/devtool", 'Wrong branch in git repo') + # Try building + bitbake('mdadm') + # Try making (minor) modifications to the source + result = runCmd("sed -i 's!^\.TH.*!.TH MDADM 8 \"\" v9.999-custom!' %s" % os.path.join(tempdir, 'mdadm.8.in')) + bitbake('mdadm -c package') + pkgd = get_bb_var('PKGD', 'mdadm') + self.assertTrue(pkgd, 'Could not query PKGD variable') + mandir = get_bb_var('mandir', 'mdadm') + self.assertTrue(mandir, 'Could not query mandir variable') + if mandir[0] == '/': + mandir = mandir[1:] + with open(os.path.join(pkgd, mandir, 'man8', 'mdadm.8'), 'r') as f: + for line in f: + if line.startswith('.TH'): + self.assertEqual(line.rstrip(), '.TH MDADM 8 "" v9.999-custom', 'man file not modified') + # Test devtool reset + stampprefix = get_bb_var('STAMP', 'mdadm') + result = runCmd('devtool reset mdadm') + result = runCmd('devtool status') + self.assertNotIn('mdadm', result.output) + self.assertTrue(stampprefix, 'Unable to get STAMP value for recipe mdadm') + matches = glob.glob(stampprefix + '*') + self.assertFalse(matches, 'Stamp files exist for recipe mdadm that should have been cleaned') + + def test_devtool_modify_invalid(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + # Try modifying some recipes + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + + testrecipes = 'perf gcc-source kernel-devsrc package-index core-image-minimal meta-toolchain packagegroup-core-sdk meta-ide-support'.split() + for testrecipe in testrecipes: + # Check it's a valid recipe + bitbake('%s -e' % testrecipe) + # devtool extract should fail + result = runCmd('devtool extract %s %s' % (testrecipe, os.path.join(tempdir, testrecipe)), ignore_status=True) + self.assertNotEqual(result.status, 0, 'devtool extract on %s should have failed' % testrecipe) + self.assertNotIn('Fetching ', result.output, 'devtool extract on %s should have errored out before trying to fetch' % testrecipe) + self.assertIn('ERROR: ', result.output, 'devtool extract on %s should have given an ERROR' % testrecipe) + # devtool modify should fail + result = runCmd('devtool modify %s -x %s' % (testrecipe, os.path.join(tempdir, testrecipe)), ignore_status=True) + self.assertNotEqual(result.status, 0, 'devtool modify on %s should have failed' % testrecipe) + self.assertIn('ERROR: ', result.output, 'devtool modify on %s should have given an ERROR' % testrecipe) + + def test_devtool_modify_git(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + testrecipe = 'mkelfimage' + src_uri = get_bb_var('SRC_URI', testrecipe) + self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe) + # Clean up anything in the workdir/sysroot/sstate cache + bitbake('%s -c cleansstate' % testrecipe) + # Try modifying a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) + result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) + self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found') + self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found') + self.assertTrue(os.path.exists(os.path.join(workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + matches = glob.glob(os.path.join(workspacedir, 'appends', 'mkelfimage_*.bbappend')) + self.assertTrue(matches, 'bbappend not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn(testrecipe, result.output) + self.assertIn(tempdir, result.output) + # Check git repo + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertEqual(result.output.strip(), "", 'Created git repo is not clean') + result = runCmd('git symbolic-ref HEAD', cwd=tempdir) + self.assertEqual(result.output.strip(), "refs/heads/devtool", 'Wrong branch in git repo') + # Try building + bitbake(testrecipe) + + def test_devtool_modify_localfiles(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + testrecipe = 'lighttpd' + src_uri = (get_bb_var('SRC_URI', testrecipe) or '').split() + foundlocal = False + for item in src_uri: + if item.startswith('file://') and '.patch' not in item: + foundlocal = True + break + self.assertTrue(foundlocal, 'This test expects the %s recipe to fetch local files and it seems that it no longer does' % testrecipe) + # Clean up anything in the workdir/sysroot/sstate cache + bitbake('%s -c cleansstate' % testrecipe) + # Try modifying a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) + result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) + self.assertTrue(os.path.exists(os.path.join(tempdir, 'configure.ac')), 'Extracted source could not be found') + self.assertTrue(os.path.exists(os.path.join(workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + matches = glob.glob(os.path.join(workspacedir, 'appends', '%s_*.bbappend' % testrecipe)) + self.assertTrue(matches, 'bbappend not created') + # Test devtool status + result = runCmd('devtool status') + self.assertIn(testrecipe, result.output) + self.assertIn(tempdir, result.output) + # Try building + bitbake(testrecipe) + + def test_devtool_update_recipe(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + testrecipe = 'minicom' + recipefile = get_bb_var('FILE', testrecipe) + src_uri = get_bb_var('SRC_URI', testrecipe) + self.assertNotIn('git://', src_uri, 'This test expects the %s recipe to NOT be a git recipe' % testrecipe) + result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile)) + self.assertEqual(result.output.strip(), "", '%s recipe is not clean' % testrecipe) + # First, modify a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + # (don't bother with cleaning the recipe on teardown, we won't be building it) + result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) + # Check git repo + self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found') + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertEqual(result.output.strip(), "", 'Created git repo is not clean') + result = runCmd('git symbolic-ref HEAD', cwd=tempdir) + self.assertEqual(result.output.strip(), "refs/heads/devtool", 'Wrong branch in git repo') + # Add a couple of commits + # FIXME: this only tests adding, need to also test update and remove + result = runCmd('echo "Additional line" >> README', cwd=tempdir) + result = runCmd('git commit -a -m "Change the README"', cwd=tempdir) + result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir) + result = runCmd('git add devtool-new-file', cwd=tempdir) + result = runCmd('git commit -m "Add a new file"', cwd=tempdir) + self.add_command_to_tearDown('cd %s; rm %s/*.patch; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, testrecipe, os.path.basename(recipefile))) + result = runCmd('devtool update-recipe %s' % testrecipe) + result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile)) + self.assertNotEqual(result.output.strip(), "", '%s recipe should be modified' % testrecipe) + status = result.output.splitlines() + self.assertEqual(len(status), 3, 'Less/more files modified than expected. Entire status:\n%s' % result.output) + for line in status: + if line.endswith('0001-Change-the-README.patch'): + self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line) + elif line.endswith('0002-Add-a-new-file.patch'): + self.assertEqual(line[:3], '?? ', 'Unexpected status in line: %s' % line) + elif re.search('%s_[^_]*.bb$' % testrecipe, line): + self.assertEqual(line[:3], ' M ', 'Unexpected status in line: %s' % line) + else: + raise AssertionError('Unexpected modified file in status: %s' % line) + + def test_devtool_update_recipe_git(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + testrecipe = 'mtd-utils' + recipefile = get_bb_var('FILE', testrecipe) + src_uri = get_bb_var('SRC_URI', testrecipe) + self.assertIn('git://', src_uri, 'This test expects the %s recipe to be a git recipe' % testrecipe) + result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile)) + self.assertEqual(result.output.strip(), "", '%s recipe is not clean' % testrecipe) + # First, modify a recipe + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + # (don't bother with cleaning the recipe on teardown, we won't be building it) + result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) + # Check git repo + self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found') + result = runCmd('git status --porcelain', cwd=tempdir) + self.assertEqual(result.output.strip(), "", 'Created git repo is not clean') + result = runCmd('git symbolic-ref HEAD', cwd=tempdir) + self.assertEqual(result.output.strip(), "refs/heads/devtool", 'Wrong branch in git repo') + # Add a couple of commits + # FIXME: this only tests adding, need to also test update and remove + result = runCmd('echo "# Additional line" >> Makefile', cwd=tempdir) + result = runCmd('git commit -a -m "Change the Makefile"', cwd=tempdir) + result = runCmd('echo "A new file" > devtool-new-file', cwd=tempdir) + result = runCmd('git add devtool-new-file', cwd=tempdir) + result = runCmd('git commit -m "Add a new file"', cwd=tempdir) + self.add_command_to_tearDown('cd %s; git checkout %s %s' % (os.path.dirname(recipefile), testrecipe, os.path.basename(recipefile))) + result = runCmd('devtool update-recipe %s' % testrecipe) + result = runCmd('git status . --porcelain', cwd=os.path.dirname(recipefile)) + self.assertNotEqual(result.output.strip(), "", '%s recipe should be modified' % testrecipe) + status = result.output.splitlines() + self.assertEqual(len(status), 3, 'Less/more files modified than expected. Entire status:\n%s' % result.output) + for line in status: + if line.endswith('add-exclusion-to-mkfs-jffs2-git-2.patch'): + self.assertEqual(line[:3], ' D ', 'Unexpected status in line: %s' % line) + elif line.endswith('fix-armv7-neon-alignment.patch'): + self.assertEqual(line[:3], ' D ', 'Unexpected status in line: %s' % line) + elif re.search('%s_[^_]*.bb$' % testrecipe, line): + self.assertEqual(line[:3], ' M ', 'Unexpected status in line: %s' % line) + else: + raise AssertionError('Unexpected modified file in status: %s' % line) + result = runCmd('git diff %s' % os.path.basename(recipefile), cwd=os.path.dirname(recipefile)) + addlines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git"'] + removelines = ['SRCREV = ".*"', 'SRC_URI = "git://git.infradead.org/mtd-utils.git \\\\', 'file://add-exclusion-to-mkfs-jffs2-git-2.patch \\\\', 'file://fix-armv7-neon-alignment.patch \\\\', '"'] + for line in result.output.splitlines(): + if line.startswith('+++') or line.startswith('---'): + continue + elif line.startswith('+'): + matched = False + for item in addlines: + if re.match(item, line[1:].strip()): + matched = True + break + self.assertTrue(matched, 'Unexpected diff add line: %s' % line) + elif line.startswith('-'): + matched = False + for item in removelines: + if re.match(item, line[1:].strip()): + matched = True + break + self.assertTrue(matched, 'Unexpected diff remove line: %s' % line) + + def test_devtool_extract(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + # Try devtool extract + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool extract remake %s' % tempdir) + self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found') + self.assertTrue(os.path.isdir(os.path.join(tempdir, '.git')), 'git repository for external source tree not found') + + def test_devtool_reset_all(self): + # Check preconditions + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + testrecipe1 = 'mdadm' + testrecipe2 = 'cronie' + result = runCmd('devtool modify -x %s %s' % (testrecipe1, os.path.join(tempdir, testrecipe1))) + result = runCmd('devtool modify -x %s %s' % (testrecipe2, os.path.join(tempdir, testrecipe2))) + result = runCmd('devtool build %s' % testrecipe1) + result = runCmd('devtool build %s' % testrecipe2) + stampprefix1 = get_bb_var('STAMP', testrecipe1) + self.assertTrue(stampprefix1, 'Unable to get STAMP value for recipe %s' % testrecipe1) + stampprefix2 = get_bb_var('STAMP', testrecipe2) + self.assertTrue(stampprefix2, 'Unable to get STAMP value for recipe %s' % testrecipe2) + result = runCmd('devtool reset -a') + self.assertIn(testrecipe1, result.output) + self.assertIn(testrecipe2, result.output) + result = runCmd('devtool status') + self.assertNotIn(testrecipe1, result.output) + self.assertNotIn(testrecipe2, result.output) + matches1 = glob.glob(stampprefix1 + '*') + self.assertFalse(matches1, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe1) + matches2 = glob.glob(stampprefix2 + '*') + self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) + + def test_devtool_deploy_target(self): + # NOTE: Whilst this test would seemingly be better placed as a runtime test, + # unfortunately the runtime tests run under bitbake and you can't run + # devtool within bitbake (since devtool needs to run bitbake itself). + # Additionally we are testing build-time functionality as well, so + # really this has to be done as an oe-selftest test. + # + # Check preconditions + machine = get_bb_var('MACHINE') + if not machine.startswith('qemu'): + self.skipTest('This test only works with qemu machines') + if not os.path.exists('/etc/runqemu-nosudo'): + self.skipTest('You must set up tap devices with scripts/runqemu-gen-tapdevs before running this test') + workspacedir = os.path.join(self.builddir, 'workspace') + self.assertTrue(not os.path.exists(workspacedir), 'This test cannot be run with a workspace directory under the build directory') + import pexpect + # Definitions + testrecipe = 'mdadm' + testfile = '/sbin/mdadm' + testimage = 'oe-selftest-image' + testhost = '192.168.7.2' + testcommand = '/sbin/mdadm --help' + # Build an image to run + bitbake("%s qemu-native qemu-helper-native" % testimage) + deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') + self.add_command_to_tearDown('bitbake -c clean %s' % testimage) + self.add_command_to_tearDown('rm -f %s/%s*' % (deploy_dir_image, testimage)) + # Clean recipe so the first deploy will fail + bitbake("%s -c clean" % testrecipe) + # Try devtool modify + tempdir = tempfile.mkdtemp(prefix='devtoolqa') + self.track_for_cleanup(tempdir) + self.track_for_cleanup(workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe) + result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir)) + # Test that deploy-target at this point fails (properly) + result = runCmd('devtool deploy-target -n %s root@%s' % (testrecipe, testhost), ignore_status=True) + self.assertNotEqual(result.output, 0, 'devtool deploy-target should have failed, output: %s' % result.output) + self.assertNotIn(result.output, 'Traceback', 'devtool deploy-target should have failed with a proper error not a traceback, output: %s' % result.output) + result = runCmd('devtool build %s' % testrecipe) + # First try a dry-run of deploy-target + result = runCmd('devtool deploy-target -n %s root@%s' % (testrecipe, testhost)) + self.assertIn(' %s' % testfile, result.output) + # Boot the image + console = pexpect.spawn('runqemu %s %s qemuparams="-snapshot" nographic' % (machine, testimage)) + console.expect("login:", timeout=120) + # Now really test deploy-target + result = runCmd('devtool deploy-target -c %s root@%s' % (testrecipe, testhost)) + result = runCmd('ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@%s %s' % (testhost, testcommand)) + # Test undeploy-target + result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, testhost)) + result = runCmd('ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@%s %s' % (testhost, testcommand), ignore_status=True) + self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have') + console.close() diff --git a/meta/lib/oeqa/selftest/lic-checksum.py b/meta/lib/oeqa/selftest/lic-checksum.py new file mode 100644 index 0000000000..92fc3a84f1 --- /dev/null +++ b/meta/lib/oeqa/selftest/lic-checksum.py @@ -0,0 +1,29 @@ +import os +import tempfile + +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import bitbake +from oeqa.utils import CommandError + +class LicenseTests(oeSelfTest): + + # Verify that changing a license file that has an absolute path causes + # the license qa to fail due to a mismatched md5sum. + def test_nonmatching_checksum(self): + bitbake_cmd = '-c configure emptytest' + error_msg = 'ERROR: emptytest: The new md5 checksum is 8d777f385d3dfec8815d20f7496026dc' + + lic_file, lic_path = tempfile.mkstemp() + os.close(lic_file) + self.track_for_cleanup(lic_path) + + self.write_recipeinc('emptytest', 'INHIBIT_DEFAULT_DEPS = "1"') + self.append_recipeinc('emptytest', 'LIC_FILES_CHKSUM = "file://%s;md5=d41d8cd98f00b204e9800998ecf8427e"' % lic_path) + result = bitbake(bitbake_cmd) + + with open(lic_path, "w") as f: + f.write("data") + + result = bitbake(bitbake_cmd, ignore_status=True) + if error_msg not in result.output: + raise AssertionError(result.output) diff --git a/meta/lib/oeqa/selftest/pkgdata.py b/meta/lib/oeqa/selftest/pkgdata.py new file mode 100644 index 0000000000..34eea468e8 --- /dev/null +++ b/meta/lib/oeqa/selftest/pkgdata.py @@ -0,0 +1,218 @@ +import unittest +import os +import tempfile +import logging +import fnmatch + +import oeqa.utils.ftools as ftools +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd, bitbake, get_bb_var +from oeqa.utils.decorators import testcase + +class OePkgdataUtilTests(oeSelfTest): + + @classmethod + def setUpClass(cls): + # Ensure we have the right data in pkgdata + logger = logging.getLogger("selftest") + logger.info('Running bitbake to generate pkgdata') + bitbake('glibc busybox zlib bash') + + def test_lookup_pkg(self): + # Forward tests + result = runCmd('oe-pkgdata-util lookup-pkg "glibc busybox"') + self.assertEqual(result.output, 'libc6\nbusybox') + result = runCmd('oe-pkgdata-util lookup-pkg zlib-dev') + self.assertEqual(result.output, 'libz-dev') + result = runCmd('oe-pkgdata-util lookup-pkg nonexistentpkg', ignore_status=True) + self.assertEqual(result.status, 1) + self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg') + # Reverse tests + result = runCmd('oe-pkgdata-util lookup-pkg -r "libc6 busybox"') + self.assertEqual(result.output, 'glibc\nbusybox') + result = runCmd('oe-pkgdata-util lookup-pkg -r libz-dev') + self.assertEqual(result.output, 'zlib-dev') + result = runCmd('oe-pkgdata-util lookup-pkg -r nonexistentpkg', ignore_status=True) + self.assertEqual(result.status, 1) + self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg') + + def test_read_value(self): + result = runCmd('oe-pkgdata-util read-value PN libz1') + self.assertEqual(result.output, 'zlib') + result = runCmd('oe-pkgdata-util read-value PKGSIZE bash') + pkgsize = int(result.output.strip()) + self.assertGreater(pkgsize, 1) + + def test_find_path(self): + result = runCmd('oe-pkgdata-util find-path /lib/libc.so.6') + self.assertEqual(result.output, 'glibc: /lib/libc.so.6') + result = runCmd('oe-pkgdata-util find-path /bin/bash') + self.assertEqual(result.output, 'bash: /bin/bash') + result = runCmd('oe-pkgdata-util find-path /not/exist', ignore_status=True) + self.assertEqual(result.status, 1) + self.assertEqual(result.output, 'ERROR: Unable to find any package producing path /not/exist') + + def test_lookup_recipe(self): + result = runCmd('oe-pkgdata-util lookup-recipe "libc6-staticdev busybox"') + self.assertEqual(result.output, 'glibc\nbusybox') + result = runCmd('oe-pkgdata-util lookup-recipe libz-dbg') + self.assertEqual(result.output, 'zlib') + result = runCmd('oe-pkgdata-util lookup-recipe nonexistentpkg', ignore_status=True) + self.assertEqual(result.status, 1) + self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg') + + def test_list_pkgs(self): + # No arguments + result = runCmd('oe-pkgdata-util list-pkgs') + pkglist = result.output.split() + self.assertIn('glibc-utils', pkglist) + self.assertIn('zlib-dev', pkglist) + # No pkgspec, runtime + result = runCmd('oe-pkgdata-util list-pkgs -r') + pkglist = result.output.split() + self.assertIn('libc6-utils', pkglist) + self.assertIn('libz-dev', pkglist) + # With recipe specified + result = runCmd('oe-pkgdata-util list-pkgs -p zlib') + pkglist = sorted(result.output.split()) + try: + pkglist.remove('zlib-ptest') # in case ptest is disabled + except ValueError: + pass + self.assertEqual(pkglist, ['zlib', 'zlib-dbg', 'zlib-dev', 'zlib-doc', 'zlib-staticdev']) + # With recipe specified, runtime + result = runCmd('oe-pkgdata-util list-pkgs -p zlib -r') + pkglist = sorted(result.output.split()) + try: + pkglist.remove('libz-ptest') # in case ptest is disabled + except ValueError: + pass + self.assertEqual(pkglist, ['libz-dbg', 'libz-dev', 'libz-doc', 'libz-staticdev', 'libz1']) + # With recipe specified and unpackaged + result = runCmd('oe-pkgdata-util list-pkgs -p zlib -u') + pkglist = sorted(result.output.split()) + self.assertIn('zlib-locale', pkglist) + # With recipe specified and unpackaged, runtime + result = runCmd('oe-pkgdata-util list-pkgs -p zlib -u -r') + pkglist = sorted(result.output.split()) + self.assertIn('libz-locale', pkglist) + # With recipe specified and pkgspec + result = runCmd('oe-pkgdata-util list-pkgs -p zlib "*-d*"') + pkglist = sorted(result.output.split()) + self.assertEqual(pkglist, ['zlib-dbg', 'zlib-dev', 'zlib-doc']) + # With recipe specified and pkgspec, runtime + result = runCmd('oe-pkgdata-util list-pkgs -p zlib -r "*-d*"') + pkglist = sorted(result.output.split()) + self.assertEqual(pkglist, ['libz-dbg', 'libz-dev', 'libz-doc']) + + def test_list_pkg_files(self): + def splitoutput(output): + files = {} + curpkg = None + for line in output.splitlines(): + if line.startswith('\t'): + self.assertTrue(curpkg, 'Unexpected non-package line:\n%s' % line) + files[curpkg].append(line.strip()) + else: + self.assertTrue(line.rstrip().endswith(':'), 'Invalid package line in output:\n%s' % line) + curpkg = line.split(':')[0] + files[curpkg] = [] + return files + base_libdir = get_bb_var('base_libdir') + libdir = get_bb_var('libdir') + includedir = get_bb_var('includedir') + mandir = get_bb_var('mandir') + # Test recipe-space package name + result = runCmd('oe-pkgdata-util list-pkg-files zlib-dev zlib-doc') + files = splitoutput(result.output) + self.assertIn('zlib-dev', files.keys()) + self.assertIn('zlib-doc', files.keys()) + self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) + self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) + # Test runtime package name + result = runCmd('oe-pkgdata-util list-pkg-files -r libz1 libz-dev') + files = splitoutput(result.output) + self.assertIn('libz1', files.keys()) + self.assertIn('libz-dev', files.keys()) + self.assertGreater(len(files['libz1']), 1) + libspec = os.path.join(base_libdir, 'libz.so.1.*') + found = False + for fileitem in files['libz1']: + if fnmatch.fnmatchcase(fileitem, libspec): + found = True + break + self.assertTrue(found, 'Could not find zlib library file %s in libz1 package file list: %s' % (libspec, files['libz1'])) + self.assertIn(os.path.join(includedir, 'zlib.h'), files['libz-dev']) + # Test recipe + result = runCmd('oe-pkgdata-util list-pkg-files -p zlib') + files = splitoutput(result.output) + self.assertIn('zlib-dbg', files.keys()) + self.assertIn('zlib-doc', files.keys()) + self.assertIn('zlib-dev', files.keys()) + self.assertIn('zlib-staticdev', files.keys()) + self.assertIn('zlib', files.keys()) + self.assertNotIn('zlib-locale', files.keys()) + # (ignore ptest, might not be there depending on config) + self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) + self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) + self.assertIn(os.path.join(libdir, 'libz.a'), files['zlib-staticdev']) + # Test recipe, runtime + result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -r') + files = splitoutput(result.output) + self.assertIn('libz-dbg', files.keys()) + self.assertIn('libz-doc', files.keys()) + self.assertIn('libz-dev', files.keys()) + self.assertIn('libz-staticdev', files.keys()) + self.assertIn('libz1', files.keys()) + self.assertNotIn('libz-locale', files.keys()) + self.assertIn(os.path.join(includedir, 'zlib.h'), files['libz-dev']) + self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc']) + self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev']) + # Test recipe, unpackaged + result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -u') + files = splitoutput(result.output) + self.assertIn('zlib-dbg', files.keys()) + self.assertIn('zlib-doc', files.keys()) + self.assertIn('zlib-dev', files.keys()) + self.assertIn('zlib-staticdev', files.keys()) + self.assertIn('zlib', files.keys()) + self.assertIn('zlib-locale', files.keys()) # this is the key one + self.assertIn(os.path.join(includedir, 'zlib.h'), files['zlib-dev']) + self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['zlib-doc']) + self.assertIn(os.path.join(libdir, 'libz.a'), files['zlib-staticdev']) + # Test recipe, runtime, unpackaged + result = runCmd('oe-pkgdata-util list-pkg-files -p zlib -r -u') + files = splitoutput(result.output) + self.assertIn('libz-dbg', files.keys()) + self.assertIn('libz-doc', files.keys()) + self.assertIn('libz-dev', files.keys()) + self.assertIn('libz-staticdev', files.keys()) + self.assertIn('libz1', files.keys()) + self.assertIn('libz-locale', files.keys()) # this is the key one + self.assertIn(os.path.join(includedir, 'zlib.h'), files['libz-dev']) + self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc']) + self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev']) + + def test_glob(self): + tempdir = tempfile.mkdtemp(prefix='pkgdataqa') + self.track_for_cleanup(tempdir) + pkglistfile = os.path.join(tempdir, 'pkglist') + with open(pkglistfile, 'w') as f: + f.write('libc6\n') + f.write('libz1\n') + f.write('busybox\n') + result = runCmd('oe-pkgdata-util glob %s "*-dev"' % pkglistfile) + desiredresult = ['libc6-dev', 'libz-dev', 'busybox-dev'] + self.assertEqual(sorted(result.output.split()), sorted(desiredresult)) + # The following should not error (because when we use this during rootfs construction, sometimes the complementary package won't exist) + result = runCmd('oe-pkgdata-util glob %s "*-nonexistent"' % pkglistfile) + self.assertEqual(result.output, '') + # Test exclude option + result = runCmd('oe-pkgdata-util glob %s "*-dev *-dbg" -x "^libz"' % pkglistfile) + resultlist = result.output.split() + self.assertNotIn('libz-dev', resultlist) + self.assertNotIn('libz-dbg', resultlist) + + def test_specify_pkgdatadir(self): + result = runCmd('oe-pkgdata-util -p %s lookup-pkg glibc' % get_bb_var('PKGDATA_DIR')) + self.assertEqual(result.output, 'libc6') diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py index cc582dd1ad..1f4770f94e 100644 --- a/meta/lib/oeqa/targetcontrol.py +++ b/meta/lib/oeqa/targetcontrol.py @@ -110,7 +110,7 @@ class BaseTarget(object): class QemuTarget(BaseTarget): - supported_image_fstypes = ['ext3'] + supported_image_fstypes = ['ext3', 'ext4'] def __init__(self, d): diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py index 802bc2f208..e8a467f4cd 100644 --- a/meta/lib/oeqa/utils/commands.py +++ b/meta/lib/oeqa/utils/commands.py @@ -110,11 +110,11 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, **opti def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **options): if postconfig: - postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf') - ftools.write_file(postconfig_file, postconfig) - extra_args = "-R %s" % postconfig_file + postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf') + ftools.write_file(postconfig_file, postconfig) + extra_args = "-R %s" % postconfig_file else: - extra_args = "" + extra_args = "" if isinstance(command, basestring): cmd = "bitbake " + extra_args + " " + command @@ -122,7 +122,7 @@ def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **optio cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]] try: - return runCmd(cmd, ignore_status, timeout, **options) + return runCmd(cmd, ignore_status, timeout, **options) finally: if postconfig: os.remove(postconfig_file) @@ -137,11 +137,18 @@ def get_bb_env(target=None, postconfig=None): def get_bb_var(var, target=None, postconfig=None): val = None bbenv = get_bb_env(target, postconfig=postconfig) + lastline = None for line in bbenv.splitlines(): - if line.startswith(var + "="): + if line.startswith(var + "=") or line.startswith("export " + var + "="): val = line.split('=')[1] - val = val.replace('\"','') + val = val.strip('\"') break + elif line.startswith("unset " + var): + # Handle [unexport] variables + if lastline.startswith('# "'): + val = lastline.split('\"')[1] + break + lastline = line return val def get_test_layer(): diff --git a/meta/lib/oeqa/utils/decorators.py b/meta/lib/oeqa/utils/decorators.py index 7f845dbb4b..ff5f278bc1 100644 --- a/meta/lib/oeqa/utils/decorators.py +++ b/meta/lib/oeqa/utils/decorators.py @@ -18,14 +18,21 @@ class getResults(object): upperf = sys._current_frames().values()[0] while (upperf.f_globals['__name__'] != 'unittest.case'): upperf = upperf.f_back - self.faillist = [ seq[0]._testMethodName for seq in upperf.f_locals['result'].failures ] - self.errorlist = [ seq[0]._testMethodName for seq in upperf.f_locals['result'].errors ] - #ignore the _ErrorHolder objects from the skipped tests list - self.skiplist = [] - for seq in upperf.f_locals['result'].skipped: - try: - self.skiplist.append(seq[0]._testMethodName) - except: pass + + def handleList(items): + ret = [] + # items is a list of tuples, (test, failure) or (_ErrorHandler(), Exception()) + for i in items: + s = i[0].id() + #Handle the _ErrorHolder objects from skipModule failures + if "setUpModule (" in s: + ret.append(s.replace("setUpModule (", "").replace(")","")) + else: + ret.append(s) + return ret + self.faillist = handleList(upperf.f_locals['result'].failures) + self.errorlist = handleList(upperf.f_locals['result'].errors) + self.skiplist = handleList(upperf.f_locals['result'].skipped) def getFailList(self): return self.faillist @@ -86,14 +93,15 @@ class testcase(object): self.test_case = test_case def __call__(self, func): - def wrapped_f(*args): - return func(*args) - wrapped_f.test_case = self.test_case - return wrapped_f + def wrapped_f(*args): + return func(*args) + wrapped_f.test_case = self.test_case + wrapped_f.__name__ = func.__name__ + return wrapped_f class NoParsingFilter(logging.Filter): def filter(self, record): - return record.levelno == 100 + return record.levelno == 100 def LogResults(original_class): orig_method = original_class.run @@ -101,51 +109,51 @@ def LogResults(original_class): #rewrite the run method of unittest.TestCase to add testcase logging def run(self, result, *args, **kws): orig_method(self, result, *args, **kws) - passed = True - testMethod = getattr(self, self._testMethodName) - - #if test case is decorated then use it's number, else use it's name - try: - test_case = testMethod.test_case - except AttributeError: - test_case = self._testMethodName - - #create custom logging level for filtering. - custom_log_level = 100 - logging.addLevelName(custom_log_level, 'RESULTS') - caller = os.path.basename(sys.argv[0]) - - def results(self, message, *args, **kws): - if self.isEnabledFor(custom_log_level): - self.log(custom_log_level, message, *args, **kws) - logging.Logger.results = results - - logging.basicConfig(filename=os.path.join(os.getcwd(),'results-'+caller+'.log'), + passed = True + testMethod = getattr(self, self._testMethodName) + + #if test case is decorated then use it's number, else use it's name + try: + test_case = testMethod.test_case + except AttributeError: + test_case = self._testMethodName + + #create custom logging level for filtering. + custom_log_level = 100 + logging.addLevelName(custom_log_level, 'RESULTS') + caller = os.path.basename(sys.argv[0]) + + def results(self, message, *args, **kws): + if self.isEnabledFor(custom_log_level): + self.log(custom_log_level, message, *args, **kws) + logging.Logger.results = results + + logging.basicConfig(filename=os.path.join(os.getcwd(),'results-'+caller+'.log'), filemode='w', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%H:%M:%S', level=custom_log_level) - for handler in logging.root.handlers: - handler.addFilter(NoParsingFilter()) - local_log = logging.getLogger(caller) + for handler in logging.root.handlers: + handler.addFilter(NoParsingFilter()) + local_log = logging.getLogger(caller) - #check status of tests and record it + #check status of tests and record it for (name, msg) in result.errors: - if self._testMethodName == str(name).split(' ')[0]: - local_log.results("Testcase "+str(test_case)+": ERROR") - local_log.results("Testcase "+str(test_case)+":\n"+msg) - passed = False + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": ERROR") + local_log.results("Testcase "+str(test_case)+":\n"+msg) + passed = False for (name, msg) in result.failures: - if self._testMethodName == str(name).split(' ')[0]: - local_log.results("Testcase "+str(test_case)+": FAILED") - local_log.results("Testcase "+str(test_case)+":\n"+msg) - passed = False + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": FAILED") + local_log.results("Testcase "+str(test_case)+":\n"+msg) + passed = False for (name, msg) in result.skipped: - if self._testMethodName == str(name).split(' ')[0]: - local_log.results("Testcase "+str(test_case)+": SKIPPED") - passed = False - if passed: - local_log.results("Testcase "+str(test_case)+": PASSED") + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": SKIPPED") + passed = False + if passed: + local_log.results("Testcase "+str(test_case)+": PASSED") original_class.run = run return original_class |