560 lines
15 KiB
Python
560 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2023 Intel Corporation
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
"""
|
|
Tests for environment.py classes' methods
|
|
"""
|
|
|
|
import mock
|
|
import os
|
|
import pytest
|
|
import shutil
|
|
|
|
from contextlib import nullcontext
|
|
|
|
import twisterlib.environment
|
|
|
|
|
|
TESTDATA_1 = [
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--short-build-path', '-k'],
|
|
'--short-build-path requires Ninja to be enabled'
|
|
),
|
|
(
|
|
'nt',
|
|
None,
|
|
None,
|
|
['--device-serial-pty', 'dummy'],
|
|
'--device-serial-pty is not supported on Windows OS'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--west-runner=dummy'],
|
|
'west-runner requires west-flash to be enabled'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--west-flash=\"--board-id=dummy\"'],
|
|
'west-flash requires device-testing to be enabled'
|
|
),
|
|
(
|
|
None,
|
|
{
|
|
'exist': [],
|
|
'missing': ['valgrind']
|
|
},
|
|
None,
|
|
['--enable-valgrind'],
|
|
'valgrind enabled but valgrind executable not found'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
[
|
|
'--device-testing',
|
|
'--device-serial',
|
|
'dummy',
|
|
'--platform',
|
|
'dummy_platform1',
|
|
'--platform',
|
|
'dummy_platform2'
|
|
],
|
|
'When --device-testing is used with --device-serial' \
|
|
' or --device-serial-pty, only one platform is allowed'
|
|
),
|
|
# Note the underscore.
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--device-flash-with-test'],
|
|
'--device-flash-with-test requires --device_testing'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--shuffle-tests'],
|
|
'--shuffle-tests requires --subset'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['--shuffle-tests-seed', '0'],
|
|
'--shuffle-tests-seed requires --shuffle-tests'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
None,
|
|
['/dummy/unrecognised/arg'],
|
|
'Unrecognized arguments found: \'/dummy/unrecognised/arg\'.' \
|
|
' Use -- to delineate extra arguments for test binary' \
|
|
' or pass -h for help.'
|
|
),
|
|
(
|
|
None,
|
|
None,
|
|
True,
|
|
[],
|
|
'By default Twister should work without pytest-twister-harness' \
|
|
' plugin being installed, so please, uninstall it by' \
|
|
' `pip uninstall pytest-twister-harness` and' \
|
|
' `git clean -dxf scripts/pylib/pytest-twister-harness`.'
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'os_name, which_dict, pytest_plugin, args, expected_error',
|
|
TESTDATA_1,
|
|
ids=[
|
|
'short build path without ninja',
|
|
'device-serial-pty on Windows',
|
|
'west runner without west flash',
|
|
'west-flash without device-testing',
|
|
'valgrind without executable',
|
|
'device serial with multiple platforms',
|
|
'device flash with test without device testing',
|
|
'shuffle-tests without subset',
|
|
'shuffle-tests-seed without shuffle-tests',
|
|
'unrecognised argument',
|
|
'pytest-twister-harness installed'
|
|
]
|
|
)
|
|
def test_parse_arguments_errors(
|
|
caplog,
|
|
os_name,
|
|
which_dict,
|
|
pytest_plugin,
|
|
args,
|
|
expected_error
|
|
):
|
|
def mock_which(name):
|
|
if name in which_dict['missing']:
|
|
return False
|
|
elif name in which_dict['exist']:
|
|
return which_dict['path'][which_dict['exist']] \
|
|
if which_dict['path'][which_dict['exist']] \
|
|
else f'dummy/path/{name}'
|
|
else:
|
|
return f'dummy/path/{name}'
|
|
|
|
with mock.patch('sys.argv', ['twister'] + args):
|
|
parser = twisterlib.environment.add_parse_arguments()
|
|
|
|
if which_dict:
|
|
which_dict['path'] = {name: shutil.which(name) \
|
|
for name in which_dict['exist']}
|
|
which_mock = mock.Mock(side_effect=mock_which)
|
|
|
|
with mock.patch('os.name', os_name) \
|
|
if os_name is not None else nullcontext(), \
|
|
mock.patch('shutil.which', which_mock) \
|
|
if which_dict else nullcontext(), \
|
|
mock.patch('twisterlib.environment' \
|
|
'.PYTEST_PLUGIN_INSTALLED', pytest_plugin) \
|
|
if pytest_plugin is not None else nullcontext():
|
|
with pytest.raises(SystemExit) as exit_info:
|
|
twisterlib.environment.parse_arguments(parser, args)
|
|
|
|
assert exit_info.value.code == 1
|
|
assert expected_error in ' '.join(caplog.text.split())
|
|
|
|
|
|
def test_parse_arguments_errors_size():
|
|
"""`options.size` is not an error, rather a different functionality."""
|
|
|
|
args = ['--size', 'dummy.elf']
|
|
|
|
with mock.patch('sys.argv', ['twister'] + args):
|
|
parser = twisterlib.environment.add_parse_arguments()
|
|
|
|
mock_calc_parent = mock.Mock()
|
|
mock_calc_parent.child = mock.Mock(return_value=mock.Mock())
|
|
|
|
def mock_calc(*args, **kwargs):
|
|
return mock_calc_parent.child(args, kwargs)
|
|
|
|
with mock.patch('twisterlib.size_calc.SizeCalculator', mock_calc):
|
|
with pytest.raises(SystemExit) as exit_info:
|
|
twisterlib.environment.parse_arguments(parser, args)
|
|
|
|
assert exit_info.value.code == 0
|
|
|
|
mock_calc_parent.child.assert_has_calls([mock.call(('dummy.elf', []), {})])
|
|
mock_calc_parent.child().size_report.assert_has_calls([mock.call()])
|
|
|
|
|
|
def test_parse_arguments_warnings(caplog):
|
|
args = ['--allow-installed-plugin']
|
|
|
|
with mock.patch('sys.argv', ['twister'] + args):
|
|
parser = twisterlib.environment.add_parse_arguments()
|
|
|
|
with mock.patch('twisterlib.environment.PYTEST_PLUGIN_INSTALLED', True):
|
|
twisterlib.environment.parse_arguments(parser, args)
|
|
|
|
assert 'You work with installed version of' \
|
|
' pytest-twister-harness plugin.' in ' '.join(caplog.text.split())
|
|
|
|
|
|
TESTDATA_2 = [
|
|
(['--show-footprint']),
|
|
(['--compare-report', 'dummy']),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'additional_args',
|
|
TESTDATA_2,
|
|
ids=['show footprint', 'compare report']
|
|
)
|
|
def test_parse_arguments(zephyr_base, additional_args):
|
|
args = ['--coverage', '--platform', 'dummy_platform'] + \
|
|
additional_args + ['--', 'dummy_extra_1', 'dummy_extra_2']
|
|
|
|
with mock.patch('sys.argv', ['twister'] + args):
|
|
parser = twisterlib.environment.add_parse_arguments()
|
|
|
|
options = twisterlib.environment.parse_arguments(parser, args)
|
|
|
|
assert os.path.join(zephyr_base, 'tests') in options.testsuite_root
|
|
assert os.path.join(zephyr_base, 'samples') in options.testsuite_root
|
|
|
|
assert options.enable_size_report
|
|
|
|
assert options.enable_coverage
|
|
|
|
assert options.coverage_platform == ['dummy_platform']
|
|
|
|
assert options.extra_test_args == ['dummy_extra_1', 'dummy_extra_2']
|
|
|
|
|
|
TESTDATA_3 = [
|
|
(
|
|
None,
|
|
mock.Mock(
|
|
generator_cmd='make',
|
|
generator='Unix Makefiles',
|
|
test_roots=None,
|
|
board_roots=None,
|
|
outdir=None,
|
|
)
|
|
),
|
|
(
|
|
mock.Mock(
|
|
ninja=True,
|
|
board_root=['dummy1', 'dummy2'],
|
|
testsuite_root=[
|
|
os.path.join('dummy', 'path', "tests"),
|
|
os.path.join('dummy', 'path', "samples")
|
|
]
|
|
),
|
|
mock.Mock(
|
|
generator_cmd='ninja',
|
|
generator='Ninja',
|
|
test_roots=[
|
|
os.path.join('dummy', 'path', "tests"),
|
|
os.path.join('dummy', 'path', "samples")
|
|
],
|
|
board_roots=['dummy1', 'dummy2'],
|
|
outdir='dummy_abspath',
|
|
)
|
|
),
|
|
(
|
|
mock.Mock(
|
|
ninja=False,
|
|
board_root='dummy0',
|
|
testsuite_root=[
|
|
os.path.join('dummy', 'path', "tests"),
|
|
os.path.join('dummy', 'path', "samples")
|
|
]
|
|
),
|
|
mock.Mock(
|
|
generator_cmd='make',
|
|
generator='Unix Makefiles',
|
|
test_roots=[
|
|
os.path.join('dummy', 'path', "tests"),
|
|
os.path.join('dummy', 'path', "samples")
|
|
],
|
|
board_roots=['dummy0'],
|
|
outdir='dummy_abspath',
|
|
)
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'options, expected_env',
|
|
TESTDATA_3,
|
|
ids=[
|
|
'no options',
|
|
'ninja',
|
|
'make'
|
|
]
|
|
)
|
|
def test_twisterenv_init(options, expected_env):
|
|
with mock.patch(
|
|
'os.path.abspath',
|
|
mock.Mock(return_value='dummy_abspath')):
|
|
twister_env = twisterlib.environment.TwisterEnv(options=options)
|
|
|
|
assert twister_env.generator_cmd == expected_env.generator_cmd
|
|
assert twister_env.generator == expected_env.generator
|
|
|
|
assert twister_env.test_roots == expected_env.test_roots
|
|
|
|
assert twister_env.board_roots == expected_env.board_roots
|
|
assert twister_env.outdir == expected_env.outdir
|
|
|
|
|
|
def test_twisterenv_discover():
|
|
options = mock.Mock(
|
|
ninja=True
|
|
)
|
|
|
|
abspath_mock = mock.Mock(return_value='dummy_abspath')
|
|
|
|
with mock.patch('os.path.abspath', abspath_mock):
|
|
twister_env = twisterlib.environment.TwisterEnv(options=options)
|
|
|
|
mock_datetime = mock.Mock(
|
|
now=mock.Mock(
|
|
return_value=mock.Mock(
|
|
isoformat=mock.Mock(return_value='dummy_time')
|
|
)
|
|
)
|
|
)
|
|
|
|
with mock.patch.object(
|
|
twisterlib.environment.TwisterEnv,
|
|
'check_zephyr_version',
|
|
mock.Mock()) as mock_czv, \
|
|
mock.patch.object(
|
|
twisterlib.environment.TwisterEnv,
|
|
'get_toolchain',
|
|
mock.Mock()) as mock_gt, \
|
|
mock.patch('twisterlib.environment.datetime', mock_datetime):
|
|
twister_env.discover()
|
|
|
|
mock_czv.assert_called_once()
|
|
mock_gt.assert_called_once()
|
|
assert twister_env.run_date == 'dummy_time'
|
|
|
|
|
|
TESTDATA_4 = [
|
|
(
|
|
mock.Mock(returncode=0, stdout='dummy stdout version'),
|
|
mock.Mock(returncode=0, stdout='dummy stdout date'),
|
|
['Zephyr version: dummy stdout version'],
|
|
'dummy stdout version',
|
|
'dummy stdout date'
|
|
),
|
|
(
|
|
mock.Mock(returncode=0, stdout=''),
|
|
mock.Mock(returncode=0, stdout='dummy stdout date'),
|
|
['Could not determine version'],
|
|
'Unknown',
|
|
'dummy stdout date'
|
|
),
|
|
(
|
|
mock.Mock(returncode=1, stdout='dummy stdout version'),
|
|
mock.Mock(returncode=0, stdout='dummy stdout date'),
|
|
['Could not determine version'],
|
|
'Unknown',
|
|
'dummy stdout date'
|
|
),
|
|
(
|
|
OSError,
|
|
mock.Mock(returncode=1),
|
|
['Could not determine version'],
|
|
'Unknown',
|
|
'Unknown'
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'git_describe_return, git_show_return, expected_logs,' \
|
|
' expected_version, expected_commit_date',
|
|
TESTDATA_4,
|
|
ids=[
|
|
'valid',
|
|
'no zephyr version on describe',
|
|
'error on git describe',
|
|
'execution error on git describe',
|
|
]
|
|
)
|
|
def test_twisterenv_check_zephyr_version(
|
|
caplog,
|
|
git_describe_return,
|
|
git_show_return,
|
|
expected_logs,
|
|
expected_version,
|
|
expected_commit_date
|
|
):
|
|
def mock_run(command, *args, **kwargs):
|
|
if all([keyword in command for keyword in ['git', 'describe']]):
|
|
if isinstance(git_describe_return, type) and \
|
|
issubclass(git_describe_return, Exception):
|
|
raise git_describe_return()
|
|
return git_describe_return
|
|
if all([keyword in command for keyword in ['git', 'show']]):
|
|
if isinstance(git_show_return, type) and \
|
|
issubclass(git_show_return, Exception):
|
|
raise git_show_return()
|
|
return git_show_return
|
|
|
|
options = mock.Mock(
|
|
ninja=True
|
|
)
|
|
|
|
abspath_mock = mock.Mock(return_value='dummy_abspath')
|
|
|
|
with mock.patch('os.path.abspath', abspath_mock):
|
|
twister_env = twisterlib.environment.TwisterEnv(options=options)
|
|
|
|
with mock.patch('subprocess.run', mock.Mock(side_effect=mock_run)):
|
|
twister_env.check_zephyr_version()
|
|
print(expected_logs)
|
|
print(caplog.text)
|
|
assert twister_env.version == expected_version
|
|
assert twister_env.commit_date == expected_commit_date
|
|
assert all([expected_log in caplog.text for expected_log in expected_logs])
|
|
|
|
|
|
TESTDATA_5 = [
|
|
(
|
|
False,
|
|
None,
|
|
None,
|
|
'Unable to find `cmake` in path',
|
|
None
|
|
),
|
|
(
|
|
True,
|
|
0,
|
|
b'somedummy\x1B[123-@d1770',
|
|
'Finished running dummy/script/path',
|
|
{
|
|
'returncode': 0,
|
|
'msg': 'Finished running dummy/script/path',
|
|
'stdout': 'somedummyd1770',
|
|
}
|
|
),
|
|
(
|
|
True,
|
|
1,
|
|
b'another\x1B_dummy',
|
|
'Cmake script failure: dummy/script/path',
|
|
{
|
|
'returncode': 1,
|
|
'returnmsg': 'anotherdummy'
|
|
}
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'find_cmake, return_code, out, expected_log, expected_result',
|
|
TESTDATA_5,
|
|
ids=[
|
|
'cmake not found',
|
|
'regex sanitation 1',
|
|
'regex sanitation 2'
|
|
]
|
|
)
|
|
def test_twisterenv_run_cmake_script(
|
|
caplog,
|
|
find_cmake,
|
|
return_code,
|
|
out,
|
|
expected_log,
|
|
expected_result
|
|
):
|
|
def mock_which(name, *args, **kwargs):
|
|
return 'dummy/cmake/path' if find_cmake else None
|
|
|
|
def mock_popen(command, *args, **kwargs):
|
|
return mock.Mock(
|
|
pid=0,
|
|
returncode=return_code,
|
|
communicate=mock.Mock(
|
|
return_value=(out, '')
|
|
)
|
|
)
|
|
|
|
args = ['dummy/script/path', 'var1=val1']
|
|
|
|
with mock.patch('shutil.which', mock_which), \
|
|
mock.patch('subprocess.Popen', mock.Mock(side_effect=mock_popen)), \
|
|
pytest.raises(Exception) \
|
|
if not find_cmake else nullcontext() as exception:
|
|
results = twisterlib.environment.TwisterEnv.run_cmake_script(args)
|
|
|
|
assert 'Running cmake script dummy/script/path' in caplog.text
|
|
|
|
assert expected_log in caplog.text
|
|
|
|
if exception is not None:
|
|
return
|
|
|
|
assert expected_result.items() <= results.items()
|
|
|
|
|
|
TESTDATA_6 = [
|
|
(
|
|
{
|
|
'returncode': 0,
|
|
'stdout': '{\"ZEPHYR_TOOLCHAIN_VARIANT\": \"dummy toolchain\"}'
|
|
},
|
|
None,
|
|
'Using \'dummy toolchain\' toolchain.'
|
|
),
|
|
(
|
|
{'returncode': 1},
|
|
2,
|
|
None
|
|
),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
'script_result, exit_value, expected_log',
|
|
TESTDATA_6,
|
|
ids=['valid', 'error']
|
|
)
|
|
def test_get_toolchain(caplog, script_result, exit_value, expected_log):
|
|
options = mock.Mock(
|
|
ninja=True
|
|
)
|
|
|
|
abspath_mock = mock.Mock(return_value='dummy_abspath')
|
|
|
|
with mock.patch('os.path.abspath', abspath_mock):
|
|
twister_env = twisterlib.environment.TwisterEnv(options=options)
|
|
|
|
with mock.patch.object(
|
|
twisterlib.environment.TwisterEnv,
|
|
'run_cmake_script',
|
|
mock.Mock(return_value=script_result)), \
|
|
pytest.raises(SystemExit) \
|
|
if exit_value is not None else nullcontext() as exit_info:
|
|
twister_env.get_toolchain()
|
|
|
|
if exit_info is not None:
|
|
assert exit_info.value.code == exit_value
|
|
else:
|
|
assert expected_log in caplog.text
|