Extend `--no-detailed-test-id` command line option: in addition to its current behavior to exclude from a test Suite name its configuration path prefix, also don't prefix each Ztest Case name with its Scenario name. For example: 'kernel.common.timing' Scenario name, the same Suite name, and 'sleep.usleep' test Case (where 'sleep' is its Ztest suite name and 'usleep' is Ztest test name. This way both TestSuite and TestCase names follow the same principle having no parent object name prefix. There is no information loss in Twister reports with this naming: TestSuite is a container object for its TestCases, whereas TestSuite has its configuration path as a property. Signed-off-by: Dmitrii Golovanov <dmitrii.golovanov@intel.com>
2857 lines
83 KiB
Python
2857 lines
83 KiB
Python
#!/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.statuses import TwisterStatus
|
|
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.sysbuild = False
|
|
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()
|
|
options.verbose = 2
|
|
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.instance.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.instance.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.filtered_configs = 3
|
|
ec.filtered_runtime = 1
|
|
ec.filtered_static = 2
|
|
ec.failed = 1
|
|
|
|
ec.summary()
|
|
|
|
out, err = capfd.readouterr()
|
|
sys.stdout.write(out)
|
|
sys.stderr.write(err)
|
|
|
|
assert (
|
|
"├── Total test suites: 12\n"
|
|
"├── Processed test suites: 9\n"
|
|
"│ ├── Filtered test suites: 3\n"
|
|
"│ │ ├── Filtered test suites (static): 2\n"
|
|
"│ │ └── Filtered test suites (at runtime): 1\n"
|
|
"│ └── Selected test suites: 6\n"
|
|
"│ ├── Skipped test suites: 0\n"
|
|
"│ ├── Passed test suites: 6\n"
|
|
"│ ├── Built only test suites: 0\n"
|
|
"│ ├── Failed test suites: 1\n"
|
|
"│ └── Errors in test suites: 2\n"
|
|
"└── Total test cases: 25\n"
|
|
" ├── Filtered test cases: 0\n"
|
|
" └── Selected test cases: 25\n"
|
|
" ├── Passed test cases: 0\n"
|
|
" ├── Skipped test cases: 6\n"
|
|
" ├── Built only test cases: 0\n"
|
|
" ├── Blocked test cases: 0\n"
|
|
" ├── Failed test cases: 0\n"
|
|
" └── Errors in test cases: 0\n"
|
|
) 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.filtered_configs == 3
|
|
assert ec.filtered_runtime == 1
|
|
assert ec.filtered_static == 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, TwisterStatus.NOTRUN, None, False, True),
|
|
(0, True, '',
|
|
False, False, TwisterStatus.PASS, None, False, False),
|
|
(1, True, 'ERROR: region `FLASH\' overflowed by 123 MB',
|
|
True, True, TwisterStatus.SKIP, 'FLASH overflow', True, False),
|
|
(1, True, 'Error: Image size (99 B) + trailer (1 B) exceeds requested size',
|
|
True, True, TwisterStatus.SKIP, 'imgtool overflow', True, False),
|
|
(1, True, 'mock.ANY',
|
|
True, True, TwisterStatus.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 = TwisterStatus.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(
|
|
TwisterStatus.NOTRUN, 'Test was built only'
|
|
)
|
|
|
|
|
|
TESTDATA_2_1 = [
|
|
('linux'),
|
|
('nt')
|
|
]
|
|
TESTDATA_2_2 = [
|
|
(True, ['dummy_stage_1', 'ds2'],
|
|
0, False, '',
|
|
True, True, False,
|
|
TwisterStatus.NONE, None,
|
|
[os.path.join('dummy', 'cmake'),
|
|
'-B' + os.path.join('build', 'dir'), '-DTC_RUNID=1', '-DTC_NAME=testcase',
|
|
'-DSB_CONFIG_COMPILER_WARNINGS_AS_ERRORS=y',
|
|
'-DEXTRA_GEN_EDT_ARGS=--edtlib-Werror', '-Gdummy_generator',
|
|
f'-DPython3_EXECUTABLE={pathlib.Path(sys.executable).as_posix()}',
|
|
'-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,
|
|
TwisterStatus.ERROR, 'CMake build failure',
|
|
[os.path.join('dummy', 'cmake'),
|
|
'-B' + os.path.join('build', 'dir'), '-DTC_RUNID=1', '-DTC_NAME=testcase',
|
|
'-DSB_CONFIG_COMPILER_WARNINGS_AS_ERRORS=n',
|
|
'-DEXTRA_GEN_EDT_ARGS=', '-Gdummy_generator',
|
|
f'-DPython3_EXECUTABLE={pathlib.Path(sys.executable).as_posix()}',
|
|
'-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 = TwisterStatus.NONE
|
|
instance_mock.reason = None
|
|
instance_mock.testsuite = mock.Mock()
|
|
instance_mock.testsuite.name = 'testcase'
|
|
instance_mock.testsuite.required_snippets = ['dummy snippet 1', 'ds2']
|
|
instance_mock.testcases = [mock.Mock(), mock.Mock()]
|
|
instance_mock.testcases[0].status = TwisterStatus.NONE
|
|
instance_mock.testcases[1].status = TwisterStatus.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'], 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', '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, *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.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()
|
|
instance_mock.sysbuild = 'sysbuild' if sysbuild else None
|
|
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'},
|
|
TwisterStatus.FAIL,
|
|
'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},
|
|
TwisterStatus.FAIL,
|
|
'Failed',
|
|
0,
|
|
None
|
|
),
|
|
(
|
|
{'op': 'filter'},
|
|
TwisterStatus.PASS,
|
|
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},
|
|
TwisterStatus.FILTER,
|
|
'runtime filter',
|
|
1,
|
|
(TwisterStatus.FILTER,)
|
|
),
|
|
(
|
|
{'op': 'filter'},
|
|
TwisterStatus.PASS,
|
|
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},
|
|
TwisterStatus.PASS,
|
|
mock.ANY,
|
|
0,
|
|
None
|
|
),
|
|
(
|
|
{'op': 'cmake'},
|
|
TwisterStatus.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},
|
|
TwisterStatus.ERROR,
|
|
'dummy error',
|
|
0,
|
|
None
|
|
),
|
|
(
|
|
{'op': 'cmake'},
|
|
TwisterStatus.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},
|
|
TwisterStatus.NOTRUN,
|
|
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},
|
|
TwisterStatus.FILTER,
|
|
'runtime filter',
|
|
1,
|
|
(TwisterStatus.FILTER,) # this is a tuple
|
|
),
|
|
(
|
|
{'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},
|
|
TwisterStatus.ERROR,
|
|
'Build Failure',
|
|
0,
|
|
None
|
|
),
|
|
(
|
|
{'op': 'build'},
|
|
TwisterStatus.SKIP,
|
|
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,
|
|
(TwisterStatus.SKIP, mock.ANY)
|
|
),
|
|
(
|
|
{'op': 'build'},
|
|
TwisterStatus.PASS,
|
|
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},
|
|
TwisterStatus.PASS,
|
|
mock.ANY,
|
|
0,
|
|
(TwisterStatus.BLOCK, 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},
|
|
TwisterStatus.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'},
|
|
TwisterStatus.FAIL,
|
|
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,
|
|
TwisterStatus.FAIL,
|
|
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'},
|
|
TwisterStatus.PASS,
|
|
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
|
|
instance_mock.testsuite.harness = 'test'
|
|
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.filtered_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.filtered_runtime_increment.call_args_list == [mock.call()] * expected_skipped
|
|
|
|
if expected_missing:
|
|
pb.instance.add_missing_case_status.assert_called_with(*expected_missing)
|
|
|
|
|
|
TESTDATA_7 = [
|
|
(
|
|
True,
|
|
[
|
|
'z_ztest_unit_test__dummy_suite1_name__dummy_test_name1',
|
|
'z_ztest_unit_test__dummy_suite2_name__test_dummy_name2',
|
|
'no match'
|
|
],
|
|
[
|
|
'dummy.test_id.dummy_suite1_name.dummy_name1',
|
|
'dummy.test_id.dummy_suite2_name.dummy_name2'
|
|
]
|
|
),
|
|
(
|
|
False,
|
|
[
|
|
'z_ztest_unit_test__dummy_suite1_name__dummy_test_name1',
|
|
'z_ztest_unit_test__dummy_suite2_name__test_dummy_name2',
|
|
'no match'
|
|
],
|
|
[
|
|
'dummy_suite1_name.dummy_name1',
|
|
'dummy_suite2_name.dummy_name2'
|
|
]
|
|
),
|
|
(
|
|
True,
|
|
[
|
|
'z_ztest_unit_test__dummy_suite2_name__test_dummy_name2',
|
|
'z_ztest_unit_test__bad_suite3_name_no_test',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name4E',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_bad_name1E',
|
|
'_ZN12_GLOBAL__N_1L51z_ztest_unit_test_dummy_suite3_name__test_bad_name2E',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name5E',
|
|
'_ZN15foobarnamespaceL54z_ztest_unit_test__dummy_suite3_name__test_dummy_name6E',
|
|
],
|
|
[
|
|
'dummy.test_id.dummy_suite2_name.dummy_name2',
|
|
'dummy.test_id.dummy_suite3_name.dummy_name4',
|
|
'dummy.test_id.dummy_suite3_name.bad_name1E',
|
|
'dummy.test_id.dummy_suite3_name.dummy_name5',
|
|
'dummy.test_id.dummy_suite3_name.dummy_name6',
|
|
]
|
|
),
|
|
(
|
|
True,
|
|
[
|
|
'z_ztest_unit_test__dummy_suite2_name__test_dummy_name2',
|
|
'z_ztest_unit_test__bad_suite3_name_no_test',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name4E',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_bad_name1E',
|
|
'_ZN12_GLOBAL__N_1L51z_ztest_unit_test_dummy_suite3_name__test_bad_name2E',
|
|
'_ZN12_GLOBAL__N_1L54z_ztest_unit_test__dummy_suite3_name__test_dummy_name5E',
|
|
'_ZN15foobarnamespaceL54z_ztest_unit_test__dummy_suite3_name__test_dummy_name6E',
|
|
],
|
|
[
|
|
'dummy_suite2_name.dummy_name2',
|
|
'dummy_suite3_name.dummy_name4',
|
|
'dummy_suite3_name.bad_name1E',
|
|
'dummy_suite3_name.dummy_name5',
|
|
'dummy_suite3_name.dummy_name6',
|
|
]
|
|
),
|
|
(
|
|
True,
|
|
['no match'],
|
|
[]
|
|
),
|
|
]
|
|
|
|
@pytest.mark.parametrize(
|
|
'detailed_id, symbols_names, added_tcs',
|
|
TESTDATA_7,
|
|
ids=['two hits, one miss', 'two hits short id', 'demangle', 'demangle short id', 'nothing']
|
|
)
|
|
def test_projectbuilder_determine_testcases(
|
|
mocked_jobserver,
|
|
mocked_env,
|
|
detailed_id,
|
|
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.test_id'
|
|
instance_mock.testsuite.ztest_suite_names = []
|
|
instance_mock.testsuite.detailed_test_id = detailed_id
|
|
instance_mock.compose_case_name = mock.Mock(side_effect=iter(added_tcs))
|
|
|
|
pb = ProjectBuilder(instance_mock, mocked_env, 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.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 = [
|
|
(
|
|
TwisterStatus.ERROR, True, True, False,
|
|
['INFO 20/25 dummy platform' \
|
|
' dummy.testsuite.name' \
|
|
' ERROR dummy reason (cmake)'],
|
|
None
|
|
),
|
|
(
|
|
TwisterStatus.FAIL, False, False, False,
|
|
['ERROR dummy platform' \
|
|
' dummy.testsuite.name' \
|
|
' FAILED: dummy reason'],
|
|
'INFO - Total complete: 20/ 25 80%' \
|
|
' built (not run): 0, filtered: 3, failed: 3, error: 1'
|
|
),
|
|
(
|
|
TwisterStatus.SKIP, True, False, False,
|
|
['INFO 20/25 dummy platform' \
|
|
' dummy.testsuite.name' \
|
|
' SKIPPED (dummy reason)'],
|
|
None
|
|
),
|
|
(
|
|
TwisterStatus.FILTER, False, False, False,
|
|
[],
|
|
'INFO - Total complete: 20/ 25 80%' \
|
|
' built (not run): 0, filtered: 4, failed: 2, error: 1'
|
|
),
|
|
(
|
|
TwisterStatus.PASS, True, False, True,
|
|
['INFO 20/25 dummy platform' \
|
|
' dummy.testsuite.name' \
|
|
' PASSED' \
|
|
' (dummy handler type: dummy dut, 60.000s)'],
|
|
None
|
|
),
|
|
(
|
|
TwisterStatus.PASS, 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%'
|
|
' built (not run): 0, filtered: 3, failed: 2, error: 1\r'
|
|
)
|
|
]
|
|
|
|
@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']
|
|
)
|
|
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'
|
|
skip_mock_tc = mock.Mock(status=TwisterStatus.SKIP, reason=None)
|
|
skip_mock_tc.name = 'mocked_testcase_to_skip'
|
|
unknown_mock_tc = mock.Mock(status=mock.Mock(value='dummystatus'), reason=None)
|
|
unknown_mock_tc.name = 'mocked_testcase_unknown'
|
|
instance_mock.testsuite.testcases = [unknown_mock_tc for _ in range(25)]
|
|
instance_mock.testcases = [unknown_mock_tc for _ in range(24)] + \
|
|
[skip_mock_tc]
|
|
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(
|
|
total = 25,
|
|
done = 19,
|
|
passed = 17,
|
|
notrun = 0,
|
|
failed = 2,
|
|
filtered_configs = 3,
|
|
filtered_runtime = 0,
|
|
filtered_static = 0,
|
|
error = 1,
|
|
cases = 0,
|
|
filtered_cases = 0,
|
|
skipped_cases = 4,
|
|
failed_cases = 0,
|
|
error_cases = 0,
|
|
blocked_cases = 0,
|
|
passed_cases = 0,
|
|
none_cases = 0,
|
|
started_cases = 0
|
|
)
|
|
results_mock.iteration = 1
|
|
def results_done_increment(value=1, decrement=False):
|
|
results_mock.done += value * (-1 if decrement else 1)
|
|
results_mock.done_increment = results_done_increment
|
|
def filtered_configs_increment(value=1, decrement=False):
|
|
results_mock.filtered_configs += value * (-1 if decrement else 1)
|
|
results_mock.filtered_configs_increment = filtered_configs_increment
|
|
def filtered_static_increment(value=1, decrement=False):
|
|
results_mock.filtered_static += value * (-1 if decrement else 1)
|
|
results_mock.filtered_static_increment = filtered_static_increment
|
|
def filtered_runtime_increment(value=1, decrement=False):
|
|
results_mock.filtered_runtime += value * (-1 if decrement else 1)
|
|
results_mock.filtered_runtime_increment = filtered_runtime_increment
|
|
def failed_increment(value=1, decrement=False):
|
|
results_mock.failed += value * (-1 if decrement else 1)
|
|
results_mock.failed_increment = failed_increment
|
|
def notrun_increment(value=1, decrement=False):
|
|
results_mock.notrun += value * (-1 if decrement else 1)
|
|
results_mock.notrun_increment = notrun_increment
|
|
|
|
pb.report_out(results_mock)
|
|
|
|
assert results_mock.cases_increment.call_args_list == [mock.call(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])
|
|
|
|
print(trim_actual_log)
|
|
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 = [
|
|
(TwisterStatus.ERROR, mock.ANY, False, False, False),
|
|
(TwisterStatus.FAIL, mock.ANY, False, False, False),
|
|
(TwisterStatus.SKIP, mock.ANY, False, False, False),
|
|
(TwisterStatus.FILTER, 'native', False, False, True),
|
|
(TwisterStatus.PASS, 'qemu', False, False, True),
|
|
(TwisterStatus.FILTER, 'unit', False, False, True),
|
|
(TwisterStatus.FILTER, 'mcu', True, True, False),
|
|
(TwisterStatus.PASS, '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
|
|
|
|
def iteration_increment(value=1, decrement=False):
|
|
results_mock().iteration += value * (-1 if decrement else 1)
|
|
results_mock().iteration_increment = iteration_increment
|
|
|
|
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=TwisterStatus.FILTER,
|
|
reason='runtime filter',
|
|
testsuite=mock.Mock(
|
|
testcases=[mock.Mock()]
|
|
)
|
|
),
|
|
'dummy2': mock.Mock(
|
|
status=TwisterStatus.FILTER,
|
|
reason='static filter',
|
|
testsuite=mock.Mock(
|
|
testcases=[mock.Mock(), mock.Mock(), mock.Mock(), mock.Mock()]
|
|
)
|
|
),
|
|
'dummy3': mock.Mock(
|
|
status=TwisterStatus.ERROR,
|
|
reason='error',
|
|
testsuite=mock.Mock(
|
|
testcases=[mock.Mock()]
|
|
)
|
|
),
|
|
'dummy4': mock.Mock(
|
|
status=TwisterStatus.PASS,
|
|
reason='OK',
|
|
testsuite=mock.Mock(
|
|
testcases=[mock.Mock()]
|
|
)
|
|
),
|
|
'dummy5': mock.Mock(
|
|
status=TwisterStatus.SKIP,
|
|
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(
|
|
total = 0,
|
|
done = 0,
|
|
passed = 0,
|
|
failed = 0,
|
|
filtered_configs = 0,
|
|
filtered_runtime = 0,
|
|
filtered_static = 0,
|
|
error = 0,
|
|
cases = 0,
|
|
filtered_cases = 0,
|
|
skipped_cases = 0,
|
|
failed_cases = 0,
|
|
error_cases = 0,
|
|
blocked_cases = 0,
|
|
passed_cases = 0,
|
|
none_cases = 0,
|
|
started_cases = 0
|
|
)
|
|
def filtered_configs_increment(value=1, decrement=False):
|
|
tr.results.filtered_configs += value * (-1 if decrement else 1)
|
|
tr.results.filtered_configs_increment = filtered_configs_increment
|
|
def filtered_static_increment(value=1, decrement=False):
|
|
tr.results.filtered_static += value * (-1 if decrement else 1)
|
|
tr.results.filtered_static_increment = filtered_static_increment
|
|
def error_increment(value=1, decrement=False):
|
|
tr.results.error += value * (-1 if decrement else 1)
|
|
tr.results.error_increment = error_increment
|
|
def cases_increment(value=1, decrement=False):
|
|
tr.results.cases += value * (-1 if decrement else 1)
|
|
tr.results.cases_increment = cases_increment
|
|
def filtered_cases_increment(value=1, decrement=False):
|
|
tr.results.filtered_cases += value * (-1 if decrement else 1)
|
|
tr.results.filtered_cases_increment = filtered_cases_increment
|
|
|
|
tr.update_counting_before_pipeline()
|
|
|
|
assert tr.results.filtered_static == 1
|
|
assert tr.results.filtered_configs == 1
|
|
assert tr.results.filtered_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(
|
|
filtered_static = 3,
|
|
filtered_configs = 4,
|
|
skipped_cases = 0,
|
|
cases = 0,
|
|
error = 0
|
|
)
|
|
|
|
tr.show_brief()
|
|
|
|
log = '2 test scenarios (5 configurations) selected,' \
|
|
' 4 configurations filtered (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=TwisterStatus.PASS, build_dir="/tmp"),
|
|
'dummy2': mock.Mock(run=True, retries=0, status=TwisterStatus.SKIP, build_dir="/tmp"),
|
|
'dummy3': mock.Mock(run=True, retries=0, status=TwisterStatus.FILTER, build_dir="/tmp"),
|
|
'dummy4': mock.Mock(run=True, retries=0, status=TwisterStatus.ERROR, build_dir="/tmp"),
|
|
'dummy5': mock.Mock(run=True, retries=0, status=TwisterStatus.FAIL, 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
|
|
)
|
|
tr.results = mock.Mock(iteration=0)
|
|
|
|
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)
|
|
process_mock().exitcode = 0
|
|
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)
|