summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/utils
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib/oeqa/utils')
-rw-r--r--meta/lib/oeqa/utils/__init__.py15
-rw-r--r--meta/lib/oeqa/utils/commands.py154
-rw-r--r--meta/lib/oeqa/utils/decorators.py158
-rw-r--r--meta/lib/oeqa/utils/ftools.py27
-rw-r--r--meta/lib/oeqa/utils/httpserver.py35
-rw-r--r--meta/lib/oeqa/utils/logparser.py125
-rw-r--r--meta/lib/oeqa/utils/qemurunner.py237
-rw-r--r--meta/lib/oeqa/utils/sshcontrol.py138
-rw-r--r--meta/lib/oeqa/utils/targetbuild.py132
9 files changed, 1021 insertions, 0 deletions
diff --git a/meta/lib/oeqa/utils/__init__.py b/meta/lib/oeqa/utils/__init__.py
new file mode 100644
index 0000000000..2260046026
--- /dev/null
+++ b/meta/lib/oeqa/utils/__init__.py
@@ -0,0 +1,15 @@
1# Enable other layers to have modules in the same named directory
2from pkgutil import extend_path
3__path__ = extend_path(__path__, __name__)
4
5
6# Borrowed from CalledProcessError
7
8class CommandError(Exception):
9 def __init__(self, retcode, cmd, output = None):
10 self.retcode = retcode
11 self.cmd = cmd
12 self.output = output
13 def __str__(self):
14 return "Command '%s' returned non-zero exit status %d with output: %s" % (self.cmd, self.retcode, self.output)
15
diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py
new file mode 100644
index 0000000000..802bc2f208
--- /dev/null
+++ b/meta/lib/oeqa/utils/commands.py
@@ -0,0 +1,154 @@
1# Copyright (c) 2013-2014 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
11import os
12import sys
13import signal
14import subprocess
15import threading
16import logging
17from oeqa.utils import CommandError
18from oeqa.utils import ftools
19
20class Command(object):
21 def __init__(self, command, bg=False, timeout=None, data=None, **options):
22
23 self.defaultopts = {
24 "stdout": subprocess.PIPE,
25 "stderr": subprocess.STDOUT,
26 "stdin": None,
27 "shell": False,
28 "bufsize": -1,
29 }
30
31 self.cmd = command
32 self.bg = bg
33 self.timeout = timeout
34 self.data = data
35
36 self.options = dict(self.defaultopts)
37 if isinstance(self.cmd, basestring):
38 self.options["shell"] = True
39 if self.data:
40 self.options['stdin'] = subprocess.PIPE
41 self.options.update(options)
42
43 self.status = None
44 self.output = None
45 self.error = None
46 self.thread = None
47
48 self.log = logging.getLogger("utils.commands")
49
50 def run(self):
51 self.process = subprocess.Popen(self.cmd, **self.options)
52
53 def commThread():
54 self.output, self.error = self.process.communicate(self.data)
55
56 self.thread = threading.Thread(target=commThread)
57 self.thread.start()
58
59 self.log.debug("Running command '%s'" % self.cmd)
60
61 if not self.bg:
62 self.thread.join(self.timeout)
63 self.stop()
64
65 def stop(self):
66 if self.thread.isAlive():
67 self.process.terminate()
68 # let's give it more time to terminate gracefully before killing it
69 self.thread.join(5)
70 if self.thread.isAlive():
71 self.process.kill()
72 self.thread.join()
73
74 self.output = self.output.rstrip()
75 self.status = self.process.poll()
76
77 self.log.debug("Command '%s' returned %d as exit code." % (self.cmd, self.status))
78 # logging the complete output is insane
79 # bitbake -e output is really big
80 # and makes the log file useless
81 if self.status:
82 lout = "\n".join(self.output.splitlines()[-20:])
83 self.log.debug("Last 20 lines:\n%s" % lout)
84
85
86class Result(object):
87 pass
88
89
90def runCmd(command, ignore_status=False, timeout=None, assert_error=True, **options):
91 result = Result()
92
93 cmd = Command(command, timeout=timeout, **options)
94 cmd.run()
95
96 result.command = command
97 result.status = cmd.status
98 result.output = cmd.output
99 result.pid = cmd.process.pid
100
101 if result.status and not ignore_status:
102 if assert_error:
103 raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, result.status, result.output))
104 else:
105 raise CommandError(result.status, command, result.output)
106
107 return result
108
109
110def bitbake(command, ignore_status=False, timeout=None, postconfig=None, **options):
111
112 if postconfig:
113 postconfig_file = os.path.join(os.environ.get('BUILDDIR'), 'oeqa-post.conf')
114 ftools.write_file(postconfig_file, postconfig)
115 extra_args = "-R %s" % postconfig_file
116 else:
117 extra_args = ""
118
119 if isinstance(command, basestring):
120 cmd = "bitbake " + extra_args + " " + command
121 else:
122 cmd = [ "bitbake" ] + [a for a in (command + extra_args.split(" ")) if a not in [""]]
123
124 try:
125 return runCmd(cmd, ignore_status, timeout, **options)
126 finally:
127 if postconfig:
128 os.remove(postconfig_file)
129
130
131def get_bb_env(target=None, postconfig=None):
132 if target:
133 return bitbake("-e %s" % target, postconfig=postconfig).output
134 else:
135 return bitbake("-e", postconfig=postconfig).output
136
137def get_bb_var(var, target=None, postconfig=None):
138 val = None
139 bbenv = get_bb_env(target, postconfig=postconfig)
140 for line in bbenv.splitlines():
141 if line.startswith(var + "="):
142 val = line.split('=')[1]
143 val = val.replace('\"','')
144 break
145 return val
146
147def get_test_layer():
148 layers = get_bb_var("BBLAYERS").split()
149 testlayer = None
150 for l in layers:
151 if "/meta-selftest" in l and os.path.isdir(l):
152 testlayer = l
153 break
154 return testlayer
diff --git a/meta/lib/oeqa/utils/decorators.py b/meta/lib/oeqa/utils/decorators.py
new file mode 100644
index 0000000000..40bd4ef2db
--- /dev/null
+++ b/meta/lib/oeqa/utils/decorators.py
@@ -0,0 +1,158 @@
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
9import os
10import logging
11import sys
12import unittest
13
14#get the "result" object from one of the upper frames provided that one of these upper frames is a unittest.case frame
15class getResults(object):
16 def __init__(self):
17 #dynamically determine the unittest.case frame and use it to get the name of the test method
18 upperf = sys._current_frames().values()[0]
19 while (upperf.f_globals['__name__'] != 'unittest.case'):
20 upperf = upperf.f_back
21
22 def handleList(items):
23 ret = []
24 # items is a list of tuples, (test, failure) or (_ErrorHandler(), Exception())
25 for i in items:
26 s = i[0].id()
27 #Handle the _ErrorHolder objects from skipModule failures
28 if "setUpModule (" in s:
29 ret.append(s.replace("setUpModule (", "").replace(")",""))
30 else:
31 ret.append(s)
32 return ret
33 self.faillist = handleList(upperf.f_locals['result'].failures)
34 self.errorlist = handleList(upperf.f_locals['result'].errors)
35 self.skiplist = handleList(upperf.f_locals['result'].skipped)
36
37 def getFailList(self):
38 return self.faillist
39
40 def getErrorList(self):
41 return self.errorlist
42
43 def getSkipList(self):
44 return self.skiplist
45
46class skipIfFailure(object):
47
48 def __init__(self,testcase):
49 self.testcase = testcase
50
51 def __call__(self,f):
52 def wrapped_f(*args):
53 res = getResults()
54 if self.testcase in (res.getFailList() or res.getErrorList()):
55 raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase)
56 return f(*args)
57 wrapped_f.__name__ = f.__name__
58 return wrapped_f
59
60class skipIfSkipped(object):
61
62 def __init__(self,testcase):
63 self.testcase = testcase
64
65 def __call__(self,f):
66 def wrapped_f(*args):
67 res = getResults()
68 if self.testcase in res.getSkipList():
69 raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase)
70 return f(*args)
71 wrapped_f.__name__ = f.__name__
72 return wrapped_f
73
74class skipUnlessPassed(object):
75
76 def __init__(self,testcase):
77 self.testcase = testcase
78
79 def __call__(self,f):
80 def wrapped_f(*args):
81 res = getResults()
82 if self.testcase in res.getSkipList() or \
83 self.testcase in res.getFailList() or \
84 self.testcase in res.getErrorList():
85 raise unittest.SkipTest("Testcase dependency not met: %s" % self.testcase)
86 return f(*args)
87 wrapped_f.__name__ = f.__name__
88 return wrapped_f
89
90class testcase(object):
91
92 def __init__(self, test_case):
93 self.test_case = test_case
94
95 def __call__(self, func):
96 def wrapped_f(*args):
97 return func(*args)
98 wrapped_f.test_case = self.test_case
99 return wrapped_f
100
101class NoParsingFilter(logging.Filter):
102 def filter(self, record):
103 return record.levelno == 100
104
105def LogResults(original_class):
106 orig_method = original_class.run
107
108 #rewrite the run method of unittest.TestCase to add testcase logging
109 def run(self, result, *args, **kws):
110 orig_method(self, result, *args, **kws)
111 passed = True
112 testMethod = getattr(self, self._testMethodName)
113
114 #if test case is decorated then use it's number, else use it's name
115 try:
116 test_case = testMethod.test_case
117 except AttributeError:
118 test_case = self._testMethodName
119
120 #create custom logging level for filtering.
121 custom_log_level = 100
122 logging.addLevelName(custom_log_level, 'RESULTS')
123 caller = os.path.basename(sys.argv[0])
124
125 def results(self, message, *args, **kws):
126 if self.isEnabledFor(custom_log_level):
127 self.log(custom_log_level, message, *args, **kws)
128 logging.Logger.results = results
129
130 logging.basicConfig(filename=os.path.join(os.getcwd(),'results-'+caller+'.log'),
131 filemode='w',
132 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
133 datefmt='%H:%M:%S',
134 level=custom_log_level)
135 for handler in logging.root.handlers:
136 handler.addFilter(NoParsingFilter())
137 local_log = logging.getLogger(caller)
138
139 #check status of tests and record it
140 for (name, msg) in result.errors:
141 if self._testMethodName == str(name).split(' ')[0]:
142 local_log.results("Testcase "+str(test_case)+": ERROR")
143 local_log.results("Testcase "+str(test_case)+":\n"+msg)
144 passed = False
145 for (name, msg) in result.failures:
146 if self._testMethodName == str(name).split(' ')[0]:
147 local_log.results("Testcase "+str(test_case)+": FAILED")
148 local_log.results("Testcase "+str(test_case)+":\n"+msg)
149 passed = False
150 for (name, msg) in result.skipped:
151 if self._testMethodName == str(name).split(' ')[0]:
152 local_log.results("Testcase "+str(test_case)+": SKIPPED")
153 passed = False
154 if passed:
155 local_log.results("Testcase "+str(test_case)+": PASSED")
156
157 original_class.run = run
158 return original_class
diff --git a/meta/lib/oeqa/utils/ftools.py b/meta/lib/oeqa/utils/ftools.py
new file mode 100644
index 0000000000..64ebe3d217
--- /dev/null
+++ b/meta/lib/oeqa/utils/ftools.py
@@ -0,0 +1,27 @@
1import os
2import re
3
4def write_file(path, data):
5 wdata = data.rstrip() + "\n"
6 with open(path, "w") as f:
7 f.write(wdata)
8
9def append_file(path, data):
10 wdata = data.rstrip() + "\n"
11 with open(path, "a") as f:
12 f.write(wdata)
13
14def read_file(path):
15 data = None
16 with open(path) as f:
17 data = f.read()
18 return data
19
20def 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..76518d8ef9
--- /dev/null
+++ b/meta/lib/oeqa/utils/httpserver.py
@@ -0,0 +1,35 @@
1import SimpleHTTPServer
2import multiprocessing
3import os
4
5class HTTPServer(SimpleHTTPServer.BaseHTTPServer.HTTPServer):
6
7 def server_start(self, root_dir):
8 import signal
9 signal.signal(signal.SIGTERM, signal.SIG_DFL)
10 os.chdir(root_dir)
11 self.serve_forever()
12
13class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
14
15 def log_message(self, format_str, *args):
16 pass
17
18class HTTPService(object):
19
20 def __init__(self, root_dir, host=''):
21 self.root_dir = root_dir
22 self.host = host
23 self.port = 0
24
25 def start(self):
26 self.server = HTTPServer((self.host, self.port), HTTPRequestHandler)
27 if self.port == 0:
28 self.port = self.server.server_port
29 self.process = multiprocessing.Process(target=self.server.server_start, args=[self.root_dir])
30 self.process.start()
31
32 def stop(self):
33 self.server.server_close()
34 self.process.terminate()
35 self.process.join()
diff --git a/meta/lib/oeqa/utils/logparser.py b/meta/lib/oeqa/utils/logparser.py
new file mode 100644
index 0000000000..87b50354cd
--- /dev/null
+++ b/meta/lib/oeqa/utils/logparser.py
@@ -0,0 +1,125 @@
1#!/usr/bin/env python
2
3import sys
4import os
5import re
6import ftools
7
8
9# A parser that can be used to identify weather a line is a test result or a section statement.
10class Lparser(object):
11
12 def __init__(self, test_0_pass_regex, test_0_fail_regex, section_0_begin_regex=None, section_0_end_regex=None, **kwargs):
13 # Initialize the arguments dictionary
14 if kwargs:
15 self.args = kwargs
16 else:
17 self.args = {}
18
19 # Add the default args to the dictionary
20 self.args['test_0_pass_regex'] = test_0_pass_regex
21 self.args['test_0_fail_regex'] = test_0_fail_regex
22 if section_0_begin_regex:
23 self.args['section_0_begin_regex'] = section_0_begin_regex
24 if section_0_end_regex:
25 self.args['section_0_end_regex'] = section_0_end_regex
26
27 self.test_possible_status = ['pass', 'fail', 'error']
28 self.section_possible_status = ['begin', 'end']
29
30 self.initialized = False
31
32
33 # Initialize the parser with the current configuration
34 def init(self):
35
36 # extra arguments can be added by the user to define new test and section categories. They must follow a pre-defined pattern: <type>_<category_name>_<status>_regex
37 self.test_argument_pattern = "^test_(.+?)_(%s)_regex" % '|'.join(map(str, self.test_possible_status))
38 self.section_argument_pattern = "^section_(.+?)_(%s)_regex" % '|'.join(map(str, self.section_possible_status))
39
40 # Initialize the test and section regex dictionaries
41 self.test_regex = {}
42 self.section_regex ={}
43
44 for arg, value in self.args.items():
45 if not value:
46 raise Exception('The value of provided argument %s is %s. Should have a valid value.' % (key, value))
47 is_test = re.search(self.test_argument_pattern, arg)
48 is_section = re.search(self.section_argument_pattern, arg)
49 if is_test:
50 if not is_test.group(1) in self.test_regex:
51 self.test_regex[is_test.group(1)] = {}
52 self.test_regex[is_test.group(1)][is_test.group(2)] = re.compile(value)
53 elif is_section:
54 if not is_section.group(1) in self.section_regex:
55 self.section_regex[is_section.group(1)] = {}
56 self.section_regex[is_section.group(1)][is_section.group(2)] = re.compile(value)
57 else:
58 # TODO: Make these call a traceback instead of a simple exception..
59 raise Exception("The provided argument name does not correspond to any valid type. Please give one of the following types:\nfor tests: %s\nfor sections: %s" % (self.test_argument_pattern, self.section_argument_pattern))
60
61 self.initialized = True
62
63 # Parse a line and return a tuple containing the type of result (test/section) and its category, status and name
64 def parse_line(self, line):
65 if not self.initialized:
66 raise Exception("The parser is not initialized..")
67
68 for test_category, test_status_list in self.test_regex.items():
69 for test_status, status_regex in test_status_list.items():
70 test_name = status_regex.search(line)
71 if test_name:
72 return ['test', test_category, test_status, test_name.group(1)]
73
74 for section_category, section_status_list in self.section_regex.items():
75 for section_status, status_regex in section_status_list.items():
76 section_name = status_regex.search(line)
77 if section_name:
78 return ['section', section_category, section_status, section_name.group(1)]
79 return None
80
81
82class Result(object):
83
84 def __init__(self):
85 self.result_dict = {}
86
87 def store(self, section, test, status):
88 if not section in self.result_dict:
89 self.result_dict[section] = []
90
91 self.result_dict[section].append((test, status))
92
93 # sort tests by the test name(the first element of the tuple), for each section. This can be helpful when using git to diff for changes by making sure they are always in the same order.
94 def sort_tests(self):
95 for package in self.result_dict:
96 sorted_results = sorted(self.result_dict[package], key=lambda tup: tup[0])
97 self.result_dict[package] = sorted_results
98
99 # Log the results as files. The file name is the section name and the contents are the tests in that section.
100 def log_as_files(self, target_dir, test_status):
101 status_regex = re.compile('|'.join(map(str, test_status)))
102 if not type(test_status) == type([]):
103 raise Exception("test_status should be a list. Got " + str(test_status) + " instead.")
104 if not os.path.exists(target_dir):
105 raise Exception("Target directory does not exist: %s" % target_dir)
106
107 for section, test_results in self.result_dict.items():
108 prefix = ''
109 for x in test_status:
110 prefix +=x+'.'
111 if (section != ''):
112 prefix += section
113 section_file = os.path.join(target_dir, prefix)
114 # purge the file contents if it exists
115 open(section_file, 'w').close()
116 for test_result in test_results:
117 (test_name, status) = test_result
118 # we log only the tests with status in the test_status list
119 match_status = status_regex.search(status)
120 if match_status:
121 ftools.append_file(section_file, status + ": " + test_name)
122
123 # Not yet implemented!
124 def log_to_lava(self):
125 pass
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py
new file mode 100644
index 0000000000..f1a7e24ab7
--- /dev/null
+++ b/meta/lib/oeqa/utils/qemurunner.py
@@ -0,0 +1,237 @@
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
8import subprocess
9import os
10import time
11import signal
12import re
13import socket
14import select
15import bb
16
17class 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..1c81795a87
--- /dev/null
+++ b/meta/lib/oeqa/utils/sshcontrol.py
@@ -0,0 +1,138 @@
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
9import subprocess
10import time
11import os
12import select
13
14
15class 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 self.logfile = None
33
34 def log(self, msg):
35 if self.logfile:
36 with open(self.logfile, "a") as f:
37 f.write("%s" % msg)
38
39 def run(self, command, timeout=None, logfile=None):
40 self.logfile = logfile
41 self.starttime = time.time()
42 output = ''
43 self.process = subprocess.Popen(command, **self.options)
44 if timeout:
45 endtime = self.starttime + timeout
46 eof = False
47 while time.time() < endtime and not eof:
48 if select.select([self.process.stdout], [], [], 5)[0] != []:
49 data = os.read(self.process.stdout.fileno(), 1024)
50 if not data:
51 self.process.stdout.close()
52 eof = True
53 else:
54 output += data
55 self.log(data)
56 endtime = time.time() + timeout
57
58
59 # process hasn't returned yet
60 if not eof:
61 self.process.terminate()
62 time.sleep(5)
63 try:
64 self.process.kill()
65 except OSError:
66 pass
67 lastline = "\nProcess killed - no output for %d seconds. Total running time: %d seconds." % (timeout, time.time() - self.starttime)
68 self.log(lastline)
69 output += lastline
70 else:
71 output = self.process.communicate()[0]
72 self.log(output.rstrip())
73
74 self.status = self.process.wait()
75 self.output = output.rstrip()
76 return (self.status, self.output)
77
78
79class SSHControl(object):
80 def __init__(self, ip, logfile=None, timeout=300, user='root', port=None):
81 self.ip = ip
82 self.defaulttimeout = timeout
83 self.ignore_status = True
84 self.logfile = logfile
85 self.user = user
86 self.ssh_options = [
87 '-o', 'UserKnownHostsFile=/dev/null',
88 '-o', 'StrictHostKeyChecking=no',
89 '-o', 'LogLevel=ERROR'
90 ]
91 self.ssh = ['ssh', '-l', self.user ] + self.ssh_options
92 self.scp = ['scp'] + self.ssh_options
93 if port:
94 self.ssh = self.ssh + [ '-p', port ]
95 self.scp = self.scp + [ '-P', port ]
96
97 def log(self, msg):
98 if self.logfile:
99 with open(self.logfile, "a") as f:
100 f.write("%s\n" % msg)
101
102 def _internal_run(self, command, timeout=None, ignore_status = True):
103 self.log("[Running]$ %s" % " ".join(command))
104
105 proc = SSHProcess()
106 status, output = proc.run(command, timeout, logfile=self.logfile)
107
108 self.log("[Command returned '%d' after %.2f seconds]" % (status, time.time() - proc.starttime))
109
110 if status and not ignore_status:
111 raise AssertionError("Command '%s' returned non-zero exit status %d:\n%s" % (command, status, output))
112
113 return (status, output)
114
115 def run(self, command, timeout=None):
116 """
117 command - ssh command to run
118 timeout=<val> - kill command if there is no output after <val> seconds
119 timeout=None - kill command if there is no output after a default value seconds
120 timeout=0 - no timeout, let command run until it returns
121 """
122
123 # We need to source /etc/profile for a proper PATH on the target
124 command = self.ssh + [self.ip, ' . /etc/profile; ' + command]
125
126 if timeout is None:
127 return self._internal_run(command, self.defaulttimeout, self.ignore_status)
128 if timeout == 0:
129 return self._internal_run(command, None, self.ignore_status)
130 return self._internal_run(command, timeout, self.ignore_status)
131
132 def copy_to(self, localpath, remotepath):
133 command = self.scp + [localpath, '%s@%s:%s' % (self.user, self.ip, remotepath)]
134 return self._internal_run(command, ignore_status=False)
135
136 def copy_from(self, remotepath, localpath):
137 command = self.scp + ['%s@%s:%s' % (self.user, self.ip, remotepath), localpath]
138 return self._internal_run(command, ignore_status=False)
diff --git a/meta/lib/oeqa/utils/targetbuild.py b/meta/lib/oeqa/utils/targetbuild.py
new file mode 100644
index 0000000000..eeb08ba716
--- /dev/null
+++ b/meta/lib/oeqa/utils/targetbuild.py
@@ -0,0 +1,132 @@
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
7import os
8import re
9import bb.utils
10import subprocess
11from abc import ABCMeta, abstractmethod
12
13class BuildProject():
14
15 __metaclass__ = ABCMeta
16
17 def __init__(self, d, uri, foldername=None, tmpdir="/tmp/"):
18 self.d = d
19 self.uri = uri
20 self.archive = os.path.basename(uri)
21 self.localarchive = os.path.join(tmpdir,self.archive)
22 self.fname = re.sub(r'.tar.bz2|tar.gz$', '', self.archive)
23 if foldername:
24 self.fname = foldername
25
26 # Download self.archive to self.localarchive
27 def _download_archive(self):
28
29 exportvars = ['HTTP_PROXY', 'http_proxy',
30 'HTTPS_PROXY', 'https_proxy',
31 'FTP_PROXY', 'ftp_proxy',
32 'FTPS_PROXY', 'ftps_proxy',
33 'NO_PROXY', 'no_proxy',
34 'ALL_PROXY', 'all_proxy',
35 'SOCKS5_USER', 'SOCKS5_PASSWD']
36
37 cmd = ''
38 for var in exportvars:
39 val = self.d.getVar(var, True)
40 if val:
41 cmd = 'export ' + var + '=\"%s\"; %s' % (val, cmd)
42
43 cmd = cmd + "wget -O %s %s" % (self.localarchive, self.uri)
44 subprocess.check_call(cmd, shell=True)
45
46 # This method should provide a way to run a command in the desired environment.
47 @abstractmethod
48 def _run(self, cmd):
49 pass
50
51 # The timeout parameter of target.run is set to 0 to make the ssh command
52 # run with no timeout.
53 def run_configure(self, configure_args=''):
54 return self._run('cd %s; ./configure %s' % (self.targetdir, configure_args))
55
56 def run_make(self, make_args=''):
57 return self._run('cd %s; make %s' % (self.targetdir, make_args))
58
59 def run_install(self, install_args=''):
60 return self._run('cd %s; make install %s' % (self.targetdir, install_args))
61
62 def clean(self):
63 self._run('rm -rf %s' % self.targetdir)
64 subprocess.call('rm -f %s' % self.localarchive, shell=True)
65 pass
66
67class TargetBuildProject(BuildProject):
68
69 def __init__(self, target, d, uri, foldername=None):
70 self.target = target
71 self.targetdir = "~/"
72 BuildProject.__init__(self, d, uri, foldername, tmpdir="/tmp")
73
74 def download_archive(self):
75
76 self._download_archive()
77
78 (status, output) = self.target.copy_to(self.localarchive, self.targetdir)
79 if status != 0:
80 raise Exception("Failed to copy archive to target, output: %s" % output)
81
82 (status, output) = self.target.run('tar xf %s%s -C %s' % (self.targetdir, self.archive, self.targetdir))
83 if status != 0:
84 raise Exception("Failed to extract archive, output: %s" % output)
85
86 #Change targetdir to project folder
87 self.targetdir = self.targetdir + self.fname
88
89 # The timeout parameter of target.run is set to 0 to make the ssh command
90 # run with no timeout.
91 def _run(self, cmd):
92 return self.target.run(cmd, 0)[0]
93
94
95class SDKBuildProject(BuildProject):
96
97 def __init__(self, testpath, sdkenv, d, uri, foldername=None):
98 self.sdkenv = sdkenv
99 self.testdir = testpath
100 self.targetdir = testpath
101 bb.utils.mkdirhier(testpath)
102 self.datetime = d.getVar('DATETIME', True)
103 self.testlogdir = d.getVar("TEST_LOG_DIR", True)
104 bb.utils.mkdirhier(self.testlogdir)
105 self.logfile = os.path.join(self.testlogdir, "sdk_target_log.%s" % self.datetime)
106 BuildProject.__init__(self, d, uri, foldername, tmpdir=testpath)
107
108 def download_archive(self):
109
110 self._download_archive()
111
112 cmd = 'tar xf %s%s -C %s' % (self.targetdir, self.archive, self.targetdir)
113 subprocess.check_call(cmd, shell=True)
114
115 #Change targetdir to project folder
116 self.targetdir = self.targetdir + self.fname
117
118 def run_configure(self, configure_args=''):
119 return super(SDKBuildProject, self).run_configure(configure_args=(configure_args or '$CONFIGURE_FLAGS'))
120
121 def run_install(self, install_args=''):
122 return super(SDKBuildProject, self).run_install(install_args=(install_args or "DESTDIR=%s/../install" % self.targetdir))
123
124 def log(self, msg):
125 if self.logfile:
126 with open(self.logfile, "a") as f:
127 f.write("%s\n" % msg)
128
129 def _run(self, cmd):
130 self.log("Running source %s; " % self.sdkenv + cmd)
131 return subprocess.call("source %s; " % self.sdkenv + cmd, shell=True)
132