diff options
author | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2018-02-26 06:55:05 +0300 |
---|---|---|
committer | Brad Bishop <bradleyb@fuzziesquirrel.com> | 2018-03-15 17:22:49 +0300 |
commit | d7bf8c17eca8f8c89898a7794462c773c449e983 (patch) | |
tree | d18618fca85ca5f0c077032cc7b009344b60f663 /import-layers/yocto-poky/meta/lib | |
parent | e2b5abdc9f28cdf8578e5b9be803c8e697443c20 (diff) | |
download | openbmc-d7bf8c17eca8f8c89898a7794462c773c449e983.tar.xz |
Yocto 2.4
Move OpenBMC to Yocto 2.4(rocko)
Tested: Built and verified Witherspoon and Palmetto images
Change-Id: I12057b18610d6fb0e6903c60213690301e9b0c67
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Diffstat (limited to 'import-layers/yocto-poky/meta/lib')
108 files changed, 3883 insertions, 1597 deletions
diff --git a/import-layers/yocto-poky/meta/lib/bblayers/create.py b/import-layers/yocto-poky/meta/lib/bblayers/create.py new file mode 100644 index 000000000..6a41fe050 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/bblayers/create.py @@ -0,0 +1,66 @@ +import logging +import os +import sys +import shutil + +import bb.utils + +from bblayers.common import LayerPlugin + +logger = logging.getLogger('bitbake-layers') + +def plugin_init(plugins): + return CreatePlugin() + +def read_template(template, template_dir='templates'): + lines = str() + with open(os.path.join(os.path.dirname(__file__), template_dir, template)) as fd: + lines = ''.join(fd.readlines()) + return lines + +class CreatePlugin(LayerPlugin): + def do_create_layer(self, args): + """Create a basic layer""" + layerdir = os.path.abspath(args.layerdir) + if os.path.exists(layerdir): + sys.stderr.write("Specified layer directory exists\n") + return 1 + + # create dirs + conf = os.path.join(layerdir, 'conf') + bb.utils.mkdirhier(conf) + + # Create the README from templates/README + readme_template = read_template('README') % (args.layerdir, args.layerdir, args.layerdir, args.layerdir, args.layerdir, args.layerdir) + readme = os.path.join(layerdir, 'README') + with open(readme, 'w') as fd: + fd.write(readme_template) + + # Copy the MIT license from meta + copying = 'COPYING.MIT' + dn = os.path.dirname + license_src = os.path.join(dn(dn(dn(__file__))), copying) + license_dst = os.path.join(layerdir, copying) + shutil.copy(license_src, license_dst) + + # Create the layer.conf from templates/layer.conf + layerconf_template = read_template('layer.conf') % (args.layerdir, args.layerdir, args.layerdir, args.priority) + layerconf = os.path.join(conf, 'layer.conf') + with open(layerconf, 'w') as fd: + fd.write(layerconf_template) + + # Create the example from templates/example.bb + example_template = read_template('example.bb') + example = os.path.join(layerdir, 'recipes-' + args.examplerecipe, args.examplerecipe) + bb.utils.mkdirhier(example) + with open(os.path.join(example, args.examplerecipe + '.bb'), 'w') as fd: + fd.write(example_template) + + logger.plain('Add your new layer with \'bitbake-layers add-layer %s\'' % args.layerdir) + + def register_commands(self, sp): + parser_create_layer = self.add_command(sp, 'create-layer', self.do_create_layer, parserecipes=False) + parser_create_layer.add_argument('layerdir', help='Layer directory to create') + parser_create_layer.add_argument('--priority', '-p', default=6, help='Layer directory to create') + parser_create_layer.add_argument('--example-recipe-name', '-e', dest='examplerecipe', default='example', help='Filename of the example recipe') + diff --git a/import-layers/yocto-poky/meta/lib/bblayers/templates/README b/import-layers/yocto-poky/meta/lib/bblayers/templates/README new file mode 100644 index 000000000..5a77f8d34 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/bblayers/templates/README @@ -0,0 +1,41 @@ +This README file contains information on the contents of the %s layer. + +Please see the corresponding sections below for details. + +Dependencies +============ + + URI: <first dependency> + branch: <branch name> + + URI: <second dependency> + branch: <branch name> + + . + . + . + +Patches +======= + +Please submit any patches against the %s layer to the xxxx mailing list (xxxx@zzzz.org) +and cc: the maintainer: + +Maintainer: XXX YYYYYY <xxx.yyyyyy@zzzzz.com> + +Table of Contents +================= + + I. Adding the %s layer to your build + II. Misc + + +I. Adding the %s layer to your build +================================================= + +Run 'bitbake-layers add-layer %s' + +II. Misc +======== + +--- replace with specific information about the %s layer --- diff --git a/import-layers/yocto-poky/meta/lib/bblayers/templates/example.bb b/import-layers/yocto-poky/meta/lib/bblayers/templates/example.bb new file mode 100644 index 000000000..c4b873d59 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/bblayers/templates/example.bb @@ -0,0 +1,11 @@ +SUMMARY = "bitbake-layers recipe" +DESCRIPTION = "Recipe created by bitbake-layers" +LICENSE = "MIT" + +python do_build() { + bb.plain("***********************************************"); + bb.plain("* *"); + bb.plain("* Example recipe created by bitbake-layers *"); + bb.plain("* *"); + bb.plain("***********************************************"); +} diff --git a/import-layers/yocto-poky/meta/lib/bblayers/templates/layer.conf b/import-layers/yocto-poky/meta/lib/bblayers/templates/layer.conf new file mode 100644 index 000000000..3c0300226 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/bblayers/templates/layer.conf @@ -0,0 +1,10 @@ +# We have a conf and classes directory, add to BBPATH +BBPATH .= ":${LAYERDIR}" + +# We have recipes-* directories, add to BBFILES +BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \ + ${LAYERDIR}/recipes-*/*/*.bbappend" + +BBFILE_COLLECTIONS += "%s" +BBFILE_PATTERN_%s = "^${LAYERDIR}/" +BBFILE_PRIORITY_%s = "%s" diff --git a/import-layers/yocto-poky/meta/lib/oe/buildhistory_analysis.py b/import-layers/yocto-poky/meta/lib/oe/buildhistory_analysis.py index 3a5b7b6b4..3e86a46a3 100644 --- a/import-layers/yocto-poky/meta/lib/oe/buildhistory_analysis.py +++ b/import-layers/yocto-poky/meta/lib/oe/buildhistory_analysis.py @@ -143,22 +143,25 @@ class ChangeRecord: out += '\n '.join(list(diff)[2:]) out += '\n --' elif self.fieldname in img_monitor_files or '/image-files/' in self.path: - fieldname = self.fieldname - if '/image-files/' in self.path: - fieldname = os.path.join('/' + self.path.split('/image-files/')[1], self.fieldname) - out = 'Changes to %s:\n ' % fieldname - else: - if outer: - prefix = 'Changes to %s ' % self.path - out = '(%s):\n ' % self.fieldname - if self.filechanges: - out += '\n '.join(['%s' % i for i in self.filechanges]) + if self.filechanges or (self.oldvalue and self.newvalue): + fieldname = self.fieldname + if '/image-files/' in self.path: + fieldname = os.path.join('/' + self.path.split('/image-files/')[1], self.fieldname) + out = 'Changes to %s:\n ' % fieldname + else: + if outer: + prefix = 'Changes to %s ' % self.path + out = '(%s):\n ' % self.fieldname + if self.filechanges: + out += '\n '.join(['%s' % i for i in self.filechanges]) + else: + alines = self.oldvalue.splitlines() + blines = self.newvalue.splitlines() + diff = difflib.unified_diff(alines, blines, fieldname, fieldname, lineterm='') + out += '\n '.join(list(diff)) + out += '\n --' else: - alines = self.oldvalue.splitlines() - blines = self.newvalue.splitlines() - diff = difflib.unified_diff(alines, blines, fieldname, fieldname, lineterm='') - out += '\n '.join(list(diff)) - out += '\n --' + out = '' else: out = '%s changed from "%s" to "%s"' % (self.fieldname, self.oldvalue, self.newvalue) @@ -169,7 +172,7 @@ class ChangeRecord: for line in chg._str_internal(False).splitlines(): out += '\n * %s' % line - return '%s%s' % (prefix, out) + return '%s%s' % (prefix, out) if out else '' class FileChange: changetype_add = 'A' @@ -508,7 +511,8 @@ def compare_siglists(a_blob, b_blob, taskdiff=False): return '\n'.join(out) -def process_changes(repopath, revision1, revision2='HEAD', report_all=False, report_ver=False, sigs=False, sigsdiff=False): +def process_changes(repopath, revision1, revision2='HEAD', report_all=False, report_ver=False, + sigs=False, sigsdiff=False, exclude_path=None): repo = git.Repo(repopath) assert repo.bare == False commit = repo.commit(revision1) @@ -601,6 +605,19 @@ def process_changes(repopath, revision1, revision2='HEAD', report_all=False, rep elif chg.path == chg2.path and chg.path.startswith('packages/') and chg2.fieldname in ['PE', 'PV', 'PR']: chg.related.append(chg2) + # filter out unwanted paths + if exclude_path: + for chg in changes: + if chg.filechanges: + fchgs = [] + for fchg in chg.filechanges: + for epath in exclude_path: + if fchg.path.startswith(epath): + break + else: + fchgs.append(fchg) + chg.filechanges = fchgs + if report_all: return changes else: diff --git a/import-layers/yocto-poky/meta/lib/oe/copy_buildsystem.py b/import-layers/yocto-poky/meta/lib/oe/copy_buildsystem.py index a37290418..ac2fae1ed 100644 --- a/import-layers/yocto-poky/meta/lib/oe/copy_buildsystem.py +++ b/import-layers/yocto-poky/meta/lib/oe/copy_buildsystem.py @@ -32,6 +32,10 @@ class BuildSystem(object): corebase = os.path.abspath(self.d.getVar('COREBASE')) layers.append(corebase) + # The bitbake build system uses the meta-skeleton layer as a layout + # for common recipies, e.g: the recipetool script to create kernel recipies + # Add the meta-skeleton layer to be included as part of the eSDK installation + layers.append(os.path.join(corebase, 'meta-skeleton')) # Exclude layers for layer_exclude in self.layers_exclude: @@ -71,6 +75,11 @@ class BuildSystem(object): layerdestpath = destdir if corebase == os.path.dirname(layer): layerdestpath += '/' + os.path.basename(corebase) + else: + layer_relative = os.path.basename(corebase) + '/' + os.path.relpath(layer, corebase) + if os.path.dirname(layer_relative) != layernewname: + layerdestpath += '/' + os.path.dirname(layer_relative) + layerdestpath += '/' + layernewname layer_relative = os.path.relpath(layerdestpath, @@ -123,6 +132,14 @@ class BuildSystem(object): line = line.replace('workspacelayer', workspace_newname) f.write(line) + # meta-skeleton layer is added as part of the build system + # but not as a layer included in the build, therefore it is + # not reported to the function caller. + for layer in layers_copied: + if layer.endswith('/meta-skeleton'): + layers_copied.remove(layer) + break + return layers_copied def generate_locked_sigs(sigfile, d): @@ -239,6 +256,7 @@ def check_sstate_task_list(d, targets, filteroutfile, cmdprefix='', cwd=None, lo cmd = "%sBB_SETSCENE_ENFORCE=1 PSEUDO_DISABLED=1 oe-check-sstate %s -s -o %s %s" % (cmdprefix, targets, filteroutfile, logparam) env = dict(d.getVar('BB_ORIGENV', False)) env.pop('BUILDDIR', '') + env.pop('BBPATH', '') pathitems = env['PATH'].split(':') env['PATH'] = ':'.join([item for item in pathitems if not item.endswith('/bitbake/bin')]) bb.process.run(cmd, stderr=subprocess.STDOUT, env=env, cwd=cwd, executable='/bin/bash') diff --git a/import-layers/yocto-poky/meta/lib/oe/distro_check.py b/import-layers/yocto-poky/meta/lib/oe/distro_check.py index 37f04ed35..e775c3a6e 100644 --- a/import-layers/yocto-poky/meta/lib/oe/distro_check.py +++ b/import-layers/yocto-poky/meta/lib/oe/distro_check.py @@ -77,17 +77,10 @@ def get_latest_released_fedora_source_package_list(d): def get_latest_released_opensuse_source_package_list(d): "Returns list of all the name os packages in the latest opensuse distro" - latest = find_latest_numeric_release("http://download.opensuse.org/source/distribution/",d) + latest = find_latest_numeric_release("http://download.opensuse.org/source/distribution/leap", d) - package_names = get_source_package_list_from_url("http://download.opensuse.org/source/distribution/%s/repo/oss/suse/src/" % latest, "main", d) - package_names |= get_source_package_list_from_url("http://download.opensuse.org/update/%s/src/" % latest, "updates", d) - return latest, package_names - -def get_latest_released_mandriva_source_package_list(d): - "Returns list of all the name os packages in the latest mandriva distro" - latest = find_latest_numeric_release("http://distrib-coffee.ipsl.jussieu.fr/pub/linux/MandrivaLinux/official/", d) - package_names = get_source_package_list_from_url("http://distrib-coffee.ipsl.jussieu.fr/pub/linux/MandrivaLinux/official/%s/SRPMS/main/release/" % latest, "main", d) - package_names |= get_source_package_list_from_url("http://distrib-coffee.ipsl.jussieu.fr/pub/linux/MandrivaLinux/official/%s/SRPMS/main/updates/" % latest, "updates", d) + package_names = get_source_package_list_from_url("http://download.opensuse.org/source/distribution/leap/%s/repo/oss/suse/src/" % latest, "main", d) + package_names |= get_source_package_list_from_url("http://download.opensuse.org/update/leap/%s/oss/src/" % latest, "updates", d) return latest, package_names def get_latest_released_clear_source_package_list(d): @@ -161,8 +154,7 @@ def create_distro_packages_list(distro_check_dir, d): ("Debian", get_latest_released_debian_source_package_list), ("Ubuntu", get_latest_released_ubuntu_source_package_list), ("Fedora", get_latest_released_fedora_source_package_list), - ("OpenSuSE", get_latest_released_opensuse_source_package_list), - ("Mandriva", get_latest_released_mandriva_source_package_list), + ("openSUSE", get_latest_released_opensuse_source_package_list), ("Clear", get_latest_released_clear_source_package_list), ) diff --git a/import-layers/yocto-poky/meta/lib/oe/gpg_sign.py b/import-layers/yocto-poky/meta/lib/oe/gpg_sign.py index 7ce767ee0..9cc88f020 100644 --- a/import-layers/yocto-poky/meta/lib/oe/gpg_sign.py +++ b/import-layers/yocto-poky/meta/lib/oe/gpg_sign.py @@ -15,7 +15,7 @@ class LocalSigner(object): def export_pubkey(self, output_file, keyid, armor=True): """Export GPG public key to a file""" - cmd = '%s --batch --yes --export -o %s ' % \ + cmd = '%s --no-permission-warning --batch --yes --export -o %s ' % \ (self.gpg_bin, output_file) if self.gpg_path: cmd += "--homedir %s " % self.gpg_path @@ -27,22 +27,27 @@ class LocalSigner(object): raise bb.build.FuncFailed('Failed to export gpg public key (%s): %s' % (keyid, output)) - def sign_rpms(self, files, keyid, passphrase): + def sign_rpms(self, files, keyid, passphrase, digest, sign_chunk, fsk=None, fsk_password=None): """Sign RPM files""" cmd = self.rpm_bin + " --addsign --define '_gpg_name %s' " % keyid - gpg_args = '--batch --passphrase=%s' % passphrase + gpg_args = '--no-permission-warning --batch --passphrase=%s' % passphrase if self.gpg_version > (2,1,): gpg_args += ' --pinentry-mode=loopback' cmd += "--define '_gpg_sign_cmd_extra_args %s' " % gpg_args + cmd += "--define '_binary_filedigest_algorithm %s' " % digest if self.gpg_bin: - cmd += "--define '%%__gpg %s' " % self.gpg_bin + cmd += "--define '__gpg %s' " % self.gpg_bin if self.gpg_path: cmd += "--define '_gpg_path %s' " % self.gpg_path - - # Sign in chunks of 100 packages - for i in range(0, len(files), 100): - status, output = oe.utils.getstatusoutput(cmd + ' '.join(files[i:i+100])) + if fsk: + cmd += "--signfiles --fskpath %s " % fsk + if fsk_password: + cmd += "--define '_file_signing_key_password %s' " % fsk_password + + # Sign in chunks + for i in range(0, len(files), sign_chunk): + status, output = oe.utils.getstatusoutput(cmd + ' '.join(files[i:i+sign_chunk])) if status: raise bb.build.FuncFailed("Failed to sign RPM packages: %s" % output) @@ -53,8 +58,8 @@ class LocalSigner(object): if passphrase_file and passphrase: raise Exception("You should use either passphrase_file of passphrase, not both") - cmd = [self.gpg_bin, '--detach-sign', '--batch', '--no-tty', '--yes', - '--passphrase-fd', '0', '-u', keyid] + cmd = [self.gpg_bin, '--detach-sign', '--no-permission-warning', '--batch', + '--no-tty', '--yes', '--passphrase-fd', '0', '-u', keyid] if self.gpg_path: cmd += ['--homedir', self.gpg_path] @@ -93,7 +98,7 @@ class LocalSigner(object): """Return the gpg version as a tuple of ints""" import subprocess try: - ver_str = subprocess.check_output((self.gpg_bin, "--version")).split()[2].decode("utf-8") + ver_str = subprocess.check_output((self.gpg_bin, "--version", "--no-permission-warning")).split()[2].decode("utf-8") return tuple([int(i) for i in ver_str.split('.')]) except subprocess.CalledProcessError as e: raise bb.build.FuncFailed("Could not get gpg version: %s" % e) @@ -101,7 +106,7 @@ class LocalSigner(object): def verify(self, sig_file): """Verify signature""" - cmd = self.gpg_bin + " --verify " + cmd = self.gpg_bin + " --verify --no-permission-warning " if self.gpg_path: cmd += "--homedir %s " % self.gpg_path cmd += sig_file diff --git a/import-layers/yocto-poky/meta/lib/oe/license.py b/import-layers/yocto-poky/meta/lib/oe/license.py index 8d2fd1709..ca385d518 100644 --- a/import-layers/yocto-poky/meta/lib/oe/license.py +++ b/import-layers/yocto-poky/meta/lib/oe/license.py @@ -106,7 +106,8 @@ def is_included(licensestr, whitelist=None, blacklist=None): license string matches the whitelist and does not match the blacklist. Returns a tuple holding the boolean state and a list of the applicable - licenses which were excluded (or None, if the state is True) + licenses that were excluded if state is False, or the licenses that were + included if the state is True. """ def include_license(license): @@ -117,10 +118,17 @@ def is_included(licensestr, whitelist=None, blacklist=None): def choose_licenses(alpha, beta): """Select the option in an OR which is the 'best' (has the most - included licenses).""" - alpha_weight = len(list(filter(include_license, alpha))) - beta_weight = len(list(filter(include_license, beta))) - if alpha_weight > beta_weight: + included licenses and no excluded licenses).""" + # The factor 1000 below is arbitrary, just expected to be much larger + # that the number of licenses actually specified. That way the weight + # will be negative if the list of licenses contains an excluded license, + # but still gives a higher weight to the list with the most included + # licenses. + alpha_weight = (len(list(filter(include_license, alpha))) - + 1000 * (len(list(filter(exclude_license, alpha))) > 0)) + beta_weight = (len(list(filter(include_license, beta))) - + 1000 * (len(list(filter(exclude_license, beta))) > 0)) + if alpha_weight >= beta_weight: return alpha else: return beta diff --git a/import-layers/yocto-poky/meta/lib/oe/lsb.py b/import-layers/yocto-poky/meta/lib/oe/lsb.py index 3a945e0fc..71c0992c5 100644 --- a/import-layers/yocto-poky/meta/lib/oe/lsb.py +++ b/import-layers/yocto-poky/meta/lib/oe/lsb.py @@ -1,19 +1,26 @@ +def get_os_release(): + """Get all key-value pairs from /etc/os-release as a dict""" + from collections import OrderedDict + + data = OrderedDict() + if os.path.exists('/etc/os-release'): + with open('/etc/os-release') as f: + for line in f: + try: + key, val = line.rstrip().split('=', 1) + except ValueError: + continue + data[key.strip()] = val.strip('"') + return data + def release_dict_osr(): """ Populate a dict with pertinent values from /etc/os-release """ - if not os.path.exists('/etc/os-release'): - return None - data = {} - with open('/etc/os-release') as f: - for line in f: - try: - key, val = line.rstrip().split('=', 1) - except ValueError: - continue - if key == 'ID': - data['DISTRIB_ID'] = val.strip('"') - if key == 'VERSION_ID': - data['DISTRIB_RELEASE'] = val.strip('"') + os_release = get_os_release() + if 'ID' in os_release: + data['DISTRIB_ID'] = os_release['ID'] + if 'VERSION_ID' in os_release: + data['DISTRIB_RELEASE'] = os_release['VERSION_ID'] return data diff --git a/import-layers/yocto-poky/meta/lib/oe/package.py b/import-layers/yocto-poky/meta/lib/oe/package.py index 4797e7d65..1e5c3aa8e 100644 --- a/import-layers/yocto-poky/meta/lib/oe/package.py +++ b/import-layers/yocto-poky/meta/lib/oe/package.py @@ -45,6 +45,115 @@ def runstrip(arg): return +def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, qa_already_stripped=False): + """ + Strip executable code (like executables, shared libraries) _in_place_ + - Based on sysroot_strip in staging.bbclass + :param dstdir: directory in which to strip files + :param strip_cmd: Strip command (usually ${STRIP}) + :param libdir: ${libdir} - strip .so files in this directory + :param base_libdir: ${base_libdir} - strip .so files in this directory + :param qa_already_stripped: Set to True if already-stripped' in ${INSANE_SKIP} + This is for proper logging and messages only. + """ + import stat, errno, oe.path, oe.utils, mmap + + # Detect .ko module by searching for "vermagic=" string + def is_kernel_module(path): + with open(path) as f: + return mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ).find(b"vermagic=") >= 0 + + # Return type (bits): + # 0 - not elf + # 1 - ELF + # 2 - stripped + # 4 - executable + # 8 - shared library + # 16 - kernel module + def is_elf(path): + exec_type = 0 + ret, result = oe.utils.getstatusoutput( + "file \"%s\"" % path.replace("\"", "\\\"")) + + if ret: + bb.error("split_and_strip_files: 'file %s' failed" % path) + return exec_type + + if "ELF" in result: + exec_type |= 1 + if "not stripped" not in result: + exec_type |= 2 + if "executable" in result: + exec_type |= 4 + if "shared" in result: + exec_type |= 8 + if "relocatable" in result and is_kernel_module(path): + exec_type |= 16 + return exec_type + + elffiles = {} + inodes = {} + libdir = os.path.abspath(dstdir + os.sep + libdir) + base_libdir = os.path.abspath(dstdir + os.sep + base_libdir) + exec_mask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + # + # First lets figure out all of the files we may have to process + # + for root, dirs, files in os.walk(dstdir): + for f in files: + file = os.path.join(root, f) + + try: + ltarget = oe.path.realpath(file, dstdir, False) + s = os.lstat(ltarget) + except OSError as e: + (err, strerror) = e.args + if err != errno.ENOENT: + raise + # Skip broken symlinks + continue + if not s: + continue + # Check its an excutable + if s[stat.ST_MODE] & exec_mask \ + or ((file.startswith(libdir) or file.startswith(base_libdir)) and ".so" in f) \ + or file.endswith('.ko'): + # If it's a symlink, and points to an ELF file, we capture the readlink target + if os.path.islink(file): + continue + + # It's a file (or hardlink), not a link + # ...but is it ELF, and is it already stripped? + elf_file = is_elf(file) + if elf_file & 1: + if elf_file & 2: + if qa_already_stripped: + bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dstdir):], pn)) + else: + bb.warn("File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dstdir):], pn)) + continue + + if s.st_ino in inodes: + os.unlink(file) + os.link(inodes[s.st_ino], file) + else: + # break hardlinks so that we do not strip the original. + inodes[s.st_ino] = file + bb.utils.copyfile(file, file) + elffiles[file] = elf_file + + # + # Now strip them (in parallel) + # + sfiles = [] + for file in elffiles: + elf_file = int(elffiles[file]) + sfiles.append((file, elf_file, strip_cmd)) + + oe.utils.multiprocess_exec(sfiles, runstrip) + + + def file_translate(file): ft = file.replace("@", "@at@") ft = ft.replace(" ", "@space@") @@ -67,8 +176,7 @@ def filedeprunner(arg): def process_deps(pipe, pkg, pkgdest, provides, requires): file = None - for line in pipe: - line = line.decode("utf-8") + for line in pipe.split("\n"): m = file_re.match(line) if m: @@ -117,12 +225,8 @@ def filedeprunner(arg): return provides, requires - try: - dep_popen = subprocess.Popen(shlex.split(rpmdeps) + pkgfiles, stdout=subprocess.PIPE) - provides, requires = process_deps(dep_popen.stdout, pkg, pkgdest, provides, requires) - except OSError as e: - bb.error("rpmdeps: '%s' command failed, '%s'" % (shlex.split(rpmdeps) + pkgfiles, e)) - raise e + output = subprocess.check_output(shlex.split(rpmdeps) + pkgfiles, stderr=subprocess.STDOUT).decode("utf-8") + provides, requires = process_deps(output, pkg, pkgdest, provides, requires) return (pkg, provides, requires) diff --git a/import-layers/yocto-poky/meta/lib/oe/package_manager.py b/import-layers/yocto-poky/meta/lib/oe/package_manager.py index 3a2daadaf..0c5d907ff 100644 --- a/import-layers/yocto-poky/meta/lib/oe/package_manager.py +++ b/import-layers/yocto-poky/meta/lib/oe/package_manager.py @@ -17,18 +17,11 @@ from oe.gpg_sign import get_signer def create_index(arg): index_cmd = arg - try: - bb.note("Executing '%s' ..." % index_cmd) - result = subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8") - except subprocess.CalledProcessError as e: - return("Index creation command '%s' failed with return code %d:\n%s" % - (e.cmd, e.returncode, e.output.decode("utf-8"))) - + bb.note("Executing '%s' ..." % index_cmd) + result = subprocess.check_output(index_cmd, stderr=subprocess.STDOUT, shell=True).decode("utf-8") if result: bb.note(result) - return None - """ This method parse the output from the package managerand return a dictionary with the information of the packages. This is used @@ -104,13 +97,25 @@ class Indexer(object, metaclass=ABCMeta): class RpmIndexer(Indexer): def write_index(self): if self.d.getVar('PACKAGE_FEED_SIGN') == '1': - raise NotImplementedError('Package feed signing not yet implementd for rpm') + signer = get_signer(self.d, self.d.getVar('PACKAGE_FEED_GPG_BACKEND')) + else: + signer = None createrepo_c = bb.utils.which(os.environ['PATH'], "createrepo_c") result = create_index("%s --update -q %s" % (createrepo_c, self.deploy_dir)) if result: bb.fatal(result) + # Sign repomd + if signer: + sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE') + is_ascii_sig = (sig_type.upper() != "BIN") + signer.detach_sign(os.path.join(self.deploy_dir, 'repodata', 'repomd.xml'), + self.d.getVar('PACKAGE_FEED_GPG_NAME'), + self.d.getVar('PACKAGE_FEED_GPG_PASSPHRASE_FILE'), + armor=is_ascii_sig) + + class OpkgIndexer(Indexer): def write_index(self): arch_vars = ["ALL_MULTILIB_PACKAGE_ARCHS", @@ -152,9 +157,7 @@ class OpkgIndexer(Indexer): bb.note("There are no packages in %s!" % self.deploy_dir) return - result = oe.utils.multiprocess_exec(index_cmds, create_index) - if result: - bb.fatal('%s' % ('\n'.join(result))) + oe.utils.multiprocess_exec(index_cmds, create_index) if signer: feed_sig_type = self.d.getVar('PACKAGE_FEED_GPG_SIGNATURE_TYPE') @@ -220,7 +223,7 @@ class DpkgIndexer(Indexer): cmd = "cd %s; PSEUDO_UNLOAD=1 %s packages . > Packages;" % (arch_dir, apt_ftparchive) - cmd += "%s -fc Packages > Packages.gz;" % gzip + cmd += "%s -fcn Packages > Packages.gz;" % gzip with open(os.path.join(arch_dir, "Release"), "w+") as release: release.write("Label: %s\n" % arch) @@ -235,9 +238,7 @@ class DpkgIndexer(Indexer): bb.note("There are no packages in %s" % self.deploy_dir) return - result = oe.utils.multiprocess_exec(index_cmds, create_index) - if result: - bb.fatal('%s' % ('\n'.join(result))) + oe.utils.multiprocess_exec(index_cmds, create_index) if self.d.getVar('PACKAGE_FEED_SIGN') == '1': raise NotImplementedError('Package feed signing not implementd for dpkg') @@ -548,6 +549,14 @@ class RpmPM(PackageManager): if feed_uris == "": return + gpg_opts = '' + if self.d.getVar('PACKAGE_FEED_SIGN') == '1': + gpg_opts += 'repo_gpgcheck=1\n' + gpg_opts += 'gpgkey=file://%s/pki/packagefeed-gpg/PACKAGEFEED-GPG-KEY-%s-%s\n' % (self.d.getVar('sysconfdir'), self.d.getVar('DISTRO'), self.d.getVar('DISTRO_CODENAME')) + + if self.d.getVar('RPM_SIGN_PACKAGES') == '0': + gpg_opts += 'gpgcheck=0\n' + bb.utils.mkdirhier(oe.path.join(self.target_rootfs, "etc", "yum.repos.d")) remote_uris = self.construct_uris(feed_uris.split(), feed_base_paths.split()) for uri in remote_uris: @@ -558,12 +567,12 @@ class RpmPM(PackageManager): repo_id = "oe-remote-repo" + "-".join(urlparse(repo_uri).path.split("/")) repo_name = "OE Remote Repo:" + " ".join(urlparse(repo_uri).path.split("/")) open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'a').write( - "[%s]\nname=%s\nbaseurl=%s\n\n" % (repo_id, repo_name, repo_uri)) + "[%s]\nname=%s\nbaseurl=%s\n%s\n" % (repo_id, repo_name, repo_uri, gpg_opts)) else: repo_name = "OE Remote Repo:" + " ".join(urlparse(uri).path.split("/")) repo_uri = uri open(oe.path.join(self.target_rootfs, "etc", "yum.repos.d", repo_base + ".repo"), 'w').write( - "[%s]\nname=%s\nbaseurl=%s\n" % (repo_base, repo_name, repo_uri)) + "[%s]\nname=%s\nbaseurl=%s\n%s" % (repo_base, repo_name, repo_uri, gpg_opts)) def _prepare_pkg_transaction(self): os.environ['D'] = self.target_rootfs @@ -608,10 +617,12 @@ class RpmPM(PackageManager): self._invoke_dnf(["remove"] + pkgs) else: cmd = bb.utils.which(os.getenv('PATH'), "rpm") - args = ["-e", "--nodeps", "--root=%s" %self.target_rootfs] + args = ["-e", "-v", "--nodeps", "--root=%s" %self.target_rootfs] try: + bb.note("Running %s" % ' '.join([cmd] + args + pkgs)) output = subprocess.check_output([cmd] + args + pkgs, stderr=subprocess.STDOUT).decode("utf-8") + bb.note(output) except subprocess.CalledProcessError as e: bb.fatal("Could not invoke rpm. Command " "'%s' returned %d:\n%s" % (' '.join([cmd] + args + pkgs), e.returncode, e.output.decode("utf-8"))) @@ -682,7 +693,7 @@ class RpmPM(PackageManager): return packages def update(self): - self._invoke_dnf(["makecache"]) + self._invoke_dnf(["makecache", "--refresh"]) def _invoke_dnf(self, dnf_args, fatal = True, print_output = True ): os.environ['RPM_ETCCONFIGDIR'] = self.target_rootfs @@ -1145,7 +1156,7 @@ class OpkgPM(OpkgDpkgPM): # Create an temp dir as opkg root for dummy installation temp_rootfs = self.d.expand('${T}/opkg') - opkg_lib_dir = self.d.getVar('OPKGLIBDIR', True) + opkg_lib_dir = self.d.getVar('OPKGLIBDIR') if opkg_lib_dir[0] == "/": opkg_lib_dir = opkg_lib_dir[1:] temp_opkg_dir = os.path.join(temp_rootfs, opkg_lib_dir, 'opkg') diff --git a/import-layers/yocto-poky/meta/lib/oe/patch.py b/import-layers/yocto-poky/meta/lib/oe/patch.py index f1ab3dd80..584bf6c05 100644 --- a/import-layers/yocto-poky/meta/lib/oe/patch.py +++ b/import-layers/yocto-poky/meta/lib/oe/patch.py @@ -1,4 +1,5 @@ import oe.path +import oe.types class NotFoundError(bb.BBHandledException): def __init__(self, path): diff --git a/import-layers/yocto-poky/meta/lib/oe/path.py b/import-layers/yocto-poky/meta/lib/oe/path.py index 448a2b944..1ea03d5d5 100644 --- a/import-layers/yocto-poky/meta/lib/oe/path.py +++ b/import-layers/yocto-poky/meta/lib/oe/path.py @@ -98,7 +98,7 @@ def copyhardlinktree(src, dst): if (os.stat(src).st_dev == os.stat(dst).st_dev): # Need to copy directories only with tar first since cp will error if two # writers try and create a directory at the same time - cmd = "cd %s; find . -type d -print | tar --xattrs --xattrs-include='*' -cf - -C %s -p --no-recursion --files-from - | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, src, dst) + cmd = "cd %s; find . -type d -print | tar --xattrs --xattrs-include='*' -cf - -C %s -p --no-recursion --files-from - | tar --xattrs --xattrs-include='*' -xhf - -C %s" % (src, src, dst) subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) source = '' if os.path.isdir(src): diff --git a/import-layers/yocto-poky/meta/lib/oe/recipeutils.py b/import-layers/yocto-poky/meta/lib/oe/recipeutils.py index a7fdd36e4..cab8e4015 100644 --- a/import-layers/yocto-poky/meta/lib/oe/recipeutils.py +++ b/import-layers/yocto-poky/meta/lib/oe/recipeutils.py @@ -2,7 +2,7 @@ # # Some code borrowed from the OE layer index # -# Copyright (C) 2013-2016 Intel Corporation +# Copyright (C) 2013-2017 Intel Corporation # import sys @@ -188,6 +188,11 @@ def patch_recipe_lines(fromlines, values, trailing_newline=True): for wrapline in wrapped[:-1]: addlines.append('%s \\%s' % (wrapline, newline)) addlines.append('%s%s' % (wrapped[-1], newline)) + + # Split on newlines - this isn't strictly necessary if you are only + # going to write the output to disk, but if you want to compare it + # (as patch_recipe_file() will do if patch=True) then it's important. + addlines = [line for l in addlines for line in l.splitlines(True)] if rewindcomments: # Ensure we insert the lines before any leading comments # (that we'd want to ensure remain leading the next value) @@ -320,7 +325,7 @@ def patch_recipe(d, fn, varvalues, patch=False, relpath=''): -def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): +def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True, all_variants=False): """Copy (local) recipe files, including both files included via include/require, and files referred to in the SRC_URI variable.""" import bb.fetch2 @@ -328,18 +333,41 @@ def copy_recipe_files(d, tgt_dir, whole_dir=False, download=True): # FIXME need a warning if the unexpanded SRC_URI value contains variable references - uris = (d.getVar('SRC_URI') or "").split() - fetch = bb.fetch2.Fetch(uris, d) - if download: - fetch.download() + uri_values = [] + localpaths = [] + def fetch_urls(rdata): + # Collect the local paths from SRC_URI + srcuri = rdata.getVar('SRC_URI') or "" + if srcuri not in uri_values: + fetch = bb.fetch2.Fetch(srcuri.split(), rdata) + if download: + fetch.download() + for pth in fetch.localpaths(): + if pth not in localpaths: + localpaths.append(pth) + uri_values.append(srcuri) + + fetch_urls(d) + if all_variants: + # Get files for other variants e.g. in the case of a SRC_URI_append + localdata = bb.data.createCopy(d) + variants = (localdata.getVar('BBCLASSEXTEND') or '').split() + if variants: + # Ensure we handle class-target if we're dealing with one of the variants + variants.append('target') + for variant in variants: + localdata.setVar('CLASSOVERRIDE', 'class-%s' % variant) + fetch_urls(localdata) # Copy local files to target directory and gather any remote files - bb_dir = os.path.dirname(d.getVar('FILE')) + os.sep + bb_dir = os.path.abspath(os.path.dirname(d.getVar('FILE'))) + os.sep remotes = [] copied = [] - includes = [path for path in d.getVar('BBINCLUDED').split() if - path.startswith(bb_dir) and os.path.exists(path)] - for path in fetch.localpaths() + includes: + # Need to do this in two steps since we want to check against the absolute path + includes = [os.path.abspath(path) for path in d.getVar('BBINCLUDED').split() if os.path.exists(path)] + # We also check this below, but we don't want any items in this list being considered remotes + includes = [path for path in includes if path.startswith(bb_dir)] + for path in localpaths + includes: # Only import files that are under the meta directory if path.startswith(bb_dir): if not whole_dir: @@ -778,7 +806,7 @@ def bbappend_recipe(rd, destlayerdir, srcfiles, install=None, wildcardver=False, def find_layerdir(fn): """ Figure out the path to the base of the layer containing a file (e.g. a recipe)""" - pth = fn + pth = os.path.abspath(fn) layerdir = '' while pth: if os.path.exists(os.path.join(pth, 'conf', 'layer.conf')): diff --git a/import-layers/yocto-poky/meta/lib/oe/rootfs.py b/import-layers/yocto-poky/meta/lib/oe/rootfs.py index 96591f370..754ef563a 100644 --- a/import-layers/yocto-poky/meta/lib/oe/rootfs.py +++ b/import-layers/yocto-poky/meta/lib/oe/rootfs.py @@ -261,15 +261,22 @@ class Rootfs(object, metaclass=ABCMeta): # Remove components that we don't need if it's a read-only rootfs unneeded_pkgs = self.d.getVar("ROOTFS_RO_UNNEEDED").split() pkgs_installed = image_list_installed_packages(self.d) - # Make sure update-alternatives is last on the command line, so - # that it is removed last. This makes sure that its database is - # available while uninstalling packages, allowing alternative - # symlinks of packages to be uninstalled to be managed correctly. + # Make sure update-alternatives is removed last. This is + # because its database has to available while uninstalling + # other packages, allowing alternative symlinks of packages + # to be uninstalled or to be managed correctly otherwise. provider = self.d.getVar("VIRTUAL-RUNTIME_update-alternatives") pkgs_to_remove = sorted([pkg for pkg in pkgs_installed if pkg in unneeded_pkgs], key=lambda x: x == provider) + # update-alternatives provider is removed in its own remove() + # call because all package managers do not guarantee the packages + # are removed in the order they given in the list (which is + # passed to the command line). The sorting done earlier is + # utilized to implement the 2-stage removal. + if len(pkgs_to_remove) > 1: + self.pm.remove(pkgs_to_remove[:-1], False) if len(pkgs_to_remove) > 0: - self.pm.remove(pkgs_to_remove, False) + self.pm.remove([pkgs_to_remove[-1]], False) if delayed_postinsts: self._save_postinsts() @@ -302,10 +309,11 @@ class Rootfs(object, metaclass=ABCMeta): bb.note("> Executing %s intercept ..." % script) try: - subprocess.check_output(script_full) + output = subprocess.check_output(script_full, stderr=subprocess.STDOUT) + if output: bb.note(output.decode("utf-8")) except subprocess.CalledProcessError as e: - bb.warn("The postinstall intercept hook '%s' failed (exit code: %d)! See log for details! (Output: %s)" % - (script, e.returncode, e.output)) + bb.warn("The postinstall intercept hook '%s' failed, details in log.do_rootfs" % script) + bb.note("Exit code %d. Output:\n%s" % (e.returncode, e.output.decode("utf-8"))) with open(script_full) as intercept: registered_pkgs = None @@ -524,7 +532,8 @@ class RpmRootfs(Rootfs): self.pm.save_rpmpostinst(pkg) def _cleanup(self): - pass + self.pm._invoke_dnf(["clean", "all"]) + class DpkgOpkgRootfs(Rootfs): def __init__(self, d, progress_reporter=None, logcatcher=None): diff --git a/import-layers/yocto-poky/meta/lib/oe/sdk.py b/import-layers/yocto-poky/meta/lib/oe/sdk.py index 9fe1687ac..a3a6c3917 100644 --- a/import-layers/yocto-poky/meta/lib/oe/sdk.py +++ b/import-layers/yocto-poky/meta/lib/oe/sdk.py @@ -152,6 +152,8 @@ class RpmSdk(Sdk): pm.install(pkgs_attempt, True) def _populate(self): + execute_pre_post_process(self.d, self.d.getVar("POPULATE_SDK_PRE_TARGET_COMMAND")) + bb.note("Installing TARGET packages") self._populate_sysroot(self.target_pm, self.target_manifest) @@ -233,6 +235,8 @@ class OpkgSdk(Sdk): [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY]) def _populate(self): + execute_pre_post_process(self.d, self.d.getVar("POPULATE_SDK_PRE_TARGET_COMMAND")) + bb.note("Installing TARGET packages") self._populate_sysroot(self.target_pm, self.target_manifest) @@ -315,6 +319,8 @@ class DpkgSdk(Sdk): [False, True][pkg_type == Manifest.PKG_TYPE_ATTEMPT_ONLY]) def _populate(self): + execute_pre_post_process(self.d, self.d.getVar("POPULATE_SDK_PRE_TARGET_COMMAND")) + bb.note("Installing TARGET packages") self._populate_sysroot(self.target_pm, self.target_manifest) @@ -379,5 +385,24 @@ def populate_sdk(d, manifest_dir=None): os.environ.clear() os.environ.update(env_bkp) +def get_extra_sdkinfo(sstate_dir): + """ + This function is going to be used for generating the target and host manifest files packages of eSDK. + """ + import math + + extra_info = {} + extra_info['tasksizes'] = {} + extra_info['filesizes'] = {} + for root, _, files in os.walk(sstate_dir): + for fn in files: + if fn.endswith('.tgz'): + fsize = int(math.ceil(float(os.path.getsize(os.path.join(root, fn))) / 1024)) + task = fn.rsplit(':',1)[1].split('_',1)[1].split(',')[0] + origtotal = extra_info['tasksizes'].get(task, 0) + extra_info['tasksizes'][task] = origtotal + fsize + extra_info['filesizes'][fn] = fsize + return extra_info + if __name__ == "__main__": pass diff --git a/import-layers/yocto-poky/meta/lib/oe/sstatesig.py b/import-layers/yocto-poky/meta/lib/oe/sstatesig.py index b8dd4c869..3a8778eae 100644 --- a/import-layers/yocto-poky/meta/lib/oe/sstatesig.py +++ b/import-layers/yocto-poky/meta/lib/oe/sstatesig.py @@ -29,7 +29,7 @@ def sstate_rundepfilter(siggen, fn, recipename, task, dep, depname, dataCache): return True # Quilt (patch application) changing isn't likely to affect anything - excludelist = ['quilt-native', 'subversion-native', 'git-native'] + excludelist = ['quilt-native', 'subversion-native', 'git-native', 'ccache-native'] if depname in excludelist and recipename != depname: return False @@ -320,7 +320,7 @@ def find_siginfo(pn, taskname, taskhashlist, d): if not taskhashlist or (len(filedates) < 2 and not foundall): # That didn't work, look in sstate-cache - hashes = taskhashlist or ['*'] + hashes = taskhashlist or ['?' * 32] localdata = bb.data.createCopy(d) for hashval in hashes: localdata.setVar('PACKAGE_ARCH', '*') diff --git a/import-layers/yocto-poky/meta/lib/oe/terminal.py b/import-layers/yocto-poky/meta/lib/oe/terminal.py index 2f18ec028..94afe394e 100644 --- a/import-layers/yocto-poky/meta/lib/oe/terminal.py +++ b/import-layers/yocto-poky/meta/lib/oe/terminal.py @@ -292,6 +292,8 @@ def check_terminal_version(terminalName): vernum = ver.split(' ')[-1] if ver.startswith('GNOME Terminal'): vernum = ver.split(' ')[-1] + if ver.startswith('MATE Terminal'): + vernum = ver.split(' ')[-1] if ver.startswith('tmux'): vernum = ver.split()[-1] return vernum diff --git a/import-layers/yocto-poky/meta/lib/oe/useradd.py b/import-layers/yocto-poky/meta/lib/oe/useradd.py new file mode 100644 index 000000000..179ac76b5 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oe/useradd.py @@ -0,0 +1,68 @@ +import argparse +import re + +class myArgumentParser(argparse.ArgumentParser): + def _print_message(self, message, file=None): + bb.warn("%s - %s: %s" % (d.getVar('PN'), pkg, message)) + + # This should never be called... + def exit(self, status=0, message=None): + message = message or ("%s - %s: useradd.bbclass: Argument parsing exited" % (d.getVar('PN'), pkg)) + error(message) + + def error(self, message): + raise bb.build.FuncFailed(message) + +def split_commands(params): + params = re.split('''[ \t]*;[ \t]*(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', params.strip()) + # Remove any empty items + return [x for x in params if x] + +def split_args(params): + params = re.split('''[ \t]+(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', params.strip()) + # Remove any empty items + return [x for x in params if x] + +def build_useradd_parser(): + # The following comes from --help on useradd from shadow + parser = myArgumentParser(prog='useradd') + parser.add_argument("-b", "--base-dir", metavar="BASE_DIR", help="base directory for the home directory of the new account") + parser.add_argument("-c", "--comment", metavar="COMMENT", help="GECOS field of the new account") + parser.add_argument("-d", "--home-dir", metavar="HOME_DIR", help="home directory of the new account") + parser.add_argument("-D", "--defaults", help="print or change default useradd configuration", action="store_true") + parser.add_argument("-e", "--expiredate", metavar="EXPIRE_DATE", help="expiration date of the new account") + parser.add_argument("-f", "--inactive", metavar="INACTIVE", help="password inactivity period of the new account") + parser.add_argument("-g", "--gid", metavar="GROUP", help="name or ID of the primary group of the new account") + parser.add_argument("-G", "--groups", metavar="GROUPS", help="list of supplementary groups of the new account") + parser.add_argument("-k", "--skel", metavar="SKEL_DIR", help="use this alternative skeleton directory") + parser.add_argument("-K", "--key", metavar="KEY=VALUE", help="override /etc/login.defs defaults") + parser.add_argument("-l", "--no-log-init", help="do not add the user to the lastlog and faillog databases", action="store_true") + parser.add_argument("-m", "--create-home", help="create the user's home directory", action="store_const", const=True) + parser.add_argument("-M", "--no-create-home", dest="create_home", help="do not create the user's home directory", action="store_const", const=False) + parser.add_argument("-N", "--no-user-group", dest="user_group", help="do not create a group with the same name as the user", action="store_const", const=False) + parser.add_argument("-o", "--non-unique", help="allow to create users with duplicate (non-unique UID)", action="store_true") + parser.add_argument("-p", "--password", metavar="PASSWORD", help="encrypted password of the new account") + parser.add_argument("-P", "--clear-password", metavar="CLEAR_PASSWORD", help="use this clear password for the new account") + parser.add_argument("-R", "--root", metavar="CHROOT_DIR", help="directory to chroot into") + parser.add_argument("-r", "--system", help="create a system account", action="store_true") + parser.add_argument("-s", "--shell", metavar="SHELL", help="login shell of the new account") + parser.add_argument("-u", "--uid", metavar="UID", help="user ID of the new account") + parser.add_argument("-U", "--user-group", help="create a group with the same name as the user", action="store_const", const=True) + parser.add_argument("LOGIN", help="Login name of the new user") + + return parser + +def build_groupadd_parser(): + # The following comes from --help on groupadd from shadow + parser = myArgumentParser(prog='groupadd') + parser.add_argument("-f", "--force", help="exit successfully if the group already exists, and cancel -g if the GID is already used", action="store_true") + parser.add_argument("-g", "--gid", metavar="GID", help="use GID for the new group") + parser.add_argument("-K", "--key", metavar="KEY=VALUE", help="override /etc/login.defs defaults") + parser.add_argument("-o", "--non-unique", help="allow to create groups with duplicate (non-unique) GID", action="store_true") + parser.add_argument("-p", "--password", metavar="PASSWORD", help="use this encrypted password for the new group") + parser.add_argument("-P", "--clear-password", metavar="CLEAR_PASSWORD", help="use this clear password for the new group") + parser.add_argument("-R", "--root", metavar="CHROOT_DIR", help="directory to chroot into") + parser.add_argument("-r", "--system", help="create a system account", action="store_true") + parser.add_argument("GROUP", help="Group name of the new group") + + return parser diff --git a/import-layers/yocto-poky/meta/lib/oe/utils.py b/import-layers/yocto-poky/meta/lib/oe/utils.py index 330a5ff94..643ab78df 100644 --- a/import-layers/yocto-poky/meta/lib/oe/utils.py +++ b/import-layers/yocto-poky/meta/lib/oe/utils.py @@ -126,6 +126,46 @@ def features_backfill(var,d): if addfeatures: d.appendVar(var, " " + " ".join(addfeatures)) +def all_distro_features(d, features, truevalue="1", falsevalue=""): + """ + Returns truevalue if *all* given features are set in DISTRO_FEATURES, + else falsevalue. The features can be given as single string or anything + that can be turned into a set. + + This is a shorter, more flexible version of + bb.utils.contains("DISTRO_FEATURES", features, truevalue, falsevalue, d). + + Without explicit true/false values it can be used directly where + Python expects a boolean: + if oe.utils.all_distro_features(d, "foo bar"): + bb.fatal("foo and bar are mutually exclusive DISTRO_FEATURES") + + With just a truevalue, it can be used to include files that are meant to be + used only when requested via DISTRO_FEATURES: + require ${@ oe.utils.all_distro_features(d, "foo bar", "foo-and-bar.inc") + """ + return bb.utils.contains("DISTRO_FEATURES", features, truevalue, falsevalue, d) + +def any_distro_features(d, features, truevalue="1", falsevalue=""): + """ + Returns truevalue if at least *one* of the given features is set in DISTRO_FEATURES, + else falsevalue. The features can be given as single string or anything + that can be turned into a set. + + This is a shorter, more flexible version of + bb.utils.contains_any("DISTRO_FEATURES", features, truevalue, falsevalue, d). + + Without explicit true/false values it can be used directly where + Python expects a boolean: + if not oe.utils.any_distro_features(d, "foo bar"): + bb.fatal("foo, bar or both must be set in DISTRO_FEATURES") + + With just a truevalue, it can be used to include files that are meant to be + used only when requested via DISTRO_FEATURES: + require ${@ oe.utils.any_distro_features(d, "foo bar", "foo-or-bar.inc") + + """ + return bb.utils.contains_any("DISTRO_FEATURES", features, truevalue, falsevalue, d) def packages_filter_out_system(d): """ @@ -184,25 +224,30 @@ def multiprocess_exec(commands, function): def init_worker(): signal.signal(signal.SIGINT, signal.SIG_IGN) + fails = [] + + def failures(res): + fails.append(res) + nproc = min(multiprocessing.cpu_count(), len(commands)) pool = bb.utils.multiprocessingpool(nproc, init_worker) - imap = pool.imap(function, commands) try: - res = list(imap) + mapresult = pool.map_async(function, commands, error_callback=failures) + pool.close() pool.join() - results = [] - for result in res: - if result is not None: - results.append(result) - return results - + results = mapresult.get() except KeyboardInterrupt: pool.terminate() pool.join() raise + if fails: + raise fails[0] + + return results + def squashspaces(string): import re return re.sub("\s+", " ", string).strip() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py index 6e62b279c..7b2b4aa2a 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/base.py @@ -485,6 +485,7 @@ class BuildPerfTestCase(unittest.TestCase): @staticmethod def sync(): """Sync and drop kernel caches""" + runCmd2('bitbake -m', ignore_status=True) log.debug("Syncing and dropping kernel caches""") KernelDropCaches.drop() os.sync() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py index a9e4a5b73..a19089a6e 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/buildperf/test_basic.py @@ -121,5 +121,7 @@ class Test4(BuildPerfTestCase): self.sync() self.measure_cmd_resources([installer, '-y', '-d', deploy_dir], 'deploy', 'eSDK deploy') + #make sure bitbake is unloaded + self.sync() self.measure_disk_usage(deploy_dir, 'deploy_dir', 'deploy dir', apparent_size=True) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py b/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py index 07418fcda..a2912fc56 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/controllers/masterimage.py @@ -108,7 +108,7 @@ class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget, metaclass=ABCMeta time.sleep(10) self.power_ctl("cycle") else: - status, output = conn.run("reboot") + status, output = conn.run("sync; { sleep 1; reboot; } > /dev/null &") if status != 0: bb.error("Failed rebooting target and no power control command defined. You need to manually reset the device.\n%s" % output) @@ -143,7 +143,7 @@ class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget, metaclass=ABCMeta def _deploy(self): pass - def start(self, params=None): + def start(self, extra_bootparams=None): bb.plain("%s - boot test image on target" % self.pn) self._start() # set the ssh object for the target/test image @@ -156,7 +156,7 @@ class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget, metaclass=ABCMeta def stop(self): bb.plain("%s - reboot/powercycle target" % self.pn) - self.power_cycle(self.connection) + self.power_cycle(self.master) class SystemdbootTarget(MasterImageHardwareTarget): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/README b/import-layers/yocto-poky/meta/lib/oeqa/core/README index 0c859fd78..d4fcda41f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/README +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/README @@ -1,38 +1,76 @@ -= OEQA Framework = += OEQA (v2) Framework = == Introduction == -This is the new OEQA framework the base clases of the framework -are in this module oeqa/core the subsequent components needs to -extend this classes. +This is version 2 of the OEQA framework. Base clases are located in the +'oeqa/core' directory and subsequent components must extend from these. -A new/unique runner was created called oe-test and is under scripts/ -oe-test, this new runner scans over oeqa module searching for test -components that supports OETestContextExecutor implemented in context -module (i.e. oeqa/core/context.py). +The main design consideration was to implement the needed functionality on +top of the Python unittest framework. To achieve this goal, the following +modules are used: -For execute an example: + * oeqa/core/runner.py: Provides OETestResult and OETestRunner base + classes extending the unittest class. These classes support exporting + results to different formats; currently RAW and XML support exist. -$ source oe-init-build-env -$ oe-test core + * oeqa/core/loader.py: Provides OETestLoader extending the unittest class. + It also features a unified implementation of decorator support and + filtering test cases. -For list supported components: + * oeqa/core/case.py: Provides OETestCase base class extending + unittest.TestCase and provides access to the Test data (td), Test context + and Logger functionality. -$ oe-test -h + * oeqa/core/decorator: Provides OETestDecorator, a new class to implement + decorators for Test cases. -== Create new Test component == + * oeqa/core/context: Provides OETestContext, a high-level API for + loadTests and runTests of certain Test component and + OETestContextExecutor a base class to enable oe-test to discover/use + the Test component. -Usally for add a new Test component the developer needs to extend -OETestContext/OETestContextExecutor in context.py and OETestCase in -case.py. +Also, a new 'oe-test' runner is located under 'scripts', allowing scans for components +that supports OETestContextExecutor (see below). -== How to run the testing of the OEQA framework == +== Terminology == + + * Test component: The area of testing in the Project, for example: runtime, SDK, eSDK, selftest. + + * Test data: Data associated with the Test component. Currently we use bitbake datastore as + a Test data input. + + * Test context: A context of what tests needs to be run and how to do it; this additionally + provides access to the Test data and could have custom methods and/or attrs. + +== oe-test == + +The new tool, oe-test, has the ability to scan the code base for test components and provide +a unified way to run test cases. Internally it scans folders inside oeqa module in order to find +specific classes that implement a test component. + +== Usage == + +Executing the example test component + + $ source oe-init-build-env + $ oe-test core + +Getting help + + $ oe-test -h + +== Creating new Test Component == + +Adding a new test component the developer needs to extend OETestContext/OETestContextExecutor +(from context.py) and OETestCase (from case.py) + +== Selftesting the framework == Run all tests: -$ PATH=$PATH:../../ python3 -m unittest discover -s tests + $ PATH=$PATH:../../ python3 -m unittest discover -s tests Run some test: -$ cd tests/ -$ ./test_data.py + $ cd tests/ + $ ./test_data.py diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/case.py b/import-layers/yocto-poky/meta/lib/oeqa/core/case.py index d2dbf20f9..917a2aa3f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/case.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/case.py @@ -23,7 +23,7 @@ class OETestCase(unittest.TestCase): # td_vars has the variables needed by a test class # or test case instance, if some var isn't into td a - # OEMissingVariable exception is raised + # OEQAMissingVariable exception is raised td_vars = None @classmethod diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/context.py b/import-layers/yocto-poky/meta/lib/oeqa/core/context.py index 4476750a3..acd547416 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/context.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/context.py @@ -7,15 +7,14 @@ import json import time import logging import collections -import re from oeqa.core.loader import OETestLoader -from oeqa.core.runner import OETestRunner, OEStreamLogger, xmlEnabled +from oeqa.core.runner import OETestRunner +from oeqa.core.exception import OEQAMissingManifest, OEQATestNotFound class OETestContext(object): loaderClass = OETestLoader runnerClass = OETestRunner - streamLoggerClass = OEStreamLogger files_dir = os.path.abspath(os.path.join(os.path.dirname( os.path.abspath(__file__)), "../files")) @@ -32,7 +31,7 @@ class OETestContext(object): def _read_modules_from_manifest(self, manifest): if not os.path.exists(manifest): - raise + raise OEQAMissingManifest("Manifest does not exist on %s" % manifest) modules = [] for line in open(manifest).readlines(): @@ -42,6 +41,14 @@ class OETestContext(object): return modules + def skipTests(self, skips): + if not skips: + return + for test in self.suites: + for skip in skips: + if test.id().startswith(skip): + setattr(test, 'setUp', lambda: test.skipTest('Skip by the command line argument "%s"' % skip)) + def loadTests(self, module_paths, modules=[], tests=[], modules_manifest="", modules_required=[], filters={}): if modules_manifest: @@ -51,9 +58,11 @@ class OETestContext(object): modules_required, filters) self.suites = self.loader.discover() - def runTests(self): - streamLogger = self.streamLoggerClass(self.logger) - self.runner = self.runnerClass(self, stream=streamLogger, verbosity=2) + def runTests(self, skips=[]): + self.runner = self.runnerClass(self, descriptions=False, verbosity=2) + + # Dinamically skip those tests specified though arguments + self.skipTests(skips) self._run_start_time = time.time() result = self.runner.run(self.suites) @@ -61,94 +70,13 @@ class OETestContext(object): return result - def logSummary(self, result, component, context_msg=''): - self.logger.info("SUMMARY:") - self.logger.info("%s (%s) - Ran %d test%s in %.3fs" % (component, - context_msg, result.testsRun, result.testsRun != 1 and "s" or "", - (self._run_end_time - self._run_start_time))) - - if result.wasSuccessful(): - msg = "%s - OK - All required tests passed" % component - else: - msg = "%s - FAIL - Required tests failed" % component - skipped = len(self._results['skipped']) - if skipped: - msg += " (skipped=%d)" % skipped - self.logger.info(msg) - - def _getDetailsNotPassed(self, case, type, desc): - found = False - - for (scase, msg) in self._results[type]: - # XXX: When XML reporting is enabled scase is - # xmlrunner.result._TestInfo instance instead of - # string. - if xmlEnabled: - if case.id() == scase.test_id: - found = True - break - scase_str = scase.test_id - else: - if case == scase: - found = True - break - scase_str = str(scase) - - # When fails at module or class level the class name is passed as string - # so figure out to see if match - m = re.search("^setUpModule \((?P<module_name>.*)\)$", scase_str) - if m: - if case.__class__.__module__ == m.group('module_name'): - found = True - break - - m = re.search("^setUpClass \((?P<class_name>.*)\)$", scase_str) - if m: - class_name = "%s.%s" % (case.__class__.__module__, - case.__class__.__name__) - - if class_name == m.group('class_name'): - found = True - break - - if found: - return (found, msg) - - return (found, None) - - def logDetails(self): - self.logger.info("RESULTS:") - for case_name in self._registry['cases']: - case = self._registry['cases'][case_name] - - result_types = ['failures', 'errors', 'skipped', 'expectedFailures'] - result_desc = ['FAILED', 'ERROR', 'SKIPPED', 'EXPECTEDFAIL'] - - fail = False - desc = None - for idx, name in enumerate(result_types): - (fail, msg) = self._getDetailsNotPassed(case, result_types[idx], - result_desc[idx]) - if fail: - desc = result_desc[idx] - break - - oeid = -1 - for d in case.decorators: - if hasattr(d, 'oeid'): - oeid = d.oeid - - if fail: - self.logger.info("RESULTS - %s - Testcase %s: %s" % (case.id(), - oeid, desc)) - if msg: - self.logger.info(msg) - else: - self.logger.info("RESULTS - %s - Testcase %s: %s" % (case.id(), - oeid, 'PASSED')) + def listTests(self, display_type): + self.runner = self.runnerClass(self, verbosity=2) + return self.runner.list_tests(self.suites, display_type) class OETestContextExecutor(object): _context_class = OETestContext + _script_executor = 'oe-test' name = 'core' help = 'core test component example' @@ -168,9 +96,14 @@ class OETestContextExecutor(object): self.parser.add_argument('--output-log', action='store', default=self.default_output_log, help="results output log, default: %s" % self.default_output_log) - self.parser.add_argument('--run-tests', action='store', + + group = self.parser.add_mutually_exclusive_group() + group.add_argument('--run-tests', action='store', nargs='+', default=self.default_tests, - help="tests to run in <module>[.<class>[.<name>]] format. Just works for modules now") + help="tests to run in <module>[.<class>[.<name>]]") + group.add_argument('--list-tests', action='store', + choices=('module', 'class', 'name'), + help="lists available tests") if self.default_test_data: self.parser.add_argument('--test-data-file', action='store', @@ -206,7 +139,8 @@ class OETestContextExecutor(object): self.tc_kwargs = {} self.tc_kwargs['init'] = {} self.tc_kwargs['load'] = {} - self.tc_kwargs['run'] = {} + self.tc_kwargs['list'] = {} + self.tc_kwargs['run'] = {} self.tc_kwargs['init']['logger'] = self._setup_logger(logger, args) if args.test_data_file: @@ -215,22 +149,36 @@ class OETestContextExecutor(object): else: self.tc_kwargs['init']['td'] = {} - if args.run_tests: - self.tc_kwargs['load']['modules'] = args.run_tests.split() + self.tc_kwargs['load']['modules'] = args.run_tests + self.tc_kwargs['load']['modules_required'] = args.run_tests else: - self.tc_kwargs['load']['modules'] = None + self.tc_kwargs['load']['modules'] = [] + + self.tc_kwargs['run']['skips'] = [] self.module_paths = args.CASES_PATHS + def _pre_run(self): + pass + def run(self, logger, args): self._process_args(logger, args) self.tc = self._context_class(**self.tc_kwargs['init']) - self.tc.loadTests(self.module_paths, **self.tc_kwargs['load']) - rc = self.tc.runTests(**self.tc_kwargs['run']) - self.tc.logSummary(rc, self.name) - self.tc.logDetails() + try: + self.tc.loadTests(self.module_paths, **self.tc_kwargs['load']) + except OEQATestNotFound as ex: + logger.error(ex) + sys.exit(1) + + if args.list_tests: + rc = self.tc.listTests(args.list_tests, **self.tc_kwargs['list']) + else: + self._pre_run() + rc = self.tc.runTests(**self.tc_kwargs['run']) + rc.logDetails() + rc.logSummary(self.name) output_link = os.path.join(os.path.dirname(args.output_log), "%s-results.log" % self.name) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/depends.py b/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/depends.py index 195711cf1..baa04341c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/depends.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/depends.py @@ -3,6 +3,7 @@ from unittest import SkipTest +from oeqa.core.threaded import OETestRunnerThreaded from oeqa.core.exception import OEQADependency from . import OETestDiscover, registerDecorator @@ -63,7 +64,12 @@ def _order_test_case_by_depends(cases, depends): return [cases[case_id] for case_id in cases_ordered] def _skipTestDependency(case, depends): - results = case.tc._results + if isinstance(case.tc.runner, OETestRunnerThreaded): + import threading + results = case.tc._results[threading.get_ident()] + else: + results = case.tc._results + skipReasons = ['errors', 'failures', 'skipped'] for reason in skipReasons: diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/oetimeout.py b/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/oetimeout.py index a247583f7..f85e7d979 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/oetimeout.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/decorator/oetimeout.py @@ -1,8 +1,12 @@ # Copyright (C) 2016 Intel Corporation # Released under the MIT license (see COPYING.MIT) -import signal from . import OETestDecorator, registerDecorator + +import signal +from threading import Timer + +from oeqa.core.threaded import OETestRunnerThreaded from oeqa.core.exception import OEQATimeoutError @registerDecorator @@ -10,16 +14,32 @@ class OETimeout(OETestDecorator): attrs = ('oetimeout',) def setUpDecorator(self): - timeout = self.oetimeout - def _timeoutHandler(signum, frame): - raise OEQATimeoutError("Timed out after %s " + self.logger.debug("Setting up a %d second(s) timeout" % self.oetimeout) + + if isinstance(self.case.tc.runner, OETestRunnerThreaded): + self.timeouted = False + def _timeoutHandler(): + self.timeouted = True + + self.timer = Timer(self.oetimeout, _timeoutHandler) + self.timer.start() + else: + timeout = self.oetimeout + def _timeoutHandler(signum, frame): + raise OEQATimeoutError("Timed out after %s " "seconds of execution" % timeout) - self.logger.debug("Setting up a %d second(s) timeout" % self.oetimeout) - self.alarmSignal = signal.signal(signal.SIGALRM, _timeoutHandler) - signal.alarm(self.oetimeout) + self.alarmSignal = signal.signal(signal.SIGALRM, _timeoutHandler) + signal.alarm(self.oetimeout) def tearDownDecorator(self): - signal.alarm(0) - signal.signal(signal.SIGALRM, self.alarmSignal) - self.logger.debug("Removed SIGALRM handler") + if isinstance(self.case.tc.runner, OETestRunnerThreaded): + self.timer.cancel() + self.logger.debug("Removed Timer handler") + if self.timeouted: + raise OEQATimeoutError("Timed out after %s " + "seconds of execution" % self.oetimeout) + else: + signal.alarm(0) + signal.signal(signal.SIGALRM, self.alarmSignal) + self.logger.debug("Removed SIGALRM handler") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/exception.py b/import-layers/yocto-poky/meta/lib/oeqa/core/exception.py index 2dfd8402c..732f2efde 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/exception.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/exception.py @@ -12,3 +12,12 @@ class OEQAMissingVariable(OEQAException): class OEQADependency(OEQAException): pass + +class OEQAMissingManifest(OEQAException): + pass + +class OEQAPreRun(OEQAException): + pass + +class OEQATestNotFound(OEQAException): + pass diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/loader.py b/import-layers/yocto-poky/meta/lib/oeqa/core/loader.py index 63a170353..975a081ba 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/loader.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/loader.py @@ -2,25 +2,30 @@ # Released under the MIT license (see COPYING.MIT) import os +import re import sys import unittest +import inspect from oeqa.core.utils.path import findFile from oeqa.core.utils.test import getSuiteModules, getCaseID +from oeqa.core.exception import OEQATestNotFound from oeqa.core.case import OETestCase from oeqa.core.decorator import decoratorClasses, OETestDecorator, \ OETestFilter, OETestDiscover -def _make_failed_test(classname, methodname, exception, suiteClass): - """ - When loading tests unittest framework stores the exception in a new - class created for be displayed into run(). - - For our purposes will be better to raise the exception in loading - step instead of wait to run the test suite. - """ - raise exception +# When loading tests, the unittest framework stores any exceptions and +# displays them only when the run method is called. +# +# For our purposes, it is better to raise the exceptions in the loading +# step rather than waiting to run the test suite. +# +# Generate the function definition because this differ across python versions +# Python >= 3.4.4 uses tree parameters instead four but for example Python 3.5.3 +# ueses four parameters so isn't incremental. +_failed_test_args = inspect.getargspec(unittest.loader._make_failed_test).args +exec("""def _make_failed_test(%s): raise exception""" % ', '.join(_failed_test_args)) unittest.loader._make_failed_test = _make_failed_test def _find_duplicated_modules(suite, directory): @@ -29,6 +34,28 @@ def _find_duplicated_modules(suite, directory): if path: raise ImportError("Duplicated %s module found in %s" % (module, path)) +def _built_modules_dict(modules): + modules_dict = {} + + if modules == None: + return modules_dict + + for module in modules: + # Assumption: package and module names do not contain upper case + # characters, whereas class names do + m = re.match(r'^([^A-Z]+)(?:\.([A-Z][^.]*)(?:\.([^.]+))?)?$', module) + + module_name, class_name, test_name = m.groups() + + if module_name and module_name not in modules_dict: + modules_dict[module_name] = {} + if class_name and class_name not in modules_dict[module_name]: + modules_dict[module_name][class_name] = [] + if test_name and test_name not in modules_dict[module_name][class_name]: + modules_dict[module_name][class_name].append(test_name) + + return modules_dict + class OETestLoader(unittest.TestLoader): caseClass = OETestCase @@ -39,7 +66,8 @@ class OETestLoader(unittest.TestLoader): filters, *args, **kwargs): self.tc = tc - self.modules = modules + self.modules = _built_modules_dict(modules) + self.tests = tests self.modules_required = modules_required @@ -63,6 +91,8 @@ class OETestLoader(unittest.TestLoader): self._patchCaseClass(self.caseClass) + super(OETestLoader, self).__init__() + def _patchCaseClass(self, testCaseClass): # Adds custom attributes to the OETestCase class setattr(testCaseClass, 'tc', self.tc) @@ -116,7 +146,35 @@ class OETestLoader(unittest.TestLoader): """ Returns True if test case must be filtered, False otherwise. """ - if self.filters: + # XXX; If the module has more than one namespace only use + # the first to support run the whole module specifying the + # <module_name>.[test_class].[test_name] + module_name_small = case.__module__.split('.')[0] + module_name = case.__module__ + + class_name = case.__class__.__name__ + test_name = case._testMethodName + + if self.modules: + module = None + try: + module = self.modules[module_name_small] + except KeyError: + try: + module = self.modules[module_name] + except KeyError: + return True + + if module: + if not class_name in module: + return True + + if module[class_name]: + if test_name not in module[class_name]: + return True + + # Decorator filters + if self.filters and isinstance(case, OETestCase): filters = self.filters.copy() case_decorators = [cd for cd in case.decorators if cd.__class__ in self.used_filters] @@ -134,7 +192,8 @@ class OETestLoader(unittest.TestLoader): return False def _getTestCase(self, testCaseClass, tcName): - if not hasattr(testCaseClass, '__oeqa_loader'): + if not hasattr(testCaseClass, '__oeqa_loader') and \ + issubclass(testCaseClass, OETestCase): # In order to support data_vars validation # monkey patch the default setUp/tearDown{Class} to use # the ones provided by OETestCase @@ -161,7 +220,8 @@ class OETestLoader(unittest.TestLoader): setattr(testCaseClass, '__oeqa_loader', True) case = testCaseClass(tcName) - setattr(case, 'decorators', []) + if isinstance(case, OETestCase): + setattr(case, 'decorators', []) return case @@ -173,9 +233,9 @@ class OETestLoader(unittest.TestLoader): raise TypeError("Test cases should not be derived from TestSuite." \ " Maybe you meant to derive %s from TestCase?" \ % testCaseClass.__name__) - if not issubclass(testCaseClass, self.caseClass): + if not issubclass(testCaseClass, unittest.case.TestCase): raise TypeError("Test %s is not derived from %s" % \ - (testCaseClass.__name__, self.caseClass.__name__)) + (testCaseClass.__name__, unittest.case.TestCase.__name__)) testCaseNames = self.getTestCaseNames(testCaseClass) if not testCaseNames and hasattr(testCaseClass, 'runTest'): @@ -196,6 +256,28 @@ class OETestLoader(unittest.TestLoader): return self.suiteClass(suite) + def _required_modules_validation(self): + """ + Search in Test context registry if a required + test is found, raise an exception when not found. + """ + + for module in self.modules_required: + found = False + + # The module name is splitted to only compare the + # first part of a test case id. + comp_len = len(module.split('.')) + for case in self.tc._registry['cases']: + case_comp = '.'.join(case.split('.')[0:comp_len]) + if module == case_comp: + found = True + break + + if not found: + raise OEQATestNotFound("Not found %s in loaded test cases" % \ + module) + def discover(self): big_suite = self.suiteClass() for path in self.module_paths: @@ -210,8 +292,41 @@ class OETestLoader(unittest.TestLoader): for clss in discover_classes: cases = clss.discover(self.tc._registry) + if self.modules_required: + self._required_modules_validation() + return self.suiteClass(cases) if cases else big_suite + def _filterModule(self, module): + if module.__name__ in sys.builtin_module_names: + msg = 'Tried to import %s test module but is a built-in' + raise ImportError(msg % module.__name__) + + # XXX; If the module has more than one namespace only use + # the first to support run the whole module specifying the + # <module_name>.[test_class].[test_name] + module_name_small = module.__name__.split('.')[0] + module_name = module.__name__ + + # Normal test modules are loaded if no modules were specified, + # if module is in the specified module list or if 'all' is in + # module list. + # Underscore modules are loaded only if specified in module list. + load_module = True if not module_name.startswith('_') \ + and (not self.modules \ + or module_name in self.modules \ + or module_name_small in self.modules \ + or 'all' in self.modules) \ + else False + + load_underscore = True if module_name.startswith('_') \ + and (module_name in self.modules or \ + module_name_small in self.modules) \ + else False + + return (load_module, load_underscore) + + # XXX After Python 3.5, remove backward compatibility hacks for # use_load_tests deprecation via *args and **kws. See issue 16662. if sys.version_info >= (3,5): @@ -219,23 +334,7 @@ class OETestLoader(unittest.TestLoader): """ Returns a suite of all tests cases contained in module. """ - if module.__name__ in sys.builtin_module_names: - msg = 'Tried to import %s test module but is a built-in' - raise ImportError(msg % module.__name__) - - # Normal test modules are loaded if no modules were specified, - # if module is in the specified module list or if 'all' is in - # module list. - # Underscore modules are loaded only if specified in module list. - load_module = True if not module.__name__.startswith('_') \ - and (not self.modules \ - or module.__name__ in self.modules \ - or 'all' in self.modules) \ - else False - - load_underscore = True if module.__name__.startswith('_') \ - and module.__name__ in self.modules \ - else False + load_module, load_underscore = self._filterModule(module) if load_module or load_underscore: return super(OETestLoader, self).loadTestsFromModule( @@ -247,23 +346,7 @@ class OETestLoader(unittest.TestLoader): """ Returns a suite of all tests cases contained in module. """ - if module.__name__ in sys.builtin_module_names: - msg = 'Tried to import %s test module but is a built-in' - raise ImportError(msg % module.__name__) - - # Normal test modules are loaded if no modules were specified, - # if module is in the specified module list or if 'all' is in - # module list. - # Underscore modules are loaded only if specified in module list. - load_module = True if not module.__name__.startswith('_') \ - and (not self.modules \ - or module.__name__ in self.modules \ - or 'all' in self.modules) \ - else False - - load_underscore = True if module.__name__.startswith('_') \ - and module.__name__ in self.modules \ - else False + load_module, load_underscore = self._filterModule(module) if load_module or load_underscore: return super(OETestLoader, self).loadTestsFromModule( diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/runner.py b/import-layers/yocto-poky/meta/lib/oeqa/core/runner.py index 44ffecb0c..13cdf5ba5 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/runner.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/runner.py @@ -5,6 +5,7 @@ import os import time import unittest import logging +import re xmlEnabled = False try: @@ -24,10 +25,14 @@ class OEStreamLogger(object): def write(self, msg): if len(msg) > 1 and msg[0] != '\n': - self.buffer += msg - else: - self.logger.log(logging.INFO, self.buffer.rstrip("\n")) - self.buffer = "" + if '...' in msg: + self.buffer += msg + elif self.buffer: + self.buffer += msg + self.logger.log(logging.INFO, self.buffer) + self.buffer = "" + else: + self.logger.log(logging.INFO, msg) def flush(self): for handler in self.logger.handlers: @@ -38,22 +43,122 @@ class OETestResult(_TestResult): super(OETestResult, self).__init__(*args, **kwargs) self.tc = tc + self._tc_map_results() + + def startTest(self, test): + # Allow us to trigger the testcase buffer mode on a per test basis + # so stdout/stderr are only printed upon failure. Enables debugging + # but clean output + if hasattr(test, "buffer"): + self.buffer = test.buffer + super(OETestResult, self).startTest(test) + def _tc_map_results(self): self.tc._results['failures'] = self.failures self.tc._results['errors'] = self.errors self.tc._results['skipped'] = self.skipped self.tc._results['expectedFailures'] = self.expectedFailures - def startTest(self, test): - super(OETestResult, self).startTest(test) + def logSummary(self, component, context_msg=''): + elapsed_time = self.tc._run_end_time - self.tc._run_start_time + self.tc.logger.info("SUMMARY:") + self.tc.logger.info("%s (%s) - Ran %d test%s in %.3fs" % (component, + context_msg, self.testsRun, self.testsRun != 1 and "s" or "", + elapsed_time)) + + if self.wasSuccessful(): + msg = "%s - OK - All required tests passed" % component + else: + msg = "%s - FAIL - Required tests failed" % component + skipped = len(self.tc._results['skipped']) + if skipped: + msg += " (skipped=%d)" % skipped + self.tc.logger.info(msg) + + def _getDetailsNotPassed(self, case, type, desc): + found = False + + for (scase, msg) in self.tc._results[type]: + # XXX: When XML reporting is enabled scase is + # xmlrunner.result._TestInfo instance instead of + # string. + if xmlEnabled: + if case.id() == scase.test_id: + found = True + break + scase_str = scase.test_id + else: + if case == scase: + found = True + break + scase_str = str(scase) + + # When fails at module or class level the class name is passed as string + # so figure out to see if match + m = re.search("^setUpModule \((?P<module_name>.*)\)$", scase_str) + if m: + if case.__class__.__module__ == m.group('module_name'): + found = True + break + + m = re.search("^setUpClass \((?P<class_name>.*)\)$", scase_str) + if m: + class_name = "%s.%s" % (case.__class__.__module__, + case.__class__.__name__) + + if class_name == m.group('class_name'): + found = True + break + + if found: + return (found, msg) + + return (found, None) + + def logDetails(self): + self.tc.logger.info("RESULTS:") + for case_name in self.tc._registry['cases']: + case = self.tc._registry['cases'][case_name] + + result_types = ['failures', 'errors', 'skipped', 'expectedFailures'] + result_desc = ['FAILED', 'ERROR', 'SKIPPED', 'EXPECTEDFAIL'] + + fail = False + desc = None + for idx, name in enumerate(result_types): + (fail, msg) = self._getDetailsNotPassed(case, result_types[idx], + result_desc[idx]) + if fail: + desc = result_desc[idx] + break + + oeid = -1 + if hasattr(case, 'decorators'): + for d in case.decorators: + if hasattr(d, 'oeid'): + oeid = d.oeid + + if fail: + self.tc.logger.info("RESULTS - %s - Testcase %s: %s" % (case.id(), + oeid, desc)) + else: + self.tc.logger.info("RESULTS - %s - Testcase %s: %s" % (case.id(), + oeid, 'PASSED')) + +class OEListTestsResult(object): + def wasSuccessful(self): + return True class OETestRunner(_TestRunner): + streamLoggerClass = OEStreamLogger + def __init__(self, tc, *args, **kwargs): if xmlEnabled: if not kwargs.get('output'): kwargs['output'] = os.path.join(os.getcwd(), 'TestResults_%s_%s' % (time.strftime("%Y%m%d%H%M%S"), os.getpid())) + kwargs['stream'] = self.streamLoggerClass(tc.logger) super(OETestRunner, self).__init__(*args, **kwargs) self.tc = tc self.resultclass = OETestResult @@ -74,3 +179,99 @@ class OETestRunner(_TestRunner): def _makeResult(self): return self.resultclass(self.tc, self.stream, self.descriptions, self.verbosity) + + + def _walk_suite(self, suite, func): + for obj in suite: + if isinstance(obj, unittest.suite.TestSuite): + if len(obj._tests): + self._walk_suite(obj, func) + elif isinstance(obj, unittest.case.TestCase): + func(self.tc.logger, obj) + self._walked_cases = self._walked_cases + 1 + + def _list_tests_name(self, suite): + from oeqa.core.decorator.oeid import OETestID + from oeqa.core.decorator.oetag import OETestTag + + self._walked_cases = 0 + + def _list_cases_without_id(logger, case): + + found_id = False + if hasattr(case, 'decorators'): + for d in case.decorators: + if isinstance(d, OETestID): + found_id = True + + if not found_id: + logger.info('oeid missing for %s' % case.id()) + + def _list_cases(logger, case): + oeid = None + oetag = None + + if hasattr(case, 'decorators'): + for d in case.decorators: + if isinstance(d, OETestID): + oeid = d.oeid + elif isinstance(d, OETestTag): + oetag = d.oetag + + logger.info("%s\t%s\t\t%s" % (oeid, oetag, case.id())) + + self.tc.logger.info("Listing test cases that don't have oeid ...") + self._walk_suite(suite, _list_cases_without_id) + self.tc.logger.info("-" * 80) + + self.tc.logger.info("Listing all available tests:") + self._walked_cases = 0 + self.tc.logger.info("id\ttag\t\ttest") + self.tc.logger.info("-" * 80) + self._walk_suite(suite, _list_cases) + self.tc.logger.info("-" * 80) + self.tc.logger.info("Total found:\t%s" % self._walked_cases) + + def _list_tests_class(self, suite): + self._walked_cases = 0 + + curr = {} + def _list_classes(logger, case): + if not 'module' in curr or curr['module'] != case.__module__: + curr['module'] = case.__module__ + logger.info(curr['module']) + + if not 'class' in curr or curr['class'] != \ + case.__class__.__name__: + curr['class'] = case.__class__.__name__ + logger.info(" -- %s" % curr['class']) + + logger.info(" -- -- %s" % case._testMethodName) + + self.tc.logger.info("Listing all available test classes:") + self._walk_suite(suite, _list_classes) + + def _list_tests_module(self, suite): + self._walked_cases = 0 + + listed = [] + def _list_modules(logger, case): + if not case.__module__ in listed: + if case.__module__.startswith('_'): + logger.info("%s (hidden)" % case.__module__) + else: + logger.info(case.__module__) + listed.append(case.__module__) + + self.tc.logger.info("Listing all available test modules:") + self._walk_suite(suite, _list_modules) + + def list_tests(self, suite, display_type): + if display_type == 'name': + self._list_tests_name(suite) + elif display_type == 'class': + self._list_tests_class(suite) + elif display_type == 'module': + self._list_tests_module(suite) + + return OEListTestsResult() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/target/qemu.py b/import-layers/yocto-poky/meta/lib/oeqa/core/target/qemu.py index 2dc521c21..d359bf9fe 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/target/qemu.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/target/qemu.py @@ -31,7 +31,7 @@ class OEQemuTarget(OESSHTarget): deploy_dir_image=dir_image, display=display, logfile=bootlog, boottime=boottime, use_kvm=kvm, dump_dir=dump_dir, - dump_host_cmds=dump_host_cmds) + dump_host_cmds=dump_host_cmds, logger=logger) def start(self, params=None, extra_bootparams=None): if self.runner.start(params, extra_bootparams=extra_bootparams): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/target/ssh.py b/import-layers/yocto-poky/meta/lib/oeqa/core/target/ssh.py index b80939c0e..151b99a77 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/target/ssh.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/target/ssh.py @@ -6,6 +6,7 @@ import time import select import logging import subprocess +import codecs from . import OETarget @@ -82,7 +83,7 @@ class OESSHTarget(OETarget): processTimeout = self.timeout status, output = self._run(sshCmd, processTimeout, True) - self.logger.info('\nCommand: %s\nOutput: %s\n' % (command, output)) + self.logger.debug('Command: %s\nOutput: %s\n' % (command, output)) return (status, output) def copyTo(self, localSrc, remoteDst): @@ -206,12 +207,12 @@ def SSHCall(command, logger, timeout=None, **opts): logger.debug('time: %s, endtime: %s' % (time.time(), endtime)) try: if select.select([process.stdout], [], [], 5)[0] != []: - data = os.read(process.stdout.fileno(), 1024) + reader = codecs.getreader('utf-8')(process.stdout) + data = reader.read(1024, 1024) if not data: process.stdout.close() eof = True else: - data = data.decode("utf-8") output += data logger.debug('Partial data from SSH call: %s' % data) endtime = time.time() + timeout @@ -233,7 +234,7 @@ def SSHCall(command, logger, timeout=None, **opts): output += lastline else: - output = process.communicate()[0].decode("utf-8") + output = process.communicate()[0].decode("utf-8", errors='replace') logger.debug('Data from SSH call: %s' % output.rstrip()) options = { diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded.py new file mode 100644 index 000000000..0fe4cb3f1 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded.py @@ -0,0 +1,12 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +from oeqa.core.case import OETestCase + +class ThreadedTest(OETestCase): + def test_threaded_no_depends(self): + self.assertTrue(True, msg='How is this possible?') + +class ThreadedTest2(OETestCase): + def test_threaded_same_module(self): + self.assertTrue(True, msg='How is this possible?') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_alone.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_alone.py new file mode 100644 index 000000000..905f39784 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_alone.py @@ -0,0 +1,8 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +from oeqa.core.case import OETestCase + +class ThreadedTestAlone(OETestCase): + def test_threaded_alone(self): + self.assertTrue(True, msg='How is this possible?') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_depends.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_depends.py new file mode 100644 index 000000000..0c158d3ba --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_depends.py @@ -0,0 +1,10 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +from oeqa.core.case import OETestCase +from oeqa.core.decorator.depends import OETestDepends + +class ThreadedTest3(OETestCase): + @OETestDepends(['threaded.ThreadedTest.test_threaded_no_depends']) + def test_threaded_depends(self): + self.assertTrue(True, msg='How is this possible?') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_module.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_module.py new file mode 100644 index 000000000..63d17e040 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/cases/loader/threaded/threaded_module.py @@ -0,0 +1,12 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +from oeqa.core.case import OETestCase + +class ThreadedTestModule(OETestCase): + def test_threaded_module(self): + self.assertTrue(True, msg='How is this possible?') + +class ThreadedTestModule2(OETestCase): + def test_threaded_module2(self): + self.assertTrue(True, msg='How is this possible?') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/common.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/common.py index 52b18a1c3..193232340 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/common.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/common.py @@ -33,3 +33,13 @@ class TestBase(unittest.TestCase): tc.loadTests(self.cases_path, modules=modules, tests=tests, filters=filters) return tc + + def _testLoaderThreaded(self, d={}, modules=[], + tests=[], filters={}): + from oeqa.core.threaded import OETestContextThreaded + + tc = OETestContextThreaded(d, self.logger) + tc.loadTests(self.cases_path, modules=modules, tests=tests, + filters=filters) + + return tc diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_decorators.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_decorators.py index f7d11e885..cf99e0d72 100755 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_decorators.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_decorators.py @@ -131,5 +131,17 @@ class TestTimeoutDecorator(TestBase): msg = "OETestTimeout didn't restore SIGALRM" self.assertIs(alarm_signal, signal.getsignal(signal.SIGALRM), msg=msg) + def test_timeout_thread(self): + tests = ['timeout.TimeoutTest.testTimeoutPass'] + msg = 'Failed to run test using OETestTimeout' + tc = self._testLoaderThreaded(modules=self.modules, tests=tests) + self.assertTrue(tc.runTests().wasSuccessful(), msg=msg) + + def test_timeout_threaded_fail(self): + tests = ['timeout.TimeoutTest.testTimeoutFail'] + msg = "OETestTimeout test didn't timeout as expected" + tc = self._testLoaderThreaded(modules=self.modules, tests=tests) + self.assertFalse(tc.runTests().wasSuccessful(), msg=msg) + if __name__ == '__main__': unittest.main() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_loader.py b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_loader.py index b79b8bad4..e0d917d31 100755 --- a/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_loader.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/tests/test_loader.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright (C) 2016 Intel Corporation +# Copyright (C) 2016-2017 Intel Corporation # Released under the MIT license (see COPYING.MIT) import os @@ -82,5 +82,33 @@ class TestLoader(TestBase): msg = 'Expected modules from two different paths' self.assertEqual(modules, expected_modules, msg=msg) + def test_loader_threaded(self): + cases_path = self.cases_path + + self.cases_path = [os.path.join(self.cases_path, 'loader', 'threaded')] + + tc = self._testLoaderThreaded() + self.assertEqual(len(tc.suites), 3, "Expected to be 3 suites") + + case_ids = ['threaded.ThreadedTest.test_threaded_no_depends', + 'threaded.ThreadedTest2.test_threaded_same_module', + 'threaded_depends.ThreadedTest3.test_threaded_depends'] + for case in tc.suites[0]._tests: + self.assertEqual(case.id(), + case_ids[tc.suites[0]._tests.index(case)]) + + case_ids = ['threaded_alone.ThreadedTestAlone.test_threaded_alone'] + for case in tc.suites[1]._tests: + self.assertEqual(case.id(), + case_ids[tc.suites[1]._tests.index(case)]) + + case_ids = ['threaded_module.ThreadedTestModule.test_threaded_module', + 'threaded_module.ThreadedTestModule2.test_threaded_module2'] + for case in tc.suites[2]._tests: + self.assertEqual(case.id(), + case_ids[tc.suites[2]._tests.index(case)]) + + self.cases_path = cases_path + if __name__ == '__main__': unittest.main() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/core/threaded.py b/import-layers/yocto-poky/meta/lib/oeqa/core/threaded.py new file mode 100644 index 000000000..2cafe03a2 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/core/threaded.py @@ -0,0 +1,275 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import threading +import multiprocessing +import queue +import time + +from unittest.suite import TestSuite + +from oeqa.core.loader import OETestLoader +from oeqa.core.runner import OEStreamLogger, OETestResult, OETestRunner +from oeqa.core.context import OETestContext + +class OETestLoaderThreaded(OETestLoader): + def __init__(self, tc, module_paths, modules, tests, modules_required, + filters, process_num=0, *args, **kwargs): + super(OETestLoaderThreaded, self).__init__(tc, module_paths, modules, + tests, modules_required, filters, *args, **kwargs) + + self.process_num = process_num + + def discover(self): + suite = super(OETestLoaderThreaded, self).discover() + + if self.process_num <= 0: + self.process_num = min(multiprocessing.cpu_count(), + len(suite._tests)) + + suites = [] + for _ in range(self.process_num): + suites.append(self.suiteClass()) + + def _search_for_module_idx(suites, case): + """ + Cases in the same module needs to be run + in the same thread because PyUnit keeps track + of setUp{Module, Class,} and tearDown{Module, Class,}. + """ + + for idx in range(self.process_num): + suite = suites[idx] + for c in suite._tests: + if case.__module__ == c.__module__: + return idx + + return -1 + + def _search_for_depend_idx(suites, depends): + """ + Dependency cases needs to be run in the same + thread, because OEQA framework look at the state + of dependant test to figure out if skip or not. + """ + + for idx in range(self.process_num): + suite = suites[idx] + + for case in suite._tests: + if case.id() in depends: + return idx + return -1 + + def _get_best_idx(suites): + sizes = [len(suite._tests) for suite in suites] + return sizes.index(min(sizes)) + + def _fill_suites(suite): + idx = -1 + for case in suite: + if isinstance(case, TestSuite): + _fill_suites(case) + else: + idx = _search_for_module_idx(suites, case) + + depends = {} + if 'depends' in self.tc._registry: + depends = self.tc._registry['depends'] + + if idx == -1 and case.id() in depends: + case_depends = depends[case.id()] + idx = _search_for_depend_idx(suites, case_depends) + + if idx == -1: + idx = _get_best_idx(suites) + + suites[idx].addTest(case) + _fill_suites(suite) + + suites_tmp = suites + suites = [] + for suite in suites_tmp: + if len(suite._tests) > 0: + suites.append(suite) + + return suites + +class OEStreamLoggerThreaded(OEStreamLogger): + _lock = threading.Lock() + buffers = {} + + def write(self, msg): + tid = threading.get_ident() + + if not tid in self.buffers: + self.buffers[tid] = "" + + if msg: + self.buffers[tid] += msg + + def finish(self): + tid = threading.get_ident() + + self._lock.acquire() + self.logger.info('THREAD: %d' % tid) + self.logger.info('-' * 70) + for line in self.buffers[tid].split('\n'): + self.logger.info(line) + self._lock.release() + +class OETestResultThreadedInternal(OETestResult): + def _tc_map_results(self): + tid = threading.get_ident() + + # PyUnit generates a result for every test module run, test + # if the thread already has an entry to avoid lose the previous + # test module results. + if not tid in self.tc._results: + self.tc._results[tid] = {} + self.tc._results[tid]['failures'] = self.failures + self.tc._results[tid]['errors'] = self.errors + self.tc._results[tid]['skipped'] = self.skipped + self.tc._results[tid]['expectedFailures'] = self.expectedFailures + +class OETestResultThreaded(object): + _results = {} + _lock = threading.Lock() + + def __init__(self, tc): + self.tc = tc + + def _fill_tc_results(self): + tids = list(self.tc._results.keys()) + fields = ['failures', 'errors', 'skipped', 'expectedFailures'] + + for tid in tids: + result = self.tc._results[tid] + for field in fields: + if not field in self.tc._results: + self.tc._results[field] = [] + self.tc._results[field].extend(result[field]) + + def addResult(self, result, run_start_time, run_end_time): + tid = threading.get_ident() + + self._lock.acquire() + self._results[tid] = {} + self._results[tid]['result'] = result + self._results[tid]['run_start_time'] = run_start_time + self._results[tid]['run_end_time'] = run_end_time + self._results[tid]['result'] = result + self._lock.release() + + def wasSuccessful(self): + wasSuccessful = True + for tid in self._results.keys(): + wasSuccessful = wasSuccessful and \ + self._results[tid]['result'].wasSuccessful() + return wasSuccessful + + def stop(self): + for tid in self._results.keys(): + self._results[tid]['result'].stop() + + def logSummary(self, component, context_msg=''): + elapsed_time = (self.tc._run_end_time - self.tc._run_start_time) + + self.tc.logger.info("SUMMARY:") + self.tc.logger.info("%s (%s) - Ran %d tests in %.3fs" % (component, + context_msg, len(self.tc._registry['cases']), elapsed_time)) + if self.wasSuccessful(): + msg = "%s - OK - All required tests passed" % component + else: + msg = "%s - FAIL - Required tests failed" % component + self.tc.logger.info(msg) + + def logDetails(self): + if list(self._results): + tid = list(self._results)[0] + result = self._results[tid]['result'] + result.logDetails() + +class _Worker(threading.Thread): + """Thread executing tasks from a given tasks queue""" + def __init__(self, tasks, result, stream): + threading.Thread.__init__(self) + self.tasks = tasks + + self.result = result + self.stream = stream + + def run(self): + while True: + try: + func, args, kargs = self.tasks.get(block=False) + except queue.Empty: + break + + try: + run_start_time = time.time() + rc = func(*args, **kargs) + run_end_time = time.time() + self.result.addResult(rc, run_start_time, run_end_time) + self.stream.finish() + except Exception as e: + print(e) + finally: + self.tasks.task_done() + +class _ThreadedPool: + """Pool of threads consuming tasks from a queue""" + def __init__(self, num_workers, num_tasks, stream=None, result=None): + self.tasks = queue.Queue(num_tasks) + self.workers = [] + + for _ in range(num_workers): + worker = _Worker(self.tasks, result, stream) + self.workers.append(worker) + + def start(self): + for worker in self.workers: + worker.start() + + def add_task(self, func, *args, **kargs): + """Add a task to the queue""" + self.tasks.put((func, args, kargs)) + + def wait_completion(self): + """Wait for completion of all the tasks in the queue""" + self.tasks.join() + for worker in self.workers: + worker.join() + +class OETestRunnerThreaded(OETestRunner): + streamLoggerClass = OEStreamLoggerThreaded + + def __init__(self, tc, *args, **kwargs): + super(OETestRunnerThreaded, self).__init__(tc, *args, **kwargs) + self.resultclass = OETestResultThreadedInternal # XXX: XML reporting overrides at __init__ + + def run(self, suites): + result = OETestResultThreaded(self.tc) + + pool = _ThreadedPool(len(suites), len(suites), stream=self.stream, + result=result) + for s in suites: + pool.add_task(super(OETestRunnerThreaded, self).run, s) + pool.start() + pool.wait_completion() + result._fill_tc_results() + + return result + +class OETestContextThreaded(OETestContext): + loaderClass = OETestLoaderThreaded + runnerClass = OETestRunnerThreaded + + def loadTests(self, module_paths, modules=[], tests=[], + modules_manifest="", modules_required=[], filters={}, process_num=0): + if modules_manifest: + modules = self._read_modules_from_manifest(modules_manifest) + + self.loader = self.loaderClass(self, module_paths, modules, tests, + modules_required, filters, process_num) + self.suites = self.loader.discover() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/case.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/case.py index c1485c986..2f190acf1 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/case.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/case.py @@ -8,10 +8,10 @@ class OERuntimeTestCase(OETestCase): # target instance set by OERuntimeTestLoader. target = None - def _oeSetUp(self): - super(OERuntimeTestCase, self)._oeSetUp() + def setUp(self): + super(OERuntimeTestCase, self).setUp() install_package(self) - def _oeTearDown(self): - super(OERuntimeTestCase, self)._oeTearDown() + def tearDown(self): + super(OERuntimeTestCase, self).tearDown() uninstall_package(self) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/_ptest.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/_ptest.py deleted file mode 100644 index aaed9a535..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/_ptest.py +++ /dev/null @@ -1,103 +0,0 @@ -import os -import shutil -import subprocess - -from oeqa.runtime.case import OERuntimeTestCase -from oeqa.core.decorator.depends import OETestDepends -from oeqa.core.decorator.oeid import OETestID -from oeqa.core.decorator.data import skipIfNotDataVar, skipIfNotFeature -from oeqa.runtime.decorator.package import OEHasPackage - -from oeqa.runtime.cases.dnf import DnfTest -from oeqa.utils.logparser import * -from oeqa.utils.httpserver import HTTPService - -class PtestRunnerTest(DnfTest): - - @classmethod - def setUpClass(cls): - rpm_deploy = os.path.join(cls.tc.td['DEPLOY_DIR'], 'rpm') - cls.repo_server = HTTPService(rpm_deploy, cls.tc.target.server_ip) - cls.repo_server.start() - - @classmethod - def tearDownClass(cls): - cls.repo_server.stop() - - # a ptest log parser - def parse_ptest(self, logfile): - parser = Lparser(test_0_pass_regex="^PASS:(.+)", - test_0_fail_regex="^FAIL:(.+)", - section_0_begin_regex="^BEGIN: .*/(.+)/ptest", - section_0_end_regex="^END: .*/(.+)/ptest") - parser.init() - result = Result() - - with open(logfile, errors='replace') as f: - for line in f: - result_tuple = parser.parse_line(line) - if not result_tuple: - continue - result_tuple = line_type, category, status, name = parser.parse_line(line) - - if line_type == 'section' and status == 'begin': - current_section = name - continue - - if line_type == 'section' and status == 'end': - current_section = None - continue - - if line_type == 'test' and status == 'pass': - result.store(current_section, name, status) - continue - - if line_type == 'test' and status == 'fail': - result.store(current_section, name, status) - continue - - result.sort_tests() - return result - - def _install_ptest_packages(self): - # Get ptest packages that can be installed in the image. - packages_dir = os.path.join(self.tc.td['DEPLOY_DIR'], 'rpm') - ptest_pkgs = [pkg[:pkg.find('-ptest')+6] - for _, _, filenames in os.walk(packages_dir) - for pkg in filenames - if 'ptest' in pkg - and pkg[:pkg.find('-ptest')] in self.tc.image_packages] - - repo_url = 'http://%s:%s' % (self.target.server_ip, - self.repo_server.port) - dnf_options = ('--repofrompath=oe-ptest-repo,%s ' - '--nogpgcheck ' - 'install -y' % repo_url) - self.dnf('%s %s ptest-runner' % (dnf_options, ' '.join(ptest_pkgs))) - - @skipIfNotFeature('package-management', - 'Test requires package-management to be in DISTRO_FEATURES') - @skipIfNotFeature('ptest', - 'Test requires package-management to be in DISTRO_FEATURES') - @skipIfNotDataVar('IMAGE_PKGTYPE', 'rpm', - 'RPM is not the primary package manager') - @OEHasPackage(['dnf']) - @OETestDepends(['ssh.SSHTest.test_ssh']) - def test_ptestrunner(self): - self.ptest_log = os.path.join(self.tc.td['TEST_LOG_DIR'], - 'ptest-%s.log' % self.tc.td['DATETIME']) - self._install_ptest_packages() - - (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.copyFrom('/tmp/ptest.log', self.ptest_log) - shutil.copyfile(self.ptest_log, "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 = ['pass','fail']) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/buildcpio.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/buildcpio.py index 59edc9c2c..79b22d04d 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/buildcpio.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/buildcpio.py @@ -9,8 +9,7 @@ class BuildCpioTest(OERuntimeTestCase): @classmethod def setUpClass(cls): - uri = 'https://ftp.gnu.org/gnu/cpio' - uri = '%s/cpio-2.12.tar.bz2' % uri + uri = 'https://downloads.yoctoproject.org/mirror/sources/cpio-2.12.tar.gz' cls.project = TargetBuildProject(cls.tc.target, uri, dl_dir = cls.tc.td['DL_DIR']) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/parselogs.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/parselogs.py index 6e929469c..1f36c6108 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/parselogs.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/parselogs.py @@ -86,9 +86,11 @@ ignore_errors = { 'qemumips' : [ 'Failed to load module "glx"', 'pci 0000:00:00.0: [Firmware Bug]: reg 0x..: invalid BAR (can\'t size)', + 'cacheinfo: Failed to find cpu0 device node', ] + common_errors, 'qemumips64' : [ 'pci 0000:00:00.0: [Firmware Bug]: reg 0x..: invalid BAR (can\'t size)', + 'cacheinfo: Failed to find cpu0 device node', ] + common_errors, 'qemuppc' : [ 'PCI 0000:00 Cannot reserve Legacy IO [io 0x0000-0x0fff]', @@ -151,6 +153,8 @@ ignore_errors = { 'failed to read out thermal zone', 'Bluetooth: hci0: Setting Intel event mask failed', 'ttyS2 - failed to request DMA', + 'Bluetooth: hci0: Failed to send firmware data (-38)', + 'atkbd serio0: Failed to enable keyboard on isa0060/serio0', ] + x86_common, 'crownbay' : x86_common, 'genericx86' : x86_common, diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/ptest.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/ptest.py new file mode 100644 index 000000000..ec8c038a5 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/cases/ptest.py @@ -0,0 +1,82 @@ +from oeqa.runtime.case import OERuntimeTestCase +from oeqa.core.decorator.depends import OETestDepends +from oeqa.core.decorator.oeid import OETestID +from oeqa.core.decorator.data import skipIfNotFeature +from oeqa.utils.logparser import Lparser, Result + +class PtestRunnerTest(OERuntimeTestCase): + + # a ptest log parser + def parse_ptest(self, logfile): + parser = Lparser(test_0_pass_regex="^PASS:(.+)", + test_0_fail_regex="^FAIL:(.+)", + test_0_skip_regex="^SKIP:(.+)", + section_0_begin_regex="^BEGIN: .*/(.+)/ptest", + section_0_end_regex="^END: .*/(.+)/ptest") + parser.init() + result = Result() + + with open(logfile, errors='replace') as f: + for line in f: + result_tuple = parser.parse_line(line) + if not result_tuple: + continue + result_tuple = line_type, category, status, name = parser.parse_line(line) + + if line_type == 'section' and status == 'begin': + current_section = name + continue + + if line_type == 'section' and status == 'end': + current_section = None + continue + + if line_type == 'test' and status == 'pass': + result.store(current_section, name, status) + continue + + if line_type == 'test' and status == 'fail': + result.store(current_section, name, status) + continue + + if line_type == 'test' and status == 'skip': + result.store(current_section, name, status) + continue + + result.sort_tests() + return result + + @OETestID(1600) + @skipIfNotFeature('ptest', 'Test requires ptest to be in DISTRO_FEATURES') + @skipIfNotFeature('ptest-pkgs', 'Test requires ptest-pkgs to be in IMAGE_FEATURES') + @OETestDepends(['ssh.SSHTest.test_ssh']) + def test_ptestrunner(self): + import datetime + + test_log_dir = self.td.get('TEST_LOG_DIR', '') + # The TEST_LOG_DIR maybe NULL when testimage is added after + # testdata.json is generated. + if not test_log_dir: + test_log_dir = os.path.join(self.td.get('WORKDIR', ''), 'testimage') + # Don't use self.td.get('DATETIME'), it's from testdata.json, not + # up-to-date, and may cause "File exists" when re-reun. + datetime = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ptest_log_dir_link = os.path.join(test_log_dir, 'ptest_log') + ptest_log_dir = '%s.%s' % (ptest_log_dir_link, datetime) + ptest_runner_log = os.path.join(ptest_log_dir, 'ptest-runner.log') + + status, output = self.target.run('ptest-runner', 0) + os.makedirs(ptest_log_dir) + with open(ptest_runner_log, 'w') as f: + f.write(output) + + # status != 0 is OK since some ptest tests may fail + self.assertTrue(status != 127, msg="Cannot execute ptest-runner!") + + # Parse and save results + parse_result = self.parse_ptest(ptest_runner_log) + parse_result.log_as_files(ptest_log_dir, test_status = ['pass','fail', 'skip']) + if os.path.exists(ptest_log_dir_link): + # Remove the old link to create a new one + os.remove(ptest_log_dir_link) + os.symlink(os.path.basename(ptest_log_dir), ptest_log_dir_link) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/runtime/context.py b/import-layers/yocto-poky/meta/lib/oeqa/runtime/context.py index c4cd76cf4..0294003fc 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/runtime/context.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/runtime/context.py @@ -92,6 +92,12 @@ class OERuntimeTestContextExecutor(OETestContextExecutor): def getTarget(target_type, logger, target_ip, server_ip, **kwargs): target = None + if target_ip: + target_ip_port = target_ip.split(':') + if len(target_ip_port) == 2: + target_ip = target_ip_port[0] + kwargs['port'] = target_ip_port[1] + if target_type == 'simpleremote': target = OESSHTarget(logger, target_ip, server_ip, **kwargs) elif target_type == 'qemu': diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildgalculator.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildgalculator.py index 42e8ddb18..780afccc7 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildgalculator.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildgalculator.py @@ -8,7 +8,7 @@ class GalculatorTest(OESDKTestCase): @classmethod def setUpClass(self): - if not (self.tc.hasTargetPackage("gtk+3") or\ + if not (self.tc.hasTargetPackage("gtk\+3") or\ self.tc.hasTargetPackage("libgtk-3.0")): raise unittest.SkipTest("GalculatorTest class: SDK don't support gtk+3") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildlzip.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildlzip.py index 2a53b783c..3a89ce862 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildlzip.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/buildlzip.py @@ -17,7 +17,8 @@ class BuildLzipTest(OESDKTestCase): machine = self.td.get("MACHINE") - if not self.tc.hasHostPackage("packagegroup-cross-canadian-%s" % machine): + if not (self.tc.hasTargetPackage("packagegroup-cross-canadian-%s" % machine) or + self.tc.hasTargetPackage("gcc")): raise unittest.SkipTest("SDK doesn't contain a cross-canadian toolchain") def test_lzip(self): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/gcc.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/gcc.py index 74ad2a2f2..d11f4b63f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/gcc.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/gcc.py @@ -18,7 +18,8 @@ class GccCompileTest(OESDKTestCase): def setUp(self): machine = self.td.get("MACHINE") - if not self.tc.hasHostPackage("packagegroup-cross-canadian-%s" % machine): + if not (self.tc.hasTargetPackage("packagegroup-cross-canadian-%s" % machine) or + self.tc.hasTargetPackage("gcc")): raise unittest.SkipTest("GccCompileTest class: SDK doesn't contain a cross-canadian toolchain") def test_gcc_compile(self): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/perl.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/perl.py index e1bded2ff..808567811 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/perl.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/perl.py @@ -8,7 +8,8 @@ from oeqa.sdk.case import OESDKTestCase class PerlTest(OESDKTestCase): @classmethod def setUpClass(self): - if not self.tc.hasHostPackage("nativesdk-perl"): + if not (self.tc.hasHostPackage("nativesdk-perl") or + self.tc.hasHostPackage("perl-native")): raise unittest.SkipTest("No perl package in the SDK") for f in ['test.pl']: diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/python.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/python.py index 94a296f0e..72dfcc72b 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/python.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/cases/python.py @@ -8,7 +8,8 @@ from oeqa.sdk.case import OESDKTestCase class PythonTest(OESDKTestCase): @classmethod def setUpClass(self): - if not self.tc.hasHostPackage("nativesdk-python"): + if not (self.tc.hasHostPackage("nativesdk-python") or + self.tc.hasHostPackage("python-native")): raise unittest.SkipTest("No python package in the SDK") for f in ['test.py']: diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdk/context.py b/import-layers/yocto-poky/meta/lib/oeqa/sdk/context.py index 0189ed851..b3d7c7518 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdk/context.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdk/context.py @@ -6,9 +6,10 @@ import sys import glob import re -from oeqa.core.context import OETestContext, OETestContextExecutor +from oeqa.core.context import OETestContextExecutor +from oeqa.core.threaded import OETestContextThreaded -class OESDKTestContext(OETestContext): +class OESDKTestContext(OETestContextThreaded): sdk_files_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "files") def __init__(self, td=None, logger=None, sdk_dir=None, sdk_env=None, @@ -44,8 +45,6 @@ class OESDKTestContextExecutor(OETestContextExecutor): default_test_data = None def register_commands(self, logger, subparsers): - import argparse_oe - super(OESDKTestContextExecutor, self).register_commands(logger, subparsers) sdk_group = self.parser.add_argument_group('sdk options') @@ -109,6 +108,8 @@ class OESDKTestContextExecutor(OETestContextExecutor): log(env) def run(self, logger, args): + import argparse_oe + if not args.sdk_dir: raise argparse_oe.ArgumentUsageError("No SDK directory "\ "specified please do, --sdk-dir SDK_DIR", self.name) @@ -128,6 +129,6 @@ class OESDKTestContextExecutor(OETestContextExecutor): "environment (%s) specified" % args.sdk_env, self.name) self.sdk_env = sdk_envs[args.sdk_env] - super(OESDKTestContextExecutor, self).run(logger, args) + return super(OESDKTestContextExecutor, self).run(logger, args) _executor_class = OESDKTestContextExecutor diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/devtool.py b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/devtool.py index a01bc0bfe..ea9051710 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/devtool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/devtool.py @@ -1,12 +1,14 @@ # Copyright (C) 2016 Intel Corporation # Released under the MIT license (see COPYING.MIT) +import os import shutil import subprocess from oeqa.sdkext.case import OESDKExtTestCase from oeqa.core.decorator.depends import OETestDepends from oeqa.core.decorator.oeid import OETestID +from oeqa.utils.httpserver import HTTPService class DevtoolTest(OESDKExtTestCase): @classmethod @@ -95,3 +97,33 @@ class DevtoolTest(OESDKExtTestCase): self._run('devtool build %s ' % package_nodejs) finally: self._run('devtool reset %s '% package_nodejs) + +class SdkUpdateTest(OESDKExtTestCase): + @classmethod + def setUpClass(self): + self.publish_dir = os.path.join(self.tc.sdk_dir, 'esdk_publish') + if os.path.exists(self.publish_dir): + shutil.rmtree(self.publish_dir) + os.mkdir(self.publish_dir) + + base_tcname = "%s/%s" % (self.td.get("SDK_DEPLOY", ''), + self.td.get("TOOLCHAINEXT_OUTPUTNAME", '')) + tcname_new = "%s-new.sh" % base_tcname + if not os.path.exists(tcname_new): + tcname_new = "%s.sh" % base_tcname + + cmd = 'oe-publish-sdk %s %s' % (tcname_new, self.publish_dir) + subprocess.check_output(cmd, shell=True) + + self.http_service = HTTPService(self.publish_dir) + self.http_service.start() + + self.http_url = "http://127.0.0.1:%d" % self.http_service.port + + def test_sdk_update_http(self): + output = self._run("devtool sdk-update \"%s\"" % self.http_url) + + @classmethod + def tearDownClass(self): + self.http_service.stop() + shutil.rmtree(self.publish_dir) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/sdk_update.py b/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/sdk_update.py deleted file mode 100644 index 2f8598bbe..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/sdkext/cases/sdk_update.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2016 Intel Corporation -# Released under the MIT license (see COPYING.MIT) - -import os -import shutil -import subprocess - -from oeqa.sdkext.case import OESDKExtTestCase -from oeqa.utils.httpserver import HTTPService - -class SdkUpdateTest(OESDKExtTestCase): - @classmethod - def setUpClass(self): - self.publish_dir = os.path.join(self.tc.sdk_dir, 'esdk_publish') - if os.path.exists(self.publish_dir): - shutil.rmtree(self.publish_dir) - os.mkdir(self.publish_dir) - - base_tcname = "%s/%s" % (self.td.get("SDK_DEPLOY", ''), - self.td.get("TOOLCHAINEXT_OUTPUTNAME", '')) - tcname_new = "%s-new.sh" % base_tcname - if not os.path.exists(tcname_new): - tcname_new = "%s.sh" % base_tcname - - cmd = 'oe-publish-sdk %s %s' % (tcname_new, self.publish_dir) - subprocess.check_output(cmd, shell=True) - - self.http_service = HTTPService(self.publish_dir) - self.http_service.start() - - self.http_url = "http://127.0.0.1:%d" % self.http_service.port - - def test_sdk_update_http(self): - output = self._run("devtool sdk-update \"%s\"" % self.http_url) - - @classmethod - def tearDownClass(self): - self.http_service.stop() - shutil.rmtree(self.publish_dir) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py deleted file mode 100644 index 3ad9513f4..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py deleted file mode 100644 index 47a8ea827..000000000 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright (c) 2013 Intel Corporation -# -# Released under the MIT license (see COPYING.MIT) - - -# DESCRIPTION -# Base class inherited by test classes in meta/lib/oeqa/selftest - -import unittest -import os -import sys -import shutil -import logging -import errno - -import oeqa.utils.ftools as ftools -from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer -from oeqa.utils.decorators import LogResults -from random import choice -import glob - -@LogResults -class oeSelfTest(unittest.TestCase): - - log = logging.getLogger("selftest.base") - longMessage = True - - def __init__(self, methodName="runTest"): - self.builddir = os.environ.get("BUILDDIR") - self.localconf_path = os.path.join(self.builddir, "conf/local.conf") - self.localconf_backup = os.path.join(self.builddir, "conf/local.bk") - self.testinc_path = os.path.join(self.builddir, "conf/selftest.inc") - self.local_bblayers_path = os.path.join(self.builddir, "conf/bblayers.conf") - self.local_bblayers_backup = os.path.join(self.builddir, - "conf/bblayers.bk") - self.testinc_bblayers_path = os.path.join(self.builddir, "conf/bblayers.inc") - self.machineinc_path = os.path.join(self.builddir, "conf/machine.inc") - self.testlayer_path = oeSelfTest.testlayer_path - self._extra_tear_down_commands = [] - self._track_for_cleanup = [ - self.testinc_path, self.testinc_bblayers_path, - self.machineinc_path, self.localconf_backup, - self.local_bblayers_backup] - super(oeSelfTest, self).__init__(methodName) - - def setUp(self): - os.chdir(self.builddir) - # Check if local.conf or bblayers.conf files backup exists - # from a previous failed test and restore them - if os.path.isfile(self.localconf_backup) or os.path.isfile( - self.local_bblayers_backup): - self.log.debug("Found a local.conf and/or bblayers.conf backup \ -from a previously aborted test. Restoring these files now, but tests should \ -be re-executed from a clean environment to ensure accurate results.") - try: - shutil.copyfile(self.localconf_backup, self.localconf_path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - try: - shutil.copyfile(self.local_bblayers_backup, - self.local_bblayers_path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - else: - # backup local.conf and bblayers.conf - shutil.copyfile(self.localconf_path, self.localconf_backup) - shutil.copyfile(self.local_bblayers_path, - self.local_bblayers_backup) - self.log.debug("Creating local.conf and bblayers.conf backups.") - # we don't know what the previous test left around in config or inc files - # if it failed so we need a fresh start - try: - os.remove(self.testinc_path) - except OSError as e: - if e.errno != errno.ENOENT: - raise - for root, _, files in os.walk(self.testlayer_path): - for f in files: - if f == 'test_recipe.inc': - os.remove(os.path.join(root, f)) - - for incl_file in [self.testinc_bblayers_path, self.machineinc_path]: - try: - os.remove(incl_file) - except OSError as e: - if e.errno != errno.ENOENT: - raise - - # Get CUSTOMMACHINE from env (set by --machine argument to oe-selftest) - custommachine = os.getenv('CUSTOMMACHINE') - if custommachine: - if custommachine == 'random': - machine = get_random_machine() - else: - machine = custommachine - machine_conf = 'MACHINE ??= "%s"\n' % machine - self.set_machine_config(machine_conf) - print('MACHINE: %s' % machine) - - # tests might need their own setup - # but if they overwrite this one they have to call - # super each time, so let's give them an alternative - self.setUpLocal() - - def setUpLocal(self): - pass - - def tearDown(self): - if self._extra_tear_down_commands: - failed_extra_commands = [] - for command in self._extra_tear_down_commands: - result = runCmd(command, ignore_status=True) - if not result.status == 0: - failed_extra_commands.append(command) - if failed_extra_commands: - self.log.warning("tearDown commands have failed: %s" % ', '.join(map(str, failed_extra_commands))) - self.log.debug("Trying to move on.") - self._extra_tear_down_commands = [] - - if self._track_for_cleanup: - for path in self._track_for_cleanup: - if os.path.isdir(path): - shutil.rmtree(path) - if os.path.isfile(path): - os.remove(path) - self._track_for_cleanup = [] - - self.tearDownLocal() - - def tearDownLocal(self): - pass - - # add test specific commands to the tearDown method. - def add_command_to_tearDown(self, command): - self.log.debug("Adding command '%s' to tearDown for this test." % command) - self._extra_tear_down_commands.append(command) - # add test specific files or directories to be removed in the tearDown method - def track_for_cleanup(self, path): - self.log.debug("Adding path '%s' to be cleaned up when test is over" % path) - self._track_for_cleanup.append(path) - - # write to <builddir>/conf/selftest.inc - def write_config(self, data): - self.log.debug("Writing to: %s\n%s\n" % (self.testinc_path, data)) - ftools.write_file(self.testinc_path, data) - - custommachine = os.getenv('CUSTOMMACHINE') - if custommachine and 'MACHINE' in data: - machine = get_bb_var('MACHINE') - self.log.warning('MACHINE overridden: %s' % machine) - - # append to <builddir>/conf/selftest.inc - def append_config(self, data): - self.log.debug("Appending to: %s\n%s\n" % (self.testinc_path, data)) - ftools.append_file(self.testinc_path, data) - - custommachine = os.getenv('CUSTOMMACHINE') - if custommachine and 'MACHINE' in data: - machine = get_bb_var('MACHINE') - self.log.warning('MACHINE overridden: %s' % machine) - - # remove data from <builddir>/conf/selftest.inc - def remove_config(self, data): - self.log.debug("Removing from: %s\n%s\n" % (self.testinc_path, data)) - ftools.remove_from_file(self.testinc_path, data) - - # write to meta-sefltest/recipes-test/<recipe>/test_recipe.inc - def write_recipeinc(self, recipe, data): - inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc') - self.log.debug("Writing to: %s\n%s\n" % (inc_file, data)) - ftools.write_file(inc_file, data) - - # append data to meta-sefltest/recipes-test/<recipe>/test_recipe.inc - def append_recipeinc(self, recipe, data): - inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc') - self.log.debug("Appending to: %s\n%s\n" % (inc_file, data)) - ftools.append_file(inc_file, data) - - # remove data from meta-sefltest/recipes-test/<recipe>/test_recipe.inc - def remove_recipeinc(self, recipe, data): - inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc') - self.log.debug("Removing from: %s\n%s\n" % (inc_file, data)) - ftools.remove_from_file(inc_file, data) - - # delete meta-sefltest/recipes-test/<recipe>/test_recipe.inc file - def delete_recipeinc(self, recipe): - inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc') - self.log.debug("Deleting file: %s" % inc_file) - try: - os.remove(inc_file) - except OSError as e: - if e.errno != errno.ENOENT: - raise - - # write to <builddir>/conf/bblayers.inc - def write_bblayers_config(self, data): - self.log.debug("Writing to: %s\n%s\n" % (self.testinc_bblayers_path, data)) - ftools.write_file(self.testinc_bblayers_path, data) - - # append to <builddir>/conf/bblayers.inc - def append_bblayers_config(self, data): - self.log.debug("Appending to: %s\n%s\n" % (self.testinc_bblayers_path, data)) - ftools.append_file(self.testinc_bblayers_path, data) - - # remove data from <builddir>/conf/bblayers.inc - def remove_bblayers_config(self, data): - self.log.debug("Removing from: %s\n%s\n" % (self.testinc_bblayers_path, data)) - ftools.remove_from_file(self.testinc_bblayers_path, data) - - # write to <builddir>/conf/machine.inc - def set_machine_config(self, data): - self.log.debug("Writing to: %s\n%s\n" % (self.machineinc_path, data)) - ftools.write_file(self.machineinc_path, data) - - -def get_available_machines(): - # Get a list of all available machines - bbpath = get_bb_var('BBPATH').split(':') - machines = [] - - for path in bbpath: - found_machines = glob.glob(os.path.join(path, 'conf', 'machine', '*.conf')) - if found_machines: - for i in found_machines: - # eg: '/home/<user>/poky/meta-intel/conf/machine/intel-core2-32.conf' - machines.append(os.path.splitext(os.path.basename(i))[0]) - - return machines - - -def get_random_machine(): - # Get a random machine - return choice(get_available_machines()) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py new file mode 100644 index 000000000..e09915b49 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py @@ -0,0 +1,278 @@ +# Copyright (C) 2013-2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import sys +import os +import shutil +import glob +import errno +from unittest.util import safe_repr + +import oeqa.utils.ftools as ftools +from oeqa.utils.commands import runCmd, bitbake, get_bb_var +from oeqa.core.case import OETestCase + +class OESelftestTestCase(OETestCase): + def __init__(self, methodName="runTest"): + self._extra_tear_down_commands = [] + super(OESelftestTestCase, self).__init__(methodName) + + @classmethod + def setUpClass(cls): + super(OESelftestTestCase, cls).setUpClass() + + cls.testlayer_path = cls.tc.config_paths['testlayer_path'] + cls.builddir = cls.tc.config_paths['builddir'] + + cls.localconf_path = cls.tc.config_paths['localconf'] + cls.localconf_backup = cls.tc.config_paths['localconf_class_backup'] + cls.local_bblayers_path = cls.tc.config_paths['bblayers'] + cls.local_bblayers_backup = cls.tc.config_paths['bblayers_class_backup'] + + cls.testinc_path = os.path.join(cls.tc.config_paths['builddir'], + "conf/selftest.inc") + cls.testinc_bblayers_path = os.path.join(cls.tc.config_paths['builddir'], + "conf/bblayers.inc") + cls.machineinc_path = os.path.join(cls.tc.config_paths['builddir'], + "conf/machine.inc") + + cls._track_for_cleanup = [ + cls.testinc_path, cls.testinc_bblayers_path, + cls.machineinc_path, cls.localconf_backup, + cls.local_bblayers_backup] + + cls.add_include() + + @classmethod + def tearDownClass(cls): + cls.remove_include() + cls.remove_inc_files() + super(OESelftestTestCase, cls).tearDownClass() + + @classmethod + def add_include(cls): + if "#include added by oe-selftest" \ + not in ftools.read_file(os.path.join(cls.builddir, "conf/local.conf")): + cls.logger.info("Adding: \"include selftest.inc\" in %s" % os.path.join(cls.builddir, "conf/local.conf")) + ftools.append_file(os.path.join(cls.builddir, "conf/local.conf"), \ + "\n#include added by oe-selftest\ninclude machine.inc\ninclude selftest.inc") + + if "#include added by oe-selftest" \ + not in ftools.read_file(os.path.join(cls.builddir, "conf/bblayers.conf")): + cls.logger.info("Adding: \"include bblayers.inc\" in bblayers.conf") + ftools.append_file(os.path.join(cls.builddir, "conf/bblayers.conf"), \ + "\n#include added by oe-selftest\ninclude bblayers.inc") + + @classmethod + def remove_include(cls): + if "#include added by oe-selftest.py" \ + in ftools.read_file(os.path.join(cls.builddir, "conf/local.conf")): + cls.logger.info("Removing the include from local.conf") + ftools.remove_from_file(os.path.join(cls.builddir, "conf/local.conf"), \ + "\n#include added by oe-selftest.py\ninclude machine.inc\ninclude selftest.inc") + + if "#include added by oe-selftest.py" \ + in ftools.read_file(os.path.join(cls.builddir, "conf/bblayers.conf")): + cls.logger.info("Removing the include from bblayers.conf") + ftools.remove_from_file(os.path.join(cls.builddir, "conf/bblayers.conf"), \ + "\n#include added by oe-selftest.py\ninclude bblayers.inc") + + @classmethod + def remove_inc_files(cls): + try: + os.remove(os.path.join(cls.builddir, "conf/selftest.inc")) + for root, _, files in os.walk(cls.testlayer_path): + for f in files: + if f == 'test_recipe.inc': + os.remove(os.path.join(root, f)) + except OSError as e: + pass + + for incl_file in ['conf/bblayers.inc', 'conf/machine.inc']: + try: + os.remove(os.path.join(cls.builddir, incl_file)) + except: + pass + + def setUp(self): + super(OESelftestTestCase, self).setUp() + os.chdir(self.builddir) + # Check if local.conf or bblayers.conf files backup exists + # from a previous failed test and restore them + if os.path.isfile(self.localconf_backup) or os.path.isfile( + self.local_bblayers_backup): + self.logger.debug("\ +Found a local.conf and/or bblayers.conf backup from a previously aborted test.\ +Restoring these files now, but tests should be re-executed from a clean environment\ +to ensure accurate results.") + try: + shutil.copyfile(self.localconf_backup, self.localconf_path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + try: + shutil.copyfile(self.local_bblayers_backup, + self.local_bblayers_path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + else: + # backup local.conf and bblayers.conf + shutil.copyfile(self.localconf_path, self.localconf_backup) + shutil.copyfile(self.local_bblayers_path, self.local_bblayers_backup) + self.logger.debug("Creating local.conf and bblayers.conf backups.") + # we don't know what the previous test left around in config or inc files + # if it failed so we need a fresh start + try: + os.remove(self.testinc_path) + except OSError as e: + if e.errno != errno.ENOENT: + raise + for root, _, files in os.walk(self.testlayer_path): + for f in files: + if f == 'test_recipe.inc': + os.remove(os.path.join(root, f)) + + for incl_file in [self.testinc_bblayers_path, self.machineinc_path]: + try: + os.remove(incl_file) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + if self.tc.custommachine: + machine_conf = 'MACHINE ??= "%s"\n' % self.tc.custommachine + self.set_machine_config(machine_conf) + + # tests might need their own setup + # but if they overwrite this one they have to call + # super each time, so let's give them an alternative + self.setUpLocal() + + def setUpLocal(self): + pass + + def tearDown(self): + if self._extra_tear_down_commands: + failed_extra_commands = [] + for command in self._extra_tear_down_commands: + result = runCmd(command, ignore_status=True) + if not result.status == 0: + failed_extra_commands.append(command) + if failed_extra_commands: + self.logger.warning("tearDown commands have failed: %s" % ', '.join(map(str, failed_extra_commands))) + self.logger.debug("Trying to move on.") + self._extra_tear_down_commands = [] + + if self._track_for_cleanup: + for path in self._track_for_cleanup: + if os.path.isdir(path): + shutil.rmtree(path) + if os.path.isfile(path): + os.remove(path) + self._track_for_cleanup = [] + + self.tearDownLocal() + super(OESelftestTestCase, self).tearDown() + + def tearDownLocal(self): + pass + + def add_command_to_tearDown(self, command): + """Add test specific commands to the tearDown method""" + self.logger.debug("Adding command '%s' to tearDown for this test." % command) + self._extra_tear_down_commands.append(command) + + def track_for_cleanup(self, path): + """Add test specific files or directories to be removed in the tearDown method""" + self.logger.debug("Adding path '%s' to be cleaned up when test is over" % path) + self._track_for_cleanup.append(path) + + def write_config(self, data): + """Write to <builddir>/conf/selftest.inc""" + + self.logger.debug("Writing to: %s\n%s\n" % (self.testinc_path, data)) + ftools.write_file(self.testinc_path, data) + + if self.tc.custommachine and 'MACHINE' in data: + machine = get_bb_var('MACHINE') + self.logger.warning('MACHINE overridden: %s' % machine) + + def append_config(self, data): + """Append to <builddir>/conf/selftest.inc""" + self.logger.debug("Appending to: %s\n%s\n" % (self.testinc_path, data)) + ftools.append_file(self.testinc_path, data) + + if self.tc.custommachine and 'MACHINE' in data: + machine = get_bb_var('MACHINE') + self.logger.warning('MACHINE overridden: %s' % machine) + + def remove_config(self, data): + """Remove data from <builddir>/conf/selftest.inc""" + self.logger.debug("Removing from: %s\n%s\n" % (self.testinc_path, data)) + ftools.remove_from_file(self.testinc_path, data) + + def recipeinc(self, recipe): + """Return absolute path of meta-sefltest/recipes-test/<recipe>/test_recipe.inc""" + return os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc') + + def write_recipeinc(self, recipe, data): + """Write to meta-sefltest/recipes-test/<recipe>/test_recipe.inc""" + inc_file = self.recipeinc(recipe) + self.logger.debug("Writing to: %s\n%s\n" % (inc_file, data)) + ftools.write_file(inc_file, data) + return inc_file + + def append_recipeinc(self, recipe, data): + """Append data to meta-sefltest/recipes-test/<recipe>/test_recipe.inc""" + inc_file = self.recipeinc(recipe) + self.logger.debug("Appending to: %s\n%s\n" % (inc_file, data)) + ftools.append_file(inc_file, data) + return inc_file + + def remove_recipeinc(self, recipe, data): + """Remove data from meta-sefltest/recipes-test/<recipe>/test_recipe.inc""" + inc_file = self.recipeinc(recipe) + self.logger.debug("Removing from: %s\n%s\n" % (inc_file, data)) + ftools.remove_from_file(inc_file, data) + + def delete_recipeinc(self, recipe): + """Delete meta-sefltest/recipes-test/<recipe>/test_recipe.inc file""" + inc_file = self.recipeinc(recipe) + self.logger.debug("Deleting file: %s" % inc_file) + try: + os.remove(inc_file) + except OSError as e: + if e.errno != errno.ENOENT: + raise + def write_bblayers_config(self, data): + """Write to <builddir>/conf/bblayers.inc""" + self.logger.debug("Writing to: %s\n%s\n" % (self.testinc_bblayers_path, data)) + ftools.write_file(self.testinc_bblayers_path, data) + + def append_bblayers_config(self, data): + """Append to <builddir>/conf/bblayers.inc""" + self.logger.debug("Appending to: %s\n%s\n" % (self.testinc_bblayers_path, data)) + ftools.append_file(self.testinc_bblayers_path, data) + + def remove_bblayers_config(self, data): + """Remove data from <builddir>/conf/bblayers.inc""" + self.logger.debug("Removing from: %s\n%s\n" % (self.testinc_bblayers_path, data)) + ftools.remove_from_file(self.testinc_bblayers_path, data) + + def set_machine_config(self, data): + """Write to <builddir>/conf/machine.inc""" + self.logger.debug("Writing to: %s\n%s\n" % (self.machineinc_path, data)) + ftools.write_file(self.machineinc_path, data) + + # check does path exist + def assertExists(self, expr, msg=None): + if not os.path.exists(expr): + msg = self._formatMessage(msg, "%s does not exist" % safe_repr(expr)) + raise self.failureException(msg) + + # check does path not exist + def assertNotExists(self, expr, msg=None): + if os.path.exists(expr): + msg = self._formatMessage(msg, "%s exists when it should not" % safe_repr(expr)) + raise self.failureException(msg) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_sstatetests_noauto.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/_sstatetests_noauto.py index fc9ae7efb..0e5896234 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_sstatetests_noauto.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/_sstatetests_noauto.py @@ -1,19 +1,16 @@ -import datetime -import unittest import os -import re import shutil import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer -from oeqa.selftest.sstate import SStateBase +from oeqa.selftest.cases.sstate import SStateBase class RebuildFromSState(SStateBase): @classmethod def setUpClass(self): + super(RebuildFromSState, self).setUpClass() self.builddir = os.path.join(os.environ.get('BUILDDIR')) def get_dep_targets(self, primary_targets): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/archiver.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/archiver.py index 7f01c36d4..f61a52201 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/archiver.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/archiver.py @@ -1,14 +1,12 @@ -from oeqa.selftest.base import oeSelfTest -from oeqa.utils.commands import bitbake, get_bb_vars -from oeqa.utils.decorators import testcase -import glob import os -import shutil - +import glob +from oeqa.utils.commands import bitbake, get_bb_vars +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID -class Archiver(oeSelfTest): +class Archiver(OESelftestTestCase): - @testcase(1345) + @OETestID(1345) def test_archiver_allows_to_filter_on_recipe_name(self): """ Summary: The archiver should offer the possibility to filter on the recipe. (#6929) @@ -42,7 +40,7 @@ class Archiver(oeSelfTest): excluded_present = len(glob.glob(src_path + '/%s-*' % exclude_recipe)) self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % exclude_recipe) - + @OETestID(1900) def test_archiver_filters_by_type(self): """ Summary: The archiver is documented to filter on the recipe type. @@ -75,6 +73,7 @@ class Archiver(oeSelfTest): excluded_present = len(glob.glob(src_path_native + '/%s-*' % native_recipe)) self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipe) + @OETestID(1901) def test_archiver_filters_by_type_and_name(self): """ Summary: Test that the archiver archives by recipe type, taking the diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bblayers.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bblayers.py index cd658c5d4..90a2249b0 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bblayers.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bblayers.py @@ -1,39 +1,37 @@ -import unittest import os -import logging import re -import shutil import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest from oeqa.utils.commands import runCmd, get_bb_var -from oeqa.utils.decorators import testcase -class BitbakeLayers(oeSelfTest): +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID - @testcase(756) +class BitbakeLayers(OESelftestTestCase): + + @OETestID(756) def test_bitbakelayers_showcrossdepends(self): result = runCmd('bitbake-layers show-cross-depends') self.assertTrue('aspell' in result.output, msg = "No dependencies were shown. bitbake-layers show-cross-depends output: %s" % result.output) - @testcase(83) + @OETestID(83) def test_bitbakelayers_showlayers(self): result = runCmd('bitbake-layers show-layers') self.assertTrue('meta-selftest' in result.output, msg = "No layers were shown. bitbake-layers show-layers output: %s" % result.output) - @testcase(93) + @OETestID(93) def test_bitbakelayers_showappends(self): recipe = "xcursor-transparent-theme" bb_file = self.get_recipe_basename(recipe) result = runCmd('bitbake-layers show-appends') self.assertTrue(bb_file in result.output, msg="%s file was not recognised. bitbake-layers show-appends output: %s" % (bb_file, result.output)) - @testcase(90) + @OETestID(90) def test_bitbakelayers_showoverlayed(self): result = runCmd('bitbake-layers show-overlayed') self.assertTrue('aspell' in result.output, msg="aspell overlayed recipe was not recognised bitbake-layers show-overlayed %s" % result.output) - @testcase(95) + @OETestID(95) def test_bitbakelayers_flatten(self): recipe = "xcursor-transparent-theme" recipe_path = "recipes-graphics/xcursor-transparent-theme" @@ -48,7 +46,7 @@ class BitbakeLayers(oeSelfTest): find_in_contents = re.search("##### bbappended from meta-selftest #####\n(.*\n)*include test_recipe.inc", contents) self.assertTrue(find_in_contents, msg = "Flattening layers did not work. bitbake-layers flatten output: %s" % result.output) - @testcase(1195) + @OETestID(1195) def test_bitbakelayers_add_remove(self): test_layer = os.path.join(get_bb_var('COREBASE'), 'meta-skeleton') result = runCmd('bitbake-layers show-layers') @@ -66,7 +64,7 @@ class BitbakeLayers(oeSelfTest): result = runCmd('bitbake-layers show-layers') self.assertNotIn('meta-skeleton', result.output, msg = "meta-skeleton should have been removed at this step. bitbake-layers show-layers output: %s" % result.output) - @testcase(1384) + @OETestID(1384) def test_bitbakelayers_showrecipes(self): result = runCmd('bitbake-layers show-recipes') self.assertIn('aspell:', result.output) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bbtests.py index 46e09f509..4c8204903 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bbtests.py @@ -2,30 +2,31 @@ import os import re import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars -from oeqa.utils.decorators import testcase -class BitbakeTests(oeSelfTest): +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID + +class BitbakeTests(OESelftestTestCase): def getline(self, res, line): for l in res.output.split('\n'): if line in l: return l - @testcase(789) + @OETestID(789) def test_run_bitbake_from_dir_1(self): os.chdir(os.path.join(self.builddir, 'conf')) self.assertEqual(bitbake('-e').status, 0, msg = "bitbake couldn't run from \"conf\" dir") - @testcase(790) + @OETestID(790) def test_run_bitbake_from_dir_2(self): my_env = os.environ.copy() my_env['BBPATH'] = my_env['BUILDDIR'] os.chdir(os.path.dirname(os.environ['BUILDDIR'])) self.assertEqual(bitbake('-e', env=my_env).status, 0, msg = "bitbake couldn't run from builddir") - @testcase(806) + @OETestID(806) def test_event_handler(self): self.write_config("INHERIT += \"test_events\"") result = bitbake('m4-native') @@ -35,7 +36,7 @@ class BitbakeTests(oeSelfTest): self.assertTrue(find_build_completed, msg = "Match failed in:\n%s" % result.output) self.assertFalse('Test for bb.event.InvalidEvent' in result.output, msg = "\"Test for bb.event.InvalidEvent\" message found during bitbake process. bitbake output: %s" % result.output) - @testcase(103) + @OETestID(103) def test_local_sstate(self): bitbake('m4-native') bitbake('m4-native -cclean') @@ -43,17 +44,17 @@ class BitbakeTests(oeSelfTest): find_setscene = re.search("m4-native.*do_.*_setscene", result.output) self.assertTrue(find_setscene, msg = "No \"m4-native.*do_.*_setscene\" message found during bitbake m4-native. bitbake output: %s" % result.output ) - @testcase(105) + @OETestID(105) def test_bitbake_invalid_recipe(self): result = bitbake('-b asdf', ignore_status=True) self.assertTrue("ERROR: Unable to find any recipe file matching 'asdf'" in result.output, msg = "Though asdf recipe doesn't exist, bitbake didn't output any err. message. bitbake output: %s" % result.output) - @testcase(107) + @OETestID(107) def test_bitbake_invalid_target(self): result = bitbake('asdf', ignore_status=True) self.assertTrue("ERROR: Nothing PROVIDES 'asdf'" in result.output, msg = "Though no 'asdf' target exists, bitbake didn't output any err. message. bitbake output: %s" % result.output) - @testcase(106) + @OETestID(106) def test_warnings_errors(self): result = bitbake('-b asdf', ignore_status=True) find_warnings = re.search("Summary: There w.{2,3}? [1-9][0-9]* WARNING messages* shown", result.output) @@ -61,7 +62,7 @@ class BitbakeTests(oeSelfTest): self.assertTrue(find_warnings, msg="Did not find the mumber of warnings at the end of the build:\n" + result.output) self.assertTrue(find_errors, msg="Did not find the mumber of errors at the end of the build:\n" + result.output) - @testcase(108) + @OETestID(108) def test_invalid_patch(self): # This patch already exists in SRC_URI so adding it again will cause the # patch to fail. @@ -73,7 +74,7 @@ class BitbakeTests(oeSelfTest): line = self.getline(result, "Function failed: patch_do_patch") self.assertTrue(line and line.startswith("ERROR:"), msg = "Repeated patch application didn't fail. bitbake output: %s" % result.output) - @testcase(1354) + @OETestID(1354) def test_force_task_1(self): # test 1 from bug 5875 test_recipe = 'zlib' @@ -98,7 +99,7 @@ class BitbakeTests(oeSelfTest): ret = bitbake(test_recipe) self.assertIn('task do_package_write_rpm:', ret.output, 'Task do_package_write_rpm did not re-executed.') - @testcase(163) + @OETestID(163) def test_force_task_2(self): # test 2 from bug 5875 test_recipe = 'zlib' @@ -111,7 +112,7 @@ class BitbakeTests(oeSelfTest): for task in look_for_tasks: self.assertIn(task, result.output, msg="Couldn't find %s task.") - @testcase(167) + @OETestID(167) def test_bitbake_g(self): result = bitbake('-g core-image-minimal') for f in ['pn-buildlist', 'recipe-depends.dot', 'task-depends.dot']: @@ -119,7 +120,7 @@ class BitbakeTests(oeSelfTest): self.assertTrue('Task dependencies saved to \'task-depends.dot\'' in result.output, msg = "No task dependency \"task-depends.dot\" file was generated for the given task target. bitbake output: %s" % result.output) self.assertTrue('busybox' in ftools.read_file(os.path.join(self.builddir, 'task-depends.dot')), msg = "No \"busybox\" dependency found in task-depends.dot file.") - @testcase(899) + @OETestID(899) def test_image_manifest(self): bitbake('core-image-minimal') bb_vars = get_bb_vars(["DEPLOY_DIR_IMAGE", "IMAGE_LINK_NAME"], "core-image-minimal") @@ -128,7 +129,7 @@ class BitbakeTests(oeSelfTest): manifest = os.path.join(deploydir, imagename + ".manifest") self.assertTrue(os.path.islink(manifest), msg="No manifest file created for image. It should have been created in %s" % manifest) - @testcase(168) + @OETestID(168) def test_invalid_recipe_src_uri(self): data = 'SRC_URI = "file://invalid"' self.write_recipeinc('man', data) @@ -149,7 +150,7 @@ doesn't exist, yet no error message encountered. bitbake output: %s" % result.ou self.assertTrue(line and line.startswith("ERROR:"), msg = "\"invalid\" file \ doesn't exist, yet fetcher didn't report any error. bitbake output: %s" % result.output) - @testcase(171) + @OETestID(171) def test_rename_downloaded_file(self): # TODO unique dldir instead of using cleanall # TODO: need to set sstatedir? @@ -167,29 +168,29 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\" self.assertTrue(os.path.isfile(os.path.join(dl_dir, 'test-aspell.tar.gz')), msg = "File rename failed. No corresponding test-aspell.tar.gz file found under %s" % dl_dir) self.assertTrue(os.path.isfile(os.path.join(dl_dir, 'test-aspell.tar.gz.done')), "File rename failed. No corresponding test-aspell.tar.gz.done file found under %s" % dl_dir) - @testcase(1028) + @OETestID(1028) def test_environment(self): self.write_config("TEST_ENV=\"localconf\"") result = runCmd('bitbake -e | grep TEST_ENV=') self.assertTrue('localconf' in result.output, msg = "bitbake didn't report any value for TEST_ENV variable. To test, run 'bitbake -e | grep TEST_ENV='") - @testcase(1029) + @OETestID(1029) def test_dry_run(self): result = runCmd('bitbake -n m4-native') self.assertEqual(0, result.status, "bitbake dry run didn't run as expected. %s" % result.output) - @testcase(1030) + @OETestID(1030) def test_just_parse(self): result = runCmd('bitbake -p') self.assertEqual(0, result.status, "errors encountered when parsing recipes. %s" % result.output) - @testcase(1031) + @OETestID(1031) def test_version(self): result = runCmd('bitbake -s | grep wget') find = re.search("wget *:([0-9a-zA-Z\.\-]+)", result.output) self.assertTrue(find, "No version returned for searched recipe. bitbake output: %s" % result.output) - @testcase(1032) + @OETestID(1032) def test_prefile(self): preconf = os.path.join(self.builddir, 'conf/prefile.conf') self.track_for_cleanup(preconf) @@ -200,7 +201,7 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\" result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') self.assertTrue('localconf' in result.output, "Preconfigure file \"prefile.conf\"was not taken into consideration.") - @testcase(1033) + @OETestID(1033) def test_postfile(self): postconf = os.path.join(self.builddir, 'conf/postfile.conf') self.track_for_cleanup(postconf) @@ -209,12 +210,12 @@ SSTATE_DIR = \"${TOPDIR}/download-selftest\" result = runCmd('bitbake -R conf/postfile.conf -e | grep TEST_POSTFILE=') self.assertTrue('postfile' in result.output, "Postconfigure file \"postfile.conf\"was not taken into consideration.") - @testcase(1034) + @OETestID(1034) def test_checkuri(self): result = runCmd('bitbake -c checkuri m4') self.assertEqual(0, result.status, msg = "\"checkuri\" task was not executed. bitbake output: %s" % result.output) - @testcase(1035) + @OETestID(1035) def test_continue(self): self.write_config("""DL_DIR = \"${TOPDIR}/download-selftest\" SSTATE_DIR = \"${TOPDIR}/download-selftest\" @@ -229,7 +230,7 @@ INHERIT_remove = \"report-error\" continuepos = result.output.find('NOTE: recipe xcursor-transparent-theme-%s: task do_unpack: Started' % manver.group(1)) self.assertLess(errorpos,continuepos, msg = "bitbake didn't pass do_fail_task. bitbake output: %s" % result.output) - @testcase(1119) + @OETestID(1119) def test_non_gplv3(self): self.write_config('INCOMPATIBLE_LICENSE = "GPLv3"') result = bitbake('selftest-ed', ignore_status=True) @@ -238,7 +239,7 @@ INHERIT_remove = \"report-error\" self.assertFalse(os.path.isfile(os.path.join(lic_dir, 'selftest-ed/generic_GPLv3'))) self.assertTrue(os.path.isfile(os.path.join(lic_dir, 'selftest-ed/generic_GPLv2'))) - @testcase(1422) + @OETestID(1422) def test_setscene_only(self): """ Bitbake option to restore from sstate only within a build (i.e. execute no real tasks, only setscene)""" test_recipe = 'ed' @@ -253,7 +254,7 @@ INHERIT_remove = \"report-error\" self.assertIn('_setscene', task, 'A task different from _setscene ran: %s.\n' 'Executed tasks were: %s' % (task, str(tasks))) - @testcase(1425) + @OETestID(1425) def test_bbappend_order(self): """ Bitbake should bbappend to recipe in a predictable order """ test_recipe = 'ed' diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildhistory.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildhistory.py index 008c39c95..06792d914 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildhistory.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildhistory.py @@ -2,12 +2,11 @@ import os import re import datetime -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import bitbake, get_bb_vars -from oeqa.utils.decorators import testcase -class BuildhistoryBase(oeSelfTest): +class BuildhistoryBase(OESelftestTestCase): def config_buildhistory(self, tmp_bh_location=False): bb_vars = get_bb_vars(['USER_CLASSES', 'INHERIT']) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildoptions.py index a6e0203f5..cf221c33a 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildoptions.py @@ -3,15 +3,15 @@ import re import glob as g import shutil import tempfile -from oeqa.selftest.base import oeSelfTest -from oeqa.selftest.buildhistory import BuildhistoryBase +from oeqa.selftest.case import OESelftestTestCase +from oeqa.selftest.cases.buildhistory import BuildhistoryBase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars import oeqa.utils.ftools as ftools -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class ImageOptionsTests(oeSelfTest): +class ImageOptionsTests(OESelftestTestCase): - @testcase(761) + @OETestID(761) def test_incremental_image_generation(self): image_pkgtype = get_bb_var("IMAGE_PKGTYPE") if image_pkgtype != 'rpm': @@ -22,15 +22,15 @@ class ImageOptionsTests(oeSelfTest): bitbake("core-image-minimal") 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("Installing : packagegroup-core-ssh-openssh", log_data_created) + incremental_created = re.search(r"Installing\s*:\s*packagegroup-core-ssh-openssh", log_data_created) self.remove_config('IMAGE_FEATURES += "ssh-server-openssh"') self.assertTrue(incremental_created, msg = "Match failed in:\n%s" % log_data_created) bitbake("core-image-minimal") log_data_removed = ftools.read_file(log_data_file) - incremental_removed = re.search("Erasing : packagegroup-core-ssh-openssh", log_data_removed) + incremental_removed = re.search(r"Erasing\s*:\s*packagegroup-core-ssh-openssh", log_data_removed) self.assertTrue(incremental_removed, msg = "Match failed in:\n%s" % log_data_removed) - @testcase(286) + @OETestID(286) def test_ccache_tool(self): bitbake("ccache-native") bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'ccache-native') @@ -43,7 +43,7 @@ class ImageOptionsTests(oeSelfTest): res = runCmd("grep ccache %s" % log_compile, ignore_status=True) self.assertEqual(0, res.status, msg="No match for ccache in m4 log.do_compile. For further details: %s" % log_compile) - @testcase(1435) + @OETestID(1435) def test_read_only_image(self): distro_features = get_bb_var('DISTRO_FEATURES') if not ('x11' in distro_features and 'opengl' in distro_features): @@ -52,9 +52,9 @@ class ImageOptionsTests(oeSelfTest): bitbake("core-image-sato") # do_image will fail if there are any pending postinsts -class DiskMonTest(oeSelfTest): +class DiskMonTest(OESelftestTestCase): - @testcase(277) + @OETestID(277) def test_stoptask_behavior(self): self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},100000G,100K"') res = bitbake("m4", ignore_status = True) @@ -68,13 +68,13 @@ class DiskMonTest(oeSelfTest): res = bitbake("m4") self.assertTrue('WARNING: The free space' in res.output, msg = "A warning should have been displayed for disk monitor is set to WARN: %s" %res.output) -class SanityOptionsTest(oeSelfTest): +class SanityOptionsTest(OESelftestTestCase): def getline(self, res, line): for l in res.output.split('\n'): if line in l: return l - @testcase(927) + @OETestID(927) def test_options_warnqa_errorqa_switch(self): self.write_config("INHERIT_remove = \"report-error\"") @@ -96,25 +96,7 @@ class SanityOptionsTest(oeSelfTest): line = self.getline(res, "QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors.") self.assertTrue(line and line.startswith("WARNING:"), msg=res.output) - @testcase(278) - def test_sanity_unsafe_script_references(self): - self.write_config('WARN_QA_append = " unsafe-references-in-scripts"') - - self.add_command_to_tearDown('bitbake -c clean gzip') - res = bitbake("gzip -f -c package_qa") - line = self.getline(res, "QA Issue: gzip") - self.assertFalse(line, "WARNING: QA Issue: gzip message is present in bitbake's output and shouldn't be: %s" % res.output) - - self.append_config(""" -do_install_append_pn-gzip () { - echo "\n${bindir}/test" >> ${D}${bindir}/zcat -} -""") - res = bitbake("gzip -f -c package_qa") - line = self.getline(res, "QA Issue: gzip") - self.assertTrue(line and line.startswith("WARNING:"), "WARNING: QA Issue: gzip message is not present in bitbake's output: %s" % res.output) - - @testcase(1421) + @OETestID(1421) def test_layer_without_git_dir(self): """ Summary: Test that layer git revisions are displayed and do not fail without git repository @@ -156,20 +138,20 @@ do_install_append_pn-gzip () { class BuildhistoryTests(BuildhistoryBase): - @testcase(293) + @OETestID(293) def test_buildhistory_basic(self): self.run_buildhistory_operation('xcursor-transparent-theme') self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR')), "buildhistory dir was not created.") - @testcase(294) + @OETestID(294) def test_buildhistory_buildtime_pr_backwards(self): target = 'xcursor-transparent-theme' error = "ERROR:.*QA Issue: Package version for package %s went backwards which would break package feeds from (.*-r1.* to .*-r0.*)" % target self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True) self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True, error_regex=error) -class ArchiverTest(oeSelfTest): - @testcase(926) +class ArchiverTest(OESelftestTestCase): + @OETestID(926) def test_arch_work_dir_and_export_source(self): """ Test for archiving the work directory and exporting the source files. diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/containerimage.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/containerimage.py index def481f14..99a5cc9e5 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/containerimage.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/containerimage.py @@ -1,7 +1,8 @@ import os -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import bitbake, get_bb_vars, runCmd +from oeqa.core.decorator.oeid import OETestID # This test builds an image with using the "container" IMAGE_FSTYPE, and # ensures that then files in the image are only the ones expected. @@ -16,10 +17,11 @@ from oeqa.utils.commands import bitbake, get_bb_vars, runCmd # of them, but this test is more to catch if other packages get added by # default other than what is in ROOTFS_BOOTSTRAP_INSTALL. # -class ContainerImageTests(oeSelfTest): +class ContainerImageTests(OESelftestTestCase): # Verify that when specifying a IMAGE_TYPEDEP_ of the form "foo.bar" that # the conversion type bar gets added as a dep as well + @OETestID(1619) def test_expected_files(self): def get_each_path_part(path): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/devtool.py index 57048665c..43280cdc0 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/devtool.py @@ -1,6 +1,4 @@ -import unittest import os -import logging import re import shutil import tempfile @@ -8,12 +6,14 @@ import glob import fnmatch import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class DevtoolBase(oeSelfTest): +class DevtoolBase(OESelftestTestCase): + + buffer = True def _test_recipe_contents(self, recipefile, checkvars, checkinherits): with open(recipefile, 'r') as f: @@ -120,6 +120,7 @@ class DevtoolTests(DevtoolBase): @classmethod def setUpClass(cls): + super(DevtoolTests, cls).setUpClass() bb_vars = get_bb_vars(['TOPDIR', 'SSTATE_DIR']) cls.original_sstate = bb_vars['SSTATE_DIR'] cls.devtool_sstate = os.path.join(bb_vars['TOPDIR'], 'sstate_devtool') @@ -129,8 +130,9 @@ class DevtoolTests(DevtoolBase): @classmethod def tearDownClass(cls): - cls.log.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate) + cls.logger.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate) runCmd('rm -rf %s' % cls.devtool_sstate) + super(DevtoolTests, cls).tearDownClass() def setUp(self): """Test case setup function""" @@ -168,7 +170,7 @@ class DevtoolTests(DevtoolBase): if expected_status: self.fail('Missing file changes: %s' % expected_status) - @testcase(1158) + @OETestID(1158) def test_create_workspace(self): # Check preconditions result = runCmd('bitbake-layers show-layers') @@ -189,31 +191,40 @@ class DevtoolTests(DevtoolBase): self.assertNotIn(tempdir, result.output) self.assertIn(self.workspacedir, result.output) - @testcase(1159) + @OETestID(1159) def test_devtool_add(self): # Fetch source tempdir = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir) + pn = 'pv' + pv = '1.5.3' 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') + result = runCmd('tar xfv %s' % os.path.basename(url), cwd=tempdir) + srcdir = os.path.join(tempdir, '%s-%s' % (pn, pv)) 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(self.workspacedir) - self.add_command_to_tearDown('bitbake -c cleansstate pv') + self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn) 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(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + result = runCmd('devtool add %s %s' % (pn, srcdir)) + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') # Test devtool status result = runCmd('devtool status') - self.assertIn('pv', result.output) + recipepath = '%s/recipes/%s/%s_%s.bb' % (self.workspacedir, pn, pn, pv) + self.assertIn(recipepath, result.output) self.assertIn(srcdir, result.output) + # Test devtool find-recipe + result = runCmd('devtool -q find-recipe %s' % pn) + self.assertEqual(recipepath, result.output.strip()) + # Test devtool edit-recipe + result = runCmd('VISUAL="echo 123" devtool -q edit-recipe %s' % pn) + self.assertEqual('123 %s' % recipepath, result.output.strip()) # 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') + bitbake('%s -c cleansstate' % pn) # Test devtool build - result = runCmd('devtool build pv') - bb_vars = get_bb_vars(['D', 'bindir'], 'pv') + result = runCmd('devtool build %s' % pn) + bb_vars = get_bb_vars(['D', 'bindir'], pn) installdir = bb_vars['D'] self.assertTrue(installdir, 'Could not query installdir variable') bindir = bb_vars['bindir'] @@ -222,7 +233,7 @@ class DevtoolTests(DevtoolBase): bindir = bindir[1:] self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D') - @testcase(1423) + @OETestID(1423) def test_devtool_add_git_local(self): # Fetch source from a remote URL, but do it outside of devtool tempdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -242,7 +253,7 @@ class DevtoolTests(DevtoolBase): self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') # Don't specify a name since we should be able to auto-detect it result = runCmd('devtool add %s' % srcdir) - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') # Check the recipe name is correct recipefile = get_bb_var('FILE', pn) self.assertIn('%s_git.bb' % pn, recipefile, 'Recipe file incorrectly named') @@ -262,7 +273,7 @@ class DevtoolTests(DevtoolBase): checkvars['DEPENDS'] = set(['dbus']) self._test_recipe_contents(recipefile, checkvars, []) - @testcase(1162) + @OETestID(1162) def test_devtool_add_library(self): # Fetch source tempdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -277,7 +288,7 @@ class DevtoolTests(DevtoolBase): self.track_for_cleanup(self.workspacedir) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool add libftdi %s -V %s' % (srcdir, version)) - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') # Test devtool status result = runCmd('devtool status') self.assertIn('libftdi', result.output) @@ -311,7 +322,7 @@ class DevtoolTests(DevtoolBase): 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') - @testcase(1160) + @OETestID(1160) def test_devtool_add_fetch(self): # Fetch source tempdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -325,7 +336,7 @@ class DevtoolTests(DevtoolBase): self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url)) - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. %s' % result.output) + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output) self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory') self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created') # Test devtool status @@ -357,7 +368,7 @@ class DevtoolTests(DevtoolBase): checkvars['SRC_URI'] = url self._test_recipe_contents(recipefile, checkvars, []) - @testcase(1161) + @OETestID(1161) def test_devtool_add_fetch_git(self): tempdir = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir) @@ -370,7 +381,7 @@ class DevtoolTests(DevtoolBase): self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool add %s %s -a -f %s' % (testrecipe, srcdir, url)) - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created: %s' % result.output) + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created: %s' % result.output) self.assertTrue(os.path.isfile(os.path.join(srcdir, 'imraa', 'imraa.c')), 'Unable to find imraa/imraa.c in source directory') # Test devtool status result = runCmd('devtool status') @@ -405,7 +416,7 @@ class DevtoolTests(DevtoolBase): checkvars['SRCREV'] = checkrev self._test_recipe_contents(recipefile, checkvars, []) - @testcase(1391) + @OETestID(1391) def test_devtool_add_fetch_simple(self): # Fetch source from a remote URL, auto-detecting name tempdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -418,7 +429,7 @@ class DevtoolTests(DevtoolBase): self.track_for_cleanup(self.workspacedir) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool add %s' % url) - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. %s' % result.output) + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output) self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory') self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created') # Test devtool status @@ -433,7 +444,7 @@ class DevtoolTests(DevtoolBase): checkvars['SRC_URI'] = url.replace(testver, '${PV}') self._test_recipe_contents(recipefile, checkvars, []) - @testcase(1164) + @OETestID(1164) def test_devtool_modify(self): import oe.path @@ -443,8 +454,8 @@ class DevtoolTests(DevtoolBase): 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.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'mdadm_*.bbappend')) self.assertTrue(matches, 'bbappend not created %s' % result.output) @@ -491,13 +502,14 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool status') self.assertNotIn('mdadm', result.output) + @OETestID(1620) def test_devtool_buildclean(self): def assertFile(path, *paths): f = os.path.join(path, *paths) - self.assertTrue(os.path.exists(f), "%r does not exist" % f) + self.assertExists(f) def assertNoFile(path, *paths): f = os.path.join(path, *paths) - self.assertFalse(os.path.exists(os.path.join(f)), "%r exists" % f) + self.assertNotExists(f) # Clean up anything in the workdir/sysroot/sstate cache bitbake('mdadm m4 -c cleansstate') @@ -537,7 +549,7 @@ class DevtoolTests(DevtoolBase): finally: self.delete_recipeinc('m4') - @testcase(1166) + @OETestID(1166) def test_devtool_modify_invalid(self): # Try modifying some recipes tempdir = tempfile.mkdtemp(prefix='devtoolqa') @@ -566,7 +578,7 @@ class DevtoolTests(DevtoolBase): self.assertNotEqual(result.status, 0, 'devtool modify on %s should have failed. devtool output: %s' % (testrecipe, result.output)) self.assertIn('ERROR: ', result.output, 'devtool modify on %s should have given an ERROR' % testrecipe) - @testcase(1365) + @OETestID(1365) def test_devtool_modify_native(self): # Check preconditions self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') @@ -596,7 +608,7 @@ class DevtoolTests(DevtoolBase): self.assertTrue(inheritnative, 'None of these recipes do "inherit native" - need to adjust testrecipes list: %s' % ', '.join(testrecipes)) - @testcase(1165) + @OETestID(1165) def test_devtool_modify_git(self): # Check preconditions testrecipe = 'mkelfimage' @@ -611,8 +623,8 @@ class DevtoolTests(DevtoolBase): 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.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. devtool output: %s' % result.output) + self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output) matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'mkelfimage_*.bbappend')) self.assertTrue(matches, 'bbappend not created') # Test devtool status @@ -624,7 +636,7 @@ class DevtoolTests(DevtoolBase): # Try building bitbake(testrecipe) - @testcase(1167) + @OETestID(1167) def test_devtool_modify_localfiles(self): # Check preconditions testrecipe = 'lighttpd' @@ -644,8 +656,8 @@ class DevtoolTests(DevtoolBase): 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(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + self.assertExists(os.path.join(tempdir, 'configure.ac'), 'Extracted source could not be found') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe)) self.assertTrue(matches, 'bbappend not created') # Test devtool status @@ -655,7 +667,7 @@ class DevtoolTests(DevtoolBase): # Try building bitbake(testrecipe) - @testcase(1378) + @OETestID(1378) def test_devtool_modify_virtual(self): # Try modifying a virtual recipe virtrecipe = 'virtual/make' @@ -665,8 +677,8 @@ class DevtoolTests(DevtoolBase): self.track_for_cleanup(self.workspacedir) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool modify %s -x %s' % (virtrecipe, tempdir)) - self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found') - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created') + self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found') + self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created') matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % realrecipe)) self.assertTrue(matches, 'bbappend not created %s' % result.output) # Test devtool status @@ -678,7 +690,7 @@ class DevtoolTests(DevtoolBase): # This is probably sufficient - @testcase(1169) + @OETestID(1169) def test_devtool_update_recipe(self): # Check preconditions testrecipe = 'minicom' @@ -711,7 +723,7 @@ class DevtoolTests(DevtoolBase): ('??', '.*/0002-Add-a-new-file.patch$')] self._check_repo_status(os.path.dirname(recipefile), expected_status) - @testcase(1172) + @OETestID(1172) def test_devtool_update_recipe_git(self): # Check preconditions testrecipe = 'mtd-utils' @@ -781,7 +793,7 @@ class DevtoolTests(DevtoolBase): ('??', '%s/0002-Add-a-new-file.patch' % relpatchpath)] self._check_repo_status(os.path.dirname(recipefile), expected_status) - @testcase(1170) + @OETestID(1170) def test_devtool_update_recipe_append(self): # Check preconditions testrecipe = 'mdadm' @@ -817,7 +829,7 @@ class DevtoolTests(DevtoolBase): appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1]) bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir) patchfile = os.path.join(appenddir, testrecipe, '0001-Add-our-custom-version.patch') - self.assertTrue(os.path.exists(patchfile), 'Patch file not created') + self.assertExists(patchfile, 'Patch file not created') # Check bbappend contents expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -834,7 +846,7 @@ class DevtoolTests(DevtoolBase): # Drop new commit and check patch gets deleted result = runCmd('git reset HEAD^', cwd=tempsrcdir) result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) - self.assertFalse(os.path.exists(patchfile), 'Patch file not deleted') + self.assertNotExists(patchfile, 'Patch file not deleted') expectedlines2 = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', '\n'] with open(bbappendfile, 'r') as f: @@ -845,12 +857,12 @@ class DevtoolTests(DevtoolBase): result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir)) self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output) - self.assertTrue(os.path.exists(patchfile), 'Patch file not created (with disabled layer)') + self.assertExists(patchfile, 'Patch file not created (with disabled layer)') with open(bbappendfile, 'r') as f: self.assertEqual(expectedlines, f.readlines()) # Deleting isn't expected to work under these circumstances - @testcase(1171) + @OETestID(1171) def test_devtool_update_recipe_append_git(self): # Check preconditions testrecipe = 'mtd-utils' @@ -898,7 +910,7 @@ class DevtoolTests(DevtoolBase): splitpath = os.path.dirname(recipefile).split(os.sep) appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1]) bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir) - self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created') + self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created') # Check bbappend contents result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) @@ -916,7 +928,7 @@ class DevtoolTests(DevtoolBase): # Drop new commit and check SRCREV changes result = runCmd('git reset HEAD^', cwd=tempsrcdir) result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) - self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created') + self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created') result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) expectedlines = set(['SRCREV = "%s"\n' % result.output, '\n', @@ -930,7 +942,7 @@ class DevtoolTests(DevtoolBase): result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir) result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir)) self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output) - self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created') + self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created') result = runCmd('git rev-parse HEAD', cwd=tempsrcdir) expectedlines = set(['SRCREV = "%s"\n' % result.output, '\n', @@ -940,7 +952,7 @@ class DevtoolTests(DevtoolBase): self.assertEqual(expectedlines, set(f.readlines())) # Deleting isn't expected to work under these circumstances - @testcase(1370) + @OETestID(1370) def test_devtool_update_recipe_local_files(self): """Check that local source files are copied over instead of patched""" testrecipe = 'makedevs' @@ -972,7 +984,7 @@ class DevtoolTests(DevtoolBase): ('??', '.*/makedevs/0001-Add-new-file.patch$')] self._check_repo_status(os.path.dirname(recipefile), expected_status) - @testcase(1371) + @OETestID(1371) def test_devtool_update_recipe_local_files_2(self): """Check local source files support when oe-local-files is in Git""" testrecipe = 'lzo' @@ -1013,6 +1025,7 @@ class DevtoolTests(DevtoolBase): ('??', '.*/0001-Add-new-file.patch$')] self._check_repo_status(os.path.dirname(recipefile), expected_status) + @OETestID(1627) def test_devtool_update_recipe_local_files_3(self): # First, modify the recipe testrecipe = 'devtool-test-localonly' @@ -1032,6 +1045,7 @@ class DevtoolTests(DevtoolBase): expected_status = [(' M', '.*/%s/file2$' % testrecipe)] self._check_repo_status(os.path.dirname(recipefile), expected_status) + @OETestID(1629) def test_devtool_update_recipe_local_patch_gz(self): # First, modify the recipe testrecipe = 'devtool-test-patch-gz' @@ -1059,8 +1073,9 @@ class DevtoolTests(DevtoolBase): if 'gzip compressed data' not in result.output: self.fail('New patch file is not gzipped - file reports:\n%s' % result.output) + @OETestID(1628) def test_devtool_update_recipe_local_files_subdir(self): - # Try devtool extract on a recipe that has a file with subdir= set in + # Try devtool update-recipe on a recipe that has a file with subdir= set in # SRC_URI such that it overwrites a file that was in an archive that # was also in SRC_URI # First, modify the recipe @@ -1075,7 +1090,7 @@ class DevtoolTests(DevtoolBase): # (don't bother with cleaning the recipe on teardown, we won't be building it) result = runCmd('devtool modify %s' % testrecipe) testfile = os.path.join(self.workspacedir, 'sources', testrecipe, 'testfile') - self.assertTrue(os.path.exists(testfile), 'Extracted source could not be found') + self.assertExists(testfile, 'Extracted source could not be found') with open(testfile, 'r') as f: contents = f.read().rstrip() self.assertEqual(contents, 'Modified version', 'File has apparently not been overwritten as it should have been') @@ -1085,30 +1100,29 @@ class DevtoolTests(DevtoolBase): expected_status = [] self._check_repo_status(os.path.dirname(recipefile), expected_status) - @testcase(1163) + @OETestID(1163) def test_devtool_extract(self): tempdir = tempfile.mkdtemp(prefix='devtoolqa') # Try devtool extract self.track_for_cleanup(tempdir) - self.append_config('PREFERRED_PROVIDER_virtual/make = "remake"') - result = runCmd('devtool extract remake %s' % tempdir) - self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found') - # devtool extract shouldn't create the workspace - self.assertFalse(os.path.exists(self.workspacedir)) + self.track_for_cleanup(self.workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') + result = runCmd('devtool extract matchbox-terminal %s' % tempdir) + self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found') self._check_src_repo(tempdir) - @testcase(1379) + @OETestID(1379) def test_devtool_extract_virtual(self): tempdir = tempfile.mkdtemp(prefix='devtoolqa') # Try devtool extract self.track_for_cleanup(tempdir) + self.track_for_cleanup(self.workspacedir) + self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool extract virtual/make %s' % tempdir) - self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found') - # devtool extract shouldn't create the workspace - self.assertFalse(os.path.exists(self.workspacedir)) + self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found') self._check_src_repo(tempdir) - @testcase(1168) + @OETestID(1168) def test_devtool_reset_all(self): tempdir = tempfile.mkdtemp(prefix='devtoolqa') self.track_for_cleanup(tempdir) @@ -1135,7 +1149,7 @@ class DevtoolTests(DevtoolBase): matches2 = glob.glob(stampprefix2 + '*') self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2) - @testcase(1272) + @OETestID(1272) 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 @@ -1221,7 +1235,7 @@ class DevtoolTests(DevtoolBase): result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True) self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have') - @testcase(1366) + @OETestID(1366) def test_devtool_build_image(self): """Test devtool build-image plugin""" # Check preconditions @@ -1255,7 +1269,7 @@ class DevtoolTests(DevtoolBase): if reqpkgs: self.fail('The following packages were not present in the image as expected: %s' % ', '.join(reqpkgs)) - @testcase(1367) + @OETestID(1367) def test_devtool_upgrade(self): # Check preconditions self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') @@ -1280,10 +1294,10 @@ class DevtoolTests(DevtoolBase): # Check if srctree at least is populated self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, version)) # Check new recipe subdirectory is present - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe, '%s-%s' % (recipe, version))), 'Recipe folder should exist') + self.assertExists(os.path.join(self.workspacedir, 'recipes', recipe, '%s-%s' % (recipe, version)), 'Recipe folder should exist') # Check new recipe file is present newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, '%s_%s.bb' % (recipe, version)) - self.assertTrue(os.path.exists(newrecipefile), 'Recipe file should exist after upgrade') + self.assertExists(newrecipefile, 'Recipe file should exist after upgrade') # Check devtool status and make sure recipe is present result = runCmd('devtool status') self.assertIn(recipe, result.output) @@ -1298,9 +1312,9 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool reset %s -n' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output) - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after resetting') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') - @testcase(1433) + @OETestID(1433) def test_devtool_upgrade_git(self): # Check preconditions self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') @@ -1320,7 +1334,7 @@ class DevtoolTests(DevtoolBase): self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, commit)) # Check new recipe file is present newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldrecipefile)) - self.assertTrue(os.path.exists(newrecipefile), 'Recipe file should exist after upgrade') + self.assertExists(newrecipefile, 'Recipe file should exist after upgrade') # Check devtool status and make sure recipe is present result = runCmd('devtool status') self.assertIn(recipe, result.output) @@ -1335,9 +1349,9 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool reset %s -n' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output) - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after resetting') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting') - @testcase(1352) + @OETestID(1352) def test_devtool_layer_plugins(self): """Test that devtool can use plugins from other layers. @@ -1352,7 +1366,7 @@ class DevtoolTests(DevtoolBase): def _copy_file_with_cleanup(self, srcfile, basedstdir, *paths): dstdir = basedstdir - self.assertTrue(os.path.exists(dstdir)) + self.assertExists(dstdir) for p in paths: dstdir = os.path.join(dstdir, p) if not os.path.exists(dstdir): @@ -1363,6 +1377,7 @@ class DevtoolTests(DevtoolBase): shutil.copy(srcfile, dstfile) self.track_for_cleanup(dstfile) + @OETestID(1625) def test_devtool_load_plugin(self): """Test that devtool loads only the first found plugin in BBPATH.""" @@ -1427,9 +1442,10 @@ class DevtoolTests(DevtoolBase): recipedir = os.path.dirname(oldrecipefile) olddir = os.path.join(recipedir, recipe + '-' + oldversion) patchfn = '0001-Add-a-note-line-to-the-quick-reference.patch' - self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Original patch file does not exist') + self.assertExists(os.path.join(olddir, patchfn), 'Original patch file does not exist') return recipe, oldrecipefile, recipedir, olddir, newversion, patchfn + @OETestID(1623) def test_devtool_finish_upgrade_origlayer(self): recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade() # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) @@ -1439,15 +1455,16 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool finish %s meta-selftest' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') - self.assertFalse(os.path.exists(oldrecipefile), 'Old recipe file should have been deleted but wasn\'t') - self.assertFalse(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should have been deleted but wasn\'t') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') + self.assertNotExists(oldrecipefile, 'Old recipe file should have been deleted but wasn\'t') + self.assertNotExists(os.path.join(olddir, patchfn), 'Old patch file should have been deleted but wasn\'t') newrecipefile = os.path.join(recipedir, '%s_%s.bb' % (recipe, newversion)) newdir = os.path.join(recipedir, recipe + '-' + newversion) - self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t') - self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t') - self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t') + self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t') + self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t') + self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t') + @OETestID(1624) def test_devtool_finish_upgrade_otherlayer(self): recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade() # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) @@ -1462,13 +1479,13 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool finish %s oe-core' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') - self.assertTrue(os.path.exists(oldrecipefile), 'Old recipe file should not have been deleted') - self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should not have been deleted') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') + self.assertExists(oldrecipefile, 'Old recipe file should not have been deleted') + self.assertExists(os.path.join(olddir, patchfn), 'Old patch file should not have been deleted') newdir = os.path.join(newrecipedir, recipe + '-' + newversion) - self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t') - self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t') - self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t') + self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t') + self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t') + self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t') def _setup_test_devtool_finish_modify(self): # Check preconditions @@ -1485,7 +1502,7 @@ class DevtoolTests(DevtoolBase): self.track_for_cleanup(tempdir) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') result = runCmd('devtool modify %s %s' % (recipe, tempdir)) - self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found') + self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') # Test devtool status result = runCmd('devtool status') self.assertIn(recipe, result.output) @@ -1503,6 +1520,7 @@ class DevtoolTests(DevtoolBase): self.fail('Unable to find recipe files directory for %s' % recipe) return recipe, oldrecipefile, recipedir, filesdir + @OETestID(1621) def test_devtool_finish_modify_origlayer(self): recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify() # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) @@ -1512,11 +1530,12 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool finish %s meta' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') expected_status = [(' M', '.*/%s$' % os.path.basename(oldrecipefile)), ('??', '.*/.*-Add-a-comment-to-the-code.patch$')] self._check_repo_status(recipedir, expected_status) + @OETestID(1622) def test_devtool_finish_modify_otherlayer(self): recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify() # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things) @@ -1529,14 +1548,14 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool finish %s meta-selftest' % recipe) result = runCmd('devtool status') self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish') result = runCmd('git status --porcelain .', cwd=recipedir) if result.output.strip(): self.fail('Recipe directory for %s contains the following unexpected changes after finish:\n%s' % (recipe, result.output.strip())) recipefn = os.path.splitext(os.path.basename(oldrecipefile))[0] recipefn = recipefn.split('_')[0] + '_%' appendfile = os.path.join(appenddir, recipefn + '.bbappend') - self.assertTrue(os.path.exists(appendfile), 'bbappend %s should have been created but wasn\'t' % appendfile) + self.assertExists(appendfile, 'bbappend %s should have been created but wasn\'t' % appendfile) newdir = os.path.join(appenddir, recipe) files = os.listdir(newdir) foundpatch = None @@ -1549,6 +1568,7 @@ class DevtoolTests(DevtoolBase): if files: self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files)) + @OETestID(1626) def test_devtool_rename(self): # Check preconditions self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory') @@ -1563,8 +1583,8 @@ class DevtoolTests(DevtoolBase): url = 'http://downloads.yoctoproject.org/mirror/sources/i2c-tools-%s.tar.bz2' % recipever def add_recipe(): result = runCmd('devtool add %s' % url) - self.assertTrue(os.path.exists(recipefile), 'Expected recipe file not created') - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', recipename)), 'Source directory not created') + self.assertExists(recipefile, 'Expected recipe file not created') + self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory not created') checkvars = {} checkvars['S'] = None checkvars['SRC_URI'] = url.replace(recipever, '${PV}') @@ -1575,10 +1595,10 @@ class DevtoolTests(DevtoolBase): newrecipever = '456' newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, newrecipever)) result = runCmd('devtool rename %s %s -V %s' % (recipename, newrecipename, newrecipever)) - self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipename)), 'Old recipe directory still exists') + self.assertExists(newrecipefile, 'Recipe file not renamed') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists') newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename) - self.assertTrue(os.path.exists(newsrctree), 'Source directory not renamed') + self.assertExists(newsrctree, 'Source directory not renamed') checkvars = {} checkvars['S'] = '${WORKDIR}/%s-%s' % (recipename, recipever) checkvars['SRC_URI'] = url @@ -1589,9 +1609,9 @@ class DevtoolTests(DevtoolBase): add_recipe() newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, recipever)) result = runCmd('devtool rename %s %s' % (recipename, newrecipename)) - self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed') - self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipename)), 'Old recipe directory still exists') - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', newrecipename)), 'Source directory not renamed') + self.assertExists(newrecipefile, 'Recipe file not renamed') + self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists') + self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed') checkvars = {} checkvars['S'] = '${WORKDIR}/%s-${PV}' % recipename checkvars['SRC_URI'] = url.replace(recipever, '${PV}') @@ -1602,14 +1622,14 @@ class DevtoolTests(DevtoolBase): add_recipe() newrecipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, newrecipever)) result = runCmd('devtool rename %s -V %s' % (recipename, newrecipever)) - self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed') - self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', recipename)), 'Source directory no longer exists') + self.assertExists(newrecipefile, 'Recipe file not renamed') + self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists') checkvars = {} checkvars['S'] = '${WORKDIR}/${BPN}-%s' % recipever checkvars['SRC_URI'] = url self._test_recipe_contents(newrecipefile, checkvars, []) - @testcase(1577) + @OETestID(1577) def test_devtool_virtual_kernel_modify(self): """ Summary: The purpose of this test case is to verify that @@ -1632,15 +1652,13 @@ class DevtoolTests(DevtoolBase): and modification to the source and configurations are reflected when building the kernel. """ - #Set machine to qemxu86 to be able to modify the kernel and - #verify the modification. - features = 'MACHINE = "qemux86"\n' - self.write_config(features) kernel_provider = get_bb_var('PREFERRED_PROVIDER_virtual/kernel') # Clean up the enviroment bitbake('%s -c clean' % kernel_provider) tempdir = tempfile.mkdtemp(prefix='devtoolqa') + tempdir_cfg = tempfile.mkdtemp(prefix='config_qa') self.track_for_cleanup(tempdir) + self.track_for_cleanup(tempdir_cfg) self.track_for_cleanup(self.workspacedir) self.add_command_to_tearDown('bitbake-layers remove-layer */workspace') self.add_command_to_tearDown('bitbake -c clean %s' % kernel_provider) @@ -1649,19 +1667,16 @@ class DevtoolTests(DevtoolBase): #time of executing this test case. bitbake('%s -c configure' % kernel_provider) bbconfig = os.path.join(get_bb_var('B', kernel_provider),'.config') - buildir= get_bb_var('TOPDIR') #Step 2 - runCmd('cp %s %s' % (bbconfig, buildir)) - self.assertTrue(os.path.exists(os.path.join(buildir, '.config')), - 'Could not copy .config file from kernel') + runCmd('cp %s %s' % (bbconfig, tempdir_cfg)) + self.assertExists(os.path.join(tempdir_cfg, '.config'), 'Could not copy .config file from kernel') - tmpconfig = os.path.join(buildir, '.config') + tmpconfig = os.path.join(tempdir_cfg, '.config') #Step 3 bitbake('%s -c clean' % kernel_provider) #Step 4.1 runCmd('devtool modify virtual/kernel -x %s' % tempdir) - self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), - 'Extracted source could not be found') + self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found') #Step 4.2 configfile = os.path.join(tempdir,'.config') diff = runCmd('diff %s %s' % (tmpconfig, configfile)) @@ -1671,12 +1686,12 @@ class DevtoolTests(DevtoolBase): result = runCmd('devtool build %s' % kernel_provider) self.assertEqual(0,result.status,'Cannot build kernel using `devtool build`') kernelfile = os.path.join(get_bb_var('KBUILD_OUTPUT', kernel_provider), 'vmlinux') - self.assertTrue(os.path.exists(kernelfile),'Kernel was not build correctly') + self.assertExists(kernelfile, 'Kernel was not build correctly') - #Modify the kernel source, this is specific for qemux86 + #Modify the kernel source modfile = os.path.join(tempdir,'arch/x86/boot/header.S') - modstring = "use a boot loader - Devtool kernel testing" - modapplied = runCmd("sed -i 's/boot loader/%s/' %s" % (modstring, modfile)) + modstring = "Use a boot loader. Devtool testing." + modapplied = runCmd("sed -i 's/Use a boot loader./%s/' %s" % (modstring, modfile)) self.assertEqual(0,modapplied.status,'Modification to %s on kernel source failed' % modfile) #Modify the configuration codeconfigfile = os.path.join(tempdir,'.config.new') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py new file mode 100644 index 000000000..12540adc7 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py @@ -0,0 +1,42 @@ +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars +from oeqa.utils.decorators import testcase +from oeqa.utils.ftools import write_file +from oeqa.core.decorator.oeid import OETestID + +class Distrodata(OESelftestTestCase): + + @classmethod + def setUpClass(cls): + super(Distrodata, cls).setUpClass() + + @OETestID(1902) + def test_checkpkg(self): + """ + Summary: Test that upstream version checks do not regress + Expected: Upstream version checks should succeed except for the recipes listed in the exception list. + Product: oe-core + Author: Alexander Kanavin <alexander.kanavin@intel.com> + """ + feature = 'INHERIT += "distrodata"\n' + feature += 'LICENSE_FLAGS_WHITELIST += " commercial"\n' + + self.write_config(feature) + bitbake('-c checkpkg world') + checkpkg_result = open(os.path.join(get_bb_var("LOG_DIR"), "checkpkg.csv")).readlines()[1:] + regressed_failures = [pkg_data[0] for pkg_data in [pkg_line.split('\t') for pkg_line in checkpkg_result] if pkg_data[11] == 'UNKNOWN_BROKEN'] + regressed_successes = [pkg_data[0] for pkg_data in [pkg_line.split('\t') for pkg_line in checkpkg_result] if pkg_data[11] == 'KNOWN_BROKEN'] + msg = "" + if len(regressed_failures) > 0: + msg = msg + """ +The following packages failed upstream version checks. Please fix them using UPSTREAM_CHECK_URI/UPSTREAM_CHECK_REGEX +(when using tarballs) or UPSTREAM_CHECK_GITTAGREGEX (when using git). If an upstream version check cannot be performed +(for example, if upstream does not use git tags), you can set UPSTREAM_VERSION_UNKNOWN to '1' in the recipe to acknowledge +that the check cannot be performed. +""" + "\n".join(regressed_failures) + if len(regressed_successes) > 0: + msg = msg + """ +The following packages have been checked successfully for upstream versions, +but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please remove that line from the recipes. +""" + "\n".join(regressed_successes) + self.assertTrue(len(regressed_failures) == 0 and len(regressed_successes) == 0, msg) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/eSDK.py index 1596c6e9d..d03188f2f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/eSDK.py @@ -1,16 +1,12 @@ -import unittest import tempfile import shutil import os import glob -import logging -import subprocess -import oeqa.utils.ftools as ftools -from oeqa.utils.decorators import testcase -from oeqa.selftest.base import oeSelfTest +from oeqa.core.decorator.oeid import OETestID +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars -class oeSDKExtSelfTest(oeSelfTest): +class oeSDKExtSelfTest(OESelftestTestCase): """ # Bugzilla Test Plan: 6033 # This code is planned to be part of the automation for eSDK containig @@ -73,6 +69,7 @@ CORE_IMAGE_EXTRA_INSTALL = "perl" @classmethod def setUpClass(cls): + super(oeSDKExtSelfTest, cls).setUpClass() cls.tmpdir_eSDKQA = tempfile.mkdtemp(prefix='eSDKQA') sstate_dir = get_bb_var('SSTATE_DIR') @@ -96,20 +93,19 @@ SSTATE_MIRRORS = "file://.* file://%s/PATH" @classmethod def tearDownClass(cls): - shutil.rmtree(cls.tmpdir_eSDKQA) + shutil.rmtree(cls.tmpdir_eSDKQA, ignore_errors=True) + super(oeSDKExtSelfTest, cls).tearDownClass() - @testcase (1602) + @OETestID(1602) def test_install_libraries_headers(self): pn_sstate = 'bc' bitbake(pn_sstate) cmd = "devtool sdk-install %s " % pn_sstate oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd) - @testcase(1603) + @OETestID(1603) def test_image_generation_binary_feeds(self): image = 'core-image-minimal' cmd = "devtool build-image %s" % image oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd) -if __name__ == '__main__': - unittest.main() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/image_typedep.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/image_typedep.py index 256142d25..e6788853a 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/image_typedep.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/image_typedep.py @@ -1,12 +1,14 @@ import os -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import bitbake +from oeqa.core.decorator.oeid import OETestID -class ImageTypeDepTests(oeSelfTest): +class ImageTypeDepTests(OESelftestTestCase): # Verify that when specifying a IMAGE_TYPEDEP_ of the form "foo.bar" that # the conversion type bar gets added as a dep as well + @OETestID(1633) def test_conversion_typedep_added(self): self.write_recipeinc('emptytest', """ diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/imagefeatures.py index 76896c798..0ffb68692 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/imagefeatures.py @@ -1,17 +1,18 @@ -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, runqemu -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID from oeqa.utils.sshcontrol import SSHControl import os -import sys -import logging +import json -class ImageFeatures(oeSelfTest): +class ImageFeatures(OESelftestTestCase): test_user = 'tester' root_user = 'root' - @testcase(1107) + buffer = True + + @OETestID(1107) def test_non_root_user_can_connect_via_ssh_without_password(self): """ Summary: Check if non root user can connect via ssh without password @@ -37,7 +38,7 @@ class ImageFeatures(oeSelfTest): status, output = ssh.run("true") self.assertEqual(status, 0, 'ssh to user %s failed with %s' % (user, output)) - @testcase(1115) + @OETestID(1115) def test_all_users_can_connect_via_ssh_without_password(self): """ Summary: Check if all users can connect via ssh without password @@ -67,7 +68,7 @@ class ImageFeatures(oeSelfTest): self.assertEqual(status, 0, 'ssh to user tester failed with %s' % output) - @testcase(1116) + @OETestID(1116) def test_clutter_image_can_be_built(self): """ Summary: Check if clutter image can be built @@ -80,7 +81,7 @@ class ImageFeatures(oeSelfTest): # Build a core-image-clutter bitbake('core-image-clutter') - @testcase(1117) + @OETestID(1117) def test_wayland_support_in_image(self): """ Summary: Check Wayland support in image @@ -98,6 +99,7 @@ class ImageFeatures(oeSelfTest): # Build a core-image-weston bitbake('core-image-weston') + @OETestID(1497) def test_bmap(self): """ Summary: Check bmap support @@ -107,7 +109,7 @@ class ImageFeatures(oeSelfTest): Author: Ed Bartosh <ed.bartosh@linux.intel.com> """ - features = 'IMAGE_FSTYPES += " ext4 ext4.bmap"' + features = 'IMAGE_FSTYPES += " ext4 ext4.bmap ext4.bmap.gz"' self.write_config(features) image_name = 'core-image-minimal' @@ -117,11 +119,122 @@ class ImageFeatures(oeSelfTest): link_name = get_bb_var('IMAGE_LINK_NAME', image_name) image_path = os.path.join(deploy_dir_image, "%s.ext4" % link_name) bmap_path = "%s.bmap" % image_path + gzip_path = "%s.gz" % bmap_path - # check if result image and bmap file are in deploy directory + # check if result image, bmap and bmap.gz files are in deploy directory self.assertTrue(os.path.exists(image_path)) self.assertTrue(os.path.exists(bmap_path)) + self.assertTrue(os.path.exists(gzip_path)) # check if result image is sparse image_stat = os.stat(image_path) self.assertTrue(image_stat.st_size > image_stat.st_blocks * 512) + + # check if the resulting gzip is valid + self.assertTrue(runCmd('gzip -t %s' % gzip_path)) + + @OETestID(1903) + def test_hypervisor_fmts(self): + """ + Summary: Check various hypervisor formats + Expected: 1. core-image-minimal can be built with vmdk, vdi and + qcow2 support. + 2. qemu-img says each image has the expected format + Product: oe-core + Author: Tom Rini <trini@konsulko.com> + """ + + img_types = [ 'vmdk', 'vdi', 'qcow2' ] + features = "" + for itype in img_types: + features += 'IMAGE_FSTYPES += "wic.%s"\n' % itype + self.write_config(features) + + image_name = 'core-image-minimal' + bitbake(image_name) + + deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') + link_name = get_bb_var('IMAGE_LINK_NAME', image_name) + for itype in img_types: + image_path = os.path.join(deploy_dir_image, "%s.wic.%s" % + (link_name, itype)) + + # check if result image file is in deploy directory + self.assertTrue(os.path.exists(image_path)) + + # check if result image is vmdk + sysroot = get_bb_var('STAGING_DIR_NATIVE', 'core-image-minimal') + result = runCmd('qemu-img info --output json %s' % image_path, + native_sysroot=sysroot) + self.assertTrue(json.loads(result.output).get('format') == itype) + + @OETestID(1905) + def test_long_chain_conversion(self): + """ + Summary: Check for chaining many CONVERSION_CMDs together + Expected: 1. core-image-minimal can be built with + ext4.bmap.gz.bz2.lzo.xz.u-boot and also create a + sha256sum + 2. The above image has a valid sha256sum + Product: oe-core + Author: Tom Rini <trini@konsulko.com> + """ + + conv = "ext4.bmap.gz.bz2.lzo.xz.u-boot" + features = 'IMAGE_FSTYPES += "%s %s.sha256sum"' % (conv, conv) + self.write_config(features) + + image_name = 'core-image-minimal' + bitbake(image_name) + + deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') + link_name = get_bb_var('IMAGE_LINK_NAME', image_name) + image_path = os.path.join(deploy_dir_image, "%s.%s" % + (link_name, conv)) + + # check if resulting image is in the deploy directory + self.assertTrue(os.path.exists(image_path)) + self.assertTrue(os.path.exists(image_path + ".sha256sum")) + + # check if the resulting sha256sum agrees + self.assertTrue(runCmd('cd %s;sha256sum -c %s.%s.sha256sum' % + (deploy_dir_image, link_name, conv))) + + @OETestID(1904) + def test_image_fstypes(self): + """ + Summary: Check if image of supported image fstypes can be built + Expected: core-image-minimal can be built for various image types + Product: oe-core + Author: Ed Bartosh <ed.bartosh@linux.intel.com> + """ + image_name = 'core-image-minimal' + + img_types = [itype for itype in get_bb_var("IMAGE_TYPES", image_name).split() \ + if itype not in ('container', 'elf', 'multiubi')] + + config = 'IMAGE_FSTYPES += "%s"\n'\ + 'MKUBIFS_ARGS ?= "-m 2048 -e 129024 -c 2047"\n'\ + 'UBINIZE_ARGS ?= "-m 2048 -p 128KiB -s 512"' % ' '.join(img_types) + + self.write_config(config) + + bitbake(image_name) + + deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE') + link_name = get_bb_var('IMAGE_LINK_NAME', image_name) + for itype in img_types: + image_path = os.path.join(deploy_dir_image, "%s.%s" % (link_name, itype)) + # check if result image is in deploy directory + self.assertTrue(os.path.exists(image_path), + "%s image %s doesn't exist" % (itype, image_path)) + + def test_useradd_static(self): + config = """ +USERADDEXTENSION = "useradd-staticids" +USERADD_ERROR_DYNAMIC = "skip" +USERADD_UID_TABLES += "files/static-passwd" +USERADD_GID_TABLES += "files/static-group" +""" + self.write_config(config) + bitbake("core-image-base") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/layerappend.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/layerappend.py index 37bb32cd1..2fd5cdb0c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/layerappend.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/layerappend.py @@ -1,15 +1,11 @@ -import unittest import os -import logging -import re -from oeqa.selftest.base import oeSelfTest -from oeqa.selftest.buildhistory import BuildhistoryBase +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var import oeqa.utils.ftools as ftools -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class LayerAppendTests(oeSelfTest): +class LayerAppendTests(OESelftestTestCase): layerconf = """ # We have a conf and classes directory, append to BBPATH BBPATH .= ":${LAYERDIR}" @@ -44,15 +40,16 @@ sysroot_stage_all_append() { append2 = """ FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" -SRC_URI_append += "file://appendtest.txt" +SRC_URI_append = " file://appendtest.txt" """ layerappend = '' def tearDownLocal(self): if self.layerappend: ftools.remove_from_file(self.builddir + "/conf/bblayers.conf", self.layerappend) + super(LayerAppendTests, self).tearDownLocal() - @testcase(1196) + @OETestID(1196) def test_layer_appends(self): corebase = get_bb_var("COREBASE") @@ -96,5 +93,3 @@ SRC_URI_append += "file://appendtest.txt" bitbake("layerappendtest") data = ftools.read_file(stagingdir + "/appendtest.txt") self.assertEqual(data, "Layer 2 test") - - diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/liboe.py index 0b0301def..e84609246 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/liboe.py @@ -1,16 +1,17 @@ -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake, runCmd import oe.path -import glob import os -import os.path -class LibOE(oeSelfTest): +class LibOE(OESelftestTestCase): @classmethod def setUpClass(cls): + super(LibOE, cls).setUpClass() cls.tmp_dir = get_bb_var('TMPDIR') + @OETestID(1635) def test_copy_tree_special(self): """ Summary: oe.path.copytree() should copy files with special character @@ -36,6 +37,7 @@ class LibOE(oeSelfTest): oe.path.remove(testloc) + @OETestID(1636) def test_copy_tree_xattr(self): """ Summary: oe.path.copytree() should preserve xattr on copied files @@ -70,6 +72,7 @@ class LibOE(oeSelfTest): oe.path.remove(testloc) + @OETestID(1634) def test_copy_hardlink_tree_count(self): """ Summary: oe.path.copyhardlinktree() shouldn't miss out files diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/lic_checksum.py index 2e81373ae..37407157c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/lic_checksum.py @@ -1,16 +1,16 @@ import os import tempfile -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import bitbake from oeqa.utils import CommandError -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class LicenseTests(oeSelfTest): +class LicenseTests(OESelftestTestCase): # Verify that changing a license file that has an absolute path causes # the license qa to fail due to a mismatched md5sum. - @testcase(1197) + @OETestID(1197) def test_nonmatching_checksum(self): bitbake_cmd = '-c populate_lic emptytest' error_msg = 'emptytest: The new md5 checksum is 8d777f385d3dfec8815d20f7496026dc' diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/manifest.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/manifest.py index fe6f94964..146071934 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/manifest.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/manifest.py @@ -1,9 +1,8 @@ -import unittest import os -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID class ManifestEntry: '''A manifest item of a collection able to list missing packages''' @@ -11,7 +10,7 @@ class ManifestEntry: self.file = entry self.missing = [] -class VerifyManifest(oeSelfTest): +class VerifyManifest(OESelftestTestCase): '''Tests for the manifest files and contents of an image''' @classmethod @@ -21,14 +20,14 @@ class VerifyManifest(oeSelfTest): with open(manifest, "r") as mfile: for line in mfile: manifest_entry = os.path.join(path, line.split()[0]) - self.log.debug("{}: looking for {}"\ + self.logger.debug("{}: looking for {}"\ .format(self.classname, manifest_entry)) if not os.path.isfile(manifest_entry): manifest_errors.append(manifest_entry) - self.log.debug("{}: {} not found"\ + self.logger.debug("{}: {} not found"\ .format(self.classname, manifest_entry)) except OSError as e: - self.log.debug("{}: checking of {} failed"\ + self.logger.debug("{}: checking of {} failed"\ .format(self.classname, manifest)) raise e @@ -40,7 +39,7 @@ class VerifyManifest(oeSelfTest): target == self.buildtarget if target == None else target directory = get_bb_var(bb_var, target); if not directory or not os.path.isdir(directory): - self.log.debug("{}: {} points to {} when target = {}"\ + self.logger.debug("{}: {} points to {} when target = {}"\ .format(self.classname, bb_var, directory, target)) raise OSError return directory @@ -48,18 +47,19 @@ class VerifyManifest(oeSelfTest): @classmethod def setUpClass(self): + super(VerifyManifest, self).setUpClass() self.buildtarget = 'core-image-minimal' self.classname = 'VerifyManifest' - self.log.info("{}: doing bitbake {} as a prerequisite of the test"\ + self.logger.info("{}: doing bitbake {} as a prerequisite of the test"\ .format(self.classname, self.buildtarget)) if bitbake(self.buildtarget).status: - self.log.debug("{} Failed to setup {}"\ + self.logger.debug("{} Failed to setup {}"\ .format(self.classname, self.buildtarget)) - unittest.SkipTest("{}: Cannot setup testing scenario"\ + self.skipTest("{}: Cannot setup testing scenario"\ .format(self.classname)) - @testcase(1380) + @OETestID(1380) def test_SDK_manifest_entries(self): '''Verifying the SDK manifest entries exist, this may take a build''' @@ -67,12 +67,12 @@ class VerifyManifest(oeSelfTest): # to do an additional setup for the sdk sdktask = '-c populate_sdk' bbargs = sdktask + ' ' + self.buildtarget - self.log.debug("{}: doing bitbake {} as a prerequisite of the test"\ + self.logger.debug("{}: doing bitbake {} as a prerequisite of the test"\ .format(self.classname, bbargs)) if bitbake(bbargs).status: - self.log.debug("{} Failed to bitbake {}"\ + self.logger.debug("{} Failed to bitbake {}"\ .format(self.classname, bbargs)) - unittest.SkipTest("{}: Cannot setup testing scenario"\ + self.skipTest("{}: Cannot setup testing scenario"\ .format(self.classname)) @@ -91,7 +91,7 @@ class VerifyManifest(oeSelfTest): k) mpath[k] = os.path.join(mdir, mfilename[k]) if not os.path.isfile(mpath[k]): - self.log.debug("{}: {} does not exist".format( + self.logger.debug("{}: {} does not exist".format( self.classname, mpath[k])) raise IOError m_entry[k] = ManifestEntry(mpath[k]) @@ -101,11 +101,11 @@ class VerifyManifest(oeSelfTest): reverse_dir[k] = os.path.join(pkgdata_dir[k], 'runtime-reverse') if not os.path.exists(reverse_dir[k]): - self.log.debug("{}: {} does not exist".format( + self.logger.debug("{}: {} does not exist".format( self.classname, reverse_dir[k])) raise IOError except OSError: - raise unittest.SkipTest("{}: Error in obtaining manifest dirs"\ + raise self.skipTest("{}: Error in obtaining manifest dirs"\ .format(self.classname)) except IOError: msg = "{}: Error cannot find manifests in the specified dir:\n{}"\ @@ -113,7 +113,7 @@ class VerifyManifest(oeSelfTest): self.fail(msg) for k in d_target.keys(): - self.log.debug("{}: Check manifest {}".format( + self.logger.debug("{}: Check manifest {}".format( self.classname, m_entry[k].file)) m_entry[k].missing = self.check_manifest_entries(\ @@ -122,11 +122,11 @@ class VerifyManifest(oeSelfTest): msg = '{}: {} Error has the following missing entries'\ .format(self.classname, m_entry[k].file) logmsg = msg+':\n'+'\n'.join(m_entry[k].missing) - self.log.debug(logmsg) - self.log.info(msg) + self.logger.debug(logmsg) + self.logger.info(msg) self.fail(logmsg) - @testcase(1381) + @OETestID(1381) def test_image_manifest_entries(self): '''Verifying the image manifest entries exist''' @@ -146,14 +146,14 @@ class VerifyManifest(oeSelfTest): revdir = os.path.join(pkgdata_dir, 'runtime-reverse') if not os.path.exists(revdir): raise IOError except OSError: - raise unittest.SkipTest("{}: Error in obtaining manifest dirs"\ + raise self.skipTest("{}: Error in obtaining manifest dirs"\ .format(self.classname)) except IOError: msg = "{}: Error cannot find manifests in dir:\n{}"\ .format(self.classname, mdir) self.fail(msg) - self.log.debug("{}: Check manifest {}"\ + self.logger.debug("{}: Check manifest {}"\ .format(self.classname, m_entry.file)) m_entry.missing = self.check_manifest_entries(\ m_entry.file, revdir) @@ -161,6 +161,6 @@ class VerifyManifest(oeSelfTest): msg = '{}: {} Error has the following missing entries'\ .format(self.classname, m_entry.file) logmsg = msg+':\n'+'\n'.join(m_entry.missing) - self.log.debug(logmsg) - self.log.info(msg) + self.logger.debug(logmsg) + self.logger.info(msg) self.fail(logmsg) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/__init__.py index e69de29bb..e69de29bb 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/__init__.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/__init__.py diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/buildhistory.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py index 5ed4b026f..08675fd82 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/buildhistory.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py @@ -1,18 +1,22 @@ import os -import unittest +from oeqa.selftest.case import OESelftestTestCase import tempfile -from git import Repo from oeqa.utils.commands import get_bb_var -from oe.buildhistory_analysis import blob_to_dict, compare_dict_blobs +from oeqa.core.decorator.oeid import OETestID -class TestBlobParsing(unittest.TestCase): +class TestBlobParsing(OESelftestTestCase): def setUp(self): import time self.repo_path = tempfile.mkdtemp(prefix='selftest-buildhistory', dir=get_bb_var('TOPDIR')) - self.repo = Repo.init(self.repo_path) + try: + from git import Repo + self.repo = Repo.init(self.repo_path) + except ImportError: + self.skipTest('Python module GitPython is not present') + self.test_file = "test" self.var_map = {} @@ -36,10 +40,12 @@ class TestBlobParsing(unittest.TestCase): self.repo.git.add("--all") self.repo.git.commit(message=msg) + @OETestID(1859) def test_blob_to_dict(self): """ Test convertion of git blobs to dictionary """ + from oe.buildhistory_analysis import blob_to_dict valuesmap = { "foo" : "1", "bar" : "2" } self.commit_vars(to_add = valuesmap) @@ -47,10 +53,13 @@ class TestBlobParsing(unittest.TestCase): self.assertEqual(valuesmap, blob_to_dict(blob), "commit was not translated correctly to dictionary") + @OETestID(1860) def test_compare_dict_blobs(self): """ Test comparisson of dictionaries extracted from git blobs """ + from oe.buildhistory_analysis import compare_dict_blobs + changesmap = { "foo-2" : ("2", "8"), "bar" : ("","4"), "bar-2" : ("","5")} self.commit_vars(to_add = { "foo" : "1", "foo-2" : "2", "foo-3" : "3" }) @@ -65,10 +74,12 @@ class TestBlobParsing(unittest.TestCase): var_changes = { x.fieldname : (x.oldvalue, x.newvalue) for x in change_records} self.assertEqual(changesmap, var_changes, "Changes not reported correctly") + @OETestID(1861) def test_compare_dict_blobs_default(self): """ Test default values for comparisson of git blob dictionaries """ + from oe.buildhistory_analysis import compare_dict_blobs defaultmap = { x : ("default", "1") for x in ["PKG", "PKGE", "PKGV", "PKGR"]} self.commit_vars(to_add = { "foo" : "1" }) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/elf.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/elf.py index 1f59037ed..74ee6a11c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/elf.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/elf.py @@ -1,7 +1,7 @@ -import unittest +from unittest.case import TestCase import oe.qa -class TestElf(unittest.TestCase): +class TestElf(TestCase): def test_machine_name(self): """ Test elf_machine_to_string() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/license.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/license.py index c38888618..d7f91fb2f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/license.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/license.py @@ -1,4 +1,4 @@ -import unittest +from unittest.case import TestCase import oe.license class SeenVisitor(oe.license.LicenseVisitor): @@ -9,7 +9,7 @@ class SeenVisitor(oe.license.LicenseVisitor): def visit_Str(self, node): self.seen.append(node.s) -class TestSingleLicense(unittest.TestCase): +class TestSingleLicense(TestCase): licenses = [ "GPLv2", "LGPL-2.0", @@ -37,7 +37,7 @@ class TestSingleLicense(unittest.TestCase): self.parse(license) self.assertEqual(cm.exception.license, license) -class TestSimpleCombinations(unittest.TestCase): +class TestSimpleCombinations(TestCase): tests = { "FOO&BAR": ["FOO", "BAR"], "BAZ & MOO": ["BAZ", "MOO"], @@ -66,3 +66,34 @@ class TestComplexCombinations(TestSimpleCombinations): "(GPL-2.0|Proprietary)&BSD-4-clause&MIT": ["GPL-2.0", "BSD-4-clause", "MIT"], } preferred = ["BAR", "OMEGA", "BETA", "GPL-2.0"] + +class TestIsIncluded(TestCase): + tests = { + ("FOO | BAR", None, None): + [True, ["FOO"]], + ("FOO | BAR", None, "FOO"): + [True, ["BAR"]], + ("FOO | BAR", "BAR", None): + [True, ["BAR"]], + ("FOO | BAR & FOOBAR", "*BAR", None): + [True, ["BAR", "FOOBAR"]], + ("FOO | BAR & FOOBAR", None, "FOO*"): + [False, ["FOOBAR"]], + ("(FOO | BAR) & FOOBAR | BARFOO", None, "FOO"): + [True, ["BAR", "FOOBAR"]], + ("(FOO | BAR) & FOOBAR | BAZ & MOO & BARFOO", None, "FOO"): + [True, ["BAZ", "MOO", "BARFOO"]], + ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, None): + [True, ["GPL-3.0", "GPL-2.0", "LGPL-2.1"]], + ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, "GPL-3.0"): + [True, ["Proprietary"]], + ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, "GPL-3.0 Proprietary"): + [False, ["GPL-3.0"]] + } + + def test_tests(self): + for args, expected in self.tests.items(): + is_included, licenses = oe.license.is_included( + args[0], (args[1] or '').split(), (args[2] or '').split()) + self.assertEqual(is_included, expected[0]) + self.assertListEqual(licenses, expected[1]) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/path.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/path.py index 44d068143..75a27c06f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/path.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/path.py @@ -1,11 +1,11 @@ -import unittest +from unittest.case import TestCase import oe, oe.path import tempfile import os import errno import shutil -class TestRealPath(unittest.TestCase): +class TestRealPath(TestCase): DIRS = [ "a", "b", "etc", "sbin", "usr", "usr/bin", "usr/binX", "usr/sbin", "usr/include", "usr/include/gdbm" ] FILES = [ "etc/passwd", "b/file" ] LINKS = [ diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/types.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/types.py index 4fe2746a3..6b53aa64e 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/types.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/types.py @@ -1,7 +1,7 @@ -import unittest +from unittest.case import TestCase from oe.maketype import create -class TestBooleanType(unittest.TestCase): +class TestBooleanType(TestCase): def test_invalid(self): self.assertRaises(ValueError, create, '', 'boolean') self.assertRaises(ValueError, create, 'foo', 'boolean') @@ -31,7 +31,7 @@ class TestBooleanType(unittest.TestCase): self.assertEqual(create('y', 'boolean'), True) self.assertNotEqual(create('y', 'boolean'), False) -class TestList(unittest.TestCase): +class TestList(TestCase): def assertListEqual(self, value, valid, sep=None): obj = create(value, 'list', separator=sep) self.assertEqual(obj, valid) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/utils.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/utils.py index 7deb10f3c..9fb6c1576 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/utils.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/utils.py @@ -1,7 +1,7 @@ -import unittest +from unittest.case import TestCase from oe.utils import packages_filter_out_system, trim_version -class TestPackagesFilterOutSystem(unittest.TestCase): +class TestPackagesFilterOutSystem(TestCase): def test_filter(self): """ Test that oe.utils.packages_filter_out_system works. @@ -31,7 +31,7 @@ class TestPackagesFilterOutSystem(unittest.TestCase): self.assertEqual(pkgs, ["foo-data"]) -class TestTrimVersion(unittest.TestCase): +class TestTrimVersion(TestCase): def test_version_exception(self): with self.assertRaises(TypeError): trim_version(None, 2) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oescripts.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oescripts.py index 29547f56a..1ee753763 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oescripts.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oescripts.py @@ -1,18 +1,11 @@ -import datetime -import unittest -import os -import re -import shutil - -import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest -from oeqa.selftest.buildhistory import BuildhistoryBase +from oeqa.selftest.case import OESelftestTestCase +from oeqa.selftest.cases.buildhistory import BuildhistoryBase from oeqa.utils.commands import Command, runCmd, bitbake, get_bb_var, get_test_layer -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID class BuildhistoryDiffTests(BuildhistoryBase): - @testcase(295) + @OETestID(295) def test_buildhistory_diff(self): target = 'xcursor-transparent-theme' self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py new file mode 100644 index 000000000..169698f78 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py @@ -0,0 +1,86 @@ +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID +from oeqa.utils.commands import bitbake, get_bb_vars +import subprocess, os +import oe.path + +class VersionOrdering(OESelftestTestCase): + # version1, version2, sort order + tests = ( + ("1.0", "1.0", 0), + ("1.0", "2.0", -1), + ("2.0", "1.0", 1), + ("2.0-rc", "2.0", 1), + ("2.0~rc", "2.0", -1), + ("1.2rc2", "1.2.0", -1) + ) + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Build the tools we need and populate a sysroot + bitbake("dpkg-native opkg-native rpm-native python3-native") + bitbake("build-sysroots -c build_native_sysroot") + + # Get the paths so we can point into the sysroot correctly + vars = get_bb_vars(["STAGING_DIR", "BUILD_ARCH", "bindir_native", "libdir_native"]) + cls.staging = oe.path.join(vars["STAGING_DIR"], vars["BUILD_ARCH"]) + cls.bindir = oe.path.join(cls.staging, vars["bindir_native"]) + cls.libdir = oe.path.join(cls.staging, vars["libdir_native"]) + + def setUp(self): + # Just for convenience + self.staging = type(self).staging + self.bindir = type(self).bindir + self.libdir = type(self).libdir + + @OETestID(1880) + def test_dpkg(self): + for ver1, ver2, sort in self.tests: + op = { -1: "<<", 0: "=", 1: ">>" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2)) + self.assertEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + # Now do it again but with incorrect operations + op = { -1: ">>", 0: ">>", 1: "<<" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2)) + self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + # Now do it again but with incorrect operations + op = { -1: "=", 0: "<<", 1: "=" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2)) + self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + @OETestID(1881) + def test_opkg(self): + for ver1, ver2, sort in self.tests: + op = { -1: "<<", 0: "=", 1: ">>" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2)) + self.assertEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + # Now do it again but with incorrect operations + op = { -1: ">>", 0: ">>", 1: "<<" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2)) + self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + # Now do it again but with incorrect operations + op = { -1: "=", 0: "<<", 1: "=" }[sort] + status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2)) + self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2)) + + @OETestID(1882) + def test_rpm(self): + # Need to tell the Python bindings where to find its configuration + env = os.environ.copy() + env["RPM_CONFIGDIR"] = oe.path.join(self.libdir, "rpm") + + for ver1, ver2, sort in self.tests: + # The only way to test rpm is via the Python module, so we need to + # execute python3-native. labelCompare returns -1/0/1 (like strcmp) + # so add 100 and use that as the exit code. + command = (oe.path.join(self.bindir, "python3-native", "python3"), "-c", + "import sys, rpm; v1=(None, \"%s\", None); v2=(None, \"%s\", None); sys.exit(rpm.labelCompare(v1, v2) + 100)" % (ver1, ver2)) + status = subprocess.call(command, env=env) + self.assertIn(status, (99, 100, 101)) + self.assertEqual(status - 100, sort, "%s %s (%d) failed" % (ver1, ver2, sort)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/pkgdata.py index d69c3c800..0b4caf1b2 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/pkgdata.py @@ -1,24 +1,21 @@ -import unittest import os import tempfile -import logging import fnmatch -import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class OePkgdataUtilTests(oeSelfTest): +class OePkgdataUtilTests(OESelftestTestCase): @classmethod def setUpClass(cls): + super(OePkgdataUtilTests, cls).setUpClass() # Ensure we have the right data in pkgdata - logger = logging.getLogger("selftest") - logger.info('Running bitbake to generate pkgdata') + cls.logger.info('Running bitbake to generate pkgdata') bitbake('busybox zlib m4') - @testcase(1203) + @OETestID(1203) def test_lookup_pkg(self): # Forward tests result = runCmd('oe-pkgdata-util lookup-pkg "zlib busybox"') @@ -37,7 +34,7 @@ class OePkgdataUtilTests(oeSelfTest): self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output) self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg') - @testcase(1205) + @OETestID(1205) def test_read_value(self): result = runCmd('oe-pkgdata-util read-value PN libz1') self.assertEqual(result.output, 'zlib') @@ -47,7 +44,7 @@ class OePkgdataUtilTests(oeSelfTest): pkgsize = int(result.output.strip()) self.assertGreater(pkgsize, 1, "Size should be greater than 1. %s" % result.output) - @testcase(1198) + @OETestID(1198) def test_find_path(self): result = runCmd('oe-pkgdata-util find-path /lib/libz.so.1') self.assertEqual(result.output, 'zlib: /lib/libz.so.1') @@ -57,7 +54,7 @@ class OePkgdataUtilTests(oeSelfTest): self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output) self.assertEqual(result.output, 'ERROR: Unable to find any package producing path /not/exist') - @testcase(1204) + @OETestID(1204) def test_lookup_recipe(self): result = runCmd('oe-pkgdata-util lookup-recipe "libz-staticdev busybox"') self.assertEqual(result.output, 'zlib\nbusybox') @@ -67,7 +64,7 @@ class OePkgdataUtilTests(oeSelfTest): self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output) self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg') - @testcase(1202) + @OETestID(1202) def test_list_pkgs(self): # No arguments result = runCmd('oe-pkgdata-util list-pkgs') @@ -111,7 +108,7 @@ class OePkgdataUtilTests(oeSelfTest): pkglist = sorted(result.output.split()) self.assertEqual(pkglist, ['libz-dbg', 'libz-dev', 'libz-doc'], "Packages listed: %s" % result.output) - @testcase(1201) + @OETestID(1201) def test_list_pkg_files(self): def splitoutput(output): files = {} @@ -201,7 +198,7 @@ class OePkgdataUtilTests(oeSelfTest): self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc']) self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev']) - @testcase(1200) + @OETestID(1200) def test_glob(self): tempdir = tempfile.mkdtemp(prefix='pkgdataqa') self.track_for_cleanup(tempdir) @@ -221,7 +218,7 @@ class OePkgdataUtilTests(oeSelfTest): self.assertNotIn('libz-dev', resultlist) self.assertNotIn('libz-dbg', resultlist) - @testcase(1206) + @OETestID(1206) def test_specify_pkgdatadir(self): result = runCmd('oe-pkgdata-util -p %s lookup-pkg zlib' % get_bb_var('PKGDATA_DIR')) self.assertEqual(result.output, 'libz1') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/prservice.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/prservice.py index 34d419762..479e52061 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/prservice.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/prservice.py @@ -1,20 +1,19 @@ -import unittest import os -import logging import re import shutil import datetime import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID from oeqa.utils.network import get_free_port -class BitbakePrTests(oeSelfTest): +class BitbakePrTests(OESelftestTestCase): @classmethod def setUpClass(cls): + super(BitbakePrTests, cls).setUpClass() cls.pkgdata_dir = get_bb_var('PKGDATA_DIR') def get_pr_version(self, package_name): @@ -43,7 +42,6 @@ class BitbakePrTests(oeSelfTest): res = bitbake(package_name, ignore_status=True) self.delete_recipeinc(package_name) self.assertEqual(res.status, 0, msg=res.output) - self.assertTrue("NOTE: Started PRServer with DBfile" in res.output, msg=res.output) def config_pr_tests(self, package_name, package_type='rpm', pr_socket='localhost:0'): config_package_data = 'PACKAGE_CLASSES = "package_%s"' % package_type @@ -74,6 +72,7 @@ class BitbakePrTests(oeSelfTest): exported_db_path = os.path.join(self.builddir, 'export.inc') export_result = runCmd("bitbake-prserv-tool export %s" % exported_db_path, ignore_status=True) self.assertEqual(export_result.status, 0, msg="PR Service database export failed: %s" % export_result.output) + self.assertTrue(os.path.exists(exported_db_path)) if replace_current_db: current_db_path = os.path.join(get_bb_var('PERSISTENT_DIR'), 'prserv.sqlite3') @@ -89,39 +88,39 @@ class BitbakePrTests(oeSelfTest): self.assertTrue(pr_2 - pr_1 == 1, "Step between same pkg. revision is greater than 1") - @testcase(930) + @OETestID(930) def test_import_export_replace_db(self): self.run_test_pr_export_import('m4') - @testcase(931) + @OETestID(931) def test_import_export_override_db(self): self.run_test_pr_export_import('m4', replace_current_db=False) - @testcase(932) + @OETestID(932) def test_pr_service_rpm_arch_dep(self): self.run_test_pr_service('m4', 'rpm', 'do_package') - @testcase(934) + @OETestID(934) def test_pr_service_deb_arch_dep(self): self.run_test_pr_service('m4', 'deb', 'do_package') - @testcase(933) + @OETestID(933) def test_pr_service_ipk_arch_dep(self): self.run_test_pr_service('m4', 'ipk', 'do_package') - @testcase(935) + @OETestID(935) def test_pr_service_rpm_arch_indep(self): self.run_test_pr_service('xcursor-transparent-theme', 'rpm', 'do_package') - @testcase(937) + @OETestID(937) def test_pr_service_deb_arch_indep(self): self.run_test_pr_service('xcursor-transparent-theme', 'deb', 'do_package') - @testcase(936) + @OETestID(936) def test_pr_service_ipk_arch_indep(self): self.run_test_pr_service('xcursor-transparent-theme', 'ipk', 'do_package') - @testcase(1419) + @OETestID(1419) def test_stopping_prservice_message(self): port = get_free_port() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/recipetool.py index dc55a5e49..754ea9498 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/recipetool.py @@ -1,18 +1,15 @@ import os -import logging import shutil import tempfile import urllib.parse from oeqa.utils.commands import runCmd, bitbake, get_bb_var from oeqa.utils.commands import get_bb_vars, create_temp_layer -from oeqa.utils.decorators import testcase -from oeqa.selftest import devtool - +from oeqa.core.decorator.oeid import OETestID +from oeqa.selftest.cases import devtool templayerdir = None - def setUpModule(): global templayerdir templayerdir = tempfile.mkdtemp(prefix='recipetoolqa') @@ -28,6 +25,7 @@ def tearDownModule(): class RecipetoolBase(devtool.DevtoolBase): def setUpLocal(self): + super(RecipetoolBase, self).setUpLocal() self.templayerdir = templayerdir self.tempdir = tempfile.mkdtemp(prefix='recipetoolqa') self.track_for_cleanup(self.tempdir) @@ -37,6 +35,7 @@ class RecipetoolBase(devtool.DevtoolBase): def tearDownLocal(self): runCmd('rm -rf %s/recipes-*' % self.templayerdir) + super(RecipetoolBase, self).tearDownLocal() def _try_recipetool_appendcmd(self, cmd, testrecipe, expectedfiles, expectedlines=None): result = runCmd(cmd) @@ -70,9 +69,9 @@ class RecipetoolTests(RecipetoolBase): @classmethod def setUpClass(cls): + super(RecipetoolTests, cls).setUpClass() # Ensure we have the right data in shlibs/pkgdata - logger = logging.getLogger("selftest") - logger.info('Running bitbake to generate pkgdata') + cls.logger.info('Running bitbake to generate pkgdata') bitbake('-c packagedata base-files coreutils busybox selftest-recipetool-appendfile') bb_vars = get_bb_vars(['COREBASE', 'BBPATH']) cls.corebase = bb_vars['COREBASE'] @@ -90,7 +89,7 @@ class RecipetoolTests(RecipetoolBase): for errorstr in checkerror: self.assertIn(errorstr, result.output) - @testcase(1177) + @OETestID(1177) def test_recipetool_appendfile_basic(self): # Basic test expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -98,14 +97,14 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('base-files', '/etc/motd', self.testfile, '', expectedlines, ['motd']) self.assertNotIn('WARNING: ', output) - @testcase(1183) + @OETestID(1183) def test_recipetool_appendfile_invalid(self): # Test some commands that should error self._try_recipetool_appendfile_fail('/etc/passwd', self.testfile, ['ERROR: /etc/passwd cannot be handled by this tool', 'useradd', 'extrausers']) self._try_recipetool_appendfile_fail('/etc/timestamp', self.testfile, ['ERROR: /etc/timestamp cannot be handled by this tool']) self._try_recipetool_appendfile_fail('/dev/console', self.testfile, ['ERROR: /dev/console cannot be handled by this tool']) - @testcase(1176) + @OETestID(1176) def test_recipetool_appendfile_alternatives(self): # Now try with a file we know should be an alternative # (this is very much a fake example, but one we know is reliably an alternative) @@ -129,7 +128,7 @@ class RecipetoolTests(RecipetoolBase): result = runCmd('diff -q %s %s' % (testfile2, copiedfile), ignore_status=True) self.assertNotEqual(result.status, 0, 'New file should have been copied but was not %s' % result.output) - @testcase(1178) + @OETestID(1178) def test_recipetool_appendfile_binary(self): # Try appending a binary file # /bin/ls can be a symlink to /usr/bin/ls @@ -138,7 +137,7 @@ class RecipetoolTests(RecipetoolBase): self.assertIn('WARNING: ', result.output) self.assertIn('is a binary', result.output) - @testcase(1173) + @OETestID(1173) def test_recipetool_appendfile_add(self): # Try arbitrary file add to a recipe expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -167,7 +166,7 @@ class RecipetoolTests(RecipetoolBase): '}\n'] self._try_recipetool_appendfile('netbase', '/usr/share/scriptname', testfile2, '-r netbase', expectedlines, ['testfile', testfile2name]) - @testcase(1174) + @OETestID(1174) def test_recipetool_appendfile_add_bindir(self): # Try arbitrary file add to a recipe, this time to a location such that should be installed as executable expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -181,7 +180,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('netbase', '/usr/bin/selftest-recipetool-testbin', self.testfile, '-r netbase', expectedlines, ['testfile']) self.assertNotIn('WARNING: ', output) - @testcase(1175) + @OETestID(1175) def test_recipetool_appendfile_add_machine(self): # Try arbitrary file add to a recipe, this time to a location such that should be installed as executable expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -197,7 +196,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase -m mymachine', expectedlines, ['mymachine/testfile']) self.assertNotIn('WARNING: ', output) - @testcase(1184) + @OETestID(1184) def test_recipetool_appendfile_orig(self): # A file that's in SRC_URI and in do_install with the same name expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -205,7 +204,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-orig', self.testfile, '', expectedlines, ['selftest-replaceme-orig']) self.assertNotIn('WARNING: ', output) - @testcase(1191) + @OETestID(1191) def test_recipetool_appendfile_todir(self): # A file that's in SRC_URI and in do_install with destination directory rather than file expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -213,7 +212,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-todir', self.testfile, '', expectedlines, ['selftest-replaceme-todir']) self.assertNotIn('WARNING: ', output) - @testcase(1187) + @OETestID(1187) def test_recipetool_appendfile_renamed(self): # A file that's in SRC_URI with a different name to the destination file expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -221,7 +220,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-renamed', self.testfile, '', expectedlines, ['file1']) self.assertNotIn('WARNING: ', output) - @testcase(1190) + @OETestID(1190) def test_recipetool_appendfile_subdir(self): # A file that's in SRC_URI in a subdir expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -235,7 +234,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-subdir', self.testfile, '', expectedlines, ['testfile']) self.assertNotIn('WARNING: ', output) - @testcase(1189) + @OETestID(1189) def test_recipetool_appendfile_src_glob(self): # A file that's in SRC_URI as a glob expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -249,7 +248,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-src-globfile', self.testfile, '', expectedlines, ['testfile']) self.assertNotIn('WARNING: ', output) - @testcase(1181) + @OETestID(1181) def test_recipetool_appendfile_inst_glob(self): # A file that's in do_install as a glob expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -257,7 +256,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-globfile', self.testfile, '', expectedlines, ['selftest-replaceme-inst-globfile']) self.assertNotIn('WARNING: ', output) - @testcase(1182) + @OETestID(1182) def test_recipetool_appendfile_inst_todir_glob(self): # A file that's in do_install as a glob with destination as a directory expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -265,7 +264,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-todir-globfile', self.testfile, '', expectedlines, ['selftest-replaceme-inst-todir-globfile']) self.assertNotIn('WARNING: ', output) - @testcase(1185) + @OETestID(1185) def test_recipetool_appendfile_patch(self): # A file that's added by a patch in SRC_URI expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -284,7 +283,7 @@ class RecipetoolTests(RecipetoolBase): else: self.fail('Patch warning not found in output:\n%s' % output) - @testcase(1188) + @OETestID(1188) def test_recipetool_appendfile_script(self): # Now, a file that's in SRC_URI but installed by a script (so no mention in do_install) expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -298,7 +297,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-scripted', self.testfile, '', expectedlines, ['testfile']) self.assertNotIn('WARNING: ', output) - @testcase(1180) + @OETestID(1180) def test_recipetool_appendfile_inst_func(self): # A file that's installed from a function called by do_install expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n', @@ -306,7 +305,7 @@ class RecipetoolTests(RecipetoolBase): _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-func', self.testfile, '', expectedlines, ['selftest-replaceme-inst-func']) self.assertNotIn('WARNING: ', output) - @testcase(1186) + @OETestID(1186) def test_recipetool_appendfile_postinstall(self): # A file that's created by a postinstall script (and explicitly mentioned in it) # First try without specifying recipe @@ -322,7 +321,7 @@ class RecipetoolTests(RecipetoolBase): '}\n'] _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-postinst', self.testfile, '-r selftest-recipetool-appendfile', expectedlines, ['testfile']) - @testcase(1179) + @OETestID(1179) def test_recipetool_appendfile_extlayer(self): # Try creating a bbappend in a layer that's not in bblayers.conf and has a different structure exttemplayerdir = os.path.join(self.tempdir, 'extlayer') @@ -338,7 +337,7 @@ class RecipetoolTests(RecipetoolBase): 'metadata/recipes/recipes-test/selftest-recipetool-appendfile/selftest-recipetool-appendfile/selftest-replaceme-orig'] self.assertEqual(sorted(createdfiles), sorted(expectedfiles)) - @testcase(1192) + @OETestID(1192) def test_recipetool_appendfile_wildcard(self): def try_appendfile_wc(options): @@ -363,7 +362,7 @@ class RecipetoolTests(RecipetoolBase): filename = try_appendfile_wc('-w') self.assertEqual(filename, recipefn.split('_')[0] + '_%.bbappend') - @testcase(1193) + @OETestID(1193) def test_recipetool_create(self): # Try adding a recipe tempsrc = os.path.join(self.tempdir, 'srctree') @@ -380,7 +379,7 @@ class RecipetoolTests(RecipetoolBase): checkvars['SRC_URI[sha256sum]'] = '2e6a401cac9024db2288297e3be1a8ab60e7401ba8e91225218aaf4a27e82a07' self._test_recipe_contents(recipefile, checkvars, []) - @testcase(1194) + @OETestID(1194) def test_recipetool_create_git(self): if 'x11' not in get_bb_var('DISTRO_FEATURES'): self.skipTest('Test requires x11 as distro feature') @@ -403,7 +402,7 @@ class RecipetoolTests(RecipetoolBase): inherits = ['autotools', 'pkgconfig'] self._test_recipe_contents(recipefile, checkvars, inherits) - @testcase(1392) + @OETestID(1392) def test_recipetool_create_simple(self): # Try adding a recipe temprecipe = os.path.join(self.tempdir, 'recipe') @@ -426,7 +425,7 @@ class RecipetoolTests(RecipetoolBase): inherits = ['autotools'] self._test_recipe_contents(os.path.join(temprecipe, dirlist[0]), checkvars, inherits) - @testcase(1418) + @OETestID(1418) def test_recipetool_create_cmake(self): # Try adding a recipe temprecipe = os.path.join(self.tempdir, 'recipe') @@ -444,6 +443,7 @@ class RecipetoolTests(RecipetoolBase): inherits = ['cmake', 'python-dir', 'gettext', 'pkgconfig'] self._test_recipe_contents(recipefile, checkvars, inherits) + @OETestID(1638) def test_recipetool_create_github(self): # Basic test to see if github URL mangling works temprecipe = os.path.join(self.tempdir, 'recipe') @@ -458,6 +458,7 @@ class RecipetoolTests(RecipetoolBase): inherits = ['setuptools'] self._test_recipe_contents(recipefile, checkvars, inherits) + @OETestID(1639) def test_recipetool_create_github_tarball(self): # Basic test to ensure github URL mangling doesn't apply to release tarballs temprecipe = os.path.join(self.tempdir, 'recipe') @@ -473,6 +474,7 @@ class RecipetoolTests(RecipetoolBase): inherits = ['setuptools'] self._test_recipe_contents(recipefile, checkvars, inherits) + @OETestID(1637) def test_recipetool_create_git_http(self): # Basic test to check http git URL mangling works temprecipe = os.path.join(self.tempdir, 'recipe') @@ -500,6 +502,7 @@ class RecipetoolTests(RecipetoolBase): shutil.copy(srcfile, dstfile) self.track_for_cleanup(dstfile) + @OETestID(1640) def test_recipetool_load_plugin(self): """Test that recipetool loads only the first found plugin in BBPATH.""" @@ -621,11 +624,11 @@ class RecipetoolAppendsrcBase(RecipetoolBase): class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): - @testcase(1273) + @OETestID(1273) def test_recipetool_appendsrcfile_basic(self): self._test_appendsrcfile('base-files', 'a-file') - @testcase(1274) + @OETestID(1274) def test_recipetool_appendsrcfile_basic_wildcard(self): testrecipe = 'base-files' self._test_appendsrcfile(testrecipe, 'a-file', options='-w') @@ -633,15 +636,15 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): bbappendfile = self._check_bbappend(testrecipe, recipefile, self.templayerdir) self.assertEqual(os.path.basename(bbappendfile), '%s_%%.bbappend' % testrecipe) - @testcase(1281) + @OETestID(1281) def test_recipetool_appendsrcfile_subdir_basic(self): self._test_appendsrcfile('base-files', 'a-file', 'tmp') - @testcase(1282) + @OETestID(1282) def test_recipetool_appendsrcfile_subdir_basic_dirdest(self): self._test_appendsrcfile('base-files', destdir='tmp') - @testcase(1280) + @OETestID(1280) def test_recipetool_appendsrcfile_srcdir_basic(self): testrecipe = 'bash' bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe) @@ -650,14 +653,14 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): subdir = os.path.relpath(srcdir, workdir) self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir) - @testcase(1275) + @OETestID(1275) def test_recipetool_appendsrcfile_existing_in_src_uri(self): testrecipe = 'base-files' filepath = self._get_first_file_uri(testrecipe) self.assertTrue(filepath, 'Unable to test, no file:// uri found in SRC_URI for %s' % testrecipe) self._test_appendsrcfile(testrecipe, filepath, has_src_uri=False) - @testcase(1276) + @OETestID(1276) def test_recipetool_appendsrcfile_existing_in_src_uri_diff_params(self): testrecipe = 'base-files' subdir = 'tmp' @@ -667,7 +670,7 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): output = self._test_appendsrcfile(testrecipe, filepath, subdir, has_src_uri=False) self.assertTrue(any('with different parameters' in l for l in output)) - @testcase(1277) + @OETestID(1277) def test_recipetool_appendsrcfile_replace_file_srcdir(self): testrecipe = 'bash' filepath = 'Makefile.in' @@ -680,7 +683,7 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): bitbake('%s:do_unpack' % testrecipe) self.assertEqual(open(self.testfile, 'r').read(), open(os.path.join(srcdir, filepath), 'r').read()) - @testcase(1278) + @OETestID(1278) def test_recipetool_appendsrcfiles_basic(self, destdir=None): newfiles = [self.testfile] for i in range(1, 5): @@ -690,6 +693,6 @@ class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase): newfiles.append(testfile) self._test_appendsrcfiles('gcc', newfiles, destdir=destdir, options='-W') - @testcase(1279) + @OETestID(1279) def test_recipetool_appendsrcfiles_basic_subdir(self): self.test_recipetool_appendsrcfiles_basic(destdir='testdir') diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py new file mode 100644 index 000000000..d76d7063c --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py @@ -0,0 +1,134 @@ +from oeqa.selftest.case import OESelftestTestCase +from oeqa.utils.commands import runCmd +from oeqa.utils import CommandError +from oeqa.core.decorator.oeid import OETestID + +import subprocess +import threading +import time +import signal + +class MemLogger(object): + def __init__(self): + self.info_msgs = [] + self.error_msgs = [] + + def info(self, msg): + self.info_msgs.append(msg) + + def error(self, msg): + self.error_msgs.append(msg) + +class RunCmdTests(OESelftestTestCase): + """ Basic tests for runCmd() utility function """ + + # The delta is intentionally smaller than the timeout, to detect cases where + # we incorrectly apply the timeout more than once. + TIMEOUT = 2 + DELTA = 1 + + @OETestID(1916) + def test_result_okay(self): + result = runCmd("true") + self.assertEqual(result.status, 0) + + @OETestID(1915) + def test_result_false(self): + result = runCmd("false", ignore_status=True) + self.assertEqual(result.status, 1) + + @OETestID(1917) + def test_shell(self): + # A shell is used for all string commands. + result = runCmd("false; true", ignore_status=True) + self.assertEqual(result.status, 0) + + @OETestID(1910) + def test_no_shell(self): + self.assertRaises(FileNotFoundError, + runCmd, "false; true", shell=False) + + @OETestID(1906) + def test_list_not_found(self): + self.assertRaises(FileNotFoundError, + runCmd, ["false; true"]) + + @OETestID(1907) + def test_list_okay(self): + result = runCmd(["true"]) + self.assertEqual(result.status, 0) + + @OETestID(1913) + def test_result_assertion(self): + self.assertRaisesRegexp(AssertionError, "Command 'echo .* false' returned non-zero exit status 1:\nfoobar", + runCmd, "echo foobar >&2; false", shell=True) + + @OETestID(1914) + def test_result_exception(self): + self.assertRaisesRegexp(CommandError, "Command 'echo .* false' returned non-zero exit status 1 with output: foobar", + runCmd, "echo foobar >&2; false", shell=True, assert_error=False) + + @OETestID(1911) + def test_output(self): + result = runCmd("echo stdout; echo stderr >&2", shell=True) + self.assertEqual("stdout\nstderr", result.output) + self.assertEqual("", result.error) + + @OETestID(1912) + def test_output_split(self): + result = runCmd("echo stdout; echo stderr >&2", shell=True, stderr=subprocess.PIPE) + self.assertEqual("stdout", result.output) + self.assertEqual("stderr", result.error) + + @OETestID(1920) + def test_timeout(self): + numthreads = threading.active_count() + start = time.time() + # Killing a hanging process only works when not using a shell?! + result = runCmd(['sleep', '60'], timeout=self.TIMEOUT, ignore_status=True) + self.assertEqual(result.status, -signal.SIGTERM) + end = time.time() + self.assertLess(end - start, self.TIMEOUT + self.DELTA) + self.assertEqual(numthreads, threading.active_count()) + + @OETestID(1921) + def test_timeout_split(self): + numthreads = threading.active_count() + start = time.time() + # Killing a hanging process only works when not using a shell?! + result = runCmd(['sleep', '60'], timeout=self.TIMEOUT, ignore_status=True, stderr=subprocess.PIPE) + self.assertEqual(result.status, -signal.SIGTERM) + end = time.time() + self.assertLess(end - start, self.TIMEOUT + self.DELTA) + self.assertEqual(numthreads, threading.active_count()) + + @OETestID(1918) + def test_stdin(self): + numthreads = threading.active_count() + result = runCmd("cat", data=b"hello world", timeout=self.TIMEOUT) + self.assertEqual("hello world", result.output) + self.assertEqual(numthreads, threading.active_count()) + + @OETestID(1919) + def test_stdin_timeout(self): + numthreads = threading.active_count() + start = time.time() + result = runCmd(['sleep', '60'], data=b"hello world", timeout=self.TIMEOUT, ignore_status=True) + self.assertEqual(result.status, -signal.SIGTERM) + end = time.time() + self.assertLess(end - start, self.TIMEOUT + self.DELTA) + self.assertEqual(numthreads, threading.active_count()) + + @OETestID(1908) + def test_log(self): + log = MemLogger() + result = runCmd("echo stdout; echo stderr >&2", shell=True, output_log=log) + self.assertEqual(["Running: echo stdout; echo stderr >&2", "stdout", "stderr"], log.info_msgs) + self.assertEqual([], log.error_msgs) + + @OETestID(1909) + def test_log_split(self): + log = MemLogger() + result = runCmd("echo stdout; echo stderr >&2", shell=True, output_log=log, stderr=subprocess.PIPE) + self.assertEqual(["Running: echo stdout; echo stderr >&2", "stdout"], log.info_msgs) + self.assertEqual(["stderr"], log.error_msgs) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runqemu.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runqemu.py index 58c6f96f9..47d41f521 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runqemu.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runqemu.py @@ -3,28 +3,26 @@ # import re -import logging -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import bitbake, runqemu, get_bb_var -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class RunqemuTests(oeSelfTest): +class RunqemuTests(OESelftestTestCase): """Runqemu test class""" image_is_ready = False deploy_dir_image = '' + # We only want to print runqemu stdout/stderr if there is a test case failure + buffer = True def setUpLocal(self): + super(RunqemuTests, self).setUpLocal() self.recipe = 'core-image-minimal' self.machine = 'qemux86-64' - self.fstypes = "ext4 iso hddimg vmdk qcow2 vdi" + self.fstypes = "ext4 iso hddimg wic.vmdk wic.qcow2 wic.vdi" self.cmd_common = "runqemu nographic" - # Avoid emit the same record multiple times. - mainlogger = logging.getLogger("BitBake.Main") - mainlogger.propagate = False - self.write_config( """ MACHINE = "%s" @@ -40,14 +38,14 @@ SYSLINUX_TIMEOUT = "10" bitbake(self.recipe) RunqemuTests.image_is_ready = True - @testcase(2001) + @OETestID(2001) def test_boot_machine(self): """Test runqemu machine""" cmd = "%s %s" % (self.cmd_common, self.machine) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd) - @testcase(2002) + @OETestID(2002) def test_boot_machine_ext4(self): """Test runqemu machine ext4""" cmd = "%s %s ext4" % (self.cmd_common, self.machine) @@ -55,45 +53,45 @@ SYSLINUX_TIMEOUT = "10" with open(qemu.qemurunnerlog) as f: self.assertTrue('rootfs.ext4' in f.read(), "Failed: %s" % cmd) - @testcase(2003) + @OETestID(2003) def test_boot_machine_iso(self): """Test runqemu machine iso""" cmd = "%s %s iso" % (self.cmd_common, self.machine) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: with open(qemu.qemurunnerlog) as f: - self.assertTrue(' -cdrom ' in f.read(), "Failed: %s" % cmd) + self.assertTrue('media=cdrom' in f.read(), "Failed: %s" % cmd) - @testcase(2004) + @OETestID(2004) def test_boot_recipe_image(self): """Test runqemu recipe-image""" cmd = "%s %s" % (self.cmd_common, self.recipe) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd) - @testcase(2005) + @OETestID(2005) def test_boot_recipe_image_vmdk(self): """Test runqemu recipe-image vmdk""" - cmd = "%s %s vmdk" % (self.cmd_common, self.recipe) + cmd = "%s %s wic.vmdk" % (self.cmd_common, self.recipe) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: with open(qemu.qemurunnerlog) as f: self.assertTrue('format=vmdk' in f.read(), "Failed: %s" % cmd) - @testcase(2006) + @OETestID(2006) def test_boot_recipe_image_vdi(self): """Test runqemu recipe-image vdi""" - cmd = "%s %s vdi" % (self.cmd_common, self.recipe) + cmd = "%s %s wic.vdi" % (self.cmd_common, self.recipe) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: with open(qemu.qemurunnerlog) as f: self.assertTrue('format=vdi' in f.read(), "Failed: %s" % cmd) - @testcase(2007) + @OETestID(2007) def test_boot_deploy(self): """Test runqemu deploy_dir_image""" cmd = "%s %s" % (self.cmd_common, self.deploy_dir_image) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd) - @testcase(2008) + @OETestID(2008) def test_boot_deploy_hddimg(self): """Test runqemu deploy_dir_image hddimg""" cmd = "%s %s hddimg" % (self.cmd_common, self.deploy_dir_image) @@ -101,7 +99,7 @@ SYSLINUX_TIMEOUT = "10" with open(qemu.qemurunnerlog) as f: self.assertTrue(re.search('file=.*.hddimg', f.read()), "Failed: %s" % cmd) - @testcase(2009) + @OETestID(2009) def test_boot_machine_slirp(self): """Test runqemu machine slirp""" cmd = "%s slirp %s" % (self.cmd_common, self.machine) @@ -109,15 +107,15 @@ SYSLINUX_TIMEOUT = "10" with open(qemu.qemurunnerlog) as f: self.assertTrue(' -netdev user' in f.read(), "Failed: %s" % cmd) - @testcase(2009) + @OETestID(2009) def test_boot_machine_slirp_qcow2(self): """Test runqemu machine slirp qcow2""" - cmd = "%s slirp qcow2 %s" % (self.cmd_common, self.machine) + cmd = "%s slirp wic.qcow2 %s" % (self.cmd_common, self.machine) with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: with open(qemu.qemurunnerlog) as f: self.assertTrue('format=qcow2' in f.read(), "Failed: %s" % cmd) - @testcase(2010) + @OETestID(2010) def test_boot_qemu_boot(self): """Test runqemu /path/to/image.qemuboot.conf""" qemuboot_conf = "%s-%s.qemuboot.conf" % (self.recipe, self.machine) @@ -128,7 +126,7 @@ SYSLINUX_TIMEOUT = "10" with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu: self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd) - @testcase(2011) + @OETestID(2011) def test_boot_rootfs(self): """Test runqemu /path/to/rootfs.ext4""" rootfs = "%s-%s.ext4" % (self.recipe, self.machine) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runtime_test.py index e498d046c..25270b753 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runtime_test.py @@ -1,15 +1,20 @@ -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu -from oeqa.utils.decorators import testcase +from oeqa.utils.sshcontrol import SSHControl +from oeqa.core.decorator.oeid import OETestID import os import re +import tempfile +import shutil -class TestExport(oeSelfTest): +class TestExport(OESelftestTestCase): @classmethod def tearDownClass(cls): runCmd("rm -rf /tmp/sdk") + super(TestExport, cls).tearDownClass() + @OETestID(1499) def test_testexport_basic(self): """ Summary: Check basic testexport functionality with only ping test enabled. @@ -49,6 +54,7 @@ class TestExport(oeSelfTest): # Verify ping test was succesful self.assertEqual(0, result.status, 'oe-test runtime returned a non 0 status') + @OETestID(1641) def test_testexport_sdk(self): """ Summary: Check sdk functionality for testexport. @@ -101,36 +107,66 @@ class TestExport(oeSelfTest): self.assertEqual(0, result.status, "Couldn't run tar from SDK") -class TestImage(oeSelfTest): +class TestImage(OESelftestTestCase): + @OETestID(1644) def test_testimage_install(self): """ Summary: Check install packages functionality for testimage/testexport. Expected: 1. Import tests from a directory other than meta. 2. Check install/uninstall of socat. - 3. Check that remote package feeds can be accessed Product: oe-core Author: Mariano Lopez <mariano.lopez@intel.com> - Author: Alexander Kanavin <alexander.kanavin@intel.com> """ if get_bb_var('DISTRO') == 'poky-tiny': self.skipTest('core-image-full-cmdline not buildable for poky-tiny') features = 'INHERIT += "testimage"\n' features += 'TEST_SUITES = "ping ssh selftest"\n' + self.write_config(features) + + # Build core-image-sato and testimage + bitbake('core-image-full-cmdline socat') + bitbake('-c testimage core-image-full-cmdline') + + @OETestID(1883) + def test_testimage_dnf(self): + """ + Summary: Check package feeds functionality for dnf + Expected: 1. Check that remote package feeds can be accessed + Product: oe-core + Author: Alexander Kanavin <alexander.kanavin@intel.com> + """ + if get_bb_var('DISTRO') == 'poky-tiny': + self.skipTest('core-image-full-cmdline not buildable for poky-tiny') + + features = 'INHERIT += "testimage"\n' + features += 'TEST_SUITES = "ping ssh dnf_runtime dnf.DnfBasicTest.test_dnf_help"\n' # We don't yet know what the server ip and port will be - they will be patched # in at the start of the on-image test features += 'PACKAGE_FEED_URIS = "http://bogus_ip:bogus_port"\n' features += 'EXTRA_IMAGE_FEATURES += "package-management"\n' - features += 'PACKAGE_CLASSES = "package_rpm"' + features += 'PACKAGE_CLASSES = "package_rpm"\n' + + # Enable package feed signing + self.gpg_home = tempfile.mkdtemp(prefix="oeqa-feed-sign-") + signing_key_dir = os.path.join(self.testlayer_path, 'files', 'signing') + runCmd('gpg --batch --homedir %s --import %s' % (self.gpg_home, os.path.join(signing_key_dir, 'key.secret'))) + features += 'INHERIT += "sign_package_feed"\n' + features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n' + features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase') + features += 'GPG_PATH = "%s"\n' % self.gpg_home self.write_config(features) # Build core-image-sato and testimage bitbake('core-image-full-cmdline socat') bitbake('-c testimage core-image-full-cmdline') -class Postinst(oeSelfTest): - @testcase(1540) + # remove the oeqa-feed-sign temporal directory + shutil.rmtree(self.gpg_home, ignore_errors=True) + +class Postinst(OESelftestTestCase): + @OETestID(1540) def test_verify_postinst(self): """ Summary: The purpose of this test is to verify the execution order of postinst Bugzilla ID: [5319] @@ -180,7 +216,7 @@ postinst-delayed-t \ self.assertEqual(idx, len(postinst_list), "Not found all postinsts") break - @testcase(1545) + @OETestID(1545) def test_postinst_rootfs_and_boot(self): """ Summary: The purpose of this test case is to verify Post-installation @@ -202,36 +238,30 @@ postinst-delayed-t \ fileboot_name = "this-was-created-at-first-boot" rootfs_pkg = 'postinst-at-rootfs' boot_pkg = 'postinst-delayed-a' - #Step 1 - features = 'MACHINE = "qemux86"\n' - features += 'CORE_IMAGE_EXTRA_INSTALL += "%s %s "\n'% (rootfs_pkg, boot_pkg) - features += 'IMAGE_FEATURES += "ssh-server-openssh"\n' + for init_manager in ("sysvinit", "systemd"): - #for sysvinit no extra configuration is needed, - if (init_manager is "systemd"): - features += 'DISTRO_FEATURES_append = " systemd"\n' - features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n' - features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n' - features += 'VIRTUAL-RUNTIME_initscripts = ""\n' - for classes in ("package_rpm package_deb package_ipk", - "package_deb package_rpm package_ipk", - "package_ipk package_deb package_rpm"): - features += 'PACKAGE_CLASSES = "%s"\n' % classes - self.write_config(features) - - #Step 2 - bitbake('core-image-minimal') - - #Step 3 - file_rootfs_created = os.path.join(get_bb_var('IMAGE_ROOTFS',"core-image-minimal"), - file_rootfs_name) - found = os.path.isfile(file_rootfs_created) - self.assertTrue(found, "File %s was not created at rootfs time by %s" % \ - (file_rootfs_name, rootfs_pkg)) - - #Step 4 - testcommand = 'ls /etc/'+fileboot_name - with runqemu('core-image-minimal') as qemu: - sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' - result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand)) - self.assertEqual(result.status, 0, 'File %s was not created at firts boot'% fileboot_name) + for classes in ("package_rpm", "package_deb", "package_ipk"): + with self.subTest(init_manager=init_manager, package_class=classes): + features = 'MACHINE = "qemux86"\n' + features += 'CORE_IMAGE_EXTRA_INSTALL += "%s %s "\n'% (rootfs_pkg, boot_pkg) + features += 'IMAGE_FEATURES += "package-management empty-root-password"\n' + features += 'PACKAGE_CLASSES = "%s"\n' % classes + if init_manager == "systemd": + features += 'DISTRO_FEATURES_append = " systemd"\n' + features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n' + features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n' + features += 'VIRTUAL-RUNTIME_initscripts = ""\n' + self.write_config(features) + + bitbake('core-image-minimal') + + file_rootfs_created = os.path.join(get_bb_var('IMAGE_ROOTFS', "core-image-minimal"), + file_rootfs_name) + found = os.path.isfile(file_rootfs_created) + self.assertTrue(found, "File %s was not created at rootfs time by %s" % \ + (file_rootfs_name, rootfs_pkg)) + + testcommand = 'ls /etc/' + fileboot_name + with runqemu('core-image-minimal') as qemu: + status, output = qemu.run_serial("-f /etc/" + fileboot_name) + self.assertEqual(status, 0, 'File %s was not created at first boot (%s)' % (fileboot_name, output)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py new file mode 100644 index 000000000..4b3cb1446 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py @@ -0,0 +1,51 @@ +import importlib +from oeqa.utils.commands import runCmd +import oeqa.selftest +from oeqa.selftest.case import OESelftestTestCase +from oeqa.core.decorator.oeid import OETestID + +class ExternalLayer(OESelftestTestCase): + + @OETestID(1885) + def test_list_imported(self): + """ + Summary: Checks functionality to import tests from other layers. + Expected: 1. File "external-layer.py" must be in + oeqa.selftest.__path__ + 2. test_unconditional_pas method must exists + in ImportedTests class + Product: oe-core + Author: Mariano Lopez <mariano.lopez@intel.com> + """ + + test_file = "external-layer.py" + test_module = "oeqa.selftest.cases.external-layer" + method_name = "test_unconditional_pass" + + # Check if "external-layer.py" is in oeqa path + found_file = search_test_file(test_file) + self.assertTrue(found_file, msg="Can't find %s in the oeqa path" % test_file) + + # Import oeqa.selftest.external-layer module and search for + # test_unconditional_pass method of ImportedTests class + found_method = search_method(test_module, method_name) + self.assertTrue(method_name, msg="Can't find %s method" % method_name) + +def search_test_file(file_name): + for layer_path in oeqa.selftest.__path__: + for _, _, files in os.walk(layer_path): + for f in files: + if f == file_name: + return True + return False + +def search_method(module, method): + modlib = importlib.import_module(module) + for var in vars(modlib): + klass = vars(modlib)[var] + if isinstance(klass, type(OESelftestTestCase)) and issubclass(klass, OESelftestTestCase): + for m in dir(klass): + if m == method: + return True + return False + diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/signing.py index 0ac3d1fac..b3d1a8292 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/signing.py @@ -1,15 +1,15 @@ -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars import os import glob import re import shutil import tempfile -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID from oeqa.utils.ftools import write_file -class Signing(oeSelfTest): +class Signing(OESelftestTestCase): gpg_dir = "" pub_key_path = "" @@ -17,19 +17,23 @@ class Signing(oeSelfTest): @classmethod def setUpClass(cls): + super(Signing, cls).setUpClass() # Check that we can find the gpg binary and fail early if we can't if not shutil.which("gpg"): raise AssertionError("This test needs GnuPG") - cls.gpg_home_dir = tempfile.TemporaryDirectory(prefix="oeqa-signing-") - cls.gpg_dir = cls.gpg_home_dir.name + cls.gpg_dir = tempfile.mkdtemp(prefix="oeqa-signing-") cls.pub_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.pub") cls.secret_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.secret") runCmd('gpg --batch --homedir %s --import %s %s' % (cls.gpg_dir, cls.pub_key_path, cls.secret_key_path)) - @testcase(1362) + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls.gpg_dir, ignore_errors=True) + + @OETestID(1362) def test_signing_packages(self): """ Summary: Test that packages can be signed in the package feed @@ -92,7 +96,7 @@ class Signing(oeSelfTest): bitbake('core-image-minimal') - @testcase(1382) + @OETestID(1382) def test_signing_sstate_archive(self): """ Summary: Test that sstate archives can be signed @@ -135,9 +139,9 @@ class Signing(oeSelfTest): self.assertIn('gpg: Good signature from', ret.output, 'Package signed incorrectly.') -class LockedSignatures(oeSelfTest): +class LockedSignatures(OESelftestTestCase): - @testcase(1420) + @OETestID(1420) def test_locked_signatures(self): """ Summary: Test locked signature mechanism diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstate.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstate.py index f54bc4146..bc2fdbd8c 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstate.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstate.py @@ -5,13 +5,14 @@ import re import shutil import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_test_layer -class SStateBase(oeSelfTest): +class SStateBase(OESelftestTestCase): def setUpLocal(self): + super(SStateBase, self).setUpLocal() self.temp_sstate_location = None needed_vars = ['SSTATE_DIR', 'NATIVELSBSTRING', 'TCLIBC', 'TUNE_ARCH', 'TOPDIR', 'TARGET_VENDOR', 'TARGET_OS'] diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstatetests.py index e35ddfff5..47900886a 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstatetests.py @@ -1,16 +1,14 @@ -import datetime -import unittest import os -import re import shutil import glob import subprocess -import oeqa.utils.ftools as ftools -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer -from oeqa.selftest.sstate import SStateBase -from oeqa.utils.decorators import testcase +from oeqa.selftest.cases.sstate import SStateBase +from oeqa.core.decorator.oeid import OETestID + +import bb.siggen class SStateTests(SStateBase): @@ -39,19 +37,19 @@ class SStateTests(SStateBase): else: self.assertTrue(not file_tracker , msg="Found sstate files in the wrong place for: %s (found %s)" % (', '.join(map(str, targets)), str(file_tracker))) - @testcase(975) + @OETestID(975) def test_sstate_creation_distro_specific_pass(self): self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True) - @testcase(1374) + @OETestID(1374) def test_sstate_creation_distro_specific_fail(self): self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True, should_pass=False) - @testcase(976) + @OETestID(976) def test_sstate_creation_distro_nonspecific_pass(self): self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) - @testcase(1375) + @OETestID(1375) def test_sstate_creation_distro_nonspecific_fail(self): self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True, should_pass=False) @@ -72,17 +70,17 @@ class SStateTests(SStateBase): tgz_removed = self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific, distro_nonspecific) self.assertTrue(not tgz_removed, msg="do_cleansstate didn't remove .tgz sstate files for: %s (%s)" % (', '.join(map(str, targets)), str(tgz_removed))) - @testcase(977) + @OETestID(977) def test_cleansstate_task_distro_specific_nonspecific(self): targets = ['binutils-cross-'+ self.tune_arch, 'binutils-native'] targets.append('linux-libc-headers') self.run_test_cleansstate_task(targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True) - @testcase(1376) + @OETestID(1376) def test_cleansstate_task_distro_nonspecific(self): self.run_test_cleansstate_task(['linux-libc-headers'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) - @testcase(1377) + @OETestID(1377) def test_cleansstate_task_distro_specific(self): targets = ['binutils-cross-'+ self.tune_arch, 'binutils-native'] targets.append('linux-libc-headers') @@ -121,15 +119,15 @@ class SStateTests(SStateBase): created_once = [x for x in file_tracker_2 if x not in file_tracker_1] self.assertTrue(created_once == [], msg="The following sstate files ware created only in the second run: %s" % ', '.join(map(str, created_once))) - @testcase(175) + @OETestID(175) def test_rebuild_distro_specific_sstate_cross_native_targets(self): self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch, 'binutils-native'], temp_sstate_location=True) - @testcase(1372) + @OETestID(1372) def test_rebuild_distro_specific_sstate_cross_target(self): self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch], temp_sstate_location=True) - @testcase(1373) + @OETestID(1373) def test_rebuild_distro_specific_sstate_native_target(self): self.run_test_rebuild_distro_specific_sstate(['binutils-native'], temp_sstate_location=True) @@ -176,7 +174,7 @@ class SStateTests(SStateBase): expected_not_actual = [x for x in expected_remaining_sstate if x not in actual_remaining_sstate] self.assertFalse(expected_not_actual, msg="Extra files ware removed: %s" ', '.join(map(str, expected_not_actual))) - @testcase(973) + @OETestID(973) def test_sstate_cache_management_script_using_pr_1(self): global_config = [] target_config = [] @@ -184,7 +182,7 @@ class SStateTests(SStateBase): target_config.append('PR = "0"') self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) - @testcase(978) + @OETestID(978) def test_sstate_cache_management_script_using_pr_2(self): global_config = [] target_config = [] @@ -194,7 +192,7 @@ class SStateTests(SStateBase): target_config.append('PR = "1"') self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) - @testcase(979) + @OETestID(979) def test_sstate_cache_management_script_using_pr_3(self): global_config = [] target_config = [] @@ -206,7 +204,7 @@ class SStateTests(SStateBase): target_config.append('PR = "1"') self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) - @testcase(974) + @OETestID(974) def test_sstate_cache_management_script_using_machine(self): global_config = [] target_config = [] @@ -216,7 +214,7 @@ class SStateTests(SStateBase): target_config.append('') self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) - @testcase(1270) + @OETestID(1270) def test_sstate_32_64_same_hash(self): """ The sstate checksums for both native and target should not vary whether @@ -263,7 +261,7 @@ PACKAGE_CLASSES = "package_rpm package_ipk package_deb" self.assertCountEqual(files1, files2) - @testcase(1271) + @OETestID(1271) def test_sstate_nativelsbstring_same_hash(self): """ The sstate checksums should be independent of whichever NATIVELSBSTRING is @@ -295,7 +293,7 @@ NATIVELSBSTRING = \"DistroB\" self.maxDiff = None self.assertCountEqual(files1, files2) - @testcase(1368) + @OETestID(1368) def test_sstate_allarch_samesigs(self): """ The sstate checksums of allarch packages should be independent of whichever @@ -314,6 +312,7 @@ MACHINE = \"qemuarm\" """ self.sstate_allarch_samesigs(configA, configB) + @OETestID(1645) def test_sstate_allarch_samesigs_multilib(self): """ The sstate checksums of allarch multilib packages should be independent of whichever @@ -369,7 +368,7 @@ MULTILIBS = \"\" self.maxDiff = None self.assertEqual(files1, files2) - @testcase(1369) + @OETestID(1369) def test_sstate_sametune_samesigs(self): """ The sstate checksums of two identical machines (using the same tune) should be the @@ -414,6 +413,7 @@ DEFAULTTUNE_virtclass-multilib-lib32 = "x86" self.assertCountEqual(files1, files2) + @OETestID(1498) def test_sstate_noop_samesigs(self): """ The sstate checksums of two builds with these variables changed or @@ -422,7 +422,7 @@ DEFAULTTUNE_virtclass-multilib-lib32 = "x86" self.write_config(""" TMPDIR = "${TOPDIR}/tmp-sstatesamehash" -BB_NUMBER_THREADS = "1" +BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}" PARALLEL_MAKE = "-j 1" DL_DIR = "${TOPDIR}/download1" TIME = "111111" @@ -435,7 +435,7 @@ http_proxy = "" bitbake("world meta-toolchain -S none") self.write_config(""" TMPDIR = "${TOPDIR}/tmp-sstatesamehash2" -BB_NUMBER_THREADS = "2" +BB_NUMBER_THREADS = "${@oe.utils.cpu_count()+1}" PARALLEL_MAKE = "-j 2" DL_DIR = "${TOPDIR}/download2" TIME = "222222" @@ -458,6 +458,24 @@ http_proxy = "http://example.com/" base = os.sep.join(root.rsplit(os.sep, 2)[-2:] + [name]) f[base] = shash return f + + def compare_sigfiles(files, files1, files2, compare=False): + for k in files: + if k in files1 and k in files2: + print("%s differs:" % k) + if compare: + sigdatafile1 = self.topdir + "/tmp-sstatesamehash/stamps/" + k + "." + files1[k] + sigdatafile2 = self.topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k] + output = bb.siggen.compare_sigfiles(sigdatafile1, sigdatafile2) + if output: + print('\n'.join(output)) + elif k in files1 and k not in files2: + print("%s in files1" % k) + elif k not in files1 and k in files2: + print("%s in files2" % k) + else: + assert "shouldn't reach here" + files1 = get_files(self.topdir + "/tmp-sstatesamehash/stamps/") files2 = get_files(self.topdir + "/tmp-sstatesamehash2/stamps/") # Remove items that are identical in both sets @@ -468,16 +486,11 @@ http_proxy = "http://example.com/" # No changes, so we're done return - for k in files1.keys() | files2.keys(): - if k in files1 and k in files2: - print("%s differs:" % k) - print(subprocess.check_output(("bitbake-diffsigs", - self.topdir + "/tmp-sstatesamehash/stamps/" + k + "." + files1[k], - self.topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k]))) - elif k in files1 and k not in files2: - print("%s in files1" % k) - elif k not in files1 and k in files2: - print("%s in files2" % k) - else: - assert "shouldn't reach here" + files = list(files1.keys() | files2.keys()) + # this is an expensive computation, thus just compare the first 'max_sigfiles_to_compare' k files + max_sigfiles_to_compare = 20 + first, rest = files[:max_sigfiles_to_compare], files[max_sigfiles_to_compare:] + compare_sigfiles(first, files1.keys(), files2.keys(), compare=True) + compare_sigfiles(rest, files1.keys(), files2.keys(), compare=False) + self.fail("sstate hashes not identical.") diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/tinfoil.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/tinfoil.py index 73a0c3bac..f889a47b2 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/tinfoil.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/tinfoil.py @@ -1,16 +1,17 @@ -import unittest import os import re +import time +import logging import bb.tinfoil -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID -class TinfoilTests(oeSelfTest): +class TinfoilTests(OESelftestTestCase): """ Basic tests for the tinfoil API """ - @testcase(1568) + @OETestID(1568) def test_getvar(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(True) @@ -18,7 +19,7 @@ class TinfoilTests(oeSelfTest): if not machine: self.fail('Unable to get MACHINE value - returned %s' % machine) - @testcase(1569) + @OETestID(1569) def test_expand(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(True) @@ -27,7 +28,7 @@ class TinfoilTests(oeSelfTest): if not pid: self.fail('Unable to expand "%s" - returned %s' % (expr, pid)) - @testcase(1570) + @OETestID(1570) def test_getvar_bb_origenv(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(True) @@ -36,7 +37,7 @@ class TinfoilTests(oeSelfTest): self.fail('Unable to get BB_ORIGENV value - returned %s' % origenv) self.assertEqual(origenv.getVar('HOME', False), os.environ['HOME']) - @testcase(1571) + @OETestID(1571) def test_parse_recipe(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=False, quiet=2) @@ -47,7 +48,7 @@ class TinfoilTests(oeSelfTest): rd = tinfoil.parse_recipe_file(best[3]) self.assertEqual(testrecipe, rd.getVar('PN')) - @testcase(1572) + @OETestID(1572) def test_parse_recipe_copy_expand(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=False, quiet=2) @@ -66,7 +67,7 @@ class TinfoilTests(oeSelfTest): localdata.setVar('PN', 'hello') self.assertEqual('hello', localdata.getVar('BPN')) - @testcase(1573) + @OETestID(1573) def test_parse_recipe_initial_datastore(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=False, quiet=2) @@ -80,7 +81,7 @@ class TinfoilTests(oeSelfTest): # Check we can get variable values self.assertEqual('somevalue', rd.getVar('MYVARIABLE')) - @testcase(1574) + @OETestID(1574) def test_list_recipes(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=False, quiet=2) @@ -99,38 +100,43 @@ class TinfoilTests(oeSelfTest): if checkpns: self.fail('Unable to find pkg_fn entries for: %s' % ', '.join(checkpns)) - @testcase(1575) + @OETestID(1575) def test_wait_event(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=True) - # Need to drain events otherwise events that will be masked will still be in the queue - while tinfoil.wait_event(0.25): - pass + tinfoil.set_event_mask(['bb.event.FilesMatchingFound', 'bb.command.CommandCompleted']) + + # Need to drain events otherwise events that were masked may still be in the queue + while tinfoil.wait_event(): + pass + pattern = 'conf' res = tinfoil.run_command('findFilesMatchingInDir', pattern, 'conf/machine') self.assertTrue(res) eventreceived = False - waitcount = 5 - while waitcount > 0: + commandcomplete = False + start = time.time() + # Wait for 5s in total so we'd detect spurious heartbeat events for example + while time.time() - start < 5: event = tinfoil.wait_event(1) if event: if isinstance(event, bb.command.CommandCompleted): - break + commandcomplete = True elif isinstance(event, bb.event.FilesMatchingFound): self.assertEqual(pattern, event._pattern) self.assertIn('qemuarm.conf', event._matches) eventreceived = True + elif isinstance(event, logging.LogRecord): + continue else: self.fail('Unexpected event: %s' % event) - waitcount = waitcount - 1 - - self.assertNotEqual(waitcount, 0, 'Timed out waiting for CommandCompleted event from bitbake server') + self.assertTrue(commandcomplete, 'Timed out waiting for CommandCompleted event from bitbake server') self.assertTrue(eventreceived, 'Did not receive FilesMatchingFound event from bitbake server') - @testcase(1576) + @OETestID(1576) def test_setvariable_clean(self): # First check that setVariable affects the datastore with bb.tinfoil.Tinfoil() as tinfoil: @@ -153,6 +159,7 @@ class TinfoilTests(oeSelfTest): value = tinfoil.run_command('getVariable', 'TESTVAR') self.assertEqual(value, 'specialvalue', 'Value set using config_data.setVar() is not reflected in config_data.getVar()') + @OETestID(1884) def test_datastore_operations(self): with bb.tinfoil.Tinfoil() as tinfoil: tinfoil.prepare(config_only=True) @@ -188,3 +195,37 @@ class TinfoilTests(oeSelfTest): tinfoil.config_data.appendVar('OVERRIDES', ':overrideone') value = tinfoil.config_data.getVar('TESTVAR') self.assertEqual(value, 'one', 'Variable overrides not functioning correctly') + + def test_variable_history(self): + # Basic test to ensure that variable history works when tracking=True + with bb.tinfoil.Tinfoil(tracking=True) as tinfoil: + tinfoil.prepare(config_only=False, quiet=2) + # Note that _tracking for any datastore we get will be + # false here, that's currently expected - so we can't check + # for that + history = tinfoil.config_data.varhistory.variable('DL_DIR') + for entry in history: + if entry['file'].endswith('/bitbake.conf'): + if entry['op'] in ['set', 'set?']: + break + else: + self.fail('Did not find history entry setting DL_DIR in bitbake.conf. History: %s' % history) + # Check it works for recipes as well + testrecipe = 'zlib' + rd = tinfoil.parse_recipe(testrecipe) + history = rd.varhistory.variable('LICENSE') + bbfound = -1 + recipefound = -1 + for i, entry in enumerate(history): + if entry['file'].endswith('/bitbake.conf'): + if entry['detail'] == 'INVALID' and entry['op'] in ['set', 'set?']: + bbfound = i + elif entry['file'].endswith('.bb'): + if entry['op'] == 'set': + recipefound = i + if bbfound == -1: + self.fail('Did not find history entry setting LICENSE in bitbake.conf parsing %s recipe. History: %s' % (testrecipe, history)) + if recipefound == -1: + self.fail('Did not find history entry setting LICENSE in %s recipe. History: %s' % (testrecipe, history)) + if bbfound > recipefound: + self.fail('History entry setting LICENSE in %s recipe and in bitbake.conf in wrong order. History: %s' % (testrecipe, history)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/wic.py index 726af19e9..651d575dc 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/wic.py @@ -28,13 +28,13 @@ import sys import unittest from glob import glob -from shutil import rmtree +from shutil import rmtree, copy from functools import wraps, lru_cache from tempfile import NamedTemporaryFile -from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.case import OESelftestTestCase from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu -from oeqa.utils.decorators import testcase +from oeqa.core.decorator.oeid import OETestID @lru_cache(maxsize=32) @@ -61,7 +61,7 @@ def only_for_arch(archs, image='core-image-minimal'): return wrapper -class Wic(oeSelfTest): +class Wic(OESelftestTestCase): """Wic test class.""" resultdir = "/var/tmp/wic.oe-selftest/" @@ -71,6 +71,7 @@ class Wic(oeSelfTest): def setUpLocal(self): """This code is executed before each test method.""" + super(Wic, self).setUpLocal() if not self.native_sysroot: Wic.native_sysroot = get_bb_var('STAGING_DIR_NATIVE', 'wic-tools') @@ -91,64 +92,65 @@ class Wic(oeSelfTest): def tearDownLocal(self): """Remove resultdir as it may contain images.""" rmtree(self.resultdir, ignore_errors=True) + super(Wic, self).tearDownLocal() - @testcase(1552) + @OETestID(1552) def test_version(self): """Test wic --version""" self.assertEqual(0, runCmd('wic --version').status) - @testcase(1208) + @OETestID(1208) def test_help(self): """Test wic --help and wic -h""" self.assertEqual(0, runCmd('wic --help').status) self.assertEqual(0, runCmd('wic -h').status) - @testcase(1209) + @OETestID(1209) def test_createhelp(self): """Test wic create --help""" self.assertEqual(0, runCmd('wic create --help').status) - @testcase(1210) + @OETestID(1210) def test_listhelp(self): """Test wic list --help""" self.assertEqual(0, runCmd('wic list --help').status) - @testcase(1553) + @OETestID(1553) def test_help_create(self): """Test wic help create""" self.assertEqual(0, runCmd('wic help create').status) - @testcase(1554) + @OETestID(1554) def test_help_list(self): """Test wic help list""" self.assertEqual(0, runCmd('wic help list').status) - @testcase(1215) + @OETestID(1215) def test_help_overview(self): """Test wic help overview""" self.assertEqual(0, runCmd('wic help overview').status) - @testcase(1216) + @OETestID(1216) def test_help_plugins(self): """Test wic help plugins""" self.assertEqual(0, runCmd('wic help plugins').status) - @testcase(1217) + @OETestID(1217) def test_help_kickstart(self): """Test wic help kickstart""" self.assertEqual(0, runCmd('wic help kickstart').status) - @testcase(1555) + @OETestID(1555) def test_list_images(self): """Test wic list images""" self.assertEqual(0, runCmd('wic list images').status) - @testcase(1556) + @OETestID(1556) def test_list_source_plugins(self): """Test wic list source-plugins""" self.assertEqual(0, runCmd('wic list source-plugins').status) - @testcase(1557) + @OETestID(1557) def test_listed_images_help(self): """Test wic listed images help""" output = runCmd('wic list images').output @@ -156,25 +158,24 @@ class Wic(oeSelfTest): for image in imagelist: self.assertEqual(0, runCmd('wic list %s help' % image).status) - @testcase(1213) + @OETestID(1213) def test_unsupported_subcommand(self): """Test unsupported subcommand""" - self.assertEqual(1, runCmd('wic unsupported', - ignore_status=True).status) + self.assertNotEqual(0, runCmd('wic unsupported', ignore_status=True).status) - @testcase(1214) + @OETestID(1214) def test_no_command(self): """Test wic without command""" self.assertEqual(1, runCmd('wic', ignore_status=True).status) - @testcase(1211) + @OETestID(1211) def test_build_image_name(self): """Test wic create wictestdisk --image-name=core-image-minimal""" cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) - @testcase(1157) + @OETestID(1157) @only_for_arch(['i586', 'i686', 'x86_64']) def test_gpt_image(self): """Test creation of core-image-minimal with gpt table and UUID boot""" @@ -182,12 +183,13 @@ class Wic(oeSelfTest): self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "directdisk-*.direct"))) - @testcase(1346) + @OETestID(1346) @only_for_arch(['i586', 'i686', 'x86_64']) def test_iso_image(self): """Test creation of hybrid iso image with legacy and EFI boot""" config = 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\ - 'MACHINE_FEATURES_append = " efi"\n' + 'MACHINE_FEATURES_append = " efi"\n'\ + 'DEPENDS_pn-core-image-minimal += "syslinux"\n' self.append_config(config) bitbake('core-image-minimal') self.remove_config(config) @@ -196,7 +198,7 @@ class Wic(oeSelfTest): self.assertEqual(1, len(glob(self.resultdir + "HYBRID_ISO_IMG-*.direct"))) self.assertEqual(1, len(glob(self.resultdir + "HYBRID_ISO_IMG-*.iso"))) - @testcase(1348) + @OETestID(1348) @only_for_arch(['i586', 'i686', 'x86_64']) def test_qemux86_directdisk(self): """Test creation of qemux-86-directdisk image""" @@ -204,7 +206,7 @@ class Wic(oeSelfTest): self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "qemux86-directdisk-*direct"))) - @testcase(1350) + @OETestID(1350) @only_for_arch(['i586', 'i686', 'x86_64']) def test_mkefidisk(self): """Test creation of mkefidisk image""" @@ -212,15 +214,19 @@ class Wic(oeSelfTest): self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "mkefidisk-*direct"))) - @testcase(1385) + @OETestID(1385) @only_for_arch(['i586', 'i686', 'x86_64']) def test_bootloader_config(self): """Test creation of directdisk-bootloader-config image""" + config = 'DEPENDS_pn-core-image-minimal += "syslinux"\n' + self.append_config(config) + bitbake('core-image-minimal') + self.remove_config(config) cmd = "wic create directdisk-bootloader-config -e core-image-minimal -o %s" % self.resultdir self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "directdisk-bootloader-config-*direct"))) - @testcase(1560) + @OETestID(1560) @only_for_arch(['i586', 'i686', 'x86_64']) def test_systemd_bootdisk(self): """Test creation of systemd-bootdisk image""" @@ -232,7 +238,7 @@ class Wic(oeSelfTest): self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "systemd-bootdisk-*direct"))) - @testcase(1561) + @OETestID(1561) def test_sdimage_bootpart(self): """Test creation of sdimage-bootpart image""" cmd = "wic create sdimage-bootpart -e core-image-minimal -o %s" % self.resultdir @@ -241,17 +247,21 @@ class Wic(oeSelfTest): self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob(self.resultdir + "sdimage-bootpart-*direct"))) - @testcase(1562) + @OETestID(1562) @only_for_arch(['i586', 'i686', 'x86_64']) def test_default_output_dir(self): """Test default output location""" for fname in glob("directdisk-*.direct"): os.remove(fname) + config = 'DEPENDS_pn-core-image-minimal += "syslinux"\n' + self.append_config(config) + bitbake('core-image-minimal') + self.remove_config(config) cmd = "wic create directdisk -e core-image-minimal" self.assertEqual(0, runCmd(cmd).status) self.assertEqual(1, len(glob("directdisk-*.direct"))) - @testcase(1212) + @OETestID(1212) @only_for_arch(['i586', 'i686', 'x86_64']) def test_build_artifacts(self): """Test wic create directdisk providing all artifacts.""" @@ -270,7 +280,7 @@ class Wic(oeSelfTest): self.assertEqual(0, status) self.assertEqual(1, len(glob(self.resultdir + "directdisk-*.direct"))) - @testcase(1264) + @OETestID(1264) def test_compress_gzip(self): """Test compressing an image with gzip""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -278,7 +288,7 @@ class Wic(oeSelfTest): "-c gzip -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.gz"))) - @testcase(1265) + @OETestID(1265) def test_compress_bzip2(self): """Test compressing an image with bzip2""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -286,7 +296,7 @@ class Wic(oeSelfTest): "-c bzip2 -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.bz2"))) - @testcase(1266) + @OETestID(1266) def test_compress_xz(self): """Test compressing an image with xz""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -294,7 +304,7 @@ class Wic(oeSelfTest): "--compress-with=xz -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.xz"))) - @testcase(1267) + @OETestID(1267) def test_wrong_compressor(self): """Test how wic breaks if wrong compressor is provided""" self.assertEqual(2, runCmd("wic create wictestdisk " @@ -302,7 +312,7 @@ class Wic(oeSelfTest): "-c wrong -o %s" % self.resultdir, ignore_status=True).status) - @testcase(1558) + @OETestID(1558) def test_debug_short(self): """Test -D option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -310,6 +320,7 @@ class Wic(oeSelfTest): "-D -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) + @OETestID(1658) def test_debug_long(self): """Test --debug option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -317,7 +328,7 @@ class Wic(oeSelfTest): "--debug -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) - @testcase(1563) + @OETestID(1563) def test_skip_build_check_short(self): """Test -s option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -325,6 +336,7 @@ class Wic(oeSelfTest): "-s -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) + @OETestID(1671) def test_skip_build_check_long(self): """Test --skip-build-check option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -333,7 +345,7 @@ class Wic(oeSelfTest): "--outdir %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) - @testcase(1564) + @OETestID(1564) def test_build_rootfs_short(self): """Test -f option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -341,6 +353,7 @@ class Wic(oeSelfTest): "-f -o %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) + @OETestID(1656) def test_build_rootfs_long(self): """Test --build-rootfs option""" self.assertEqual(0, runCmd("wic create wictestdisk " @@ -349,7 +362,7 @@ class Wic(oeSelfTest): "--outdir %s" % self.resultdir).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct"))) - @testcase(1268) + @OETestID(1268) @only_for_arch(['i586', 'i686', 'x86_64']) def test_rootfs_indirect_recipes(self): """Test usage of rootfs plugin with rootfs recipes""" @@ -361,7 +374,7 @@ class Wic(oeSelfTest): self.assertEqual(0, status) self.assertEqual(1, len(glob(self.resultdir + "directdisk-multi-rootfs*.direct"))) - @testcase(1269) + @OETestID(1269) @only_for_arch(['i586', 'i686', 'x86_64']) def test_rootfs_artifacts(self): """Test usage of rootfs plugin with rootfs paths""" @@ -382,6 +395,7 @@ class Wic(oeSelfTest): self.assertEqual(0, status) self.assertEqual(1, len(glob(self.resultdir + "%(wks)s-*.direct" % bbvars))) + @OETestID(1661) def test_exclude_path(self): """Test --exclude-path wks option.""" @@ -489,6 +503,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r finally: os.environ['PATH'] = oldpath + @OETestID(1662) def test_exclude_path_errors(self): """Test --exclude-path wks option error handling.""" wks_file = 'temp.wks' @@ -507,7 +522,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r % (wks_file, self.resultdir), ignore_status=True).status) os.remove(wks_file) - @testcase(1496) + @OETestID(1496) def test_bmap_short(self): """Test generation of .bmap file -m option""" cmd = "wic create wictestdisk -e core-image-minimal -m -o %s" % self.resultdir @@ -516,6 +531,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct"))) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct.bmap"))) + @OETestID(1655) def test_bmap_long(self): """Test generation of .bmap file --bmap option""" cmd = "wic create wictestdisk -e core-image-minimal --bmap -o %s" % self.resultdir @@ -534,7 +550,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.wicenv_cache[image] = os.path.join(stdir, machine, 'imgdata') return self.wicenv_cache[image] - @testcase(1347) + @OETestID(1347) def test_image_env(self): """Test generation of <image>.env files.""" image = 'core-image-minimal' @@ -557,29 +573,36 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertTrue(var in content, "%s is not in .env file" % var) self.assertTrue(content[var]) - @testcase(1559) + @OETestID(1559) def test_image_vars_dir_short(self): """Test image vars directory selection -v option""" image = 'core-image-minimal' imgenvdir = self._get_image_env_path(image) + native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools") self.assertEqual(0, runCmd("wic create wictestdisk " - "--image-name=%s -v %s -o %s" - % (image, imgenvdir, self.resultdir)).status) + "--image-name=%s -v %s -n %s -o %s" + % (image, imgenvdir, native_sysroot, + self.resultdir)).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct"))) + @OETestID(1665) def test_image_vars_dir_long(self): """Test image vars directory selection --vars option""" image = 'core-image-minimal' imgenvdir = self._get_image_env_path(image) + native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools") + self.assertEqual(0, runCmd("wic create wictestdisk " "--image-name=%s " "--vars %s " + "--native-sysroot %s " "--outdir %s" - % (image, imgenvdir, self.resultdir)).status) + % (image, imgenvdir, native_sysroot, + self.resultdir)).status) self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct"))) - @testcase(1351) + @OETestID(1351) @only_for_arch(['i586', 'i686', 'x86_64']) def test_wic_image_type(self): """Test building wic images by bitbake""" @@ -600,7 +623,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertTrue(os.path.islink(path)) self.assertTrue(os.path.isfile(os.path.realpath(path))) - @testcase(1422) + @OETestID(1422) @only_for_arch(['i586', 'i686', 'x86_64']) def test_qemu(self): """Test wic-image-minimal under qemu""" @@ -614,9 +637,10 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r cmd = "mount |grep '^/dev/' | cut -f1,3 -d ' '" status, output = qemu.run_serial(cmd) self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) - self.assertEqual(output, '/dev/root /\r\n/dev/sda3 /mnt') + self.assertEqual(output, '/dev/root /\r\n/dev/sda1 /boot\r\n/dev/sda3 /mnt') @only_for_arch(['i586', 'i686', 'x86_64']) + @OETestID(1852) def test_qemu_efi(self): """Test core-image-minimal efi image under qemu""" config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "mkefidisk.wks"\n' @@ -646,6 +670,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r return wkspath, wksname + @OETestID(1847) def test_fixed_size(self): """ Test creation of a simple image with partition size controlled through @@ -676,6 +701,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertEqual(1, len(partlns)) self.assertEqual("1:0.00MiB:200MiB:200MiB:ext4::;", partlns[0]) + @OETestID(1848) def test_fixed_size_error(self): """ Test creation of a simple image with partition size controlled through @@ -691,6 +717,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertEqual(0, len(wicout)) @only_for_arch(['i586', 'i686', 'x86_64']) + @OETestID(1854) def test_rawcopy_plugin_qemu(self): """Test rawcopy plugin in qemu""" # build ext4 and wic images @@ -706,6 +733,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) self.assertEqual(output, '2') + @OETestID(1853) def test_rawcopy_plugin(self): """Test rawcopy plugin""" img = 'core-image-minimal' @@ -722,6 +750,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r out = glob(self.resultdir + "%s-*direct" % wksname) self.assertEqual(1, len(out)) + @OETestID(1849) def test_fs_types(self): """Test filesystem types for empty and not empty partitions""" img = 'core-image-minimal' @@ -741,6 +770,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r out = glob(self.resultdir + "%s-*direct" % wksname) self.assertEqual(1, len(out)) + @OETestID(1851) def test_kickstart_parser(self): """Test wks parser options""" with NamedTemporaryFile("w", suffix=".wks") as wks: @@ -753,6 +783,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r out = glob(self.resultdir + "%s-*direct" % wksname) self.assertEqual(1, len(out)) + @OETestID(1850) def test_image_bootpart_globbed(self): """Test globbed sources with image-bootpart plugin""" img = "core-image-minimal" @@ -763,6 +794,7 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r self.remove_config(config) self.assertEqual(1, len(glob(self.resultdir + "sdimage-bootpart-*direct"))) + @OETestID(1855) def test_sparse_copy(self): """Test sparse_copy with FIEMAP and SEEK_HOLE filemap APIs""" libpath = os.path.join(get_bb_var('COREBASE'), 'scripts', 'lib', 'wic') @@ -790,3 +822,242 @@ part /etc --source rootfs --ondisk mmcblk0 --fstype=ext4 --exclude-path bin/ --r # 8 blocks is 4K (physical sector size) self.assertEqual(dest_stat.st_blocks, 8) os.unlink(dest) + + @OETestID(1857) + def test_wic_ls(self): + """Test listing image content using 'wic ls'""" + self.assertEqual(0, runCmd("wic create wictestdisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "wictestdisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list partitions + result = runCmd("wic ls %s -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertEqual(3, len(result.output.split('\n'))) + + # list directory content of the first partition + result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertEqual(6, len(result.output.split('\n'))) + + @OETestID(1856) + def test_wic_cp(self): + """Test copy files and directories to the the wic image.""" + self.assertEqual(0, runCmd("wic create wictestdisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "wictestdisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list directory content of the first partition + result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertEqual(6, len(result.output.split('\n'))) + + with NamedTemporaryFile("w", suffix=".wic-cp") as testfile: + testfile.write("test") + + # copy file to the partition + result = runCmd("wic cp %s %s:1/ -n %s" % (testfile.name, images[0], sysroot)) + self.assertEqual(0, result.status) + + # check if file is there + result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertEqual(7, len(result.output.split('\n'))) + self.assertTrue(os.path.basename(testfile.name) in result.output) + + # prepare directory + testdir = os.path.join(self.resultdir, 'wic-test-cp-dir') + testsubdir = os.path.join(testdir, 'subdir') + os.makedirs(os.path.join(testsubdir)) + copy(testfile.name, testdir) + + # copy directory to the partition + result = runCmd("wic cp %s %s:1/ -n %s" % (testdir, images[0], sysroot)) + self.assertEqual(0, result.status) + + # check if directory is there + result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertEqual(8, len(result.output.split('\n'))) + self.assertTrue(os.path.basename(testdir) in result.output) + + @OETestID(1858) + def test_wic_rm(self): + """Test removing files and directories from the the wic image.""" + self.assertEqual(0, runCmd("wic create mkefidisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "mkefidisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list directory content of the first partition + result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertIn('\nBZIMAGE ', result.output) + self.assertIn('\nEFI <DIR> ', result.output) + + # remove file + result = runCmd("wic rm %s:1/bzimage -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + + # remove directory + result = runCmd("wic rm %s:1/efi -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + + # check if they're removed + result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertNotIn('\nBZIMAGE ', result.output) + self.assertNotIn('\nEFI <DIR> ', result.output) + + @OETestID(1922) + def test_mkfs_extraopts(self): + """Test wks option --mkfs-extraopts for empty and not empty partitions""" + img = 'core-image-minimal' + with NamedTemporaryFile("w", suffix=".wks") as wks: + wks.writelines( + ['part ext2 --fstype ext2 --source rootfs --mkfs-extraopts "-D -F -i 8192"\n', + "part btrfs --fstype btrfs --source rootfs --size 40M --mkfs-extraopts='--quiet'\n", + 'part squash --fstype squashfs --source rootfs --mkfs-extraopts "-no-sparse -b 4096"\n', + 'part emptyvfat --fstype vfat --size 1M --mkfs-extraopts "-S 1024 -s 64"\n', + 'part emptymsdos --fstype msdos --size 1M --mkfs-extraopts "-S 1024 -s 64"\n', + 'part emptyext2 --fstype ext2 --size 1M --mkfs-extraopts "-D -F -i 8192"\n', + 'part emptybtrfs --fstype btrfs --size 100M --mkfs-extraopts "--mixed -K"\n']) + wks.flush() + cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir) + self.assertEqual(0, runCmd(cmd).status) + wksname = os.path.splitext(os.path.basename(wks.name))[0] + out = glob(self.resultdir + "%s-*direct" % wksname) + self.assertEqual(1, len(out)) + + def test_expand_mbr_image(self): + """Test wic write --expand command for mbr image""" + # build an image + config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "directdisk.wks"\n' + self.append_config(config) + self.assertEqual(0, bitbake('core-image-minimal').status) + + # get path to the image + bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'MACHINE']) + deploy_dir = bb_vars['DEPLOY_DIR_IMAGE'] + machine = bb_vars['MACHINE'] + image_path = os.path.join(deploy_dir, 'core-image-minimal-%s.wic' % machine) + + self.remove_config(config) + + try: + # expand image to 1G + new_image_path = None + with NamedTemporaryFile(mode='wb', suffix='.wic.exp', + dir=deploy_dir, delete=False) as sparse: + sparse.truncate(1024 ** 3) + new_image_path = sparse.name + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + cmd = "wic write -n %s --expand 1:0 %s %s" % (sysroot, image_path, new_image_path) + self.assertEqual(0, runCmd(cmd).status) + + # check if partitions are expanded + orig = runCmd("wic ls %s -n %s" % (image_path, sysroot)) + exp = runCmd("wic ls %s -n %s" % (new_image_path, sysroot)) + orig_sizes = [int(line.split()[3]) for line in orig.output.split('\n')[1:]] + exp_sizes = [int(line.split()[3]) for line in exp.output.split('\n')[1:]] + self.assertEqual(orig_sizes[0], exp_sizes[0]) # first partition is not resized + self.assertTrue(orig_sizes[1] < exp_sizes[1]) + + # Check if all free space is partitioned + result = runCmd("%s/usr/sbin/sfdisk -F %s" % (sysroot, new_image_path)) + self.assertTrue("0 B, 0 bytes, 0 sectors" in result.output) + + os.rename(image_path, image_path + '.bak') + os.rename(new_image_path, image_path) + + # Check if it boots in qemu + with runqemu('core-image-minimal', ssh=False) as qemu: + cmd = "ls /etc/" + status, output = qemu.run_serial('true') + self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output)) + finally: + if os.path.exists(new_image_path): + os.unlink(new_image_path) + if os.path.exists(image_path + '.bak'): + os.rename(image_path + '.bak', image_path) + + def test_wic_ls_ext(self): + """Test listing content of the ext partition using 'wic ls'""" + self.assertEqual(0, runCmd("wic create wictestdisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "wictestdisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list directory content of the second ext4 partition + result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset( + set(line.split()[-1] for line in result.output.split('\n') if line))) + + def test_wic_cp_ext(self): + """Test copy files and directories to the ext partition.""" + self.assertEqual(0, runCmd("wic create wictestdisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "wictestdisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list directory content of the ext4 partition + result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + dirs = set(line.split()[-1] for line in result.output.split('\n') if line) + self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(dirs)) + + with NamedTemporaryFile("w", suffix=".wic-cp") as testfile: + testfile.write("test") + + # copy file to the partition + result = runCmd("wic cp %s %s:2/ -n %s" % (testfile.name, images[0], sysroot)) + self.assertEqual(0, result.status) + + # check if file is there + result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + newdirs = set(line.split()[-1] for line in result.output.split('\n') if line) + self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)])) + + def test_wic_rm_ext(self): + """Test removing files from the ext partition.""" + self.assertEqual(0, runCmd("wic create mkefidisk " + "--image-name=core-image-minimal " + "-D -o %s" % self.resultdir).status) + images = glob(self.resultdir + "mkefidisk-*.direct") + self.assertEqual(1, len(images)) + + sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools') + + # list directory content of the /etc directory on ext4 partition + result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertTrue('fstab' in [line.split()[-1] for line in result.output.split('\n') if line]) + + # remove file + result = runCmd("wic rm %s:2/etc/fstab -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + + # check if it's removed + result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot)) + self.assertEqual(0, result.status) + self.assertTrue('fstab' not in [line.split()[-1] for line in result.output.split('\n') if line]) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py new file mode 100644 index 000000000..9e90d3c25 --- /dev/null +++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py @@ -0,0 +1,279 @@ +# Copyright (C) 2017 Intel Corporation +# Released under the MIT license (see COPYING.MIT) + +import os +import time +import glob +import sys +import imp +import signal +from shutil import copyfile +from random import choice + +import oeqa + +from oeqa.core.context import OETestContext, OETestContextExecutor +from oeqa.core.exception import OEQAPreRun, OEQATestNotFound + +from oeqa.utils.commands import runCmd, get_bb_vars, get_test_layer + +class OESelftestTestContext(OETestContext): + def __init__(self, td=None, logger=None, machines=None, config_paths=None): + super(OESelftestTestContext, self).__init__(td, logger) + + self.machines = machines + self.custommachine = None + self.config_paths = config_paths + + def runTests(self, machine=None, skips=[]): + if machine: + self.custommachine = machine + if machine == 'random': + self.custommachine = choice(self.machines) + self.logger.info('Run tests with custom MACHINE set to: %s' % \ + self.custommachine) + return super(OESelftestTestContext, self).runTests(skips) + + def listTests(self, display_type, machine=None): + return super(OESelftestTestContext, self).listTests(display_type) + +class OESelftestTestContextExecutor(OETestContextExecutor): + _context_class = OESelftestTestContext + _script_executor = 'oe-selftest' + + name = 'oe-selftest' + help = 'oe-selftest test component' + description = 'Executes selftest tests' + + def register_commands(self, logger, parser): + group = parser.add_mutually_exclusive_group(required=True) + + group.add_argument('-a', '--run-all-tests', default=False, + action="store_true", dest="run_all_tests", + help='Run all (unhidden) tests') + group.add_argument('-R', '--skip-tests', required=False, action='store', + nargs='+', dest="skips", default=None, + help='Run all (unhidden) tests except the ones specified. Format should be <module>[.<class>[.<test_method>]]') + group.add_argument('-r', '--run-tests', required=False, action='store', + nargs='+', dest="run_tests", default=None, + help='Select what tests to run (modules, classes or test methods). Format should be: <module>.<class>.<test_method>') + + group.add_argument('-m', '--list-modules', required=False, + action="store_true", default=False, + help='List all available test modules.') + group.add_argument('--list-classes', required=False, + action="store_true", default=False, + help='List all available test classes.') + group.add_argument('-l', '--list-tests', required=False, + action="store_true", default=False, + help='List all available tests.') + + parser.add_argument('--machine', required=False, choices=['random', 'all'], + help='Run tests on different machines (random/all).') + + parser.set_defaults(func=self.run) + + def _get_available_machines(self): + machines = [] + + bbpath = self.tc_kwargs['init']['td']['BBPATH'].split(':') + + for path in bbpath: + found_machines = glob.glob(os.path.join(path, 'conf', 'machine', '*.conf')) + if found_machines: + for i in found_machines: + # eg: '/home/<user>/poky/meta-intel/conf/machine/intel-core2-32.conf' + machines.append(os.path.splitext(os.path.basename(i))[0]) + + return machines + + def _get_cases_paths(self, bbpath): + cases_paths = [] + for layer in bbpath: + cases_dir = os.path.join(layer, 'lib', 'oeqa', 'selftest', 'cases') + if os.path.isdir(cases_dir): + cases_paths.append(cases_dir) + return cases_paths + + def _process_args(self, logger, args): + args.output_log = '%s-results-%s.log' % (self.name, + time.strftime("%Y%m%d%H%M%S")) + args.test_data_file = None + args.CASES_PATHS = None + + super(OESelftestTestContextExecutor, self)._process_args(logger, args) + + if args.list_modules: + args.list_tests = 'module' + elif args.list_classes: + args.list_tests = 'class' + elif args.list_tests: + args.list_tests = 'name' + + self.tc_kwargs['init']['td'] = get_bb_vars() + self.tc_kwargs['init']['machines'] = self._get_available_machines() + + builddir = os.environ.get("BUILDDIR") + self.tc_kwargs['init']['config_paths'] = {} + self.tc_kwargs['init']['config_paths']['testlayer_path'] = \ + get_test_layer() + self.tc_kwargs['init']['config_paths']['builddir'] = builddir + self.tc_kwargs['init']['config_paths']['localconf'] = \ + os.path.join(builddir, "conf/local.conf") + self.tc_kwargs['init']['config_paths']['localconf_backup'] = \ + os.path.join(builddir, "conf/local.conf.orig") + self.tc_kwargs['init']['config_paths']['localconf_class_backup'] = \ + os.path.join(builddir, "conf/local.conf.bk") + self.tc_kwargs['init']['config_paths']['bblayers'] = \ + os.path.join(builddir, "conf/bblayers.conf") + self.tc_kwargs['init']['config_paths']['bblayers_backup'] = \ + os.path.join(builddir, "conf/bblayers.conf.orig") + self.tc_kwargs['init']['config_paths']['bblayers_class_backup'] = \ + os.path.join(builddir, "conf/bblayers.conf.bk") + + copyfile(self.tc_kwargs['init']['config_paths']['localconf'], + self.tc_kwargs['init']['config_paths']['localconf_backup']) + copyfile(self.tc_kwargs['init']['config_paths']['bblayers'], + self.tc_kwargs['init']['config_paths']['bblayers_backup']) + + self.tc_kwargs['run']['skips'] = args.skips + + def _pre_run(self): + def _check_required_env_variables(vars): + for var in vars: + if not os.environ.get(var): + self.tc.logger.error("%s is not set. Did you forget to source your build environment setup script?" % var) + raise OEQAPreRun + + def _check_presence_meta_selftest(): + builddir = os.environ.get("BUILDDIR") + if os.getcwd() != builddir: + self.tc.logger.info("Changing cwd to %s" % builddir) + os.chdir(builddir) + + if not "meta-selftest" in self.tc.td["BBLAYERS"]: + self.tc.logger.warn("meta-selftest layer not found in BBLAYERS, adding it") + meta_selftestdir = os.path.join( + self.tc.td["BBLAYERS_FETCH_DIR"], 'meta-selftest') + if os.path.isdir(meta_selftestdir): + runCmd("bitbake-layers add-layer %s" %meta_selftestdir) + # reload data is needed because a meta-selftest layer was add + self.tc.td = get_bb_vars() + self.tc.config_paths['testlayer_path'] = get_test_layer() + else: + self.tc.logger.error("could not locate meta-selftest in:\n%s" % meta_selftestdir) + raise OEQAPreRun + + def _add_layer_libs(): + bbpath = self.tc.td['BBPATH'].split(':') + layer_libdirs = [p for p in (os.path.join(l, 'lib') \ + for l in bbpath) if os.path.exists(p)] + if layer_libdirs: + self.tc.logger.info("Adding layer libraries:") + for l in layer_libdirs: + self.tc.logger.info("\t%s" % l) + + sys.path.extend(layer_libdirs) + imp.reload(oeqa.selftest) + + _check_required_env_variables(["BUILDDIR"]) + _check_presence_meta_selftest() + + if "buildhistory.bbclass" in self.tc.td["BBINCLUDED"]: + self.tc.logger.error("You have buildhistory enabled already and this isn't recommended for selftest, please disable it first.") + raise OEQAPreRun + + if "PRSERV_HOST" in self.tc.td: + self.tc.logger.error("Please unset PRSERV_HOST in order to run oe-selftest") + raise OEQAPreRun + + if "SANITY_TESTED_DISTROS" in self.tc.td: + self.tc.logger.error("Please unset SANITY_TESTED_DISTROS in order to run oe-selftest") + raise OEQAPreRun + + _add_layer_libs() + + self.tc.logger.info("Running bitbake -p") + runCmd("bitbake -p") + + def _internal_run(self, logger, args): + self.module_paths = self._get_cases_paths( + self.tc_kwargs['init']['td']['BBPATH'].split(':')) + + self.tc = self._context_class(**self.tc_kwargs['init']) + try: + self.tc.loadTests(self.module_paths, **self.tc_kwargs['load']) + except OEQATestNotFound as ex: + logger.error(ex) + sys.exit(1) + + if args.list_tests: + rc = self.tc.listTests(args.list_tests, **self.tc_kwargs['list']) + else: + self._pre_run() + rc = self.tc.runTests(**self.tc_kwargs['run']) + rc.logDetails() + rc.logSummary(self.name) + + return rc + + def _signal_clean_handler(self, signum, frame): + sys.exit(1) + + def run(self, logger, args): + self._process_args(logger, args) + + signal.signal(signal.SIGTERM, self._signal_clean_handler) + + rc = None + try: + if args.machine: + logger.info('Custom machine mode enabled. MACHINE set to %s' % + args.machine) + + if args.machine == 'all': + results = [] + for m in self.tc_kwargs['init']['machines']: + self.tc_kwargs['run']['machine'] = m + results.append(self._internal_run(logger, args)) + + # XXX: the oe-selftest script only needs to know if one + # machine run fails + for r in results: + rc = r + if not r.wasSuccessful(): + break + + else: + self.tc_kwargs['run']['machine'] = args.machine + return self._internal_run(logger, args) + + else: + self.tc_kwargs['run']['machine'] = args.machine + rc = self._internal_run(logger, args) + finally: + config_paths = self.tc_kwargs['init']['config_paths'] + if os.path.exists(config_paths['localconf_backup']): + copyfile(config_paths['localconf_backup'], + config_paths['localconf']) + os.remove(config_paths['localconf_backup']) + + if os.path.exists(config_paths['bblayers_backup']): + copyfile(config_paths['bblayers_backup'], + config_paths['bblayers']) + os.remove(config_paths['bblayers_backup']) + + if os.path.exists(config_paths['localconf_class_backup']): + os.remove(config_paths['localconf_class_backup']) + if os.path.exists(config_paths['bblayers_class_backup']): + os.remove(config_paths['bblayers_class_backup']) + + output_link = os.path.join(os.path.dirname(args.output_log), + "%s-results.log" % self.name) + if os.path.exists(output_link): + os.remove(output_link) + os.symlink(args.output_log, output_link) + + return rc + +_executor_class = OESelftestTestContextExecutor diff --git a/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py b/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py index 3255e3a5c..f63936c3e 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/targetcontrol.py @@ -18,44 +18,18 @@ from oeqa.utils.dump import TargetDumper from oeqa.controllers.testtargetloader import TestTargetLoader from abc import ABCMeta, abstractmethod -logger = logging.getLogger('BitBake.QemuRunner') - -def get_target_controller(d): - testtarget = d.getVar("TEST_TARGET") - # old, simple names - if testtarget == "qemu": - return QemuTarget(d) - elif testtarget == "simpleremote": - return SimpleRemoteTarget(d) - else: - # use the class name - try: - # is it a core class defined here? - controller = getattr(sys.modules[__name__], testtarget) - except AttributeError: - # nope, perhaps a layer defined one - try: - bbpath = d.getVar("BBPATH").split(':') - testtargetloader = TestTargetLoader() - controller = testtargetloader.get_controller_module(testtarget, bbpath) - except ImportError as e: - bb.fatal("Failed to import {0} from available controller modules:\n{1}".format(testtarget,traceback.format_exc())) - except AttributeError as e: - bb.fatal("Invalid TEST_TARGET - " + str(e)) - return controller(d) - - class BaseTarget(object, metaclass=ABCMeta): supported_image_fstypes = [] - def __init__(self, d): + def __init__(self, d, logger): self.connection = None self.ip = None self.server_ip = None self.datetime = d.getVar('DATETIME') self.testdir = d.getVar("TEST_LOG_DIR") self.pn = d.getVar("PN") + self.logger = logger @abstractmethod def deploy(self): @@ -65,7 +39,7 @@ class BaseTarget(object, metaclass=ABCMeta): if os.path.islink(sshloglink): os.unlink(sshloglink) os.symlink(self.sshlog, sshloglink) - logger.info("SSH log file: %s" % self.sshlog) + self.logger.info("SSH log file: %s" % self.sshlog) @abstractmethod def start(self, params=None, ssh=True, extra_bootparams=None): @@ -115,9 +89,9 @@ class QemuTarget(BaseTarget): supported_image_fstypes = ['ext3', 'ext4', 'cpio.gz', 'wic'] - def __init__(self, d, image_fstype=None): + def __init__(self, d, logger, image_fstype=None): - super(QemuTarget, self).__init__(d) + super(QemuTarget, self).__init__(d, logger) self.rootfs = '' self.kernel = '' @@ -145,7 +119,7 @@ class QemuTarget(BaseTarget): self.qemurunnerlog = os.path.join(self.testdir, 'qemurunner_log.%s' % self.datetime) loggerhandler = logging.FileHandler(self.qemurunnerlog) loggerhandler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) - logger.addHandler(loggerhandler) + self.logger.addHandler(loggerhandler) oe.path.symlink(os.path.basename(self.qemurunnerlog), os.path.join(self.testdir, 'qemurunner_log'), force=True) if d.getVar("DISTRO") == "poky-tiny": @@ -156,7 +130,8 @@ class QemuTarget(BaseTarget): display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY"), logfile = self.qemulog, kernel = self.kernel, - boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT"))) + boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")), + logger = logger) else: self.runner = QemuRunner(machine=d.getVar("MACHINE"), rootfs=self.rootfs, @@ -167,7 +142,8 @@ class QemuTarget(BaseTarget): boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT")), use_kvm = use_kvm, dump_dir = dump_dir, - dump_host_cmds = d.getVar("testimage_dump_host")) + dump_host_cmds = d.getVar("testimage_dump_host"), + logger = logger) self.target_dumper = TargetDumper(dump_target_cmds, dump_dir, self.runner) @@ -179,8 +155,8 @@ class QemuTarget(BaseTarget): os.unlink(qemuloglink) os.symlink(self.qemulog, qemuloglink) - logger.info("rootfs file: %s" % self.rootfs) - logger.info("Qemu log file: %s" % self.qemulog) + self.logger.info("rootfs file: %s" % self.rootfs) + self.logger.info("Qemu log file: %s" % self.qemulog) super(QemuTarget, self).deploy() def start(self, params=None, ssh=True, extra_bootparams='', runqemuparams='', launch_cmd='', discard_writes=True): @@ -232,14 +208,14 @@ class SimpleRemoteTarget(BaseTarget): self.port = addr.split(":")[1] except IndexError: self.port = None - logger.info("Target IP: %s" % self.ip) + self.logger.info("Target IP: %s" % self.ip) self.server_ip = d.getVar("TEST_SERVER_IP") if not self.server_ip: try: self.server_ip = subprocess.check_output(['ip', 'route', 'get', self.ip ]).split("\n")[0].split()[-1] except Exception as e: bb.fatal("Failed to determine the host IP address (alternatively you can set TEST_SERVER_IP with the IP address of this machine): %s" % e) - logger.info("Server IP: %s" % self.server_ip) + self.logger.info("Server IP: %s" % self.server_ip) def deploy(self): super(SimpleRemoteTarget, self).deploy() diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/__init__.py index 485de031a..d38a32301 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/__init__.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/__init__.py @@ -2,7 +2,6 @@ from pkgutil import extend_path __path__ = extend_path(__path__, __name__) - # Borrowed from CalledProcessError class CommandError(Exception): @@ -66,3 +65,39 @@ def make_logger_bitbake_compatible(logger): logger.info = _bitbake_log_info return logger + +def load_test_components(logger, executor): + import sys + import os + import importlib + + from oeqa.core.context import OETestContextExecutor + + components = {} + + for path in sys.path: + base_dir = os.path.join(path, 'oeqa') + if os.path.exists(base_dir) and os.path.isdir(base_dir): + for file in os.listdir(base_dir): + comp_name = file + comp_context = os.path.join(base_dir, file, 'context.py') + if os.path.exists(comp_context): + comp_plugin = importlib.import_module('oeqa.%s.%s' % \ + (comp_name, 'context')) + try: + if not issubclass(comp_plugin._executor_class, + OETestContextExecutor): + raise TypeError("Component %s in %s, _executor_class "\ + "isn't derived from OETestContextExecutor."\ + % (comp_name, comp_context)) + + if comp_plugin._executor_class._script_executor \ + != executor: + continue + + components[comp_name] = comp_plugin._executor_class() + except AttributeError: + raise AttributeError("Component %s in %s don't have "\ + "_executor_class defined." % (comp_name, comp_context)) + + return components diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/buildproject.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/buildproject.py index 487f08be4..721f35d99 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/buildproject.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/buildproject.py @@ -52,4 +52,4 @@ class BuildProject(metaclass=ABCMeta): def clean(self): self._run('rm -rf %s' % self.targetdir) - subprocess.call('rm -f %s' % self.localarchive, shell=True) + subprocess.check_call('rm -f %s' % self.localarchive, shell=True) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py index 57286fcb1..0bb90028d 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/commands.py @@ -13,6 +13,7 @@ import sys import signal import subprocess import threading +import time import logging from oeqa.utils import CommandError from oeqa.utils import ftools @@ -25,7 +26,7 @@ except ImportError: pass class Command(object): - def __init__(self, command, bg=False, timeout=None, data=None, **options): + def __init__(self, command, bg=False, timeout=None, data=None, output_log=None, **options): self.defaultopts = { "stdout": subprocess.PIPE, @@ -48,41 +49,103 @@ class Command(object): self.options.update(options) self.status = None + # We collect chunks of output before joining them at the end. + self._output_chunks = [] + self._error_chunks = [] self.output = None self.error = None - self.thread = None + self.threads = [] + self.output_log = output_log self.log = logging.getLogger("utils.commands") def run(self): self.process = subprocess.Popen(self.cmd, **self.options) - def commThread(): - self.output, self.error = self.process.communicate(self.data) - - self.thread = threading.Thread(target=commThread) - self.thread.start() + def readThread(output, stream, logfunc): + if logfunc: + for line in stream: + output.append(line) + logfunc(line.decode("utf-8", errors='replace').rstrip()) + else: + output.append(stream.read()) + + def readStderrThread(): + readThread(self._error_chunks, self.process.stderr, self.output_log.error if self.output_log else None) + + def readStdoutThread(): + readThread(self._output_chunks, self.process.stdout, self.output_log.info if self.output_log else None) + + def writeThread(): + try: + self.process.stdin.write(self.data) + self.process.stdin.close() + except OSError as ex: + # It's not an error when the command does not consume all + # of our data. subprocess.communicate() also ignores that. + if ex.errno != EPIPE: + raise + + # We write in a separate thread because then we can read + # without worrying about deadlocks. The additional thread is + # expected to terminate by itself and we mark it as a daemon, + # so even it should happen to not terminate for whatever + # reason, the main process will still exit, which will then + # kill the write thread. + if self.data: + threading.Thread(target=writeThread, daemon=True).start() + if self.process.stderr: + thread = threading.Thread(target=readStderrThread) + thread.start() + self.threads.append(thread) + if self.output_log: + self.output_log.info('Running: %s' % self.cmd) + thread = threading.Thread(target=readStdoutThread) + thread.start() + self.threads.append(thread) self.log.debug("Running command '%s'" % self.cmd) if not self.bg: - self.thread.join(self.timeout) + if self.timeout is None: + for thread in self.threads: + thread.join() + else: + deadline = time.time() + self.timeout + for thread in self.threads: + timeout = deadline - time.time() + if timeout < 0: + timeout = 0 + thread.join(timeout) self.stop() def stop(self): - if self.thread.isAlive(): - self.process.terminate() + for thread in self.threads: + if thread.isAlive(): + self.process.terminate() # let's give it more time to terminate gracefully before killing it - self.thread.join(5) - if self.thread.isAlive(): + thread.join(5) + if thread.isAlive(): self.process.kill() - self.thread.join() + thread.join() - if not self.output: - self.output = "" - else: - self.output = self.output.decode("utf-8", errors='replace').rstrip() - self.status = self.process.poll() + def finalize_output(data): + if not data: + data = "" + else: + data = b"".join(data) + data = data.decode("utf-8", errors='replace').rstrip() + return data + + self.output = finalize_output(self._output_chunks) + self._output_chunks = None + # self.error used to be a byte string earlier, probably unintentionally. + # Now it is a normal string, just like self.output. + self.error = finalize_output(self._error_chunks) + self._error_chunks = None + # At this point we know that the process has closed stdout/stderr, so + # it is safe and necessary to wait for the actual process completion. + self.status = self.process.wait() self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status)) # logging the complete output is insane @@ -98,7 +161,7 @@ class Result(object): def runCmd(command, ignore_status=False, timeout=None, assert_error=True, - native_sysroot=None, limit_exc_output=0, **options): + native_sysroot=None, limit_exc_output=0, output_log=None, **options): result = Result() if native_sysroot: @@ -108,7 +171,7 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, nenv['PATH'] = extra_paths + ':' + nenv.get('PATH', '') options['env'] = nenv - cmd = Command(command, timeout=timeout, **options) + cmd = Command(command, timeout=timeout, output_log=output_log, **options) cmd.run() result.command = command @@ -132,7 +195,7 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, return result -def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **options): +def bitbake(command, ignore_status=False, timeout=None, postconfig=None, output_log=None, **options): if postconfig: postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf') @@ -147,7 +210,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, output_log=output_log, **options) finally: if postconfig: os.remove(postconfig_file) @@ -233,6 +296,12 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, import bb.tinfoil import bb.build + # Need a non-'BitBake' logger to capture the runner output + targetlogger = logging.getLogger('TargetRunner') + targetlogger.setLevel(logging.DEBUG) + handler = logging.StreamHandler(sys.stdout) + targetlogger.addHandler(handler) + tinfoil = bb.tinfoil.Tinfoil() tinfoil.prepare(config_only=False, quiet=True) try: @@ -250,31 +319,15 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, for key, value in overrides.items(): recipedata.setVar(key, value) - # The QemuRunner log is saved out, but we need to ensure it is at the right - # log level (and then ensure that since it's a child of the BitBake logger, - # we disable propagation so we don't then see the log events on the console) - logger = logging.getLogger('BitBake.QemuRunner') - logger.setLevel(logging.DEBUG) - logger.propagate = False logdir = recipedata.getVar("TEST_LOG_DIR") - qemu = oeqa.targetcontrol.QemuTarget(recipedata, image_fstype) + qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype) finally: # We need to shut down tinfoil early here in case we actually want # to run tinfoil-using utilities with the running QEMU instance. # Luckily QemuTarget doesn't need it after the constructor. tinfoil.shutdown() - # Setup bitbake logger as console handler is removed by tinfoil.shutdown - bblogger = logging.getLogger('BitBake') - bblogger.setLevel(logging.INFO) - console = logging.StreamHandler(sys.stdout) - bbformat = bb.msg.BBLogFormatter("%(levelname)s: %(message)s") - if sys.stdout.isatty(): - bbformat.enable_color() - console.setFormatter(bbformat) - bblogger.addHandler(console) - try: qemu.deploy() try: @@ -289,6 +342,7 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemu.stop() except: pass + targetlogger.removeHandler(handler) def updateEnv(env_file): """ diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py index e0cb3f0db..757e3f0cb 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/git.py @@ -64,7 +64,7 @@ class GitRepo(object): def rev_parse(self, revision): """Do git rev-parse""" try: - return self.run_cmd(['rev-parse', revision]) + return self.run_cmd(['rev-parse', '--verify', revision]) except GitError: # Revision does not exist return None diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py index b377dcd27..0670627c3 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/logparser.py @@ -9,7 +9,7 @@ from . import ftools # A parser that can be used to identify weather a line is a test result or a section statement. class Lparser(object): - def __init__(self, test_0_pass_regex, test_0_fail_regex, section_0_begin_regex=None, section_0_end_regex=None, **kwargs): + def __init__(self, test_0_pass_regex, test_0_fail_regex, test_0_skip_regex, section_0_begin_regex=None, section_0_end_regex=None, **kwargs): # Initialize the arguments dictionary if kwargs: self.args = kwargs @@ -19,12 +19,13 @@ class Lparser(object): # Add the default args to the dictionary self.args['test_0_pass_regex'] = test_0_pass_regex self.args['test_0_fail_regex'] = test_0_fail_regex + self.args['test_0_skip_regex'] = test_0_skip_regex if section_0_begin_regex: self.args['section_0_begin_regex'] = section_0_begin_regex if section_0_end_regex: self.args['section_0_end_regex'] = section_0_end_regex - self.test_possible_status = ['pass', 'fail', 'error'] + self.test_possible_status = ['pass', 'fail', 'error', 'skip'] self.section_possible_status = ['begin', 'end'] self.initialized = False @@ -108,7 +109,7 @@ class Result(object): prefix = '' for x in test_status: prefix +=x+'.' - if (section != ''): + if section: prefix += section section_file = os.path.join(target_dir, prefix) # purge the file contents if it exists diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/metadata.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/metadata.py index cb81155e5..65bbdc61f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/metadata.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/metadata.py @@ -10,19 +10,9 @@ from collections.abc import MutableMapping from xml.dom.minidom import parseString from xml.etree.ElementTree import Element, tostring +from oe.lsb import get_os_release from oeqa.utils.commands import runCmd, get_bb_vars -def get_os_release(): - """Get info from /etc/os-release as a dict""" - data = OrderedDict() - os_release_file = '/etc/os-release' - if not os.path.exists(os_release_file): - return None - with open(os_release_file) as fobj: - for line in fobj: - key, value = line.split('=', 1) - data[key.strip().lower()] = value.strip().strip('"') - return data def metadata_from_bb(): """ Returns test's metadata as OrderedDict. @@ -45,9 +35,9 @@ def metadata_from_bb(): os_release = get_os_release() if os_release: info_dict['host_distro'] = OrderedDict() - for key in ('id', 'version_id', 'pretty_name'): + for key in ('ID', 'VERSION_ID', 'PRETTY_NAME'): if key in os_release: - info_dict['host_distro'][key] = os_release[key] + info_dict['host_distro'][key.lower()] = os_release[key] info_dict['layers'] = get_layers(data_dict['BBLAYERS']) info_dict['bitbake'] = git_rev_info(os.path.dirname(bb.__file__)) diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py index ba44b96f5..0631d4321 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemurunner.py @@ -17,11 +17,8 @@ import errno import string import threading import codecs -from oeqa.utils.dump import HostDumper - import logging -logger = logging.getLogger("BitBake.QemuRunner") -logger.addHandler(logging.StreamHandler()) +from oeqa.utils.dump import HostDumper # Get Unicode non printable control chars control_range = list(range(0,32))+list(range(127,160)) @@ -31,7 +28,7 @@ re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) class QemuRunner: - def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, use_kvm): + def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, use_kvm, logger): # Popen object for runqemu self.runqemu = None @@ -54,10 +51,14 @@ class QemuRunner: self.logged = False self.thread = None self.use_kvm = use_kvm + self.msg = '' - self.runqemutime = 60 + self.runqemutime = 120 + self.qemu_pidfile = 'pidfile_'+str(os.getpid()) self.host_dumper = HostDumper(dump_host_cmds, dump_dir) + self.logger = logger + def create_socket(self): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -65,7 +66,7 @@ class QemuRunner: sock.bind(("127.0.0.1",0)) sock.listen(2) port = sock.getsockname()[1] - logger.info("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port) + self.logger.debug("Created listening socket for qemu serial console on: 127.0.0.1:%s" % port) return (sock, port) except socket.error: @@ -78,6 +79,7 @@ class QemuRunner: # because is possible to have control characters msg = msg.decode("utf-8", errors='ignore') msg = re_control_char.sub('', msg) + self.msg += msg with codecs.open(self.logfile, "a", encoding="utf-8") as f: f.write("%s" % msg) @@ -91,58 +93,63 @@ class QemuRunner: def handleSIGCHLD(self, signum, frame): if self.runqemu and self.runqemu.poll(): if self.runqemu.returncode: - logger.info('runqemu exited with code %d' % self.runqemu.returncode) - logger.info("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout)) + self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode) + self.logger.debug("Output from runqemu:\n%s" % self.getOutput(self.runqemu.stdout)) self.stop() self._dump_host() raise SystemExit def start(self, qemuparams = None, get_ip = True, extra_bootparams = None, runqemuparams='', launch_cmd=None, discard_writes=True): + env = os.environ.copy() if self.display: - os.environ["DISPLAY"] = self.display + env["DISPLAY"] = self.display # Set this flag so that Qemu doesn't do any grabs as SDL grabs # interact badly with screensavers. - os.environ["QEMU_DONT_GRAB"] = "1" + env["QEMU_DONT_GRAB"] = "1" if not os.path.exists(self.rootfs): - logger.error("Invalid rootfs %s" % self.rootfs) + self.logger.error("Invalid rootfs %s" % self.rootfs) return False if not os.path.exists(self.tmpdir): - logger.error("Invalid TMPDIR path %s" % self.tmpdir) + self.logger.error("Invalid TMPDIR path %s" % self.tmpdir) return False else: - os.environ["OE_TMPDIR"] = self.tmpdir + env["OE_TMPDIR"] = self.tmpdir if not os.path.exists(self.deploy_dir_image): - logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) + self.logger.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) return False else: - os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image + env["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image if not launch_cmd: launch_cmd = 'runqemu %s %s ' % ('snapshot' if discard_writes else '', runqemuparams) if self.use_kvm: - logger.info('Using kvm for runqemu') + self.logger.debug('Using kvm for runqemu') launch_cmd += ' kvm' else: - logger.info('Not using kvm for runqemu') + self.logger.debug('Not using kvm for runqemu') if not self.display: launch_cmd += ' nographic' launch_cmd += ' %s %s' % (self.machine, self.rootfs) - return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams) + return self.launch(launch_cmd, qemuparams=qemuparams, get_ip=get_ip, extra_bootparams=extra_bootparams, env=env) - def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None): + def launch(self, launch_cmd, get_ip = True, qemuparams = None, extra_bootparams = None, env = None): try: threadsock, threadport = self.create_socket() self.server_socket, self.serverport = self.create_socket() except socket.error as msg: - logger.error("Failed to create listening socket: %s" % msg[1]) + self.logger.error("Failed to create listening socket: %s" % msg[1]) return False bootparams = 'console=tty1 console=ttyS0,115200n8 printk.time=1' if extra_bootparams: bootparams = bootparams + ' ' + extra_bootparams - self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1}"'.format(bootparams, threadport) + # Ask QEMU to store the QEMU process PID in file, this way we don't have to parse running processes + # and analyze descendents in order to determine it. + if os.path.exists(self.qemu_pidfile): + os.remove(self.qemu_pidfile) + self.qemuparams = 'bootparams="{0}" qemuparams="-serial tcp:127.0.0.1:{1} -pidfile {2}"'.format(bootparams, threadport, self.qemu_pidfile) if qemuparams: self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' @@ -151,13 +158,13 @@ class QemuRunner: self.origchldhandler = signal.getsignal(signal.SIGCHLD) signal.signal(signal.SIGCHLD, self.handleSIGCHLD) - logger.info('launchcmd=%s'%(launch_cmd)) + self.logger.debug('launchcmd=%s'%(launch_cmd)) # FIXME: We pass in stdin=subprocess.PIPE here to work around stty # blocking at the end of the runqemu script when using this within # oe-selftest (this makes stty error out immediately). There ought # to be a proper fix but this will suffice for now. - self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp) + self.runqemu = subprocess.Popen(launch_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, preexec_fn=os.setpgrp, env=env) output = self.runqemu.stdout # @@ -186,143 +193,149 @@ class QemuRunner: os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) sys.exit(0) - logger.info("runqemu started, pid is %s" % self.runqemu.pid) - logger.info("waiting at most %s seconds for qemu pid" % self.runqemutime) + self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) + self.logger.debug("waiting at most %s seconds for qemu pid" % self.runqemutime) endtime = time.time() + self.runqemutime while not self.is_alive() and time.time() < endtime: if self.runqemu.poll(): if self.runqemu.returncode: # No point waiting any longer - logger.info('runqemu exited with code %d' % self.runqemu.returncode) + self.logger.debug('runqemu exited with code %d' % self.runqemu.returncode) self._dump_host() self.stop() - logger.info("Output from runqemu:\n%s" % self.getOutput(output)) + self.logger.debug("Output from runqemu:\n%s" % self.getOutput(output)) return False - time.sleep(1) + time.sleep(0.5) + + if not self.is_alive(): + self.logger.error("Qemu pid didn't appear in %s seconds" % self.runqemutime) + # Dump all processes to help us to figure out what is going on... + ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command '], stdout=subprocess.PIPE).communicate()[0] + processes = ps.decode("utf-8") + self.logger.debug("Running processes:\n%s" % processes) + self._dump_host() + self.stop() + op = self.getOutput(output) + if op: + self.logger.error("Output from runqemu:\n%s" % op) + else: + self.logger.error("No output from runqemu.\n") + return False + # We are alive: qemu is running out = self.getOutput(output) netconf = False # network configuration is not required by default - if self.is_alive(): - logger.info("qemu started - qemu procces pid is %s" % self.qemupid) - if get_ip: - cmdline = '' - with open('/proc/%s/cmdline' % self.qemupid) as p: - cmdline = p.read() - # It is needed to sanitize the data received - # because is possible to have control characters - cmdline = re_control_char.sub('', cmdline) - try: - ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) - self.ip = ips[0] - self.server_ip = ips[1] - logger.info("qemu cmdline used:\n{}".format(cmdline)) - except (IndexError, ValueError): - # Try to get network configuration from runqemu output - match = re.match('.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*', - out, re.MULTILINE|re.DOTALL) - if match: - self.ip, self.server_ip, self.netmask = match.groups() - # network configuration is required as we couldn't get it - # from the runqemu command line, so qemu doesn't run kernel - # and guest networking is not configured - netconf = True - else: - logger.error("Couldn't get ip from qemu command line and runqemu output! " - "Here is the qemu command line used:\n%s\n" - "and output from runqemu:\n%s" % (cmdline, out)) - self._dump_host() - self.stop() - return False - - logger.info("Target IP: %s" % self.ip) - logger.info("Server IP: %s" % self.server_ip) - - self.thread = LoggingThread(self.log, threadsock, logger) - self.thread.start() - if not self.thread.connection_established.wait(self.boottime): - logger.error("Didn't receive a console connection from qemu. " - "Here is the qemu command line used:\n%s\nand " - "output from runqemu:\n%s" % (cmdline, out)) - self.stop_thread() - return False - - logger.info("Output from runqemu:\n%s", out) - logger.info("Waiting at most %d seconds for login banner" % self.boottime) - endtime = time.time() + self.boottime - socklist = [self.server_socket] - reachedlogin = False - stopread = False - qemusock = None - bootlog = '' - data = b'' - while time.time() < endtime and not stopread: - try: - sread, swrite, serror = select.select(socklist, [], [], 5) - except InterruptedError: - continue - for sock in sread: - if sock is self.server_socket: - qemusock, addr = self.server_socket.accept() - qemusock.setblocking(0) - socklist.append(qemusock) - socklist.remove(self.server_socket) - logger.info("Connection from %s:%s" % addr) - else: - data = data + sock.recv(1024) - if data: - try: - data = data.decode("utf-8", errors="surrogateescape") - bootlog += data - data = b'' - if re.search(".* login:", bootlog): - self.server_socket = qemusock - stopread = True - reachedlogin = True - logger.info("Reached login banner") - except UnicodeDecodeError: - continue - else: - socklist.remove(sock) - sock.close() - stopread = True + self.logger.debug("qemu started in %s seconds - qemu procces pid is %s" % (time.time() - (endtime - self.runqemutime), self.qemupid)) + if get_ip: + cmdline = '' + with open('/proc/%s/cmdline' % self.qemupid) as p: + cmdline = p.read() + # It is needed to sanitize the data received + # because is possible to have control characters + cmdline = re_control_char.sub(' ', cmdline) + try: + ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) + self.ip = ips[0] + self.server_ip = ips[1] + self.logger.debug("qemu cmdline used:\n{}".format(cmdline)) + except (IndexError, ValueError): + # Try to get network configuration from runqemu output + match = re.match('.*Network configuration: ([0-9.]+)::([0-9.]+):([0-9.]+)$.*', + out, re.MULTILINE|re.DOTALL) + if match: + self.ip, self.server_ip, self.netmask = match.groups() + # network configuration is required as we couldn't get it + # from the runqemu command line, so qemu doesn't run kernel + # and guest networking is not configured + netconf = True + else: + self.logger.error("Couldn't get ip from qemu command line and runqemu output! " + "Here is the qemu command line used:\n%s\n" + "and output from runqemu:\n%s" % (cmdline, out)) + self._dump_host() + self.stop() + return False - if not reachedlogin: - logger.info("Target didn't reached login boot in %d seconds" % self.boottime) - lines = "\n".join(bootlog.splitlines()[-25:]) - logger.info("Last 25 lines of text:\n%s" % lines) - logger.info("Check full boot log: %s" % self.logfile) - self._dump_host() - self.stop() - return False + self.logger.debug("Target IP: %s" % self.ip) + self.logger.debug("Server IP: %s" % self.server_ip) - # If we are not able to login the tests can continue + self.thread = LoggingThread(self.log, threadsock, self.logger) + self.thread.start() + if not self.thread.connection_established.wait(self.boottime): + self.logger.error("Didn't receive a console connection from qemu. " + "Here is the qemu command line used:\n%s\nand " + "output from runqemu:\n%s" % (cmdline, out)) + self.stop_thread() + return False + + self.logger.debug("Output from runqemu:\n%s", out) + self.logger.debug("Waiting at most %d seconds for login banner" % self.boottime) + endtime = time.time() + self.boottime + socklist = [self.server_socket] + reachedlogin = False + stopread = False + qemusock = None + bootlog = b'' + data = b'' + while time.time() < endtime and not stopread: try: - (status, output) = self.run_serial("root\n", raw=True) - if re.search("root@[a-zA-Z0-9\-]+:~#", output): - self.logged = True - logger.info("Logged as root in serial console") - if netconf: - # configure guest networking - cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) - output = self.run_serial(cmd, raw=True)[1] - if re.search("root@[a-zA-Z0-9\-]+:~#", output): - logger.info("configured ip address %s", self.ip) - else: - logger.info("Couldn't configure guest networking") + sread, swrite, serror = select.select(socklist, [], [], 5) + except InterruptedError: + continue + for sock in sread: + if sock is self.server_socket: + qemusock, addr = self.server_socket.accept() + qemusock.setblocking(0) + socklist.append(qemusock) + socklist.remove(self.server_socket) + self.logger.debug("Connection from %s:%s" % addr) else: - logger.info("Couldn't login into serial console" - " as root using blank password") - except: - logger.info("Serial console failed while trying to login") - - else: - logger.info("Qemu pid didn't appeared in %s seconds" % self.runqemutime) + data = data + sock.recv(1024) + if data: + bootlog += data + data = b'' + if b' login:' in bootlog: + self.server_socket = qemusock + stopread = True + reachedlogin = True + self.logger.debug("Reached login banner") + else: + socklist.remove(sock) + sock.close() + stopread = True + + + if not reachedlogin: + self.logger.debug("Target didn't reached login boot in %d seconds" % self.boottime) + tail = lambda l: "\n".join(l.splitlines()[-25:]) + # in case bootlog is empty, use tail qemu log store at self.msg + lines = tail(bootlog if bootlog else self.msg) + self.logger.debug("Last 25 lines of text:\n%s" % lines) + self.logger.debug("Check full boot log: %s" % self.logfile) self._dump_host() self.stop() - logger.info("Output from runqemu:\n%s" % self.getOutput(output)) return False - return self.is_alive() + # If we are not able to login the tests can continue + try: + (status, output) = self.run_serial("root\n", raw=True) + if re.search("root@[a-zA-Z0-9\-]+:~#", output): + self.logged = True + self.logger.debug("Logged as root in serial console") + if netconf: + # configure guest networking + cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) + output = self.run_serial(cmd, raw=True)[1] + if re.search("root@[a-zA-Z0-9\-]+:~#", output): + self.logger.debug("configured ip address %s", self.ip) + else: + self.logger.debug("Couldn't configure guest networking") + else: + self.logger.debug("Couldn't login into serial console" + " as root using blank password") + except: + self.logger.debug("Serial console failed while trying to login") + return True def stop(self): self.stop_thread() @@ -332,7 +345,7 @@ class QemuRunner: if self.runqemu: if hasattr(self, "monitorpid"): os.kill(self.monitorpid, signal.SIGKILL) - logger.info("Sending SIGTERM to runqemu") + self.logger.debug("Sending SIGTERM to runqemu") try: os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) except OSError as e: @@ -342,7 +355,7 @@ class QemuRunner: while self.runqemu.poll() is None and time.time() < endtime: time.sleep(1) if self.runqemu.poll() is None: - logger.info("Sending SIGKILL to runqemu") + self.logger.debug("Sending SIGKILL to runqemu") os.killpg(os.getpgid(self.runqemu.pid), signal.SIGKILL) self.runqemu = None if hasattr(self, 'server_socket') and self.server_socket: @@ -350,6 +363,8 @@ class QemuRunner: self.server_socket = None self.qemupid = None self.ip = None + if os.path.exists(self.qemu_pidfile): + os.remove(self.qemu_pidfile) def stop_qemu_system(self): if self.qemupid: @@ -357,7 +372,7 @@ class QemuRunner: # qemu-system behaves well and a SIGTERM is enough os.kill(self.qemupid, signal.SIGTERM) except ProcessLookupError as e: - logger.warn('qemu-system ended unexpectedly') + self.logger.warn('qemu-system ended unexpectedly') def stop_thread(self): if self.thread and self.thread.is_alive(): @@ -365,7 +380,7 @@ class QemuRunner: self.thread.join() def restart(self, qemuparams = None): - logger.info("Restarting qemu process") + self.logger.debug("Restarting qemu process") if self.runqemu.poll() is None: self.stop() if self.start(qemuparams): @@ -375,56 +390,16 @@ class QemuRunner: def is_alive(self): if not self.runqemu: return False - qemu_child = self.find_child(str(self.runqemu.pid)) - if qemu_child: - self.qemupid = qemu_child[0] - if os.path.exists("/proc/" + str(self.qemupid)): + if os.path.isfile(self.qemu_pidfile): + f = open(self.qemu_pidfile, 'r') + qemu_pid = f.read() + f.close() + qemupid = int(qemu_pid) + if os.path.exists("/proc/" + str(qemupid)): + self.qemupid = qemupid return True return False - def find_child(self,parent_pid): - # - # Walk the process tree from the process specified looking for a qemu-system. Return its [pid'cmd] - # - ps = subprocess.Popen(['ps', 'axww', '-o', 'pid,ppid,command'], stdout=subprocess.PIPE).communicate()[0] - processes = ps.decode("utf-8").split('\n') - nfields = len(processes[0].split()) - 1 - pids = {} - commands = {} - for row in processes[1:]: - data = row.split(None, nfields) - if len(data) != 3: - continue - if data[1] not in pids: - pids[data[1]] = [] - - pids[data[1]].append(data[0]) - commands[data[0]] = data[2] - - if parent_pid not in pids: - return [] - - parents = [] - newparents = pids[parent_pid] - while newparents: - next = [] - for p in newparents: - if p in pids: - for n in pids[p]: - if n not in parents and n not in next: - next.append(n) - if p not in parents: - parents.append(p) - newparents = next - #print("Children matching %s:" % str(parents)) - for p in parents: - # Need to be careful here since runqemu runs "ldd qemu-system-xxxx" - # Also, old versions of ldd (2.11) run "LD_XXXX qemu-system-xxxx" - basecmd = commands[p].split()[0] - basecmd = os.path.basename(basecmd) - if "qemu-system" in basecmd and "-serial tcp" in commands[p]: - return [int(p),commands[p]] - def run_serial(self, command, raw=False, timeout=5): # We assume target system have echo to get command status if not raw: @@ -474,7 +449,7 @@ class QemuRunner: def _dump_host(self): self.host_dumper.create_dir("qemu") - logger.warn("Qemu ended unexpectedly, dump data from host" + self.logger.warn("Qemu ended unexpectedly, dump data from host" " is in %s" % self.host_dumper.dump_dir) self.host_dumper.dump_host() @@ -503,17 +478,17 @@ class LoggingThread(threading.Thread): self.teardown() def run(self): - self.logger.info("Starting logging thread") + self.logger.debug("Starting logging thread") self.readpipe, self.writepipe = os.pipe() threading.Thread.run(self) def stop(self): - self.logger.info("Stopping logging thread") + self.logger.debug("Stopping logging thread") if self.running: os.write(self.writepipe, bytes("stop", "utf-8")) def teardown(self): - self.logger.info("Tearing down logging thread") + self.logger.debug("Tearing down logging thread") self.close_socket(self.serversock) if self.readsock is not None: @@ -531,7 +506,7 @@ class LoggingThread(threading.Thread): breakout = False self.running = True - self.logger.info("Starting thread event loop") + self.logger.debug("Starting thread event loop") while not breakout: events = poll.poll() for event in events: @@ -541,19 +516,19 @@ class LoggingThread(threading.Thread): # Event to stop the thread if self.readpipe == event[0]: - self.logger.info("Stop event received") + self.logger.debug("Stop event received") breakout = True break # A connection request was received elif self.serversock.fileno() == event[0]: - self.logger.info("Connection request received") + self.logger.debug("Connection request received") self.readsock, _ = self.serversock.accept() self.readsock.setblocking(0) poll.unregister(self.serversock.fileno()) poll.register(self.readsock.fileno(), event_read_mask) - self.logger.info("Setting connection established event") + self.logger.debug("Setting connection established event") self.connection_established.set() # Actual data to be logged diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py index 1bf59007f..63b5d1648 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/qemutinyrunner.py @@ -17,7 +17,7 @@ from .qemurunner import QemuRunner class QemuTinyRunner(QemuRunner): - def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, kernel, boottime): + def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, kernel, boottime, logger): # Popen object for runqemu self.runqemu = None @@ -40,6 +40,7 @@ class QemuTinyRunner(QemuRunner): self.socketfile = "console.sock" self.server_socket = None self.kernel = kernel + self.logger = logger def create_socket(self): diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py index 05d650255..d292893c0 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/sshcontrol.py @@ -150,12 +150,9 @@ class SSHControl(object): def copy_to(self, localpath, remotepath): if os.path.islink(localpath): - link = os.readlink(localpath) - dst_dir, dst_base = os.path.split(remotepath) - return self.run("cd %s; ln -s %s %s" % (dst_dir, link, dst_base)) - else: - command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] - return self._internal_run(command, ignore_status=False) + localpath = os.path.dirname(localpath) + "/" + os.readlink(localpath) + command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)] + return self._internal_run(command, ignore_status=False) def copy_from(self, remotepath, localpath): command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath] diff --git a/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py b/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py index 9249fa263..1202d579f 100644 --- a/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py +++ b/import-layers/yocto-poky/meta/lib/oeqa/utils/targetbuild.py @@ -69,7 +69,7 @@ class BuildProject(metaclass=ABCMeta): def clean(self): self._run('rm -rf %s' % self.targetdir) - subprocess.call('rm -f %s' % self.localarchive, shell=True) + subprocess.check_call('rm -f %s' % self.localarchive, shell=True) pass class TargetBuildProject(BuildProject): @@ -136,4 +136,4 @@ class SDKBuildProject(BuildProject): def _run(self, cmd): self.log("Running . %s; " % self.sdkenv + cmd) - return subprocess.call(". %s; " % self.sdkenv + cmd, shell=True) + return subprocess.check_call(". %s; " % self.sdkenv + cmd, shell=True) |