zephyr/scripts/tests/twister_blackbox/test_coverage.py
Lukasz Mrugala 558c74be04 scripts: twister: decouple debug and verbosity
Currently, debug logging in the console and verbosity
are tightly coupled - verbosity of level 2 and higher
enables logging at the debug level.

This change introduces a separate Twister flag
responsible for controlling the debug logging,
while leaving the rest of verbosity unchanged.

This allows for controlling the verbosity on
both logging levels, according to one's needs.

Signed-off-by: Lukasz Mrugala <lukaszx.mrugala@intel.com>
2024-09-20 11:07:48 +02:00

391 lines
13 KiB
Python

#!/usr/bin/env python3
# Copyright (c) 2024 Intel Corporation
#
# SPDX-License-Identifier: Apache-2.0
"""
Blackbox tests for twister's command line functions
"""
import importlib
import re
import mock
import os
import pytest
import sys
import json
# pylint: disable=duplicate-code
from conftest import TEST_DATA, ZEPHYR_BASE, testsuite_filename_mock, clear_log_in_test
from twisterlib.testplan import TestPlan
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
class TestCoverage:
TESTDATA_1 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic'),
['qemu_x86'],
[
'coverage.log', 'coverage.json',
'coverage'
],
),
]
TESTDATA_2 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic'),
['qemu_x86'],
[
'GCOV_COVERAGE_DUMP_START', 'GCOV_COVERAGE_DUMP_END'
],
),
]
TESTDATA_3 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2'),
['qemu_x86'],
[
'coverage.log', 'coverage.json',
'coverage'
],
r'{"files": \[], "gcovr/format_version": ".*"}'
),
]
TESTDATA_4 = [
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','coverage.xml')
],
'xml'
),
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','coverage.sonarqube.xml')
],
'sonarqube'
),
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','coverage.txt')
],
'txt'
),
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','coverage.csv')
],
'csv'
),
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','coverage.coveralls.json')
],
'coveralls'
),
(
'gcovr',
[
'coverage.log', 'coverage.json',
'coverage', os.path.join('coverage','index.html')
],
'html'
),
(
'lcov',
[
'coverage.log', 'coverage.info',
'ztest.info', 'coverage',
os.path.join('coverage','index.html')
],
'html'
),
(
'lcov',
[
'coverage.log', 'coverage.info',
'ztest.info'
],
'lcov'
),
]
TESTDATA_5 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2'),
['qemu_x86'],
'gcovr',
'Running gcovr -r'
),
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2'),
['qemu_x86'],
'lcov',
'Running lcov --gcov-tool'
)
]
TESTDATA_6 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2'),
['qemu_x86'],
['The specified file does not exist.', r'\[Errno 13\] Permission denied:'],
)
]
TESTDATA_7 = [
(
os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2'),
['qemu_x86_64', 'qemu_x86'],
['qemu_x86_64', 'qemu_x86', ['qemu_x86_64', 'qemu_x86']],
)
]
@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)
@pytest.mark.parametrize(
'test_path, test_platforms, file_name',
TESTDATA_1,
ids=[
'coverage',
]
)
def test_coverage(self, capfd, test_path, test_platforms, out_path, file_name):
args = ['-i','--outdir', out_path, '-T', test_path] + \
['--coverage', '--coverage-tool', 'gcovr'] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '0'
for f_name in file_name:
path = os.path.join(out_path, f_name)
assert os.path.exists(path), f'file not found {f_name}'
@pytest.mark.parametrize(
'test_path, test_platforms, expected',
TESTDATA_2,
ids=[
'enable_coverage',
]
)
def test_enable_coverage(self, capfd, test_path, test_platforms, out_path, expected):
args = ['-i','--outdir', out_path, '-T', test_path] + \
['--enable-coverage', '-vv', '-ll', 'DEBUG'] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '0'
for line in expected:
match = re.search(line, err)
assert match, f'line not found: {line}'
@pytest.mark.parametrize(
'test_path, test_platforms, file_name, expected_content',
TESTDATA_3,
ids=[
'coverage_basedir',
]
)
def test_coverage_basedir(self, capfd, test_path, test_platforms, out_path, file_name, expected_content):
base_dir = os.path.join(TEST_DATA, "test_dir")
if os.path.exists(base_dir):
os.rmdir(base_dir)
os.mkdir(base_dir)
args = ['--outdir', out_path,'-i', '-T', test_path] + \
['--coverage', '--coverage-tool', 'gcovr', '-v', '--coverage-basedir', base_dir] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '0'
for f_name in file_name:
path = os.path.join(out_path, f_name)
assert os.path.exists(path), f'file not found {f_name}'
if f_name == 'coverage.json':
with open(path, "r") as json_file:
json_content = json.load(json_file)
pattern = re.compile(expected_content)
assert pattern.match(json.dumps(json_content))
if os.path.exists(base_dir):
os.rmdir(base_dir)
@pytest.mark.parametrize(
'cov_tool, file_name, cov_format',
TESTDATA_4,
ids=[
'coverage_format gcovr xml',
'coverage_format gcovr sonarqube',
'coverage_format gcovr txt',
'coverage_format gcovr csv',
'coverage_format gcovr coveralls',
'coverage_format gcovr html',
'coverage_format lcov html',
'coverage_format lcov lcov',
]
)
def test_coverage_format(self, capfd, out_path, cov_tool, file_name, cov_format):
test_path = os.path.join(TEST_DATA, 'tests', 'dummy', 'agnostic', 'group2')
test_platforms = ['qemu_x86']
args = ['--outdir', out_path,'-i', '-T', test_path] + \
['--coverage', '--coverage-tool', cov_tool, '--coverage-formats', cov_format, '-v'] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '0'
for f_name in file_name:
path = os.path.join(out_path, f_name)
assert os.path.exists(path), f'file not found {f_name}, probably format {cov_format} not work properly'
@pytest.mark.parametrize(
'test_path, test_platforms, cov_tool, expected_content',
TESTDATA_5,
ids=[
'coverage_tool gcovr',
'coverage_tool lcov'
]
)
def test_coverage_tool(self, capfd, caplog, test_path, test_platforms, out_path, cov_tool, expected_content):
args = ['--outdir', out_path,'-i', '-T', test_path] + \
['--coverage', '--coverage-tool', cov_tool, '-v'] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '0'
assert re.search(expected_content, caplog.text), f'{cov_tool} line not found'
@pytest.mark.parametrize(
'test_path, test_platforms, expected_content',
TESTDATA_6,
ids=[
'missing tool'
]
)
def test_gcov_tool(self, capfd, test_path, test_platforms, out_path, expected_content):
args = ['--outdir', out_path, '-i', '-T', test_path] + \
['--coverage', '--gcov-tool', TEST_DATA, '-v'] + \
[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)
out, err = capfd.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
assert str(sys_exit.value) == '1'
for line in expected_content:
result = re.search(line, err)
assert result, f'missing information in log: {line}'
@pytest.mark.parametrize(
'test_path, test_platforms, cov_platform',
TESTDATA_7,
ids=[
'coverage platform'
]
)
def test_coverage_platform(self, capfd, test_path, test_platforms, out_path, cov_platform):
def search_cov():
pattern = r'TOTAL\s+(\d+)'
coverage_file_path = os.path.join(out_path, 'coverage', 'coverage.txt')
with open(coverage_file_path, 'r') as file:
data = file.read()
match = re.search(pattern, data)
if match:
total = int(match.group(1))
return total
else:
print('Error, pattern not found')
run = []
for element in cov_platform:
args = ['--outdir', out_path, '-i', '-T', test_path] + \
['--coverage', '--coverage-formats', 'txt', '-v'] + \
[val for pair in zip(
['-p'] * len(test_platforms), test_platforms
) for val in pair]
if isinstance(element, list):
for nested_element in element:
args += ['--coverage-platform', nested_element]
else:
args += ['--coverage-platform', element]
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'
run += [search_cov()]
capfd.readouterr()
clear_log_in_test()
assert run[2] > run[0], 'Broader coverage platform selection did not result in broader coverage'
assert run[2] > run[1], 'Broader coverage platform selection did not result in broader coverage'