blob: e2a51fc2a09d1af33d4acdff397a7edf7490d8a1 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2023 Google LLC
#
# SPDX-License-Identifier: Apache-2.0
"""
Tests for runner.py classes
"""
import errno
import mock
import os
import pathlib
import pytest
import queue
import re
import subprocess
import sys
import yaml
from contextlib import nullcontext
from elftools.elf.sections import SymbolTableSection
from typing import List
ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister"))
from twisterlib.error import BuildError
from twisterlib.harness import Pytest
from twisterlib.runner import (
CMake,
ExecutionCounter,
FilterBuilder,
ProjectBuilder,
TwisterRunner
)
@pytest.fixture
def mocked_instance(tmp_path):
instance = mock.Mock()
testsuite = mock.Mock()
testsuite.source_dir: str = ''
instance.testsuite = testsuite
platform = mock.Mock()
platform.binaries: List[str] = []
instance.platform = platform
build_dir = tmp_path / 'build_dir'
os.makedirs(build_dir)
instance.build_dir: str = str(build_dir)
return instance
@pytest.fixture
def mocked_env():
env = mock.Mock()
options = mock.Mock()
env.options = options
return env
@pytest.fixture
def mocked_jobserver():
jobserver = mock.Mock()
return jobserver
@pytest.fixture
def project_builder(mocked_instance, mocked_env, mocked_jobserver) -> ProjectBuilder:
project_builder = ProjectBuilder(mocked_instance, mocked_env, mocked_jobserver)
return project_builder
@pytest.fixture
def runners(project_builder: ProjectBuilder) -> dict:
"""
Create runners.yaml file in build_dir/zephyr directory and return file
content as dict.
"""
build_dir_zephyr_path = os.path.join(project_builder.instance.build_dir, 'zephyr')
os.makedirs(build_dir_zephyr_path)
runners_file_path = os.path.join(build_dir_zephyr_path, 'runners.yaml')
runners_content: dict = {
'config': {
'elf_file': 'zephyr.elf',
'hex_file': os.path.join(build_dir_zephyr_path, 'zephyr.elf'),
'bin_file': 'zephyr.bin',
}
}
with open(runners_file_path, 'w') as file:
yaml.dump(runners_content, file)
return runners_content
@mock.patch("os.path.exists")
def test_projectbuilder_cmake_assemble_args_single(m):
# Causes the additional_overlay_path to be appended
m.return_value = True
class MockHandler:
pass
handler = MockHandler()
handler.args = ["handler_arg1", "handler_arg2"]
handler.ready = True
assert(ProjectBuilder.cmake_assemble_args(
["basearg1", "CONFIG_t=\"test\"", "SNIPPET_t=\"test\""],
handler,
["a.conf;b.conf", "c.conf"],
["extra_overlay.conf"],
["x.overlay;y.overlay", "z.overlay"],
["cmake1=foo", "cmake2=bar"],
"/builddir/",
) == [
"-DCONFIG_t=\"test\"",
"-Dcmake1=foo", "-Dcmake2=bar",
"-Dbasearg1", "-DSNIPPET_t=test",
"-Dhandler_arg1", "-Dhandler_arg2",
"-DCONF_FILE=a.conf;b.conf;c.conf",
"-DDTC_OVERLAY_FILE=x.overlay;y.overlay;z.overlay",
"-DOVERLAY_CONFIG=extra_overlay.conf "
"/builddir/twister/testsuite_extra.conf",
])
def test_if_default_binaries_are_taken_properly(project_builder: ProjectBuilder):
default_binaries = [
os.path.join('zephyr', 'zephyr.hex'),
os.path.join('zephyr', 'zephyr.bin'),
os.path.join('zephyr', 'zephyr.elf'),
os.path.join('zephyr', 'zephyr.exe'),
]
project_builder.testsuite.sysbuild = False
binaries = project_builder._get_binaries()
assert sorted(binaries) == sorted(default_binaries)
def test_if_binaries_from_platform_are_taken_properly(project_builder: ProjectBuilder):
platform_binaries = ['spi_image.bin']
project_builder.platform.binaries = platform_binaries
project_builder.testsuite.sysbuild = False
platform_binaries_expected = [os.path.join('zephyr', bin) for bin in platform_binaries]
binaries = project_builder._get_binaries()
assert sorted(binaries) == sorted(platform_binaries_expected)
def test_if_binaries_from_runners_are_taken_properly(runners, project_builder: ProjectBuilder):
runners_binaries = list(runners['config'].values())
runners_binaries_expected = [bin if os.path.isabs(bin) else os.path.join('zephyr', bin) for bin in runners_binaries]
binaries = project_builder._get_binaries_from_runners()
assert sorted(binaries) == sorted(runners_binaries_expected)
def test_if_runners_file_is_sanitized_properly(runners, project_builder: ProjectBuilder):
runners_file_path = os.path.join(project_builder.instance.build_dir, 'zephyr', 'runners.yaml')
with open(runners_file_path, 'r') as file:
unsanitized_runners_content = yaml.safe_load(file)
unsanitized_runners_binaries = list(unsanitized_runners_content['config'].values())
abs_paths = [bin for bin in unsanitized_runners_binaries if os.path.isabs(bin)]
assert len(abs_paths) > 0
project_builder._sanitize_runners_file()
with open(runners_file_path, 'r') as file:
sanitized_runners_content = yaml.safe_load(file)
sanitized_runners_binaries = list(sanitized_runners_content['config'].values())
abs_paths = [bin for bin in sanitized_runners_binaries if os.path.isabs(bin)]
assert len(abs_paths) == 0
def test_if_zephyr_base_is_sanitized_properly(project_builder: ProjectBuilder):
sanitized_path_expected = os.path.join('sanitized', 'path')
path_to_sanitize = os.path.join(os.path.realpath(ZEPHYR_BASE), sanitized_path_expected)
cmakecache_file_path = os.path.join(project_builder.instance.build_dir, 'CMakeCache.txt')
with open(cmakecache_file_path, 'w') as file:
file.write(path_to_sanitize)
project_builder._sanitize_zephyr_base_from_files()
with open(cmakecache_file_path, 'r') as file:
sanitized_path = file.read()
assert sanitized_path == sanitized_path_expected
def test_executioncounter(capfd):
ec = ExecutionCounter(total=12)
ec.cases = 25
ec.skipped_cases = 6
ec.error = 2
ec.iteration = 2
ec.done = 9
ec.passed = 6
ec.skipped_configs = 3
ec.skipped_runtime = 1
ec.skipped_filter = 2
ec.failed = 1
ec.summary()
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert (
f'--------------------------------\n'
f'Total test suites: 12\n'
f'Total test cases: 25\n'
f'Executed test cases: 19\n'
f'Skipped test cases: 6\n'
f'Completed test suites: 9\n'
f'Passing test suites: 6\n'
f'Failing test suites: 1\n'
f'Skipped test suites: 3\n'
f'Skipped test suites (runtime): 1\n'
f'Skipped test suites (filter): 2\n'
f'Errors: 2\n'
f'--------------------------------'
) in out
assert ec.cases == 25
assert ec.skipped_cases == 6
assert ec.error == 2
assert ec.iteration == 2
assert ec.done == 9
assert ec.passed == 6
assert ec.skipped_configs == 3
assert ec.skipped_runtime == 1
assert ec.skipped_filter == 2
assert ec.failed == 1
def test_cmake_parse_generated(mocked_jobserver):
testsuite_mock = mock.Mock()
platform_mock = mock.Mock()
source_dir = os.path.join('source', 'dir')
build_dir = os.path.join('build', 'dir')
cmake = CMake(testsuite_mock, platform_mock, source_dir, build_dir,
mocked_jobserver)
result = cmake.parse_generated()
assert cmake.defconfig == {}
assert result == {}
TESTDATA_1_1 = [
('linux'),
('nt')
]
TESTDATA_1_2 = [
(0, False, 'dummy out',
True, True, 'passed', None, False, True),
(0, True, '',
False, False, 'passed', None, False, False),
(1, True, 'ERROR: region `FLASH\' overflowed by 123 MB',
True, True, 'skipped', 'FLASH overflow', True, False),
(1, True, 'Error: Image size (99 B) + trailer (1 B) exceeds requested size',
True, True, 'skipped', 'imgtool overflow', True, False),
(1, True, 'mock.ANY',
True, True, 'error', 'Build failure', False, False)
]
@pytest.mark.parametrize(
'return_code, is_instance_run, p_out, expect_returncode,' \
' expect_writes, expected_status, expected_reason,' \
' expected_change_skip, expected_add_missing',
TESTDATA_1_2,
ids=['no error, no instance run', 'no error, instance run',
'error - region overflow', 'error - image size exceed', 'error']
)
@pytest.mark.parametrize('sys_platform', TESTDATA_1_1)
def test_cmake_run_build(
sys_platform,
return_code,
is_instance_run,
p_out,
expect_returncode,
expect_writes,
expected_status,
expected_reason,
expected_change_skip,
expected_add_missing
):
process_mock = mock.Mock(
returncode=return_code,
communicate=mock.Mock(
return_value=(p_out.encode(sys.getdefaultencoding()), None)
)
)
def mock_popen(*args, **kwargs):
return process_mock
testsuite_mock = mock.Mock()
platform_mock = mock.Mock()
platform_mock.name = '<platform name>'
source_dir = os.path.join('source', 'dir')
build_dir = os.path.join('build', 'dir')
jobserver_mock = mock.Mock(
popen=mock.Mock(side_effect=mock_popen)
)
instance_mock = mock.Mock(add_missing_case_status=mock.Mock())
instance_mock.build_time = 0
instance_mock.run = is_instance_run
instance_mock.status = None
instance_mock.reason = None
cmake = CMake(testsuite_mock, platform_mock, source_dir, build_dir,
jobserver_mock)
cmake.cwd = os.path.join('dummy', 'working', 'dir')
cmake.instance = instance_mock
cmake.options = mock.Mock()
cmake.options.overflow_as_errors = False
cmake_path = os.path.join('dummy', 'cmake')
popen_mock = mock.Mock(side_effect=mock_popen)
change_mock = mock.Mock()
with mock.patch('sys.platform', sys_platform), \
mock.patch('shutil.which', return_value=cmake_path), \
mock.patch('twisterlib.runner.change_skip_to_error_if_integration',
change_mock), \
mock.patch('builtins.open', mock.mock_open()), \
mock.patch('subprocess.Popen', popen_mock):
result = cmake.run_build(args=['arg1', 'arg2'])
expected_results = {}
if expect_returncode:
expected_results['returncode'] = return_code
if expected_results == {}:
expected_results = None
assert expected_results == result
popen_caller = cmake.jobserver.popen if sys_platform == 'linux' else \
popen_mock
popen_caller.assert_called_once_with(
[os.path.join('dummy', 'cmake'), 'arg1', 'arg2'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=os.path.join('dummy', 'working', 'dir')
)
assert cmake.instance.status == expected_status
assert cmake.instance.reason == expected_reason
if expected_change_skip:
change_mock.assert_called_once()
if expected_add_missing:
cmake.instance.add_missing_case_status.assert_called_once_with(
'skipped', 'Test was built only'
)
TESTDATA_2_1 = [
('linux'),
('nt')
]
TESTDATA_2_2 = [
(True, ['dummy_stage_1', 'ds2'],
0, False, '',
True, True, False,
None, None,
[os.path.join('dummy', 'cmake'),
'-B' + os.path.join('build', 'dir'), '-DTC_RUNID=1',
'-DSB_CONFIG_COMPILER_WARNINGS_AS_ERRORS=y',
'-DEXTRA_GEN_DEFINES_ARGS=--edtlib-Werror', '-Gdummy_generator',
'-S' + os.path.join('source', 'dir'),
'arg1', 'arg2',
'-DBOARD=<platform name>',
'-DSNIPPET=dummy snippet 1;ds2',
'-DMODULES=dummy_stage_1,ds2',
'-Pzephyr_base/cmake/package_helper.cmake']),
(False, [],
1, True, 'ERROR: region `FLASH\' overflowed by 123 MB',
True, False, True,
'error', 'Cmake build failure',
[os.path.join('dummy', 'cmake'),
'-B' + os.path.join('build', 'dir'), '-DTC_RUNID=1',
'-DSB_CONFIG_COMPILER_WARNINGS_AS_ERRORS=n',
'-DEXTRA_GEN_DEFINES_ARGS=', '-Gdummy_generator',
'-Szephyr_base/share/sysbuild',
'-DAPP_DIR=' + os.path.join('source', 'dir'),
'arg1', 'arg2',
'-DBOARD=<platform name>',
'-DSNIPPET=dummy snippet 1;ds2']),
]
@pytest.mark.parametrize(
'error_warns, f_stages,' \
' return_code, is_instance_run, p_out, expect_returncode,' \
' expect_filter, expect_writes, expected_status, expected_reason,' \
' expected_cmd',
TESTDATA_2_2,
ids=['filter_stages with success', 'no stages with error']
)
@pytest.mark.parametrize('sys_platform', TESTDATA_2_1)
def test_cmake_run_cmake(
sys_platform,
error_warns,
f_stages,
return_code,
is_instance_run,
p_out,
expect_returncode,
expect_filter,
expect_writes,
expected_status,
expected_reason,
expected_cmd
):
process_mock = mock.Mock(
returncode=return_code,
communicate=mock.Mock(
return_value=(p_out.encode(sys.getdefaultencoding()), None)
)
)
def mock_popen(*args, **kwargs):
return process_mock
testsuite_mock = mock.Mock()
testsuite_mock.sysbuild = True
platform_mock = mock.Mock()
platform_mock.name = '<platform name>'
source_dir = os.path.join('source', 'dir')
build_dir = os.path.join('build', 'dir')
jobserver_mock = mock.Mock(
popen=mock.Mock(side_effect=mock_popen)
)
instance_mock = mock.Mock(add_missing_case_status=mock.Mock())
instance_mock.run = is_instance_run
instance_mock.run_id = 1
instance_mock.build_time = 0
instance_mock.status = None
instance_mock.reason = None
instance_mock.testsuite = mock.Mock()
instance_mock.testsuite.required_snippets = ['dummy snippet 1', 'ds2']
instance_mock.testcases = [mock.Mock(), mock.Mock()]
instance_mock.testcases[0].status = None
instance_mock.testcases[1].status = None
cmake = CMake(testsuite_mock, platform_mock, source_dir, build_dir,
jobserver_mock)
cmake.cwd = os.path.join('dummy', 'working', 'dir')
cmake.instance = instance_mock
cmake.options = mock.Mock()
cmake.options.disable_warnings_as_errors = not error_warns
cmake.options.overflow_as_errors = False
cmake.env = mock.Mock()
cmake.env.generator = 'dummy_generator'
cmake_path = os.path.join('dummy', 'cmake')
popen_mock = mock.Mock(side_effect=mock_popen)
change_mock = mock.Mock()
with mock.patch('sys.platform', sys_platform), \
mock.patch('shutil.which', return_value=cmake_path), \
mock.patch('twisterlib.runner.change_skip_to_error_if_integration',
change_mock), \
mock.patch('twisterlib.runner.canonical_zephyr_base',
'zephyr_base'), \
mock.patch('builtins.open', mock.mock_open()), \
mock.patch('subprocess.Popen', popen_mock):
result = cmake.run_cmake(args=['arg1', 'arg2'], filter_stages=f_stages)
expected_results = {}
if expect_returncode:
expected_results['returncode'] = return_code
if expect_filter:
expected_results['filter'] = {}
if expected_results == {}:
expected_results = None
assert expected_results == result
popen_caller = cmake.jobserver.popen if sys_platform == 'linux' else \
popen_mock
popen_caller.assert_called_once_with(
expected_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=os.path.join('dummy', 'working', 'dir')
)
assert cmake.instance.status == expected_status
assert cmake.instance.reason == expected_reason
for tc in cmake.instance.testcases:
assert tc.status == cmake.instance.status
TESTDATA_3 = [
('unit_testing', [], False, True, None, True, None, True,
None, None, {}, {}, None, None, [], {}),
(
'other', [], True,
True, ['dummy', 'west', 'options'], True,
None, True,
os.path.join('domain', 'build', 'dir', 'zephyr', '.config'),
os.path.join('domain', 'build', 'dir', 'zephyr', 'edt.pickle'),
{'CONFIG_FOO': 'no'},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'CONFIG_FOO': 'no', 'dummy cache elem': 1},
b'dummy edt pickle contents',
[f'Loaded sysbuild domain data from' \
f' {os.path.join("build", "dir", "domains.yaml")}'],
{os.path.join('other', 'dummy.testsuite.name'): True}
),
(
'other', ['kconfig'], True,
True, ['dummy', 'west', 'options'], True,
'Dummy parse results', True,
os.path.join('build', 'dir', 'zephyr', '.config'),
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{'CONFIG_FOO': 'no'},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'CONFIG_FOO': 'no', 'dummy cache elem': 1},
b'dummy edt pickle contents',
[],
{os.path.join('other', 'dummy.testsuite.name'): False}
),
(
'other', ['other'], False,
False, None, True,
'Dummy parse results', True,
None,
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{},
{},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True},
b'dummy edt pickle contents',
[],
{os.path.join('other', 'dummy.testsuite.name'): False}
),
(
'other', ['other'], True,
False, None, True,
'Dummy parse results', True,
None,
None,
{},
{},
{},
None,
['Sysbuild test will be skipped. West must be used for flashing.'],
{os.path.join('other', 'dummy.testsuite.name'): True}
),
(
'other', ['other'], True,
False, ['--erase'], True,
'Dummy parse results', True,
None,
None,
{},
{},
None,
b'dummy edt pickle contents',
['Sysbuild test will be skipped,' \
' --erase is not supported with --west-flash'],
{os.path.join('other', 'dummy.testsuite.name'): True}
),
(
'other', ['other'], False,
True, None, False,
'Dummy parse results', True,
None,
None,
{},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1},
None,
[],
{os.path.join('other', 'dummy.testsuite.name'): False}
),
(
'other', ['other'], False,
True, None, True,
'Dummy parse results', True,
None,
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1},
b'dummy edt pickle contents',
[],
{os.path.join('other', 'dummy.testsuite.name'): False}
),
(
'other', ['other'], False,
True, None, True,
None, True,
None,
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1},
b'dummy edt pickle contents',
[],
{os.path.join('other', 'dummy.testsuite.name'): True}
),
(
'other', ['other'], False,
True, None, True,
'Dummy parse results', False,
None,
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1},
b'dummy edt pickle contents',
[],
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1}
),
(
'other', ['other'], False,
True, None, True,
SyntaxError, True,
None,
os.path.join('build', 'dir', 'zephyr', 'edt.pickle'),
{},
{'dummy cache elem': 1},
{'ARCH': 'dummy arch', 'PLATFORM': 'other', 'env_dummy': True,
'dummy cache elem': 1},
b'dummy edt pickle contents',
['Failed processing testsuite.yaml'],
SyntaxError
),
]
@pytest.mark.parametrize(
'platform_name, filter_stages, sysbuild,' \
' do_find_cache, west_flash_options, edt_exists,' \
' parse_results, testsuite_filter,' \
' expected_defconfig_path, expected_edt_pickle_path,' \
' expected_defconfig, expected_cmakecache, expected_filter_data,' \
' expected_edt,' \
' expected_logs, expected_return',
TESTDATA_3,
ids=['unit testing', 'domain', 'kconfig', 'no cache',
'no west options', 'erase west flash option', 'no edt',
'parse result', 'no parse result', 'no testsuite filter', 'parse err']
)
def test_filterbuilder_parse_generated(
caplog,
mocked_jobserver,
platform_name,
filter_stages,
sysbuild,
do_find_cache,
west_flash_options,
edt_exists,
parse_results,
testsuite_filter,
expected_defconfig_path,
expected_edt_pickle_path,
expected_defconfig,
expected_cmakecache,
expected_filter_data,
expected_edt,
expected_logs,
expected_return
):
def mock_domains_from_file(*args, **kwargs):
dom = mock.Mock()
dom.build_dir = os.path.join('domain', 'build', 'dir')
res = mock.Mock(get_default_domain=mock.Mock(return_value=dom))
return res
def mock_cmakecache_from_file(*args, **kwargs):
if not do_find_cache:
raise FileNotFoundError(errno.ENOENT, 'Cache not found')
cache_elem = mock.Mock()
cache_elem.name = 'dummy cache elem'
cache_elem.value = 1
cache = [cache_elem]
return cache
def mock_open(filepath, type, *args, **kwargs):
if filepath == expected_defconfig_path:
rd = 'I am not a proper line\n' \
'CONFIG_FOO="no"'
elif filepath == expected_edt_pickle_path:
rd = b'dummy edt pickle contents'
else:
raise FileNotFoundError(errno.ENOENT,
f'File {filepath} not mocked.')
return mock.mock_open(read_data=rd)()
def mock_parser(filter, filter_data, edt):
assert filter_data == expected_filter_data
if isinstance(parse_results, type) and \
issubclass(parse_results, Exception):
raise parse_results
return parse_results
def mock_pickle(datafile):
assert datafile.read() == expected_edt
return mock.Mock()
testsuite_mock = mock.Mock()
testsuite_mock.sysbuild = 'sysbuild' if sysbuild else None
testsuite_mock.name = 'dummy.testsuite.name'
testsuite_mock.filter = testsuite_filter
platform_mock = mock.Mock()
platform_mock.name = platform_name
platform_mock.arch = 'dummy arch'
source_dir = os.path.join('source', 'dir')
build_dir = os.path.join('build', 'dir')
fb = FilterBuilder(testsuite_mock, platform_mock, source_dir, build_dir,
mocked_jobserver)
instance_mock = mock.Mock()
fb.instance = instance_mock
fb.env = mock.Mock()
fb.env.options = mock.Mock()
fb.env.options.west_flash = west_flash_options
fb.env.options.device_testing = True
environ_mock = {'env_dummy': True}
with mock.patch('twisterlib.runner.Domains.from_file',
mock_domains_from_file), \
mock.patch('twisterlib.runner.CMakeCache.from_file',
mock_cmakecache_from_file), \
mock.patch('builtins.open', mock_open), \
mock.patch('expr_parser.parse', mock_parser), \
mock.patch('pickle.load', mock_pickle), \
mock.patch('os.path.exists', return_value=edt_exists), \
mock.patch('os.environ', environ_mock), \
pytest.raises(expected_return) if \
isinstance(parse_results, type) and \
issubclass(parse_results, Exception) else nullcontext() as err:
result = fb.parse_generated(filter_stages)
if err:
assert True
return
assert all([log in caplog.text for log in expected_logs])
assert fb.defconfig == expected_defconfig
assert fb.cmake_cache == expected_cmakecache
assert result == expected_return
TESTDATA_4 = [
(False, False, [f"see: {os.path.join('dummy', 'path', 'dummy_file.log')}"]),
(True, False, [os.path.join('dummy', 'path', 'dummy_file.log'),
'file contents',
os.path.join('dummy', 'path', 'dummy_file.log')]),
(True, True, [os.path.join('dummy', 'path', 'dummy_file.log'),
'Unable to read log data ([Errno 2] ERROR: dummy_file.log)',
os.path.join('dummy', 'path', 'dummy_file.log')]),
]
@pytest.mark.parametrize(
'inline_logs, read_exception, expected_logs',
TESTDATA_4,
ids=['basic', 'inline logs', 'inline logs+read_exception']
)
def test_projectbuilder_log_info(
caplog,
mocked_jobserver,
inline_logs,
read_exception,
expected_logs
):
def mock_open(filename, *args, **kwargs):
if read_exception:
raise OSError(errno.ENOENT, f'ERROR: {os.path.basename(filename)}')
return mock.mock_open(read_data='file contents')()
def mock_realpath(filename, *args, **kwargs):
return os.path.join('path', filename)
def mock_abspath(filename, *args, **kwargs):
return os.path.join('dummy', filename)
filename = 'dummy_file.log'
env_mock = mock.Mock()
instance_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
with mock.patch('builtins.open', mock_open), \
mock.patch('os.path.realpath', mock_realpath), \
mock.patch('os.path.abspath', mock_abspath):
pb.log_info(filename, inline_logs)
assert all([log in caplog.text for log in expected_logs])
TESTDATA_5 = [
(True, False, False, "Valgrind error", 0, 0, 'build_dir/valgrind.log'),
(True, False, False, "Error", 0, 0, 'build_dir/build.log'),
(False, True, False, None, 1024, 0, 'build_dir/handler.log'),
(False, True, False, None, 0, 0, 'build_dir/build.log'),
(False, False, True, None, 0, 1024, 'build_dir/device.log'),
(False, False, True, None, 0, 0, 'build_dir/build.log'),
(False, False, False, None, 0, 0, 'build_dir/build.log'),
]
@pytest.mark.parametrize(
'valgrind_log_exists, handler_log_exists, device_log_exists,' \
' instance_reason, handler_log_getsize, device_log_getsize, expected_log',
TESTDATA_5,
ids=['valgrind log', 'valgrind log unused',
'handler log', 'handler log unused',
'device log', 'device log unused',
'no logs']
)
def test_projectbuilder_log_info_file(
caplog,
mocked_jobserver,
valgrind_log_exists,
handler_log_exists,
device_log_exists,
instance_reason,
handler_log_getsize,
device_log_getsize,
expected_log
):
def mock_exists(filename, *args, **kwargs):
if filename == 'build_dir/handler.log':
return handler_log_exists
if filename == 'build_dir/valgrind.log':
return valgrind_log_exists
if filename == 'build_dir/device.log':
return device_log_exists
return False
def mock_getsize(filename, *args, **kwargs):
if filename == 'build_dir/handler.log':
return handler_log_getsize
if filename == 'build_dir/device.log':
return device_log_getsize
return 0
env_mock = mock.Mock()
instance_mock = mock.Mock()
instance_mock.reason = instance_reason
instance_mock.build_dir = 'build_dir'
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
log_info_mock = mock.Mock()
with mock.patch('os.path.exists', mock_exists), \
mock.patch('os.path.getsize', mock_getsize), \
mock.patch('twisterlib.runner.ProjectBuilder.log_info', log_info_mock):
pb.log_info_file(None)
log_info_mock.assert_called_with(expected_log, mock.ANY)
TESTDATA_6 = [
(
{'op': 'filter'},
'failed',
'Failed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
'failed',
'Failed',
0,
None
),
(
{'op': 'filter'},
'passed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'filter': { 'dummy instance name': True }},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['filtering dummy instance name'],
{'op': 'report', 'test': mock.ANY},
'filtered',
'runtime filter',
1,
('skipped',)
),
(
{'op': 'filter'},
'passed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'filter': { 'another dummy instance name': True }},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'cmake', 'test': mock.ANY},
'passed',
mock.ANY,
0,
None
),
(
{'op': 'cmake'},
'error',
'dummy error',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
'error',
'dummy error',
0,
None
),
(
{'op': 'cmake'},
None,
mock.ANY,
mock.ANY,
mock.ANY,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
'passed',
mock.ANY,
0,
None
),
(
{'op': 'cmake'},
'success',
mock.ANY,
mock.ANY,
mock.ANY,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
'success',
mock.ANY,
0,
None
),
(
{'op': 'cmake'},
'success',
mock.ANY,
mock.ANY,
mock.ANY,
False,
mock.ANY,
mock.ANY,
mock.ANY,
{'filter': {'dummy instance name': True}},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
['filtering dummy instance name'],
{'op': 'report', 'test': mock.ANY},
'filtered',
'runtime filter',
1,
('skipped',)
),
(
{'op': 'cmake'},
'success',
mock.ANY,
mock.ANY,
mock.ANY,
False,
mock.ANY,
mock.ANY,
mock.ANY,
{'filter': {}},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'build', 'test': mock.ANY},
'success',
mock.ANY,
0,
None
),
(
{'op': 'build'},
mock.ANY,
None,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
None,
mock.ANY,
mock.ANY,
mock.ANY,
['build test: dummy instance name'],
{'op': 'report', 'test': mock.ANY},
'error',
'Build Failure',
0,
None
),
(
{'op': 'build'},
'skipped',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0},
mock.ANY,
mock.ANY,
mock.ANY,
['build test: dummy instance name',
'Determine test cases for test instance: dummy instance name'],
{'op': 'gather_metrics', 'test': mock.ANY},
mock.ANY,
mock.ANY,
1,
('skipped', mock.ANY)
),
(
{'op': 'build'},
'passed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'dummy': 'dummy'},
mock.ANY,
mock.ANY,
mock.ANY,
['build test: dummy instance name'],
{'op': 'report', 'test': mock.ANY},
'passed',
mock.ANY,
0,
('blocked', mock.ANY)
),
(
{'op': 'build'},
'success',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0},
mock.ANY,
mock.ANY,
mock.ANY,
['build test: dummy instance name',
'Determine test cases for test instance: dummy instance name'],
{'op': 'gather_metrics', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'build'},
'success',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0},
mock.ANY,
mock.ANY,
BuildError,
['build test: dummy instance name',
'Determine test cases for test instance: dummy instance name'],
{'op': 'report', 'test': mock.ANY},
'error',
'Determine Testcases Error!',
0,
None
),
(
{'op': 'gather_metrics'},
mock.ANY,
mock.ANY,
True,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0}, # metrics_res
mock.ANY,
mock.ANY,
[],
{'op': 'run', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
), # 'gather metrics, run and ready handler'
(
{'op': 'gather_metrics'},
mock.ANY,
mock.ANY,
False,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0}, # metrics_res
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
), # 'gather metrics'
(
{'op': 'gather_metrics'},
mock.ANY,
mock.ANY,
False,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
{'returncode': 0}, # build_res
{'returncode': 1}, # metrics_res
mock.ANY,
mock.ANY,
[],
{'op': 'report', 'test': mock.ANY},
'error',
'Build Failure at gather_metrics.',
0,
None
), # 'build ok, gather metrics fail',
(
{'op': 'run'},
'success',
'OK',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
None,
mock.ANY,
['run test: dummy instance name',
'run status: dummy instance name success'],
{'op': 'report', 'test': mock.ANY, 'status': 'success', 'reason': 'OK'},
'success',
'OK',
0,
None
),
(
{'op': 'run'},
'failed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
RuntimeError,
mock.ANY,
['run test: dummy instance name',
'run status: dummy instance name failed',
'RuntimeError: Pipeline Error!'],
None,
'failed',
mock.ANY,
0,
None
),
(
{'op': 'report'},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
False,
True,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'cleanup', 'mode': 'device', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'report'},
'passed',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
False,
False,
'pass',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'cleanup', 'mode': 'passed', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'report'},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
False,
False,
'all',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
{'op': 'cleanup', 'mode': 'all', 'test': mock.ANY},
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'report'},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
False,
False,
'other',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
None,
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'cleanup', 'mode': 'device'},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
None,
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'cleanup', 'mode': 'passed'},
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
None,
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'cleanup', 'mode': 'all'},
mock.ANY,
'Valgrind error',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
None,
mock.ANY,
mock.ANY,
0,
None
),
(
{'op': 'cleanup', 'mode': 'all'},
mock.ANY,
'Cmake build failure',
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
mock.ANY,
[],
None,
mock.ANY,
mock.ANY,
0,
None
),
]
@pytest.mark.parametrize(
'message,' \
' instance_status, instance_reason, instance_run, instance_handler_ready,' \
' options_cmake_only,' \
' options_coverage, options_prep_artifacts, options_runtime_artifacts,' \
' cmake_res, build_res, metrics_res,' \
' pipeline_runtime_error, determine_testcases_build_error,' \
' expected_logs, resulting_message,' \
' expected_status, expected_reason, expected_skipped, expected_missing',
TESTDATA_6,
ids=[
'filter, failed', 'filter, cmake res', 'filter, no cmake res',
'cmake, failed', 'cmake, cmake_only, no status', 'cmake, cmake_only',
'cmake, no cmake_only, cmake res', 'cmake, no cmake_only, no cmake res',
'build, no build res', 'build, skipped', 'build, blocked',
'build, determine testcases', 'build, determine testcases Error',
'gather metrics, run and ready handler', 'gather metrics',
'build ok, gather metrics fail',
'run', 'run, Pipeline Runtime Error',
'report, prep artifacts for testing',
'report, runtime artifact cleanup pass, status passed',
'report, runtime artifact cleanup all', 'report, no message put',
'cleanup, device', 'cleanup, mode passed', 'cleanup, mode all',
'cleanup, mode all, cmake build failure'
]
)
def test_projectbuilder_process(
caplog,
mocked_jobserver,
message,
instance_status,
instance_reason,
instance_run,
instance_handler_ready,
options_cmake_only,
options_coverage,
options_prep_artifacts,
options_runtime_artifacts,
cmake_res,
build_res,
metrics_res,
pipeline_runtime_error,
determine_testcases_build_error,
expected_logs,
resulting_message,
expected_status,
expected_reason,
expected_skipped,
expected_missing
):
def mock_pipeline_put(msg):
if isinstance(pipeline_runtime_error, type) and \
issubclass(pipeline_runtime_error, Exception):
raise RuntimeError('Pipeline Error!')
def mock_determine_testcases(res):
if isinstance(determine_testcases_build_error, type) and \
issubclass(determine_testcases_build_error, Exception):
raise BuildError('Determine Testcases Error!')
instance_mock = mock.Mock()
instance_mock.name = 'dummy instance name'
instance_mock.status = instance_status
instance_mock.reason = instance_reason
instance_mock.run = instance_run
instance_mock.handler = mock.Mock()
instance_mock.handler.ready = instance_handler_ready
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.options = mock.Mock()
pb.options.coverage = options_coverage
pb.options.prep_artifacts_for_testing = options_prep_artifacts
pb.options.runtime_artifact_cleanup = options_runtime_artifacts
pb.options.cmake_only = options_cmake_only
pb.cmake = mock.Mock(return_value=cmake_res)
pb.build = mock.Mock(return_value=build_res)
pb.determine_testcases = mock.Mock(side_effect=mock_determine_testcases)
pb.report_out = mock.Mock()
pb.cleanup_artifacts = mock.Mock()
pb.cleanup_device_testing_artifacts = mock.Mock()
pb.run = mock.Mock()
pb.gather_metrics = mock.Mock(return_value=metrics_res)
pipeline_mock = mock.Mock(put=mock.Mock(side_effect=mock_pipeline_put))
done_mock = mock.Mock()
lock_mock = mock.Mock(
__enter__=mock.Mock(return_value=(mock.Mock(), mock.Mock())),
__exit__=mock.Mock(return_value=None)
)
results_mock = mock.Mock()
results_mock.skipped_runtime = 0
pb.process(pipeline_mock, done_mock, message, lock_mock, results_mock)
assert all([log in caplog.text for log in expected_logs])
if resulting_message:
pipeline_mock.put.assert_called_with(resulting_message)
assert pb.instance.status == expected_status
assert pb.instance.reason == expected_reason
assert results_mock.skipped_runtime == expected_skipped
if expected_missing:
pb.instance.add_missing_case_status.assert_called_with(*expected_missing)
TESTDATA_7 = [
(
[
'z_ztest_unit_test__dummy_suite_name__dummy_test_name',
'z_ztest_unit_test__dummy_suite_name__test_dummy_name',
'no match'
],
['dummy_id.dummy_name', 'dummy_id.dummy_name']
),
(
['no match'],
[]
),
]
@pytest.mark.parametrize(
'symbols_names, added_tcs',
TESTDATA_7,
ids=['two hits, one miss', 'nothing']
)
def test_projectbuilder_determine_testcases(
mocked_jobserver,
symbols_names,
added_tcs
):
symbols_mock = [mock.Mock(n=name) for name in symbols_names]
for m in symbols_mock:
m.configure_mock(name=m.n)
sections_mock = [mock.Mock(spec=SymbolTableSection)]
sections_mock[0].iter_symbols = mock.Mock(return_value=symbols_mock)
elf_mock = mock.Mock()
elf_mock().iter_sections = mock.Mock(return_value=sections_mock)
results_mock = mock.Mock()
instance_mock = mock.Mock()
instance_mock.testcases = []
instance_mock.testsuite.id = 'dummy_id'
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
with mock.patch('twisterlib.runner.ELFFile', elf_mock), \
mock.patch('builtins.open', mock.mock_open()):
pb.determine_testcases(results_mock)
pb.instance.add_testcase.assert_has_calls(
[mock.call(name=x) for x in added_tcs]
)
pb.instance.testsuite.add_testcase.assert_has_calls(
[mock.call(name=x) for x in added_tcs]
)
TESTDATA_8 = [
(
['addition.al'],
'dummy',
['addition.al', '.config', 'zephyr']
),
(
[],
'all',
['.config', 'zephyr', 'testsuite_extra.conf', 'twister']
),
]
@pytest.mark.parametrize(
'additional_keep, runtime_artifact_cleanup, expected_files',
TESTDATA_8,
ids=['additional keep', 'all cleanup']
)
def test_projectbuilder_cleanup_artifacts(
tmpdir,
mocked_jobserver,
additional_keep,
runtime_artifact_cleanup,
expected_files
):
# tmpdir
# ┣ twister
# ┃ ┗ testsuite_extra.conf
# ┣ dummy_dir
# ┃ ┗ dummy.del
# ┣ dummy_link_dir -> zephyr
# ┣ zephyr
# ┃ ┗ .config
# â”— addition.al
twister_dir = tmpdir.mkdir('twister')
testsuite_extra_conf = twister_dir.join('testsuite_extra.conf')
testsuite_extra_conf.write_text('dummy', 'utf-8')
dummy_dir = tmpdir.mkdir('dummy_dir')
dummy_del = dummy_dir.join('dummy.del')
dummy_del.write_text('dummy', 'utf-8')
zephyr = tmpdir.mkdir('zephyr')
config = zephyr.join('.config')
config.write_text('dummy', 'utf-8')
dummy_link_dir = tmpdir.join('dummy_link_dir')
os.symlink(zephyr, dummy_link_dir)
addition_al = tmpdir.join('addition.al')
addition_al.write_text('dummy', 'utf-8')
instance_mock = mock.Mock()
instance_mock.build_dir = tmpdir
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.options = mock.Mock(runtime_artifact_cleanup=runtime_artifact_cleanup)
pb.cleanup_artifacts(additional_keep)
files_left = [p.name for p in list(pathlib.Path(tmpdir).glob('**/*'))]
assert sorted(files_left) == sorted(expected_files)
def test_projectbuilder_cleanup_device_testing_artifacts(
caplog,
mocked_jobserver
):
bins = [os.path.join('zephyr', 'file.bin')]
instance_mock = mock.Mock()
instance_mock.testsuite.sysbuild = False
build_dir = os.path.join('build', 'dir')
instance_mock.build_dir = build_dir
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb._get_binaries = mock.Mock(return_value=bins)
pb.cleanup_artifacts = mock.Mock()
pb._sanitize_files = mock.Mock()
pb.cleanup_device_testing_artifacts()
assert f'Cleaning up for Device Testing {build_dir}' in caplog.text
pb.cleanup_artifacts.assert_called_once_with(
[os.path.join('zephyr', 'file.bin'),
os.path.join('zephyr', 'runners.yaml')]
)
pb._sanitize_files.assert_called_once()
TESTDATA_9 = [
(
None,
[],
[os.path.join('zephyr', 'zephyr.hex'),
os.path.join('zephyr', 'zephyr.bin'),
os.path.join('zephyr', 'zephyr.elf'),
os.path.join('zephyr', 'zephyr.exe')]
),
(
[os.path.join('dummy.bin'), os.path.join('dummy.hex')],
[os.path.join('dir2', 'dummy.elf')],
[os.path.join('zephyr', 'dummy.bin'),
os.path.join('zephyr', 'dummy.hex'),
os.path.join('dir2', 'dummy.elf')]
),
]
@pytest.mark.parametrize(
'platform_binaries, runner_binaries, expected_binaries',
TESTDATA_9,
ids=['default', 'valid']
)
def test_projectbuilder_get_binaries(
mocked_jobserver,
platform_binaries,
runner_binaries,
expected_binaries
):
def mock_get_domains(*args, **kwargs):
return []
instance_mock = mock.Mock()
instance_mock.build_dir = os.path.join('build', 'dir')
instance_mock.domains.get_domains.side_effect = mock_get_domains
instance_mock.platform = mock.Mock()
instance_mock.platform.binaries = platform_binaries
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb._get_binaries_from_runners = mock.Mock(return_value=runner_binaries)
bins = pb._get_binaries()
assert all(bin in expected_binaries for bin in bins)
assert all(bin in bins for bin in expected_binaries)
TESTDATA_10 = [
(None, None, []),
(None, {'dummy': 'dummy'}, []),
( None,
{
'config': {
'elf_file': '/absolute/path/dummy.elf',
'bin_file': 'path/dummy.bin'
}
},
['/absolute/path/dummy.elf', os.path.join('zephyr', 'path/dummy.bin')]
),
( 'test_domain',
{
'config': {
'elf_file': '/absolute/path/dummy.elf',
'bin_file': 'path/dummy.bin'
}
},
['/absolute/path/dummy.elf', os.path.join('test_domain', 'zephyr', 'path/dummy.bin')]
),
]
@pytest.mark.parametrize(
'domain, runners_content, expected_binaries',
TESTDATA_10,
ids=['no file', 'no config', 'valid', 'with domain']
)
def test_projectbuilder_get_binaries_from_runners(
mocked_jobserver,
domain,
runners_content,
expected_binaries
):
def mock_exists(fname):
assert fname == os.path.join('build', 'dir', domain if domain else '',
'zephyr', 'runners.yaml')
return runners_content is not None
instance_mock = mock.Mock()
instance_mock.build_dir = os.path.join('build', 'dir')
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
with mock.patch('os.path.exists', mock_exists), \
mock.patch('builtins.open', mock.mock_open()), \
mock.patch('yaml.load', return_value=runners_content):
if domain:
bins = pb._get_binaries_from_runners(domain)
else:
bins = pb._get_binaries_from_runners()
assert all(bin in expected_binaries for bin in bins)
assert all(bin in bins for bin in expected_binaries)
def test_projectbuilder_sanitize_files(mocked_jobserver):
instance_mock = mock.Mock()
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb._sanitize_runners_file = mock.Mock()
pb._sanitize_zephyr_base_from_files = mock.Mock()
pb._sanitize_files()
pb._sanitize_runners_file.assert_called_once()
pb._sanitize_zephyr_base_from_files.assert_called_once()
TESTDATA_11 = [
(None, None),
('dummy: []', None),
(
"""
config:
elf_file: relative/path/dummy.elf
hex_file: /absolute/path/build_dir/zephyr/dummy.hex
""",
"""
config:
elf_file: relative/path/dummy.elf
hex_file: dummy.hex
"""
),
]
@pytest.mark.parametrize(
'runners_text, expected_write_text',
TESTDATA_11,
ids=['no file', 'no config', 'valid']
)
def test_projectbuilder_sanitize_runners_file(
mocked_jobserver,
runners_text,
expected_write_text
):
def mock_exists(fname):
return runners_text is not None
instance_mock = mock.Mock()
instance_mock.build_dir = '/absolute/path/build_dir'
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
with mock.patch('os.path.exists', mock_exists), \
mock.patch('builtins.open',
mock.mock_open(read_data=runners_text)) as f:
pb._sanitize_runners_file()
if expected_write_text is not None:
f().write.assert_called_with(expected_write_text)
else:
f().write.assert_not_called()
TESTDATA_12 = [
(
{
'CMakeCache.txt': mock.mock_open(
read_data='canonical/zephyr/base/dummy.file: ERROR'
)
},
{
'CMakeCache.txt': 'dummy.file: ERROR'
}
),
(
{
os.path.join('zephyr', 'runners.yaml'): mock.mock_open(
read_data='There was canonical/zephyr/base/dummy.file here'
)
},
{
os.path.join('zephyr', 'runners.yaml'): 'There was dummy.file here'
}
),
]
@pytest.mark.parametrize(
'text_mocks, expected_write_texts',
TESTDATA_12,
ids=['CMakeCache file', 'runners.yaml file']
)
def test_projectbuilder_sanitize_zephyr_base_from_files(
mocked_jobserver,
text_mocks,
expected_write_texts
):
build_dir_path = 'canonical/zephyr/base/build_dir/'
def mock_exists(fname):
if not fname.startswith(build_dir_path):
return False
return fname[len(build_dir_path):] in text_mocks
def mock_open(fname, *args, **kwargs):
if not fname.startswith(build_dir_path):
raise FileNotFoundError(errno.ENOENT, f'File {fname} not found.')
return text_mocks[fname[len(build_dir_path):]]()
instance_mock = mock.Mock()
instance_mock.build_dir = build_dir_path
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
with mock.patch('os.path.exists', mock_exists), \
mock.patch('builtins.open', mock_open), \
mock.patch('twisterlib.runner.canonical_zephyr_base',
'canonical/zephyr/base'):
pb._sanitize_zephyr_base_from_files()
for fname, fhandler in text_mocks.items():
fhandler().write.assert_called_with(expected_write_texts[fname])
TESTDATA_13 = [
(
'error', True, True, False,
['INFO 20/25 dummy platform' \
' dummy.testsuite.name' \
' ERROR dummy reason (cmake)'],
None
),
(
'failed', False, False, False,
['ERROR dummy platform' \
' dummy.testsuite.name' \
' FAILED : dummy reason'],
'INFO - Total complete: 20/ 25 80% skipped: 3,' \
' failed: 3, error: 1'
),
(
'skipped', True, False, False,
['INFO 20/25 dummy platform' \
' dummy.testsuite.name' \
' SKIPPED (dummy reason)'],
None
),
(
'filtered', False, False, False,
[],
'INFO - Total complete: 20/ 25 80% skipped: 4,' \
' failed: 2, error: 1'
),
(
'passed', True, False, True,
['INFO 20/25 dummy platform' \
' dummy.testsuite.name' \
' PASSED' \
' (dummy handler type: dummy dut, 60.000s)'],
None
),
(
'passed', True, False, False,
['INFO 20/25 dummy platform' \
' dummy.testsuite.name' \
' PASSED (build)'],
None
),
(
'unknown status', False, False, False,
['Unknown status = unknown status'],
'INFO - Total complete: 20/ 25 80% skipped: 3,' \
' failed: 2, error: 1\r'
),
(
'timeout', True, False, True,
['INFO 20/25 dummy platform' \
' dummy.testsuite.name' \
' UNKNOWN' \
' (dummy handler type: dummy dut, 60.000s/seed: 123)'],
None
),
]
@pytest.mark.parametrize(
'status, verbose, cmake_only, ready_run, expected_logs, expected_out',
TESTDATA_13,
ids=['verbose error cmake only', 'failed', 'verbose skipped', 'filtered',
'verbose passed ready run', 'verbose passed', 'unknown status',
'timeout']
)
def test_projectbuilder_report_out(
capfd,
caplog,
mocked_jobserver,
status,
verbose,
cmake_only,
ready_run,
expected_logs,
expected_out
):
instance_mock = mock.Mock()
instance_mock.handler.type_str = 'dummy handler type'
instance_mock.handler.seed = 123
instance_mock.handler.ready = ready_run
instance_mock.run = ready_run
instance_mock.dut = 'dummy dut'
instance_mock.execution_time = 60
instance_mock.platform.name = 'dummy platform'
instance_mock.status = status
instance_mock.reason = 'dummy reason'
instance_mock.testsuite.name = 'dummy.testsuite.name'
instance_mock.testsuite.testcases = [mock.Mock() for _ in range(25)]
instance_mock.testcases = [mock.Mock() for _ in range(24)] + \
[mock.Mock(status='skipped')]
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.options.verbose = verbose
pb.options.cmake_only = cmake_only
pb.options.seed = 123
pb.log_info_file = mock.Mock()
results_mock = mock.Mock()
results_mock.iteration = 1
results_mock.total = 25
results_mock.done = 19
results_mock.passed = 17
results_mock.skipped_configs = 3
results_mock.skipped_cases = 4
results_mock.failed = 2
results_mock.error = 1
results_mock.cases = 0
pb.report_out(results_mock)
assert results_mock.cases == 25
trim_actual_log = re.sub(
r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])',
'',
caplog.text
)
trim_actual_log = re.sub(r'twister:runner.py:\d+', '', trim_actual_log)
assert all([log in trim_actual_log for log in expected_logs])
if expected_out:
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
# Remove 7b ANSI C1 escape sequences (colours)
out = re.sub(
r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])',
'',
out
)
assert expected_out in out
def test_projectbuilder_cmake_assemble_args():
extra_args = ['CONFIG_FOO=y', 'DUMMY_EXTRA="yes"']
handler = mock.Mock(ready=True, args=['dummy_handler'])
extra_conf_files = ['extrafile1.conf', 'extrafile2.conf']
extra_overlay_confs = ['extra_overlay_conf']
extra_dtc_overlay_files = ['overlay1.dtc', 'overlay2.dtc']
cmake_extra_args = ['CMAKE1="yes"', 'CMAKE2=n']
build_dir = os.path.join('build', 'dir')
with mock.patch('os.path.exists', return_value=True):
results = ProjectBuilder.cmake_assemble_args(extra_args, handler,
extra_conf_files,
extra_overlay_confs,
extra_dtc_overlay_files,
cmake_extra_args,
build_dir)
expected_results = [
'-DCONFIG_FOO=y',
'-DCMAKE1=\"yes\"',
'-DCMAKE2=n',
'-DDUMMY_EXTRA=yes',
'-Ddummy_handler',
'-DCONF_FILE=extrafile1.conf;extrafile2.conf',
'-DDTC_OVERLAY_FILE=overlay1.dtc;overlay2.dtc',
f'-DOVERLAY_CONFIG=extra_overlay_conf ' \
f'{os.path.join("build", "dir", "twister", "testsuite_extra.conf")}'
]
assert results == expected_results
def test_projectbuilder_cmake():
instance_mock = mock.Mock()
instance_mock.handler = 'dummy handler'
instance_mock.build_dir = os.path.join('build', 'dir')
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.build_dir = 'build_dir'
pb.testsuite.extra_args = ['some', 'args']
pb.testsuite.extra_conf_files = ['some', 'files1']
pb.testsuite.extra_overlay_confs = ['some', 'files2']
pb.testsuite.extra_dtc_overlay_files = ['some', 'files3']
pb.options.extra_args = ['other', 'args']
pb.cmake_assemble_args = mock.Mock(return_value=['dummy'])
cmake_res_mock = mock.Mock()
pb.run_cmake = mock.Mock(return_value=cmake_res_mock)
res = pb.cmake(['dummy filter'])
assert res == cmake_res_mock
pb.cmake_assemble_args.assert_called_once_with(
pb.testsuite.extra_args,
pb.instance.handler,
pb.testsuite.extra_conf_files,
pb.testsuite.extra_overlay_confs,
pb.testsuite.extra_dtc_overlay_files,
pb.options.extra_args,
pb.instance.build_dir
)
pb.run_cmake.assert_called_once_with(['dummy'], ['dummy filter'])
def test_projectbuilder_build(mocked_jobserver):
instance_mock = mock.Mock()
instance_mock.testsuite.harness = 'test'
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.build_dir = 'build_dir'
pb.run_build = mock.Mock(return_value={'dummy': 'dummy'})
res = pb.build()
pb.run_build.assert_called_once_with(['--build', 'build_dir'])
assert res == {'dummy': 'dummy'}
TESTDATA_14 = [
(
True,
'device',
234,
'native_sim',
'posix',
{'CONFIG_FAKE_ENTROPY_NATIVE_POSIX': 'y'},
'pytest',
True,
True,
True,
True,
True,
False
),
(
True,
'not device',
None,
'native_sim',
'not posix',
{'CONFIG_FAKE_ENTROPY_NATIVE_POSIX': 'y'},
'not pytest',
False,
False,
False,
False,
False,
True
),
(
False,
'device',
234,
'native_sim',
'posix',
{'CONFIG_FAKE_ENTROPY_NATIVE_POSIX': 'y'},
'pytest',
False,
False,
False,
False,
False,
False
),
]
@pytest.mark.parametrize(
'ready, type_str, seed, platform_name, platform_arch, defconfig, harness,' \
' expect_duts, expect_parse_generated, expect_seed,' \
' expect_extra_test_args, expect_pytest, expect_handle',
TESTDATA_14,
ids=['pytest full', 'not pytest minimal', 'not ready']
)
def test_projectbuilder_run(
mocked_jobserver,
ready,
type_str,
seed,
platform_name,
platform_arch,
defconfig,
harness,
expect_duts,
expect_parse_generated,
expect_seed,
expect_extra_test_args,
expect_pytest,
expect_handle
):
pytest_mock = mock.Mock(spec=Pytest)
harness_mock = mock.Mock()
def mock_harness(name):
if name == 'Pytest':
return pytest_mock
else:
return harness_mock
instance_mock = mock.Mock()
instance_mock.handler.get_test_timeout = mock.Mock(return_value=60)
instance_mock.handler.seed = 123
instance_mock.handler.ready = ready
instance_mock.handler.type_str = type_str
instance_mock.handler.duts = [mock.Mock(name='dummy dut')]
instance_mock.platform.name = platform_name
instance_mock.platform.arch = platform_arch
instance_mock.testsuite.harness = harness
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.options.extra_test_args = ['dummy_arg1', 'dummy_arg2']
pb.duts = ['another dut']
pb.options.seed = seed
pb.defconfig = defconfig
pb.parse_generated = mock.Mock()
with mock.patch('twisterlib.runner.HarnessImporter.get_harness',
mock_harness):
pb.run()
if expect_duts:
assert pb.instance.handler.duts == ['another dut']
if expect_parse_generated:
pb.parse_generated.assert_called_once()
if expect_seed:
assert pb.instance.handler.seed == seed
if expect_extra_test_args:
assert pb.instance.handler.extra_test_args == ['dummy_arg1',
'dummy_arg2']
if expect_pytest:
pytest_mock.pytest_run.assert_called_once_with(60)
if expect_handle:
pb.instance.handler.handle.assert_called_once_with(harness_mock)
TESTDATA_15 = [
(False, False, False, True),
(True, False, True, False),
(False, True, False, True),
(True, True, False, True),
]
@pytest.mark.parametrize(
'enable_size_report, cmake_only, expect_calc_size, expect_zeroes',
TESTDATA_15,
ids=['none', 'size_report', 'cmake', 'size_report+cmake']
)
def test_projectbuilder_gather_metrics(
mocked_jobserver,
enable_size_report,
cmake_only,
expect_calc_size,
expect_zeroes
):
instance_mock = mock.Mock()
instance_mock.metrics = {}
env_mock = mock.Mock()
pb = ProjectBuilder(instance_mock, env_mock, mocked_jobserver)
pb.options.enable_size_report = enable_size_report
pb.options.create_rom_ram_report = False
pb.options.cmake_only = cmake_only
pb.calc_size = mock.Mock()
pb.gather_metrics(instance_mock)
if expect_calc_size:
pb.calc_size.assert_called_once()
if expect_zeroes:
assert instance_mock.metrics['used_ram'] == 0
assert instance_mock.metrics['used_rom'] == 0
assert instance_mock.metrics['available_rom'] == 0
assert instance_mock.metrics['available_ram'] == 0
assert instance_mock.metrics['unrecognized'] == []
TESTDATA_16 = [
('error', mock.ANY, False, False, False),
('failed', mock.ANY, False, False, False),
('skipped', mock.ANY, False, False, False),
('filtered', 'native', False, False, True),
('passed', 'qemu', False, False, True),
('filtered', 'unit', False, False, True),
('filtered', 'mcu', True, True, False),
('passed', 'frdm_k64f', False, True, False),
]
@pytest.mark.parametrize(
'status, platform_type, expect_warnings, expect_calcs, expect_zeroes',
TESTDATA_16,
ids=[x[0] + (', ' + x[1]) if x[1] != mock.ANY else '' for x in TESTDATA_16]
)
def test_projectbuilder_calc_size(
status,
platform_type,
expect_warnings,
expect_calcs,
expect_zeroes
):
size_calc_mock = mock.Mock()
instance_mock = mock.Mock()
instance_mock.status = status
instance_mock.platform.type = platform_type
instance_mock.metrics = {}
instance_mock.calculate_sizes = mock.Mock(return_value=size_calc_mock)
from_buildlog = True
ProjectBuilder.calc_size(instance_mock, from_buildlog)
if expect_calcs:
instance_mock.calculate_sizes.assert_called_once_with(
from_buildlog=from_buildlog,
generate_warning=expect_warnings
)
assert instance_mock.metrics['used_ram'] == \
size_calc_mock.get_used_ram()
assert instance_mock.metrics['used_rom'] == \
size_calc_mock.get_used_rom()
assert instance_mock.metrics['available_rom'] == \
size_calc_mock.get_available_rom()
assert instance_mock.metrics['available_ram'] == \
size_calc_mock.get_available_ram()
assert instance_mock.metrics['unrecognized'] == \
size_calc_mock.unrecognized_sections()
if expect_zeroes:
assert instance_mock.metrics['used_ram'] == 0
assert instance_mock.metrics['used_rom'] == 0
assert instance_mock.metrics['available_rom'] == 0
assert instance_mock.metrics['available_ram'] == 0
assert instance_mock.metrics['unrecognized'] == []
if expect_calcs or expect_zeroes:
assert instance_mock.metrics['handler_time'] == \
instance_mock.execution_time
else:
assert instance_mock.metrics == {}
TESTDATA_17 = [
('linux', 'posix', {'jobs': 4}, True, 32, 'GNUMakeJobClient'),
('linux', 'posix', {'build_only': True}, False, 16, 'GNUMakeJobServer'),
('linux', '???', {}, False, 8, 'JobClient'),
('linux', '???', {'jobs': 4}, False, 4, 'JobClient'),
]
@pytest.mark.parametrize(
'platform, os_name, options, jobclient_from_environ, expected_jobs,' \
' expected_jobserver',
TESTDATA_17,
ids=['GNUMakeJobClient', 'GNUMakeJobServer',
'JobClient', 'Jobclient+options']
)
def test_twisterrunner_run(
caplog,
platform,
os_name,
options,
jobclient_from_environ,
expected_jobs,
expected_jobserver
):
def mock_client_from_environ(jobs):
if jobclient_from_environ:
jobclient_mock = mock.Mock(jobs=32)
jobclient_mock.name = 'GNUMakeJobClient'
return jobclient_mock
return None
instances = {'dummy instance': mock.Mock(metrics={'k': 'v'})}
suites = [mock.Mock()]
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.options.retry_failed = 2
tr.options.retry_interval = 10
tr.options.retry_build_errors = True
tr.options.jobs = None
tr.options.build_only = None
for k, v in options.items():
setattr(tr.options, k, v)
tr.update_counting_before_pipeline = mock.Mock()
tr.execute = mock.Mock()
tr.show_brief = mock.Mock()
gnumakejobserver_mock = mock.Mock()
gnumakejobserver_mock().name='GNUMakeJobServer'
jobclient_mock = mock.Mock()
jobclient_mock().name='JobClient'
pipeline_q = queue.LifoQueue()
done_q = queue.LifoQueue()
done_instance = mock.Mock(
metrics={'k2': 'v2'},
execution_time=30
)
done_instance.name='dummy instance'
done_q.put(done_instance)
manager_mock = mock.Mock()
manager_mock().LifoQueue = mock.Mock(
side_effect=iter([pipeline_q, done_q])
)
results_mock = mock.Mock()
results_mock().error = 1
results_mock().iteration = 0
results_mock().failed = 2
results_mock().total = 9
with mock.patch('twisterlib.runner.ExecutionCounter', results_mock), \
mock.patch('twisterlib.runner.BaseManager', manager_mock), \
mock.patch('twisterlib.runner.GNUMakeJobClient.from_environ',
mock_client_from_environ), \
mock.patch('twisterlib.runner.GNUMakeJobServer',
gnumakejobserver_mock), \
mock.patch('twisterlib.runner.JobClient', jobclient_mock), \
mock.patch('multiprocessing.cpu_count', return_value=8), \
mock.patch('sys.platform', platform), \
mock.patch('time.sleep', mock.Mock()), \
mock.patch('os.name', os_name):
tr.run()
assert f'JOBS: {expected_jobs}' in caplog.text
assert tr.jobserver.name == expected_jobserver
assert tr.instances['dummy instance'].metrics == {
'k': 'v',
'k2': 'v2',
'handler_time': 30,
'unrecognized': []
}
assert results_mock().error == 0
def test_twisterrunner_update_counting_before_pipeline():
instances = {
'dummy1': mock.Mock(
status='filtered',
reason='runtime filter',
testsuite=mock.Mock(
testcases=[mock.Mock()]
)
),
'dummy2': mock.Mock(
status='filtered',
reason='static filter',
testsuite=mock.Mock(
testcases=[mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()]
)
),
'dummy3': mock.Mock(
status='error',
reason='error',
testsuite=mock.Mock(
testcases=[mock.Mock()]
)
),
'dummy4': mock.Mock(
status='passed',
reason='OK',
testsuite=mock.Mock(
testcases=[mock.Mock()]
)
),
'dummy5': mock.Mock(
status='skipped',
reason=None,
testsuite=mock.Mock(
testcases=[mock.Mock()]
)
)
}
suites = [mock.Mock()]
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.results = mock.Mock(
skipped_filter = 0,
skipped_configs = 0,
skipped_cases = 0,
cases = 0,
error = 0
)
tr.update_counting_before_pipeline()
assert tr.results.skipped_filter == 1
assert tr.results.skipped_configs == 1
assert tr.results.skipped_cases == 4
assert tr.results.cases == 4
assert tr.results.error == 1
def test_twisterrunner_show_brief(caplog):
instances = {
'dummy1': mock.Mock(),
'dummy2': mock.Mock(),
'dummy3': mock.Mock(),
'dummy4': mock.Mock(),
'dummy5': mock.Mock()
}
suites = [mock.Mock(), mock.Mock()]
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.results = mock.Mock(
skipped_filter = 3,
skipped_configs = 4,
skipped_cases = 0,
cases = 0,
error = 0
)
tr.show_brief()
log = '2 test scenarios (5 test instances) selected,' \
' 4 configurations skipped (3 by static filter, 1 at runtime).'
assert log in caplog.text
TESTDATA_18 = [
(False, False, False, [{'op': 'cmake', 'test': mock.ANY}]),
(False, False, True, [{'op': 'filter', 'test': mock.ANY},
{'op': 'cmake', 'test': mock.ANY}]),
(False, True, True, [{'op': 'run', 'test': mock.ANY},
{'op': 'run', 'test': mock.ANY}]),
(False, True, False, [{'op': 'run', 'test': mock.ANY}]),
(True, True, False, [{'op': 'cmake', 'test': mock.ANY}]),
(True, True, True, [{'op': 'filter', 'test': mock.ANY},
{'op': 'cmake', 'test': mock.ANY}]),
(True, False, True, [{'op': 'filter', 'test': mock.ANY},
{'op': 'cmake', 'test': mock.ANY}]),
(True, False, False, [{'op': 'cmake', 'test': mock.ANY}]),
]
@pytest.mark.parametrize(
'build_only, test_only, retry_build_errors, expected_pipeline_elements',
TESTDATA_18,
ids=['none', 'retry', 'test+retry', 'test', 'build+test',
'build+test+retry', 'build+retry', 'build']
)
def test_twisterrunner_add_tasks_to_queue(
build_only,
test_only,
retry_build_errors,
expected_pipeline_elements
):
def mock_get_cmake_filter_stages(filter, keys):
return [filter]
instances = {
'dummy1': mock.Mock(run=True, retries=0, status='passed', build_dir="/tmp"),
'dummy2': mock.Mock(run=True, retries=0, status='skipped', build_dir="/tmp"),
'dummy3': mock.Mock(run=True, retries=0, status='filtered', build_dir="/tmp"),
'dummy4': mock.Mock(run=True, retries=0, status='error', build_dir="/tmp"),
'dummy5': mock.Mock(run=True, retries=0, status='failed', build_dir="/tmp")
}
instances['dummy4'].testsuite.filter = 'some'
instances['dummy5'].testsuite.filter = 'full'
suites = [mock.Mock(), mock.Mock()]
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.get_cmake_filter_stages = mock.Mock(
side_effect=mock_get_cmake_filter_stages
)
pipeline_mock = mock.Mock()
tr.add_tasks_to_queue(
pipeline_mock,
build_only,
test_only,
retry_build_errors
)
assert all(
[build_only != instance.run for instance in instances.values()]
)
tr.get_cmake_filter_stages.assert_any_call('full', mock.ANY)
if retry_build_errors:
tr.get_cmake_filter_stages.assert_any_call('some', mock.ANY)
print(pipeline_mock.put.call_args_list)
print([mock.call(el) for el in expected_pipeline_elements])
assert pipeline_mock.put.call_args_list == \
[mock.call(el) for el in expected_pipeline_elements]
TESTDATA_19 = [
('linux'),
('nt')
]
@pytest.mark.parametrize(
'platform',
TESTDATA_19,
)
def test_twisterrunner_pipeline_mgr(mocked_jobserver, platform):
counter = 0
def mock_get_nowait():
nonlocal counter
counter += 1
if counter > 5:
raise queue.Empty()
return {'test': 'dummy'}
instances = {}
suites = []
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.jobserver = mock.Mock(
get_job=mock.Mock(
return_value=nullcontext()
)
)
pipeline_mock = mock.Mock()
pipeline_mock.get_nowait = mock.Mock(side_effect=mock_get_nowait)
done_queue_mock = mock.Mock()
lock_mock = mock.Mock()
results_mock = mock.Mock()
with mock.patch('sys.platform', platform), \
mock.patch('twisterlib.runner.ProjectBuilder',\
return_value=mock.Mock()) as pb:
tr.pipeline_mgr(pipeline_mock, done_queue_mock, lock_mock, results_mock)
assert len(pb().process.call_args_list) == 5
if platform == 'linux':
tr.jobserver.get_job.assert_called_once()
def test_twisterrunner_execute(caplog):
counter = 0
def mock_join():
nonlocal counter
counter += 1
if counter > 3:
raise KeyboardInterrupt()
instances = {}
suites = []
env_mock = mock.Mock()
tr = TwisterRunner(instances, suites, env=env_mock)
tr.add_tasks_to_queue = mock.Mock()
tr.jobs = 5
process_mock = mock.Mock()
process_mock().join = mock.Mock(side_effect=mock_join)
pipeline_mock = mock.Mock()
done_mock = mock.Mock()
with mock.patch('twisterlib.runner.Process', process_mock):
tr.execute(pipeline_mock, done_mock)
assert 'Execution interrupted' in caplog.text
assert len(process_mock().start.call_args_list) == 5
assert len(process_mock().join.call_args_list) == 4
assert len(process_mock().terminate.call_args_list) == 5
TESTDATA_20 = [
('', []),
('not ARCH in ["x86", "arc"]', ['full']),
('dt_dummy(x, y)', ['dts']),
('not CONFIG_FOO', ['kconfig']),
('dt_dummy and CONFIG_FOO', ['dts', 'kconfig']),
]
@pytest.mark.parametrize(
'filter, expected_result',
TESTDATA_20,
ids=['none', 'full', 'dts', 'kconfig', 'dts+kconfig']
)
def test_twisterrunner_get_cmake_filter_stages(filter, expected_result):
result = TwisterRunner.get_cmake_filter_stages(filter, ['not', 'and'])
assert sorted(result) == sorted(expected_result)