From c527fd1f14c27855a37f2e8ac5346ce8d940ced2 Mon Sep 17 00:00:00 2001 From: Tudor Florea Date: Thu, 16 Oct 2014 03:05:19 +0200 Subject: initial commit for Enea Linux 4.0-140929 Migrated from the internal git server on the daisy-enea-point-release branch Signed-off-by: Tudor Florea --- meta/lib/oeqa/__init__.py | 0 meta/lib/oeqa/controllers/__init__.py | 3 + meta/lib/oeqa/controllers/masterimage.py | 133 +++++++++++++++ meta/lib/oeqa/controllers/testtargetloader.py | 69 ++++++++ meta/lib/oeqa/oetest.py | 107 ++++++++++++ meta/lib/oeqa/runexported.py | 140 +++++++++++++++ meta/lib/oeqa/runtime/__init__.py | 3 + meta/lib/oeqa/runtime/buildcvs.py | 30 ++++ meta/lib/oeqa/runtime/buildiptables.py | 30 ++++ meta/lib/oeqa/runtime/buildsudoku.py | 27 +++ meta/lib/oeqa/runtime/connman.py | 30 ++++ meta/lib/oeqa/runtime/date.py | 22 +++ meta/lib/oeqa/runtime/df.py | 11 ++ meta/lib/oeqa/runtime/dmesg.py | 11 ++ meta/lib/oeqa/runtime/files/hellomod.c | 19 +++ meta/lib/oeqa/runtime/files/hellomod_makefile | 8 + meta/lib/oeqa/runtime/files/test.c | 26 +++ meta/lib/oeqa/runtime/files/test.pl | 2 + meta/lib/oeqa/runtime/files/test.py | 6 + meta/lib/oeqa/runtime/files/testmakefile | 5 + meta/lib/oeqa/runtime/gcc.py | 36 ++++ meta/lib/oeqa/runtime/kernelmodule.py | 33 ++++ meta/lib/oeqa/runtime/ldd.py | 19 +++ meta/lib/oeqa/runtime/logrotate.py | 27 +++ meta/lib/oeqa/runtime/multilib.py | 17 ++ meta/lib/oeqa/runtime/pam.py | 24 +++ meta/lib/oeqa/runtime/perl.py | 28 +++ meta/lib/oeqa/runtime/ping.py | 20 +++ meta/lib/oeqa/runtime/python.py | 33 ++++ meta/lib/oeqa/runtime/rpm.py | 50 ++++++ meta/lib/oeqa/runtime/scanelf.py | 26 +++ meta/lib/oeqa/runtime/scp.py | 21 +++ meta/lib/oeqa/runtime/skeletoninit.py | 28 +++ meta/lib/oeqa/runtime/smart.py | 110 ++++++++++++ meta/lib/oeqa/runtime/ssh.py | 18 ++ meta/lib/oeqa/runtime/syslog.py | 46 +++++ meta/lib/oeqa/runtime/systemd.py | 84 +++++++++ meta/lib/oeqa/runtime/vnc.py | 19 +++ meta/lib/oeqa/runtime/x32lib.py | 17 ++ meta/lib/oeqa/runtime/xorg.py | 21 +++ meta/lib/oeqa/selftest/__init__.py | 2 + meta/lib/oeqa/selftest/_sstatetests_noauto.py | 95 +++++++++++ meta/lib/oeqa/selftest/base.py | 129 ++++++++++++++ meta/lib/oeqa/selftest/bblayers.py | 37 ++++ meta/lib/oeqa/selftest/bbtests.py | 104 +++++++++++ meta/lib/oeqa/selftest/buildhistory.py | 45 +++++ meta/lib/oeqa/selftest/buildoptions.py | 113 ++++++++++++ meta/lib/oeqa/selftest/oescripts.py | 60 +++++++ meta/lib/oeqa/selftest/prservice.py | 113 ++++++++++++ meta/lib/oeqa/selftest/sstate.py | 53 ++++++ meta/lib/oeqa/selftest/sstatetests.py | 193 +++++++++++++++++++++ meta/lib/oeqa/targetcontrol.py | 175 +++++++++++++++++++ meta/lib/oeqa/utils/__init__.py | 3 + meta/lib/oeqa/utils/commands.py | 137 +++++++++++++++ meta/lib/oeqa/utils/decorators.py | 50 ++++++ meta/lib/oeqa/utils/ftools.py | 27 +++ meta/lib/oeqa/utils/httpserver.py | 33 ++++ meta/lib/oeqa/utils/qemurunner.py | 237 ++++++++++++++++++++++++++ meta/lib/oeqa/utils/sshcontrol.py | 127 ++++++++++++++ meta/lib/oeqa/utils/targetbuild.py | 68 ++++++++ 60 files changed, 3160 insertions(+) create mode 100644 meta/lib/oeqa/__init__.py create mode 100644 meta/lib/oeqa/controllers/__init__.py create mode 100644 meta/lib/oeqa/controllers/masterimage.py create mode 100644 meta/lib/oeqa/controllers/testtargetloader.py create mode 100644 meta/lib/oeqa/oetest.py create mode 100755 meta/lib/oeqa/runexported.py create mode 100644 meta/lib/oeqa/runtime/__init__.py create mode 100644 meta/lib/oeqa/runtime/buildcvs.py create mode 100644 meta/lib/oeqa/runtime/buildiptables.py create mode 100644 meta/lib/oeqa/runtime/buildsudoku.py create mode 100644 meta/lib/oeqa/runtime/connman.py create mode 100644 meta/lib/oeqa/runtime/date.py create mode 100644 meta/lib/oeqa/runtime/df.py create mode 100644 meta/lib/oeqa/runtime/dmesg.py create mode 100644 meta/lib/oeqa/runtime/files/hellomod.c create mode 100644 meta/lib/oeqa/runtime/files/hellomod_makefile create mode 100644 meta/lib/oeqa/runtime/files/test.c create mode 100644 meta/lib/oeqa/runtime/files/test.pl create mode 100644 meta/lib/oeqa/runtime/files/test.py create mode 100644 meta/lib/oeqa/runtime/files/testmakefile create mode 100644 meta/lib/oeqa/runtime/gcc.py create mode 100644 meta/lib/oeqa/runtime/kernelmodule.py create mode 100644 meta/lib/oeqa/runtime/ldd.py create mode 100644 meta/lib/oeqa/runtime/logrotate.py create mode 100644 meta/lib/oeqa/runtime/multilib.py create mode 100644 meta/lib/oeqa/runtime/pam.py create mode 100644 meta/lib/oeqa/runtime/perl.py create mode 100644 meta/lib/oeqa/runtime/ping.py create mode 100644 meta/lib/oeqa/runtime/python.py create mode 100644 meta/lib/oeqa/runtime/rpm.py create mode 100644 meta/lib/oeqa/runtime/scanelf.py create mode 100644 meta/lib/oeqa/runtime/scp.py create mode 100644 meta/lib/oeqa/runtime/skeletoninit.py create mode 100644 meta/lib/oeqa/runtime/smart.py create mode 100644 meta/lib/oeqa/runtime/ssh.py create mode 100644 meta/lib/oeqa/runtime/syslog.py create mode 100644 meta/lib/oeqa/runtime/systemd.py create mode 100644 meta/lib/oeqa/runtime/vnc.py create mode 100644 meta/lib/oeqa/runtime/x32lib.py create mode 100644 meta/lib/oeqa/runtime/xorg.py create mode 100644 meta/lib/oeqa/selftest/__init__.py create mode 100644 meta/lib/oeqa/selftest/_sstatetests_noauto.py create mode 100644 meta/lib/oeqa/selftest/base.py create mode 100644 meta/lib/oeqa/selftest/bblayers.py create mode 100644 meta/lib/oeqa/selftest/bbtests.py create mode 100644 meta/lib/oeqa/selftest/buildhistory.py create mode 100644 meta/lib/oeqa/selftest/buildoptions.py create mode 100644 meta/lib/oeqa/selftest/oescripts.py create mode 100644 meta/lib/oeqa/selftest/prservice.py create mode 100644 meta/lib/oeqa/selftest/sstate.py create mode 100644 meta/lib/oeqa/selftest/sstatetests.py create mode 100644 meta/lib/oeqa/targetcontrol.py create mode 100644 meta/lib/oeqa/utils/__init__.py create mode 100644 meta/lib/oeqa/utils/commands.py create mode 100644 meta/lib/oeqa/utils/decorators.py create mode 100644 meta/lib/oeqa/utils/ftools.py create mode 100644 meta/lib/oeqa/utils/httpserver.py create mode 100644 meta/lib/oeqa/utils/qemurunner.py create mode 100644 meta/lib/oeqa/utils/sshcontrol.py create mode 100644 meta/lib/oeqa/utils/targetbuild.py (limited to 'meta/lib/oeqa') diff --git a/meta/lib/oeqa/__init__.py b/meta/lib/oeqa/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/meta/lib/oeqa/controllers/__init__.py b/meta/lib/oeqa/controllers/__init__.py new file mode 100644 index 0000000000..8eda92763c --- /dev/null +++ b/meta/lib/oeqa/controllers/__init__.py @@ -0,0 +1,3 @@ +# Enable other layers to have modules in the same named directory +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/meta/lib/oeqa/controllers/masterimage.py b/meta/lib/oeqa/controllers/masterimage.py new file mode 100644 index 0000000000..188c630bcd --- /dev/null +++ b/meta/lib/oeqa/controllers/masterimage.py @@ -0,0 +1,133 @@ +import os +import bb +import traceback +import time + +import oeqa.targetcontrol +import oeqa.utils.sshcontrol as sshcontrol +import oeqa.utils.commands as commands + +class GummibootTarget(oeqa.targetcontrol.SimpleRemoteTarget): + + def __init__(self, d): + # let our base class do the ip thing + super(GummibootTarget, self).__init__(d) + + # test rootfs + kernel + self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.tar.gz') + self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("KERNEL_IMAGETYPE")) + if not os.path.isfile(self.rootfs): + # we could've checked that IMAGE_FSTYPES contains tar.gz but the config for running testimage might not be + # the same as the config with which the image was build, ie + # you bitbake core-image-sato with IMAGE_FSTYPES += "tar.gz" + # and your autobuilder overwrites the config, adds the test bits and runs bitbake core-image-sato -c testimage + bb.fatal("No rootfs found. Did you build the image ?\nIf yes, did you build it with IMAGE_FSTYPES += \"tar.gz\" ? \ + \nExpected path: %s" % self.rootfs) + if not os.path.isfile(self.kernel): + bb.fatal("No kernel found. Expected path: %s" % self.kernel) + + # if the user knows what he's doing, then by all means... + # test-rootfs.tar.gz and test-kernel are hardcoded names in other places + # they really have to be used like that in commands though + cmds = d.getVar("TEST_DEPLOY_CMDS", True) + + # this the value we need to set in the LoaderEntryOneShot EFI variable + # so the system boots the 'test' bootloader label and not the default + # The first four bytes are EFI bits, and the rest is an utf-16le string + # (EFI vars values need to be utf-16) + # $ echo -en "test\0" | iconv -f ascii -t utf-16le | hexdump -C + # 00000000 74 00 65 00 73 00 74 00 00 00 |t.e.s.t...| + self.efivarvalue = r'\x07\x00\x00\x00\x74\x00\x65\x00\x73\x00\x74\x00\x00\x00' + + if cmds: + self.deploy_cmds = cmds.split("\n") + else: + self.deploy_cmds = [ + 'mount -L boot /boot', + 'mkdir -p /mnt/testrootfs', + 'mount -L testrootfs /mnt/testrootfs', + 'modprobe efivarfs', + 'mount -t efivarfs efivarfs /sys/firmware/efi/efivars', + 'cp ~/test-kernel /boot', + 'rm -rf /mnt/testrootfs/*', + 'tar xzvf ~/test-rootfs.tar.gz -C /mnt/testrootfs', + 'printf "%s" > /sys/firmware/efi/efivars/LoaderEntryOneShot-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f' % self.efivarvalue + ] + + # master ssh connection + self.master = None + + # this is the name of the command that controls the power for a board + # e.g: TEST_POWERCONTROL_CMD = "/home/user/myscripts/powercontrol.py ${MACHINE} what-ever-other-args-the-script-wants" + # the command should take as the last argument "off" and "on" and "cycle" (off, on) + self.powercontrol_cmd = d.getVar("TEST_POWERCONTROL_CMD", True) or None + self.powercontrol_args = d.getVar("TEST_POWERCONTROL_EXTRA_ARGS") or "" + self.origenv = os.environ + if self.powercontrol_cmd: + if self.powercontrol_args: + self.powercontrol_cmd = "%s %s" % (self.powercontrol_cmd, self.powercontrol_args) + # the external script for controlling power might use ssh + # ssh + keys means we need the original user env + bborigenv = d.getVar("BB_ORIGENV", False) or {} + for key in bborigenv: + val = bborigenv.getVar(key, True) + if val is not None: + self.origenv[key] = str(val) + self.power_ctl("on") + + def power_ctl(self, msg): + if self.powercontrol_cmd: + cmd = "%s %s" % (self.powercontrol_cmd, msg) + commands.runCmd(cmd, preexec_fn=os.setsid, env=self.origenv) + + def power_cycle(self, conn): + if self.powercontrol_cmd: + # be nice, don't just cut power + conn.run("shutdown -h now") + time.sleep(10) + self.power_ctl("cycle") + else: + status, output = conn.run("reboot") + if status != 0: + bb.error("Failed rebooting target and no power control command defined. You need to manually reset the device.\n%s" % output) + + def deploy(self): + bb.plain("%s - deploying image on target" % self.pn) + # base class just sets the ssh log file for us + super(GummibootTarget, self).deploy() + self.master = sshcontrol.SSHControl(ip=self.ip, logfile=self.sshlog, timeout=600, port=self.port) + try: + self._deploy() + except Exception as e: + bb.fatal("Failed deploying test image: %s" % e) + + def _deploy(self): + # make sure we are in the right image + status, output = self.master.run("cat /etc/masterimage") + if status != 0: + raise Exception("No ssh connectivity or target isn't running a master image.\n%s" % output) + + # make sure these aren't mounted + self.master.run("umount /boot; umount /mnt/testrootfs; umount /sys/firmware/efi/efivars;") + + # from now on, every deploy cmd should return 0 + # else an exception will be thrown by sshcontrol + self.master.ignore_status = False + self.master.copy_to(self.rootfs, "~/test-rootfs.tar.gz") + self.master.copy_to(self.kernel, "~/test-kernel") + for cmd in self.deploy_cmds: + self.master.run(cmd) + + + def start(self, params=None): + bb.plain("%s - boot test image on target" % self.pn) + self.power_cycle(self.master) + # there are better ways than a timeout but this should work for now + time.sleep(120) + # set the ssh object for the target/test image + self.connection = sshcontrol.SSHControl(self.ip, logfile=self.sshlog, port=self.port) + bb.plain("%s - start running tests" % self.pn) + + def stop(self): + bb.plain("%s - reboot/powercycle target" % self.pn) + self.power_cycle(self.connection) diff --git a/meta/lib/oeqa/controllers/testtargetloader.py b/meta/lib/oeqa/controllers/testtargetloader.py new file mode 100644 index 0000000000..019bbfd840 --- /dev/null +++ b/meta/lib/oeqa/controllers/testtargetloader.py @@ -0,0 +1,69 @@ +import types +import bb + +# This class is responsible for loading a test target controller +class TestTargetLoader: + + # Search oeqa.controllers module directory for and return a controller + # corresponding to the given target name. + # AttributeError raised if not found. + # ImportError raised if a provided module can not be imported. + def get_controller_module(self, target, bbpath): + controllerslist = self.get_controller_modulenames(bbpath) + bb.note("Available controller modules: %s" % str(controllerslist)) + controller = self.load_controller_from_name(target, controllerslist) + return controller + + # Return a list of all python modules in lib/oeqa/controllers for each + # layer in bbpath + def get_controller_modulenames(self, bbpath): + + controllerslist = [] + + def add_controller_list(path): + if not os.path.exists(os.path.join(path, '__init__.py')): + bb.fatal('Controllers directory %s exists but is missing __init__.py' % path) + files = sorted([f for f in os.listdir(path) if f.endswith('.py') and not f.startswith('_')]) + for f in files: + module = 'oeqa.controllers.' + f[:-3] + if module not in controllerslist: + controllerslist.append(module) + else: + bb.warn("Duplicate controller module found for %s, only one added. Layers should create unique controller module names" % module) + + for p in bbpath: + controllerpath = os.path.join(p, 'lib', 'oeqa', 'controllers') + bb.debug(2, 'Searching for target controllers in %s' % controllerpath) + if os.path.exists(controllerpath): + add_controller_list(controllerpath) + return controllerslist + + # Search for and return a controller from given target name and + # set of module names. + # Raise AttributeError if not found. + # Raise ImportError if a provided module can not be imported + def load_controller_from_name(self, target, modulenames): + for name in modulenames: + obj = self.load_controller_from_module(target, name) + if obj: + return obj + raise AttributeError("Unable to load {0} from available modules: {1}".format(target, str(modulenames))) + + # Search for and return a controller or None from given module name + def load_controller_from_module(self, target, modulename): + obj = None + # import module, allowing it to raise import exception + module = __import__(modulename, globals(), locals(), [target]) + # look for target class in the module, catching any exceptions as it + # is valid that a module may not have the target class. + try: + obj = getattr(module, target) + if obj: + from oeqa.targetcontrol import BaseTarget + if (not isinstance(obj, (type, types.ClassType))): + bb.warn("Target {0} found, but not of type Class".format(target)) + if( not issubclass(obj, BaseTarget)): + bb.warn("Target {0} found, but subclass is not BaseTarget".format(target)) + except: + obj = None + return obj diff --git a/meta/lib/oeqa/oetest.py b/meta/lib/oeqa/oetest.py new file mode 100644 index 0000000000..0db6cb80a9 --- /dev/null +++ b/meta/lib/oeqa/oetest.py @@ -0,0 +1,107 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# Main unittest module used by testimage.bbclass +# This provides the oeRuntimeTest base class which is inherited by all tests in meta/lib/oeqa/runtime. + +# It also has some helper functions and it's responsible for actually starting the tests + +import os, re, mmap +import unittest +import inspect + + +def loadTests(tc): + + # set the context object passed from the test class + setattr(oeTest, "tc", tc) + # set ps command to use + setattr(oeRuntimeTest, "pscmd", "ps -ef" if oeTest.hasPackage("procps") else "ps") + # prepare test suite, loader and runner + suite = unittest.TestSuite() + testloader = unittest.TestLoader() + testloader.sortTestMethodsUsing = None + suite = testloader.loadTestsFromNames(tc.testslist) + + return suite + +def runTests(tc): + + suite = loadTests(tc) + print("Test modules %s" % tc.testslist) + print("Found %s tests" % suite.countTestCases()) + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + return result + + +class oeTest(unittest.TestCase): + + longMessage = True + testFailures = [] + testSkipped = [] + testErrors = [] + + def run(self, result=None): + super(oeTest, self).run(result) + + # we add to our own lists the results, we use those for decorators + if len(result.failures) > len(oeTest.testFailures): + oeTest.testFailures.append(str(result.failures[-1][0]).split()[0]) + if len(result.skipped) > len(oeTest.testSkipped): + oeTest.testSkipped.append(str(result.skipped[-1][0]).split()[0]) + if len(result.errors) > len(oeTest.testErrors): + oeTest.testErrors.append(str(result.errors[-1][0]).split()[0]) + + @classmethod + def hasPackage(self, pkg): + + if re.search(pkg, oeTest.tc.pkgmanifest): + return True + return False + + @classmethod + def hasFeature(self,feature): + + if feature in oeTest.tc.imagefeatures or \ + feature in oeTest.tc.distrofeatures: + return True + else: + return False + + +class oeRuntimeTest(oeTest): + + def __init__(self, methodName='runTest'): + self.target = oeRuntimeTest.tc.target + super(oeRuntimeTest, self).__init__(methodName) + + +def getmodule(pos=2): + # stack returns a list of tuples containg frame information + # First element of the list the is current frame, caller is 1 + frameinfo = inspect.stack()[pos] + modname = inspect.getmodulename(frameinfo[1]) + #modname = inspect.getmodule(frameinfo[0]).__name__ + return modname + +def skipModule(reason, pos=2): + modname = getmodule(pos) + if modname not in oeTest.tc.testsrequired: + raise unittest.SkipTest("%s: %s" % (modname, reason)) + else: + raise Exception("\nTest %s wants to be skipped.\nReason is: %s" \ + "\nTest was required in TEST_SUITES, so either the condition for skipping is wrong" \ + "\nor the image really doesn't have the required feature/package when it should." % (modname, reason)) + +def skipModuleIf(cond, reason): + + if cond: + skipModule(reason, 3) + +def skipModuleUnless(cond, reason): + + if not cond: + skipModule(reason, 3) diff --git a/meta/lib/oeqa/runexported.py b/meta/lib/oeqa/runexported.py new file mode 100755 index 0000000000..e1b6642ec2 --- /dev/null +++ b/meta/lib/oeqa/runexported.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python + + +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# This script should be used outside of the build system to run image tests. +# It needs a json file as input as exported by the build. +# E.g for an already built image: +#- export the tests: +# TEST_EXPORT_ONLY = "1" +# TEST_TARGET = "simpleremote" +# TEST_TARGET_IP = "192.168.7.2" +# TEST_SERVER_IP = "192.168.7.1" +# bitbake core-image-sato -c testimage +# Setup your target, e.g for qemu: runqemu core-image-sato +# cd build/tmp/testimage/core-image-sato +# ./runexported.py testdata.json + +import sys +import os +import time +from optparse import OptionParser + +try: + import simplejson as json +except ImportError: + import json + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "oeqa"))) + +from oeqa.oetest import runTests +from oeqa.utils.sshcontrol import SSHControl + +# this isn't pretty but we need a fake target object +# for running the tests externally as we don't care +# about deploy/start we only care about the connection methods (run, copy) +class FakeTarget(object): + def __init__(self, d): + self.connection = None + self.ip = None + self.server_ip = None + self.datetime = time.strftime('%Y%m%d%H%M%S',time.gmtime()) + self.testdir = d.getVar("TEST_LOG_DIR", True) + self.pn = d.getVar("PN", True) + + def exportStart(self): + self.sshlog = os.path.join(self.testdir, "ssh_target_log.%s" % self.datetime) + sshloglink = os.path.join(self.testdir, "ssh_target_log") + if os.path.islink(sshloglink): + os.unlink(sshloglink) + os.symlink(self.sshlog, sshloglink) + print("SSH log file: %s" % self.sshlog) + self.connection = SSHControl(self.ip, logfile=self.sshlog) + + def run(self, cmd, timeout=None): + return self.connection.run(cmd, timeout) + + def copy_to(self, localpath, remotepath): + return self.connection.copy_to(localpath, remotepath) + + def copy_from(self, remotepath, localpath): + return self.connection.copy_from(remotepath, localpath) + + +class MyDataDict(dict): + def getVar(self, key, unused = None): + return self.get(key, "") + +class TestContext(object): + def __init__(self): + self.d = None + self.target = None + +def main(): + + usage = "usage: %prog [options] " + parser = OptionParser(usage=usage) + parser.add_option("-t", "--target-ip", dest="ip", help="The IP address of the target machine. Use this to \ + overwrite the value determined from TEST_TARGET_IP at build time") + parser.add_option("-s", "--server-ip", dest="server_ip", help="The IP address of this machine. Use this to \ + overwrite the value determined from TEST_SERVER_IP at build time.") + parser.add_option("-d", "--deploy-dir", dest="deploy_dir", help="Full path to the package feeds, that this \ + the contents of what used to be DEPLOY_DIR on the build machine. If not specified it will use the value \ + specified in the json if that directory actually exists or it will error out.") + parser.add_option("-l", "--log-dir", dest="log_dir", help="This sets the path for TEST_LOG_DIR. If not specified \ + the current dir is used. This is used for usually creating a ssh log file and a scp test file.") + + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error("Incorrect number of arguments. The one and only argument should be a json file exported by the build system") + + with open(args[0], "r") as f: + loaded = json.load(f) + + if options.ip: + loaded["target"]["ip"] = options.ip + if options.server_ip: + loaded["target"]["server_ip"] = options.server_ip + + d = MyDataDict() + for key in loaded["d"].keys(): + d[key] = loaded["d"][key] + + if options.log_dir: + d["TEST_LOG_DIR"] = options.log_dir + else: + d["TEST_LOG_DIR"] = os.path.abspath(os.path.dirname(__file__)) + if options.deploy_dir: + d["DEPLOY_DIR"] = options.deploy_dir + else: + if not os.path.isdir(d["DEPLOY_DIR"]): + raise Exception("The path to DEPLOY_DIR does not exists: %s" % d["DEPLOY_DIR"]) + + + target = FakeTarget(d) + for key in loaded["target"].keys(): + setattr(target, key, loaded["target"][key]) + + tc = TestContext() + setattr(tc, "d", d) + setattr(tc, "target", target) + for key in loaded.keys(): + if key != "d" and key != "target": + setattr(tc, key, loaded[key]) + + target.exportStart() + runTests(tc) + + return 0 + +if __name__ == "__main__": + try: + ret = main() + except Exception: + ret = 1 + import traceback + traceback.print_exc(5) + sys.exit(ret) diff --git a/meta/lib/oeqa/runtime/__init__.py b/meta/lib/oeqa/runtime/__init__.py new file mode 100644 index 0000000000..4cf3fa76b6 --- /dev/null +++ b/meta/lib/oeqa/runtime/__init__.py @@ -0,0 +1,3 @@ +# Enable other layers to have tests in the same named directory +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/meta/lib/oeqa/runtime/buildcvs.py b/meta/lib/oeqa/runtime/buildcvs.py new file mode 100644 index 0000000000..f1fbf19c1f --- /dev/null +++ b/meta/lib/oeqa/runtime/buildcvs.py @@ -0,0 +1,30 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import TargetBuildProject + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + +class BuildCvsTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + self.project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, + "http://ftp.gnu.org/non-gnu/cvs/source/feature/1.12.13/cvs-1.12.13.tar.bz2") + self.project.download_archive() + + @skipUnlessPassed("test_ssh") + def test_cvs(self): + self.assertEqual(self.project.run_configure(), 0, + msg="Running configure failed") + + self.assertEqual(self.project.run_make(), 0, + msg="Running make failed") + + self.assertEqual(self.project.run_install(), 0, + msg="Running make install failed") + + @classmethod + def tearDownClass(self): + self.project.clean() diff --git a/meta/lib/oeqa/runtime/buildiptables.py b/meta/lib/oeqa/runtime/buildiptables.py new file mode 100644 index 0000000000..f6061a7f98 --- /dev/null +++ b/meta/lib/oeqa/runtime/buildiptables.py @@ -0,0 +1,30 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import TargetBuildProject + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + +class BuildIptablesTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + self.project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, + "http://netfilter.org/projects/iptables/files/iptables-1.4.13.tar.bz2") + self.project.download_archive() + + @skipUnlessPassed("test_ssh") + def test_iptables(self): + self.assertEqual(self.project.run_configure(), 0, + msg="Running configure failed") + + self.assertEqual(self.project.run_make(), 0, + msg="Running make failed") + + self.assertEqual(self.project.run_install(), 0, + msg="Running make install failed") + + @classmethod + def tearDownClass(self): + self.project.clean() diff --git a/meta/lib/oeqa/runtime/buildsudoku.py b/meta/lib/oeqa/runtime/buildsudoku.py new file mode 100644 index 0000000000..a754f1d9ea --- /dev/null +++ b/meta/lib/oeqa/runtime/buildsudoku.py @@ -0,0 +1,27 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import TargetBuildProject + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + +class SudokuTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + self.project = TargetBuildProject(oeRuntimeTest.tc.target, oeRuntimeTest.tc.d, + "http://downloads.sourceforge.net/project/sudoku-savant/sudoku-savant/sudoku-savant-1.3/sudoku-savant-1.3.tar.bz2") + self.project.download_archive() + + @skipUnlessPassed("test_ssh") + def test_sudoku(self): + self.assertEqual(self.project.run_configure(), 0, + msg="Running configure failed") + + self.assertEqual(self.project.run_make(), 0, + msg="Running make failed") + + @classmethod + def tearDownClass(self): + self.project.clean() diff --git a/meta/lib/oeqa/runtime/connman.py b/meta/lib/oeqa/runtime/connman.py new file mode 100644 index 0000000000..c03688206f --- /dev/null +++ b/meta/lib/oeqa/runtime/connman.py @@ -0,0 +1,30 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("connman"): + skipModule("No connman package in image") + + +class ConnmanTest(oeRuntimeTest): + + def service_status(self, service): + if oeRuntimeTest.hasFeature("systemd"): + (status, output) = self.target.run('systemctl status -l %s' % service) + return output + else: + return "Unable to get status or logs for %s" % service + + @skipUnlessPassed('test_ssh') + def test_connmand_help(self): + (status, output) = self.target.run('/usr/sbin/connmand --help') + self.assertEqual(status, 0, msg="status and output: %s and %s" % (status,output)) + + + @skipUnlessPassed('test_connmand_help') + def test_connmand_running(self): + (status, output) = self.target.run(oeRuntimeTest.pscmd + ' | grep [c]onnmand') + if status != 0: + print self.service_status("connman") + self.fail("No connmand process running") diff --git a/meta/lib/oeqa/runtime/date.py b/meta/lib/oeqa/runtime/date.py new file mode 100644 index 0000000000..a208e29ada --- /dev/null +++ b/meta/lib/oeqa/runtime/date.py @@ -0,0 +1,22 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +import re + +class DateTest(oeRuntimeTest): + + @skipUnlessPassed("test_ssh") + def test_date(self): + (status, output) = self.target.run('date +"%Y-%m-%d %T"') + self.assertEqual(status, 0, msg="Failed to get initial date, output: %s" % output) + oldDate = output + + sampleDate = '"2016-08-09 10:00:00"' + (status, output) = self.target.run("date -s %s" % sampleDate) + self.assertEqual(status, 0, msg="Date set failed, output: %s" % output) + + (status, output) = self.target.run("date -R") + p = re.match('Tue, 09 Aug 2016 10:00:.. \+0000', output) + self.assertTrue(p, msg="The date was not set correctly, output: %s" % output) + + (status, output) = self.target.run('date -s "%s"' % oldDate) + self.assertEqual(status, 0, msg="Failed to reset date, output: %s" % output) diff --git a/meta/lib/oeqa/runtime/df.py b/meta/lib/oeqa/runtime/df.py new file mode 100644 index 0000000000..b6da35027c --- /dev/null +++ b/meta/lib/oeqa/runtime/df.py @@ -0,0 +1,11 @@ +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + + +class DfTest(oeRuntimeTest): + + @skipUnlessPassed("test_ssh") + def test_df(self): + (status,output) = self.target.run("df / | sed -n '2p' | awk '{print $4}'") + self.assertTrue(int(output)>5120, msg="Not enough space on image. Current size is %s" % output) diff --git a/meta/lib/oeqa/runtime/dmesg.py b/meta/lib/oeqa/runtime/dmesg.py new file mode 100644 index 0000000000..64247ea704 --- /dev/null +++ b/meta/lib/oeqa/runtime/dmesg.py @@ -0,0 +1,11 @@ +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + + +class DmesgTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_dmesg(self): + (status, output) = self.target.run('dmesg | grep -v mmci-pl18x | grep -v "error changing net interface name" | grep -iv "dma timeout" | grep -i error') + self.assertEqual(status, 1, msg = "Error messages in dmesg log: %s" % output) diff --git a/meta/lib/oeqa/runtime/files/hellomod.c b/meta/lib/oeqa/runtime/files/hellomod.c new file mode 100644 index 0000000000..a383397e93 --- /dev/null +++ b/meta/lib/oeqa/runtime/files/hellomod.c @@ -0,0 +1,19 @@ +#include +#include +#include + +static int __init hello_init(void) +{ + printk(KERN_INFO "Hello world!\n"); + return 0; +} + +static void __exit hello_cleanup(void) +{ + printk(KERN_INFO "Cleaning up hellomod.\n"); +} + +module_init(hello_init); +module_exit(hello_cleanup); + +MODULE_LICENSE("GPL"); diff --git a/meta/lib/oeqa/runtime/files/hellomod_makefile b/meta/lib/oeqa/runtime/files/hellomod_makefile new file mode 100644 index 0000000000..b92d5c8fe0 --- /dev/null +++ b/meta/lib/oeqa/runtime/files/hellomod_makefile @@ -0,0 +1,8 @@ +obj-m := hellomod.o +KDIR := /usr/src/kernel + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/meta/lib/oeqa/runtime/files/test.c b/meta/lib/oeqa/runtime/files/test.c new file mode 100644 index 0000000000..2d8389c92e --- /dev/null +++ b/meta/lib/oeqa/runtime/files/test.c @@ -0,0 +1,26 @@ +#include +#include +#include + +double convert(long long l) +{ + return (double)l; +} + +int main(int argc, char * argv[]) { + + long long l = 10; + double f; + double check = 10.0; + + f = convert(l); + printf("convert: %lld => %f\n", l, f); + if ( f != check ) exit(1); + + f = 1234.67; + check = 1234.0; + printf("floorf(%f) = %f\n", f, floorf(f)); + if ( floorf(f) != check) exit(1); + + return 0; +} diff --git a/meta/lib/oeqa/runtime/files/test.pl b/meta/lib/oeqa/runtime/files/test.pl new file mode 100644 index 0000000000..689c8f1635 --- /dev/null +++ b/meta/lib/oeqa/runtime/files/test.pl @@ -0,0 +1,2 @@ +$a = 9.01e+21 - 9.01e+21 + 0.01; +print ("the value of a is ", $a, "\n"); diff --git a/meta/lib/oeqa/runtime/files/test.py b/meta/lib/oeqa/runtime/files/test.py new file mode 100644 index 0000000000..f3a2273c52 --- /dev/null +++ b/meta/lib/oeqa/runtime/files/test.py @@ -0,0 +1,6 @@ +import os + +os.system('touch /tmp/testfile.python') + +a = 9.01e+21 - 9.01e+21 + 0.01 +print "the value of a is %s" % a diff --git a/meta/lib/oeqa/runtime/files/testmakefile b/meta/lib/oeqa/runtime/files/testmakefile new file mode 100644 index 0000000000..ca1844e930 --- /dev/null +++ b/meta/lib/oeqa/runtime/files/testmakefile @@ -0,0 +1,5 @@ +test: test.o + gcc -o test test.o -lm +test.o: test.c + gcc -c test.c + diff --git a/meta/lib/oeqa/runtime/gcc.py b/meta/lib/oeqa/runtime/gcc.py new file mode 100644 index 0000000000..b63badd3e4 --- /dev/null +++ b/meta/lib/oeqa/runtime/gcc.py @@ -0,0 +1,36 @@ +import unittest +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + + +class GccCompileTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + oeRuntimeTest.tc.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "test.c"), "/tmp/test.c") + oeRuntimeTest.tc.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "testmakefile"), "/tmp/testmakefile") + + def test_gcc_compile(self): + (status, output) = self.target.run('gcc /tmp/test.c -o /tmp/test -lm') + self.assertEqual(status, 0, msg="gcc compile failed, output: %s" % output) + (status, output) = self.target.run('/tmp/test') + self.assertEqual(status, 0, msg="running compiled file failed, output %s" % output) + + def test_gpp_compile(self): + (status, output) = self.target.run('g++ /tmp/test.c -o /tmp/test -lm') + self.assertEqual(status, 0, msg="g++ compile failed, output: %s" % output) + (status, output) = self.target.run('/tmp/test') + self.assertEqual(status, 0, msg="running compiled file failed, output %s" % output) + + def test_make(self): + (status, output) = self.target.run('cd /tmp; make -f testmakefile') + self.assertEqual(status, 0, msg="running make failed, output %s" % output) + + @classmethod + def tearDownClass(self): + oeRuntimeTest.tc.target.run("rm /tmp/test.c /tmp/test.o /tmp/test /tmp/testmakefile") diff --git a/meta/lib/oeqa/runtime/kernelmodule.py b/meta/lib/oeqa/runtime/kernelmodule.py new file mode 100644 index 0000000000..cbc5742eff --- /dev/null +++ b/meta/lib/oeqa/runtime/kernelmodule.py @@ -0,0 +1,33 @@ +import unittest +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + + +class KernelModuleTest(oeRuntimeTest): + + def setUp(self): + self.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "hellomod.c"), "/tmp/hellomod.c") + self.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "hellomod_makefile"), "/tmp/Makefile") + + @skipUnlessPassed('test_ssh') + @skipUnlessPassed('test_gcc_compile') + def test_kernel_module(self): + cmds = [ + 'cd /usr/src/kernel && make scripts', + 'cd /tmp && make', + 'cd /tmp && insmod hellomod.ko', + 'lsmod | grep hellomod', + 'dmesg | grep Hello', + 'rmmod hellomod', 'dmesg | grep "Cleaning up hellomod"' + ] + for cmd in cmds: + (status, output) = self.target.run(cmd, 900) + self.assertEqual(status, 0, msg="\n".join([cmd, output])) + + def tearDown(self): + self.target.run('rm -f /tmp/Makefile /tmp/hellomod.c') diff --git a/meta/lib/oeqa/runtime/ldd.py b/meta/lib/oeqa/runtime/ldd.py new file mode 100644 index 0000000000..4374530fc4 --- /dev/null +++ b/meta/lib/oeqa/runtime/ldd.py @@ -0,0 +1,19 @@ +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("tools-sdk"): + skipModule("Image doesn't have tools-sdk in IMAGE_FEATURES") + +class LddTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_ldd_exists(self): + (status, output) = self.target.run('which ldd') + self.assertEqual(status, 0, msg = "ldd does not exist in PATH: which ldd: %s" % output) + + @skipUnlessPassed('test_ldd_exists') + def test_ldd_rtldlist_check(self): + (status, output) = self.target.run('for i in $(which ldd | xargs cat | grep "^RTLDLIST"|cut -d\'=\' -f2|tr -d \'"\'); do test -f $i && echo $i && break; done') + self.assertEqual(status, 0, msg = "ldd path not correct or RTLDLIST files don't exist. ") diff --git a/meta/lib/oeqa/runtime/logrotate.py b/meta/lib/oeqa/runtime/logrotate.py new file mode 100644 index 0000000000..80489a3267 --- /dev/null +++ b/meta/lib/oeqa/runtime/logrotate.py @@ -0,0 +1,27 @@ +# This test should cover https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=289 testcase +# Note that the image under test must have logrotate installed + +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("logrotate"): + skipModule("No logrotate package in image") + + +class LogrotateTest(oeRuntimeTest): + + @skipUnlessPassed("test_ssh") + def test_1_logrotate_setup(self): + (status, output) = self.target.run('mkdir /home/root/logrotate_dir') + self.assertEqual(status, 0, msg = "Could not create logrotate_dir. Output: %s" % output) + (status, output) = self.target.run("sed -i 's#wtmp {#wtmp {\\n olddir /home/root/logrotate_dir#' /etc/logrotate.conf") + self.assertEqual(status, 0, msg = "Could not write to logrotate.conf file. Status and output: %s and %s)" % (status, output)) + + @skipUnlessPassed("test_1_logrotate_setup") + def test_2_logrotate(self): + (status, output) = self.target.run('logrotate -f /etc/logrotate.conf') + self.assertEqual(status, 0, msg = "logrotate service could not be reloaded. Status and output: %s and %s" % (status, output)) + output = self.target.run('ls -la /home/root/logrotate_dir/ | wc -l')[1] + self.assertTrue(int(output)>=3, msg = "new logfile could not be created. List of files within log directory: %s" %(self.target.run('ls -la /home/root/logrotate_dir')[1])) diff --git a/meta/lib/oeqa/runtime/multilib.py b/meta/lib/oeqa/runtime/multilib.py new file mode 100644 index 0000000000..13a3b54b18 --- /dev/null +++ b/meta/lib/oeqa/runtime/multilib.py @@ -0,0 +1,17 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + multilibs = oeRuntimeTest.tc.d.getVar("MULTILIBS", True) or "" + if "multilib:lib32" not in multilibs: + skipModule("this isn't a multilib:lib32 image") + + +class MultilibTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_file_connman(self): + self.assertTrue(oeRuntimeTest.hasPackage('connman-gnome'), msg="This test assumes connman-gnome is installed") + (status, output) = self.target.run("readelf -h /usr/bin/connman-applet | sed -n '3p' | awk '{print $2}'") + self.assertEqual(output, "ELF32", msg="connman-applet isn't an ELF32 binary. readelf says: %s" % self.target.run("readelf -h /usr/bin/connman-applet")[1]) diff --git a/meta/lib/oeqa/runtime/pam.py b/meta/lib/oeqa/runtime/pam.py new file mode 100644 index 0000000000..52e1eb88e6 --- /dev/null +++ b/meta/lib/oeqa/runtime/pam.py @@ -0,0 +1,24 @@ +# This test should cover https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=287 testcase +# Note that the image under test must have "pam" in DISTRO_FEATURES + +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("pam"): + skipModule("target doesn't have 'pam' in DISTRO_FEATURES") + + +class PamBasicTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_pam(self): + (status, output) = self.target.run('login --help') + self.assertEqual(status, 1, msg = "login command does not work as expected. Status and output:%s and %s" %(status, output)) + (status, output) = self.target.run('passwd --help') + self.assertEqual(status, 6, msg = "passwd command does not work as expected. Status and output:%s and %s" %(status, output)) + (status, output) = self.target.run('su --help') + self.assertEqual(status, 2, msg = "su command does not work as expected. Status and output:%s and %s" %(status, output)) + (status, output) = self.target.run('useradd --help') + self.assertEqual(status, 2, msg = "useradd command does not work as expected. Status and output:%s and %s" %(status, output)) diff --git a/meta/lib/oeqa/runtime/perl.py b/meta/lib/oeqa/runtime/perl.py new file mode 100644 index 0000000000..c9bb684c11 --- /dev/null +++ b/meta/lib/oeqa/runtime/perl.py @@ -0,0 +1,28 @@ +import unittest +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("perl"): + skipModule("No perl package in the image") + + +class PerlTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + oeRuntimeTest.tc.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "test.pl"), "/tmp/test.pl") + + def test_perl_exists(self): + (status, output) = self.target.run('which perl') + self.assertEqual(status, 0, msg="Perl binary not in PATH or not on target.") + + def test_perl_works(self): + (status, output) = self.target.run('perl /tmp/test.pl') + self.assertEqual(status, 0, msg="Exit status was not 0. Output: %s" % output) + self.assertEqual(output, "the value of a is 0.01", msg="Incorrect output: %s" % output) + + @classmethod + def tearDownClass(self): + oeRuntimeTest.tc.target.run("rm /tmp/test.pl") diff --git a/meta/lib/oeqa/runtime/ping.py b/meta/lib/oeqa/runtime/ping.py new file mode 100644 index 0000000000..a73c72402a --- /dev/null +++ b/meta/lib/oeqa/runtime/ping.py @@ -0,0 +1,20 @@ +import subprocess +import unittest +import sys +import time +from oeqa.oetest import oeRuntimeTest + +class PingTest(oeRuntimeTest): + + def test_ping(self): + output = '' + count = 0 + endtime = time.time() + 60 + while count < 5 and time.time() < endtime: + proc = subprocess.Popen("ping -c 1 %s" % self.target.ip, shell=True, stdout=subprocess.PIPE) + output += proc.communicate()[0] + if proc.poll() == 0: + count += 1 + else: + count = 0 + self.assertEqual(count, 5, msg = "Expected 5 consecutive replies, got %d.\nping output is:\n%s" % (count,output)) diff --git a/meta/lib/oeqa/runtime/python.py b/meta/lib/oeqa/runtime/python.py new file mode 100644 index 0000000000..c037ab2c18 --- /dev/null +++ b/meta/lib/oeqa/runtime/python.py @@ -0,0 +1,33 @@ +import unittest +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("python"): + skipModule("No python package in the image") + + +class PythonTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + oeRuntimeTest.tc.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "test.py"), "/tmp/test.py") + + def test_python_exists(self): + (status, output) = self.target.run('which python') + self.assertEqual(status, 0, msg="Python binary not in PATH or not on target.") + + def test_python_stdout(self): + (status, output) = self.target.run('python /tmp/test.py') + self.assertEqual(status, 0, msg="Exit status was not 0. Output: %s" % output) + self.assertEqual(output, "the value of a is 0.01", msg="Incorrect output: %s" % output) + + def test_python_testfile(self): + (status, output) = self.target.run('ls /tmp/testfile.python') + self.assertEqual(status, 0, msg="Python test file generate failed.") + + + @classmethod + def tearDownClass(self): + oeRuntimeTest.tc.target.run("rm /tmp/test.py /tmp/testfile.python") diff --git a/meta/lib/oeqa/runtime/rpm.py b/meta/lib/oeqa/runtime/rpm.py new file mode 100644 index 0000000000..084d22f96b --- /dev/null +++ b/meta/lib/oeqa/runtime/rpm.py @@ -0,0 +1,50 @@ +import unittest +import os +import fnmatch +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("package-management"): + skipModule("rpm module skipped: target doesn't have package-management in IMAGE_FEATURES") + if "package_rpm" != oeRuntimeTest.tc.d.getVar("PACKAGE_CLASSES", True).split()[0]: + skipModule("rpm module skipped: target doesn't have rpm as primary package manager") + + +class RpmBasicTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_rpm_help(self): + (status, output) = self.target.run('rpm --help') + self.assertEqual(status, 0, msg="status and output: %s and %s" % (status,output)) + + @skipUnlessPassed('test_rpm_help') + def test_rpm_query(self): + (status, output) = self.target.run('rpm -q rpm') + self.assertEqual(status, 0, msg="status and output: %s and %s" % (status,output)) + +class RpmInstallRemoveTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + pkgarch = oeRuntimeTest.tc.d.getVar('TUNE_PKGARCH', True).replace("-", "_") + rpmdir = os.path.join(oeRuntimeTest.tc.d.getVar('DEPLOY_DIR', True), "rpm", pkgarch) + # pick rpm-doc as a test file to get installed, because it's small and it will always be built for standard targets + for f in fnmatch.filter(os.listdir(rpmdir), "rpm-doc-*.%s.rpm" % pkgarch): + testrpmfile = f + oeRuntimeTest.tc.target.copy_to(os.path.join(rpmdir,testrpmfile), "/tmp/rpm-doc.rpm") + + @skipUnlessPassed('test_rpm_help') + def test_rpm_install(self): + (status, output) = self.target.run('rpm -ivh /tmp/rpm-doc.rpm') + self.assertEqual(status, 0, msg="Failed to install rpm-doc package: %s" % output) + + @skipUnlessPassed('test_rpm_install') + def test_rpm_remove(self): + (status,output) = self.target.run('rpm -e rpm-doc') + self.assertEqual(status, 0, msg="Failed to remove rpm-doc package: %s" % output) + + @classmethod + def tearDownClass(self): + oeRuntimeTest.tc.target.run('rm -f /tmp/rpm-doc.rpm') + diff --git a/meta/lib/oeqa/runtime/scanelf.py b/meta/lib/oeqa/runtime/scanelf.py new file mode 100644 index 0000000000..b9abf24640 --- /dev/null +++ b/meta/lib/oeqa/runtime/scanelf.py @@ -0,0 +1,26 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("pax-utils"): + skipModule("pax-utils package not installed") + +class ScanelfTest(oeRuntimeTest): + + def setUp(self): + self.scancmd = 'scanelf --quiet --recursive --mount --ldpath --path' + + @skipUnlessPassed('test_ssh') + def test_scanelf_textrel(self): + # print TEXTREL information + self.scancmd += " --textrel" + (status, output) = self.target.run(self.scancmd) + self.assertEqual(output.strip(), "", "\n".join([self.scancmd, output])) + + @skipUnlessPassed('test_ssh') + def test_scanelf_rpath(self): + # print RPATH information + self.scancmd += " --rpath" + (status, output) = self.target.run(self.scancmd) + self.assertEqual(output.strip(), "", "\n".join([self.scancmd, output])) diff --git a/meta/lib/oeqa/runtime/scp.py b/meta/lib/oeqa/runtime/scp.py new file mode 100644 index 0000000000..03095bf966 --- /dev/null +++ b/meta/lib/oeqa/runtime/scp.py @@ -0,0 +1,21 @@ +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import skipUnlessPassed + +def setUpModule(): + if not (oeRuntimeTest.hasPackage("dropbear") or oeRuntimeTest.hasPackage("openssh-sshd")): + skipModule("No ssh package in image") + +class ScpTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_scp_file(self): + test_log_dir = oeRuntimeTest.tc.d.getVar("TEST_LOG_DIR", True) + test_file_path = os.path.join(test_log_dir, 'test_scp_file') + with open(test_file_path, 'w') as test_scp_file: + test_scp_file.seek(2 ** 22 - 1) + test_scp_file.write(os.linesep) + (status, output) = self.target.copy_to(test_file_path, '/tmp/test_scp_file') + self.assertEqual(status, 0, msg = "File could not be copied. Output: %s" % output) + (status, output) = self.target.run("ls -la /tmp/test_scp_file") + self.assertEqual(status, 0, msg = "SCP test failed") diff --git a/meta/lib/oeqa/runtime/skeletoninit.py b/meta/lib/oeqa/runtime/skeletoninit.py new file mode 100644 index 0000000000..557e715a3e --- /dev/null +++ b/meta/lib/oeqa/runtime/skeletoninit.py @@ -0,0 +1,28 @@ +# This test should cover https://bugzilla.yoctoproject.org/tr_show_case.cgi?case_id=284 testcase +# Note that the image under test must have meta-skeleton layer in bblayers and IMAGE_INSTALL_append = " service" in local.conf + +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("service"): + skipModule("No service package in image") + + +class SkeletonBasicTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + @unittest.skipIf("systemd" == oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager"), "Not appropiate for systemd image") + def test_skeleton_availability(self): + (status, output) = self.target.run('ls /etc/init.d/skeleton') + self.assertEqual(status, 0, msg = "skeleton init script not found. Output:\n%s " % output) + (status, output) = self.target.run('ls /usr/sbin/skeleton-test') + self.assertEqual(status, 0, msg = "skeleton-test not found. Output:\n%s" % output) + + @skipUnlessPassed('test_skeleton_availability') + @unittest.skipIf("systemd" == oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager"), "Not appropiate for systemd image") + def test_skeleton_script(self): + output1 = self.target.run("/etc/init.d/skeleton start")[1] + (status, output2) = self.target.run(oeRuntimeTest.pscmd + ' | grep [s]keleton-test') + self.assertEqual(status, 0, msg = "Skeleton script could not be started:\n%s\n%s" % (output1, output2)) diff --git a/meta/lib/oeqa/runtime/smart.py b/meta/lib/oeqa/runtime/smart.py new file mode 100644 index 0000000000..195f1170c6 --- /dev/null +++ b/meta/lib/oeqa/runtime/smart.py @@ -0,0 +1,110 @@ +import unittest +import re +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +from oeqa.utils.httpserver import HTTPService + +def setUpModule(): + if not oeRuntimeTest.hasFeature("package-management"): + skipModule("Image doesn't have package management feature") + if not oeRuntimeTest.hasPackage("smart"): + skipModule("Image doesn't have smart installed") + if "package_rpm" != oeRuntimeTest.tc.d.getVar("PACKAGE_CLASSES", True).split()[0]: + skipModule("Rpm is not the primary package manager") + +class SmartTest(oeRuntimeTest): + + @skipUnlessPassed('test_smart_help') + def smart(self, command, expected = 0): + command = 'smart %s' % command + status, output = self.target.run(command, 1500) + message = os.linesep.join([command, output]) + self.assertEqual(status, expected, message) + self.assertFalse("Cannot allocate memory" in output, message) + return output + +class SmartBasicTest(SmartTest): + + @skipUnlessPassed('test_ssh') + def test_smart_help(self): + self.smart('--help') + + def test_smart_version(self): + self.smart('--version') + + def test_smart_info(self): + self.smart('info python-smartpm') + + def test_smart_query(self): + self.smart('query python-smartpm') + + def test_smart_search(self): + self.smart('search python-smartpm') + + def test_smart_stats(self): + self.smart('stats') + +class SmartRepoTest(SmartTest): + + @classmethod + def setUpClass(self): + self.repo_server = HTTPService(oeRuntimeTest.tc.d.getVar('DEPLOY_DIR', True), oeRuntimeTest.tc.target.server_ip) + self.repo_server.start() + + @classmethod + def tearDownClass(self): + self.repo_server.stop() + + def test_smart_channel(self): + self.smart('channel', 1) + + def test_smart_channel_add(self): + image_pkgtype = self.tc.d.getVar('IMAGE_PKGTYPE', True) + deploy_url = 'http://%s:%s/%s' %(self.target.server_ip, self.repo_server.port, image_pkgtype) + pkgarchs = self.tc.d.getVar('PACKAGE_ARCHS', True).replace("-","_").split() + for arch in os.listdir('%s/%s' % (self.repo_server.root_dir, image_pkgtype)): + if arch in pkgarchs: + self.smart('channel -y --add {a} type=rpm-md baseurl={u}/{a}'.format(a=arch, u=deploy_url)) + self.smart('update') + + def test_smart_channel_help(self): + self.smart('channel --help') + + def test_smart_channel_list(self): + self.smart('channel --list') + + def test_smart_channel_show(self): + self.smart('channel --show') + + def test_smart_channel_rpmsys(self): + self.smart('channel --show rpmsys') + self.smart('channel --disable rpmsys') + self.smart('channel --enable rpmsys') + + @skipUnlessPassed('test_smart_channel_add') + def test_smart_install(self): + self.smart('remove -y psplash-default') + self.smart('install -y psplash-default') + + @skipUnlessPassed('test_smart_install') + def test_smart_install_dependency(self): + self.smart('remove -y psplash') + self.smart('install -y psplash-default') + + @skipUnlessPassed('test_smart_channel_add') + def test_smart_install_from_disk(self): + self.smart('remove -y psplash-default') + self.smart('download psplash-default') + self.smart('install -y ./psplash-default*') + + @skipUnlessPassed('test_smart_channel_add') + def test_smart_install_from_http(self): + output = self.smart('download --urls psplash-default') + url = re.search('(http://.*/psplash-default.*\.rpm)', output) + self.assertTrue(url, msg="Couln't find download url in %s" % output) + self.smart('remove -y psplash-default') + self.smart('install -y %s' % url.group(0)) + + @skipUnlessPassed('test_smart_install') + def test_smart_reinstall(self): + self.smart('reinstall -y psplash-default') diff --git a/meta/lib/oeqa/runtime/ssh.py b/meta/lib/oeqa/runtime/ssh.py new file mode 100644 index 0000000000..e64866019f --- /dev/null +++ b/meta/lib/oeqa/runtime/ssh.py @@ -0,0 +1,18 @@ +import subprocess +import unittest +import sys +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not (oeRuntimeTest.hasPackage("dropbear") or oeRuntimeTest.hasPackage("openssh")): + skipModule("No ssh package in image") + +class SshTest(oeRuntimeTest): + + @skipUnlessPassed('test_ping') + def test_ssh(self): + (status, output) = self.target.run('uname -a') + self.assertEqual(status, 0, msg="SSH Test failed: %s" % output) + (status, output) = self.target.run('cat /etc/masterimage') + self.assertEqual(status, 1, msg="This isn't the right image - /etc/masterimage shouldn't be here %s" % output) diff --git a/meta/lib/oeqa/runtime/syslog.py b/meta/lib/oeqa/runtime/syslog.py new file mode 100644 index 0000000000..b95b36175a --- /dev/null +++ b/meta/lib/oeqa/runtime/syslog.py @@ -0,0 +1,46 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasPackage("syslog"): + skipModule("No syslog package in image") + +class SyslogTest(oeRuntimeTest): + + @skipUnlessPassed("test_ssh") + def test_syslog_help(self): + (status,output) = self.target.run('/sbin/syslogd --help') + self.assertEqual(status, 0, msg="status and output: %s and %s" % (status,output)) + + @skipUnlessPassed("test_syslog_help") + def test_syslog_running(self): + (status,output) = self.target.run(oeRuntimeTest.pscmd + ' | grep -i [s]yslogd') + self.assertEqual(status, 0, msg="no syslogd process, ps output: %s" % self.target.run(oeRuntimeTest.pscmd)[1]) + + +class SyslogTestConfig(oeRuntimeTest): + + @skipUnlessPassed("test_syslog_running") + def test_syslog_logger(self): + (status,output) = self.target.run('logger foobar && test -e /var/log/messages && grep foobar /var/log/messages || logread | grep foobar') + self.assertEqual(status, 0, msg="Test log string not found in /var/log/messages. Output: %s " % output) + + @skipUnlessPassed("test_syslog_running") + def test_syslog_restart(self): + if "systemd" != oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager"): + (status,output) = self.target.run('/etc/init.d/syslog restart') + else: + (status,output) = self.target.run('systemctl restart syslog.service') + + @skipUnlessPassed("test_syslog_restart") + @skipUnlessPassed("test_syslog_logger") + @unittest.skipIf("systemd" == oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager"), "Not appropiate for systemd image") + def test_syslog_startup_config(self): + self.target.run('echo "LOGFILE=/var/log/test" >> /etc/syslog-startup.conf') + (status,output) = self.target.run('/etc/init.d/syslog restart') + self.assertEqual(status, 0, msg="Could not restart syslog service. Status and output: %s and %s" % (status,output)) + (status,output) = self.target.run('logger foobar && grep foobar /var/log/test') + self.assertEqual(status, 0, msg="Test log string not found. Output: %s " % output) + self.target.run("sed -i 's#LOGFILE=/var/log/test##' /etc/syslog-startup.conf") + self.target.run('/etc/init.d/syslog restart') diff --git a/meta/lib/oeqa/runtime/systemd.py b/meta/lib/oeqa/runtime/systemd.py new file mode 100644 index 0000000000..6de84f891b --- /dev/null +++ b/meta/lib/oeqa/runtime/systemd.py @@ -0,0 +1,84 @@ +import unittest +import re +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("systemd"): + skipModule("target doesn't have systemd in DISTRO_FEATURES") + if "systemd" != oeRuntimeTest.tc.d.getVar("VIRTUAL-RUNTIME_init_manager", True): + skipModule("systemd is not the init manager for this image") + + +class SystemdTest(oeRuntimeTest): + + def systemctl(self, action = '', target = '', expected = 0, verbose = False): + command = 'systemctl %s %s' % (action, target) + status, output = self.target.run(command) + message = '\n'.join([command, output]) + if status != expected and verbose: + message += self.target.run('systemctl status --full %s' % target)[1] + self.assertEqual(status, expected, message) + return output + + +class SystemdBasicTests(SystemdTest): + + @skipUnlessPassed('test_ssh') + def test_systemd_basic(self): + self.systemctl('--version') + + @skipUnlessPassed('test_system_basic') + def test_systemd_list(self): + self.systemctl('list-unit-files') + + def settle(self): + """ + Block until systemd has finished activating any units being activated, + or until two minutes has elapsed. + + Returns a tuple, either (True, '') if all units have finished + activating, or (False, message string) if there are still units + activating (generally, failing units that restart). + """ + import time + endtime = time.time() + (60 * 2) + while True: + status, output = self.target.run('systemctl --state=activating') + if "0 loaded units listed" in output: + return (True, '') + if time.time() >= endtime: + return (False, output) + time.sleep(10) + + @skipUnlessPassed('test_systemd_basic') + def test_systemd_failed(self): + settled, output = self.settle() + self.assertTrue(settled, msg="Timed out waiting for systemd to settle:\n" + output) + + output = self.systemctl('list-units', '--failed') + match = re.search("0 loaded units listed", output) + if not match: + output += self.systemctl('status --full --failed') + self.assertTrue(match, msg="Some systemd units failed:\n%s" % output) + + +class SystemdServiceTests(SystemdTest): + + @skipUnlessPassed('test_systemd_basic') + def test_systemd_status(self): + self.systemctl('status --full', 'avahi-daemon.service') + + @skipUnlessPassed('test_systemd_status') + def test_systemd_stop_start(self): + self.systemctl('stop', 'avahi-daemon.service') + self.systemctl('is-active', 'avahi-daemon.service', expected=3, verbose=True) + self.systemctl('start','avahi-daemon.service') + self.systemctl('is-active', 'avahi-daemon.service', verbose=True) + + @skipUnlessPassed('test_systemd_basic') + def test_systemd_disable_enable(self): + self.systemctl('disable', 'avahi-daemon.service') + self.systemctl('is-enabled', 'avahi-daemon.service', expected=1) + self.systemctl('enable', 'avahi-daemon.service') + self.systemctl('is-enabled', 'avahi-daemon.service') diff --git a/meta/lib/oeqa/runtime/vnc.py b/meta/lib/oeqa/runtime/vnc.py new file mode 100644 index 0000000000..5ed10727bc --- /dev/null +++ b/meta/lib/oeqa/runtime/vnc.py @@ -0,0 +1,19 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +import re + +def setUpModule(): + skipModuleUnless(oeRuntimeTest.hasPackage('x11vnc'), "No x11vnc package in image") + +class VNCTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_vnc(self): + (status, output) = self.target.run('x11vnc -display :0 -bg -o x11vnc.log') + self.assertEqual(status, 0, msg="x11vnc server failed to start: %s" % output) + port = re.search('PORT=[0-9]*', output) + self.assertTrue(port, msg="Listening port not specified in command output: %s" %output) + + vncport = port.group(0).split('=')[1] + (status, output) = self.target.run('netstat -ntl | grep ":%s"' % vncport) + self.assertEqual(status, 0, msg="x11vnc server not running on port %s\n\n%s" % (vncport, self.target.run('netstat -ntl; cat x11vnc.log')[1])) diff --git a/meta/lib/oeqa/runtime/x32lib.py b/meta/lib/oeqa/runtime/x32lib.py new file mode 100644 index 0000000000..6bad201b12 --- /dev/null +++ b/meta/lib/oeqa/runtime/x32lib.py @@ -0,0 +1,17 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + #check if DEFAULTTUNE is set and it's value is: x86-64-x32 + defaulttune = oeRuntimeTest.tc.d.getVar("DEFAULTTUNE", True) + if "x86-64-x32" not in defaulttune: + skipModule("DEFAULTTUNE is not set to x86-64-x32") + +class X32libTest(oeRuntimeTest): + + @skipUnlessPassed("test_ssh") + def test_x32_file(self): + status1 = self.target.run("readelf -h /bin/ls | grep Class | grep ELF32")[0] + status2 = self.target.run("readelf -h /bin/ls | grep Machine | grep X86-64")[0] + self.assertTrue(status1 == 0 and status2 == 0, msg="/bin/ls isn't an X86-64 ELF32 binary. readelf says: %s" % self.target.run("readelf -h /bin/ls")[1]) diff --git a/meta/lib/oeqa/runtime/xorg.py b/meta/lib/oeqa/runtime/xorg.py new file mode 100644 index 0000000000..12dccd8198 --- /dev/null +++ b/meta/lib/oeqa/runtime/xorg.py @@ -0,0 +1,21 @@ +import unittest +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("x11-base"): + skipModule("target doesn't have x11 in IMAGE_FEATURES") + + +class XorgTest(oeRuntimeTest): + + @skipUnlessPassed('test_ssh') + def test_xorg_running(self): + (status, output) = self.target.run(oeRuntimeTest.pscmd + ' | grep -v xinit | grep [X]org') + self.assertEqual(status, 0, msg="Xorg does not appear to be running %s" % self.target.run(oeRuntimeTest.pscmd)[1]) + + @skipUnlessPassed('test_ssh') + def test_xorg_error(self): + (status, output) = self.target.run('cat /var/log/Xorg.0.log | grep -v "(EE) error," | grep -v "PreInit" | grep -v "evdev:" | grep -v "glx" | grep "(EE)"') + self.assertEqual(status, 1, msg="Errors in Xorg log: %s" % output) + diff --git a/meta/lib/oeqa/selftest/__init__.py b/meta/lib/oeqa/selftest/__init__.py new file mode 100644 index 0000000000..3ad9513f40 --- /dev/null +++ b/meta/lib/oeqa/selftest/__init__.py @@ -0,0 +1,2 @@ +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/meta/lib/oeqa/selftest/_sstatetests_noauto.py b/meta/lib/oeqa/selftest/_sstatetests_noauto.py new file mode 100644 index 0000000000..fc9ae7efb9 --- /dev/null +++ b/meta/lib/oeqa/selftest/_sstatetests_noauto.py @@ -0,0 +1,95 @@ +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 + + +class RebuildFromSState(SStateBase): + + @classmethod + def setUpClass(self): + self.builddir = os.path.join(os.environ.get('BUILDDIR')) + + def get_dep_targets(self, primary_targets): + found_targets = [] + bitbake("-g " + ' '.join(map(str, primary_targets))) + with open(os.path.join(self.builddir, 'pn-buildlist'), 'r') as pnfile: + found_targets = pnfile.read().splitlines() + return found_targets + + def configure_builddir(self, builddir): + os.mkdir(builddir) + self.track_for_cleanup(builddir) + os.mkdir(os.path.join(builddir, 'conf')) + shutil.copyfile(os.path.join(os.environ.get('BUILDDIR'), 'conf/local.conf'), os.path.join(builddir, 'conf/local.conf')) + config = {} + config['default_sstate_dir'] = "SSTATE_DIR ?= \"${TOPDIR}/sstate-cache\"" + config['null_sstate_mirrors'] = "SSTATE_MIRRORS = \"\"" + config['default_tmp_dir'] = "TMPDIR = \"${TOPDIR}/tmp\"" + for key in config: + ftools.append_file(os.path.join(builddir, 'conf/selftest.inc'), config[key]) + shutil.copyfile(os.path.join(os.environ.get('BUILDDIR'), 'conf/bblayers.conf'), os.path.join(builddir, 'conf/bblayers.conf')) + try: + shutil.copyfile(os.path.join(os.environ.get('BUILDDIR'), 'conf/auto.conf'), os.path.join(builddir, 'conf/auto.conf')) + except: + pass + + def hardlink_tree(self, src, dst): + os.mkdir(dst) + self.track_for_cleanup(dst) + for root, dirs, files in os.walk(src): + if root == src: + continue + os.mkdir(os.path.join(dst, root.split(src)[1][1:])) + for sstate_file in files: + os.link(os.path.join(root, sstate_file), os.path.join(dst, root.split(src)[1][1:], sstate_file)) + + def run_test_sstate_rebuild(self, primary_targets, relocate=False, rebuild_dependencies=False): + buildA = os.path.join(self.builddir, 'buildA') + if relocate: + buildB = os.path.join(self.builddir, 'buildB') + else: + buildB = buildA + + if rebuild_dependencies: + rebuild_targets = self.get_dep_targets(primary_targets) + else: + rebuild_targets = primary_targets + + self.configure_builddir(buildA) + runCmd((". %s/oe-init-build-env %s && " % (get_bb_var('COREBASE'), buildA)) + 'bitbake ' + ' '.join(map(str, primary_targets)), shell=True, executable='/bin/bash') + self.hardlink_tree(os.path.join(buildA, 'sstate-cache'), os.path.join(self.builddir, 'sstate-cache-buildA')) + shutil.rmtree(buildA) + + failed_rebuild = [] + failed_cleansstate = [] + for target in rebuild_targets: + self.configure_builddir(buildB) + self.hardlink_tree(os.path.join(self.builddir, 'sstate-cache-buildA'), os.path.join(buildB, 'sstate-cache')) + + result_cleansstate = runCmd((". %s/oe-init-build-env %s && " % (get_bb_var('COREBASE'), buildB)) + 'bitbake -ccleansstate ' + target, ignore_status=True, shell=True, executable='/bin/bash') + if not result_cleansstate.status == 0: + failed_cleansstate.append(target) + shutil.rmtree(buildB) + continue + + result_build = runCmd((". %s/oe-init-build-env %s && " % (get_bb_var('COREBASE'), buildB)) + 'bitbake ' + target, ignore_status=True, shell=True, executable='/bin/bash') + if not result_build.status == 0: + failed_rebuild.append(target) + + shutil.rmtree(buildB) + + self.assertFalse(failed_rebuild, msg="The following recipes have failed to rebuild: %s" % ' '.join(map(str, failed_rebuild))) + self.assertFalse(failed_cleansstate, msg="The following recipes have failed cleansstate(all others have passed both cleansstate and rebuild from sstate tests): %s" % ' '.join(map(str, failed_cleansstate))) + + def test_sstate_relocation(self): + self.run_test_sstate_rebuild(['core-image-sato-sdk'], relocate=True, rebuild_dependencies=True) + + def test_sstate_rebuild(self): + self.run_test_sstate_rebuild(['core-image-sato-sdk'], relocate=False, rebuild_dependencies=True) diff --git a/meta/lib/oeqa/selftest/base.py b/meta/lib/oeqa/selftest/base.py new file mode 100644 index 0000000000..fc880e9d26 --- /dev/null +++ b/meta/lib/oeqa/selftest/base.py @@ -0,0 +1,129 @@ +# Copyright (c) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + + +# DESCRIPTION +# Base class inherited by test classes in meta/lib/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 + +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.testinc_path = os.path.join(self.builddir, "conf/selftest.inc") + self.testlayer_path = oeSelfTest.testlayer_path + self._extra_tear_down_commands = [] + self._track_for_cleanup = [] + super(oeSelfTest, self).__init__(methodName) + + def setUp(self): + os.chdir(self.builddir) + # 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)) + # 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 /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) + + # append to /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) + + # remove data from /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//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//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//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//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 diff --git a/meta/lib/oeqa/selftest/bblayers.py b/meta/lib/oeqa/selftest/bblayers.py new file mode 100644 index 0000000000..52aa4f8112 --- /dev/null +++ b/meta/lib/oeqa/selftest/bblayers.py @@ -0,0 +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 + +class BitbakeLayers(oeSelfTest): + + def test_bitbakelayers_showcrossdepends(self): + result = runCmd('bitbake-layers show-cross-depends') + self.assertTrue('aspell' in result.output) + + def test_bitbakelayers_showlayers(self): + result = runCmd('bitbake-layers show_layers') + self.assertTrue('meta-selftest' in result.output) + + def test_bitbakelayers_showappends(self): + result = runCmd('bitbake-layers show_appends') + self.assertTrue('xcursor-transparent-theme_0.1.1.bbappend' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') + + def test_bitbakelayers_showoverlayed(self): + result = runCmd('bitbake-layers show_overlayed') + self.assertTrue('aspell' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') + + def test_bitbakelayers_flatten(self): + self.assertFalse(os.path.isdir(os.path.join(self.builddir, 'test'))) + result = runCmd('bitbake-layers flatten test') + bb_file = os.path.join(self.builddir, 'test/recipes-graphics/xcursor-transparent-theme/xcursor-transparent-theme_0.1.1.bb') + self.assertTrue(os.path.isfile(bb_file)) + contents = ftools.read_file(bb_file) + find_in_contents = re.search("##### bbappended from meta-selftest #####\n(.*\n)*include test_recipe.inc", contents) + shutil.rmtree(os.path.join(self.builddir, 'test')) + self.assertTrue(find_in_contents) diff --git a/meta/lib/oeqa/selftest/bbtests.py b/meta/lib/oeqa/selftest/bbtests.py new file mode 100644 index 0000000000..6815ecfe0b --- /dev/null +++ b/meta/lib/oeqa/selftest/bbtests.py @@ -0,0 +1,104 @@ +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, bitbake, get_bb_var + +class BitbakeTests(oeSelfTest): + + def test_run_bitbake_from_dir_1(self): + os.chdir(os.path.join(self.builddir, 'conf')) + bitbake('-e') + + 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'])) + bitbake('-e', env=my_env) + + def test_event_handler(self): + self.write_config("INHERIT += \"test_events\"") + result = bitbake('m4-native') + find_build_started = re.search("NOTE: Test for bb\.event\.BuildStarted(\n.*)*NOTE: Preparing runqueue", result.output) + find_build_completed = re.search("Tasks Summary:.*(\n.*)*NOTE: Test for bb\.event\.BuildCompleted", result.output) + self.assertTrue(find_build_started, msg = "Match failed in:\n%s" % result.output) + self.assertTrue(find_build_completed, msg = "Match failed in:\n%s" % result.output) + self.assertFalse('Test for bb.event.InvalidEvent' in result.output) + + def test_local_sstate(self): + bitbake('m4-native -ccleansstate') + bitbake('m4-native') + bitbake('m4-native -cclean') + result = bitbake('m4-native') + find_setscene = re.search("m4-native.*do_.*_setscene", result.output) + self.assertTrue(find_setscene) + + 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) + + def test_bitbake_invalid_target(self): + result = bitbake('asdf', ignore_status=True) + self.assertTrue("ERROR: Nothing PROVIDES 'asdf'" in result.output) + + 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) + find_errors = re.search("Summary: There w.{2,3}? [1-9][0-9]* ERROR messages* shown", result.output) + 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) + + def test_invalid_patch(self): + self.write_recipeinc('man', 'SRC_URI += "file://man-1.5h1-make.patch"') + result = bitbake('man -c patch', ignore_status=True) + self.delete_recipeinc('man') + bitbake('-cclean man') + self.assertTrue("ERROR: Function failed: patch_do_patch" in result.output) + + def test_force_task(self): + bitbake('m4-native') + result = bitbake('-C compile m4-native') + look_for_tasks = ['do_compile', 'do_install', 'do_populate_sysroot'] + for task in look_for_tasks: + find_task = re.search("m4-native.*%s" % task, result.output) + self.assertTrue(find_task) + + def test_bitbake_g(self): + result = bitbake('-g core-image-full-cmdline') + self.assertTrue('NOTE: PN build list saved to \'pn-buildlist\'' in result.output) + self.assertTrue('openssh' in ftools.read_file(os.path.join(self.builddir, 'pn-buildlist'))) + for f in ['pn-buildlist', 'pn-depends.dot', 'package-depends.dot', 'task-depends.dot']: + os.remove(f) + + def test_image_manifest(self): + bitbake('core-image-minimal') + deploydir = get_bb_var("DEPLOY_DIR_IMAGE", target="core-image-minimal") + imagename = get_bb_var("IMAGE_LINK_NAME", target="core-image-minimal") + manifest = os.path.join(deploydir, imagename + ".manifest") + self.assertTrue(os.path.islink(manifest), msg="No manifest file created for image") + + def test_invalid_recipe_src_uri(self): + data = 'SRC_URI = "file://invalid"' + self.write_recipeinc('man', data) + bitbake('-ccleanall man') + result = bitbake('-c fetch man', ignore_status=True) + bitbake('-ccleanall man') + self.delete_recipeinc('man') + self.assertEqual(result.status, 1, msg='Command succeded when it should have failed') + self.assertTrue('ERROR: Fetcher failure: Unable to find file file://invalid anywhere. The paths that were searched were:' in result.output) + self.assertTrue('ERROR: Function failed: Fetcher failure for URL: \'file://invalid\'. Unable to fetch URL from any source.' in result.output) + + def test_rename_downloaded_file(self): + data = 'SRC_URI_append = ";downloadfilename=test-aspell.tar.gz"' + self.write_recipeinc('aspell', data) + bitbake('-ccleanall aspell') + result = bitbake('-c fetch aspell', ignore_status=True) + self.delete_recipeinc('aspell') + self.assertEqual(result.status, 0) + self.assertTrue(os.path.isfile(os.path.join(get_bb_var("DL_DIR"), 'test-aspell.tar.gz'))) + self.assertTrue(os.path.isfile(os.path.join(get_bb_var("DL_DIR"), 'test-aspell.tar.gz.done'))) + bitbake('-ccleanall aspell') diff --git a/meta/lib/oeqa/selftest/buildhistory.py b/meta/lib/oeqa/selftest/buildhistory.py new file mode 100644 index 0000000000..d8cae4664b --- /dev/null +++ b/meta/lib/oeqa/selftest/buildhistory.py @@ -0,0 +1,45 @@ +import unittest +import os +import re +import shutil +import datetime + +import oeqa.utils.ftools as ftools +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import Command, runCmd, bitbake, get_bb_var, get_test_layer + + +class BuildhistoryBase(oeSelfTest): + + def config_buildhistory(self, tmp_bh_location=False): + if (not 'buildhistory' in get_bb_var('USER_CLASSES')) and (not 'buildhistory' in get_bb_var('INHERIT')): + add_buildhistory_config = 'INHERIT += "buildhistory"\nBUILDHISTORY_COMMIT = "1"' + self.append_config(add_buildhistory_config) + + if tmp_bh_location: + # Using a temporary buildhistory location for testing + tmp_bh_dir = os.path.join(self.builddir, "tmp_buildhistory_%s" % datetime.datetime.now().strftime('%Y%m%d%H%M%S')) + buildhistory_dir_config = "BUILDHISTORY_DIR = \"%s\"" % tmp_bh_dir + self.append_config(buildhistory_dir_config) + self.track_for_cleanup(tmp_bh_dir) + + def run_buildhistory_operation(self, target, global_config='', target_config='', change_bh_location=False, expect_error=False, error_regex=''): + if change_bh_location: + tmp_bh_location = True + else: + tmp_bh_location = False + self.config_buildhistory(tmp_bh_location) + + self.append_config(global_config) + self.append_recipeinc(target, target_config) + bitbake("-cclean %s" % target) + result = bitbake(target, ignore_status=True) + self.remove_config(global_config) + self.remove_recipeinc(target, target_config) + + if expect_error: + self.assertEqual(result.status, 1, msg="Error expected for global config '%s' and target config '%s'" % (global_config, target_config)) + search_for_error = re.search(error_regex, result.output) + self.assertTrue(search_for_error, msg="Could not find desired error in output: %s" % error_regex) + else: + self.assertEqual(result.status, 0, msg="Command 'bitbake %s' has failed unexpectedly: %s" % (target, result.output)) diff --git a/meta/lib/oeqa/selftest/buildoptions.py b/meta/lib/oeqa/selftest/buildoptions.py new file mode 100644 index 0000000000..8ff40baddc --- /dev/null +++ b/meta/lib/oeqa/selftest/buildoptions.py @@ -0,0 +1,113 @@ +import unittest +import os +import logging +import re + +from oeqa.selftest.base import oeSelfTest +from oeqa.selftest.buildhistory import BuildhistoryBase +from oeqa.utils.commands import runCmd, bitbake, get_bb_var +import oeqa.utils.ftools as ftools + +class ImageOptionsTests(oeSelfTest): + + def test_incremental_image_generation(self): + bitbake("-c cleanall core-image-minimal") + self.write_config('INC_RPM_IMAGE_GEN = "1"') + self.append_config('IMAGE_FEATURES += "ssh-server-openssh"') + bitbake("core-image-minimal") + res = runCmd("grep 'Installing openssh-sshd' %s" % (os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs")), ignore_status=True) + self.remove_config('IMAGE_FEATURES += "ssh-server-openssh"') + self.assertEqual(0, res.status, msg="No match for openssh-sshd in log.do_rootfs") + bitbake("core-image-minimal") + res = runCmd("grep 'Removing openssh-sshd' %s" %(os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs")),ignore_status=True) + self.assertEqual(0, res.status, msg="openssh-sshd was not removed from image") + + def test_rm_old_image(self): + bitbake("core-image-minimal") + deploydir = get_bb_var("DEPLOY_DIR_IMAGE", target="core-image-minimal") + imagename = get_bb_var("IMAGE_LINK_NAME", target="core-image-minimal") + deploydir_files = os.listdir(deploydir) + track_original_files = [] + for image_file in deploydir_files: + if imagename in image_file and os.path.islink(os.path.join(deploydir, image_file)): + track_original_files.append(os.path.realpath(os.path.join(deploydir, image_file))) + self.append_config("RM_OLD_IMAGE = \"1\"") + bitbake("-C rootfs core-image-minimal") + deploydir_files = os.listdir(deploydir) + remaining_not_expected = [path for path in track_original_files if os.path.basename(path) in deploydir_files] + self.assertFalse(remaining_not_expected, msg="\nThe following image files ware not removed: %s" % ', '.join(map(str, remaining_not_expected))) + + def test_ccache_tool(self): + bitbake("ccache-native") + self.assertTrue(os.path.isfile(os.path.join(get_bb_var('STAGING_BINDIR_NATIVE', 'ccache-native'), "ccache"))) + self.write_config('INHERIT += "ccache"') + bitbake("m4 -c cleansstate") + bitbake("m4 -c compile") + res = runCmd("grep ccache %s" % (os.path.join(get_bb_var("WORKDIR","m4"),"temp/log.do_compile")), ignore_status=True) + self.assertEqual(0, res.status, msg="No match for ccache in m4 log.do_compile") + bitbake("ccache-native -ccleansstate") + + +class DiskMonTest(oeSelfTest): + + def test_stoptask_behavior(self): + result = runCmd("df -Pk %s" % os.getcwd()) + size = result.output.split("\n")[1].split()[3] + self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},%sK,4510K"' % size) + res = bitbake("m4", ignore_status = True) + self.assertTrue('ERROR: No new tasks can be executed since the disk space monitor action is "STOPTASKS"!' in res.output) + self.assertEqual(res.status, 1) + self.write_config('BB_DISKMON_DIRS = "ABORT,${TMPDIR},%sK,4510K"' % size) + res = bitbake("m4", ignore_status = True) + self.assertTrue('ERROR: Immediately abort since the disk space monitor action is "ABORT"!' in res.output) + self.assertEqual(res.status, 1) + self.write_config('BB_DISKMON_DIRS = "WARN,${TMPDIR},%sK,4510K"' % size) + res = bitbake("m4") + self.assertTrue('WARNING: The free space' in res.output) + +class SanityOptionsTest(oeSelfTest): + + def test_options_warnqa_errorqa_switch(self): + bitbake("xcursor-transparent-theme -ccleansstate") + + if "packages-list" not in get_bb_var("ERROR_QA"): + self.write_config("ERROR_QA_append = \" packages-list\"") + + self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') + res = bitbake("xcursor-transparent-theme", ignore_status=True) + self.delete_recipeinc('xcursor-transparent-theme') + self.assertTrue("ERROR: QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors." in res.output) + self.assertEqual(res.status, 1) + self.write_recipeinc('xcursor-transparent-theme', 'PACKAGES += \"${PN}-dbg\"') + self.append_config('ERROR_QA_remove = "packages-list"') + self.append_config('WARN_QA_append = " packages-list"') + res = bitbake("xcursor-transparent-theme") + bitbake("xcursor-transparent-theme -ccleansstate") + self.delete_recipeinc('xcursor-transparent-theme') + self.assertTrue("WARNING: QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors." in res.output) + + def test_sanity_userspace_dependency(self): + self.append_config('WARN_QA_append = " unsafe-references-in-binaries unsafe-references-in-scripts"') + bitbake("-ccleansstate gzip nfs-utils") + res = bitbake("gzip nfs-utils") + self.assertTrue("WARNING: QA Issue: gzip" in res.output) + self.assertTrue("WARNING: QA Issue: nfs-utils" in res.output) + +class BuildhistoryTests(BuildhistoryBase): + + def test_buildhistory_basic(self): + self.run_buildhistory_operation('xcursor-transparent-theme') + self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR'))) + + def test_buildhistory_buildtime_pr_backwards(self): + self.add_command_to_tearDown('cleanup-workdir') + 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) + + + + + + diff --git a/meta/lib/oeqa/selftest/oescripts.py b/meta/lib/oeqa/selftest/oescripts.py new file mode 100644 index 0000000000..4aab2ed095 --- /dev/null +++ b/meta/lib/oeqa/selftest/oescripts.py @@ -0,0 +1,60 @@ +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.utils.commands import Command, runCmd, bitbake, get_bb_var, get_test_layer + +class TestScripts(oeSelfTest): + + def test_cleanup_workdir(self): + path = os.path.dirname(get_bb_var('WORKDIR', 'gzip')) + old_version_recipe = os.path.join(get_bb_var('COREBASE'), 'meta/recipes-extended/gzip/gzip_1.3.12.bb') + old_version = '1.3.12' + bitbake("-ccleansstate gzip") + bitbake("-ccleansstate -b %s" % old_version_recipe) + if os.path.exists(get_bb_var('WORKDIR', "-b %s" % old_version_recipe)): + shutil.rmtree(get_bb_var('WORKDIR', "-b %s" % old_version_recipe)) + if os.path.exists(get_bb_var('WORKDIR', 'gzip')): + shutil.rmtree(get_bb_var('WORKDIR', 'gzip')) + + if os.path.exists(path): + initial_contents = os.listdir(path) + else: + initial_contents = [] + + bitbake('gzip') + intermediary_contents = os.listdir(path) + bitbake("-b %s" % old_version_recipe) + runCmd('cleanup-workdir') + remaining_contents = os.listdir(path) + + expected_contents = [x for x in intermediary_contents if x not in initial_contents] + remaining_not_expected = [x for x in remaining_contents if x not in expected_contents] + self.assertFalse(remaining_not_expected, msg="Not all necessary content has been deleted from %s: %s" % (path, ', '.join(map(str, remaining_not_expected)))) + expected_not_remaining = [x for x in expected_contents if x not in remaining_contents] + self.assertFalse(expected_not_remaining, msg="The script removed extra contents from %s: %s" % (path, ', '.join(map(str, expected_not_remaining)))) + +class BuildhistoryDiffTests(BuildhistoryBase): + + def test_buildhistory_diff(self): + self.add_command_to_tearDown('cleanup-workdir') + target = 'xcursor-transparent-theme' + 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) + result = runCmd("buildhistory-diff -p %s" % get_bb_var('BUILDHISTORY_DIR')) + expected_output = 'PR changed from "r1" to "r0"' + self.assertTrue(expected_output in result.output, msg="Did not find expected output: %s" % result.output) + + + + + + + + + diff --git a/meta/lib/oeqa/selftest/prservice.py b/meta/lib/oeqa/selftest/prservice.py new file mode 100644 index 0000000000..789c05f1e5 --- /dev/null +++ b/meta/lib/oeqa/selftest/prservice.py @@ -0,0 +1,113 @@ +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.utils.commands import runCmd, bitbake, get_bb_var + +class BitbakePrTests(oeSelfTest): + + def get_pr_version(self, package_name): + pkgdata_dir = get_bb_var('PKGDATA_DIR') + package_data_file = os.path.join(pkgdata_dir, 'runtime', package_name) + package_data = ftools.read_file(package_data_file) + find_pr = re.search("PKGR: r[0-9]+\.([0-9]+)", package_data) + self.assertTrue(find_pr) + return int(find_pr.group(1)) + + def get_task_stamp(self, package_name, recipe_task): + stampdata = get_bb_var('STAMP', target=package_name).split('/') + prefix = stampdata[-1] + package_stamps_path = "/".join(stampdata[:-1]) + stamps = [] + for stamp in os.listdir(package_stamps_path): + find_stamp = re.match("%s\.%s\.([a-z0-9]{32})" % (prefix, recipe_task), stamp) + if find_stamp: + stamps.append(find_stamp.group(1)) + self.assertFalse(len(stamps) == 0, msg="Cound not find stamp for task %s for recipe %s" % (recipe_task, package_name)) + self.assertFalse(len(stamps) > 1, msg="Found multiple %s stamps for the %s recipe in the %s directory." % (recipe_task, package_name, package_stamps_path)) + return str(stamps[0]) + + def increment_package_pr(self, package_name): + inc_data = "do_package_append() {\nbb.build.exec_func('do_test_prserv', d)\n}\ndo_test_prserv() {\necho \"The current date is: %s\"\n}" % datetime.datetime.now() + self.write_recipeinc(package_name, inc_data) + bitbake("-ccleansstate %s" % package_name) + 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 + self.write_config(config_package_data) + config_server_data = 'PRSERV_HOST = "%s"' % pr_socket + self.append_config(config_server_data) + + def run_test_pr_service(self, package_name, package_type='rpm', track_task='do_package', pr_socket='localhost:0'): + self.config_pr_tests(package_name, package_type, pr_socket) + + self.increment_package_pr(package_name) + pr_1 = self.get_pr_version(package_name) + stamp_1 = self.get_task_stamp(package_name, track_task) + + self.increment_package_pr(package_name) + pr_2 = self.get_pr_version(package_name) + stamp_2 = self.get_task_stamp(package_name, track_task) + + bitbake("-ccleansstate %s" % package_name) + self.assertTrue(pr_2 - pr_1 == 1) + self.assertTrue(stamp_1 != stamp_2) + + def run_test_pr_export_import(self, package_name, replace_current_db=True): + self.config_pr_tests(package_name) + + self.increment_package_pr(package_name) + pr_1 = self.get_pr_version(package_name) + + 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) + + if replace_current_db: + current_db_path = os.path.join(get_bb_var('PERSISTENT_DIR'), 'prserv.sqlite3') + self.assertTrue(os.path.exists(current_db_path), msg="Path to current PR Service database is invalid: %s" % current_db_path) + os.remove(current_db_path) + + import_result = runCmd("bitbake-prserv-tool import %s" % exported_db_path, ignore_status=True) + os.remove(exported_db_path) + self.assertEqual(import_result.status, 0, msg="PR Service database import failed: %s" % import_result.output) + + self.increment_package_pr(package_name) + pr_2 = self.get_pr_version(package_name) + + bitbake("-ccleansstate %s" % package_name) + self.assertTrue(pr_2 - pr_1 == 1) + + + def test_import_export_replace_db(self): + self.run_test_pr_export_import('m4') + + def test_import_export_override_db(self): + self.run_test_pr_export_import('m4', replace_current_db=False) + + def test_pr_service_rpm_arch_dep(self): + self.run_test_pr_service('m4', 'rpm', 'do_package') + + def test_pr_service_deb_arch_dep(self): + self.run_test_pr_service('m4', 'deb', 'do_package') + + def test_pr_service_ipk_arch_dep(self): + self.run_test_pr_service('m4', 'ipk', 'do_package') + + def test_pr_service_rpm_arch_indep(self): + self.run_test_pr_service('xcursor-transparent-theme', 'rpm', 'do_package') + + def test_pr_service_deb_arch_indep(self): + self.run_test_pr_service('xcursor-transparent-theme', 'deb', 'do_package') + + def test_pr_service_ipk_arch_indep(self): + self.run_test_pr_service('xcursor-transparent-theme', 'ipk', 'do_package') diff --git a/meta/lib/oeqa/selftest/sstate.py b/meta/lib/oeqa/selftest/sstate.py new file mode 100644 index 0000000000..5989724432 --- /dev/null +++ b/meta/lib/oeqa/selftest/sstate.py @@ -0,0 +1,53 @@ +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 + + +class SStateBase(oeSelfTest): + + def setUpLocal(self): + self.temp_sstate_location = None + self.sstate_path = get_bb_var('SSTATE_DIR') + self.distro = get_bb_var('NATIVELSBSTRING') + self.distro_specific_sstate = os.path.join(self.sstate_path, self.distro) + + # Creates a special sstate configuration with the option to add sstate mirrors + def config_sstate(self, temp_sstate_location=False, add_local_mirrors=[]): + self.temp_sstate_location = temp_sstate_location + + if self.temp_sstate_location: + temp_sstate_path = os.path.join(self.builddir, "temp_sstate_%s" % datetime.datetime.now().strftime('%Y%m%d%H%M%S')) + config_temp_sstate = "SSTATE_DIR = \"%s\"" % temp_sstate_path + self.append_config(config_temp_sstate) + self.track_for_cleanup(temp_sstate_path) + self.sstate_path = get_bb_var('SSTATE_DIR') + self.distro = get_bb_var('NATIVELSBSTRING') + self.distro_specific_sstate = os.path.join(self.sstate_path, self.distro) + + if add_local_mirrors: + config_set_sstate_if_not_set = 'SSTATE_MIRRORS ?= ""' + self.append_config(config_set_sstate_if_not_set) + for local_mirror in add_local_mirrors: + self.assertFalse(os.path.join(local_mirror) == os.path.join(self.sstate_path), msg='Cannot add the current sstate path as a sstate mirror') + config_sstate_mirror = "SSTATE_MIRRORS += \"file://.* file:///%s/PATH\"" % local_mirror + self.append_config(config_sstate_mirror) + + # Returns a list containing sstate files + def search_sstate(self, filename_regex, distro_specific=True, distro_nonspecific=True): + result = [] + for root, dirs, files in os.walk(self.sstate_path): + if distro_specific and re.search("%s/[a-z0-9]{2}$" % self.distro, root): + for f in files: + if re.search(filename_regex, f): + result.append(f) + if distro_nonspecific and re.search("%s/[a-z0-9]{2}$" % self.sstate_path, root): + for f in files: + if re.search(filename_regex, f): + result.append(f) + return result diff --git a/meta/lib/oeqa/selftest/sstatetests.py b/meta/lib/oeqa/selftest/sstatetests.py new file mode 100644 index 0000000000..35ff28b04a --- /dev/null +++ b/meta/lib/oeqa/selftest/sstatetests.py @@ -0,0 +1,193 @@ +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 + + +class SStateTests(SStateBase): + + # Test sstate files creation and their location + def run_test_sstate_creation(self, targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True, should_pass=True): + self.config_sstate(temp_sstate_location) + + if self.temp_sstate_location: + bitbake(['-cclean'] + targets) + else: + bitbake(['-ccleansstate'] + targets) + + bitbake(targets) + file_tracker = self.search_sstate('|'.join(map(str, targets)), distro_specific, distro_nonspecific) + if should_pass: + self.assertTrue(file_tracker , msg="Could not find sstate files for: %s" % ', '.join(map(str, targets))) + else: + self.assertTrue(not file_tracker , msg="Found sstate files in the wrong place for: %s" % ', '.join(map(str, targets))) + + def test_sstate_creation_distro_specific_pass(self): + self.run_test_sstate_creation(['binutils-cross', 'binutils-native'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True) + + def test_sstate_creation_distro_specific_fail(self): + self.run_test_sstate_creation(['binutils-cross', 'binutils-native'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True, should_pass=False) + + def test_sstate_creation_distro_nonspecific_pass(self): + self.run_test_sstate_creation(['eglibc-initial'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) + + def test_sstate_creation_distro_nonspecific_fail(self): + self.run_test_sstate_creation(['eglibc-initial'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True, should_pass=False) + + + # Test the sstate files deletion part of the do_cleansstate task + def run_test_cleansstate_task(self, targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True): + self.config_sstate(temp_sstate_location) + + bitbake(['-ccleansstate'] + targets) + + bitbake(targets) + tgz_created = self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific, distro_nonspecific) + self.assertTrue(tgz_created, msg="Could not find sstate .tgz files for: %s" % ', '.join(map(str, targets))) + + siginfo_created = self.search_sstate('|'.join(map(str, [s + '.*?\.siginfo$' for s in targets])), distro_specific, distro_nonspecific) + self.assertTrue(siginfo_created, msg="Could not find sstate .siginfo files for: %s" % ', '.join(map(str, targets))) + + bitbake(['-ccleansstate'] + targets) + 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" % ', '.join(map(str, targets))) + + def test_cleansstate_task_distro_specific_nonspecific(self): + self.run_test_cleansstate_task(['binutils-cross', 'binutils-native', 'eglibc-initial'], distro_specific=True, distro_nonspecific=True, temp_sstate_location=True) + + def test_cleansstate_task_distro_nonspecific(self): + self.run_test_cleansstate_task(['eglibc-initial'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) + + def test_cleansstate_task_distro_specific(self): + self.run_test_cleansstate_task(['binutils-cross', 'binutils-native', 'eglibc-initial'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True) + + + # Test rebuilding of distro-specific sstate files + def run_test_rebuild_distro_specific_sstate(self, targets, temp_sstate_location=True): + self.config_sstate(temp_sstate_location) + + bitbake(['-ccleansstate'] + targets) + + bitbake(targets) + self.assertTrue(self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific=False, distro_nonspecific=True) == [], msg="Found distro non-specific sstate for: %s" % ', '.join(map(str, targets))) + file_tracker_1 = self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False) + self.assertTrue(len(file_tracker_1) >= len(targets), msg = "Not all sstate files ware created for: %s" % ', '.join(map(str, targets))) + + self.track_for_cleanup(self.distro_specific_sstate + "_old") + shutil.copytree(self.distro_specific_sstate, self.distro_specific_sstate + "_old") + shutil.rmtree(self.distro_specific_sstate) + + bitbake(['-cclean'] + targets) + bitbake(targets) + file_tracker_2 = self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific=True, distro_nonspecific=False) + self.assertTrue(len(file_tracker_2) >= len(targets), msg = "Not all sstate files ware created for: %s" % ', '.join(map(str, targets))) + + not_recreated = [x for x in file_tracker_1 if x not in file_tracker_2] + self.assertTrue(not_recreated == [], msg="The following sstate files ware not recreated: %s" % ', '.join(map(str, not_recreated))) + + 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))) + + def test_rebuild_distro_specific_sstate_cross_native_targets(self): + self.run_test_rebuild_distro_specific_sstate(['binutils-cross', 'binutils-native'], temp_sstate_location=True) + + def test_rebuild_distro_specific_sstate_cross_target(self): + self.run_test_rebuild_distro_specific_sstate(['binutils-cross'], temp_sstate_location=True) + + def test_rebuild_distro_specific_sstate_native_target(self): + self.run_test_rebuild_distro_specific_sstate(['binutils-native'], temp_sstate_location=True) + + + # Test the sstate-cache-management script. Each element in the global_config list is used with the corresponding element in the target_config list + # global_config elements are expected to not generate any sstate files that would be removed by sstate-cache-management.sh (such as changing the value of MACHINE) + def run_test_sstate_cache_management_script(self, target, global_config=[''], target_config=[''], ignore_patterns=[]): + self.assertTrue(global_config) + self.assertTrue(target_config) + self.assertTrue(len(global_config) == len(target_config), msg='Lists global_config and target_config should have the same number of elements') + self.config_sstate(temp_sstate_location=True, add_local_mirrors=[self.sstate_path]) + + # If buildhistory is enabled, we need to disable version-going-backwards QA checks for this test. It may report errors otherwise. + if ('buildhistory' in get_bb_var('USER_CLASSES')) or ('buildhistory' in get_bb_var('INHERIT')): + remove_errors_config = 'ERROR_QA_remove = "version-going-backwards"' + self.append_config(remove_errors_config) + + # For not this only checks if random sstate tasks are handled correctly as a group. + # In the future we should add control over what tasks we check for. + + sstate_archs_list = [] + expected_remaining_sstate = [] + for idx in range(len(target_config)): + self.append_config(global_config[idx]) + self.append_recipeinc(target, target_config[idx]) + sstate_arch = get_bb_var('SSTATE_PKGARCH', target) + if not sstate_arch in sstate_archs_list: + sstate_archs_list.append(sstate_arch) + if target_config[idx] == target_config[-1]: + target_sstate_before_build = self.search_sstate(target + '.*?\.tgz$') + bitbake("-cclean %s" % target) + result = bitbake(target, ignore_status=True) + if target_config[idx] == target_config[-1]: + target_sstate_after_build = self.search_sstate(target + '.*?\.tgz$') + expected_remaining_sstate += [x for x in target_sstate_after_build if x not in target_sstate_before_build if not any(pattern in x for pattern in ignore_patterns)] + self.remove_config(global_config[idx]) + self.remove_recipeinc(target, target_config[idx]) + self.assertEqual(result.status, 0) + + runCmd("sstate-cache-management.sh -y --cache-dir=%s --remove-duplicated --extra-archs=%s" % (self.sstate_path, ','.join(map(str, sstate_archs_list)))) + actual_remaining_sstate = [x for x in self.search_sstate(target + '.*?\.tgz$') if not any(pattern in x for pattern in ignore_patterns)] + + actual_not_expected = [x for x in actual_remaining_sstate if x not in expected_remaining_sstate] + self.assertFalse(actual_not_expected, msg="Files should have been removed but ware not: %s" % ', '.join(map(str, actual_not_expected))) + 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))) + + + def test_sstate_cache_management_script_using_pr_1(self): + global_config = [] + target_config = [] + global_config.append('') + target_config.append('PR = "0"') + self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) + + def test_sstate_cache_management_script_using_pr_2(self): + global_config = [] + target_config = [] + global_config.append('') + target_config.append('PR = "0"') + global_config.append('') + target_config.append('PR = "1"') + self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) + + def test_sstate_cache_management_script_using_pr_3(self): + global_config = [] + target_config = [] + global_config.append('MACHINE = "qemux86-64"') + target_config.append('PR = "0"') + global_config.append(global_config[0]) + target_config.append('PR = "1"') + global_config.append('MACHINE = "qemux86"') + target_config.append('PR = "1"') + self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) + + def test_sstate_cache_management_script_using_machine(self): + global_config = [] + target_config = [] + global_config.append('MACHINE = "qemux86-64"') + target_config.append('') + global_config.append('MACHINE = "qemux86"') + target_config.append('') + self.run_test_sstate_cache_management_script('m4', global_config, target_config, ignore_patterns=['populate_lic']) + + + + + + + + diff --git a/meta/lib/oeqa/targetcontrol.py b/meta/lib/oeqa/targetcontrol.py new file mode 100644 index 0000000000..873a66457a --- /dev/null +++ b/meta/lib/oeqa/targetcontrol.py @@ -0,0 +1,175 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# This module is used by testimage.bbclass for setting up and controlling a target machine. + +import os +import shutil +import subprocess +import bb +import traceback +import sys +from oeqa.utils.sshcontrol import SSHControl +from oeqa.utils.qemurunner import QemuRunner +from oeqa.controllers.testtargetloader import TestTargetLoader +from abc import ABCMeta, abstractmethod + +def get_target_controller(d): + testtarget = d.getVar("TEST_TARGET", True) + # 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", True).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 + + def __init__(self, d): + self.connection = None + self.ip = None + self.server_ip = None + self.datetime = d.getVar('DATETIME', True) + self.testdir = d.getVar("TEST_LOG_DIR", True) + self.pn = d.getVar("PN", True) + + @abstractmethod + def deploy(self): + + self.sshlog = os.path.join(self.testdir, "ssh_target_log.%s" % self.datetime) + sshloglink = os.path.join(self.testdir, "ssh_target_log") + if os.path.islink(sshloglink): + os.unlink(sshloglink) + os.symlink(self.sshlog, sshloglink) + bb.note("SSH log file: %s" % self.sshlog) + + @abstractmethod + def start(self, params=None): + pass + + @abstractmethod + def stop(self): + pass + + @abstractmethod + def restart(self, params=None): + pass + + def run(self, cmd, timeout=None): + return self.connection.run(cmd, timeout) + + def copy_to(self, localpath, remotepath): + return self.connection.copy_to(localpath, remotepath) + + def copy_from(self, remotepath, localpath): + return self.connection.copy_from(remotepath, localpath) + + + +class QemuTarget(BaseTarget): + + def __init__(self, d): + + super(QemuTarget, self).__init__(d) + + self.qemulog = os.path.join(self.testdir, "qemu_boot_log.%s" % self.datetime) + self.origrootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.ext3') + self.rootfs = os.path.join(self.testdir, d.getVar("IMAGE_LINK_NAME", True) + '-testimage.ext3') + + self.runner = QemuRunner(machine=d.getVar("MACHINE", True), + rootfs=self.rootfs, + tmpdir = d.getVar("TMPDIR", True), + deploy_dir_image = d.getVar("DEPLOY_DIR_IMAGE", True), + display = d.getVar("BB_ORIGENV", False).getVar("DISPLAY", True), + logfile = self.qemulog, + boottime = int(d.getVar("TEST_QEMUBOOT_TIMEOUT", True))) + + def deploy(self): + try: + shutil.copyfile(self.origrootfs, self.rootfs) + except Exception as e: + bb.fatal("Error copying rootfs: %s" % e) + + qemuloglink = os.path.join(self.testdir, "qemu_boot_log") + if os.path.islink(qemuloglink): + os.unlink(qemuloglink) + os.symlink(self.qemulog, qemuloglink) + + bb.note("rootfs file: %s" % self.rootfs) + bb.note("Qemu log file: %s" % self.qemulog) + super(QemuTarget, self).deploy() + + def start(self, params=None): + if self.runner.start(params): + self.ip = self.runner.ip + self.server_ip = self.runner.server_ip + self.connection = SSHControl(ip=self.ip, logfile=self.sshlog) + else: + raise bb.build.FuncFailed("%s - FAILED to start qemu - check the task log and the boot log" % self.pn) + + def stop(self): + self.runner.stop() + self.connection = None + self.ip = None + self.server_ip = None + + def restart(self, params=None): + if self.runner.restart(params): + self.ip = self.runner.ip + self.server_ip = self.runner.server_ip + self.connection = SSHControl(ip=self.ip, logfile=self.sshlog) + else: + raise bb.build.FuncFailed("%s - FAILED to re-start qemu - check the task log and the boot log" % self.pn) + + +class SimpleRemoteTarget(BaseTarget): + + def __init__(self, d): + super(SimpleRemoteTarget, self).__init__(d) + addr = d.getVar("TEST_TARGET_IP", True) or bb.fatal('Please set TEST_TARGET_IP with the IP address of the machine you want to run the tests on.') + self.ip = addr.split(":")[0] + try: + self.port = addr.split(":")[1] + except IndexError: + self.port = None + bb.note("Target IP: %s" % self.ip) + self.server_ip = d.getVar("TEST_SERVER_IP", True) + 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) + bb.note("Server IP: %s" % self.server_ip) + + def deploy(self): + super(SimpleRemoteTarget, self).deploy() + + def start(self, params=None): + self.connection = SSHControl(self.ip, logfile=self.sshlog, port=self.port) + + def stop(self): + self.connection = None + self.ip = None + self.server_ip = None + + def restart(self, params=None): + pass diff --git a/meta/lib/oeqa/utils/__init__.py b/meta/lib/oeqa/utils/__init__.py new file mode 100644 index 0000000000..8eda92763c --- /dev/null +++ b/meta/lib/oeqa/utils/__init__.py @@ -0,0 +1,3 @@ +# Enable other layers to have modules in the same named directory +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py new file mode 100644 index 0000000000..9b42620610 --- /dev/null +++ b/meta/lib/oeqa/utils/commands.py @@ -0,0 +1,137 @@ +# Copyright (c) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# DESCRIPTION +# This module is mainly used by scripts/oe-selftest and modules under meta/oeqa/selftest +# It provides a class and methods for running commands on the host in a convienent way for tests. + + + +import os +import sys +import signal +import subprocess +import threading +import logging + +class Command(object): + def __init__(self, command, bg=False, timeout=None, data=None, **options): + + self.defaultopts = { + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "stdin": None, + "shell": False, + "bufsize": -1, + } + + self.cmd = command + self.bg = bg + self.timeout = timeout + self.data = data + + self.options = dict(self.defaultopts) + if isinstance(self.cmd, basestring): + self.options["shell"] = True + if self.data: + self.options['stdin'] = subprocess.PIPE + self.options.update(options) + + self.status = None + self.output = None + self.error = None + self.thread = None + + 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() + + self.log.debug("Running command '%s'" % self.cmd) + + if not self.bg: + self.thread.join(self.timeout) + self.stop() + + def stop(self): + if self.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(): + self.process.kill() + self.thread.join() + + self.output = self.output.rstrip() + self.status = self.process.poll() + + self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status)) + # logging the complete output is insane + # bitbake -e output is really big + # and makes the log file useless + if self.status: + lout = "\n".join(self.output.splitlines()[-20:]) + self.log.debug("Last 20 lines:\n%s" % lout) + + +class Result(object): + pass + +def runCmd(command, ignore_status=False, timeout=None, **options): + + result = Result() + + cmd = Command(command, timeout=timeout, **options) + cmd.run() + + result.command = command + result.status = cmd.status + result.output = cmd.output + result.pid = cmd.process.pid + + if result.status and not ignore_status: + raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, result.output)) + + return result + + +def bitbake(command, ignore_status=False, timeout=None, **options): + if isinstance(command, basestring): + cmd = "bitbake " + command + else: + cmd = [ "bitbake" ] + command + + return runCmd(cmd, ignore_status, timeout, **options) + + +def get_bb_env(target=None): + if target: + return runCmd("bitbake -e %s" % target).output + else: + return runCmd("bitbake -e").output + +def get_bb_var(var, target=None): + val = None + bbenv = get_bb_env(target) + for line in bbenv.splitlines(): + if line.startswith(var + "="): + val = line.split('=')[1] + val = val.replace('\"','') + break + return val + +def get_test_layer(): + layers = get_bb_var("BBLAYERS").split() + testlayer = None + for l in layers: + if "/meta-selftest" in l and os.path.isdir(l): + testlayer = l + break + return testlayer diff --git a/meta/lib/oeqa/utils/decorators.py b/meta/lib/oeqa/utils/decorators.py new file mode 100644 index 0000000000..b99da8d76d --- /dev/null +++ b/meta/lib/oeqa/utils/decorators.py @@ -0,0 +1,50 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# Some custom decorators that can be used by unittests +# Most useful is skipUnlessPassed which can be used for +# creating dependecies between two test methods. + +from oeqa.oetest import * + +class skipIfFailure(object): + + def __init__(self,testcase): + self.testcase = testcase + + def __call__(self,f): + def wrapped_f(*args): + if self.testcase in (oeTest.testFailures or oeTest.testErrors): + raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase) + return f(*args) + wrapped_f.__name__ = f.__name__ + return wrapped_f + +class skipIfSkipped(object): + + def __init__(self,testcase): + self.testcase = testcase + + def __call__(self,f): + def wrapped_f(*args): + if self.testcase in oeTest.testSkipped: + raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase) + return f(*args) + wrapped_f.__name__ = f.__name__ + return wrapped_f + +class skipUnlessPassed(object): + + def __init__(self,testcase): + self.testcase = testcase + + def __call__(self,f): + def wrapped_f(*args): + if self.testcase in oeTest.testSkipped or \ + self.testcase in oeTest.testFailures or \ + self.testcase in oeTest.testErrors: + raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase) + return f(*args) + wrapped_f.__name__ = f.__name__ + return wrapped_f diff --git a/meta/lib/oeqa/utils/ftools.py b/meta/lib/oeqa/utils/ftools.py new file mode 100644 index 0000000000..64ebe3d217 --- /dev/null +++ b/meta/lib/oeqa/utils/ftools.py @@ -0,0 +1,27 @@ +import os +import re + +def write_file(path, data): + wdata = data.rstrip() + "\n" + with open(path, "w") as f: + f.write(wdata) + +def append_file(path, data): + wdata = data.rstrip() + "\n" + with open(path, "a") as f: + f.write(wdata) + +def read_file(path): + data = None + with open(path) as f: + data = f.read() + return data + +def remove_from_file(path, data): + lines = read_file(path).splitlines() + rmdata = data.strip().splitlines() + for l in rmdata: + for c in range(0, lines.count(l)): + i = lines.index(l) + del(lines[i]) + write_file(path, "\n".join(lines)) diff --git a/meta/lib/oeqa/utils/httpserver.py b/meta/lib/oeqa/utils/httpserver.py new file mode 100644 index 0000000000..f161a1bddd --- /dev/null +++ b/meta/lib/oeqa/utils/httpserver.py @@ -0,0 +1,33 @@ +import SimpleHTTPServer +import multiprocessing +import os + +class HTTPServer(SimpleHTTPServer.BaseHTTPServer.HTTPServer): + + def server_start(self, root_dir): + os.chdir(root_dir) + self.serve_forever() + +class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + + def log_message(self, format_str, *args): + pass + +class HTTPService(object): + + def __init__(self, root_dir, host=''): + self.root_dir = root_dir + self.host = host + self.port = 0 + + def start(self): + self.server = HTTPServer((self.host, self.port), HTTPRequestHandler) + if self.port == 0: + self.port = self.server.server_port + self.process = multiprocessing.Process(target=self.server.server_start, args=[self.root_dir]) + self.process.start() + + def stop(self): + self.server.server_close() + self.process.terminate() + self.process.join() diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py new file mode 100644 index 0000000000..f1a7e24ab7 --- /dev/null +++ b/meta/lib/oeqa/utils/qemurunner.py @@ -0,0 +1,237 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# This module provides a class for starting qemu images using runqemu. +# It's used by testimage.bbclass. + +import subprocess +import os +import time +import signal +import re +import socket +import select +import bb + +class QemuRunner: + + def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime): + + # Popen object for runqemu + self.runqemu = None + # pid of the qemu process that runqemu will start + self.qemupid = None + # target ip - from the command line + self.ip = None + # host ip - where qemu is running + self.server_ip = None + + self.machine = machine + self.rootfs = rootfs + self.display = display + self.tmpdir = tmpdir + self.deploy_dir_image = deploy_dir_image + self.logfile = logfile + self.boottime = boottime + + self.runqemutime = 60 + + self.create_socket() + + + def create_socket(self): + + self.bootlog = '' + self.qemusock = None + + try: + self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server_socket.setblocking(0) + self.server_socket.bind(("127.0.0.1",0)) + self.server_socket.listen(2) + self.serverport = self.server_socket.getsockname()[1] + bb.note("Created listening socket for qemu serial console on: 127.0.0.1:%s" % self.serverport) + except socket.error, msg: + self.server_socket.close() + bb.fatal("Failed to create listening socket: %s" %msg[1]) + + + def log(self, msg): + if self.logfile: + with open(self.logfile, "a") as f: + f.write("%s" % msg) + + def start(self, qemuparams = None): + + if self.display: + os.environ["DISPLAY"] = self.display + else: + bb.error("To start qemu I need a X desktop, please set DISPLAY correctly (e.g. DISPLAY=:1)") + return False + if not os.path.exists(self.rootfs): + bb.error("Invalid rootfs %s" % self.rootfs) + return False + if not os.path.exists(self.tmpdir): + bb.error("Invalid TMPDIR path %s" % self.tmpdir) + return False + else: + os.environ["OE_TMPDIR"] = self.tmpdir + if not os.path.exists(self.deploy_dir_image): + bb.error("Invalid DEPLOY_DIR_IMAGE path %s" % self.deploy_dir_image) + return False + else: + os.environ["DEPLOY_DIR_IMAGE"] = self.deploy_dir_image + + # Set this flag so that Qemu doesn't do any grabs as SDL grabs interact + # badly with screensavers. + os.environ["QEMU_DONT_GRAB"] = "1" + self.qemuparams = 'bootparams="console=tty1 console=ttyS0,115200n8" qemuparams="-serial tcp:127.0.0.1:%s"' % self.serverport + if qemuparams: + self.qemuparams = self.qemuparams[:-1] + " " + qemuparams + " " + '\"' + + launch_cmd = 'runqemu %s %s %s' % (self.machine, self.rootfs, self.qemuparams) + self.runqemu = subprocess.Popen(launch_cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,preexec_fn=os.setpgrp) + + bb.note("runqemu started, pid is %s" % self.runqemu.pid) + bb.note("waiting at most %s seconds for qemu pid" % self.runqemutime) + endtime = time.time() + self.runqemutime + while not self.is_alive() and time.time() < endtime: + time.sleep(1) + + if self.is_alive(): + bb.note("qemu started - qemu procces pid is %s" % self.qemupid) + cmdline = '' + with open('/proc/%s/cmdline' % self.qemupid) as p: + cmdline = p.read() + ips = re.findall("((?:[0-9]{1,3}\.){3}[0-9]{1,3})", cmdline.split("ip=")[1]) + if not ips or len(ips) != 3: + bb.note("Couldn't get ip from qemu process arguments! Here is the qemu command line used: %s" % cmdline) + self.stop() + return False + else: + self.ip = ips[0] + self.server_ip = ips[1] + bb.note("Target IP: %s" % self.ip) + bb.note("Server IP: %s" % self.server_ip) + bb.note("Waiting at most %d seconds for login banner" % self.boottime ) + endtime = time.time() + self.boottime + socklist = [self.server_socket] + reachedlogin = False + stopread = False + while time.time() < endtime and not stopread: + sread, swrite, serror = select.select(socklist, [], [], 5) + for sock in sread: + if sock is self.server_socket: + self.qemusock, addr = self.server_socket.accept() + self.qemusock.setblocking(0) + socklist.append(self.qemusock) + socklist.remove(self.server_socket) + bb.note("Connection from %s:%s" % addr) + else: + data = sock.recv(1024) + if data: + self.log(data) + self.bootlog += data + if re.search("qemu.* login:", self.bootlog): + stopread = True + reachedlogin = True + bb.note("Reached login banner") + else: + socklist.remove(sock) + sock.close() + stopread = True + + if not reachedlogin: + bb.note("Target didn't reached login boot in %d seconds" % self.boottime) + lines = "\n".join(self.bootlog.splitlines()[-5:]) + bb.note("Last 5 lines of text:\n%s" % lines) + bb.note("Check full boot log: %s" % self.logfile) + self.stop() + return False + else: + bb.note("Qemu pid didn't appeared in %s seconds" % self.runqemutime) + output = self.runqemu.stdout + self.stop() + bb.note("Output from runqemu:\n%s" % output.read()) + return False + + return self.is_alive() + + def stop(self): + + if self.runqemu: + bb.note("Sending SIGTERM to runqemu") + os.killpg(self.runqemu.pid, signal.SIGTERM) + endtime = time.time() + self.runqemutime + while self.runqemu.poll() is None and time.time() < endtime: + time.sleep(1) + if self.runqemu.poll() is None: + bb.note("Sending SIGKILL to runqemu") + os.killpg(self.runqemu.pid, signal.SIGKILL) + self.runqemu = None + if self.server_socket: + self.server_socket.close() + self.server_socket = None + self.qemupid = None + self.ip = None + + def restart(self, qemuparams = None): + bb.note("Restarting qemu process") + if self.runqemu.poll() is None: + self.stop() + self.create_socket() + if self.start(qemuparams): + return True + return False + + def is_alive(self): + 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)): + 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.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-internal 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]] diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py new file mode 100644 index 0000000000..d355d5e8e9 --- /dev/null +++ b/meta/lib/oeqa/utils/sshcontrol.py @@ -0,0 +1,127 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# Provides a class for setting up ssh connections, +# running commands and copying files to/from a target. +# It's used by testimage.bbclass and tests in lib/oeqa/runtime. + +import subprocess +import time +import os +import select + + +class SSHProcess(object): + def __init__(self, **options): + + self.defaultopts = { + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "stdin": None, + "shell": False, + "bufsize": -1, + "preexec_fn": os.setsid, + } + self.options = dict(self.defaultopts) + self.options.update(options) + self.status = None + self.output = None + self.process = None + self.starttime = None + + def run(self, command, timeout=None): + self.starttime = time.time() + output = '' + self.process = subprocess.Popen(command, **self.options) + if timeout: + endtime = self.starttime + timeout + eof = False + while time.time() < endtime and not eof: + if select.select([self.process.stdout], [], [], 5)[0] != []: + data = os.read(self.process.stdout.fileno(), 1024) + if not data: + self.process.stdout.close() + eof = True + else: + output += data + endtime = time.time() + timeout + + # process hasn't returned yet + if not eof: + self.process.terminate() + time.sleep(5) + try: + self.process.kill() + except OSError: + pass + output += "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime) + else: + output = self.process.communicate()[0] + + self.status = self.process.wait() + self.output = output.rstrip() + return (self.status, self.output) + + +class SSHControl(object): + def __init__(self, ip, logfile=None, timeout=300, user='root', port=None): + self.ip = ip + self.defaulttimeout = timeout + self.ignore_status = True + self.logfile = logfile + self.user = user + self.ssh_options = [ + '-o', 'UserKnownHostsFile=/dev/null', + '-o', 'StrictHostKeyChecking=no', + '-o', 'LogLevel=ERROR' + ] + self.ssh = ['ssh', '-l', self.user ] + self.ssh_options + self.scp = ['scp'] + self.ssh_options + if port: + self.ssh = self.ssh + [ '-p', port ] + self.scp = self.scp + [ '-P', port ] + + def log(self, msg): + if self.logfile: + with open(self.logfile, "a") as f: + f.write("%s\n" % msg) + + def _internal_run(self, command, timeout=None, ignore_status = True): + self.log("[Running]$ %s" % " ".join(command)) + + proc = SSHProcess() + status, output = proc.run(command, timeout) + + self.log("%s" % output) + self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime)) + + if status and not ignore_status: + raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output)) + + return (status, output) + + def run(self, command, timeout=None): + """ + command - ssh command to run + timeout= - kill command if there is no output after seconds + timeout=None - kill command if there is no output after a default value seconds + timeout=0 - no timeout, let command run until it returns + """ + + # We need to source /etc/profile for a proper PATH on the target + command = self.ssh + [self.ip, ' . /etc/profile; ' + command] + + if timeout is None: + return self._internal_run(command, self.defaulttimeout, self.ignore_status) + if timeout == 0: + return self._internal_run(command, None, self.ignore_status) + return self._internal_run(command, timeout, self.ignore_status) + + def copy_to(self, localpath, remotepath): + 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] + return self._internal_run(command, ignore_status=False) diff --git a/meta/lib/oeqa/utils/targetbuild.py b/meta/lib/oeqa/utils/targetbuild.py new file mode 100644 index 0000000000..32296762c0 --- /dev/null +++ b/meta/lib/oeqa/utils/targetbuild.py @@ -0,0 +1,68 @@ +# Copyright (C) 2013 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# Provides a class for automating build tests for projects + +import os +import re +import subprocess + + +class TargetBuildProject(): + + def __init__(self, target, d, uri, foldername=None): + self.target = target + self.d = d + self.uri = uri + self.targetdir = "~/" + self.archive = os.path.basename(uri) + self.localarchive = "/tmp/" + self.archive + self.fname = re.sub(r'.tar.bz2|tar.gz$', '', self.archive) + if foldername: + self.fname = foldername + + def download_archive(self): + + exportvars = ['HTTP_PROXY', 'http_proxy', + 'HTTPS_PROXY', 'https_proxy', + 'FTP_PROXY', 'ftp_proxy', + 'FTPS_PROXY', 'ftps_proxy', + 'NO_PROXY', 'no_proxy', + 'ALL_PROXY', 'all_proxy', + 'SOCKS5_USER', 'SOCKS5_PASSWD'] + + cmd = '' + for var in exportvars: + val = self.d.getVar(var, True) + if val: + cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd) + + cmd = cmd + "wget -O %s %s" % (self.localarchive, self.uri) + subprocess.check_call(cmd, shell=True) + + (status, output) = self.target.copy_to(self.localarchive, self.targetdir) + if status != 0: + raise Exception("Failed to copy archive to target, output: %s" % output) + + (status, output) = self.target.run('tar xf %s%s -C %s' % (self.targetdir, self.archive, self.targetdir)) + if status != 0: + raise Exception("Failed to extract archive, output: %s" % output) + + #Change targetdir to project folder + self.targetdir = self.targetdir + self.fname + + # The timeout parameter of target.run is set to 0 to make the ssh command + # run with no timeout. + def run_configure(self): + return self.target.run('cd %s; ./configure' % self.targetdir, 0)[0] + + def run_make(self): + return self.target.run('cd %s; make' % self.targetdir, 0)[0] + + def run_install(self): + return self.target.run('cd %s; make install' % self.targetdir, 0)[0] + + def clean(self): + self.target.run('rm -rf %s' % self.targetdir) + subprocess.call('rm -f %s' % self.localarchive, shell=True) -- cgit v1.2.3-54-g00ecf