tbot.machine.board

This module contains definitions for interacting with embedded hardware. tbot models hardware in a ‘layered’ approach:

  • The underlying physical hardware is called a board. The board class defines how tbot can connect to the board’s console, how to turn power on and off, etc.

  • Ontop of the board, the software ‘layer’ is defined. This might be a U-Boot, a Linux directly, or a Linux ontop of a U-Boot. It could also be something completely custom if you implement the relevant Initializer s and Shell s.

Each of these ‘layers’ is its own machine. See the docs for:

Board Hardware

The board-machine should inherit from tbot.machine.board.Board and define the pysical access for the hardware. This means the connector will most likely be a ConsoleConnector and it might make use of the tbot.machine.board.PowerControl initializer to turn on/off power for the board.

Example:

from tbot.machine import connector, board, linux

class MyBoard(
    connector.ConsoleConnector,
    board.PowerControl,
    board.Board,
):
    # TODO
class tbot.machine.board.Board[source]

Bases: tbot.machine.shell.RawShell

Base class for board-machines.

This class does nothing special except providing the .interactive() method for directly interacting with the serial-console.

Board Initializers

class tbot.machine.board.PowerControl[source]

Bases: tbot.machine.machine.Initializer

Machine-initializer for controlling power for a hardware.

When initializing, poweron() is called and when deinitializing, poweroff() is called.

abstract poweron() → None[source]

Power-on the hardware.

If the machine is using the ConsoleConnector, you can use self.host.exec0() to run commands on the lab-host.

Example:

def poweron(self):
    self.host.exec0("power-control.sh", "on")
abstract poweroff() → None[source]

Power-off the hardware.

power_check() → bool[source]

Check if the board is already on and someone else might be using it.

Implementations of this function should raise an exception in case they detect the board to be on or return False. If the board is off and ready to be used, an implementation should return True.

Board Software

The ‘software layer’ machines for a board should not directly use a connector to connect to the boards console, but instead rely on the generic tbot.machine.board.Connector class. This allows cleanly separating the hardware and software and thus reusing the software machines with different hardware.

In practice, these ‘software machines’ should be defined like this:

from tbot.machine import board

class MyBoard(
    ...
    board.Board,
):
    # Hardware machine
    ...

class MyBoardSoftware(
   board.Connector,
   ...
):
   # Software machine
   ...

And then used like

with MyBoard(lh) as b:
   with MyBoardSoftware(b) as bs:
      bs.exec(...)

# Or, more consise
with MyBoard(lh) as b, MyBoardSoftware(b) as bs:
   bs.exec(...)

Board U-Boot

If you are using U-Boot you’ll probably want access to it from your testcases. This is done using a U-Boot machine. Like any software ‘layer’, a U-Boot machine should use the tbot.machine.board.Connector class. The shell should be a tbot.machine.board.UBootShell.

In case your U-Boot is configured for autoboot, you’ll want to use the optional UBootAutobootIntercept initializer as well.

Example:

class BoardUBoot(
    board.Connector,
    board.UBootShell,
):
    prompt = "U-Boot> "
class tbot.machine.board.UBootShell[source]

Bases: tbot.machine.shell.Shell, tbot.machine.board.uboot.UbootStartup

U-Boot shell.

The interface of this shell was designed to be close to the Linux shell design. This means that U-Boot shells also provide

  • ub.escape() - Escape args for the U-Boot shell.

  • ub.exec0() - Run command and ensure it succeeded.

  • ub.exec() - Run command and return output and return code.

  • ub.test() - Run command and return boolean whether it succeeded.

  • ub.env() - Get/Set environment variables.

  • ub.interactive() - Start an interactive session for this machine.

There is also the special ub.boot() which will boot a payload and return the machine’s channel, for use in a machine for the booted payload.

prompt = 'U-Boot> '

Prompt which was configured for U-Boot.

Commonly "U-Boot> ", "=> ", or "U-Boot# ".

Warning

Don’t forget the trailing space, if your prompt has one!

bootlog = None

Transcript of console output during boot.

escape(*args: Union[str, tbot.machine.linux.special.Special]) → str[source]

Escape a string so it can be used safely on the U-Boot command-line.

exec(*args: Union[str, tbot.machine.linux.special.Special]) → Tuple[int, str][source]

Run a command in U-Boot.

Example:

retcode, output = ub.exec("version")
assert retcode == 0
Return type

tuple(int, str)

Returns

A tuple with the return code of the command and its console output. The output will also contain a trailing newline in most cases.

exec0(*args: Union[str, tbot.machine.linux.special.Special]) → str[source]

Run a command and assert its return code to be 0.

Example:

output = ub.exec0("version")

# This will raise an exception!
ub.exec0("false")
Return type

str

Returns

The command’s console output. It will also contain a trailing newline in most cases.

test(*args: Union[str, tbot.machine.linux.special.Special]) → bool[source]

Run a command and return a boolean value whether it succeeded.

Example:

if ub.test("true"):
    tbot.log.message("Is correct")
Return type

bool

Returns

Boolean representation of commands success. True if return code was 0, False otherwise.

env(var: str, value: Union[str, tbot.machine.linux.special.Special, None] = None) → str[source]

Get or set an environment variable.

Example:

# Get the value of a var
value = ub.env("bootcmd")

# Set the value of a var
lnx.env("bootargs", "loglevel=7")
Parameters
  • var (str) – Environment variable name.

  • value (str) – Optional value to set the variable to.

Return type

str

Returns

Current (new) value of the environment variable.

