scripts: pytest: add Shell helper unit tests

Add unit tests dedicated for Shell helper sample class.
Add shell_simulator_script.py script to simulate shell application
behavior.

Signed-off-by: Piotr Golyzniak <piotr.golyzniak@nordicsemi.no>
This commit is contained in:
Piotr Golyzniak 2023-08-07 11:57:54 +02:00 committed by Anas Nashif
commit e326015cda
8 changed files with 138 additions and 52 deletions

View file

@ -6,14 +6,18 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
from typing import Generator
import pytest import pytest
from twister_harness.device.binary_adapter import NativeSimulatorAdapter
from twister_harness.twister_harness_config import DeviceConfig
@pytest.fixture @pytest.fixture
def resources(request: pytest.FixtureRequest) -> Path: def resources() -> Path:
"""Return path to `data` folder""" """Return path to `resources` folder"""
return Path(request.module.__file__).parent.joinpath('data') return Path(__file__).parent.joinpath('resources')
@pytest.fixture @pytest.fixture
@ -30,3 +34,24 @@ def twister_harness(zephyr_base) -> str:
"""Retrun path to pytest-twister-harness src directory""" """Retrun path to pytest-twister-harness src directory"""
pytest_twister_harness_path = str(Path(zephyr_base) / 'scripts' / 'pylib' / 'pytest-twister-harness' / 'src') pytest_twister_harness_path = str(Path(zephyr_base) / 'scripts' / 'pylib' / 'pytest-twister-harness' / 'src')
return pytest_twister_harness_path return pytest_twister_harness_path
@pytest.fixture
def shell_simulator_path(resources: Path) -> str:
return str(resources / 'shell_simulator.py')
@pytest.fixture
def shell_simulator_adapter(
tmp_path: Path, shell_simulator_path: str
) -> Generator[NativeSimulatorAdapter, None, None]:
build_dir = tmp_path / 'build_dir'
os.mkdir(build_dir)
device = NativeSimulatorAdapter(DeviceConfig(build_dir=build_dir, type='native', base_timeout=5.0))
try:
device.command = ['python3', shell_simulator_path]
device.launch()
yield device
finally:
device.write(b'quit\n')
device.close()

View file

@ -183,6 +183,18 @@ def test_if_binary_adapter_is_able_to_read_leftovers_after_disconnect_or_close(
assert len(device.readlines()) > 0 assert len(device.readlines()) > 0
def test_if_binary_adapter_properly_send_data_to_subprocess(
shell_simulator_adapter: NativeSimulatorAdapter
) -> None:
"""Run shell_simulator.py program, send "zen" command and verify output."""
device = shell_simulator_adapter
time.sleep(0.1)
device.write(b'zen\n')
time.sleep(1)
lines = device.readlines_until(regex='Namespaces are one honking great idea')
assert 'The Zen of Python, by Tim Peters' in lines
def test_if_native_binary_adapter_get_command_returns_proper_string(device: NativeSimulatorAdapter) -> None: def test_if_native_binary_adapter_get_command_returns_proper_string(device: NativeSimulatorAdapter) -> None:
device.generate_command() device.generate_command()
assert isinstance(device.command, list) assert isinstance(device.command, list)

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
import os import os
import time
from pathlib import Path from pathlib import Path
from unittest import mock from unittest import mock
@ -198,3 +199,21 @@ def test_if_hardware_adapter_uses_serial_pty(
device.disconnect() device.disconnect()
assert not device._serial_pty_proc assert not device._serial_pty_proc
def test_if_hardware_adapter_properly_send_data_to_subprocess(
device: HardwareAdapter, shell_simulator_path: str
) -> None:
"""
Run shell_simulator.py under serial_pty, send "zen" command and verify
output. Flashing command is mocked by "dummy" echo command.
"""
device.command = ['echo', 'TEST'] # only to mock flashing command
device.device_config.serial_pty = f'python3 {shell_simulator_path}'
device.launch()
time.sleep(0.1)
device.write(b'zen\n')
time.sleep(1)
lines = device.readlines_until(regex='Namespaces are one honking great idea')
assert 'The Zen of Python, by Tim Peters' in lines
device.write(b'quit\n')

View file

@ -0,0 +1,14 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
from twister_harness.device.binary_adapter import NativeSimulatorAdapter
from twister_harness.helpers.shell import Shell
def test_if_shell_helper_properly_send_command(shell_simulator_adapter: NativeSimulatorAdapter) -> None:
"""Run shell_simulator.py program, send "zen" command via shell helper and verify output."""
shell = Shell(shell_simulator_adapter, timeout=5.0)
assert shell.wait_for_prompt()
lines = shell.exec_command('zen')
assert 'The Zen of Python, by Tim Peters' in lines

View file

@ -9,29 +9,7 @@ import threading
import time import time
from argparse import ArgumentParser from argparse import ArgumentParser
content = """ from zen_of_python import zen_of_python
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
"""
class FifoFile: class FifoFile:
@ -90,7 +68,7 @@ def main():
logger.info('Start') logger.info('Start')
with FifoFile(write_path, 'wb') as wf, FifoFile(read_path, 'rb'): with FifoFile(write_path, 'wb') as wf, FifoFile(read_path, 'rb'):
for line in content.split('\n'): for line in zen_of_python:
wf.write(f'{line}\n'.encode('utf-8')) wf.write(f'{line}\n'.encode('utf-8'))
time.sleep(1) # give a moment for external programs to collect all outputs time.sleep(1) # give a moment for external programs to collect all outputs
return 0 return 0

View file

@ -10,29 +10,7 @@ import sys
import time import time
from argparse import ArgumentParser from argparse import ArgumentParser
s = """ from zen_of_python import zen_of_python
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
"""
def main() -> int: def main() -> int:
@ -50,12 +28,12 @@ def main() -> int:
if args.long_sleep: if args.long_sleep:
# prints data and wait for certain time # prints data and wait for certain time
for line in s.split('\n'): for line in zen_of_python:
print(line, flush=True) print(line, flush=True)
time.sleep(args.sleep) time.sleep(args.sleep)
else: else:
# prints lines with delay # prints lines with delay
for line in s.split('\n'): for line in zen_of_python:
print(line, flush=True) print(line, flush=True)
time.sleep(args.sleep) time.sleep(args.sleep)

View file

@ -0,0 +1,31 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
"""
Simple shell simulator.
"""
import sys
from zen_of_python import zen_of_python
PROMPT = 'uart:~$ '
def main() -> int:
print('Start shell simulator', flush=True)
print(PROMPT, end='', flush=True)
for line in sys.stdin:
line = line.strip()
print(line, flush=True)
if line == 'quit':
break
elif line == 'zen':
for zen_line in zen_of_python:
print(zen_line, flush=True)
print(PROMPT, end='', flush=True)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,29 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
zen_of_python: list[str] = """
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
""".split('\n')