300507d43b
Ignore no-name-in-module pylint warnings. Signed-off-by: Dmitrii Golovanov <dmitrii.golovanov@intel.com>
482 lines
19 KiB
Python
482 lines
19 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 related to memory footprints.
|
|
"""
|
|
|
|
import importlib
|
|
import json
|
|
import mock
|
|
import os
|
|
import pytest
|
|
import sys
|
|
import re
|
|
|
|
# pylint: disable=no-name-in-module
|
|
from conftest import ZEPHYR_BASE, TEST_DATA, testsuite_filename_mock, clear_log_in_test
|
|
from twisterlib.testplan import TestPlan
|
|
|
|
|
|
@mock.patch.object(TestPlan, 'TESTSUITE_FILENAME', testsuite_filename_mock)
|
|
class TestFootprint:
|
|
# Log printed when entering delta calculations
|
|
FOOTPRINT_LOG = 'running footprint_reports'
|
|
|
|
# These warnings notify us that deltas were shown in log.
|
|
# Coupled with the code under test.
|
|
DELTA_WARNING_COMPARE = re.compile(
|
|
r'Found [1-9]+[0-9]* footprint deltas to .*blackbox-out\.[0-9]+/twister.json as a baseline'
|
|
)
|
|
DELTA_WARNING_RUN = re.compile(r'Found [1-9]+[0-9]* footprint deltas to the last twister run')
|
|
|
|
# Size report key we modify to control for deltas
|
|
RAM_KEY = 'used_ram'
|
|
DELTA_DETAIL = re.compile(RAM_KEY + r' \+[0-9]+, is now +[0-9]+ \+[0-9.]+%')
|
|
|
|
@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(
|
|
'old_ram_multiplier, expect_delta_log',
|
|
[
|
|
(0.75, True),
|
|
(1.25, False),
|
|
],
|
|
ids=['footprint increased', 'footprint reduced']
|
|
)
|
|
def test_compare_report(self, caplog, out_path, old_ram_multiplier, expect_delta_log):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Modify the older report so we can control the difference.
|
|
# Note: if footprint tests take too long, replace first run with a prepared twister.json
|
|
# That will increase test-to-code_under_test coupling, however.
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
# We assume positive RAM usage.
|
|
ts[self.RAM_KEY] *= old_ram_multiplier
|
|
with open(os.path.join(out_path, 'twister.json'), 'w') as f:
|
|
f.write(json.dumps(j, indent=4))
|
|
|
|
report_path = os.path.join(
|
|
os.path.dirname(out_path),
|
|
f'{os.path.basename(out_path)}.1',
|
|
'twister.json'
|
|
)
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--compare-report', report_path] + \
|
|
['--show-footprint'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
assert self.FOOTPRINT_LOG in caplog.text
|
|
|
|
if expect_delta_log:
|
|
assert self.RAM_KEY in caplog.text
|
|
assert re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Expected footprint deltas not logged.'
|
|
else:
|
|
assert self.RAM_KEY not in caplog.text
|
|
assert not re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Unexpected footprint deltas logged.'
|
|
|
|
def test_footprint_from_buildlog(self, out_path):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
[] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Get values
|
|
old_values = []
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
assert self.RAM_KEY in ts
|
|
old_values += [ts[self.RAM_KEY]]
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--footprint-from-buildlog'] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Get values
|
|
new_values = []
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
assert self.RAM_KEY in ts
|
|
new_values += [ts[self.RAM_KEY]]
|
|
|
|
# There can be false positives if our calculations become too accurate.
|
|
# Turn this test into a dummy (check only exit value) in such case.
|
|
assert sorted(old_values) != sorted(new_values), \
|
|
'Same values whether calculating size or using buildlog.'
|
|
|
|
@pytest.mark.parametrize(
|
|
'old_ram_multiplier, threshold, expect_delta_log',
|
|
[
|
|
(0.75, 95, False),
|
|
(0.75, 25, True),
|
|
],
|
|
ids=['footprint threshold not met', 'footprint threshold met']
|
|
)
|
|
def test_footprint_threshold(self, caplog, out_path, old_ram_multiplier,
|
|
threshold, expect_delta_log):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Modify the older report so we can control the difference.
|
|
# Note: if footprint tests take too long, replace first run with a prepared twister.json
|
|
# That will increase test-to-code_under_test coupling, however.
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
# We assume positive RAM usage.
|
|
ts[self.RAM_KEY] *= old_ram_multiplier
|
|
with open(os.path.join(out_path, 'twister.json'), 'w') as f:
|
|
f.write(json.dumps(j, indent=4))
|
|
|
|
report_path = os.path.join(
|
|
os.path.dirname(out_path),
|
|
f'{os.path.basename(out_path)}.1',
|
|
'twister.json'
|
|
)
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
[f'--footprint-threshold={threshold}'] + \
|
|
['--compare-report', report_path, '--show-footprint'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
assert self.FOOTPRINT_LOG in caplog.text
|
|
|
|
if expect_delta_log:
|
|
assert self.RAM_KEY in caplog.text
|
|
assert re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Expected footprint deltas not logged.'
|
|
else:
|
|
assert self.RAM_KEY not in caplog.text
|
|
assert not re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Unexpected footprint deltas logged.'
|
|
|
|
@pytest.mark.parametrize(
|
|
'flags, old_ram_multiplier, expect_delta_log',
|
|
[
|
|
([], 0.75, False),
|
|
(['--show-footprint'], 0.75, True),
|
|
],
|
|
ids=['footprint reduced, no show', 'footprint reduced, show']
|
|
)
|
|
def test_show_footprint(self, caplog, out_path, flags, old_ram_multiplier, expect_delta_log):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Modify the older report so we can control the difference.
|
|
# Note: if footprint tests take too long, replace first run with a prepared twister.json
|
|
# That will increase test-to-code_under_test coupling, however.
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
# We assume positive RAM usage.
|
|
ts[self.RAM_KEY] *= old_ram_multiplier
|
|
with open(os.path.join(out_path, 'twister.json'), 'w') as f:
|
|
f.write(json.dumps(j, indent=4))
|
|
|
|
report_path = os.path.join(
|
|
os.path.dirname(out_path),
|
|
f'{os.path.basename(out_path)}.1',
|
|
'twister.json'
|
|
)
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
flags + \
|
|
['--compare-report', report_path] + \
|
|
[val for pair in zip(
|
|
['-p'] * len(test_platforms), test_platforms
|
|
) for val in pair]
|
|
print(args)
|
|
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 self.FOOTPRINT_LOG in caplog.text
|
|
|
|
if expect_delta_log:
|
|
assert self.RAM_KEY in caplog.text
|
|
assert re.search(self.DELTA_DETAIL, caplog.text), \
|
|
'Expected footprint delta not logged.'
|
|
assert re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Expected footprint deltas not logged.'
|
|
else:
|
|
assert self.RAM_KEY not in caplog.text
|
|
assert not re.search(self.DELTA_DETAIL, caplog.text), \
|
|
'Expected footprint delta not logged.'
|
|
assert re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Expected footprint deltas logged.'
|
|
|
|
@pytest.mark.parametrize(
|
|
'old_ram_multiplier, expect_delta_log',
|
|
[
|
|
(0.75, True),
|
|
(1.25, False),
|
|
],
|
|
ids=['footprint increased', 'footprint reduced']
|
|
)
|
|
def test_last_metrics(self, caplog, out_path, old_ram_multiplier, expect_delta_log):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Modify the older report so we can control the difference.
|
|
# Note: if footprint tests take too long, replace first run with a prepared twister.json
|
|
# That will increase test-to-code_under_test coupling, however.
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
# We assume positive RAM usage.
|
|
ts[self.RAM_KEY] *= old_ram_multiplier
|
|
with open(os.path.join(out_path, 'twister.json'), 'w') as f:
|
|
f.write(json.dumps(j, indent=4))
|
|
|
|
report_path = os.path.join(
|
|
os.path.dirname(out_path),
|
|
f'{os.path.basename(out_path)}.1',
|
|
'twister.json'
|
|
)
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--last-metrics'] + \
|
|
['--show-footprint'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
assert self.FOOTPRINT_LOG in caplog.text
|
|
|
|
if expect_delta_log:
|
|
assert self.RAM_KEY in caplog.text
|
|
assert re.search(self.DELTA_WARNING_RUN, caplog.text), \
|
|
'Expected footprint deltas not logged.'
|
|
else:
|
|
assert self.RAM_KEY not in caplog.text
|
|
assert not re.search(self.DELTA_WARNING_RUN, caplog.text), \
|
|
'Unexpected footprint deltas logged.'
|
|
|
|
second_logs = caplog.records
|
|
caplog.clear()
|
|
clear_log_in_test()
|
|
|
|
# Third run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--compare-report', report_path] + \
|
|
['--show-footprint'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Since second run should use the same source as the third, we should compare them.
|
|
delta_logs = [
|
|
record.getMessage() for record in second_logs \
|
|
if self.RAM_KEY in record.getMessage()
|
|
]
|
|
assert all([log in caplog.text for log in delta_logs])
|
|
|
|
@pytest.mark.parametrize(
|
|
'old_ram_multiplier, expect_delta_log',
|
|
[
|
|
(0.75, True),
|
|
(1.00, False),
|
|
(1.25, True),
|
|
],
|
|
ids=['footprint increased', 'footprint constant', 'footprint reduced']
|
|
)
|
|
def test_all_deltas(self, caplog, out_path, old_ram_multiplier, expect_delta_log):
|
|
# First run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy', 'device', 'group')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--enable-size-report'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
# Modify the older report so we can control the difference.
|
|
# Note: if footprint tests take too long, replace first run with a prepared twister.json
|
|
# That will increase test-to-code_under_test coupling, however.
|
|
with open(os.path.join(out_path, 'twister.json')) as f:
|
|
j = json.load(f)
|
|
for ts in j['testsuites']:
|
|
if 'reason' not in ts:
|
|
# We assume positive RAM usage.
|
|
ts[self.RAM_KEY] *= old_ram_multiplier
|
|
with open(os.path.join(out_path, 'twister.json'), 'w') as f:
|
|
f.write(json.dumps(j, indent=4))
|
|
|
|
report_path = os.path.join(
|
|
os.path.dirname(out_path),
|
|
f'{os.path.basename(out_path)}.1',
|
|
'twister.json'
|
|
)
|
|
|
|
# Second run
|
|
test_platforms = ['frdm_k64f']
|
|
path = os.path.join(TEST_DATA, 'tests', 'dummy')
|
|
args = ['-i', '--outdir', out_path, '-T', path] + \
|
|
['--all-deltas'] + \
|
|
['--compare-report', report_path, '--show-footprint'] + \
|
|
[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)
|
|
|
|
assert str(sys_exit.value) == '0'
|
|
|
|
assert self.FOOTPRINT_LOG in caplog.text
|
|
|
|
if expect_delta_log:
|
|
assert self.RAM_KEY in caplog.text
|
|
assert re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Expected footprint deltas not logged.'
|
|
else:
|
|
assert self.RAM_KEY not in caplog.text
|
|
assert not re.search(self.DELTA_WARNING_COMPARE, caplog.text), \
|
|
'Unexpected footprint deltas logged.'
|