diff options
Diffstat (limited to 'meta/lib/oeqa/utils')
-rw-r--r-- | meta/lib/oeqa/utils/__init__.py | 8 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/commands.py | 40 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/gitarchive.py | 6 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/metadata.py | 5 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/postactions.py | 70 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 40 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 6 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/subprocesstweak.py | 13 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/testexport.py | 10 |
9 files changed, 126 insertions, 72 deletions
diff --git a/meta/lib/oeqa/utils/__init__.py b/meta/lib/oeqa/utils/__init__.py index 53bdcbf266..e03f7e33bb 100644 --- a/meta/lib/oeqa/utils/__init__.py +++ b/meta/lib/oeqa/utils/__init__.py | |||
@@ -96,4 +96,10 @@ def get_json_result_dir(d): | |||
96 | custom_json_result_dir = d.getVar("OEQA_JSON_RESULT_DIR") | 96 | custom_json_result_dir = d.getVar("OEQA_JSON_RESULT_DIR") |
97 | if custom_json_result_dir: | 97 | if custom_json_result_dir: |
98 | json_result_dir = custom_json_result_dir | 98 | json_result_dir = custom_json_result_dir |
99 | return json_result_dir \ No newline at end of file | 99 | return json_result_dir |
100 | |||
101 | def get_artefact_dir(d): | ||
102 | custom_json_result_dir = d.getVar("OEQA_ARTEFACT_DIR") | ||
103 | if custom_json_result_dir: | ||
104 | return custom_json_result_dir | ||
105 | return os.path.join(d.getVar("LOG_DIR"), 'oeqa-artefacts') | ||
diff --git a/meta/lib/oeqa/utils/commands.py b/meta/lib/oeqa/utils/commands.py index 575e380017..b60a6e6c38 100644 --- a/meta/lib/oeqa/utils/commands.py +++ b/meta/lib/oeqa/utils/commands.py | |||
@@ -203,6 +203,8 @@ def runCmd(command, ignore_status=False, timeout=None, assert_error=True, sync=T | |||
203 | 203 | ||
204 | if result.status and not ignore_status: | 204 | if result.status and not ignore_status: |
205 | exc_output = result.output | 205 | exc_output = result.output |
206 | if result.error: | ||
207 | exc_output = exc_output + result.error | ||
206 | if limit_exc_output > 0: | 208 | if limit_exc_output > 0: |
207 | split = result.output.splitlines() | 209 | split = result.output.splitlines() |
208 | if len(split) > limit_exc_output: | 210 | if len(split) > limit_exc_output: |
@@ -283,7 +285,20 @@ def get_bb_vars(variables=None, target=None, postconfig=None): | |||
283 | return values | 285 | return values |
284 | 286 | ||
285 | def get_bb_var(var, target=None, postconfig=None): | 287 | def get_bb_var(var, target=None, postconfig=None): |
286 | return get_bb_vars([var], target, postconfig)[var] | 288 | if postconfig: |
289 | return bitbake("-e %s" % target or "", postconfig=postconfig).output | ||
290 | else: | ||
291 | # Fast-path for the non-postconfig case | ||
292 | cmd = ["bitbake-getvar", "--quiet", "--value", var] | ||
293 | if target: | ||
294 | cmd.extend(["--recipe", target]) | ||
295 | try: | ||
296 | return subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE).stdout.strip() | ||
297 | except subprocess.CalledProcessError as e: | ||
298 | # We need to return None not the empty string if the variable hasn't been set. | ||
299 | if e.returncode == 1: | ||
300 | return None | ||
301 | raise | ||
287 | 302 | ||
288 | def get_test_layer(bblayers=None): | 303 | def get_test_layer(bblayers=None): |
289 | if bblayers is None: | 304 | if bblayers is None: |
@@ -312,9 +327,26 @@ def create_temp_layer(templayerdir, templayername, priority=999, recipepathspec= | |||
312 | f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames)) | 327 | f.write('LAYERSERIES_COMPAT_%s = "%s"\n' % (templayername, corenames)) |
313 | 328 | ||
314 | @contextlib.contextmanager | 329 | @contextlib.contextmanager |
315 | def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, discard_writes=True): | 330 | def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, qemuparams=None, overrides={}, boot_patterns = {}, discard_writes=True): |
316 | """ | 331 | """ |
317 | launch_cmd means directly run the command, don't need set rootfs or env vars. | 332 | Starts a context manager for a 'oeqa.targetcontrol.QemuTarget' resource. |
333 | The underlying Qemu will be booted into a shell when the generator yields | ||
334 | and stopped when the 'with' block exits. | ||
335 | |||
336 | Usage: | ||
337 | |||
338 | with runqemu('core-image-minimal') as qemu: | ||
339 | qemu.run_serial('cat /proc/cpuinfo') | ||
340 | |||
341 | Args: | ||
342 | pn (str): (image) recipe to run on | ||
343 | ssh (boolean): whether or not to enable SSH (network access) | ||
344 | runqemuparams (str): space-separated list of params to pass to 'runqemu' script (like 'nographics', 'ovmf', etc.) | ||
345 | image_fstype (str): IMAGE_FSTYPE to use | ||
346 | launch_cmd (str): directly run this command and bypass automatic runqemu parameter generation | ||
347 | overrides (dict): dict of "'<bitbake-variable>': value" pairs that allows overriding bitbake variables | ||
348 | boot_patterns (dict): dict of "'<pattern-name>': value" pairs to override default boot patterns, e.g. when not booting Linux | ||
349 | discard_writes (boolean): enables qemu -snapshot feature to prevent modifying original image | ||
318 | """ | 350 | """ |
319 | 351 | ||
320 | import bb.tinfoil | 352 | import bb.tinfoil |
@@ -345,7 +377,7 @@ def runqemu(pn, ssh=True, runqemuparams='', image_fstype=None, launch_cmd=None, | |||
345 | 377 | ||
346 | logdir = recipedata.getVar("TEST_LOG_DIR") | 378 | logdir = recipedata.getVar("TEST_LOG_DIR") |
347 | 379 | ||
348 | qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype) | 380 | qemu = oeqa.targetcontrol.QemuTarget(recipedata, targetlogger, image_fstype, boot_patterns=boot_patterns) |
349 | finally: | 381 | finally: |
350 | # We need to shut down tinfoil early here in case we actually want | 382 | # We need to shut down tinfoil early here in case we actually want |
351 | # to run tinfoil-using utilities with the running QEMU instance. | 383 | # to run tinfoil-using utilities with the running QEMU instance. |
diff --git a/meta/lib/oeqa/utils/gitarchive.py b/meta/lib/oeqa/utils/gitarchive.py index 10cb267dfa..7e1d505748 100644 --- a/meta/lib/oeqa/utils/gitarchive.py +++ b/meta/lib/oeqa/utils/gitarchive.py | |||
@@ -67,7 +67,7 @@ def git_commit_data(repo, data_dir, branch, message, exclude, notes, log): | |||
67 | 67 | ||
68 | # Remove files that are excluded | 68 | # Remove files that are excluded |
69 | if exclude: | 69 | if exclude: |
70 | repo.run_cmd(['rm', '--cached'] + [f for f in exclude], env_update) | 70 | repo.run_cmd(['rm', '--cached', '--ignore-unmatch'] + [f for f in exclude], env_update) |
71 | 71 | ||
72 | tree = repo.run_cmd('write-tree', env_update) | 72 | tree = repo.run_cmd('write-tree', env_update) |
73 | 73 | ||
@@ -146,7 +146,7 @@ def expand_tag_strings(repo, name_pattern, msg_subj_pattern, msg_body_pattern, | |||
146 | keyws['tag_number'] = '{tag_number}' | 146 | keyws['tag_number'] = '{tag_number}' |
147 | tag_re = format_str(name_pattern, keyws) | 147 | tag_re = format_str(name_pattern, keyws) |
148 | # Replace parentheses for proper regex matching | 148 | # Replace parentheses for proper regex matching |
149 | tag_re = tag_re.replace('(', '\(').replace(')', '\)') + '$' | 149 | tag_re = tag_re.replace('(', r'\(').replace(')', r'\)') + '$' |
150 | # Inject regex group pattern for 'tag_number' | 150 | # Inject regex group pattern for 'tag_number' |
151 | tag_re = tag_re.format(tag_number='(?P<tag_number>[0-9]{1,5})') | 151 | tag_re = tag_re.format(tag_number='(?P<tag_number>[0-9]{1,5})') |
152 | 152 | ||
@@ -202,6 +202,8 @@ def gitarchive(data_dir, git_dir, no_create, bare, commit_msg_subject, commit_ms | |||
202 | log.info("Pushing data to remote") | 202 | log.info("Pushing data to remote") |
203 | data_repo.run_cmd(cmd) | 203 | data_repo.run_cmd(cmd) |
204 | 204 | ||
205 | return tag_name | ||
206 | |||
205 | # Container class for tester revisions | 207 | # Container class for tester revisions |
206 | TestedRev = namedtuple('TestedRev', 'commit commit_number tags') | 208 | TestedRev = namedtuple('TestedRev', 'commit commit_number tags') |
207 | 209 | ||
diff --git a/meta/lib/oeqa/utils/metadata.py b/meta/lib/oeqa/utils/metadata.py index 15ec190c4a..b320df67e0 100644 --- a/meta/lib/oeqa/utils/metadata.py +++ b/meta/lib/oeqa/utils/metadata.py | |||
@@ -76,6 +76,10 @@ def git_rev_info(path): | |||
76 | info['commit_count'] = int(subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=path).decode('utf-8').strip()) | 76 | info['commit_count'] = int(subprocess.check_output(["git", "rev-list", "--count", "HEAD"], cwd=path).decode('utf-8').strip()) |
77 | except subprocess.CalledProcessError: | 77 | except subprocess.CalledProcessError: |
78 | pass | 78 | pass |
79 | try: | ||
80 | info['commit_time'] = int(subprocess.check_output(["git", "show", "--no-patch", "--format=%ct", "HEAD"], cwd=path).decode('utf-8').strip()) | ||
81 | except subprocess.CalledProcessError: | ||
82 | pass | ||
79 | return info | 83 | return info |
80 | try: | 84 | try: |
81 | repo = Repo(path, search_parent_directories=True) | 85 | repo = Repo(path, search_parent_directories=True) |
@@ -83,6 +87,7 @@ def git_rev_info(path): | |||
83 | return info | 87 | return info |
84 | info['commit'] = repo.head.commit.hexsha | 88 | info['commit'] = repo.head.commit.hexsha |
85 | info['commit_count'] = repo.head.commit.count() | 89 | info['commit_count'] = repo.head.commit.count() |
90 | info['commit_time'] = repo.head.commit.committed_date | ||
86 | try: | 91 | try: |
87 | info['branch'] = repo.active_branch.name | 92 | info['branch'] = repo.active_branch.name |
88 | except TypeError: | 93 | except TypeError: |
diff --git a/meta/lib/oeqa/utils/postactions.py b/meta/lib/oeqa/utils/postactions.py index ecdddd2d40..c69481db6c 100644 --- a/meta/lib/oeqa/utils/postactions.py +++ b/meta/lib/oeqa/utils/postactions.py | |||
@@ -7,23 +7,20 @@ | |||
7 | # Run a set of actions after tests. The runner provides internal data | 7 | # Run a set of actions after tests. The runner provides internal data |
8 | # dictionary as well as test context to any action to run. | 8 | # dictionary as well as test context to any action to run. |
9 | 9 | ||
10 | from oeqa.utils import get_json_result_dir | 10 | import datetime |
11 | 11 | import io | |
12 | def create_artifacts_directory(d, tc): | 12 | import os |
13 | import shutil | 13 | import stat |
14 | 14 | import subprocess | |
15 | local_artifacts_dir = os.path.join(get_json_result_dir(d), "artifacts") | 15 | import tempfile |
16 | if os.path.isdir(local_artifacts_dir): | 16 | from oeqa.utils import get_artefact_dir |
17 | shutil.rmtree(local_artifacts_dir) | ||
18 | |||
19 | os.makedirs(local_artifacts_dir) | ||
20 | 17 | ||
21 | ################################################################## | 18 | ################################################################## |
22 | # Host/target statistics | 19 | # Host/target statistics |
23 | ################################################################## | 20 | ################################################################## |
24 | 21 | ||
25 | def get_target_disk_usage(d, tc): | 22 | def get_target_disk_usage(d, tc, artifacts_list, outputdir): |
26 | output_file = os.path.join(get_json_result_dir(d), "artifacts", "target_disk_usage.txt") | 23 | output_file = os.path.join(outputdir, "target_disk_usage.txt") |
27 | try: | 24 | try: |
28 | (status, output) = tc.target.run('df -h') | 25 | (status, output) = tc.target.run('df -h') |
29 | with open(output_file, 'w') as f: | 26 | with open(output_file, 'w') as f: |
@@ -32,10 +29,10 @@ def get_target_disk_usage(d, tc): | |||
32 | except Exception as e: | 29 | except Exception as e: |
33 | bb.warn(f"Can not get target disk usage: {e}") | 30 | bb.warn(f"Can not get target disk usage: {e}") |
34 | 31 | ||
35 | def get_host_disk_usage(d, tc): | 32 | def get_host_disk_usage(d, tc, artifacts_list, outputdir): |
36 | import subprocess | 33 | import subprocess |
37 | 34 | ||
38 | output_file = os.path.join(get_json_result_dir(d), "artifacts", "host_disk_usage.txt") | 35 | output_file = os.path.join(outputdir, "host_disk_usage.txt") |
39 | try: | 36 | try: |
40 | with open(output_file, 'w') as f: | 37 | with open(output_file, 'w') as f: |
41 | output = subprocess.run(['df', '-hl'], check=True, text=True, stdout=f, env={}) | 38 | output = subprocess.run(['df', '-hl'], check=True, text=True, stdout=f, env={}) |
@@ -61,25 +58,22 @@ def get_artifacts_list(target, raw_list): | |||
61 | 58 | ||
62 | return result | 59 | return result |
63 | 60 | ||
64 | def retrieve_test_artifacts(target, artifacts_list, target_dir): | 61 | def list_and_fetch_failed_tests_artifacts(d, tc, artifacts_list, outputdir): |
65 | local_artifacts_dir = os.path.join(target_dir, "artifacts") | 62 | artifacts_list = get_artifacts_list(tc.target, artifacts_list) |
66 | for artifact_path in artifacts_list: | ||
67 | if not os.path.isabs(artifact_path): | ||
68 | bb.warn(f"{artifact_path} is not an absolute path") | ||
69 | continue | ||
70 | try: | ||
71 | dest_dir = os.path.join(local_artifacts_dir, os.path.dirname(artifact_path[1:])) | ||
72 | os.makedirs(dest_dir, exist_ok=True) | ||
73 | target.copyFrom(artifact_path, dest_dir) | ||
74 | except Exception as e: | ||
75 | bb.warn(f"Can not retrieve {artifact_path} from test target: {e}") | ||
76 | |||
77 | def list_and_fetch_failed_tests_artifacts(d, tc): | ||
78 | artifacts_list = get_artifacts_list(tc.target, d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS")) | ||
79 | if not artifacts_list: | 63 | if not artifacts_list: |
80 | bb.warn("Could not load artifacts list, skip artifacts retrieval") | 64 | bb.warn("Could not load artifacts list, skip artifacts retrieval") |
81 | else: | 65 | return |
82 | retrieve_test_artifacts(tc.target, artifacts_list, get_json_result_dir(d)) | 66 | try: |
67 | # We need gnu tar for sparse files, not busybox | ||
68 | cmd = "tar --sparse -zcf - " + " ".join(artifacts_list) | ||
69 | (status, output) = tc.target.run(cmd, raw = True) | ||
70 | if status != 0 or not output: | ||
71 | raise Exception("Error while fetching compressed artifacts") | ||
72 | archive_name = os.path.join(outputdir, "tests_artifacts.tar.gz") | ||
73 | with open(archive_name, "wb") as f: | ||
74 | f.write(output) | ||
75 | except Exception as e: | ||
76 | bb.warn(f"Can not retrieve artifacts from test target: {e}") | ||
83 | 77 | ||
84 | 78 | ||
85 | ################################################################## | 79 | ################################################################## |
@@ -87,12 +81,22 @@ def list_and_fetch_failed_tests_artifacts(d, tc): | |||
87 | ################################################################## | 81 | ################################################################## |
88 | 82 | ||
89 | def run_failed_tests_post_actions(d, tc): | 83 | def run_failed_tests_post_actions(d, tc): |
84 | artifacts = d.getVar("TESTIMAGE_FAILED_QA_ARTIFACTS") | ||
85 | # Allow all the code to be disabled by having no artifacts set, e.g. for systems with no ssh support | ||
86 | if not artifacts: | ||
87 | return | ||
88 | |||
89 | outputdir = get_artefact_dir(d) | ||
90 | os.makedirs(outputdir, exist_ok=True) | ||
91 | datestr = datetime.datetime.now().strftime('%Y%m%d') | ||
92 | outputdir = tempfile.mkdtemp(prefix='oeqa-target-artefacts-%s-' % datestr, dir=outputdir) | ||
93 | os.chmod(outputdir, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) | ||
94 | |||
90 | post_actions=[ | 95 | post_actions=[ |
91 | create_artifacts_directory, | ||
92 | list_and_fetch_failed_tests_artifacts, | 96 | list_and_fetch_failed_tests_artifacts, |
93 | get_target_disk_usage, | 97 | get_target_disk_usage, |
94 | get_host_disk_usage | 98 | get_host_disk_usage |
95 | ] | 99 | ] |
96 | 100 | ||
97 | for action in post_actions: | 101 | for action in post_actions: |
98 | action(d, tc) | 102 | action(d, tc, artifacts, outputdir) |
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index cda43aad8c..c4db0cf038 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
@@ -30,6 +30,8 @@ control_range = list(range(0,32))+list(range(127,160)) | |||
30 | control_chars = [chr(x) for x in control_range | 30 | control_chars = [chr(x) for x in control_range |
31 | if chr(x) not in string.printable] | 31 | if chr(x) not in string.printable] |
32 | re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) | 32 | re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) |
33 | # Regex to remove the ANSI (color) control codes from console strings in order to match the text only | ||
34 | re_vt100 = re.compile(r'(\x1b\[|\x9b)[^@-_a-z]*[@-_a-z]|\x1b[@-_a-z]') | ||
33 | 35 | ||
34 | def getOutput(o): | 36 | def getOutput(o): |
35 | import fcntl | 37 | import fcntl |
@@ -101,7 +103,7 @@ class QemuRunner: | |||
101 | 103 | ||
102 | # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" | 104 | # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" |
103 | for pattern in accepted_patterns: | 105 | for pattern in accepted_patterns: |
104 | if not self.boot_patterns[pattern]: | 106 | if pattern not in self.boot_patterns or not self.boot_patterns[pattern]: |
105 | self.boot_patterns[pattern] = default_boot_patterns[pattern] | 107 | self.boot_patterns[pattern] = default_boot_patterns[pattern] |
106 | 108 | ||
107 | def create_socket(self): | 109 | def create_socket(self): |
@@ -265,12 +267,15 @@ class QemuRunner: | |||
265 | self.monitorpipe = os.fdopen(w, "w") | 267 | self.monitorpipe = os.fdopen(w, "w") |
266 | else: | 268 | else: |
267 | # child process | 269 | # child process |
268 | os.setpgrp() | 270 | try: |
269 | os.close(w) | 271 | os.setpgrp() |
270 | r = os.fdopen(r) | 272 | os.close(w) |
271 | x = r.read() | 273 | r = os.fdopen(r) |
272 | os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) | 274 | x = r.read() |
273 | os._exit(0) | 275 | os.killpg(os.getpgid(self.runqemu.pid), signal.SIGTERM) |
276 | finally: | ||
277 | # We must exit under all circumstances | ||
278 | os._exit(0) | ||
274 | 279 | ||
275 | self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) | 280 | self.logger.debug("runqemu started, pid is %s" % self.runqemu.pid) |
276 | self.logger.debug("waiting at most %d seconds for qemu pid (%s)" % | 281 | self.logger.debug("waiting at most %d seconds for qemu pid (%s)" % |
@@ -519,7 +524,6 @@ class QemuRunner: | |||
519 | except Exception as e: | 524 | except Exception as e: |
520 | self.logger.warning('Extra log data exception %s' % repr(e)) | 525 | self.logger.warning('Extra log data exception %s' % repr(e)) |
521 | data = None | 526 | data = None |
522 | self.thread.serial_lock.release() | ||
523 | return False | 527 | return False |
524 | 528 | ||
525 | with self.thread.serial_lock: | 529 | with self.thread.serial_lock: |
@@ -533,7 +537,7 @@ class QemuRunner: | |||
533 | self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", "")) | 537 | self.logger.debug("Logged in as %s in serial console" % self.boot_patterns['send_login_user'].replace("\n", "")) |
534 | if netconf: | 538 | if netconf: |
535 | # configure guest networking | 539 | # configure guest networking |
536 | cmd = "ifconfig eth0 %s netmask %s up\n" % (self.ip, self.netmask) | 540 | cmd = "ip addr add %s/%s dev eth0\nip link set dev eth0 up\n" % (self.ip, self.netmask) |
537 | output = self.run_serial(cmd, raw=True)[1] | 541 | output = self.run_serial(cmd, raw=True)[1] |
538 | if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): | 542 | if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): |
539 | self.logger.debug("configured ip address %s", self.ip) | 543 | self.logger.debug("configured ip address %s", self.ip) |
@@ -681,7 +685,7 @@ class QemuRunner: | |||
681 | time.sleep(0.1) | 685 | time.sleep(0.1) |
682 | answer = self.server_socket.recv(1024) | 686 | answer = self.server_socket.recv(1024) |
683 | if answer: | 687 | if answer: |
684 | data += answer.decode('utf-8') | 688 | data += re_vt100.sub("", answer.decode('utf-8')) |
685 | # Search the prompt to stop | 689 | # Search the prompt to stop |
686 | if re.search(self.boot_patterns['search_cmd_finished'], data): | 690 | if re.search(self.boot_patterns['search_cmd_finished'], data): |
687 | break | 691 | break |
@@ -745,8 +749,10 @@ class LoggingThread(threading.Thread): | |||
745 | def threadtarget(self): | 749 | def threadtarget(self): |
746 | try: | 750 | try: |
747 | self.eventloop() | 751 | self.eventloop() |
748 | except Exception as e: | 752 | except Exception: |
749 | self.logger.warning("Exception %s in logging thread" % traceback.format_exception(e)) | 753 | exc_type, exc_value, exc_traceback = sys.exc_info() |
754 | self.logger.warning("Exception %s in logging thread" % | ||
755 | traceback.format_exception(exc_type, exc_value, exc_traceback)) | ||
750 | finally: | 756 | finally: |
751 | self.teardown() | 757 | self.teardown() |
752 | 758 | ||
@@ -822,10 +828,12 @@ class LoggingThread(threading.Thread): | |||
822 | self.logfunc(data, ".stdout") | 828 | self.logfunc(data, ".stdout") |
823 | elif self.serialsock and self.serialsock.fileno() == fd: | 829 | elif self.serialsock and self.serialsock.fileno() == fd: |
824 | if self.serial_lock.acquire(blocking=False): | 830 | if self.serial_lock.acquire(blocking=False): |
825 | data = self.recv(1024, self.serialsock) | 831 | try: |
826 | self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) | 832 | data = self.recv(1024, self.serialsock) |
827 | self.logfunc(data, ".2") | 833 | self.logger.debug("Data received serial thread %s" % data.decode('utf-8', 'replace')) |
828 | self.serial_lock.release() | 834 | self.logfunc(data, ".2") |
835 | finally: | ||
836 | self.serial_lock.release() | ||
829 | else: | 837 | else: |
830 | serial_registered = False | 838 | serial_registered = False |
831 | poll.unregister(self.serialsock.fileno()) | 839 | poll.unregister(self.serialsock.fileno()) |
diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py index 36c2ecb3db..88a61aff63 100644 --- a/meta/lib/oeqa/utils/sshcontrol.py +++ b/meta/lib/oeqa/utils/sshcontrol.py | |||
@@ -57,8 +57,10 @@ class SSHProcess(object): | |||
57 | if select.select([self.process.stdout], [], [], 5)[0] != []: | 57 | if select.select([self.process.stdout], [], [], 5)[0] != []: |
58 | data = os.read(self.process.stdout.fileno(), 1024) | 58 | data = os.read(self.process.stdout.fileno(), 1024) |
59 | if not data: | 59 | if not data: |
60 | self.process.stdout.close() | 60 | self.process.poll() |
61 | eof = True | 61 | if self.process.returncode is not None: |
62 | self.process.stdout.close() | ||
63 | eof = True | ||
62 | else: | 64 | else: |
63 | data = data.decode("utf-8") | 65 | data = data.decode("utf-8") |
64 | output += data | 66 | output += data |
diff --git a/meta/lib/oeqa/utils/subprocesstweak.py b/meta/lib/oeqa/utils/subprocesstweak.py index 3e43ed547b..1774513023 100644 --- a/meta/lib/oeqa/utils/subprocesstweak.py +++ b/meta/lib/oeqa/utils/subprocesstweak.py | |||
@@ -8,16 +8,11 @@ import subprocess | |||
8 | class OETestCalledProcessError(subprocess.CalledProcessError): | 8 | class OETestCalledProcessError(subprocess.CalledProcessError): |
9 | def __str__(self): | 9 | def __str__(self): |
10 | def strify(o): | 10 | def strify(o): |
11 | if isinstance(o, bytes): | 11 | return o.decode("utf-8", errors="replace") if isinstance(o, bytes) else o |
12 | return o.decode("utf-8", errors="replace") | ||
13 | else: | ||
14 | return o | ||
15 | 12 | ||
16 | s = "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) | 13 | s = super().__str__() |
17 | if hasattr(self, "output") and self.output: | 14 | s = s + "\nStandard Output: " + strify(self.output) |
18 | s = s + "\nStandard Output: " + strify(self.output) | 15 | s = s + "\nStandard Error: " + strify(self.stderr) |
19 | if hasattr(self, "stderr") and self.stderr: | ||
20 | s = s + "\nStandard Error: " + strify(self.stderr) | ||
21 | return s | 16 | return s |
22 | 17 | ||
23 | def errors_have_output(): | 18 | def errors_have_output(): |
diff --git a/meta/lib/oeqa/utils/testexport.py b/meta/lib/oeqa/utils/testexport.py index e89d130a9c..3ab024d9e9 100644 --- a/meta/lib/oeqa/utils/testexport.py +++ b/meta/lib/oeqa/utils/testexport.py | |||
@@ -60,17 +60,17 @@ def process_binaries(d, params): | |||
60 | export_env = d.getVar("TEST_EXPORT_ONLY") | 60 | export_env = d.getVar("TEST_EXPORT_ONLY") |
61 | 61 | ||
62 | def extract_binary(pth_to_pkg, dest_pth=None): | 62 | def extract_binary(pth_to_pkg, dest_pth=None): |
63 | cpio_command = runCmd("which cpio") | 63 | tar_command = runCmd("which tar") |
64 | rpm2cpio_command = runCmd("ls /usr/bin/rpm2cpio") | 64 | rpm2archive_command = runCmd("ls /usr/bin/rpm2archive") |
65 | if (cpio_command.status != 0) and (rpm2cpio_command.status != 0): | 65 | if (tar_command.status != 0) and (rpm2archive_command.status != 0): |
66 | bb.fatal("Either \"rpm2cpio\" or \"cpio\" tools are not available on your system." | 66 | bb.fatal("Either \"rpm2archive\" or \"tar\" tools are not available on your system." |
67 | "All binaries extraction processes will not be available, crashing all related tests." | 67 | "All binaries extraction processes will not be available, crashing all related tests." |
68 | "Please install them according to your OS recommendations") # will exit here | 68 | "Please install them according to your OS recommendations") # will exit here |
69 | if dest_pth: | 69 | if dest_pth: |
70 | os.chdir(dest_pth) | 70 | os.chdir(dest_pth) |
71 | else: | 71 | else: |
72 | os.chdir("%s" % os.sep)# this is for native package | 72 | os.chdir("%s" % os.sep)# this is for native package |
73 | extract_bin_command = runCmd("%s %s | %s -idm" % (rpm2cpio_command.output, pth_to_pkg, cpio_command.output)) # semi-hardcoded because of a bug on poky's rpm2cpio | 73 | extract_bin_command = runCmd("%s -n %s | %s xv" % (rpm2archive_command.output, pth_to_pkg, tar_command.output)) # semi-hardcoded because of a bug on poky's rpm2cpio |
74 | return extract_bin_command | 74 | return extract_bin_command |
75 | 75 | ||
76 | if determine_if_poky_env(): # machine with poky environment | 76 | if determine_if_poky_env(): # machine with poky environment |