Ejemplo n.º 1
0
        def wrapped(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
            with tbot.testcase(name):
                return tc(*args, **kwargs)

            # This line will only be reached when a testcase was skipped.
            # Return `None` as the placeholder return value.
            return None
Ejemplo n.º 2
0
Archivo: shell.py Proyecto: zkrx/tbot
def check_for_tool(host: linux.LinuxShell, tool: str) -> bool:
    """
    Check whether a certain tool/program is installed on a host.

    Results from previous invocations are cached.

    **Example**:

    .. code-block:: python

        if shell.check_for_tool(lh, "wget"):
            lh.exec0("wget", download_url, "-O", lh.workdir / "download.tgz")
        elif shell.check_for_tool(lh, "curl"):
            lh.exec0("curl", download_url, "-o", lh.workdir / "download.tgz")
        else:
            raise Exception("Need either 'wget' or 'curl'!")

    :param linux.LinuxShell host: The host to ceck on.
    :param str tool: Name of the binary to check for.
    :rtype: bool
    :returns: ``True`` if the tool was found and ``False`` otherwise.
    """
    if host not in _TOOL_CACHE:
        _TOOL_CACHE[host] = {}

    if tool not in _TOOL_CACHE[host]:
        with tbot.testcase("check_for_tool"):
            has_tool = host.test("which", tool)
            _TOOL_CACHE[host][tool] = has_tool

            if has_tool:
                tbot.log.message(
                    f"Host '{tbot.log.c(host.name).yellow}' has "
                    + f"'{tbot.log.c(tool).bold}' installed."
                )
            else:
                tbot.log.message(
                    f"Host '{tbot.log.c(host.name).yellow}' does "
                    + f"{tbot.log.c('not').red} have '{tbot.log.c(tool).bold}' installed."
                )

    return _TOOL_CACHE[host][tool]
Ejemplo n.º 3
0
    def _build(
        builder: "typing.Optional[UBootBuilder]" = None,
        *,
        clean: bool = True,
        repo: typing.Optional[git.GitRepository[BH]] = None,
        unpatched_repo: typing.Optional[git.GitRepository[BH]] = None,
        path: typing.Optional[linux.Path[BH]] = None,
        host: typing.Optional[BH] = None,
        lab: typing.Optional[linux.Lab] = None,
    ) -> git.GitRepository[BH]:
        """
        Build U-Boot.

        There are a few ways this testcase can be called:

        * From the commandline as ``uboot_build`` or in a testcase without
          any arguments:  tbot will use the configured build-host and builder
          config (see :class:`~tbot.tc.uboot.UBootBuilder`) to attempt building
          U-Boot.  You can use the ``clean`` parameter to specify whether the
          build should reuse existing artifacts or start from scratch.
        * Specifying just the ``lab`` parameter:  Use ``lab`` as the lab-host
          from where tbot should connect to its default build-host.
        * Specifying just the ``host`` parameter:  Build U-Boot on ``host``.
        * Just the ``path`` parameter:  Checkout U-Boot to ``path`` on ``path``'s
          associated host (which must be a build-host).
        * Only the ``unpatched_repo``:  Apply the patch step onto an already
          checked out revision before attempting the build.
        * Just the ``repo`` parameter:  Use the already checked-out revision
          that is assumed to already have necessary patches applied.

        In any case, tbot will attempt building U-Boot and if it succeeded,
        the testcase will return the git repo.  Depending on the way it was called,
        it will skip certain steps (See list above).  This can be used to build
        eg. with a pre-configured checkout or build in a bisect-run.

        You can only specify one of ``repo``, ``unpatched_repo``, ``path``, ``host``
        or ``lab``!

        :param bool clean:  Whether the U-Boot tree should be cleand of all leftovers
            from previous builds.
        :param git.GitRepository repo:  Build from existing, checkout-out revision.
        :param git.GitRepository unpatched_repo:  Build from existing, checkout-out revision,
            but also apply patches.
        :param linux.Path path:  Checkout U-Boot to ``path``.
        :param linux.BuildMachine host:  Build U-Boot on this host.
        :param linux.Lab lab:  Build U-Boot on the default build-host of this lab.
        :rtype: git.GitRepository
        :returns:  Location of the U-Boot tree containing build artifacts
        """
        # If we don't have a builder, take the one for the selected board
        if builder is None:
            builder = UBootBuilder._get_selected_builder()

        # Assert this testcase is called correctly
        argslist = (repo, unpatched_repo, path, host, lab)
        assert (
            argslist.count(None) >= len(argslist) - 1
        ), "At most one of repo, unpatched_repo, path, host, or lab can be specified!"

        with contextlib.ExitStack() as cx:
            if repo is not None:
                host = repo.host
            if unpatched_repo is not None:
                host = unpatched_repo.host
            elif path is not None:
                host = path.host
            elif host is None:
                if lab is None:
                    lab = cx.enter_context(tbot.acquire_lab())
                host = typing.cast(BH, cx.enter_context(lab.build()))

            if unpatched_repo is not None:
                repo = unpatched_repo
                builder.do_patch(repo)

            if repo is None:
                # Set host to none if we have a path
                checkout_host = host if path is None else None
                repo = checkout(builder,
                                clean=clean,
                                path=path,
                                host=checkout_host)

            with builder.do_toolchain(host):
                host.exec0("cd", repo)

                if clean:
                    tbot.log.message("Cleaning previous build ...")
                    host.exec0("make", "mrproper")
                if not (repo / ".config").exists():
                    tbot.log.message("Configuring build ...")
                    builder.do_configure(host, repo)

                with tbot.testcase("uboot_make"):
                    builder.do_build(repo.host, repo)

        assert repo is not None
        return repo
Ejemplo n.º 4
0
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