Skip to content

Commit

Permalink
twister: Add robot framework integration
Browse files Browse the repository at this point in the history
This change adds an entrypoint for the common robot framework and a
first library to interact with the board, similar to the functionality
currently available in pytest.

To reuse the command line parsing and have a proper twister config, we
provide a robot_wrapper which serializes the configuration and reloads
it in the library.

This change addresses zephyrproject-rtos#64825, which asks for robot scripts without
renode.

Signed-off-by: Stefan Kraus <[email protected]>
  • Loading branch information
MP-StefanKraus committed Jul 1, 2024
1 parent a90f569 commit 43c031b
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#

import pickle

from robot.libraries.BuiltIn import BuiltIn
from twister_harness import DeviceAdapter, Shell
from twister_harness.device.factory import DeviceFactory
from twister_harness.twister_harness_config import TwisterHarnessConfig


class ZephyrLibrary:
ROBOT_LIBRARY_SCOPE = "SUITE"

def __init__(self):
self.device: DeviceAdapter | None = None
self.shell: Shell | None = None

self.twister_harness_config = self._get_twister_harness_config()

def _get_twister_harness_config(self) -> TwisterHarnessConfig:
all_variables = BuiltIn().get_variables()
twister_harness_config_file = all_variables.get(
"${TWISTER_HARNESS_CONFIG_FILE}", None
)
if twister_harness_config_file is None:
raise ValueError(
"TWISTER_HARNESS_CONFIG_FILE variable must be set to file with pickle"
)

with open(twister_harness_config_file, "rb") as twister_harness_file:
return pickle.load(twister_harness_file)

def get_a_device(self):
if self.device is None:
device_object = DeviceFactory.get_device_object(self.twister_harness_config)
self.device = device_object
return device_object

def run_device(self):
if self.device is None:
raise ValueError("Currently no device")

if not self.device.is_device_running():
self.device.launch()

def run_command(self, command: str) -> list[str]:
if self.device is None:
raise ValueError("Currently no device")

if self.shell is None:
self.shell = Shell(self.device)
assert self.shell.wait_for_prompt()

return self.shell.exec_command(command)

def close_device(self):
if self.device is None:
raise ValueError("Currently no device")

self.device.close()
self.device = None
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#

import argparse
import pickle

import robot
from twister_harness.argument_handler import ArgumentHandler
from twister_harness.twister_harness_config import TwisterHarnessConfig

ROBOT_TWISTER_VAR_FILE = "robot_twister_var.pickle"


def _parse_arguments():
parser = argparse.ArgumentParser(allow_abbrev=False)

handler = ArgumentHandler(parser.add_argument)
handler.add_common_arguments()

options, rest_args = parser.parse_known_args()

handler.sanitize_options(options)

return options, rest_args


def _serialize_config(twister_harness_config: TwisterHarnessConfig):
device = twister_harness_config.devices[0]

filepath = device.build_dir / ROBOT_TWISTER_VAR_FILE

with open(device.build_dir / ROBOT_TWISTER_VAR_FILE, "wb") as robot_twister_vars:
pickle.dump(twister_harness_config, robot_twister_vars)

return filepath


def main():
options, rest_args = _parse_arguments()
twister_harness_config = TwisterHarnessConfig.create(options)
variables_filepath = _serialize_config(twister_harness_config)

robot.run_cli(
["--variable", f"TWISTER_HARNESS_CONFIG_FILE:{variables_filepath}"] + rest_args
)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#

*** Settings ***
Documentation Robot testfile to check for existence of commands.
Library twister_harness.robot_framework.ZephyrLibrary

*** Test Cases ***
Command workflow test
[Documentation] Test that all commands exist.
Get a Device
Run Device
Run Command echo 'my device'
Close Device
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#

import pathlib
import sys
from unittest.mock import patch

import pytest
from twister_harness.robot_framework import robot_wrapper


def test__integration__dry_run_robotfile__no_errors():
robot_file = pathlib.Path(__file__).parent / "device_test.robot"
cli_arguments = [
"twister_harness.robot_framework.robot_wrapper",
"--quiet",
"--dryrun",
"--build-dir",
".",
"--device-type=custom",
str(robot_file),
]
with patch.object(sys, "argv", cli_arguments):
with pytest.raises(SystemExit) as excinfo:
robot_wrapper.main()

assert excinfo.value.code == 0


def test__integration__missing_parameter__raises_nonzero_systemexit():
cli_arguments = [
"twister_harness.robot_framework.robot_wrapper",
"--quiet",
"--dryrun",
"--build-dir",
".",
"--device-type=custom",
]
with patch.object(sys, "argv", cli_arguments):
with pytest.raises(SystemExit) as excinfo:
robot_wrapper.main()

assert excinfo.value.code != 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-License-Identifier: Apache-2.0
#

from unittest.mock import patch

import pytest
from twister_harness.device.binary_adapter import NativeSimulatorAdapter
from twister_harness.robot_framework.ZephyrLibrary import ZephyrLibrary
from twister_harness.twister_harness_config import DeviceConfig, TwisterHarnessConfig


@pytest.fixture
def twister_harness_config(tmp_path):
device_config = DeviceConfig(type="custom", build_dir=tmp_path)
twister_harness_config = TwisterHarnessConfig()
twister_harness_config.devices = [device_config]
yield twister_harness_config


@pytest.fixture
def zephyr_library(twister_harness_config):
with patch.object(
ZephyrLibrary,
"_get_twister_harness_config",
return_value=twister_harness_config,
):
yield ZephyrLibrary()


def test__init__correctly_initialized(zephyr_library, twister_harness_config):
assert zephyr_library.device is None
assert zephyr_library.shell is None
assert zephyr_library is not None

assert zephyr_library.twister_harness_config == twister_harness_config


def test__run_device__correctly_setup(
zephyr_library, shell_simulator_adapter: NativeSimulatorAdapter
):
zephyr_library.device = shell_simulator_adapter

zephyr_library.run_device()


def test__run_command__can_execute_commands(
zephyr_library, shell_simulator_adapter: NativeSimulatorAdapter
):
zephyr_library.device = shell_simulator_adapter

zephyr_library.run_device()
result = zephyr_library.run_command("zen")

assert result is not None
assert isinstance(result, list)

0 comments on commit 43c031b

Please sign in to comment.