def swupdate_update_web(lh: linux.Lab, swu_file: linux.Path, target_ip: str, timeout: int = 300) -> None: """ Upload an ``.swu`` file to a running swupdate server. :param linux.Lab lh: Optionally the lab-host from where to initiate the update. :param linux.Path swu_file: Path to the ``.swu`` file (on the lab-host or locally). :param str target_ip: IP-Address of the target host. :param int timeout: Timeout. """ with tbot.acquire_local() as lo: # Needed for the script script_path = lh.workdir / "tbot_swupdate_web.py" swu_path = lh.workdir / "image.swu" script_source = linux.Path(lo, __file__).parent / "swupdate_script.py" shell.copy(script_source, script_path) shell.copy(swu_file, swu_path) lh.exec0("python3", script_path, swu_path, target_ip, str(timeout))
def minisshd(h: linux.Lab, port: int = 2022) -> typing.Generator: """ Create a new minisshd server and machine. Intended for use in a ``with``-statement:: if check_minisshd(lh): with minisshd(lh) as ssh: ssh.exec0("uname", "-a") :param linux.Lab h: lab-host :param int port: Port for the ssh server, defaults to ``2022``. :rtype: MiniSSHMachine """ server_dir = h.workdir / "minisshd" h.exec0("mkdir", "-p", server_dir) key_file = server_dir / "ssh_host_key" pid_file = server_dir / "dropbear.pid" # Create host key if not key_file.exists(): h.exec0("dropbearkey", "-t", "rsa", "-f", key_file) h.exec0("dropbear", "-p", "127.0.0.1:2022", "-r", key_file, "-P", pid_file) # Try reading the file again if it does not yet exist for i in range(10): ret, pid = h.exec("cat", pid_file) if ret == 0: pid = pid.strip() break else: raise RuntimeError("dropbear did not create a pid-file!") try: with MiniSSHMachine(h, port) as ssh_machine: yield ssh_machine finally: tbot.log.message("Stopping dropbear ...") h.exec0("kill", pid)
def git_prepare(lab: linux.Lab) -> str: """Prepare a test git repo.""" global _GIT if _GIT is None: # Git committer and author information in case the user's git # environment is not set up yet lab.env("GIT_AUTHOR_NAME", "tbot selftest") lab.env("GIT_AUTHOR_EMAIL", "*****@*****.**") lab.env("GIT_COMMITTER_NAME", "tbot selftest") lab.env("GIT_COMMITTER_EMAIL", "*****@*****.**") p = lab.workdir / "selftest-git-remote" if p.exists(): lab.exec0("rm", "-rf", p) tbot.log.message("Setting up test repo ...") lab.exec0("mkdir", "-p", p) lab.exec0("git", "-C", p, "init") repo = git.GitRepository(p, clean=False) lab.exec0( "echo", """\ # tbot Selftest This repo exists to test tbot's git testcase. You can safely remove it, but please **do not** modify it as that might break the tests.""", linux.RedirStdout(repo / "README.md"), ) repo.add(repo / "README.md") repo.commit("Initial", author="tbot Selftest <none@none>") tbot.log.message("Creating test patch ...") lab.exec0( "echo", """\ # File 2 A second file that will have been added by patching.""", linux.RedirStdout(repo / "file2.md"), ) repo.add(repo / "file2.md") repo.commit("Add file2", author="tbot Selftest <none@none>") patch_name = repo.git0("format-patch", "HEAD~1").strip() lab.exec0("mv", repo / patch_name, lab.workdir / "selftest-git.patch") tbot.log.message("Resetting repo ...") repo.reset("HEAD~1", git.ResetMode.HARD) _GIT = p._local_str() return _GIT else: return _GIT
def selftest_decorated_lab(lh: linux.Lab) -> None: lh.exec0("uname", "-a")
def __init__(self, labhost: linux.Lab, port: int) -> None: """Create a new MiniSSHMachine.""" self._port = port self._username = labhost.env("USER") super().__init__(labhost)
def check_minisshd(h: linux.Lab) -> bool: """Check if this host can run a minisshd.""" return h.test("which", "dropbear")
def testpy( lh: linux.Lab, *, build_host: typing.Optional[linux.Builder] = None, uboot_builder: typing.Optional[uboot_tc.UBootBuilder] = None, boardenv: typing.Optional[str] = None, testpy_args: typing.List[str] = [], ) -> None: """ Run U-Boot's test/py test-framework against the selected board. This testcase can be called from the command-line as ``uboot_testpy``. :param tbot.machine.linux.Builder build_host: Optional build-host where U-Boot should be built (and in this case, where test/py will run). By default, ``tbot.acquire_lab().build()`` is used. :param tbot.tc.uboot.UBootBuilder uboot_builder: Optional configuration for U-Boot checkout. By default, ``tbot.acquire_uboot().build`` is used (exactly like ``uboot_build`` does). :param str boardenv: Optional contents for the ``boardenv.py`` file. If this option is not given, ``UBootBuilder.testpy_boardenv`` is used (or nothing). :param list(str) testpy_args: Additional arguments to be passed to test/py. Can be used, for example, to limit which tests should be run (using ``testpy_args=["-k", "sf"]``). **Example**: The following additions to a board-config make it possible to call ``tbot ... -vv uboot_testpy``: .. code-block:: python from tbot.tc import uboot class DHComUBootBuilder(uboot.UBootBuilder): name = "dhcom-pdk2" defconfig = "dh_imx6_defconfig" toolchain = "imx6q" testpy_boardenv = r\"""# Config for dhcom pdk2 board # A list of sections of Flash memory to be tested. env__sf_configs = ( { # Where in SPI Flash should the test operate. 'offset': 0x140000, # This value is optional. # If present, specifies if the test can write to Flash offset # If missing, defaults to False. 'writeable': True, }, ) \""" class DHComUBoot(board.Connector, board.UBootShell): name = "dhcom-uboot" prompt = "=> " # Don't forget this! build = DHComUBootBuilder() """ with contextlib.ExitStack() as cx: if build_host is not None: bh = cx.enter_context(build_host) else: bh = cx.enter_context(lh.build()) if bh is lh: tbot.log.warning("""\ The build() method for the selected lab should not return `self` but instead `self.clone()`. Otherwise, `uboot_testpy` might not be able to run test/py in parallel to switching board power. Attempting to call build_host.clone() automatically now ...""") bh = cx.enter_context(bh.clone()) # Spawn a subshell to not mess up the parent shell's environment and PWD cx.enter_context(bh.subshell()) chan_console, chan_command = setup_testhooks( bh, cx.enter_context(bh.clone()), cx.enter_context(bh.clone())) if uboot_builder is None: builder = uboot_tc.UBootBuilder._get_selected_builder() else: builder = uboot_builder uboot_repo = uboot_tc.checkout(builder, clean=False, host=bh) # test/py wants to read U-Boot's config. Run the builder's configure # step if no `.config` is available and then also generate `autoconf.mk`. dotconfig_missing = not (uboot_repo / ".config").exists() autoconfmk_missing = not (uboot_repo / "include" / "autoconf.mk").exists() if dotconfig_missing or autoconfmk_missing: with tbot.testcase("uboot_configure"), builder.do_toolchain(bh): tbot.log.message("Configuring U-Boot checkout ...") bh.exec0("cd", uboot_repo) if dotconfig_missing: builder.do_configure(bh, uboot_repo) if autoconfmk_missing: bh.exec0("make", "include/autoconf.mk") # Initialize the board # TODO: Add a parameter to allow passing in a board b = cx.enter_context(tbot.acquire_board(lh)) # type: ignore assert isinstance(b, board.PowerControl) ub = cx.enter_context(tbot.acquire_uboot(b)) chan_uboot = ub.ch # If a boardenv was passed in, copy it to the build-host and set # a board-type to make test/py pick it up. board_type = "unknown" if boardenv is None: # If no explicit boardenv was given, maybe the builder has one. try: boardenv = getattr(builder, "testpy_boardenv") except AttributeError: pass if boardenv is not None: board_type = f"tbot-{b.name}" bt_filename = board_type.replace("-", "_") be_file = uboot_repo / "test" / "py" / f"u_boot_boardenv_{bt_filename}.py" be_file.write_text(boardenv) # Start test/py as an interactive command bh.exec0("cd", uboot_repo) chan_testpy = cx.enter_context( bh.run( "./test/py/test.py", "--build-dir", ".", "--board-type", board_type, *testpy_args, )) # We have to deal with incoming data on any of the following channels. # The comments denote what needs to be done for each channel: readfds = [ chan_console, # Send data to U-Boot chan_uboot, # Send data to chan_console (test/py) chan_command, # Powercycle the board chan_testpy, # Read data so the log-event picks it up ] while True: r, _, _ = select.select(readfds, [], []) if chan_console in r: # Send data to U-Boot data = os.read(chan_console.fileno(), 4096) os.write(chan_uboot.fileno(), data) if chan_uboot in r: # Send data to chan_console (test/py) data = os.read(chan_uboot.fileno(), 4096) os.write(chan_console.fileno(), data) if chan_command in r: # Powercycle the board msg = chan_command.read() if msg[:2] == b"RE": b.poweroff() b.poweron() else: raise Exception(f"Got unknown command {msg!r}!") if chan_testpy in r: # Read data so the log-event picks it up. If a # DeathStringException occurs here, test/py finished and we # need to properly terminate the LinuxShell.run() context. try: chan_testpy.read() except linux.CommandEndedException: chan_testpy.terminate0() break