blob: 67aee1ea44832c24158a6f0791779f86dceb1399 [file] [log] [blame]
#!/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
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_RELEASE = 'Deltas based on metrics from last release'
DELTA_WARNING_RUN = 'Deltas based on metrics from last run'
# Size report key we modify to control for deltas
RAM_KEY = 'used_ram'
@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 self.DELTA_WARNING_RELEASE in caplog.text, \
'Expected footprint deltas not logged.'
else:
assert self.RAM_KEY not in caplog.text
assert self.DELTA_WARNING_RELEASE not in 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] + \
[] + \
['--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'
# 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'] + \
['--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'
# 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 self.DELTA_WARNING_RELEASE in caplog.text, \
'Expected footprint deltas not logged.'
else:
assert self.RAM_KEY not in caplog.text
assert self.DELTA_WARNING_RELEASE not in 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 self.DELTA_WARNING_RELEASE in caplog.text, \
'Expected footprint deltas not logged.'
else:
assert self.RAM_KEY not in caplog.text
assert self.DELTA_WARNING_RELEASE not in caplog.text, \
'Unexpected 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 self.DELTA_WARNING_RUN in caplog.text, \
'Expected footprint deltas not logged.'
else:
assert self.RAM_KEY not in caplog.text
assert self.DELTA_WARNING_RUN not in 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 self.DELTA_WARNING_RELEASE in caplog.text, \
'Expected footprint deltas not logged.'
else:
assert self.RAM_KEY not in caplog.text
assert self.DELTA_WARNING_RELEASE not in caplog.text, \
'Unexpected footprint deltas logged.'