diff options
Diffstat (limited to 'meta/lib/oeqa/utils/commands.py')
-rw-r--r-- | meta/lib/oeqa/utils/commands.py | 154 |
1 files changed, 154 insertions, 0 deletions
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 | |||
11 | import os | ||
12 | import sys | ||
13 | import signal | ||
14 | import subprocess | ||
15 | import threading | ||
16 | import logging | ||
17 | from oeqa.utils import CommandError | ||
18 | from oeqa.utils import ftools | ||
19 | |||
20 | class 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 | |||
86 | class Result(object): | ||
87 | pass | ||
88 | |||
89 | |||
90 | def 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 | |||
110 | def 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 | |||
131 | def 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 | |||
137 | def 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 | |||
147 | def 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 | ||