boot(*args: Union[str, tbot.machine.linux.special.Special]) → tbot.machine.channel.channel.Channel[source]

Boot a payload from U-Boot.

This method will run the given command and expects it to start booting a payload. ub.boot() will then return the channel so a new machine can be built ontop of it for the booted payload.

Example:

ub.env("bootargs", "loglevel=7")
ch = ub.boot("bootm", "0x10000000")
Return type

tbot.machine.channel.Channel

interactive() → None[source]

Start an interactive session on this machine.

This method will connect tbot’s stdio to the machine’s channel so you can interactively run commands. This method is used by the interactive_uboot testcase.

class tbot.machine.board.UBootAutobootIntercept[source]

Bases: tbot.machine.machine.Initializer, tbot.machine.board.uboot.UbootStartup

Machine-initializer to intercept U-Boot autobooting.

The default settings for this class should work for most cases, but if a custom autoboot prompt was configured, or a special key sequence is necessary, you will have to adjust this here.

Example:

class MyUBoot(
    board.Connector,
    board.UBootAutobootIntercept,
    board.UBootShell,
):
    autoboot_prompt = re.compile(b"Press DEL 4 times.{0,100}", re.DOTALL)
    autoboot_keys = "\x7f\x7f\x7f\x7f"
autoboot_prompt = re.compile(b'autoboot:\\s{0,5}\\d{0,3}\\s{0,3}.{0,80}')

Autoboot prompt to wait for.

autoboot_keys = '\r'

Keys to press as soon as autoboot prompt is detected.

Board Linux

The board’s Linux machine can be configured in different ways, depending on your setup and needs.

  • If you do not need any bootloader interaction, you can define it in a way that goes straight from power-on to waiting for Linux. This is done very similarly to the U-Boot machine above, by using the board.Connector.

  • If you do need bootloader interaction (because of manual commands to boot Linux for example), you should instead use the much more powerful board.LinuxUbootConnector. This class allows to boot Linux from U-Boot, either by passing it an existing U-Boot machine, or by automatically waiting for U-Boot first and then booting Linux.

In code, the two options look like this:

Example for a standalone Linux (no bootloader interaction):

from tbot.machine import board, linux

class StandaloneLinux(
    board.Connector,
    board.LinuxBootLogin,
    linux.Bash,
):
    # No config for the connector needed

    # LinuxBootLogin handles waiting for Linux to boot & logging in
    username = "root"
    password = "hunter2"

Example for a Linux booting from U-Boot:

from tbot.machine import board, linux

class MyUBoot(board.Connector, board.UBootShell):
    ...

class LinuxFromUBoot(
    board.LinuxUbootConnector,
    board.LinuxBootLogin,
    linux.Bash,
):
    # Configuration for LinuxUbootConnector
    uboot = MyUBoot  # <- Our UBoot machine

    def do_boot(ub):  # <- Procedure to boot Linux
       ub.env("autoload", "false")
       ub.exec0("dhcp")
       return ub.boot("run", "nfsboot")

    # LinuxBootLogin handles waiting for Linux to boot & logging in
    username = "root"
    password = "hunter2"

With both options, you’ll use the LinuxBootLogin initializer to handle the boot and login.

class tbot.machine.board.LinuxUbootConnector(b: Union[tbot.machine.board.board.Board, tbot.machine.board.uboot.UBootShell])[source]

Bases: tbot.machine.connector.connector.Connector, tbot.machine.board.linux.LinuxBootLogin

Connector for booting Linux from U-Boot.

This connector can either boot from a Board instance or from a UBootShell instance. If booting directly from the board, it will first initialize a U-Boot machine and then use it to kick off the boot to Linux. See above for an example.

abstract property uboot

U-Boot machine to use when booting directly from a Board instance.

do_boot(ub: tbot.machine.board.uboot.UBootShell) → tbot.machine.channel.channel.Channel[source]

Boot procedure.

An implementation of this method should use the U-Boot machine given as ub to kick off the Linux boot. It should return the channel to the now booting Linux. This will in almost all cases be archieved by using the tbot.machine.board.UBootShell.boot() method.

Example:

from tbot.machine import board, linux

class LinuxFromUBoot(
    board.LinuxUbootConnector,
    board.LinuxBootLogin,
    linux.Bash,
):
    uboot = MyUBoot  # <- Our UBoot machine

    def do_boot(ub):  # <- Procedure to boot Linux
       # Any logic necessary to prepare for boot
       ub.env("autoload", "false")
       ub.exec0("dhcp")

       # Return the channel using ub.boot()
       return ub.boot("run", "nfsboot")

    ...
clone() → Self[source]

This machine cannot be cloned.

class tbot.machine.board.LinuxBootLogin[source]

Bases: tbot.machine.machine.Initializer, tbot.machine.board.linux.LinuxBoot

Machine Initializer to wait for linux boot-up and automatically login.

Use this initializer whenever you have a serial-console for a Linux system.

Example:

from tbot.machine import board, linux

class StandaloneLinux(
    board.Connector,
    board.LinuxBootLogin,
    linux.Bash,
):
    # board.LinuxBootLogin config:
    username = "root"
    password = "hunter2"
login_prompt = 'login: '

Prompt that indicates tbot should send the username.

login_delay = 0

The delay between first occurence of login_prompt and actual login.

This delay might be necessary if your system clutters the login prompt with log-messages during the first few seconds after boot.

bootlog = None

Log of kernel-messages which were output during boot.

abstract property username

Username to login as.

abstract property password

Password to login with. Set to None if no password is needed.