From 972dcfcdbfe75dcfeb777150c136576cf1a71e99 Mon Sep 17 00:00:00 2001 From: Tudor Florea Date: Fri, 9 Oct 2015 22:59:03 +0200 Subject: initial commit for Enea Linux 5.0 arm Signed-off-by: Tudor Florea --- meta/lib/oeqa/__init__.py | 0 meta/lib/oeqa/controllers/__init__.py | 3 + meta/lib/oeqa/controllers/masterimage.py | 201 ++++++++++++ meta/lib/oeqa/controllers/testtargetloader.py | 70 ++++ meta/lib/oeqa/oetest.py | 106 ++++++ meta/lib/oeqa/runexported.py | 140 ++++++++ meta/lib/oeqa/runtime/__init__.py | 3 + meta/lib/oeqa/runtime/_ptest.py | 124 +++++++ meta/lib/oeqa/runtime/buildcvs.py | 31 ++ meta/lib/oeqa/runtime/buildiptables.py | 31 ++ meta/lib/oeqa/runtime/buildsudoku.py | 28 ++ meta/lib/oeqa/runtime/connman.py | 30 ++ meta/lib/oeqa/runtime/date.py | 23 ++ meta/lib/oeqa/runtime/df.py | 12 + meta/lib/oeqa/runtime/dmesg.py | 12 + 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.cpp | 3 + 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 | 46 +++ meta/lib/oeqa/runtime/kernelmodule.py | 34 ++ meta/lib/oeqa/runtime/ldd.py | 20 ++ meta/lib/oeqa/runtime/logrotate.py | 28 ++ meta/lib/oeqa/runtime/multilib.py | 18 ++ meta/lib/oeqa/runtime/pam.py | 25 ++ meta/lib/oeqa/runtime/parselogs.py | 178 +++++++++++ meta/lib/oeqa/runtime/perl.py | 29 ++ meta/lib/oeqa/runtime/ping.py | 20 ++ meta/lib/oeqa/runtime/python.py | 34 ++ meta/lib/oeqa/runtime/rpm.py | 53 +++ meta/lib/oeqa/runtime/scanelf.py | 28 ++ meta/lib/oeqa/runtime/scp.py | 22 ++ meta/lib/oeqa/runtime/skeletoninit.py | 29 ++ meta/lib/oeqa/runtime/smart.py | 121 +++++++ meta/lib/oeqa/runtime/ssh.py | 19 ++ meta/lib/oeqa/runtime/syslog.py | 48 +++ meta/lib/oeqa/runtime/systemd.py | 88 +++++ meta/lib/oeqa/runtime/vnc.py | 20 ++ meta/lib/oeqa/runtime/x32lib.py | 18 ++ meta/lib/oeqa/runtime/xorg.py | 17 + meta/lib/oeqa/sdk/__init__.py | 3 + meta/lib/oeqa/sdk/buildcvs.py | 25 ++ meta/lib/oeqa/sdk/buildiptables.py | 26 ++ meta/lib/oeqa/sdk/buildsudoku.py | 26 ++ meta/lib/oeqa/selftest/__init__.py | 2 + meta/lib/oeqa/selftest/_sstatetests_noauto.py | 95 ++++++ meta/lib/oeqa/selftest/_toaster.py | 445 ++++++++++++++++++++++++++ meta/lib/oeqa/selftest/base.py | 131 ++++++++ meta/lib/oeqa/selftest/bblayers.py | 43 +++ meta/lib/oeqa/selftest/bbtests.py | 178 +++++++++++ meta/lib/oeqa/selftest/buildhistory.py | 45 +++ meta/lib/oeqa/selftest/buildoptions.py | 120 +++++++ meta/lib/oeqa/selftest/oescripts.py | 54 ++++ meta/lib/oeqa/selftest/prservice.py | 121 +++++++ meta/lib/oeqa/selftest/sstate.py | 53 +++ meta/lib/oeqa/selftest/sstatetests.py | 204 ++++++++++++ meta/lib/oeqa/targetcontrol.py | 199 ++++++++++++ meta/lib/oeqa/utils/__init__.py | 15 + meta/lib/oeqa/utils/commands.py | 154 +++++++++ meta/lib/oeqa/utils/decorators.py | 158 +++++++++ meta/lib/oeqa/utils/ftools.py | 27 ++ meta/lib/oeqa/utils/httpserver.py | 35 ++ meta/lib/oeqa/utils/logparser.py | 125 ++++++++ meta/lib/oeqa/utils/qemurunner.py | 237 ++++++++++++++ meta/lib/oeqa/utils/sshcontrol.py | 138 ++++++++ meta/lib/oeqa/utils/targetbuild.py | 132 ++++++++ 69 files changed, 4569 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/_ptest.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.cpp 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/parselogs.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/sdk/__init__.py create mode 100644 meta/lib/oeqa/sdk/buildcvs.py create mode 100644 meta/lib/oeqa/sdk/buildiptables.py create mode 100644 meta/lib/oeqa/sdk/buildsudoku.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/_toaster.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/logparser.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..311f0cf68c --- /dev/null +++ b/meta/lib/oeqa/controllers/masterimage.py @@ -0,0 +1,201 @@ +# Copyright (C) 2014 Intel Corporation +# +# Released under the MIT license (see COPYING.MIT) + +# This module adds support to testimage.bbclass to deploy images and run +# tests using a "master image" - this is a "known good" image that is +# installed onto the device as part of initial setup and will be booted into +# with no interaction; we can then use it to deploy the image to be tested +# to a second partition before running the tests. +# +# For an example master image, see core-image-testmaster +# (meta/recipes-extended/images/core-image-testmaster.bb) + +import os +import bb +import traceback +import time +import subprocess + +import oeqa.targetcontrol +import oeqa.utils.sshcontrol as sshcontrol +import oeqa.utils.commands as commands +from oeqa.utils import CommandError + +from abc import ABCMeta, abstractmethod + +class MasterImageHardwareTarget(oeqa.targetcontrol.BaseTarget): + + __metaclass__ = ABCMeta + + supported_image_fstypes = ['tar.gz', 'tar.bz2'] + + def __init__(self, d): + super(MasterImageHardwareTarget, self).__init__(d) + + # target ip + 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) + + # test rootfs + kernel + self.image_fstype = self.get_image_fstype(d) + self.rootfs = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("IMAGE_LINK_NAME", True) + '.' + self.image_fstype) + self.kernel = os.path.join(d.getVar("DEPLOY_DIR_IMAGE", True), d.getVar("KERNEL_IMAGETYPE") + '-' + d.getVar('MACHINE') + '.bin') + 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) + + # master ssh connection + self.master = None + # if the user knows what they are doing, then by all means... + self.user_cmds = d.getVar("TEST_DEPLOY_CMDS", True) + self.deploy_cmds = 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.serialcontrol_cmd = d.getVar("TEST_SERIALCONTROL_CMD", True) or None + self.serialcontrol_args = d.getVar("TEST_SERIALCONTROL_EXTRA_ARGS") or "" + + self.origenv = os.environ + if self.powercontrol_cmd or self.serialcontrol_cmd: + # 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) + + if self.powercontrol_cmd: + if self.powercontrol_args: + self.powercontrol_cmd = "%s %s" % (self.powercontrol_cmd, self.powercontrol_args) + if self.serialcontrol_cmd: + if self.serialcontrol_args: + self.serialcontrol_cmd = "%s %s" % (self.serialcontrol_cmd, self.serialcontrol_args) + + def power_ctl(self, msg): + if self.powercontrol_cmd: + cmd = "%s %s" % (self.powercontrol_cmd, msg) + try: + commands.runCmd(cmd, assert_error=False, preexec_fn=os.setsid, env=self.origenv) + except CommandError as e: + bb.fatal(str(e)) + + 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 _wait_until_booted(self): + ''' Waits until the target device has booted (if we have just power cycled it) ''' + # Subclasses with better methods of determining boot can override this + time.sleep(120) + + def deploy(self): + # base class just sets the ssh log file for us + super(MasterImageHardwareTarget, self).deploy() + self.master = sshcontrol.SSHControl(ip=self.ip, logfile=self.sshlog, timeout=600, port=self.port) + status, output = self.master.run("cat /etc/masterimage") + if status != 0: + # We're not booted into the master image, so try rebooting + bb.plain("%s - booting into the master image" % self.pn) + self.power_ctl("cycle") + self._wait_until_booted() + + bb.plain("%s - deploying image on target" % self.pn) + status, output = self.master.run("cat /etc/masterimage") + if status != 0: + bb.fatal("No ssh connectivity or target isn't running a master image.\n%s" % output) + if self.user_cmds: + self.deploy_cmds = self.user_cmds.split("\n") + try: + self._deploy() + except Exception as e: + bb.fatal("Failed deploying test image: %s" % e) + + @abstractmethod + def _deploy(self): + pass + + def start(self, params=None): + bb.plain("%s - boot test image on target" % self.pn) + self._start() + # 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) + + @abstractmethod + def _start(self): + pass + + def stop(self): + bb.plain("%s - reboot/powercycle target" % self.pn) + self.power_cycle(self.connection) + + +class GummibootTarget(MasterImageHardwareTarget): + + def __init__(self, d): + super(GummibootTarget, self).__init__(d) + # 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' + 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 xvf ~/test-rootfs.%s -C /mnt/testrootfs' % self.image_fstype, + 'printf "%s" > /sys/firmware/efi/efivars/LoaderEntryOneShot-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f' % self.efivarvalue + ] + + def _deploy(self): + # 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." + self.image_fstype) + self.master.copy_to(self.kernel, "~/test-kernel") + for cmd in self.deploy_cmds: + self.master.run(cmd) + + def _start(self, params=None): + self.power_cycle(self.master) + # there are better ways than a timeout but this should work for now + time.sleep(120) diff --git a/meta/lib/oeqa/controllers/testtargetloader.py b/meta/lib/oeqa/controllers/testtargetloader.py new file mode 100644 index 0000000000..a1b7b1d92b --- /dev/null +++ b/meta/lib/oeqa/controllers/testtargetloader.py @@ -0,0 +1,70 @@ +import types +import bb +import os + +# 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..0b7e7dc42d --- /dev/null +++ b/meta/lib/oeqa/oetest.py @@ -0,0 +1,106 @@ +# 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 +import subprocess +from oeqa.utils.decorators import LogResults + +def loadTests(tc, type="runtime"): + if type == "runtime": + # 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() + elif type == "sdk": + # set the context object passed from the test class + setattr(oeTest, "tc", tc) + testloader = unittest.TestLoader() + testloader.sortTestMethodsUsing = None + suite = testloader.loadTestsFromNames(tc.testslist) + + return suite + +def runTests(tc, type="runtime"): + + suite = loadTests(tc, type) + print("Test modules %s" % tc.testslist) + print("Found %s tests" % suite.countTestCases()) + runner = unittest.TextTestRunner(verbosity=2) + result = runner.run(suite) + + return result + +@LogResults +class oeTest(unittest.TestCase): + + longMessage = True + + @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) + + #TODO: use package_manager.py to install packages on any type of image + def install_packages(self, packagelist): + for package in packagelist: + (status, result) = self.target.run("smart install -y "+package) + if status != 0: + return status + +class oeSDKTest(oeTest): + def __init__(self, methodName='runTest'): + self.sdktestdir = oeSDKTest.tc.sdktestdir + super(oeSDKTest, 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/_ptest.py b/meta/lib/oeqa/runtime/_ptest.py new file mode 100644 index 0000000000..4c58dc1d7f --- /dev/null +++ b/meta/lib/oeqa/runtime/_ptest.py @@ -0,0 +1,124 @@ +import unittest, os, shutil +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import * +from oeqa.utils.logparser import * +from oeqa.utils.httpserver import HTTPService +import bb +import glob +from oe.package_manager import RpmPkgsList +import subprocess + +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 PtestRunnerTest(oeRuntimeTest): + + # a ptest log parser + def parse_ptest(self, logfile): + parser = Lparser(test_0_pass_regex="^PASS:(.+)", test_0_fail_regex="^FAIL:(.+)", section_0_begin_regex="^BEGIN: .*/(.+)/ptest", section_0_end_regex="^END: .*/(.+)/ptest") + parser.init() + result = Result() + + with open(logfile) as f: + for line in f: + result_tuple = parser.parse_line(line) + if not result_tuple: + continue + result_tuple = line_type, category, status, name = parser.parse_line(line) + + if line_type == 'section' and status == 'begin': + current_section = name + continue + + if line_type == 'section' and status == 'end': + current_section = None + continue + + if line_type == 'test' and status == 'pass': + result.store(current_section, name, status) + continue + + if line_type == 'test' and status == 'fail': + result.store(current_section, name, status) + continue + + result.sort_tests() + return result + + @classmethod + def setUpClass(self): + #note the existing channels that are on the board before creating new ones +# self.existingchannels = set() +# (status, result) = oeRuntimeTest.tc.target.run('smart channel --show | grep "\["', 0) +# for x in result.split("\n"): +# self.existingchannels.add(x) + 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() + #remove created channels to be able to repeat the tests on same image +# (status, result) = oeRuntimeTest.tc.target.run('smart channel --show | grep "\["', 0) +# for x in result.split("\n"): +# if x not in self.existingchannels: +# oeRuntimeTest.tc.target.run('smart channel --remove '+x[1:-1]+' -y', 0) + + def add_smart_channel(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.target.run('smart channel -y --add {a} type=rpm-md baseurl={u}/{a}'.format(a=arch, u=deploy_url), 0) + self.target.run('smart update', 0) + + def install_complementary(self, globs=None): + installed_pkgs_file = os.path.join(oeRuntimeTest.tc.d.getVar('WORKDIR', True), + "installed_pkgs.txt") + self.pkgs_list = RpmPkgsList(oeRuntimeTest.tc.d, oeRuntimeTest.tc.d.getVar('IMAGE_ROOTFS', True), oeRuntimeTest.tc.d.getVar('arch_var', True), oeRuntimeTest.tc.d.getVar('os_var', True)) + with open(installed_pkgs_file, "w+") as installed_pkgs: + installed_pkgs.write(self.pkgs_list.list("arch")) + + cmd = [bb.utils.which(os.getenv('PATH'), "oe-pkgdata-util"), + "glob", oeRuntimeTest.tc.d.getVar('PKGDATA_DIR', True), installed_pkgs_file, + globs] + try: + bb.note("Installing complementary packages ...") + complementary_pkgs = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + bb.fatal("Could not compute complementary packages list. Command " + "'%s' returned %d:\n%s" % + (' '.join(cmd), e.returncode, e.output)) + + return complementary_pkgs.split() + + def setUp(self): + self.buildhist_dir = oeRuntimeTest.tc.d.getVar("BUILDHISTORY_DIR_IMAGE", True) + self.assertTrue(os.path.exists(self.buildhist_dir)) + self.ptest_log = os.path.join(oeRuntimeTest.tc.d.getVar("TEST_LOG_DIR",True), "ptest-%s.log" % oeRuntimeTest.tc.d.getVar('DATETIME', True)) + + @skipUnlessPassed('test_ssh') + def test_ptestrunner(self): + self.add_smart_channel() + cond = oeRuntimeTest.hasPackage("ptest-runner") and oeRuntimeTest.hasFeature("ptest") and oeRuntimeTest.hasPackage("-ptest") + if not cond: + self.install_packages(self.install_complementary("*-ptest")) + self.install_packages(['ptest-runner']) + + self.target.run('/usr/bin/ptest-runner > /tmp/ptest.log 2>&1', 0) + self.target.copy_from('/tmp/ptest.log', self.ptest_log) + shutil.copyfile(self.ptest_log, os.path.join(self.buildhist_dir, "ptest.log")) + + result = self.parse_ptest(os.path.join(self.buildhist_dir, "ptest.log")) + log_results_to_location = "./results" + if os.path.exists(log_results_to_location): + shutil.rmtree(log_results_to_location) + os.makedirs(log_results_to_location) + + result.log_as_files(log_results_to_location, test_status = ['fail']) diff --git a/meta/lib/oeqa/runtime/buildcvs.py b/meta/lib/oeqa/runtime/buildcvs.py new file mode 100644 index 0000000000..fe6cbfbcd5 --- /dev/null +++ b/meta/lib/oeqa/runtime/buildcvs.py @@ -0,0 +1,31 @@ +from oeqa.oetest import oeRuntimeTest, skipModule +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() + + @testcase(205) + @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..09e252df8c --- /dev/null +++ b/meta/lib/oeqa/runtime/buildiptables.py @@ -0,0 +1,31 @@ +from oeqa.oetest import oeRuntimeTest, skipModule +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() + + @testcase(206) + @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..802b060010 --- /dev/null +++ b/meta/lib/oeqa/runtime/buildsudoku.py @@ -0,0 +1,28 @@ +from oeqa.oetest import oeRuntimeTest, skipModule +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() + + @testcase(207) + @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..cc537f7766 --- /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)) + + @testcase(221) + @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..97e8ee42ad --- /dev/null +++ b/meta/lib/oeqa/runtime/date.py @@ -0,0 +1,23 @@ +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * +import re + +class DateTest(oeRuntimeTest): + + @testcase(211) + @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..09569d5ff6 --- /dev/null +++ b/meta/lib/oeqa/runtime/df.py @@ -0,0 +1,12 @@ +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + + +class DfTest(oeRuntimeTest): + + @testcase(234) + @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..5831471e50 --- /dev/null +++ b/meta/lib/oeqa/runtime/dmesg.py @@ -0,0 +1,12 @@ +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + + +class DmesgTest(oeRuntimeTest): + + @testcase(215) + @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 -v usbhid | 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.cpp b/meta/lib/oeqa/runtime/files/test.cpp new file mode 100644 index 0000000000..9e1a76473d --- /dev/null +++ b/meta/lib/oeqa/runtime/files/test.cpp @@ -0,0 +1,3 @@ +#include + +int main() {} \ No newline at end of file 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..a7f62e1758 --- /dev/null +++ b/meta/lib/oeqa/runtime/gcc.py @@ -0,0 +1,46 @@ +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") + oeRuntimeTest.tc.target.copy_to(os.path.join(oeRuntimeTest.tc.filesdir, "test.cpp"), "/tmp/test.cpp") + + @testcase(203) + 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) + + @testcase(200) + 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_gpp2_compile(self): + (status, output) = self.target.run('g++ /tmp/test.cpp -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) + + @testcase(204) + 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..2e81720327 --- /dev/null +++ b/meta/lib/oeqa/runtime/kernelmodule.py @@ -0,0 +1,34 @@ +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") + + @testcase('316') + @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..bce56c4270 --- /dev/null +++ b/meta/lib/oeqa/runtime/ldd.py @@ -0,0 +1,20 @@ +import unittest +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 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) + + @testcase(239) + @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..86d791c300 --- /dev/null +++ b/meta/lib/oeqa/runtime/logrotate.py @@ -0,0 +1,28 @@ +# 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)) + + @testcase(289) + @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..ab0a6ccd69 --- /dev/null +++ b/meta/lib/oeqa/runtime/multilib.py @@ -0,0 +1,18 @@ +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): + + @testcase('279') + @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..c8205c9abc --- /dev/null +++ b/meta/lib/oeqa/runtime/pam.py @@ -0,0 +1,25 @@ +# 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, skipModule +from oeqa.utils.decorators import * + +def setUpModule(): + if not oeRuntimeTest.hasFeature("pam"): + skipModule("target doesn't have 'pam' in DISTRO_FEATURES") + + +class PamBasicTest(oeRuntimeTest): + + @testcase(287) + @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, 0, 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, 0, 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, 0, msg = "useradd command does not work as expected. Status and output:%s and %s" %(status, output)) diff --git a/meta/lib/oeqa/runtime/parselogs.py b/meta/lib/oeqa/runtime/parselogs.py new file mode 100644 index 0000000000..42cb1b5e6f --- /dev/null +++ b/meta/lib/oeqa/runtime/parselogs.py @@ -0,0 +1,178 @@ +import os +import unittest +from oeqa.oetest import oeRuntimeTest +from oeqa.utils.decorators import * + +#in the future these lists could be moved outside of module +errors = ["error", "cannot", "can\'t", "failed"] + +common_errors = [ + '(WW) warning, (EE) error, (NI) not implemented, (??) unknown.', + 'dma timeout', + 'can\'t add hid device:', + 'usbhid: probe of ', + ] + +x86_common = [ + '[drm:psb_do_init] *ERROR* Debug is', + 'wrong ELF class', + 'Could not enable PowerButton event', + 'probe of LNXPWRBN:00 failed with error -22', +] + common_errors + +qemux86_common = [ + 'Fast TSC calibration', + '_OSC failed (AE_NOT_FOUND); disabling ASPM', + 'Open ACPI failed (/var/run/acpid.socket) (No such file or directory)', + 'Failed to load module "vesa"', + 'Failed to load module "modesetting"', + 'Failed to load module "glx"', + 'wrong ELF class', +] + common_errors + +ignore_errors = { + 'default' : common_errors, + 'qemux86' : [ + 'Failed to access perfctr msr (MSR c1 is 0)', + "fail to add MMCONFIG information, can't access extended PCI configuration space under this bridge.", + ] + qemux86_common, + 'qemux86-64' : qemux86_common, + 'qemumips' : [ + 'Failed to load module "glx"', + ] + common_errors, + 'qemuppc' : [ + 'PCI 0000:00 Cannot reserve Legacy IO [io 0x0000-0x0fff]', + 'mode "640x480" test failed', + 'Failed to load module "glx"', + ] + common_errors, + 'qemuarm' : [ + 'mmci-pl18x: probe of fpga:05 failed with error -22', + 'mmci-pl18x: probe of fpga:0b failed with error -22', + 'Failed to load module "glx"' + ] + common_errors, + 'emenlow' : x86_common, + 'crownbay' : x86_common, + 'genericx86' : x86_common, + 'genericx86-64' : x86_common, +} + +log_locations = ["/var/log/","/var/log/dmesg", "/tmp/dmesg_output.log"] + +class ParseLogsTest(oeRuntimeTest): + + @classmethod + def setUpClass(self): + self.errors = errors + self.ignore_errors = ignore_errors + self.log_locations = log_locations + self.msg = "" + + def getMachine(self): + (status, output) = self.target.run("uname -n") + return output + + #get some information on the CPU of the machine to display at the beginning of the output. This info might be useful in some cases. + def getHardwareInfo(self): + hwi = "" + (status, cpu_name) = self.target.run("cat /proc/cpuinfo | grep \"model name\" | head -n1 | awk 'BEGIN{FS=\":\"}{print $2}'") + (status, cpu_physical_cores) = self.target.run("cat /proc/cpuinfo | grep \"cpu cores\" | head -n1 | awk {'print $4'}") + (status, cpu_logical_cores) = self.target.run("cat /proc/cpuinfo | grep \"processor\" | wc -l") + (status, cpu_arch) = self.target.run("uname -m") + hwi += "Machine information: \n" + hwi += "*******************************\n" + hwi += "Machine name: "+self.getMachine()+"\n" + hwi += "CPU: "+str(cpu_name)+"\n" + hwi += "Arch: "+str(cpu_arch)+"\n" + hwi += "Physical cores: "+str(cpu_physical_cores)+"\n" + hwi += "Logical cores: "+str(cpu_logical_cores)+"\n" + hwi += "*******************************\n" + return hwi + + #go through the log locations provided and if it's a folder create a list with all the .log files in it, if it's a file just add + #it to that list + def getLogList(self, log_locations): + logs = [] + for location in log_locations: + (status, output) = self.target.run("test -f "+str(location)) + if (status == 0): + logs.append(str(location)) + else: + (status, output) = self.target.run("test -d "+str(location)) + if (status == 0): + (status, output) = self.target.run("find "+str(location)+"/*.log -maxdepth 1 -type f") + output = output.splitlines() + for logfile in output: + logs.append(os.path.join(location,str(logfile))) + return logs + + #build the grep command to be used with filters and exclusions + def build_grepcmd(self, errors, ignore_errors, log): + grepcmd = "grep " + grepcmd +="-Ei \"" + for error in errors: + grepcmd += error+"|" + grepcmd = grepcmd[:-1] + grepcmd += "\" "+str(log)+" | grep -Eiv \'" + try: + errorlist = ignore_errors[self.getMachine()] + except KeyError: + self.msg += "No ignore list found for this machine, using default\n" + errorlist = ignore_errors['default'] + for ignore_error in errorlist: + ignore_error = ignore_error.replace("(", "\(") + ignore_error = ignore_error.replace(")", "\)") + ignore_error = ignore_error.replace("'", ".") + ignore_error = ignore_error.replace("?", "\?") + ignore_error = ignore_error.replace("[", "\[") + ignore_error = ignore_error.replace("]", "\]") + ignore_error = ignore_error.replace("*", "\*") + grepcmd += ignore_error+"|" + grepcmd = grepcmd[:-1] + grepcmd += "\'" + return grepcmd + + #grep only the errors so that their context could be collected. Default context is 10 lines before and after the error itself + def parse_logs(self, errors, ignore_errors, logs, lines_before = 10, lines_after = 10): + results = {} + rez = [] + for log in logs: + thegrep = self.build_grepcmd(errors, ignore_errors, log) + try: + (status, result) = self.target.run(thegrep) + except: + pass + if result: + results[log] = {} + rez = result.splitlines() + for xrez in rez: + command = "grep \"\\"+str(xrez)+"\" -B "+str(lines_before)+" -A "+str(lines_after)+" "+str(log) + try: + (status, yrez) = self.target.run(command) + except: + pass + results[log][xrez]=yrez + return results + + #get the output of dmesg and write it in a file. This file is added to log_locations. + def write_dmesg(self): + (status, dmesg) = self.target.run("dmesg") + (status, dmesg2) = self.target.run("echo \""+str(dmesg)+"\" > /tmp/dmesg_output.log") + + @skipUnlessPassed('test_ssh') + def test_parselogs(self): + self.write_dmesg() + log_list = self.getLogList(self.log_locations) + result = self.parse_logs(self.errors, self.ignore_errors, log_list) + print self.getHardwareInfo() + errcount = 0 + for log in result: + self.msg += "Log: "+log+"\n" + self.msg += "-----------------------\n" + for error in result[log]: + errcount += 1 + self.msg += "Central error: "+str(error)+"\n" + self.msg += "***********************\n" + self.msg += result[str(log)][str(error)]+"\n" + self.msg += "***********************\n" + self.msg += "%s errors found in logs." % errcount + self.assertEqual(errcount, 0, msg=self.msg) diff --git a/meta/lib/oeqa/runtime/perl.py b/meta/lib/oeqa/runtime/perl.py new file mode 100644 index 0000000000..65da028d4b --- /dev/null +++ b/meta/lib/oeqa/runtime/perl.py @@ -0,0 +1,29 @@ +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.") + + @testcase(208) + 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..0387b9a03e --- /dev/null +++ b/meta/lib/oeqa/runtime/python.py @@ -0,0 +1,34 @@ +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.") + + @testcase(965) + 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..b17e8b46a8 --- /dev/null +++ b/meta/lib/oeqa/runtime/rpm.py @@ -0,0 +1,53 @@ +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)) + + @testcase(191) + @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") + + @testcase(192) + @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) + + @testcase(194) + @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..43a024ab9a --- /dev/null +++ b/meta/lib/oeqa/runtime/scanelf.py @@ -0,0 +1,28 @@ +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' + + @testcase(966) + @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])) + + @testcase(967) + @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..48e87d2d0b --- /dev/null +++ b/meta/lib/oeqa/runtime/scp.py @@ -0,0 +1,22 @@ +import os +from oeqa.oetest import oeRuntimeTest, skipModule +from oeqa.utils.decorators import skipUnlessPassed, testcase + +def setUpModule(): + if not (oeRuntimeTest.hasPackage("dropbear") or oeRuntimeTest.hasPackage("openssh-sshd")): + skipModule("No ssh package in image") + +class ScpTest(oeRuntimeTest): + + @testcase(220) + @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..7c7f402e5d --- /dev/null +++ b/meta/lib/oeqa/runtime/skeletoninit.py @@ -0,0 +1,29 @@ +# 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, skipModule +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) + + @testcase(284) + @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..3b49314df7 --- /dev/null +++ b/meta/lib/oeqa/runtime/smart.py @@ -0,0 +1,121 @@ +import unittest +import re +from oeqa.oetest import oeRuntimeTest, skipModule +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): + + @testcase(716) + @skipUnlessPassed('test_ssh') + def test_smart_help(self): + self.smart('--help') + + def test_smart_version(self): + self.smart('--version') + + @testcase(721) + def test_smart_info(self): + self.smart('info python-smartpm') + + @testcase(421) + def test_smart_query(self): + self.smart('query python-smartpm') + + @testcase(720) + def test_smart_search(self): + self.smart('search python-smartpm') + + @testcase(722) + 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) + + @testcase(719) + 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') + + @testcase(717) + 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') + + @testcase(728) + @skipUnlessPassed('test_smart_install') + def test_smart_install_dependency(self): + self.smart('remove -y psplash') + self.smart('install -y psplash-default') + + @testcase(723) + @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*') + + @testcase(725) + @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)) + + @testcase(729) + @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..0e76d5d512 --- /dev/null +++ b/meta/lib/oeqa/runtime/ssh.py @@ -0,0 +1,19 @@ +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): + + @testcase(224) + @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..7fa018e97f --- /dev/null +++ b/meta/lib/oeqa/runtime/syslog.py @@ -0,0 +1,48 @@ +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)) + + @testcase(201) + @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') + + @testcase(202) + @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..1451698bb3 --- /dev/null +++ b/meta/lib/oeqa/runtime/systemd.py @@ -0,0 +1,88 @@ +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') + + @testcase(551) + @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) + + @testcase(550) + @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') + + @testcase(695) + @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) + + @testcase(696) + @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..f31deff306 --- /dev/null +++ b/meta/lib/oeqa/runtime/vnc.py @@ -0,0 +1,20 @@ +from oeqa.oetest import oeRuntimeTest, skipModuleUnless +from oeqa.utils.decorators import * +import re + +def setUpModule(): + skipModuleUnless(oeRuntimeTest.hasPackage('x11vnc'), "No x11vnc package in image") + +class VNCTest(oeRuntimeTest): + + @testcase(213) + @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..ce5e214035 --- /dev/null +++ b/meta/lib/oeqa/runtime/x32lib.py @@ -0,0 +1,18 @@ +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): + + @testcase(281) + @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..a07031e5c8 --- /dev/null +++ b/meta/lib/oeqa/runtime/xorg.py @@ -0,0 +1,17 @@ +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]) + + diff --git a/meta/lib/oeqa/sdk/__init__.py b/meta/lib/oeqa/sdk/__init__.py new file mode 100644 index 0000000000..4cf3fa76b6 --- /dev/null +++ b/meta/lib/oeqa/sdk/__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/sdk/buildcvs.py b/meta/lib/oeqa/sdk/buildcvs.py new file mode 100644 index 0000000000..c7146fa4af --- /dev/null +++ b/meta/lib/oeqa/sdk/buildcvs.py @@ -0,0 +1,25 @@ +from oeqa.oetest import oeSDKTest, skipModule +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import SDKBuildProject + +class BuildCvsTest(oeSDKTest): + + @classmethod + def setUpClass(self): + self.project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/cvs/", oeSDKTest.tc.sdkenv, oeSDKTest.tc.d, + "http://ftp.gnu.org/non-gnu/cvs/source/feature/1.12.13/cvs-1.12.13.tar.bz2") + self.project.download_archive() + + 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/sdk/buildiptables.py b/meta/lib/oeqa/sdk/buildiptables.py new file mode 100644 index 0000000000..062e5316e7 --- /dev/null +++ b/meta/lib/oeqa/sdk/buildiptables.py @@ -0,0 +1,26 @@ +from oeqa.oetest import oeSDKTest +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import SDKBuildProject + + +class BuildIptablesTest(oeSDKTest): + + @classmethod + def setUpClass(self): + self.project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/iptables/", oeSDKTest.tc.sdkenv, oeSDKTest.tc.d, + "http://netfilter.org/projects/iptables/files/iptables-1.4.13.tar.bz2") + self.project.download_archive() + + 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/sdk/buildsudoku.py b/meta/lib/oeqa/sdk/buildsudoku.py new file mode 100644 index 0000000000..dea77c6599 --- /dev/null +++ b/meta/lib/oeqa/sdk/buildsudoku.py @@ -0,0 +1,26 @@ +from oeqa.oetest import oeSDKTest, skipModule +from oeqa.utils.decorators import * +from oeqa.utils.targetbuild import SDKBuildProject + +def setUpModule(): + if not oeSDKTest.hasPackage("gtk\+"): + skipModule("Image doesn't have gtk+ in manifest") + +class SudokuTest(oeSDKTest): + + @classmethod + def setUpClass(self): + self.project = SDKBuildProject(oeSDKTest.tc.sdktestdir + "/sudoku/", oeSDKTest.tc.sdkenv, oeSDKTest.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() + + 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/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/_toaster.py b/meta/lib/oeqa/selftest/_toaster.py new file mode 100644 index 0000000000..1cf28a0144 --- /dev/null +++ b/meta/lib/oeqa/selftest/_toaster.py @@ -0,0 +1,445 @@ +import unittest +import os +import sys +import shlex, subprocess +import urllib, commands, time, getpass, re, json, shlex + +import oeqa.utils.ftools as ftools +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../', 'bitbake/lib/toaster'))) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "toastermain.settings") + +import toastermain.settings +from django.db.models import Q +from orm.models import * +from oeqa.utils.decorators import testcase + +class ToasterSetup(oeSelfTest): + + def recipe_parse(self, file_path, var): + for line in open(file_path,'r'): + if line.find(var) > -1: + val = line.split(" = ")[1].replace("\"", "").strip() + return val + + def fix_file_path(self, file_path): + if ":" in file_path: + file_path=file_path.split(":")[2] + return file_path + +class Toaster_DB_Tests(ToasterSetup): + + # Check if build name is unique - tc_id=795 + @testcase(795) + def test_Build_Unique_Name(self): + all_builds = Build.objects.all().count() + distinct_builds = Build.objects.values('id').distinct().count() + self.assertEqual(distinct_builds, all_builds, msg = 'Build name is not unique') + + # Check if build coocker log path is unique - tc_id=819 + @testcase(819) + def test_Build_Unique_Cooker_Log_Path(self): + distinct_path = Build.objects.values('cooker_log_path').distinct().count() + total_builds = Build.objects.values('id').count() + self.assertEqual(distinct_path, total_builds, msg = 'Build coocker log path is not unique') + + # Check if the number of errors matches the number of orm_logmessage.level entries with value 2 - tc_id=820 + @testcase(820) + def test_Build_Errors_No(self): + builds = Build.objects.values('id', 'errors_no') + cnt_err = [] + for build in builds: + log_mess_err_no = LogMessage.objects.filter(build = build['id'], level = 2).count() + if (build['errors_no'] != log_mess_err_no): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + + # Check if the number of warnings matches the number of orm_logmessage.level entries with value 1 - tc=821 + @testcase(821) + def test_Build_Warnings_No(self): + builds = Build.objects.values('id', 'warnings_no') + cnt_err = [] + for build in builds: + log_mess_warn_no = LogMessage.objects.filter(build = build['id'], level = 1).count() + if (build['warnings_no'] != log_mess_warn_no): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + + # Check if the build succeeded then the errors_no is 0 - tc_id=822 + @testcase(822) + def test_Build_Suceeded_Errors_No(self): + builds = Build.objects.filter(outcome = 0).values('id', 'errors_no') + cnt_err = [] + for build in builds: + if (build['errors_no'] != 0): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + + # Check if task order is unique for one build - tc=824 + @testcase(824) + def test_Task_Unique_Order(self): + builds = Build.objects.values('id') + cnt_err = [] + for build in builds: + total_task_order = Task.objects.filter(build = build['id']).values('order').count() + distinct_task_order = Task.objects.filter(build = build['id']).values('order').distinct().count() + if (total_task_order != distinct_task_order): + cnt_err.append(build['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for build id: %s' % cnt_err) + + # Check task order sequence for one build - tc=825 + @testcase(825) + def test_Task_Order_Sequence(self): + builds = builds = Build.objects.values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter(Q(build = build['id']), ~Q(order = None), ~Q(task_name__contains = '_setscene')).values('id', 'order').order_by("order") + cnt_tasks = 0 + for task in tasks: + cnt_tasks += 1 + if (task['order'] != cnt_tasks): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if disk_io matches the difference between EndTimeIO and StartTimeIO in build stats - tc=828 + ### this needs to be updated ### + #def test_Task_Disk_IO_TC828(self): + + # Check if outcome = 2 (SSTATE) then sstate_result must be 3 (RESTORED) - tc=832 + @testcase(832) + def test_Task_If_Outcome_2_Sstate_Result_Must_Be_3(self): + tasks = Task.objects.filter(outcome = 2).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (row['sstate_result'] != 3): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if outcome = 1 (COVERED) or 3 (EXISTING) then sstate_result must be 0 (SSTATE_NA) - tc=833 + @testcase(833) + def test_Task_If_Outcome_1_3_Sstate_Result_Must_Be_0(self): + tasks = Task.objects.filter(outcome__in = (1, 3)).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (task['sstate_result'] != 0): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if outcome is 0 (SUCCESS) or 4 (FAILED) then sstate_result must be 0 (NA), 1 (MISS) or 2 (FAILED) - tc=834 + @testcase(834) + def test_Task_If_Outcome_0_4_Sstate_Result_Must_Be_0_1_2(self): + tasks = Task.objects.filter(outcome__in = (0, 4)).values('id', 'sstate_result') + cnt_err = [] + for task in tasks: + if (task['sstate_result'] not in [0, 1, 2]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if task_executed = TRUE (1), script_type must be 0 (CODING_NA), 2 (CODING_PYTHON), 3 (CODING_SHELL) - tc=891 + @testcase(891) + def test_Task_If_Task_Executed_True_Script_Type_0_2_3(self): + tasks = Task.objects.filter(task_executed = 1).values('id', 'script_type') + cnt_err = [] + for task in tasks: + if (task['script_type'] not in [0, 2, 3]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if task_executed = TRUE (1), outcome must be 0 (SUCCESS) or 4 (FAILED) - tc=836 + @testcase(836) + def test_Task_If_Task_Executed_True_Outcome_0_4(self): + tasks = Task.objects.filter(task_executed = 1).values('id', 'outcome') + cnt_err = [] + for task in tasks: + if (task['outcome'] not in [0, 4]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if task_executed = FALSE (0), script_type must be 0 - tc=890 + @testcase(890) + def test_Task_If_Task_Executed_False_Script_Type_0(self): + tasks = Task.objects.filter(task_executed = 0).values('id', 'script_type') + cnt_err = [] + for task in tasks: + if (task['script_type'] != 0): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Check if task_executed = FALSE (0) and build outcome = SUCCEEDED (0), task outcome must be 1 (COVERED), 2 (CACHED), 3 (PREBUILT), 5 (EMPTY) - tc=837 + @testcase(837) + def test_Task_If_Task_Executed_False_Outcome_1_2_3_5(self): + builds = Build.objects.filter(outcome = 0).values('id') + cnt_err = [] + for build in builds: + tasks = Task.objects.filter(build = build['id'], task_executed = 0).values('id', 'outcome') + for task in tasks: + if (task['outcome'] not in [1, 2, 3, 5]): + cnt_err.append(task['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task id: %s' % cnt_err) + + # Key verification - tc=888 + @testcase(888) + def test_Target_Installed_Package(self): + rows = Target_Installed_Package.objects.values('id', 'target_id', 'package_id') + cnt_err = [] + for row in rows: + target = Target.objects.filter(id = row['target_id']).values('id') + package = Package.objects.filter(id = row['package_id']).values('id') + if (not target or not package): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for target installed package id: %s' % cnt_err) + + # Key verification - tc=889 + @testcase(889) + def test_Task_Dependency(self): + rows = Task_Dependency.objects.values('id', 'task_id', 'depends_on_id') + cnt_err = [] + for row in rows: + task_id = Task.objects.filter(id = row['task_id']).values('id') + depends_on_id = Task.objects.filter(id = row['depends_on_id']).values('id') + if (not task_id or not depends_on_id): + cnt_err.append(row['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for task dependency id: %s' % cnt_err) + + # Check if build target file_name is populated only if is_image=true AND orm_build.outcome=0 then if the file exists and its size matches the file_size value + ### Need to add the tc in the test run + @testcase(1037) + def test_Target_File_Name_Populated(self): + builds = Build.objects.filter(outcome = 0).values('id') + for build in builds: + targets = Target.objects.filter(build_id = build['id'], is_image = 1).values('id') + for target in targets: + target_files = Target_Image_File.objects.filter(target_id = target['id']).values('id', 'file_name', 'file_size') + cnt_err = [] + for file_info in target_files: + target_id = file_info['id'] + target_file_name = file_info['file_name'] + target_file_size = file_info['file_size'] + if (not target_file_name or not target_file_size): + cnt_err.append(target_id) + else: + if (not os.path.exists(target_file_name)): + cnt_err.append(target_id) + else: + if (os.path.getsize(target_file_name) != target_file_size): + cnt_err.append(target_id) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for target image file id: %s' % cnt_err) + + # Key verification - tc=884 + @testcase(884) + def test_Package_Dependency(self): + cnt_err = [] + deps = Package_Dependency.objects.values('id', 'package_id', 'depends_on_id') + for dep in deps: + if (dep['package_id'] == dep['depends_on_id']): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package dependency id: %s' % cnt_err) + + # Check if recipe name does not start with a number (0-9) - tc=838 + @testcase(838) + def test_Recipe_Name(self): + recipes = Recipe.objects.values('id', 'name') + cnt_err = [] + for recipe in recipes: + if (recipe['name'][0].isdigit() is True): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + + # Check if recipe section matches the content of the SECTION variable (if set) in file_path - tc=839 + @testcase(839) + def test_Recipe_DB_Section_Match_Recipe_File_Section(self): + recipes = Recipe.objects.values('id', 'section', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_section = self.recipe_parse(file_path, "SECTION = ") + db_section = recipe['section'] + if file_section: + if (db_section != file_section): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + + # Check if recipe license matches the content of the LICENSE variable (if set) in file_path - tc=840 + @testcase(840) + def test_Recipe_DB_License_Match_Recipe_File_License(self): + recipes = Recipe.objects.values('id', 'license', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_license = self.recipe_parse(file_path, "LICENSE = ") + db_license = recipe['license'] + if file_license: + if (db_license != file_license): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + + # Check if recipe homepage matches the content of the HOMEPAGE variable (if set) in file_path - tc=841 + @testcase(841) + def test_Recipe_DB_Homepage_Match_Recipe_File_Homepage(self): + recipes = Recipe.objects.values('id', 'homepage', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_homepage = self.recipe_parse(file_path, "HOMEPAGE = ") + db_homepage = recipe['homepage'] + if file_homepage: + if (db_homepage != file_homepage): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + + # Check if recipe bugtracker matches the content of the BUGTRACKER variable (if set) in file_path - tc=842 + @testcase(842) + def test_Recipe_DB_Bugtracker_Match_Recipe_File_Bugtracker(self): + recipes = Recipe.objects.values('id', 'bugtracker', 'file_path') + cnt_err = [] + for recipe in recipes: + file_path = self.fix_file_path(recipe['file_path']) + file_exists = os.path.isfile(file_path) + if (not file_path or (file_exists is False)): + cnt_err.append(recipe['id']) + else: + file_bugtracker = self.recipe_parse(file_path, "BUGTRACKER = ") + db_bugtracker = recipe['bugtracker'] + if file_bugtracker: + if (db_bugtracker != file_bugtracker): + cnt_err.append(recipe['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe id: %s' % cnt_err) + + # Recipe key verification, recipe name does not depends on a recipe having the same name - tc=883 + @testcase(883) + def test_Recipe_Dependency(self): + deps = Recipe_Dependency.objects.values('id', 'recipe_id', 'depends_on_id') + cnt_err = [] + for dep in deps: + if (not dep['recipe_id'] or not dep['depends_on_id']): + cnt_err.append(dep['id']) + else: + name = Recipe.objects.filter(id = dep['recipe_id']).values('name') + dep_name = Recipe.objects.filter(id = dep['depends_on_id']).values('name') + if (name == dep_name): + cnt_err.append(dep['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for recipe dependency id: %s' % cnt_err) + + # Check if package name does not start with a number (0-9) - tc=846 + @testcase(846) + def test_Package_Name_For_Number(self): + packages = Package.objects.filter(~Q(size = -1)).values('id', 'name') + cnt_err = [] + for package in packages: + if (package['name'][0].isdigit() is True): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check if package version starts with a number (0-9) - tc=847 + @testcase(847) + def test_Package_Version_Starts_With_Number(self): + packages = Package.objects.filter(~Q(size = -1)).values('id', 'version') + cnt_err = [] + for package in packages: + if (package['version'][0].isdigit() is False): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check if package revision starts with 'r' - tc=848 + @testcase(848) + def test_Package_Revision_Starts_With_r(self): + packages = Package.objects.filter(~Q(size = -1)).values('id', 'revision') + cnt_err = [] + for package in packages: + if (package['revision'][0].startswith("r") is False): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check the validity of the package build_id + ### TC must be added in test run + @testcase(1038) + def test_Package_Build_Id(self): + packages = Package.objects.filter(~Q(size = -1)).values('id', 'build_id') + cnt_err = [] + for package in packages: + build_id = Build.objects.filter(id = package['build_id']).values('id') + if (not build_id): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check the validity of package recipe_id + ### TC must be added in test run + @testcase(1039) + def test_Package_Recipe_Id(self): + packages = Package.objects.filter(~Q(size = -1)).values('id', 'recipe_id') + cnt_err = [] + for package in packages: + recipe_id = Recipe.objects.filter(id = package['recipe_id']).values('id') + if (not recipe_id): + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check if package installed_size field is not null + ### TC must be aded in test run + @testcase(1040) + def test_Package_Installed_Size_Not_NULL(self): + packages = Package.objects.filter(installed_size__isnull = True).values('id') + cnt_err = [] + for package in packages: + cnt_err.append(package['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for package id: %s' % cnt_err) + + # Check if all layers requests return exit code is 200 - tc=843 + @testcase(843) + def test_Layers_Requests_Exit_Code(self): + layers = Layer.objects.values('id', 'layer_index_url') + cnt_err = [] + for layer in layers: + resp = urllib.urlopen(layer['layer_index_url']) + if (resp.getcode() != 200): + cnt_err.append(layer['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) + + # Check if the output of bitbake-layers show_layers matches the info from database - tc=895 + @testcase(895) + def test_Layers_Show_Layers(self): + layers = Layer.objects.values('id', 'name', 'local_path') + cmd = commands.getoutput('bitbake-layers show_layers') + cnt_err = [] + for layer in layers: + if (layer['name'] or layer['local_path']) not in cmd: + cnt_err.append(layer['id']) + self.assertEqual(len(cnt_err), 0, msg = 'Errors for layer id: %s' % cnt_err) + + # Check if django server starts regardless of the timezone set on the machine - tc=905 + @testcase(905) + def test_Start_Django_Timezone(self): + current_path = os.getcwd() + zonefilelist = [] + ZONEINFOPATH = '/usr/share/zoneinfo/' + os.chdir("../bitbake/lib/toaster/") + cnt_err = 0 + for filename in os.listdir(ZONEINFOPATH): + if os.path.isfile(os.path.join(ZONEINFOPATH, filename)): + zonefilelist.append(filename) + for k in range(len(zonefilelist)): + if k <= 5: + files = zonefilelist[k] + os.system("export TZ="+str(files)+"; python manage.py runserver > /dev/null 2>&1 &") + time.sleep(3) + pid = subprocess.check_output("ps aux | grep '[/u]sr/bin/python manage.py runserver' | awk '{print $2}'", shell = True) + if pid: + os.system("kill -9 "+str(pid)) + else: + cnt_err.append(zonefilelist[k]) + self.assertEqual(cnt_err, 0, msg = 'Errors django server does not start with timezone: %s' % cnt_err) + os.chdir(current_path) diff --git a/meta/lib/oeqa/selftest/base.py b/meta/lib/oeqa/selftest/base.py new file mode 100644 index 0000000000..80b9b4b312 --- /dev/null +++ b/meta/lib/oeqa/selftest/base.py @@ -0,0 +1,131 @@ +# 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 +from oeqa.utils.decorators import LogResults + +@LogResults +class oeSelfTest(unittest.TestCase): + + log = logging.getLogger("selftest.base") + longMessage = True + + def __init__(self, methodName="runTest"): + self.builddir = os.environ.get("BUILDDIR") + self.localconf_path = os.path.join(self.builddir, "conf/local.conf") + self.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..1ead8e8671 --- /dev/null +++ b/meta/lib/oeqa/selftest/bblayers.py @@ -0,0 +1,43 @@ +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 +from oeqa.utils.decorators import testcase + +class BitbakeLayers(oeSelfTest): + + @testcase(756) + def test_bitbakelayers_showcrossdepends(self): + result = runCmd('bitbake-layers show-cross-depends') + self.assertTrue('aspell' in result.output) + + @testcase(83) + def test_bitbakelayers_showlayers(self): + result = runCmd('bitbake-layers show_layers') + self.assertTrue('meta-selftest' in result.output) + + @testcase(93) + def test_bitbakelayers_showappends(self): + result = runCmd('bitbake-layers show_appends') + self.assertTrue('xcursor-transparent-theme_0.1.1.bbappend' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') + + @testcase(90) + def test_bitbakelayers_showoverlayed(self): + result = runCmd('bitbake-layers show_overlayed') + self.assertTrue('aspell' in result.output, msg='xcursor-transparent-theme_0.1.1.bbappend file was not recognised') + + @testcase(95) + def test_bitbakelayers_flatten(self): + self.assertFalse(os.path.isdir(os.path.join(self.builddir, 'test'))) + result = runCmd('bitbake-layers flatten test') + bb_file = os.path.join(self.builddir, 'test/recipes-graphics/xcursor-transparent-theme/xcursor-transparent-theme_0.1.1.bb') + 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..68f97bd8e3 --- /dev/null +++ b/meta/lib/oeqa/selftest/bbtests.py @@ -0,0 +1,178 @@ +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 +from oeqa.utils.decorators import testcase + +class BitbakeTests(oeSelfTest): + + @testcase(789) + def test_run_bitbake_from_dir_1(self): + os.chdir(os.path.join(self.builddir, 'conf')) + bitbake('-e') + + @testcase(790) + def test_run_bitbake_from_dir_2(self): + my_env = os.environ.copy() + my_env['BBPATH'] = my_env['BUILDDIR'] + os.chdir(os.path.dirname(os.environ['BUILDDIR'])) + bitbake('-e', env=my_env) + + @testcase(806) + 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) + + @testcase(103) + 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) + + @testcase(105) + def test_bitbake_invalid_recipe(self): + result = bitbake('-b asdf', ignore_status=True) + self.assertTrue("ERROR: Unable to find any recipe file matching 'asdf'" in result.output) + + @testcase(107) + def test_bitbake_invalid_target(self): + result = bitbake('asdf', ignore_status=True) + self.assertTrue("ERROR: Nothing PROVIDES 'asdf'" in result.output) + + @testcase(106) + def test_warnings_errors(self): + result = bitbake('-b asdf', ignore_status=True) + find_warnings = re.search("Summary: There w.{2,3}? [1-9][0-9]* WARNING messages* shown", result.output) + 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) + + @testcase(108) + 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) + + @testcase(163) + 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) + + @testcase(167) + 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) + + @testcase(899) + 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") + + @testcase(168) + 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('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) + + @testcase(171) + 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') + + @testcase(1028) + def test_environment(self): + self.append_config("TEST_ENV=\"localconf\"") + result = runCmd('bitbake -e | grep TEST_ENV=') + self.assertTrue('localconf' in result.output) + self.remove_config("TEST_ENV=\"localconf\"") + + @testcase(1029) + def test_dry_run(self): + result = runCmd('bitbake -n m4-native') + self.assertEqual(0, result.status) + + @testcase(1030) + def test_just_parse(self): + result = runCmd('bitbake -p') + self.assertEqual(0, result.status) + + @testcase(1031) + def test_version(self): + result = runCmd('bitbake -s | grep wget') + find = re.search("wget *:([0-9a-zA-Z\.\-]+)", result.output) + self.assertTrue(find) + + @testcase(1032) + def test_prefile(self): + preconf = os.path.join(self.builddir, 'conf/prefile.conf') + self.track_for_cleanup(preconf) + ftools.write_file(preconf ,"TEST_PREFILE=\"prefile\"") + result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') + self.assertTrue('prefile' in result.output) + self.append_config("TEST_PREFILE=\"localconf\"") + result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=') + self.assertTrue('localconf' in result.output) + self.remove_config("TEST_PREFILE=\"localconf\"") + + @testcase(1033) + def test_postfile(self): + postconf = os.path.join(self.builddir, 'conf/postfile.conf') + self.track_for_cleanup(postconf) + ftools.write_file(postconf , "TEST_POSTFILE=\"postfile\"") + self.append_config("TEST_POSTFILE=\"localconf\"") + result = runCmd('bitbake -R conf/postfile.conf -e | grep TEST_POSTFILE=') + self.assertTrue('postfile' in result.output) + self.remove_config("TEST_POSTFILE=\"localconf\"") + + @testcase(1034) + def test_checkuri(self): + result = runCmd('bitbake -c checkuri m4') + self.assertEqual(0, result.status) + + @testcase(1035) + def test_continue(self): + self.write_recipeinc('man',"\ndo_fail_task () {\nexit 1 \n}\n\naddtask do_fail_task before do_fetch\n" ) + runCmd('bitbake -c cleanall man xcursor-transparent-theme') + result = runCmd('bitbake man xcursor-transparent-theme -k', ignore_status=True) + errorpos = result.output.find('ERROR: Function failed: do_fail_task') + manver = re.search("NOTE: recipe xcursor-transparent-theme-(.*?): task do_unpack: Started", result.output) + continuepos = result.output.find('NOTE: recipe xcursor-transparent-theme-%s: task do_unpack: Started' % manver.group(1)) + self.assertLess(errorpos,continuepos) 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..a250cae0e1 --- /dev/null +++ b/meta/lib/oeqa/selftest/buildoptions.py @@ -0,0 +1,120 @@ +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 +from oeqa.utils.decorators import testcase + +class ImageOptionsTests(oeSelfTest): + + @testcase(761) + 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") + + @testcase(925) + 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))) + + @testcase(286) + 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): + + @testcase(277) + def test_stoptask_behavior(self): + self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},100000G,100K"') + 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},100000G,100K"') + 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},100000G,100K"') + res = bitbake("m4") + self.assertTrue('WARNING: The free space' in res.output) + +class SanityOptionsTest(oeSelfTest): + + @testcase(927) + 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, msg=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"') + bitbake("xcursor-transparent-theme -ccleansstate") + res = bitbake("xcursor-transparent-theme") + 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, msg=res.output) + + @testcase(278) + 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): + + @testcase(293) + def test_buildhistory_basic(self): + self.run_buildhistory_operation('xcursor-transparent-theme') + self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR'))) + + @testcase(294) + 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..31cd50809c --- /dev/null +++ b/meta/lib/oeqa/selftest/oescripts.py @@ -0,0 +1,54 @@ +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 +from oeqa.utils.decorators import testcase + +class TestScripts(oeSelfTest): + + @testcase(300) + 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): + + @testcase(295) + 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..fb6d68d3bf --- /dev/null +++ b/meta/lib/oeqa/selftest/prservice.py @@ -0,0 +1,121 @@ +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 +from oeqa.utils.decorators import testcase + +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) + + @testcase(930) + def test_import_export_replace_db(self): + self.run_test_pr_export_import('m4') + + @testcase(931) + def test_import_export_override_db(self): + self.run_test_pr_export_import('m4', replace_current_db=False) + + @testcase(932) + def test_pr_service_rpm_arch_dep(self): + self.run_test_pr_service('m4', 'rpm', 'do_package') + + @testcase(934) + def test_pr_service_deb_arch_dep(self): + self.run_test_pr_service('m4', 'deb', 'do_package') + + @testcase(933) + def test_pr_service_ipk_arch_dep(self): + self.run_test_pr_service('m4', 'ipk', 'do_package') + + @testcase(935) + def test_pr_service_rpm_arch_indep(self): + self.run_test_pr_service('xcursor-transparent-theme', 'rpm', 'do_package') + + @testcase(937) + def test_pr_service_deb_arch_indep(self): + self.run_test_pr_service('xcursor-transparent-theme', 'deb', 'do_package') + + @testcase(936) + 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..d578ddd489 --- /dev/null +++ b/meta/lib/oeqa/selftest/sstatetests.py @@ -0,0 +1,204 @@ +import datetime +import unittest +import os +import re +import shutil + +import oeqa.utils.ftools as ftools +from oeqa.selftest.base import oeSelfTest +from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer +from oeqa.selftest.sstate import SStateBase +from oeqa.utils.decorators import testcase + +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))) + + @testcase(975) + def test_sstate_creation_distro_specific_pass(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_sstate_creation(['binutils-cross-'+ targetarch, 'binutils-native'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True) + + @testcase(975) + def test_sstate_creation_distro_specific_fail(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_sstate_creation(['binutils-cross-'+ targetarch, 'binutils-native'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True, should_pass=False) + + @testcase(976) + def test_sstate_creation_distro_nonspecific_pass(self): + self.run_test_sstate_creation(['glibc-initial'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) + + @testcase(976) + def test_sstate_creation_distro_nonspecific_fail(self): + self.run_test_sstate_creation(['glibc-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))) + + @testcase(977) + def test_cleansstate_task_distro_specific_nonspecific(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_cleansstate_task(['binutils-cross-' + targetarch, 'binutils-native', 'glibc-initial'], distro_specific=True, distro_nonspecific=True, temp_sstate_location=True) + + @testcase(977) + def test_cleansstate_task_distro_nonspecific(self): + self.run_test_cleansstate_task(['glibc-initial'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True) + + @testcase(977) + def test_cleansstate_task_distro_specific(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_cleansstate_task(['binutils-cross-'+ targetarch, 'binutils-native', 'glibc-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))) + + @testcase(175) + def test_rebuild_distro_specific_sstate_cross_native_targets(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + targetarch, 'binutils-native'], temp_sstate_location=True) + + @testcase(175) + def test_rebuild_distro_specific_sstate_cross_target(self): + targetarch = get_bb_var('TUNE_ARCH') + self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + targetarch], temp_sstate_location=True) + + @testcase(175) + 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))) + + @testcase(973) + 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']) + + @testcase(978) + 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']) + + @testcase(979) + 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']) + + @testcase(974) + 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..cc582dd1ad --- /dev/null +++ b/meta/lib/oeqa/targetcontrol.py @@ -0,0 +1,199 @@ +# 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 + + supported_image_fstypes = [] + + 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 + + @classmethod + def get_extra_files(self): + return None + + @classmethod + def match_image_fstype(self, d, image_fstypes=None): + if not image_fstypes: + image_fstypes = d.getVar('IMAGE_FSTYPES', True).split(' ') + possible_image_fstypes = [fstype for fstype in self.supported_image_fstypes if fstype in image_fstypes] + if possible_image_fstypes: + return possible_image_fstypes[0] + else: + return None + + def get_image_fstype(self, d): + image_fstype = self.match_image_fstype(d) + if image_fstype: + return image_fstype + else: + bb.fatal("IMAGE_FSTYPES should contain a Target Controller supported image fstype: %s " % ', '.join(map(str, self.supported_image_fstypes))) + + def restart(self, params=None): + self.stop() + self.start(params) + + 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): + + supported_image_fstypes = ['ext3'] + + def __init__(self, d): + + super(QemuTarget, self).__init__(d) + + self.image_fstype = self.get_image_fstype(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) + '.' + self.image_fstype) + self.rootfs = os.path.join(self.testdir, d.getVar("IMAGE_LINK_NAME", True) + '-testimage.' + self.image_fstype) + + 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: + self.stop() + 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 diff --git a/meta/lib/oeqa/utils/__init__.py b/meta/lib/oeqa/utils/__init__.py new file mode 100644 index 0000000000..2260046026 --- /dev/null +++ b/meta/lib/oeqa/utils/__init__.py @@ -0,0 +1,15 @@ +# Enable other layers to have modules in the same named directory +from pkgutil import extend_path +__path__ = extend_path(__path__, __name__) + + +# Borrowed from CalledProcessError + +class CommandError(Exception): + def __init__(self, retcode, cmd, output = None): + self.retcode = retcode + self.cmd = cmd + self.output = output + def __str__(self): + return "Command '%s' returned non-zero exit status %d with output: %s" % (self.cmd, self.retcode, self.output) + diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py new file mode 100644 index 0000000000..802bc2f208 --- /dev/null +++ b/meta/lib/oeqa/utils/commands.py @@ -0,0 +1,154 @@ +# Copyright (c) 2013-2014 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 +from oeqa.utils import CommandError +from oeqa.utils import ftools + +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, assert_error=True, **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: + if assert_error: + raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, result.output)) + else: + raise CommandError(result.status, command, result.output) + + return result + + +def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **options): + + if postconfig: + postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf') + ftools.write_file(postconfig_file, postconfig) + extra_args = "-R %s" % postconfig_file + else: + extra_args = "" + + if isinstance(command, basestring): + cmd = "bitbake " + extra_args + " " + command + else: + cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]] + + try: + return runCmd(cmd, ignore_status, timeout, **options) + finally: + if postconfig: + os.remove(postconfig_file) + + +def get_bb_env(target=None, postconfig=None): + if target: + return bitbake("-e %s" % target, postconfig=postconfig).output + else: + return bitbake("-e", postconfig=postconfig).output + +def get_bb_var(var, target=None, postconfig=None): + val = None + bbenv = get_bb_env(target, postconfig=postconfig) + 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..40bd4ef2db --- /dev/null +++ b/meta/lib/oeqa/utils/decorators.py @@ -0,0 +1,158 @@ +# 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. + +import os +import logging +import sys +import unittest + +#get the "result" object from one of the upper frames provided that one of these upper frames is a unittest.case frame +class getResults(object): + def __init__(self): + #dynamically determine the unittest.case frame and use it to get the name of the test method + upperf = sys._current_frames().values()[0] + while (upperf.f_globals['__name__'] != 'unittest.case'): + upperf = upperf.f_back + + def handleList(items): + ret = [] + # items is a list of tuples, (test, failure) or (_ErrorHandler(), Exception()) + for i in items: + s = i[0].id() + #Handle the _ErrorHolder objects from skipModule failures + if "setUpModule (" in s: + ret.append(s.replace("setUpModule (", "").replace(")","")) + else: + ret.append(s) + return ret + self.faillist = handleList(upperf.f_locals['result'].failures) + self.errorlist = handleList(upperf.f_locals['result'].errors) + self.skiplist = handleList(upperf.f_locals['result'].skipped) + + def getFailList(self): + return self.faillist + + def getErrorList(self): + return self.errorlist + + def getSkipList(self): + return self.skiplist + +class skipIfFailure(object): + + def __init__(self,testcase): + self.testcase = testcase + + def __call__(self,f): + def wrapped_f(*args): + res = getResults() + if self.testcase in (res.getFailList() or res.getErrorList()): + 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): + res = getResults() + if self.testcase in res.getSkipList(): + 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): + res = getResults() + if self.testcase in res.getSkipList() or \ + self.testcase in res.getFailList() or \ + self.testcase in res.getErrorList(): + raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase) + return f(*args) + wrapped_f.__name__ = f.__name__ + return wrapped_f + +class testcase(object): + + def __init__(self, test_case): + self.test_case = test_case + + def __call__(self, func): + def wrapped_f(*args): + return func(*args) + wrapped_f.test_case = self.test_case + return wrapped_f + +class NoParsingFilter(logging.Filter): + def filter(self, record): + return record.levelno == 100 + +def LogResults(original_class): + orig_method = original_class.run + + #rewrite the run method of unittest.TestCase to add testcase logging + def run(self, result, *args, **kws): + orig_method(self, result, *args, **kws) + passed = True + testMethod = getattr(self, self._testMethodName) + + #if test case is decorated then use it's number, else use it's name + try: + test_case = testMethod.test_case + except AttributeError: + test_case = self._testMethodName + + #create custom logging level for filtering. + custom_log_level = 100 + logging.addLevelName(custom_log_level, 'RESULTS') + caller = os.path.basename(sys.argv[0]) + + def results(self, message, *args, **kws): + if self.isEnabledFor(custom_log_level): + self.log(custom_log_level, message, *args, **kws) + logging.Logger.results = results + + logging.basicConfig(filename=os.path.join(os.getcwd(),'results-'+caller+'.log'), + filemode='w', + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%H:%M:%S', + level=custom_log_level) + for handler in logging.root.handlers: + handler.addFilter(NoParsingFilter()) + local_log = logging.getLogger(caller) + + #check status of tests and record it + for (name, msg) in result.errors: + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": ERROR") + local_log.results("Testcase "+str(test_case)+":\n"+msg) + passed = False + for (name, msg) in result.failures: + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": FAILED") + local_log.results("Testcase "+str(test_case)+":\n"+msg) + passed = False + for (name, msg) in result.skipped: + if self._testMethodName == str(name).split(' ')[0]: + local_log.results("Testcase "+str(test_case)+": SKIPPED") + passed = False + if passed: + local_log.results("Testcase "+str(test_case)+": PASSED") + + original_class.run = run + return original_class 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..76518d8ef9 --- /dev/null +++ b/meta/lib/oeqa/utils/httpserver.py @@ -0,0 +1,35 @@ +import SimpleHTTPServer +import multiprocessing +import os + +class HTTPServer(SimpleHTTPServer.BaseHTTPServer.HTTPServer): + + def server_start(self, root_dir): + import signal + signal.signal(signal.SIGTERM, signal.SIG_DFL) + 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/logparser.py b/meta/lib/oeqa/utils/logparser.py new file mode 100644 index 0000000000..87b50354cd --- /dev/null +++ b/meta/lib/oeqa/utils/logparser.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +import sys +import os +import re +import ftools + + +# A parser that can be used to identify weather a line is a test result or a section statement. +class Lparser(object): + + def __init__(self, test_0_pass_regex, test_0_fail_regex, section_0_begin_regex=None, section_0_end_regex=None, **kwargs): + # Initialize the arguments dictionary + if kwargs: + self.args = kwargs + else: + self.args = {} + + # Add the default args to the dictionary + self.args['test_0_pass_regex'] = test_0_pass_regex + self.args['test_0_fail_regex'] = test_0_fail_regex + if section_0_begin_regex: + self.args['section_0_begin_regex'] = section_0_begin_regex + if section_0_end_regex: + self.args['section_0_end_regex'] = section_0_end_regex + + self.test_possible_status = ['pass', 'fail', 'error'] + self.section_possible_status = ['begin', 'end'] + + self.initialized = False + + + # Initialize the parser with the current configuration + def init(self): + + # extra arguments can be added by the user to define new test and section categories. They must follow a pre-defined pattern: ___regex + self.test_argument_pattern = "^test_(.+?)_(%s)_regex" % '|'.join(map(str, self.test_possible_status)) + self.section_argument_pattern = "^section_(.+?)_(%s)_regex" % '|'.join(map(str, self.section_possible_status)) + + # Initialize the test and section regex dictionaries + self.test_regex = {} + self.section_regex ={} + + for arg, value in self.args.items(): + if not value: + raise Exception('The value of provided argument %s is %s. Should have a valid value.' % (key, value)) + is_test = re.search(self.test_argument_pattern, arg) + is_section = re.search(self.section_argument_pattern, arg) + if is_test: + if not is_test.group(1) in self.test_regex: + self.test_regex[is_test.group(1)] = {} + self.test_regex[is_test.group(1)][is_test.group(2)] = re.compile(value) + elif is_section: + if not is_section.group(1) in self.section_regex: + self.section_regex[is_section.group(1)] = {} + self.section_regex[is_section.group(1)][is_section.group(2)] = re.compile(value) + else: + # TODO: Make these call a traceback instead of a simple exception.. + raise Exception("The provided argument name does not correspond to any valid type. Please give one of the following types:\nfor tests: %s\nfor sections: %s" % (self.test_argument_pattern, self.section_argument_pattern)) + + self.initialized = True + + # Parse a line and return a tuple containing the type of result (test/section) and its category, status and name + def parse_line(self, line): + if not self.initialized: + raise Exception("The parser is not initialized..") + + for test_category, test_status_list in self.test_regex.items(): + for test_status, status_regex in test_status_list.items(): + test_name = status_regex.search(line) + if test_name: + return ['test', test_category, test_status, test_name.group(1)] + + for section_category, section_status_list in self.section_regex.items(): + for section_status, status_regex in section_status_list.items(): + section_name = status_regex.search(line) + if section_name: + return ['section', section_category, section_status, section_name.group(1)] + return None + + +class Result(object): + + def __init__(self): + self.result_dict = {} + + def store(self, section, test, status): + if not section in self.result_dict: + self.result_dict[section] = [] + + self.result_dict[section].append((test, status)) + + # sort tests by the test name(the first element of the tuple), for each section. This can be helpful when using git to diff for changes by making sure they are always in the same order. + def sort_tests(self): + for package in self.result_dict: + sorted_results = sorted(self.result_dict[package], key=lambda tup: tup[0]) + self.result_dict[package] = sorted_results + + # Log the results as files. The file name is the section name and the contents are the tests in that section. + def log_as_files(self, target_dir, test_status): + status_regex = re.compile('|'.join(map(str, test_status))) + if not type(test_status) == type([]): + raise Exception("test_status should be a list. Got " + str(test_status) + " instead.") + if not os.path.exists(target_dir): + raise Exception("Target directory does not exist: %s" % target_dir) + + for section, test_results in self.result_dict.items(): + prefix = '' + for x in test_status: + prefix +=x+'.' + if (section != ''): + prefix += section + section_file = os.path.join(target_dir, prefix) + # purge the file contents if it exists + open(section_file, 'w').close() + for test_result in test_results: + (test_name, status) = test_result + # we log only the tests with status in the test_status list + match_status = status_regex.search(status) + if match_status: + ftools.append_file(section_file, status + ": " + test_name) + + # Not yet implemented! + def log_to_lava(self): + pass 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..1c81795a87 --- /dev/null +++ b/meta/lib/oeqa/utils/sshcontrol.py @@ -0,0 +1,138 @@ +# 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 + self.logfile = None + + def log(self, msg): + if self.logfile: + with open(self.logfile, "a") as f: + f.write("%s" % msg) + + def run(self, command, timeout=None, logfile=None): + self.logfile = logfile + 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 + self.log(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 + lastline = "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime) + self.log(lastline) + output += lastline + else: + output = self.process.communicate()[0] + self.log(output.rstrip()) + + 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, logfile=self.logfile) + + 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..eeb08ba716 --- /dev/null +++ b/meta/lib/oeqa/utils/targetbuild.py @@ -0,0 +1,132 @@ +# 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 bb.utils +import subprocess +from abc import ABCMeta, abstractmethod + +class BuildProject(): + + __metaclass__ = ABCMeta + + def __init__(self, d, uri, foldername=None, tmpdir="/tmp/"): + self.d = d + self.uri = uri + self.archive = os.path.basename(uri) + self.localarchive = os.path.join(tmpdir,self.archive) + self.fname = re.sub(r'.tar.bz2|tar.gz$', '', self.archive) + if foldername: + self.fname = foldername + + # Download self.archive to self.localarchive + 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) + + # This method should provide a way to run a command in the desired environment. + @abstractmethod + def _run(self, cmd): + pass + + # The timeout parameter of target.run is set to 0 to make the ssh command + # run with no timeout. + def run_configure(self, configure_args=''): + return self._run('cd %s; ./configure %s' % (self.targetdir, configure_args)) + + def run_make(self, make_args=''): + return self._run('cd %s; make %s' % (self.targetdir, make_args)) + + def run_install(self, install_args=''): + return self._run('cd %s; make install %s' % (self.targetdir, install_args)) + + def clean(self): + self._run('rm -rf %s' % self.targetdir) + subprocess.call('rm -f %s' % self.localarchive, shell=True) + pass + +class TargetBuildProject(BuildProject): + + def __init__(self, target, d, uri, foldername=None): + self.target = target + self.targetdir = "~/" + BuildProject.__init__(self, d, uri, foldername, tmpdir="/tmp") + + def download_archive(self): + + self._download_archive() + + (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(self, cmd): + return self.target.run(cmd, 0)[0] + + +class SDKBuildProject(BuildProject): + + def __init__(self, testpath, sdkenv, d, uri, foldername=None): + self.sdkenv = sdkenv + self.testdir = testpath + self.targetdir = testpath + bb.utils.mkdirhier(testpath) + self.datetime = d.getVar('DATETIME', True) + self.testlogdir = d.getVar("TEST_LOG_DIR", True) + bb.utils.mkdirhier(self.testlogdir) + self.logfile = os.path.join(self.testlogdir, "sdk_target_log.%s" % self.datetime) + BuildProject.__init__(self, d, uri, foldername, tmpdir=testpath) + + def download_archive(self): + + self._download_archive() + + cmd = 'tar xf %s%s -C %s' % (self.targetdir, self.archive, self.targetdir) + subprocess.check_call(cmd, shell=True) + + #Change targetdir to project folder + self.targetdir = self.targetdir + self.fname + + def run_configure(self, configure_args=''): + return super(SDKBuildProject, self).run_configure(configure_args=(configure_args or '$CONFIGURE_FLAGS')) + + def run_install(self, install_args=''): + return super(SDKBuildProject, self).run_install(install_args=(install_args or "DESTDIR=%s/../install" % self.targetdir)) + + def log(self, msg): + if self.logfile: + with open(self.logfile, "a") as f: + f.write("%s\n" % msg) + + def _run(self, cmd): + self.log("Running source %s; " % self.sdkenv + cmd) + return subprocess.call("source %s; " % self.sdkenv + cmd, shell=True) + -- cgit v1.2.3-54-g00ecf