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