diff options
| -rw-r--r-- | meta/classes/testimage.bbclass | 39 | ||||
| -rw-r--r-- | meta/lib/oeqa/core/target/qemu.py | 7 | ||||
| -rw-r--r-- | meta/lib/oeqa/utils/qemurunner.py | 31 |
3 files changed, 70 insertions, 7 deletions
diff --git a/meta/classes/testimage.bbclass b/meta/classes/testimage.bbclass index 0d55c3247e..75f0f2c3e3 100644 --- a/meta/classes/testimage.bbclass +++ b/meta/classes/testimage.bbclass | |||
| @@ -34,6 +34,17 @@ TESTIMAGE_AUTO ??= "0" | |||
| 34 | # TEST_QEMUPARAMS can be used to pass extra parameters to qemu, e.g. "-m 1024" for setting the amount of ram to 1 GB. | 34 | # TEST_QEMUPARAMS can be used to pass extra parameters to qemu, e.g. "-m 1024" for setting the amount of ram to 1 GB. |
| 35 | # TEST_RUNQEMUPARAMS can be used to pass extra parameters to runqemu, e.g. "gl" to enable OpenGL acceleration. | 35 | # TEST_RUNQEMUPARAMS can be used to pass extra parameters to runqemu, e.g. "gl" to enable OpenGL acceleration. |
| 36 | 36 | ||
| 37 | # TESTIMAGE_BOOT_PATTERNS can be used to override certain patterns used to communicate with the target when booting, | ||
| 38 | # if a pattern is not specifically present on this variable a default will be used when booting the target. | ||
| 39 | # TESTIMAGE_BOOT_PATTERNS[<flag>] overrides the pattern used for that specific flag, where flag comes from a list of accepted flags | ||
| 40 | # e.g. normally the system boots and waits for a login prompt (login:), after that it sends the command: "root\n" to log as the root user | ||
| 41 | # if we wanted to log in as the hypothetical "webserver" user for example we could set the following: | ||
| 42 | # TESTIMAGE_BOOT_PATTERNS = "send_login_user search_login_succeeded" | ||
| 43 | # TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" | ||
| 44 | # TESTIMAGE_BOOT_PATTERNS[search_login_succeeded] = "webserver@[a-zA-Z0-9\-]+:~#" | ||
| 45 | # The accepted flags are the following: search_reached_prompt, send_login_user, search_login_succeeded, search_cmd_finished. | ||
| 46 | # They are prefixed with either search/send, to differentiate if the pattern is meant to be sent or searched to/from the target terminal | ||
| 47 | |||
| 37 | TEST_LOG_DIR ?= "${WORKDIR}/testimage" | 48 | TEST_LOG_DIR ?= "${WORKDIR}/testimage" |
| 38 | 49 | ||
| 39 | TEST_EXPORT_DIR ?= "${TMPDIR}/testimage/${PN}" | 50 | TEST_EXPORT_DIR ?= "${TMPDIR}/testimage/${PN}" |
| @@ -68,6 +79,8 @@ TEST_TARGET ?= "qemu" | |||
| 68 | TEST_QEMUPARAMS ?= "" | 79 | TEST_QEMUPARAMS ?= "" |
| 69 | TEST_RUNQEMUPARAMS ?= "" | 80 | TEST_RUNQEMUPARAMS ?= "" |
| 70 | 81 | ||
| 82 | TESTIMAGE_BOOT_PATTERNS ?= "" | ||
| 83 | |||
| 71 | TESTIMAGEDEPENDS = "" | 84 | TESTIMAGEDEPENDS = "" |
| 72 | TESTIMAGEDEPENDS_append_qemuall = " qemu-native:do_populate_sysroot qemu-helper-native:do_populate_sysroot qemu-helper-native:do_addto_recipe_sysroot" | 85 | TESTIMAGEDEPENDS_append_qemuall = " qemu-native:do_populate_sysroot qemu-helper-native:do_populate_sysroot qemu-helper-native:do_addto_recipe_sysroot" |
| 73 | TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'cpio-native:do_populate_sysroot', '', d)}" | 86 | TESTIMAGEDEPENDS += "${@bb.utils.contains('IMAGE_PKGTYPE', 'rpm', 'cpio-native:do_populate_sysroot', '', d)}" |
| @@ -150,6 +163,29 @@ def get_testimage_json_result_dir(d): | |||
| 150 | def get_testimage_result_id(configuration): | 163 | def get_testimage_result_id(configuration): |
| 151 | return '%s_%s_%s_%s' % (configuration['TEST_TYPE'], configuration['IMAGE_BASENAME'], configuration['MACHINE'], configuration['STARTTIME']) | 164 | return '%s_%s_%s_%s' % (configuration['TEST_TYPE'], configuration['IMAGE_BASENAME'], configuration['MACHINE'], configuration['STARTTIME']) |
| 152 | 165 | ||
| 166 | def get_testimage_boot_patterns(d): | ||
| 167 | from collections import defaultdict | ||
| 168 | boot_patterns = defaultdict(str) | ||
| 169 | # Only accept certain values | ||
| 170 | accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished'] | ||
| 171 | # Not all patterns need to be overriden, e.g. perhaps we only want to change the user | ||
| 172 | boot_patterns_flags = d.getVarFlags('TESTIMAGE_BOOT_PATTERNS') or {} | ||
| 173 | if boot_patterns_flags: | ||
| 174 | patterns_set = [p for p in boot_patterns_flags.items() if p[0] in d.getVar('TESTIMAGE_BOOT_PATTERNS').split()] | ||
| 175 | for flag, flagval in patterns_set: | ||
| 176 | if flag not in accepted_patterns: | ||
| 177 | bb.fatal('Testimage: The only accepted boot patterns are: search_reached_prompt,send_login_user, \ | ||
| 178 | search_login_succeeded,search_cmd_finished\n Make sure your TESTIMAGE_BOOT_PATTERNS=%s \ | ||
| 179 | contains an accepted flag.' % d.getVar('TESTIMAGE_BOOT_PATTERNS')) | ||
| 180 | return | ||
| 181 | # We know boot prompt is searched through in binary format, others might be expressions | ||
| 182 | if flag == 'search_reached_prompt': | ||
| 183 | boot_patterns[flag] = flagval.encode() | ||
| 184 | else: | ||
| 185 | boot_patterns[flag] = flagval.encode().decode('unicode-escape') | ||
| 186 | return boot_patterns | ||
| 187 | |||
| 188 | |||
| 153 | def testimage_main(d): | 189 | def testimage_main(d): |
| 154 | import os | 190 | import os |
| 155 | import json | 191 | import json |
| @@ -259,6 +295,9 @@ def testimage_main(d): | |||
| 259 | 'serial_ports': len(d.getVar("SERIAL_CONSOLES").split()), | 295 | 'serial_ports': len(d.getVar("SERIAL_CONSOLES").split()), |
| 260 | } | 296 | } |
| 261 | 297 | ||
| 298 | if d.getVar("TESTIMAGE_BOOT_PATTERNS"): | ||
| 299 | target_kwargs['boot_patterns'] = get_testimage_boot_patterns(d) | ||
| 300 | |||
| 262 | # TODO: Currently BBPATH is needed for custom loading of targets. | 301 | # TODO: Currently BBPATH is needed for custom loading of targets. |
| 263 | # It would be better to find these modules using instrospection. | 302 | # It would be better to find these modules using instrospection. |
| 264 | target_kwargs['target_modules_path'] = d.getVar('BBPATH') | 303 | target_kwargs['target_modules_path'] = d.getVar('BBPATH') |
diff --git a/meta/lib/oeqa/core/target/qemu.py b/meta/lib/oeqa/core/target/qemu.py index 008a9f03ce..059106e915 100644 --- a/meta/lib/oeqa/core/target/qemu.py +++ b/meta/lib/oeqa/core/target/qemu.py | |||
| @@ -8,6 +8,7 @@ import os | |||
| 8 | import sys | 8 | import sys |
| 9 | import signal | 9 | import signal |
| 10 | import time | 10 | import time |
| 11 | from collections import defaultdict | ||
| 11 | 12 | ||
| 12 | from .ssh import OESSHTarget | 13 | from .ssh import OESSHTarget |
| 13 | from oeqa.utils.qemurunner import QemuRunner | 14 | from oeqa.utils.qemurunner import QemuRunner |
| @@ -18,7 +19,8 @@ class OEQemuTarget(OESSHTarget): | |||
| 18 | def __init__(self, logger, server_ip, timeout=300, user='root', | 19 | def __init__(self, logger, server_ip, timeout=300, user='root', |
| 19 | port=None, machine='', rootfs='', kernel='', kvm=False, slirp=False, | 20 | port=None, machine='', rootfs='', kernel='', kvm=False, slirp=False, |
| 20 | dump_dir='', dump_host_cmds='', display='', bootlog='', | 21 | dump_dir='', dump_host_cmds='', display='', bootlog='', |
| 21 | tmpdir='', dir_image='', boottime=60, serial_ports=2, **kwargs): | 22 | tmpdir='', dir_image='', boottime=60, serial_ports=2, |
| 23 | boot_patterns = defaultdict(str), **kwargs): | ||
| 22 | 24 | ||
| 23 | super(OEQemuTarget, self).__init__(logger, None, server_ip, timeout, | 25 | super(OEQemuTarget, self).__init__(logger, None, server_ip, timeout, |
| 24 | user, port) | 26 | user, port) |
| @@ -30,13 +32,14 @@ class OEQemuTarget(OESSHTarget): | |||
| 30 | self.kernel = kernel | 32 | self.kernel = kernel |
| 31 | self.kvm = kvm | 33 | self.kvm = kvm |
| 32 | self.use_slirp = slirp | 34 | self.use_slirp = slirp |
| 35 | self.boot_patterns = boot_patterns | ||
| 33 | 36 | ||
| 34 | self.runner = QemuRunner(machine=machine, rootfs=rootfs, tmpdir=tmpdir, | 37 | self.runner = QemuRunner(machine=machine, rootfs=rootfs, tmpdir=tmpdir, |
| 35 | deploy_dir_image=dir_image, display=display, | 38 | deploy_dir_image=dir_image, display=display, |
| 36 | logfile=bootlog, boottime=boottime, | 39 | logfile=bootlog, boottime=boottime, |
| 37 | use_kvm=kvm, use_slirp=slirp, dump_dir=dump_dir, | 40 | use_kvm=kvm, use_slirp=slirp, dump_dir=dump_dir, |
| 38 | dump_host_cmds=dump_host_cmds, logger=logger, | 41 | dump_host_cmds=dump_host_cmds, logger=logger, |
| 39 | serial_ports=serial_ports) | 42 | serial_ports=serial_ports, boot_patterns = boot_patterns) |
| 40 | 43 | ||
| 41 | def start(self, params=None, extra_bootparams=None, runqemuparams=''): | 44 | def start(self, params=None, extra_bootparams=None, runqemuparams=''): |
| 42 | if self.use_slirp and not self.server_ip: | 45 | if self.use_slirp and not self.server_ip: |
diff --git a/meta/lib/oeqa/utils/qemurunner.py b/meta/lib/oeqa/utils/qemurunner.py index 4704422211..ed74ea8fad 100644 --- a/meta/lib/oeqa/utils/qemurunner.py +++ b/meta/lib/oeqa/utils/qemurunner.py | |||
| @@ -21,6 +21,7 @@ import threading | |||
| 21 | import codecs | 21 | import codecs |
| 22 | import logging | 22 | import logging |
| 23 | from oeqa.utils.dump import HostDumper | 23 | from oeqa.utils.dump import HostDumper |
| 24 | from collections import defaultdict | ||
| 24 | 25 | ||
| 25 | # Get Unicode non printable control chars | 26 | # Get Unicode non printable control chars |
| 26 | control_range = list(range(0,32))+list(range(127,160)) | 27 | control_range = list(range(0,32))+list(range(127,160)) |
| @@ -31,7 +32,7 @@ re_control_char = re.compile('[%s]' % re.escape("".join(control_chars))) | |||
| 31 | class QemuRunner: | 32 | class QemuRunner: |
| 32 | 33 | ||
| 33 | def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, | 34 | def __init__(self, machine, rootfs, display, tmpdir, deploy_dir_image, logfile, boottime, dump_dir, dump_host_cmds, |
| 34 | use_kvm, logger, use_slirp=False, serial_ports=2): | 35 | use_kvm, logger, use_slirp=False, serial_ports=2, boot_patterns = defaultdict(str)): |
| 35 | 36 | ||
| 36 | # Popen object for runqemu | 37 | # Popen object for runqemu |
| 37 | self.runqemu = None | 38 | self.runqemu = None |
| @@ -57,6 +58,7 @@ class QemuRunner: | |||
| 57 | self.use_slirp = use_slirp | 58 | self.use_slirp = use_slirp |
| 58 | self.serial_ports = serial_ports | 59 | self.serial_ports = serial_ports |
| 59 | self.msg = '' | 60 | self.msg = '' |
| 61 | self.boot_patterns = boot_patterns | ||
| 60 | 62 | ||
| 61 | self.runqemutime = 120 | 63 | self.runqemutime = 120 |
| 62 | self.qemu_pidfile = 'pidfile_'+str(os.getpid()) | 64 | self.qemu_pidfile = 'pidfile_'+str(os.getpid()) |
| @@ -65,6 +67,25 @@ class QemuRunner: | |||
| 65 | 67 | ||
| 66 | self.logger = logger | 68 | self.logger = logger |
| 67 | 69 | ||
| 70 | # Enable testing other OS's | ||
| 71 | # Set commands for target communication, and default to Linux ALWAYS | ||
| 72 | # Other OS's or baremetal applications need to provide their | ||
| 73 | # own implementation passing it through QemuRunner's constructor | ||
| 74 | # or by passing them through TESTIMAGE_BOOT_PATTERNS[flag] | ||
| 75 | # provided variables, where <flag> is one of the mentioned below. | ||
| 76 | accepted_patterns = ['search_reached_prompt', 'send_login_user', 'search_login_succeeded', 'search_cmd_finished'] | ||
| 77 | default_boot_patterns = defaultdict(str) | ||
| 78 | # Default to the usual paterns used to communicate with the target | ||
| 79 | default_boot_patterns['search_reached_prompt'] = b' login:' | ||
| 80 | default_boot_patterns['send_login_user'] = 'root\n' | ||
| 81 | default_boot_patterns['search_login_succeeded'] = r"root@[a-zA-Z0-9\-]+:~#" | ||
| 82 | default_boot_patterns['search_cmd_finished'] = r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#" | ||
| 83 | |||
| 84 | # Only override patterns that were set e.g. login user TESTIMAGE_BOOT_PATTERNS[send_login_user] = "webserver\n" | ||
| 85 | for pattern in accepted_patterns: | ||
| 86 | if not self.boot_patterns[pattern]: | ||
| 87 | self.boot_patterns[pattern] = default_boot_patterns[pattern] | ||
| 88 | |||
| 68 | def create_socket(self): | 89 | def create_socket(self): |
| 69 | try: | 90 | try: |
| 70 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | 91 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| @@ -321,7 +342,7 @@ class QemuRunner: | |||
| 321 | self.log(data) | 342 | self.log(data) |
| 322 | 343 | ||
| 323 | data = b'' | 344 | data = b'' |
| 324 | if b' login:' in bootlog: | 345 | if self.boot_patterns['search_reached_prompt'] in bootlog: |
| 325 | self.server_socket = qemusock | 346 | self.server_socket = qemusock |
| 326 | stopread = True | 347 | stopread = True |
| 327 | reachedlogin = True | 348 | reachedlogin = True |
| @@ -353,8 +374,8 @@ class QemuRunner: | |||
| 353 | 374 | ||
| 354 | # If we are not able to login the tests can continue | 375 | # If we are not able to login the tests can continue |
| 355 | try: | 376 | try: |
| 356 | (status, output) = self.run_serial("root\n", raw=True) | 377 | (status, output) = self.run_serial(self.boot_patterns['send_login_user'], raw=True) |
| 357 | if re.search(r"root@[a-zA-Z0-9\-]+:~#", output): | 378 | if re.search(self.boot_patterns['search_login_succeeded'], output): |
| 358 | self.logged = True | 379 | self.logged = True |
| 359 | self.logger.debug("Logged as root in serial console") | 380 | self.logger.debug("Logged as root in serial console") |
| 360 | if netconf: | 381 | if netconf: |
| @@ -478,7 +499,7 @@ class QemuRunner: | |||
| 478 | if answer: | 499 | if answer: |
| 479 | data += answer.decode('utf-8') | 500 | data += answer.decode('utf-8') |
| 480 | # Search the prompt to stop | 501 | # Search the prompt to stop |
| 481 | if re.search(r"[a-zA-Z0-9]+@[a-zA-Z0-9\-]+:~#", data): | 502 | if re.search(self.boot_patterns['search_cmd_finished'], data): |
| 482 | break | 503 | break |
| 483 | else: | 504 | else: |
| 484 | raise Exception("No data on serial console socket") | 505 | raise Exception("No data on serial console socket") |
