twister: refactor twister_main to simplify the system tests
Refactored twister_main.py module to simplify the code and system tests. Removed the need to patch `sys.argv` in blackbox tests. Signed-off-by: Lukasz Fundakowski <lukasz.fundakowski@nordicsemi.no>
This commit is contained in:
committed by
Benjamin Cabé
parent
654d794dd0
commit
4f637258ef
@@ -9,11 +9,17 @@ import os
|
||||
import shutil
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
|
||||
import colorama
|
||||
from colorama import Fore
|
||||
from twisterlib.coverage import run_coverage
|
||||
from twisterlib.environment import TwisterEnv
|
||||
from twisterlib.environment import (
|
||||
TwisterEnv,
|
||||
add_parse_arguments,
|
||||
parse_arguments,
|
||||
python_version_guard,
|
||||
)
|
||||
from twisterlib.hardwaremap import HardwareMap
|
||||
from twisterlib.log_helper import close_logging, setup_logging
|
||||
from twisterlib.package import Artifacts
|
||||
@@ -27,7 +33,7 @@ def init_color(colorama_strip):
|
||||
colorama.init(strip=colorama_strip)
|
||||
|
||||
|
||||
def twister(options: argparse.Namespace, default_options: argparse.Namespace):
|
||||
def twister(options: argparse.Namespace, default_options: argparse.Namespace) -> int:
|
||||
start_time = time.time()
|
||||
|
||||
# Configure color output
|
||||
@@ -230,9 +236,17 @@ def twister(options: argparse.Namespace, default_options: argparse.Namespace):
|
||||
return 0
|
||||
|
||||
|
||||
def main(options: argparse.Namespace, default_options: argparse.Namespace):
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
"""Main function to run twister."""
|
||||
try:
|
||||
return_code = twister(options, default_options)
|
||||
python_version_guard()
|
||||
|
||||
parser = add_parse_arguments()
|
||||
options = parse_arguments(parser, argv)
|
||||
default_options = parse_arguments(parser, [], on_init=False)
|
||||
return twister(options, default_options)
|
||||
finally:
|
||||
close_logging()
|
||||
return return_code
|
||||
if (os.name != "nt") and os.isatty(1):
|
||||
# (OS is not Windows) and (stdout is interactive)
|
||||
os.system("stty sane <&1")
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
Blackbox tests for twister's command line functions related to test filtering.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from unittest import mock
|
||||
import os
|
||||
import pytest
|
||||
@@ -14,63 +13,35 @@ import sys
|
||||
import re
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
|
||||
from conftest import TEST_DATA, suite_filename_mock
|
||||
from twisterlib.testplan import TestPlan
|
||||
from twisterlib.twister_main import main as twister_main
|
||||
|
||||
|
||||
class TestDevice:
|
||||
TESTDATA_1 = [
|
||||
(
|
||||
1234,
|
||||
),
|
||||
(
|
||||
4321,
|
||||
),
|
||||
(
|
||||
1324,
|
||||
)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
|
||||
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
|
||||
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
|
||||
cls.twister_module = importlib.util.module_from_spec(cls.spec)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'seed',
|
||||
TESTDATA_1,
|
||||
ids=[
|
||||
'seed 1234',
|
||||
'seed 4321',
|
||||
'seed 1324'
|
||||
],
|
||||
[1234, 4321, 1324],
|
||||
)
|
||||
|
||||
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
|
||||
def test_seed(self, capfd, out_path, seed):
|
||||
test_platforms = ['native_sim']
|
||||
path = os.path.join(TEST_DATA, 'tests', 'seed_native_sim')
|
||||
args = ['--no-detailed-test-id', '--outdir', out_path, '-i', '-T', path, '-vv',] + \
|
||||
['--seed', f'{seed[0]}'] + \
|
||||
[val for pair in zip(
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
args = [
|
||||
'--no-detailed-test-id', '--outdir', out_path, '-i', '-T', path, '-vv',
|
||||
'--seed', f'{seed}',
|
||||
*[val for pair in zip(['-p'] * len(test_platforms), test_platforms) for val in pair]
|
||||
]
|
||||
|
||||
return_value = twister_main(args)
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
|
||||
assert str(sys_exit.value) == '1'
|
||||
assert return_value == 1
|
||||
|
||||
expected_line = r'seed_native_sim.dummy\s+FAILED rc=1 \(native (\d+\.\d+)s/seed: {} <host>\)'.format(seed[0])
|
||||
assert re.search(expected_line, err)
|
||||
expected_line = r'seed_native_sim.dummy\s+FAILED rc=1 \(native (\d+\.\d+)s/seed: {} <host>\)'.format(seed)
|
||||
assert re.search(expected_line, err), f'Regex not found: r"{expected_line}"'
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
Blackbox tests for twister's command line functions related to disable features.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import pytest
|
||||
from unittest import mock
|
||||
import os
|
||||
@@ -14,8 +13,9 @@ import sys
|
||||
import re
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
|
||||
from conftest import TEST_DATA, suite_filename_mock
|
||||
from twisterlib.testplan import TestPlan
|
||||
from twisterlib.twister_main import main as twister_main
|
||||
|
||||
|
||||
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
|
||||
@@ -41,30 +41,17 @@ class TestDisable:
|
||||
os.path.join(TEST_DATA, 'tests', 'always_warning'),
|
||||
['qemu_x86'],
|
||||
'--disable-warnings-as-errors',
|
||||
'0'
|
||||
0
|
||||
),
|
||||
(
|
||||
os.path.join(TEST_DATA, 'tests', 'always_warning'),
|
||||
['qemu_x86'],
|
||||
'-v',
|
||||
'1'
|
||||
1
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
|
||||
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
|
||||
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
|
||||
cls.twister_module = importlib.util.module_from_spec(cls.spec)
|
||||
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'test_path, test_platforms, flag, expected, expected_none',
|
||||
TESTDATA_1,
|
||||
@@ -82,15 +69,14 @@ class TestDisable:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
return_value = twister_main(args)
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
assert return_value == 0
|
||||
if expected_none:
|
||||
assert re.search(expected[0], err) is None, f"Not expected string in log: {expected[0]}"
|
||||
assert re.search(expected[1], err) is None, f"Not expected: {expected[1]}"
|
||||
@@ -117,13 +103,12 @@ class TestDisable:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
return_value = twister_main(args)
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
|
||||
assert str(sys_exit.value) == expected_exit_code, \
|
||||
f"Twister return not expected ({expected_exit_code}) exit code: ({sys_exit.value})"
|
||||
assert return_value == expected_exit_code, \
|
||||
f"Twister return not expected ({expected_exit_code}) exit code: ({return_value})"
|
||||
|
||||
@@ -6,29 +6,17 @@
|
||||
Blackbox tests for twister's command line functions related to saving and loading a testlist.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from unittest import mock
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
import json
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock, clear_log_in_test
|
||||
from conftest import TEST_DATA, suite_filename_mock, clear_log_in_test
|
||||
from twisterlib.testplan import TestPlan
|
||||
from twisterlib.twister_main import main as twister_main
|
||||
|
||||
|
||||
class TestTestlist:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
|
||||
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
|
||||
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
|
||||
cls.twister_module = importlib.util.module_from_spec(cls.spec)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', suite_filename_mock)
|
||||
def test_save_tests(self, out_path):
|
||||
@@ -42,11 +30,7 @@ class TestTestlist:
|
||||
) for val in pair]
|
||||
|
||||
# Save agnostics tests
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
assert twister_main(args) == 0
|
||||
|
||||
clear_log_in_test()
|
||||
|
||||
@@ -58,11 +42,7 @@ class TestTestlist:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
assert twister_main(args) == 0
|
||||
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
|
||||
@@ -6,22 +6,21 @@
|
||||
Blackbox tests for twister's command line functions - those requiring testplan.json
|
||||
"""
|
||||
|
||||
import importlib
|
||||
from unittest import mock
|
||||
import os
|
||||
import pytest
|
||||
import sys
|
||||
import json
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from conftest import ZEPHYR_BASE, TEST_DATA, suite_filename_mock
|
||||
from conftest import TEST_DATA, suite_filename_mock
|
||||
from twisterlib.testplan import TestPlan
|
||||
from twisterlib.error import TwisterRuntimeError
|
||||
from twisterlib.twister_main import main as twister_main
|
||||
|
||||
|
||||
class TestTestPlan:
|
||||
TESTDATA_1 = [
|
||||
('dummy.agnostic.group2.a2_tests.assert1', SystemExit, 4),
|
||||
('dummy.agnostic.group2.a2_tests.assert1', None, 4),
|
||||
(
|
||||
os.path.join('scripts', 'tests', 'twister_blackbox', 'test_data', 'tests',
|
||||
'dummy', 'agnostic', 'group1', 'subgroup1',
|
||||
@@ -39,17 +38,6 @@ class TestTestPlan:
|
||||
(False, 7),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
|
||||
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
|
||||
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
|
||||
cls.twister_module = importlib.util.module_from_spec(cls.spec)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'test, expected_exception, expected_subtest_count',
|
||||
TESTDATA_1,
|
||||
@@ -65,24 +53,21 @@ class TestTestPlan:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(expected_exception) as exc:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
if expected_exception:
|
||||
with pytest.raises(expected_exception):
|
||||
twister_main(args)
|
||||
else:
|
||||
return_value = twister_main(args)
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
filtered_j = [
|
||||
(ts['platform'], ts['name'], tc['identifier']) \
|
||||
for ts in j['testsuites'] \
|
||||
for tc in ts['testcases'] if 'reason' not in tc
|
||||
]
|
||||
|
||||
if expected_exception != SystemExit:
|
||||
assert True
|
||||
return
|
||||
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
filtered_j = [
|
||||
(ts['platform'], ts['name'], tc['identifier']) \
|
||||
for ts in j['testsuites'] \
|
||||
for tc in ts['testcases'] if 'reason' not in tc
|
||||
]
|
||||
|
||||
assert str(exc.value) == '0'
|
||||
assert len(filtered_j) == expected_subtest_count
|
||||
assert return_value == 0
|
||||
assert len(filtered_j) == expected_subtest_count
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'filter, expected_count',
|
||||
@@ -98,11 +83,8 @@ class TestTestPlan:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as exc:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
assert twister_main(args) == 0
|
||||
|
||||
assert str(exc.value) == '0'
|
||||
import pprint
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
@@ -132,11 +114,7 @@ class TestTestPlan:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as exc:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
assert str(exc.value) == '0'
|
||||
assert twister_main(args) == 0
|
||||
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
|
||||
@@ -7,7 +7,6 @@ Blackbox tests for twister's command line functions related to Twister's tooling
|
||||
"""
|
||||
# pylint: disable=duplicate-code
|
||||
|
||||
import importlib
|
||||
from unittest import mock
|
||||
import os
|
||||
import pytest
|
||||
@@ -15,22 +14,13 @@ import sys
|
||||
import json
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from conftest import ZEPHYR_BASE, TEST_DATA, sample_filename_mock, suite_filename_mock
|
||||
from conftest import TEST_DATA, sample_filename_mock, suite_filename_mock
|
||||
from twisterlib.statuses import TwisterStatus
|
||||
from twisterlib.testplan import TestPlan
|
||||
from twisterlib.twister_main import main as twister_main
|
||||
|
||||
|
||||
class TestTooling:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
apath = os.path.join(ZEPHYR_BASE, 'scripts', 'twister')
|
||||
cls.loader = importlib.machinery.SourceFileLoader('__main__', apath)
|
||||
cls.spec = importlib.util.spec_from_loader(cls.loader.name, cls.loader)
|
||||
cls.twister_module = importlib.util.module_from_spec(cls.spec)
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
pass
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'jobs',
|
||||
@@ -47,15 +37,14 @@ class TestTooling:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
return_value = twister_main(args)
|
||||
|
||||
with open(os.path.join(out_path, 'twister.log')) as f:
|
||||
log = f.read()
|
||||
assert f'JOBS: {jobs}' in log
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
assert return_value == 0
|
||||
|
||||
@mock.patch.object(TestPlan, 'SAMPLE_FILENAME', sample_filename_mock)
|
||||
def test_force_toolchain(self, out_path):
|
||||
@@ -69,11 +58,7 @@ class TestTooling:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
return_value = twister_main(args)
|
||||
|
||||
with open(os.path.join(out_path, 'testplan.json')) as f:
|
||||
j = json.load(f)
|
||||
@@ -87,6 +72,8 @@ class TestTooling:
|
||||
assert len(filtered_j) == 1
|
||||
assert filtered_j[0][3] != TwisterStatus.FILTER
|
||||
|
||||
assert return_value == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'test_path, test_platforms',
|
||||
[
|
||||
@@ -110,12 +97,10 @@ class TestTooling:
|
||||
['-p'] * len(test_platforms), test_platforms
|
||||
) for val in pair]
|
||||
|
||||
with mock.patch.object(sys, 'argv', [sys.argv[0]] + args), \
|
||||
pytest.raises(SystemExit) as sys_exit:
|
||||
self.loader.exec_module(self.twister_module)
|
||||
return_value = twister_main(args)
|
||||
|
||||
out, err = capfd.readouterr()
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
|
||||
assert str(sys_exit.value) == '0'
|
||||
assert return_value == 0
|
||||
|
||||
@@ -186,7 +186,6 @@ import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
|
||||
if not ZEPHYR_BASE:
|
||||
# This file has been zephyr/scripts/twister for years,
|
||||
@@ -203,22 +202,7 @@ if not ZEPHYR_BASE:
|
||||
sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/twister/"))
|
||||
sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts/pylib/build_helpers"))
|
||||
|
||||
from twisterlib.environment import add_parse_arguments, parse_arguments, python_version_guard
|
||||
from twisterlib.twister_main import main
|
||||
|
||||
from twisterlib.twister_main import main # noqa: E402
|
||||
|
||||
if __name__ == "__main__":
|
||||
ret = 0
|
||||
try:
|
||||
python_version_guard()
|
||||
|
||||
parser = add_parse_arguments()
|
||||
options = parse_arguments(parser, sys.argv[1:])
|
||||
default_options = parse_arguments(parser, [], on_init=False)
|
||||
ret = main(options, default_options)
|
||||
finally:
|
||||
if (os.name != "nt") and os.isatty(1):
|
||||
# (OS is not Windows) and (stdout is interactive)
|
||||
os.system("stty sane <&1")
|
||||
|
||||
sys.exit(ret)
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user