diff options
author | Tudor Florea <tudor.florea@enea.com> | 2014-10-16 03:05:19 +0200 |
---|---|---|
committer | Tudor Florea <tudor.florea@enea.com> | 2014-10-16 03:05:19 +0200 |
commit | c527fd1f14c27855a37f2e8ac5346ce8d940ced2 (patch) | |
tree | bb002c1fdf011c41dbd2f0927bed23ecb5f83c97 /meta/lib/oeqa/utils | |
download | poky-c527fd1f14c27855a37f2e8ac5346ce8d940ced2.tar.gz |
initial commit for Enea Linux 4.0-140929daisy-140929
Migrated from the internal git server on the daisy-enea-point-release branch
Signed-off-by: Tudor Florea <tudor.florea@enea.com>
Diffstat (limited to 'meta/lib/oeqa/utils')
-rw-r--r-- | meta/lib/oeqa/utils/__init__.py | 3 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/commands.py | 137 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/decorators.py | 50 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/ftools.py | 27 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/httpserver.py | 33 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 237 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 127 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/targetbuild.py | 68 |
8 files changed, 682 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..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) | ||