mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
1724 lines
70 KiB
Python
Executable File
1724 lines
70 KiB
Python
Executable File
#!/usr/bin/env python
|
|
#
|
|
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import collections
|
|
import fnmatch
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from io import StringIO
|
|
|
|
try:
|
|
from ldgen.entity import Entity, EntityDB
|
|
from ldgen.fragments import parse_fragment_file
|
|
from ldgen.generation import Generation, GenerationException
|
|
from ldgen.linker_script import LinkerScript
|
|
from ldgen.output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress
|
|
from ldgen.sdkconfig import SDKConfig
|
|
except ImportError:
|
|
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
|
from ldgen.entity import Entity, EntityDB
|
|
from ldgen.fragments import parse_fragment_file
|
|
from ldgen.generation import Generation, GenerationException
|
|
from ldgen.linker_script import LinkerScript
|
|
from ldgen.output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress
|
|
from ldgen.sdkconfig import SDKConfig
|
|
|
|
ROOT = Entity('*')
|
|
|
|
FREERTOS = Entity('libfreertos.a')
|
|
CROUTINE = Entity('libfreertos.a', 'croutine')
|
|
TIMERS = Entity('libfreertos.a', 'timers')
|
|
|
|
FREERTOS2 = Entity('libfreertos2.a')
|
|
|
|
|
|
class GenerationTest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.generation = Generation()
|
|
self.entities = None
|
|
self.linker_script = None
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
self.kconfigs_source_file = os.path.join(tempfile.gettempdir(), f.name)
|
|
self.addCleanup(os.remove, self.kconfigs_source_file)
|
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
|
self.kconfig_projbuilds_source_file = os.path.join(tempfile.gettempdir(), f.name)
|
|
self.addCleanup(os.remove, self.kconfig_projbuilds_source_file)
|
|
|
|
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')
|
|
|
|
fragment_file = parse_fragment_file('data/base.lf', self.sdkconfig)
|
|
self.generation.add_fragments_from_file(fragment_file)
|
|
|
|
self.entities = EntityDB()
|
|
|
|
with open('data/libfreertos.a.txt') as objdump:
|
|
self.entities.add_sections_info(objdump)
|
|
|
|
with open('data/linker_script.ld') as linker_script:
|
|
self.linker_script = LinkerScript(linker_script)
|
|
|
|
@staticmethod
|
|
def create_fragment_file(contents, name='test_fragment.lf'):
|
|
f = StringIO(contents)
|
|
f.name = name
|
|
return f
|
|
|
|
def add_fragments(self, text):
|
|
fragment_file = self.create_fragment_file(text)
|
|
fragment_file = parse_fragment_file(fragment_file, self.sdkconfig)
|
|
self.generation.add_fragments_from_file(fragment_file)
|
|
|
|
def write(self, expected, actual):
|
|
self.linker_script.fill(expected)
|
|
self.linker_script.write(open('expected.ld', 'w'))
|
|
|
|
self.linker_script.fill(actual)
|
|
self.linker_script.write(open('actual.ld', 'w'))
|
|
|
|
def generate_default_rules(self):
|
|
rules = collections.defaultdict(list)
|
|
|
|
rules['dram0_bss'].append(InputSectionDesc(ROOT, ['.bss', '.bss.*'], []))
|
|
rules['dram0_bss'].append(InputSectionDesc(ROOT, ['COMMON'], []))
|
|
rules['dram0_data'].append(InputSectionDesc(ROOT, ['.data', '.data.*'], []))
|
|
rules['dram0_data'].append(InputSectionDesc(ROOT, ['.dram', '.dram.*'], []))
|
|
rules['flash_text'].append(InputSectionDesc(ROOT, ['.literal', '.literal.*', '.text', '.text.*'], []))
|
|
rules['flash_rodata'].append(InputSectionDesc(ROOT, ['.rodata', '.rodata.*'], []))
|
|
rules['iram0_text'].append(InputSectionDesc(ROOT, ['.iram', '.iram.*'], []))
|
|
rules['rtc_bss'].append(InputSectionDesc(ROOT, ['.rtc.bss'], []))
|
|
rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.data'], []))
|
|
rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.rodata'], []))
|
|
rules['rtc_text'].append(InputSectionDesc(ROOT, ['.rtc.text', '.rtc.literal'], []))
|
|
|
|
return rules
|
|
|
|
def compare_rules(self, expected, actual):
|
|
self.assertEqual(expected, actual)
|
|
|
|
def get_default(self, target, rules):
|
|
return rules[target][0]
|
|
|
|
|
|
class DefaultMappingTest(GenerationTest):
|
|
|
|
def test_rule_generation_default(self):
|
|
# Checks that default rules are generated from
|
|
# the default scheme properly and even if no mappings
|
|
# are defined.
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_default_mapping_lib(self):
|
|
# Mapping a library with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (default)
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
def test_default_mapping_obj(self):
|
|
# Mapping an object with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (default)
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
def test_default_mapping_symbol(self):
|
|
# Mapping a symbol with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine:prvCheckPendingReadyList (default) #1
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
def test_default_mapping_all(self):
|
|
# Mapping a library, object, and symbol with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (default) #1
|
|
croutine (default) #2
|
|
croutine:prvCheckPendingReadyList (default) #3
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
def test_default_mapping_lib_symbol(self):
|
|
# Mapping a library, and symbol with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
#
|
|
# This is a check needed to make sure generation does not generate
|
|
# intermediate commands due to presence of symbol mapping.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (default) #1
|
|
croutine:prvCheckPendingReadyList (default) #2
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
def test_default_mapping_obj_symbol(self):
|
|
# Mapping a library, and symbol with default mapping. This should not emit additional rules,
|
|
# other than the default ones.
|
|
#
|
|
# This is a check needed to make sure generation does not generate
|
|
# intermediate commands due to presence of symbol mapping.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (default) #1
|
|
croutine:prvCheckPendingReadyList (default) #2
|
|
"""
|
|
self.add_fragments(mapping)
|
|
self.test_rule_generation_default()
|
|
|
|
|
|
class BasicTest(GenerationTest):
|
|
# Test basic and fundamental interactions between typical
|
|
# entries.
|
|
|
|
def test_nondefault_mapping_lib(self, alt=None):
|
|
# Test mapping entry different from default for a library.
|
|
# There should be exclusions in the default commands for flash_text and flash_rodata:
|
|
#
|
|
# flash_text
|
|
# *((EXCLUDE_FILE(libfreertos.a)) .literal ...) A
|
|
#
|
|
# Commands placing the entire library in iram, dram should be generated:
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a(.literal ...) B
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (noflash) #1
|
|
"""
|
|
self.add_fragments(alt if alt else mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Generate exclusions in flash_text and flash_rodata A
|
|
flash_text[0].exclusions.add(FREERTOS)
|
|
flash_rodata[0].exclusions.add(FREERTOS)
|
|
|
|
# Input section commands in iram_text and dram0_data for #1 B
|
|
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, []))
|
|
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_nondefault_mapping_obj(self, alt=None):
|
|
# Test mapping entry different from default for an object.
|
|
# There should be exclusions in the default commands for flash_text and flash_rodata:
|
|
#
|
|
# flash_text
|
|
# *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A
|
|
#
|
|
# Commands placing the entire library in iram, dram should be generated:
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a:croutine(.literal ...) B
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
"""
|
|
|
|
self.add_fragments(alt if alt else mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Generate exclusions in flash_text and flash_rodata A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Input section commands in iram_text and dram0_data for #1 B
|
|
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_nondefault_mapping_symbol(self):
|
|
# Test mapping entry different from default for symbol.
|
|
# There should be exclusions in the default commands for flash_text, as well as the implicit intermediate object command
|
|
# with an exclusion from default:
|
|
#
|
|
# flash_text
|
|
# *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...) A
|
|
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B
|
|
#
|
|
# Commands placing the entire library in iram should be generated:
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) C
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine:prvCheckPendingReadyList (noflash) #1
|
|
"""
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Generate exclusion in flash_text A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
|
|
# Generate intermediate command B
|
|
# List all relevant sections except the symbol
|
|
# being mapped
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Input section commands in iram_text for #1 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_default_symbol_nondefault_lib(self):
|
|
# Test default symbol mapping with different lib mapping. This should create an implicit intermediate object command.
|
|
# The significant targets are flash_text, flash_rodata, iram0_text, dram0_data.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a) .text ...) A
|
|
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A
|
|
#
|
|
# iram0_text
|
|
# * ( .iram ...)
|
|
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C.1
|
|
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) D
|
|
#
|
|
# dram0_data
|
|
# * ( .dram ...)
|
|
# libfreertos.a ( .rodata ...) C.2
|
|
#
|
|
# Only default commands are in the other targets.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (noflash) #1
|
|
croutine:prvCheckPendingReadyList (default) #2
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(FREERTOS)
|
|
flash_rodata[0].exclusions.add(FREERTOS)
|
|
|
|
# Commands for #1 C.1 & C.2
|
|
# C.1 excludes intermediate command for #2
|
|
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
|
|
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
|
|
|
|
# Intermediate command for excluding #2 D
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Command for #2 B
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_default_symbol_nondefault_obj(self):
|
|
# Test default symbol mapping with different obj mapping. Since there is an explicit entry for the object,
|
|
# the sections for that object should just be expanded and the symbol section subtracted, to be placed
|
|
# using another command.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
|
|
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) B
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) A
|
|
#
|
|
# iram0_text
|
|
# *( .iram ...)
|
|
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C.1
|
|
#
|
|
# dram0_data
|
|
# *(.data ..)
|
|
# *libfreertos.a:croutine(.rodata ....) C.2
|
|
#
|
|
# Only default commands are in the other targets
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
croutine:prvCheckPendingReadyList (default) #2
|
|
"""
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Commands for #1 C.1 & C.2
|
|
# C.1 list relevant sections for libfreertos.a:croutine to
|
|
# exclude symbol to map
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
# Command for #2 B
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_default_nondefault_alternating(self):
|
|
# Here, each of the entries map sections to something different
|
|
# than its one-level-up entry.
|
|
#
|
|
# * text -> flash, rodata -> flash
|
|
# libfreertos.a text -> iram, rodata -> dram
|
|
# libfreertos.a:croutine text -> flash, rodata -> flash
|
|
# croutine:prvCheckPendingReadyList text -> iram
|
|
#
|
|
# The significant targets are flash_text, flash_rodata, iram0_text, and dram0_data.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a) .text ...) A
|
|
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) B.1
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a) .rodata ...) A
|
|
# *libfreertos.a:croutine(.rodata .rodata.*) B.2
|
|
#
|
|
# iram0_text
|
|
# * ( .iram ...)
|
|
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...) C
|
|
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) D
|
|
#
|
|
# dram0_data
|
|
# * ( .dram ...)
|
|
# libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .rodata ...) C
|
|
#
|
|
# For the other targets only the default commands should be present.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (noflash) #1
|
|
croutine (default) #2
|
|
croutine:prvCheckPendingReadyList (noflash) #3
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
# Only for flash_text and flash_rodata
|
|
flash_text[0].exclusions.add(FREERTOS)
|
|
flash_rodata[0].exclusions.add(FREERTOS)
|
|
|
|
# Commands for #1 C
|
|
# with exclusions for #2
|
|
iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
|
|
dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [CROUTINE]))
|
|
|
|
# Commands for #2 B.1
|
|
flash_rodata.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
# List all relevant sections in case of flash_text B.2
|
|
# as exclusion for #3
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Command for #3 D
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_nondefault_but_same_lib_and_obj(self):
|
|
# Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
|
|
# to #1. Output is similar to test_different_mapping_lib.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (noflash) #1
|
|
croutine (noflash) #2
|
|
"""
|
|
self.test_nondefault_mapping_lib(mapping)
|
|
|
|
def test_nondefault_but_same_lib_and_sym(self):
|
|
# Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
|
|
# to #1. Output is similar to test_different_mapping_lib.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
* (noflash) #1
|
|
croutine:prvCheckPendingReadyList (noflash) #2
|
|
"""
|
|
self.test_nondefault_mapping_lib(mapping)
|
|
|
|
def test_nondefault_but_same_obj_and_sym(self):
|
|
# Commands should not be generated for #2, since it does similar mapping
|
|
# to #1. Output is similar to test_different_mapping_obj.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
croutine:prvCheckPendingReadyList (noflash) #2
|
|
"""
|
|
self.test_nondefault_mapping_obj(mapping)
|
|
|
|
def test_multiple_symbols_excluded_from_intermediate_command(self):
|
|
# Test mapping multiple symbols from the same object.
|
|
# All these symbols must be succesfully excluded from
|
|
# the intermediate command.
|
|
#
|
|
# flash_text
|
|
# * (EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
|
|
# libfreertos:croutine(.text ...) B
|
|
#
|
|
# iram0_text
|
|
#
|
|
#
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine:prvCheckPendingReadyList (noflash) #1
|
|
croutine:prvCheckDelayedList (noflash) #2
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Exclusions for #1 & #2 intermediate command A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
|
|
# Intermediate command for #1 & #2 which lists B
|
|
# all relevant sections in croutine except prvCheckPendingReadyList
|
|
# and prvCheckDelayedList
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
|
|
filtered_sections.append('.text')
|
|
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Commands for #1 & 2
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []))
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_root_mapping_fragment(self):
|
|
# Test creation of a mapping fragment that maps '*'.
|
|
# This should generate another default command in iram0_text:
|
|
#
|
|
# iram0_text
|
|
# * (.custom_section) A
|
|
# * (.iram .iram.*)
|
|
mapping = u"""
|
|
[sections:custom_section]
|
|
entries:
|
|
.custom_section
|
|
|
|
[scheme:custom_scheme]
|
|
entries:
|
|
custom_section -> iram0_text
|
|
|
|
[mapping:default2]
|
|
archive: *
|
|
entries:
|
|
* (custom_scheme) #1
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
# Generate default command A
|
|
# Since these are the same 'specificity', the commands
|
|
# are arranged alphabetically.
|
|
expected['iram0_text'].append(expected['iram0_text'][0])
|
|
expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
|
|
class AdvancedTest(GenerationTest):
|
|
|
|
# Test valid but quirky cases, corner cases, failure cases, and
|
|
# cases involving interaction between schemes, other mapping
|
|
# fragments.
|
|
|
|
def test_same_entity_no_scheme_common(self):
|
|
# Test same entity being mapped by schemes that have nothing in common.
|
|
#
|
|
# noflash_data: rodata -> dram0_data
|
|
# noflash_text: text -> iram0_text
|
|
#
|
|
# This operation should succeed with the following commands:
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a:croutine(.text .text.* ...) C
|
|
#
|
|
# dram0_data
|
|
# *(.data ..)
|
|
# *(.dram ...)
|
|
# *libfreertos.a:croutine(.rodata .rodata.*) D
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash_text) #1
|
|
croutine (noflash_data) #2
|
|
"""
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
|
|
# Exclusions for #2 B
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Command for #1 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
|
|
|
|
# Command for #2 D
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_same_entity_sub_scheme(self):
|
|
# Test same entity being mapped by scheme that is a subset of the other.
|
|
#
|
|
# noflash: text -> iram0_text, rodata -> dram0_data
|
|
# noflash_text: text -> iram0_text
|
|
#
|
|
# `text -> iram0_text` is common between the two schemes.
|
|
#
|
|
# This operation should succeed with the following commands:
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...) A
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...) B
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a:croutine(.text .text.* ...) C
|
|
#
|
|
# dram0_data
|
|
# *(.data ..)
|
|
# *(.dram ...)
|
|
# *libfreertos.a:croutine(.rodata .rodata.*) D
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
croutine (noflash_data) #2
|
|
"""
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
|
|
# Exclusions for #1 & #2 B
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Command for #1 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
|
|
|
|
# Command for #1 & #2 D
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_same_entity_conflicting_scheme(self, alt=None):
|
|
# Test same entity being mapped by scheme conflicting with another.
|
|
#
|
|
# rtc = text -> rtc_text, rodata -> rtc_data
|
|
# noflash = text -> iram0_text, rodata -> dram0_data
|
|
#
|
|
# This operation should fail.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
croutine (rtc) #2
|
|
"""
|
|
self.add_fragments(alt if alt else mapping)
|
|
|
|
with self.assertRaises(GenerationException):
|
|
self.generation.generate(self.entities)
|
|
|
|
def test_same_entity_conflicting_section(self):
|
|
# Test same entity being mapped by scheme conflicting with another.
|
|
#
|
|
# custom_rtc = .text -> rtc_text
|
|
# noflash = .text -> iram0_text, .rodata -> dram0_data
|
|
#
|
|
# This operation should fail.
|
|
mapping = u"""
|
|
[sections:custom_text]
|
|
entries:
|
|
.text+
|
|
.literal+
|
|
|
|
[scheme:custom_rtc]
|
|
entries:
|
|
custom_text -> rtc_text
|
|
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
croutine (custom_rtc) #2
|
|
"""
|
|
self.test_same_entity_conflicting_scheme(mapping)
|
|
|
|
def test_complex_mapping_case(self, alt=None):
|
|
# Test a complex case where an object is mapped using
|
|
# one scheme, but a specific symbol in that object is mapped
|
|
# using another. Another object and symbol is mapped the other way around.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...) A, B
|
|
#
|
|
# flash_rodata
|
|
# *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...) A, B
|
|
#
|
|
# dram0_data
|
|
# *(EXCLUDE_FILES(libfreertos.a:timers) .data ..) B
|
|
# *(.dram ...)
|
|
# *libfreertos.a:croutine(.rodata .rodata.*) C
|
|
# *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...) E
|
|
#
|
|
# dram0_bss
|
|
# *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...) B
|
|
# *(EXCLUDE_FILE(libfreertos.a:timers) COMMON) B
|
|
#
|
|
# iram0_text
|
|
# *(.iram ...)
|
|
# *libfreertos.a:croutine(.literal .literal.prvCheckDelayedList ...) C
|
|
# *libfreertos.a:timers(.literal .literal.prvProcessReceivedCommands ...) E
|
|
#
|
|
# rtc_text
|
|
# *(rtc.text .rtc.literal)
|
|
# libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) F
|
|
# libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...) D.2
|
|
#
|
|
# rtc_data
|
|
# *(rtc.data)
|
|
# *(rtc.rodata)
|
|
# libfreertos.a:timers (.data .data.*) D
|
|
# libfreertos.a:timers (.rodata ...) D.2
|
|
#
|
|
# rtc_bss
|
|
# *(rtc.bss .rtc.bss)
|
|
# libfreertos.a:timers (.bss .bss.*) D
|
|
# libfreertos.a:timers (COMMON) D
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
timers (rtc) #2
|
|
timers:prvProcessReceivedCommands (noflash) #3
|
|
croutine:prvCheckPendingReadyList (rtc) #4
|
|
"""
|
|
|
|
self.add_fragments(alt if alt else mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
dram0_data = expected['dram0_data']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_bss = expected['dram0_bss']
|
|
rtc_text = expected['rtc_text']
|
|
rtc_data = expected['rtc_data']
|
|
rtc_bss = expected['rtc_bss']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Exclusions for #2 B
|
|
flash_text[0].exclusions.add(TIMERS)
|
|
flash_rodata[0].exclusions.add(TIMERS)
|
|
dram0_data[0].exclusions.add(TIMERS)
|
|
dram0_bss[0].exclusions.add(TIMERS)
|
|
dram0_bss[1].exclusions.add(TIMERS)
|
|
|
|
# Commands for #1 C
|
|
# List all relevant sections excluding #4 for text -> iram0_text
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
# Commands for #4 F
|
|
# Processed first due to alphabetical ordering
|
|
rtc_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
# Commands for #2 D
|
|
# List all relevant sections excluding #3 for text -> rtc_text and D.2
|
|
# rodata -> rtc_data
|
|
timers_sections = self.entities.get_sections('libfreertos.a', 'timers')
|
|
filtered_sections = fnmatch.filter(timers_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(timers_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
|
|
filtered_sections.append('.text')
|
|
rtc_text.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
|
|
|
|
rtc_data.append(InputSectionDesc(TIMERS, dram0_data[0].sections, []))
|
|
filtered_sections = fnmatch.filter(timers_sections, '.rodata.*')
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
|
|
rtc_data.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
|
|
|
|
rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[0].sections, []))
|
|
rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[1].sections, []))
|
|
|
|
# Commands for #3 E
|
|
iram0_text.append(InputSectionDesc(TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), []))
|
|
dram0_data.append(InputSectionDesc(TIMERS, set(['.rodata.prvProcessReceivedCommands']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_multiple_mapping_fragments(self):
|
|
# Test mapping multiple fragments succeeds, particularly
|
|
# generating exclusions from the default command of archive
|
|
# and object specificity.
|
|
#
|
|
# flash_text
|
|
# * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
|
|
#
|
|
# flash_rodata
|
|
# * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
|
|
#
|
|
# iram0_text
|
|
mapping = u"""
|
|
[mapping:test_1]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
|
|
[mapping:test_2]
|
|
archive: libfreertos2.a
|
|
entries:
|
|
* (noflash) #2
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_rodata[0].exclusions.add(CROUTINE)
|
|
|
|
# Exclusions for #1 & #2 B
|
|
flash_text[0].exclusions.add(FREERTOS2)
|
|
flash_rodata[0].exclusions.add(FREERTOS2)
|
|
|
|
# Command for #1 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
|
|
dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
|
|
|
|
# Command for #1 & #2 D
|
|
iram0_text.append(InputSectionDesc(FREERTOS2, flash_text[0].sections, []))
|
|
dram0_data.append(InputSectionDesc(FREERTOS2, flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_mapping_same_lib_in_multiple_fragments_no_conflict(self):
|
|
# Test mapping fragments operating on the same archive.
|
|
# In these cases, the entries are taken together.
|
|
#
|
|
# Uses the same entries as C_05 but spreads them across
|
|
# two fragments. The output should still be the same.
|
|
mapping = u"""
|
|
[mapping:test_1]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
timers:prvProcessReceivedCommands (noflash) #3
|
|
|
|
[mapping:test_2]
|
|
archive: libfreertos.a
|
|
entries:
|
|
timers (rtc) #2
|
|
croutine:prvCheckPendingReadyList (rtc) #4
|
|
"""
|
|
self.test_complex_mapping_case(mapping)
|
|
|
|
def test_mapping_same_lib_in_multiple_fragments_conflict(self):
|
|
# Test mapping fragments operating on the same archive
|
|
# with conflicting mappings.
|
|
mapping = u"""
|
|
[mapping:test_1]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash) #1
|
|
|
|
[mapping:test_2]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (rtc) #2
|
|
"""
|
|
self.test_same_entity_conflicting_scheme(mapping)
|
|
|
|
def test_command_order(self):
|
|
# Test command order sorting: the commands should be sorted by specificity, then
|
|
# alphabetically. This contributes to deterministic output given
|
|
# the same input mapping entries.
|
|
#
|
|
# This ordering is also tested in other tests as a side-effect.
|
|
#
|
|
# flash_text
|
|
# * (EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:croutine2)) A
|
|
# libfreertos.a:croutine(.text ....) B
|
|
#
|
|
# iram0_text
|
|
#
|
|
# * (.iram .iram.*)
|
|
# libfreertos:croutine(.text .literal ...) C
|
|
# libfreertos:croutine(.text.prvCheckDelayedList .literal.prvCheckDelayedList) F
|
|
# libfreertos:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList) G
|
|
# libfreertos2:croutine(.text .literal ...) D
|
|
# libfreertos2:croutine2(.text .literal ...) E
|
|
mapping = u"""
|
|
[mapping:freertos2]
|
|
archive: libfreertos2.a
|
|
entries:
|
|
croutine2 (noflash_text) #1
|
|
croutine (noflash_text) #2
|
|
|
|
[mapping:freertos]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine:prvCheckPendingReadyList (noflash_text) #3
|
|
croutine:prvCheckDelayedList (noflash_text) #4
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Exclusions for #1 A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine2'))
|
|
flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine'))
|
|
|
|
# Intermediate command for #3 and #4 B
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Command for
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []))
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine'), flash_text[0].sections, []))
|
|
iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine2'), flash_text[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_ambigious_obj(self):
|
|
# Command generation for ambiguous entry should fail.
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
port:xPortGetTickRateHz (noflash) #1
|
|
"""
|
|
self.add_fragments(mapping)
|
|
|
|
with self.assertRaises(GenerationException):
|
|
self.generation.generate(self.entities)
|
|
|
|
def test_root_mapping_fragment_conflict(self):
|
|
# Test that root mapping fragments are also checked for
|
|
# conflicts.
|
|
#
|
|
# 'custom_scheme' entries conflict the 'default' scheme
|
|
# entries.
|
|
mapping = u"""
|
|
[scheme:custom_scheme]
|
|
entries:
|
|
flash_text -> iram0_text
|
|
|
|
[mapping:default2]
|
|
archive: *
|
|
entries:
|
|
* (custom_scheme)
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
with self.assertRaises(GenerationException):
|
|
self.generation.generate(self.entities)
|
|
|
|
def test_root_mapping_fragment_duplicate(self):
|
|
# Same root mappings have no effect.
|
|
#
|
|
# custom_scheme has the 'iram -> iram0_text' in common with
|
|
# default scheme
|
|
mapping = u"""
|
|
[sections:custom_section]
|
|
entries:
|
|
.custom_section
|
|
|
|
[scheme:custom_scheme]
|
|
entries:
|
|
iram -> iram0_text
|
|
custom_section -> iram0_text
|
|
|
|
[mapping:default2]
|
|
archive: *
|
|
entries:
|
|
* (custom_scheme)
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
# Generate default command A
|
|
# Since these are the same 'specificity', the commands
|
|
# are arranged alphabetically.
|
|
expected['iram0_text'].append(expected['iram0_text'][0])
|
|
expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
|
|
class ConfigTest(GenerationTest):
|
|
# Test command generation with conditions
|
|
|
|
def _test_conditional_on_scheme(self, perf, alt=None):
|
|
# Test that proper commands are generated if using
|
|
# schemes with conditional entries.
|
|
scheme = u"""
|
|
[sections:cond_text_data]
|
|
entries:
|
|
if PERFORMANCE_LEVEL >= 1:
|
|
.text+
|
|
.literal+
|
|
else:
|
|
.rodata+
|
|
|
|
[scheme:cond_noflash]
|
|
entries:
|
|
if PERFORMANCE_LEVEL >= 1:
|
|
cond_text_data -> iram0_text
|
|
else:
|
|
cond_text_data -> dram0_data
|
|
"""
|
|
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: lib.a
|
|
entries:
|
|
* (cond_noflash)
|
|
"""
|
|
self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf))
|
|
self.add_fragments(scheme)
|
|
self.add_fragments(alt if alt else mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
if perf >= 1:
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
flash_text[0].exclusions.add(Entity('lib.a'))
|
|
iram0_text.append(InputSectionDesc(Entity('lib.a'), flash_text[0].sections, []))
|
|
else:
|
|
flash_rodata = expected['flash_rodata']
|
|
dram0_data = expected['dram0_data']
|
|
flash_rodata[0].exclusions.add(Entity('lib.a'))
|
|
dram0_data.append(InputSectionDesc(Entity('lib.a'), flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_conditional_on_scheme_00(self):
|
|
self._test_conditional_on_scheme(0)
|
|
|
|
def test_conditional_on_scheme_01(self):
|
|
self._test_conditional_on_scheme(1)
|
|
|
|
def test_conditional_mapping(self, alt=None):
|
|
# Test that proper commands are generated
|
|
# in conditional mapping entries.
|
|
mapping = u"""
|
|
[mapping:default]
|
|
archive: *
|
|
entries:
|
|
* (default)
|
|
|
|
[mapping:test]
|
|
archive: lib.a
|
|
entries:
|
|
if PERFORMANCE_LEVEL = 1:
|
|
obj1 (noflash)
|
|
elif PERFORMANCE_LEVEL = 2:
|
|
obj1 (noflash)
|
|
obj2 (noflash)
|
|
elif PERFORMANCE_LEVEL = 3:
|
|
obj1 (noflash)
|
|
obj2 (noflash)
|
|
obj3 (noflash)
|
|
"""
|
|
|
|
for perf_level in range(0, 4):
|
|
self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level))
|
|
|
|
self.generation.mappings = {}
|
|
self.add_fragments(alt if alt else mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
if perf_level < 4 and perf_level > 0:
|
|
for append_no in range(1, perf_level + 1):
|
|
flash_text = expected['flash_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
iram0_text = expected['iram0_text']
|
|
dram0_data = expected['dram0_data']
|
|
|
|
obj_str = 'obj' + str(append_no)
|
|
|
|
flash_text[0].exclusions.add(Entity('lib.a', obj_str))
|
|
flash_rodata[0].exclusions.add(Entity('lib.a', obj_str))
|
|
|
|
iram0_text.append(InputSectionDesc(Entity('lib.a', obj_str), flash_text[0].sections, []))
|
|
dram0_data.append(InputSectionDesc(Entity('lib.a', obj_str), flash_rodata[0].sections, []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_multiple_fragment_same_lib_conditional(self):
|
|
# Test conditional entries on new mapping fragment grammar.
|
|
# across multiple fragments.
|
|
mapping = u"""
|
|
[mapping:default]
|
|
archive: *
|
|
entries:
|
|
* (default)
|
|
|
|
[mapping:base]
|
|
archive: lib.a
|
|
entries:
|
|
if PERFORMANCE_LEVEL = 1:
|
|
obj1 (noflash)
|
|
elif PERFORMANCE_LEVEL = 2:
|
|
obj1 (noflash)
|
|
elif PERFORMANCE_LEVEL = 3:
|
|
obj1 (noflash)
|
|
|
|
[mapping:extra]
|
|
archive: lib.a
|
|
entries:
|
|
if PERFORMANCE_LEVEL = 1:
|
|
obj1 (noflash) # ignore duplicate definition
|
|
elif PERFORMANCE_LEVEL = 2:
|
|
obj2 (noflash)
|
|
elif PERFORMANCE_LEVEL = 3:
|
|
obj2 (noflash)
|
|
obj3 (noflash)
|
|
"""
|
|
|
|
self.test_conditional_mapping(mapping)
|
|
|
|
|
|
class FlagTest(GenerationTest):
|
|
|
|
# Test correct generation of mapping fragment entries
|
|
# with flags.
|
|
|
|
def test_flags_basics(self):
|
|
# Test that input section commands additions are done (KEEP SORT).
|
|
# Test that order dependent commands are properly generated (ALIGN, SURROUND)
|
|
# Normally, if an entry has the same mapping as parent, commands.
|
|
# are not emitted for them. However, if there are flags, they should be -
|
|
# only for the scheme entries that have flags, though.
|
|
# Flag entries split across multiple entries work.
|
|
#
|
|
# flash_text
|
|
# *((EXCLUDE_FILE(libfreertos:timers libfreertos:croutine).text ...) A
|
|
# KEEP(* (SORT_BY_NAME(EXCLUDE_FILE(libfreertos:timers).text) ...) B
|
|
#
|
|
# flash_rodata
|
|
# *((EXCLUDE_FILE(libfreertos:timers) .rodata ...) C
|
|
# _sym2_start D.1
|
|
# . = ALIGN(4) E.1
|
|
# KEEP(* (EXCLUDE_FILE(libfreertos:timers) .rodata ...) F
|
|
# _sym2_end D.2
|
|
# . = ALIGN(4) E.2
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# . = ALIGN(4) G.1
|
|
# _sym1_start H.1
|
|
# libfreertos.a:croutine(.text .literal ...) I
|
|
# . = ALIGN(4) G.2
|
|
# _sym1_end H.2
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (noflash_text);
|
|
text->iram0_text ALIGN(4, pre, post) SURROUND(sym1) #1
|
|
timers (default);
|
|
text->flash_text KEEP() SORT(name) #2
|
|
timers (default);
|
|
rodata->flash_rodata SURROUND(sym2) ALIGN(4, pre, post) #3
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
flash_rodata = expected['flash_rodata']
|
|
|
|
# Exclusions in flash_text for timers and croutine A
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
flash_text[0].exclusions.add(TIMERS)
|
|
|
|
# Command for #3 B
|
|
flash_text.append(InputSectionDesc(TIMERS, flash_text[0].sections, [], keep=True, sort=('name', None)))
|
|
|
|
# Exclusions in flash_rodata for timers C
|
|
flash_rodata[0].exclusions.add(TIMERS)
|
|
|
|
# Commands for #3 D.1, E.1, F, D.2, E.2
|
|
flash_rodata.append(SymbolAtAddress('_sym2_start'))
|
|
flash_rodata.append(AlignAtAddress(4))
|
|
flash_rodata.append(InputSectionDesc(TIMERS, flash_rodata[0].sections, []))
|
|
flash_rodata.append(SymbolAtAddress('_sym2_end'))
|
|
flash_rodata.append(AlignAtAddress(4))
|
|
|
|
# Commands for # G.1, H.1, I, G.2, H.2
|
|
iram0_text.append(AlignAtAddress(4))
|
|
iram0_text.append(SymbolAtAddress('_sym1_start'))
|
|
iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
|
|
iram0_text.append(AlignAtAddress(4))
|
|
iram0_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_intermediate_exclusion_command_root(self):
|
|
# Test that intermediate exclusion commands from root-level commands
|
|
# are included in the flags.
|
|
#
|
|
# flash_text
|
|
# _sym1_start A.1
|
|
# KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...) B
|
|
# KEEP(libfreertos.a:croutine(...))) C
|
|
# _sym1_end A.2
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
|
|
mapping = u"""
|
|
[mapping:default]
|
|
archive: *
|
|
entries:
|
|
# 1
|
|
* (default);
|
|
text->flash_text SURROUND(sym1) KEEP() #2
|
|
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine:prvCheckPendingReadyList (noflash_text) #3
|
|
"""
|
|
|
|
self.generation.mappings = {}
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Command for #2, pre A.1
|
|
flash_text.insert(0, SymbolAtAddress('_sym1_start'))
|
|
|
|
# Command for #1 with KEEP() B
|
|
# and exclusion for #3
|
|
flash_text[1].keep = True
|
|
flash_text[1].exclusions.add(CROUTINE)
|
|
|
|
# Implicit exclusion command for #3 C
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
|
|
|
|
# Command for #2, post A.2
|
|
flash_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
# Command for #3 D
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_intermediate_exclusion_command_lib(self):
|
|
# Test that intermediate exclusion commands from lib-level commands
|
|
# are included in the flags.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a).text ...)
|
|
# _sym1_start A.1
|
|
# KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...)) B
|
|
# KEEP(libfreertos.a:croutine(...))) C
|
|
# _sym1_end A.2
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
# 1
|
|
* (default);
|
|
text->flash_text SURROUND(sym1) KEEP() #2
|
|
croutine:prvCheckPendingReadyList (noflash_text) #3
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Command for #2, pre A.1
|
|
flash_text.append(SymbolAtAddress('_sym1_start'))
|
|
flash_text[0].exclusions.add(FREERTOS)
|
|
|
|
# Command for #1 with KEEP() B
|
|
# and exclusion for #3
|
|
flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
|
|
|
|
# Implicit exclusion command for #3 C
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
|
|
|
|
# Command for #2, post A.2
|
|
flash_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
# Command for #3 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_intermediate_exclusion_command_obj(self):
|
|
# Test that intermediate exclusion commands from obj-level commands
|
|
# are included in the flags.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a).text ...)
|
|
# _sym1_start A.1
|
|
# KEEP(libfreertos.a:croutine(...))) B
|
|
# _sym1_end A.2
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) C
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
# 1
|
|
croutine (default);
|
|
text->flash_text SURROUND(sym1) KEEP() #2
|
|
croutine:prvCheckPendingReadyList (noflash_text) #3
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Command for #2, pre A.1
|
|
flash_text.append(SymbolAtAddress('_sym1_start'))
|
|
flash_text[0].exclusions.add(CROUTINE)
|
|
|
|
# Implicit exclusion command for #3 B
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
|
|
|
|
# Command for #2, post A.2
|
|
flash_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
# Command for #3 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_separate_exclusion_command_if_explicit_root(self):
|
|
# Explicit commands are separated from the parent's flags.
|
|
#
|
|
# flash_text
|
|
# _sym1_start A.1
|
|
# KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...) B
|
|
# _sym1_end A.2
|
|
# KEEP(libfreertos.a:croutine(...))) C
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
|
|
mapping = u"""
|
|
[mapping:default]
|
|
archive: *
|
|
entries:
|
|
# 1
|
|
* (default);
|
|
text->flash_text SURROUND(sym1) KEEP() #2
|
|
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
croutine (default) #3
|
|
croutine:prvCheckPendingReadyList (noflash_text) #4
|
|
"""
|
|
|
|
self.generation.mappings = {}
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Command for #2, pre A.1
|
|
flash_text.insert(0, SymbolAtAddress('_sym1_start'))
|
|
|
|
# Command for #1 with KEEP() B
|
|
# and exclusion for #3
|
|
flash_text[1].keep = True
|
|
flash_text[1].exclusions.add(CROUTINE)
|
|
|
|
# Command for #2, post A.2
|
|
flash_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
# Command for #3 C
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Command for #4 D
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_separate_exclusion_command_if_explicit_lib(self):
|
|
# Explicit commands are separated from the parent's flags.
|
|
#
|
|
# flash_text
|
|
# *(EXCLUDE_FILE(libfreertos.a).text ...)
|
|
# _sym1_start A.1
|
|
# KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...)) B
|
|
# _sym1_end A.2
|
|
# KEEP(libfreertos.a:croutine(...))) C
|
|
#
|
|
# iram0_text
|
|
# *(.iram .iram.*)
|
|
# libfreertos.a:croutine(.text.prvCheckPendingReadyList ...) D
|
|
mapping = u"""
|
|
[mapping:test]
|
|
archive: libfreertos.a
|
|
entries:
|
|
# 1
|
|
* (default);
|
|
text->flash_text SURROUND(sym1) KEEP()
|
|
croutine (default) #2
|
|
croutine:prvCheckPendingReadyList (noflash_text) #3
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
iram0_text = expected['iram0_text']
|
|
|
|
# Command for #2, pre A.1
|
|
flash_text.append(SymbolAtAddress('_sym1_start'))
|
|
flash_text[0].exclusions.add(FREERTOS)
|
|
|
|
# Command for #1 with KEEP() B
|
|
# and exclusion for #3
|
|
flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
|
|
|
|
# Command for #2, post A.2
|
|
flash_text.append(SymbolAtAddress('_sym1_end'))
|
|
|
|
# Implicit exclusion command for #3 C
|
|
croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
|
|
filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
|
|
filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
|
|
|
|
filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
|
|
filtered_sections.append('.text')
|
|
flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
|
|
|
|
# Command for #3 C
|
|
iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flag_additions(self):
|
|
# Test ability to add flags as long as no other mapping fragments
|
|
# does the same thing.
|
|
mapping = u"""
|
|
[mapping:default_add_flag]
|
|
archive: *
|
|
entries:
|
|
* (default);
|
|
text->flash_text KEEP()
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_text[0].keep = True
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_flag_additions_duplicate(self):
|
|
# Test same flags added to same entity - these
|
|
# are ignored.
|
|
mapping = u"""
|
|
[mapping:default_add_flag_1]
|
|
archive: *
|
|
entries:
|
|
* (default);
|
|
text->flash_text KEEP()
|
|
|
|
[mapping:default_add_flag_2]
|
|
archive: *
|
|
entries:
|
|
* (default);
|
|
text->flash_text KEEP()
|
|
"""
|
|
|
|
self.add_fragments(mapping)
|
|
|
|
actual = self.generation.generate(self.entities)
|
|
expected = self.generate_default_rules()
|
|
|
|
flash_text = expected['flash_text']
|
|
flash_text[0].keep = True
|
|
|
|
self.compare_rules(expected, actual)
|
|
|
|
def test_flags_flag_additions_conflict(self):
|
|
# Test condition where multiple fragments specifies flags
|
|
# to same entity - should generate exception.
|
|
mapping = u"""
|
|
[mapping:default_add_flag_1]
|
|
archive: *
|
|
entries:
|
|
* (default);
|
|
text->flash_text ALIGN(2)
|
|
|
|
[mapping:default_add_flag_2]
|
|
archive: *
|
|
entries:
|
|
* (default);
|
|
text->flash_text SURROUND(sym1)
|
|
"""
|
|
self.add_fragments(mapping)
|
|
|
|
with self.assertRaises(GenerationException):
|
|
self.generation.generate(self.entities)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|