Plugin System
Sometimes, we need to cover complex testing scenarios where our System Under Test utilizes specific protocols and infrastructures to communicate with our host machine and execute LTP tests.
For this reason, Kirk provides a plugin system to recognize custom SUT
and ComChannel class implementations inside any external folder. These
classes are used to implement complex scenarios, and in the next sections, we
will see how to communicate with the plugin system.
To verify supported SUT, please run:
kirk --sut help
Note
If you want to implement a new ComChannel communication handler, please
refer to the natively supported implementations such as shell.py.
Custom System Under Test
All the channels implementations provided by kirk can be used or duplicated to
use them inside our SUT. The way we create these instances is as follows:
kirk --plugins my/plugins/folder \
--com ssh:host=192.168.0.1:id=ssh_host0 \
--sut mysut \
--run-suite syscalls
We just created a SSH channel ssh_host0 that can be used by mysut
implementation in order to setup testing.
Our SUT can now use the libkirk.com.get_channels() utility to read
available channels and get the one we need, as follows:
def setup(self, **kwargs: Dict[str, Any]) -> None:
self._ssh = next(
(c for in libkirk.com.get_channels() if c.name == "ssh_host0"),
None,
)
But remember that only one channel must be given back to kirk in order to
communicate with the System Under Test via get_channel() API.
def get_channel() -> ComChannel:
return self._ssh
Practical example
We might want to test LTP inside an embedded system on our desk via SSH. We have two scripts to run before communicating with the SUT:
install_firmware.shto install a new firmwarereboot_board.shto reboot board if it’s not responding anymore
The idea is that we install a new firmware before running tests, run tests and if system breaks/panic/timeout, we reboot it, continuing testing suite from where we left.
We can easily achieve this scenario with the following implementation:
import os
from typing import Dict, Optional
import libkirk.com
from libkirk.com import ComChannel, IOBuffer
from libkirk.errors import SUTError
from libkirk.sut import SUT
class EmbeddedSUT(SUT):
# This is needed by kirk to know what is the name of the SUT
# we are implementing
_name = "embedded"
def __init__(self) -> None:
self._ssh = None
self._shell = None
currdir = os.path.dirname(os.path.realpath(__file__))
self._install_sh = os.path.join(currdir, "install_firmware.sh")
self._reboot_sh = os.path.join(currdir, "reboot_board.sh")
def setup(self, **kwargs: Dict[str, str]) -> None:
# Here we fetch all data we need. At this point we know that kirk
# already initialized all communication channels
chan_name = kwargs.get("com", "ssh")
self._ssh = next(
(c for c in libkirk.com.get_channels() if c.name == chan_name), None
)
self._shell = next(
(c for c in libkirk.com.get_channels() if c.name == "shell"), None
)
if not self._ssh:
raise SUTError(f"Can't find channel '{chan_name}'")
@property
def config_help(self) -> Dict[str, str]:
# Parameters to setup our SUT
return {
"com": "Communication channel (default: ssh)",
}
def get_channel(self) -> ComChannel:
# Here we return our main communication channel
return self._ssh
async def start(self, iobuffer: Optional[IOBuffer] = None) -> None:
# Initialize the SUT by running commands, scripts and everything
# that can be done via our communication channels
if await self.is_running():
return
await self._shell.ensure_communicate(iobuffer=iobuffer)
ret = await self._shell.run_command(self._install_sh, iobuffer=iobuffer)
if ret["returncode"] != 0:
raise SUTError(f"{self._install_sh} failed")
await self._ssh.ensure_communicate(iobuffer=iobuffer)
async def stop(self, iobuffer: Optional[IOBuffer] = None) -> None:
# Stop any operation in our SUT. This can be requires in any moment
# during tests run
if not await self.is_running():
return
await self._ssh.stop(iobuffer=iobuffer)
async def restart(self, iobuffer: Optional[IOBuffer] = None) -> None:
# Stop any operation in our SUT and restart the system
await self.stop(iobuffer=iobuffer)
ret = await self._shell.run_command(self._reboot_sh, iobuffer=iobuffer)
if ret["returncode"] != 0:
raise SUTError(f"{self._reboot_sh} failed")
await self._shell.stop(iobuffer=iobuffer)
await self.start(iobuffer=iobuffer)
async def is_running(self) -> bool:
# Tell kirk when SUT is operating or not
return await self._ssh.active()
Let’s suppose we have a $HOME/plugins folder where we placed our
EmbeddedSUT implementation and its scripts. Then we can run syscalls
testing suite with kirk as following:
kirk --plugins $HOME/plugins \
--sut embedded \
--com ssh:host=192.168.0.1:user=root:key_file=/home/user/.ssh/id_rsa \
--run-suite syscalls