esp-idf/tools/ldgen/test/test_fragments.py

831 lines
25 KiB
Python
Executable File

#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
import os
import sys
import tempfile
import unittest
from io import StringIO
try:
from ldgen.fragments import Align, Flag, Keep, Sort, Surround, parse_fragment_file
from ldgen.sdkconfig import SDKConfig
except ImportError:
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from ldgen.fragments import Align, Flag, Keep, Sort, Surround, parse_fragment_file
from ldgen.sdkconfig import SDKConfig
from pyparsing import ParseException, ParseFatalException
class FragmentTest(unittest.TestCase):
def setUp(self):
with tempfile.NamedTemporaryFile(delete=False) as f:
self.kconfigs_source_file = os.path.join(tempfile.gettempdir(), f.name)
with tempfile.NamedTemporaryFile(delete=False) as f:
self.kconfig_projbuilds_source_file = os.path.join(tempfile.gettempdir(), f.name)
os.environ['COMPONENT_KCONFIGS_SOURCE_FILE'] = self.kconfigs_source_file
os.environ['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE'] = self.kconfig_projbuilds_source_file
os.environ['COMPONENT_KCONFIGS'] = ''
os.environ['COMPONENT_KCONFIGS_PROJBUILD'] = ''
# prepare_kconfig_files.py doesn't have to be called because COMPONENT_KCONFIGS and
# COMPONENT_KCONFIGS_PROJBUILD are empty
self.sdkconfig = SDKConfig('data/Kconfig', 'data/sdkconfig')
def tearDown(self):
try:
os.remove(self.kconfigs_source_file)
os.remove(self.kconfig_projbuilds_source_file)
except Exception:
pass
@staticmethod
def create_fragment_file(contents, name='test_fragment.lf'):
f = StringIO(contents)
f.name = name
return f
def test_basic(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
value_2 # comments should be ignored
value_3
# this is a comment as well
value_a
# this is the last comment
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test')
self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3', 'value_a'})
def test_conditional(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
if PERFORMANCE_A = y:
value_2
value_3
if PERFORMANCE_A = n:
value_4
if PERFORMANCE_B = n:
value_5
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test')
self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3', 'value_5'})
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
if PERFORMANCE_B = y:
value_2
elif PERFORMANCE_C = y:
value_3
elif PERFORMANCE_A = y:
value_4
else:
value_5
value_6
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test')
self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_3', 'value_6'})
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
if PERFORMANCE_A = y:
value_2
if PERFORMANCE_B = y:
value_3
else:
value_4
if PERFORMANCE_C = y:
value_5
value_6
value_7
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test')
self.assertEqual(fragment_file.fragments[0].entries,
{'value_1', 'value_2', 'value_4', 'value_5', 'value_6', 'value_7'})
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
if PERFORMANCE_A = n:
value_2
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_empty_file(self):
test_fragment = self.create_fragment_file(u"""
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(len(fragment_file.fragments), 0)
def test_setting_indent(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
value_2
value_3
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test')
self.assertEqual(fragment_file.fragments[0].entries, {'value_1', 'value_2', 'value_3'})
def test_settings_unmatch_indent(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
value_1
value_2 # first element dictates indent
value_3
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_unsupported_key(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
key_1:
value_a
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_empty_fragment(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_empty_conditional(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
if PERFORMANCE_B = y:
else:
value_1
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
if PERFORMANCE_B = y:
value_1
else PERFORMANCE_B = y:
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
if PERFORMANCE_B = y:
value_1
elif PERFORMANCE_B = y:
else:
value_2
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_out_of_order_conditional(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
elif PERFORMANCE_B = y:
value_1
else:
value_2
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
else:
value_2
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_multiple_fragments(self):
test_fragment = self.create_fragment_file(u"""
[sections:test1]
entries:
value_1
[scheme:test2]
entries:
section -> target
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].name, 'test1')
self.assertEqual(fragment_file.fragments[0].entries, {'value_1'})
self.assertEqual(fragment_file.fragments[1].name, 'test2')
self.assertEqual(fragment_file.fragments[1].entries, {('section', 'target')})
def test_whole_conditional_fragment(self):
test_fragment = self.create_fragment_file(u"""
if PERFORMANCE_B = y:
[sections:test1]
entries:
value_1
else:
[sections:test2]
entries:
value_2
if PERFORMANCE_A = y:
[sections:test3]
entries:
value_3
if PERFORMANCE_C = y:
value_6
[sections:test4]
entries:
value_4
[sections:test5]
entries:
value_5
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(len(fragment_file.fragments), 4)
self.assertEqual(fragment_file.fragments[0].name, 'test2')
self.assertEqual(fragment_file.fragments[0].entries, {'value_2'})
self.assertEqual(fragment_file.fragments[1].name, 'test3')
self.assertEqual(fragment_file.fragments[1].entries, {'value_3', 'value_6'})
self.assertEqual(fragment_file.fragments[2].name, 'test4')
self.assertEqual(fragment_file.fragments[2].entries, {'value_4'})
self.assertEqual(fragment_file.fragments[3].name, 'test5')
self.assertEqual(fragment_file.fragments[3].entries, {'value_5'})
def test_equivalent_conditional_fragment(self):
test_fragment1 = self.create_fragment_file(u"""
if PERFORMANCE_A = y:
[sections:test1]
entries:
value_1
else:
[sections:test2]
entries:
value_2
""")
fragment_file1 = parse_fragment_file(test_fragment1, self.sdkconfig)
self.assertEqual(fragment_file1.fragments[0].name, 'test1')
self.assertEqual(fragment_file1.fragments[0].entries, {'value_1'})
test_fragment2 = self.create_fragment_file(u"""
[sections:test1]
entries:
if PERFORMANCE_A = y:
value_1
else:
value_2
""")
fragment_file2 = parse_fragment_file(test_fragment2, self.sdkconfig)
self.assertEqual(fragment_file2.fragments[0].name, 'test1')
self.assertEqual(fragment_file2.fragments[0].entries, {'value_1'})
class SectionsTest(FragmentTest):
def test_basic(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
.section1
.section2
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries, {'.section1', '.section2'})
def test_duplicate_entries(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
.section1
.section2
.section3
.section2
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries, {'.section1', '.section2', '.section3'})
def test_empty_entries(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
if PERFORMANCE_B = y:
.section1
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_entries_grammar(self):
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
_valid1
valid2.
.valid3_-
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries,
{'_valid1', 'valid2.', '.valid3_-'})
# invalid starting char
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
1invalid
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
-invalid
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
# + notation
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
valid+
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries,
{'valid+'})
test_fragment = self.create_fragment_file(u"""
[sections:test]
entries:
inva+lid+
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
class SchemeTest(FragmentTest):
def test_basic(self):
test_fragment = self.create_fragment_file(u"""
[scheme:test]
entries:
sections1 -> target1
sections2 -> target2
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries,
{('sections1', 'target1'),
('sections2', 'target2')})
def test_duplicate_entries(self):
test_fragment = self.create_fragment_file(u"""
[scheme:test]
entries:
sections1 -> target1
sections2 -> target2
sections2 -> target2
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(fragment_file.fragments[0].entries,
{('sections1', 'target1'),
('sections2', 'target2')})
def test_empty_entries(self):
test_fragment = self.create_fragment_file(u"""
[scheme:test]
entries:
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[scheme:test]
entries:
if PERFORMANCE_B = y:
sections1 -> target1
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_improper_grammar(self):
test_fragment = self.create_fragment_file(u"""
[scheme:test]
entries:
sections1, target1 # improper separator
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
class MappingTest(FragmentTest):
def test_basic(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
obj:symbol (noflash)
obj (noflash)
obj:symbol_2 (noflash)
obj_2 (noflash)
* (noflash)
""")
expected = {('obj', 'symbol', 'noflash'),
('obj', None, 'noflash'),
('obj', 'symbol_2', 'noflash'),
('obj_2', None, 'noflash'),
('*', None, 'noflash')}
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(expected, fragment_file.fragments[0].entries)
def test_archive(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
entries:
* (default)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
lib1.a
lib2.a
entries:
* (default)
""")
with self.assertRaises(ParseFatalException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_archive_allowed_names(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
libstdc++.a
entries:
* (default)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual('libstdc++.a', fragment_file.fragments[0].archive)
def test_empty_entries(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
lib.a
entries:
if PERFORMANCE_B = y:
* (noflash) # if condition is false, then no 'entries' key value
""")
expected = set()
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(expected, fragment_file.fragments[0].entries)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
lib.a
entries:
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_duplicate_entries(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
lib.a
entries:
obj:symbol (noflash)
obj:symbol (noflash)
""")
expected = {('obj', 'symbol', 'noflash')}
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
self.assertEqual(expected, fragment_file.fragments[0].entries)
def test_invalid_grammar(self):
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive:
lib.a
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
entries:
* (default)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
obj: (noflash)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
obj: ()
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
obj:symbol
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
(noflash)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
obj:* (noflash)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
:symbol (noflash)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
test_fragment = self.create_fragment_file(u"""
[mapping:test]
archive: lib.a
entries:
*:symbol (noflash)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_keep_flag(self):
# Test parsing combinations and orders of flags
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text KEEP(),
rodata->flash_rodata KEEP() KEEP()
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Keep()]),
Flag('rodata', 'flash_rodata', [Keep(), Keep()])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
def test_align_flag(self):
# Test parsing combinations and orders of flags
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text ALIGN(8),
rodata->flash_rodata ALIGN(8, pre),
data->dram0_data ALIGN(8, pre, post),
bss->dram0_bss ALIGN(8, post),
common->dram0_bss ALIGN(8, pre, post) ALIGN(8)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Align(8, True, False)]),
Flag('rodata', 'flash_rodata', [Align(8, True, False)]),
Flag('data', 'dram0_data', [Align(8, True, True)]),
Flag('bss', 'dram0_bss', [Align(8, False, True)]),
Flag('common', 'dram0_bss', [Align(8, True, True), Align(8, True, False)])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
# Wrong post, pre order
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (noflash)
text->iram0_text ALIGN(8, post, pre)
""")
with self.assertRaises(ParseException):
parse_fragment_file(test_fragment, self.sdkconfig)
def test_sort_flag(self):
# Test parsing combinations and orders of flags
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text SORT(name),
rodata->flash_rodata SORT(alignment),
data->dram0_data SORT(init_priority),
bss->dram0_bss SORT(name, alignment),
common->dram0_bss SORT(alignment, name),
iram->iram0_text SORT(name, name),
dram->dram0_data SORT(alignment, alignment)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Sort('name')]),
Flag('rodata', 'flash_rodata', [Sort('alignment')]),
Flag('data', 'dram0_data', [Sort('init_priority')]),
Flag('bss', 'dram0_bss', [Sort('name', 'alignment')]),
Flag('common', 'dram0_bss', [Sort('alignment', 'name')]),
Flag('iram', 'iram0_text', [Sort('name', 'name')]),
Flag('dram', 'dram0_data', [Sort('alignment', 'alignment')])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
def test_surround_flag(self):
# Test parsing combinations and orders of flags
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text SURROUND(sym1)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Surround('sym1')])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
def test_flag_order(self):
# Test that the order in which the flags are specified is retained
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text ALIGN(4) KEEP() SURROUND(sym1) ALIGN(8) SORT(name),
rodata->flash_rodata KEEP() ALIGN(4) KEEP() SURROUND(sym1) ALIGN(8) ALIGN(4) SORT(name)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Align(4, True, False),
Keep(),
Surround('sym1'),
Align(8, True, False),
Sort('name')]),
Flag('rodata', 'flash_rodata', [Keep(),
Align(4, True, False),
Keep(),
Surround('sym1'),
Align(8, True, False),
Align(4, True, False),
Sort('name')])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
def test_flags_entries_multiple_flags(self):
# Not an error, generation step handles this, since
# it that step has a more complete information
# about all mappings.
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name),
text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Align(4, True, False),
Keep(),
Surround('sym1'),
Sort('name')]),
Flag('text', 'flash_text', [Align(4, True, False),
Keep(),
Surround('sym1'),
Sort('name')])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
def test_flags_entries_multiple_flags_and_entries(self):
# Not an error, generation step handles this, since
# it that step has a more complete information
# about all mappings. This can happen across multiple
# mapping fragments.
test_fragment = self.create_fragment_file(u"""
[mapping:map]
archive: libmain.a
entries:
obj1 (default);
text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name)
obj1 (default);
text->flash_text ALIGN(4) KEEP() SURROUND(sym1) SORT(name)
""")
fragment_file = parse_fragment_file(test_fragment, self.sdkconfig)
fragment = fragment_file.fragments[0]
expected = [Flag('text', 'flash_text', [Align(4, True, False),
Keep(),
Surround('sym1'),
Sort('name')]),
Flag('text', 'flash_text', [Align(4, True, False),
Keep(),
Surround('sym1'),
Sort('name')])]
actual = fragment.flags[('obj1', None, 'default')]
self.assertEqual(expected, actual)
if __name__ == '__main__':
unittest.main()