1#!/usr/bin/env python
2#
3# Copyright 2021 Espressif Systems (Shanghai) CO LTD
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import collections
19import fnmatch
20import os
21import sys
22import tempfile
23import unittest
24
25try:
26    from generation import Generation, GenerationException
27except ImportError:
28    sys.path.append('../')
29    from generation import Generation, GenerationException
30
31from io import StringIO
32
33from entity import Entity, EntityDB
34from fragments import FragmentFile
35from linker_script import LinkerScript
36from output_commands import AlignAtAddress, InputSectionDesc, SymbolAtAddress
37from sdkconfig import SDKConfig
38
39ROOT = Entity('*')
40
41FREERTOS = Entity('libfreertos.a')
42CROUTINE = Entity('libfreertos.a', 'croutine')
43TIMERS = Entity('libfreertos.a', 'timers')
44
45FREERTOS2 = Entity('libfreertos2.a')
46
47
48class GenerationTest(unittest.TestCase):
49
50    def setUp(self):
51        self.generation = Generation()
52        self.entities = None
53        self.linker_script = None
54
55        with tempfile.NamedTemporaryFile(delete=False) as f:
56            self.kconfigs_source_file = os.path.join(tempfile.gettempdir(), f.name)
57            self.addCleanup(os.remove, self.kconfigs_source_file)
58        with tempfile.NamedTemporaryFile(delete=False) as f:
59            self.kconfig_projbuilds_source_file = os.path.join(tempfile.gettempdir(), f.name)
60            self.addCleanup(os.remove, self.kconfig_projbuilds_source_file)
61
62        os.environ['COMPONENT_KCONFIGS_SOURCE_FILE'] = self.kconfigs_source_file
63        os.environ['COMPONENT_KCONFIGS_PROJBUILD_SOURCE_FILE'] = self.kconfig_projbuilds_source_file
64        os.environ['COMPONENT_KCONFIGS'] = ''
65        os.environ['COMPONENT_KCONFIGS_PROJBUILD'] = ''
66
67        # prepare_kconfig_files.py doesn't have to be called because COMPONENT_KCONFIGS and
68        # COMPONENT_KCONFIGS_PROJBUILD are empty
69
70        self.sdkconfig = SDKConfig('data/Kconfig', 'data/sdkconfig')
71
72        with open('data/base.lf') as fragment_file_obj:
73            fragment_file = FragmentFile(fragment_file_obj, self.sdkconfig)
74            self.generation.add_fragments_from_file(fragment_file)
75
76        self.entities = EntityDB()
77
78        with open('data/libfreertos.a.txt') as objdump:
79            self.entities.add_sections_info(objdump)
80
81        with open('data/linker_script.ld') as linker_script:
82            self.linker_script = LinkerScript(linker_script)
83
84    @staticmethod
85    def create_fragment_file(contents, name='test_fragment.lf'):
86        f = StringIO(contents)
87        f.name = name
88        return f
89
90    def add_fragments(self, text):
91        fragment_file = self.create_fragment_file(text)
92        fragment_file = FragmentFile(fragment_file, self.sdkconfig)
93        self.generation.add_fragments_from_file(fragment_file)
94
95    def write(self, expected, actual):
96        self.linker_script.fill(expected)
97        self.linker_script.write(open('expected.ld', 'w'))
98
99        self.linker_script.fill(actual)
100        self.linker_script.write(open('actual.ld', 'w'))
101
102    def generate_default_rules(self):
103        rules = collections.defaultdict(list)
104
105        rules['flash_text'].append(InputSectionDesc(ROOT, ['.literal', '.literal.*', '.text', '.text.*'], []))
106        rules['flash_rodata'].append(InputSectionDesc(ROOT, ['.rodata', '.rodata.*'], []))
107        rules['dram0_data'].append(InputSectionDesc(ROOT, ['.data', '.data.*'], []))
108        rules['dram0_data'].append(InputSectionDesc(ROOT, ['.dram', '.dram.*'], []))
109        rules['dram0_bss'].append(InputSectionDesc(ROOT, ['.bss', '.bss.*'], []))
110        rules['dram0_bss'].append(InputSectionDesc(ROOT, ['COMMON'], []))
111        rules['iram0_text'].append(InputSectionDesc(ROOT, ['.iram', '.iram.*'], []))
112        rules['rtc_text'].append(InputSectionDesc(ROOT, ['.rtc.text', '.rtc.literal'], []))
113        rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.data'], []))
114        rules['rtc_data'].append(InputSectionDesc(ROOT, ['.rtc.rodata'], []))
115        rules['rtc_bss'].append(InputSectionDesc(ROOT, ['.rtc.bss'], []))
116
117        return rules
118
119    def compare_rules(self, expected, actual):
120        self.assertEqual(set(expected.keys()), set(actual.keys()))
121
122        for target in sorted(actual.keys()):
123            message = 'failed target %s' % target
124            a_cmds = actual[target]
125            e_cmds = expected[target]
126
127            self.assertEqual(len(a_cmds), len(e_cmds), message)
128
129            for a, e in zip(a_cmds, e_cmds):
130                self.assertEqual(a, e, message)
131
132    def get_default(self, target, rules):
133        return rules[target][0]
134
135
136class DefaultMappingTest(GenerationTest):
137
138    def test_rule_generation_default(self):
139        # Checks that default rules are generated from
140        # the default scheme properly and even if no mappings
141        # are defined.
142        actual = self.generation.generate(self.entities)
143        expected = self.generate_default_rules()
144
145        self.compare_rules(expected, actual)
146
147    def test_default_mapping_lib(self):
148        # Mapping a library with default mapping. This should not emit additional rules,
149        # other than the default ones.
150        mapping = u"""
151[mapping:test]
152archive: libfreertos.a
153entries:
154    * (default)
155"""
156        self.add_fragments(mapping)
157        self.test_rule_generation_default()
158
159    def test_default_mapping_obj(self):
160        # Mapping an object with default mapping. This should not emit additional rules,
161        # other than the default ones.
162        mapping = u"""
163[mapping:test]
164archive: libfreertos.a
165entries:
166    croutine (default)
167"""
168        self.add_fragments(mapping)
169        self.test_rule_generation_default()
170
171    def test_default_mapping_symbol(self):
172        # Mapping a symbol with default mapping. This should not emit additional rules,
173        # other than the default ones.
174        mapping = u"""
175[mapping:test]
176archive: libfreertos.a
177entries:
178    croutine:prvCheckPendingReadyList (default)                 #1
179"""
180        self.add_fragments(mapping)
181        self.test_rule_generation_default()
182
183    def test_default_mapping_all(self):
184        # Mapping a library, object, and symbol with default mapping. This should not emit additional rules,
185        # other than the default ones.
186        mapping = u"""
187[mapping:test]
188archive: libfreertos.a
189entries:
190    * (default)                                     #1
191    croutine (default)                              #2
192    croutine:prvCheckPendingReadyList (default)     #3
193"""
194        self.add_fragments(mapping)
195        self.test_rule_generation_default()
196
197    def test_default_mapping_lib_symbol(self):
198        # Mapping a library, and symbol with default mapping. This should not emit additional rules,
199        # other than the default ones.
200        #
201        # This is a check needed to make sure generation does not generate
202        # intermediate commands due to presence of symbol mapping.
203        mapping = u"""
204[mapping:test]
205archive: libfreertos.a
206entries:
207    * (default)                                     #1
208    croutine:prvCheckPendingReadyList (default)     #2
209"""
210        self.add_fragments(mapping)
211        self.test_rule_generation_default()
212
213    def test_default_mapping_obj_symbol(self):
214        # Mapping a library, and symbol with default mapping. This should not emit additional rules,
215        # other than the default ones.
216        #
217        # This is a check needed to make sure generation does not generate
218        # intermediate commands due to presence of symbol mapping.
219        mapping = u"""
220[mapping:test]
221archive: libfreertos.a
222entries:
223    croutine (default)                              #1
224    croutine:prvCheckPendingReadyList (default)     #2
225"""
226        self.add_fragments(mapping)
227        self.test_rule_generation_default()
228
229
230class BasicTest(GenerationTest):
231    # Test basic and fundamental interactions between typical
232    # entries.
233
234    def test_nondefault_mapping_lib(self, alt=None):
235        # Test mapping entry different from default for a library.
236        # There should be exclusions in the default commands for flash_text and flash_rodata:
237        #
238        # flash_text
239        #   *((EXCLUDE_FILE(libfreertos.a)) .literal ...)                                                          A
240        #
241        # Commands placing the entire library in iram, dram should be generated:
242        #
243        # iram0_text
244        #   *(.iram ...)
245        #   *libfreertos.a(.literal  ...)                                                                          B
246        mapping = u"""
247[mapping:test]
248archive: libfreertos.a
249entries:
250    * (noflash)                     #1
251"""
252        self.add_fragments(alt if alt else mapping)
253        actual = self.generation.generate(self.entities)
254        expected = self.generate_default_rules()
255
256        flash_text = expected['flash_text']
257        flash_rodata = expected['flash_rodata']
258        iram0_text = expected['iram0_text']
259        dram0_data = expected['dram0_data']
260
261        # Generate exclusions in flash_text and flash_rodata                                                 A
262        flash_text[0].exclusions.add(FREERTOS)
263        flash_rodata[0].exclusions.add(FREERTOS)
264
265        # Input section commands in iram_text and dram0_data for #1                                          B
266        iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, []))
267        dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
268
269        self.compare_rules(expected, actual)
270
271    def test_nondefault_mapping_obj(self, alt=None):
272        # Test mapping entry different from default for an object.
273        # There should be exclusions in the default commands for flash_text and flash_rodata:
274        #
275        # flash_text
276        #   *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...)                                                          A
277        #
278        # Commands placing the entire library in iram, dram should be generated:
279        #
280        # iram0_text
281        #   *(.iram ...)
282        #   *libfreertos.a:croutine(.literal  ...)                                                                          B
283        mapping = u"""
284[mapping:test]
285archive: libfreertos.a
286entries:
287    croutine (noflash)                              #1
288"""
289
290        self.add_fragments(alt if alt else mapping)
291        actual = self.generation.generate(self.entities)
292        expected = self.generate_default_rules()
293
294        flash_text = expected['flash_text']
295        flash_rodata = expected['flash_rodata']
296        iram0_text = expected['iram0_text']
297        dram0_data = expected['dram0_data']
298
299        # Generate exclusions in flash_text and flash_rodata                                                A
300        flash_text[0].exclusions.add(CROUTINE)
301        flash_rodata[0].exclusions.add(CROUTINE)
302
303        # Input section commands in iram_text and dram0_data for #1                                         B
304        iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
305        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
306
307        self.compare_rules(expected, actual)
308
309    def test_nondefault_mapping_symbol(self):
310        # Test mapping entry different from default for symbol.
311        # There should be exclusions in the default commands for flash_text, as well as the implicit intermediate object command
312        # with an exclusion from default:
313        #
314        # flash_text
315        #   *((EXCLUDE_FILE(libfreertos.a:croutine)) .literal ...)                                                          A
316        #   *libfreertos.a:croutine(.literal  .literal.prvCheckDelayedList ...)                                             B
317        #
318        # Commands placing the entire library in iram should be generated:
319        #
320        # iram0_text
321        #   *(.iram ...)
322        #   *libfreertos.a:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)                       C
323        mapping = u"""
324[mapping:test]
325archive: libfreertos.a
326entries:
327    croutine:prvCheckPendingReadyList (noflash)         #1
328"""
329        self.add_fragments(mapping)
330        actual = self.generation.generate(self.entities)
331        expected = self.generate_default_rules()
332
333        flash_text = expected['flash_text']
334        iram0_text = expected['iram0_text']
335
336        # Generate exclusion in flash_text                                                A
337        flash_text[0].exclusions.add(CROUTINE)
338
339        # Generate intermediate command                                                   B
340        # List all relevant sections except the symbol
341        # being mapped
342        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
343        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
344        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
345
346        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
347        filtered_sections.append('.text')
348
349        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
350
351        # Input section commands in iram_text for #1                                     C
352        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
353
354        self.compare_rules(expected, actual)
355
356    def test_default_symbol_nondefault_lib(self):
357        # Test default symbol mapping with different lib mapping. This should create an implicit intermediate object command.
358        # The significant targets are flash_text, flash_rodata, iram0_text, dram0_data.
359        #
360        # flash_text
361        #   *(EXCLUDE_FILE(libfreertos.a) .text ...)                                                        A
362        #  libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)        B
363        #
364        # flash_rodata
365        #   *(EXCLUDE_FILE(libfreertos.a) .rodata ...)                                                      A
366        #
367        # iram0_text
368        #  * ( .iram ...)
369        #  libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...)                                     C.1
370        #   *libfreertos.a:croutine(.literal  .literal.prvCheckDelayedList ...)                             D
371        #
372        # dram0_data
373        #  * ( .dram ...)
374        #  libfreertos.a ( .rodata ...)                                                                     C.2
375        #
376        # Only default commands are in the other targets.
377        mapping = u"""
378[mapping:test]
379archive: libfreertos.a
380entries:
381    * (noflash)                                         #1
382    croutine:prvCheckPendingReadyList (default)         #2
383"""
384
385        self.add_fragments(mapping)
386        actual = self.generation.generate(self.entities)
387        expected = self.generate_default_rules()
388
389        flash_text = expected['flash_text']
390        flash_rodata = expected['flash_rodata']
391        iram0_text = expected['iram0_text']
392        dram0_data = expected['dram0_data']
393
394        # Exclusions for #1                                                                                 A
395        flash_text[0].exclusions.add(FREERTOS)
396        flash_rodata[0].exclusions.add(FREERTOS)
397
398        # Commands for #1                                                                                   C.1 & C.2
399        # C.1 excludes intermediate command for #2
400        iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
401        dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, []))
402
403        # Intermediate command for excluding #2                                                             D
404        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
405        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
406        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
407
408        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
409        filtered_sections.append('.text')
410
411        iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
412
413        # Command for #2                                                                                    B
414        flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
415
416        self.compare_rules(expected, actual)
417
418    def test_default_symbol_nondefault_obj(self):
419        # Test default symbol mapping with different obj mapping. Since there is an explicit entry for the object,
420        # the sections for that object should just be expanded and the symbol section subtracted, to be placed
421        # using another command.
422        #
423        # flash_text
424        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...)                                                        A
425        #   libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)                B
426        #
427        # flash_rodata
428        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...)                                                      A
429        #
430        # iram0_text
431        #   *( .iram ...)
432        #   *libfreertos.a:croutine(.literal  .literal.prvCheckDelayedList ...)                                      C.1
433        #
434        # dram0_data
435        #   *(.data ..)
436        #   *libfreertos.a:croutine(.rodata ....)                                                                    C.2
437        #
438        # Only default commands are in the other targets
439        mapping = u"""
440[mapping:test]
441archive: libfreertos.a
442entries:
443    croutine (noflash)                                  #1
444    croutine:prvCheckPendingReadyList (default)         #2
445"""
446        self.add_fragments(mapping)
447        actual = self.generation.generate(self.entities)
448        expected = self.generate_default_rules()
449
450        flash_text = expected['flash_text']
451        flash_rodata = expected['flash_rodata']
452        iram0_text = expected['iram0_text']
453        dram0_data = expected['dram0_data']
454
455        # Exclusions for #1                                                                                 A
456        flash_text[0].exclusions.add(CROUTINE)
457        flash_rodata[0].exclusions.add(CROUTINE)
458
459        # Commands for #1                                                                                   C.1 & C.2
460        # C.1 list relevant sections for libfreertos.a:croutine to
461        # exclude symbol to map
462        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
463        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
464        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
465
466        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
467        filtered_sections.append('.text')
468
469        iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
470        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
471
472        # Command for #2                                                                                    B
473        flash_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
474
475        self.compare_rules(expected, actual)
476
477    def test_default_nondefault_alternating(self):
478        # Here, each of the entries map sections to something different
479        # than its one-level-up entry.
480        #
481        # *                                         text -> flash, rodata -> flash
482        # libfreertos.a                             text -> iram, rodata -> dram
483        # libfreertos.a:croutine                    text -> flash, rodata -> flash
484        # croutine:prvCheckPendingReadyList         text  -> iram
485        #
486        # The significant targets are flash_text, flash_rodata, iram0_text, and dram0_data.
487        #
488        # flash_text
489        #   *(EXCLUDE_FILE(libfreertos.a) .text ...)                                                         A
490        #   *libfreertos.a:croutine(.literal  .literal.prvCheckDelayedList ...)                              B.1
491        #
492        # flash_rodata
493        #   *(EXCLUDE_FILE(libfreertos.a) .rodata ...)                                                       A
494        #   *libfreertos.a:croutine(.rodata .rodata.*)                                                       B.2
495        #
496        # iram0_text
497        #  * ( .iram ...)
498        #  libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .text ...)                                      C
499        #  libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)         D
500        #
501        # dram0_data
502        #  * ( .dram ...)
503        #  libfreertos.a (EXCLUDE_FILE(libfreertos:croutine) .rodata ...)                                    C
504        #
505        # For the other targets only the default commands should be present.
506        mapping = u"""
507[mapping:test]
508archive: libfreertos.a
509entries:
510    * (noflash)                                         #1
511    croutine (default)                                  #2
512    croutine:prvCheckPendingReadyList (noflash)         #3
513"""
514
515        self.add_fragments(mapping)
516        actual = self.generation.generate(self.entities)
517        expected = self.generate_default_rules()
518
519        flash_text = expected['flash_text']
520        flash_rodata = expected['flash_rodata']
521        iram0_text = expected['iram0_text']
522        dram0_data = expected['dram0_data']
523
524        # Exclusions for #1                                                                                 A
525        # Only for flash_text and flash_rodata
526        flash_text[0].exclusions.add(FREERTOS)
527        flash_rodata[0].exclusions.add(FREERTOS)
528
529        # Commands for #1                                                                                   C
530        # with exclusions for #2
531        iram0_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE]))
532        dram0_data.append(InputSectionDesc(FREERTOS, flash_rodata[0].sections, [CROUTINE]))
533
534        # Commands for #2                                                                                   B.1
535        flash_rodata.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
536
537        # List all relevant sections in case of flash_text                                                  B.2
538        # as exclusion for #3
539        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
540        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
541        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
542
543        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
544        filtered_sections.append('.text')
545
546        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
547
548        # Command for #3                                                                                    D
549        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
550
551        self.compare_rules(expected, actual)
552
553    def test_nondefault_but_same_lib_and_obj(self):
554        # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
555        # to #1. Output is similar to test_different_mapping_lib.
556        mapping = u"""
557[mapping:test]
558archive: libfreertos.a
559entries:
560    * (noflash)                                     #1
561    croutine (noflash)                              #2
562"""
563        self.test_nondefault_mapping_lib(mapping)
564
565    def test_nondefault_but_same_lib_and_sym(self):
566        # Extension of DefaultMappingTest. Commands should not be generated for #2, since it does similar mapping
567        # to #1. Output is similar to test_different_mapping_lib.
568        mapping = u"""
569[mapping:test]
570archive: libfreertos.a
571entries:
572    * (noflash)                                     #1
573    croutine:prvCheckPendingReadyList (noflash)     #2
574"""
575        self.test_nondefault_mapping_lib(mapping)
576
577    def test_nondefault_but_same_obj_and_sym(self):
578        # Commands should not be generated for #2, since it does similar mapping
579        # to #1. Output is similar to test_different_mapping_obj.
580        mapping = u"""
581[mapping:test]
582archive: libfreertos.a
583entries:
584    croutine (noflash)                                     #1
585    croutine:prvCheckPendingReadyList (noflash)            #2
586"""
587        self.test_nondefault_mapping_obj(mapping)
588
589    def test_multiple_symbols_excluded_from_intermediate_command(self):
590        # Test mapping multiple symbols from the same object.
591        # All these symbols must be succesfully excluded from
592        # the intermediate command.
593        #
594        # flash_text
595        #   * (EXCLUDE_FILE(libfreertos.a:croutine) .text ...)                  A
596        #   libfreertos:croutine(.text ...)                                     B
597        #
598        # iram0_text
599        #
600        #
601        mapping = u"""
602[mapping:test]
603archive: libfreertos.a
604entries:
605    croutine:prvCheckPendingReadyList (noflash)            #1
606    croutine:prvCheckDelayedList (noflash)                 #2
607"""
608
609        self.add_fragments(mapping)
610        actual = self.generation.generate(self.entities)
611        expected = self.generate_default_rules()
612
613        flash_text = expected['flash_text']
614        iram0_text = expected['iram0_text']
615
616        # Exclusions for #1 & #2 intermediate command                                       A
617        flash_text[0].exclusions.add(CROUTINE)
618
619        # Intermediate command for #1 & #2 which lists                                      B
620        # all relevant sections in croutine except prvCheckPendingReadyList
621        # and prvCheckDelayedList
622        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
623        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
624        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
625
626        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
627        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
628        filtered_sections.append('.text')
629
630        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
631
632        # Commands for #1 & 2
633        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []))
634        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
635
636        self.compare_rules(expected, actual)
637
638    def test_root_mapping_fragment(self):
639        # Test creation of a mapping fragment that maps '*'.
640        # This should generate another default command in iram0_text:
641        #
642        # iram0_text
643        #   * (.custom_section)                                  A
644        #   * (.iram .iram.*)
645        mapping = u"""
646[sections:custom_section]
647entries:
648    .custom_section
649
650[scheme:custom_scheme]
651entries:
652    custom_section -> iram0_text
653
654[mapping:default2]
655archive: *
656entries:
657    * (custom_scheme)                                           #1
658"""
659
660        self.add_fragments(mapping)
661        actual = self.generation.generate(self.entities)
662        expected = self.generate_default_rules()
663
664        # Generate default command                              A
665        # Since these are the same 'specificity', the commands
666        # are arranged alphabetically.
667        expected['iram0_text'].append(expected['iram0_text'][0])
668        expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
669
670        self.compare_rules(expected, actual)
671
672
673class AdvancedTest(GenerationTest):
674
675    # Test valid but quirky cases, corner cases, failure cases, and
676    # cases involving interaction between schemes, other mapping
677    # fragments.
678
679    def test_same_entity_no_scheme_common(self):
680        # Test same entity being mapped by schemes that have nothing in common.
681        #
682        # noflash_data: rodata -> dram0_data
683        # noflash_text: text -> iram0_text
684        #
685        # This operation should succeed with the following commands:
686        #
687        # flash_text
688        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...)                                                        A
689        #
690        # flash_rodata
691        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...)                                                      B
692        #
693        # iram0_text
694        #   *(.iram ...)
695        #   *libfreertos.a:croutine(.text .text.* ...)                                                               C
696        #
697        # dram0_data
698        #   *(.data ..)
699        #   *(.dram ...)
700        #   *libfreertos.a:croutine(.rodata .rodata.*)                                                               D
701        mapping = u"""
702[mapping:test]
703archive: libfreertos.a
704entries:
705    croutine (noflash_text)                         #1
706    croutine (noflash_data)                         #2
707"""
708        self.add_fragments(mapping)
709        actual = self.generation.generate(self.entities)
710        expected = self.generate_default_rules()
711
712        flash_text = expected['flash_text']
713        flash_rodata = expected['flash_rodata']
714        iram0_text = expected['iram0_text']
715        dram0_data = expected['dram0_data']
716
717        # Exclusions for #1                                                                 A
718        flash_text[0].exclusions.add(CROUTINE)
719
720        # Exclusions for #2                                                                 B
721        flash_rodata[0].exclusions.add(CROUTINE)
722
723        # Command for #1                                                                    C
724        iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
725
726        # Command for #2                                                                    D
727        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
728
729        self.compare_rules(expected, actual)
730
731    def test_same_entity_sub_scheme(self):
732        # Test same entity being mapped by scheme that is a subset of the other.
733        #
734        # noflash: text -> iram0_text, rodata -> dram0_data
735        # noflash_text: text -> iram0_text
736        #
737        # `text -> iram0_text` is common between the two schemes.
738        #
739        # This operation should succeed with the following commands:
740        #
741        # flash_text
742        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .text ...)                                                        A
743        #
744        # flash_rodata
745        #   *(EXCLUDE_FILE(libfreertos.a:croutine) .rodata ...)                                                      B
746        #
747        # iram0_text
748        #   *(.iram ...)
749        #   *libfreertos.a:croutine(.text .text.* ...)                                                               C
750        #
751        # dram0_data
752        #   *(.data ..)
753        #   *(.dram ...)
754        #   *libfreertos.a:croutine(.rodata .rodata.*)                                                               D
755        mapping = u"""
756[mapping:test]
757archive: libfreertos.a
758entries:
759    croutine (noflash)                              #1
760    croutine (noflash_data)                         #2
761"""
762        self.add_fragments(mapping)
763        actual = self.generation.generate(self.entities)
764        expected = self.generate_default_rules()
765
766        flash_text = expected['flash_text']
767        flash_rodata = expected['flash_rodata']
768        iram0_text = expected['iram0_text']
769        dram0_data = expected['dram0_data']
770
771        # Exclusions for #1                                                                 A
772        flash_text[0].exclusions.add(CROUTINE)
773
774        # Exclusions for #1 & #2                                                            B
775        flash_rodata[0].exclusions.add(CROUTINE)
776
777        # Command for #1                                                                    C
778        iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
779
780        # Command for #1 & #2                                                               D
781        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
782
783        self.compare_rules(expected, actual)
784
785    def test_same_entity_conflicting_scheme(self, alt=None):
786        # Test same entity being mapped by scheme conflicting with another.
787        #
788        # rtc = text -> rtc_text, rodata -> rtc_data
789        # noflash = text -> iram0_text, rodata -> dram0_data
790        #
791        # This operation should fail.
792        mapping = u"""
793[mapping:test]
794archive: libfreertos.a
795entries:
796    croutine (noflash)                              #1
797    croutine (rtc)                                  #2
798"""
799        self.add_fragments(alt if alt else mapping)
800
801        with self.assertRaises(GenerationException):
802            self.generation.generate(self.entities)
803
804    def test_complex_mapping_case(self, alt=None):
805        # Test a complex case where an object is mapped using
806        # one scheme, but a specific symbol in that object is mapped
807        # using another. Another object and symbol is mapped the other way around.
808        #
809        # flash_text
810        #   *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .text ...)                                                        A, B
811        #
812        # flash_rodata
813        #   *(EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:timers) .rodata ...)                                                      A, B
814        #
815        # dram0_data
816        #   *(EXCLUDE_FILES(libfreertos.a:timers) .data ..)                                                                               B
817        #   *(.dram ...)
818        #   *libfreertos.a:croutine(.rodata .rodata.*)                                                                                    C
819        #   *libfreertos.a:timers(.rodata.prvProcessReceivedCommands ...)                                                                 E
820        #
821        # dram0_bss
822        #   *(EXCLUDE_FILE(libfreertos.a:timers) .bss .bss.* ...)                                                                         B
823        #   *(EXCLUDE_FILE(libfreertos.a:timers) COMMON)                                                                                  B
824        #
825        # iram0_text
826        #   *(.iram ...)
827        #   *libfreertos.a:croutine(.literal  .literal.prvCheckDelayedList ...)                                                           C
828        #   *libfreertos.a:timers(.literal  .literal.prvProcessReceivedCommands ...)                                                      E
829        #
830        # rtc_text
831        #   *(rtc.text .rtc.literal)
832        #   libfreertos.a:croutine (.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)                                     F
833        #   libfreertos.a:timers (.text .text.prvCheckForValidListAndQueue ...)                                                           D.2
834        #
835        # rtc_data
836        #   *(rtc.data)
837        #   *(rtc.rodata)
838        #   libfreertos.a:timers (.data .data.*)                                                                                          D
839        #   libfreertos.a:timers (.rodata ...)                                                                                            D.2
840        #
841        # rtc_bss
842        #   *(rtc.bss .rtc.bss)
843        #   libfreertos.a:timers (.bss .bss.*)                                                                                            D
844        #   libfreertos.a:timers (COMMON)                                                                                                 D
845        mapping = u"""
846[mapping:test]
847archive: libfreertos.a
848entries:
849    croutine (noflash)                                  #1
850    timers (rtc)                                        #2
851    timers:prvProcessReceivedCommands (noflash)         #3
852    croutine:prvCheckPendingReadyList (rtc)             #4
853"""
854
855        self.add_fragments(alt if alt else mapping)
856        actual = self.generation.generate(self.entities)
857        expected = self.generate_default_rules()
858
859        flash_text = expected['flash_text']
860        flash_rodata = expected['flash_rodata']
861        dram0_data = expected['dram0_data']
862        iram0_text = expected['iram0_text']
863        dram0_bss = expected['dram0_bss']
864        rtc_text = expected['rtc_text']
865        rtc_data = expected['rtc_data']
866        rtc_bss = expected['rtc_bss']
867
868        # Exclusions for #1                                                                 A
869        flash_text[0].exclusions.add(CROUTINE)
870        flash_rodata[0].exclusions.add(CROUTINE)
871
872        # Exclusions for #2                                                                 B
873        flash_text[0].exclusions.add(TIMERS)
874        flash_rodata[0].exclusions.add(TIMERS)
875        dram0_data[0].exclusions.add(TIMERS)
876        dram0_bss[0].exclusions.add(TIMERS)
877        dram0_bss[1].exclusions.add(TIMERS)
878
879        # Commands for #1                                                                   C
880        # List all relevant sections excluding #4 for text -> iram0_text
881        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
882        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
883        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
884
885        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
886        filtered_sections.append('.text')
887
888        iram0_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
889        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
890
891        # Commands for #4                                                                   F
892        # Processed first due to alphabetical ordering
893        rtc_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
894
895        # Commands for #2                                                                   D
896        # List all relevant sections excluding #3 for text -> rtc_text and                  D.2
897        # rodata -> rtc_data
898        timers_sections = self.entities.get_sections('libfreertos.a', 'timers')
899        filtered_sections = fnmatch.filter(timers_sections, '.literal.*')
900        filtered_sections.extend(fnmatch.filter(timers_sections, '.text.*'))
901
902        filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
903        filtered_sections.append('.text')
904        rtc_text.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
905
906        rtc_data.append(InputSectionDesc(TIMERS, dram0_data[0].sections, []))
907        filtered_sections = fnmatch.filter(timers_sections, '.rodata.*')
908        filtered_sections = [s for s in filtered_sections if not s.endswith('prvProcessReceivedCommands')]
909        rtc_data.append(InputSectionDesc(TIMERS, set(filtered_sections), []))
910
911        rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[0].sections, []))
912        rtc_bss.append(InputSectionDesc(TIMERS, dram0_bss[1].sections, []))
913
914        # Commands for #3                                                                  E
915        iram0_text.append(InputSectionDesc(TIMERS, set(['.text.prvProcessReceivedCommands', '.literal.prvProcessReceivedCommands']), []))
916        dram0_data.append(InputSectionDesc(TIMERS, set(['.rodata.prvProcessReceivedCommands']), []))
917
918        self.compare_rules(expected, actual)
919
920    def test_multiple_mapping_fragments(self):
921        # Test mapping multiple fragments succeeds, particularly
922        # generating exclusions from the default command of archive
923        # and object specificity.
924        #
925        # flash_text
926        #   * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
927        #
928        # flash_rodata
929        #   * (EXCLUDE_FILE(libfreertos.a libfreertos.a:croutine) .text ...)
930        #
931        # iram0_text
932        mapping = u"""
933[mapping:test_1]
934archive: libfreertos.a
935entries:
936    croutine (noflash)                              #1
937
938[mapping:test_2]
939archive: libfreertos2.a
940entries:
941    * (noflash)                                     #2
942"""
943
944        self.add_fragments(mapping)
945        actual = self.generation.generate(self.entities)
946        expected = self.generate_default_rules()
947
948        flash_text = expected['flash_text']
949        flash_rodata = expected['flash_rodata']
950        iram0_text = expected['iram0_text']
951        dram0_data = expected['dram0_data']
952
953        # Exclusions for #1                                                                 A
954        flash_text[0].exclusions.add(CROUTINE)
955        flash_rodata[0].exclusions.add(CROUTINE)
956
957        # Exclusions for #1 & #2                                                            B
958        flash_text[0].exclusions.add(FREERTOS2)
959        flash_rodata[0].exclusions.add(FREERTOS2)
960
961        # Command for #1                                                                    C
962        iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
963        dram0_data.append(InputSectionDesc(CROUTINE, flash_rodata[0].sections, []))
964
965        # Command for #1 & #2                                                               D
966        iram0_text.append(InputSectionDesc(FREERTOS2, flash_text[0].sections, []))
967        dram0_data.append(InputSectionDesc(FREERTOS2, flash_rodata[0].sections, []))
968
969        self.compare_rules(expected, actual)
970
971    def test_mapping_same_lib_in_multiple_fragments_no_conflict(self):
972        # Test mapping fragments operating on the same archive.
973        # In these cases, the entries are taken together.
974        #
975        # Uses the same entries as C_05 but spreads them across
976        # two fragments. The output should still be the same.
977        mapping = u"""
978[mapping:test_1]
979archive: libfreertos.a
980entries:
981    croutine (noflash)                                  #1
982    timers:prvProcessReceivedCommands (noflash)         #3
983
984[mapping:test_2]
985archive: libfreertos.a
986entries:
987    timers (rtc)                                        #2
988    croutine:prvCheckPendingReadyList (rtc)             #4
989"""
990        self.test_complex_mapping_case(mapping)
991
992    def test_mapping_same_lib_in_multiple_fragments_conflict(self):
993        # Test mapping fragments operating on the same archive
994        # with conflicting mappings.
995        mapping = u"""
996[mapping:test_1]
997archive: libfreertos.a
998entries:
999    croutine (noflash)                              #1
1000
1001[mapping:test_2]
1002archive: libfreertos.a
1003entries:
1004    croutine (rtc)                                  #2
1005"""
1006        self.test_same_entity_conflicting_scheme(mapping)
1007
1008    def test_command_order(self):
1009        # Test command order sorting: the commands should be sorted by specificity, then
1010        # alphabetically. This contributes to deterministic output given
1011        # the same input mapping entries.
1012        #
1013        # This ordering is also tested in other tests as a side-effect.
1014        #
1015        # flash_text
1016        #   * (EXCLUDE_FILE(libfreertos.a:croutine libfreertos.a:croutine2))                                    A
1017        #   libfreertos.a:croutine(.text ....)                                                                  B
1018        #
1019        # iram0_text
1020        #
1021        #   * (.iram .iram.*)
1022        #   libfreertos:croutine(.text .literal ...)                                                            C
1023        #   libfreertos:croutine(.text.prvCheckDelayedList .literal.prvCheckDelayedList)                        F
1024        #   libfreertos:croutine(.text.prvCheckPendingReadyList .literal.prvCheckPendingReadyList)              G
1025        #   libfreertos2:croutine(.text .literal ...)                                                           D
1026        #   libfreertos2:croutine2(.text .literal ...)                                                          E
1027        mapping = u"""
1028[mapping:freertos2]
1029archive: libfreertos2.a
1030entries:
1031    croutine2 (noflash_text)                                    #1
1032    croutine (noflash_text)                                     #2
1033
1034[mapping:freertos]
1035archive: libfreertos.a
1036entries:
1037    croutine:prvCheckPendingReadyList (noflash_text)            #3
1038    croutine:prvCheckDelayedList (noflash_text)                 #4
1039"""
1040
1041        self.add_fragments(mapping)
1042        actual = self.generation.generate(self.entities)
1043        expected = self.generate_default_rules()
1044
1045        flash_text = expected['flash_text']
1046        iram0_text = expected['iram0_text']
1047
1048        # Exclusions for #1                                                                 A
1049        flash_text[0].exclusions.add(CROUTINE)
1050        flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine2'))
1051        flash_text[0].exclusions.add(Entity(FREERTOS2.archive, 'croutine'))
1052
1053        # Intermediate command for #3 and #4                                                B
1054        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1055
1056        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1057        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1058
1059        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1060        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckDelayedList')]
1061        filtered_sections.append('.text')
1062        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
1063
1064        # Command for
1065        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckDelayedList', '.literal.prvCheckDelayedList']), []))
1066        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1067
1068        iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine'), flash_text[0].sections, []))
1069        iram0_text.append(InputSectionDesc(Entity(FREERTOS2.archive, 'croutine2'), flash_text[0].sections, []))
1070
1071        self.compare_rules(expected, actual)
1072
1073    def test_ambigious_obj(self):
1074        # Command generation for ambiguous entry should fail.
1075        mapping = u"""
1076[mapping:test]
1077archive: libfreertos.a
1078entries:
1079    port:xPortGetTickRateHz (noflash)                 #1
1080"""
1081        self.add_fragments(mapping)
1082
1083        with self.assertRaises(GenerationException):
1084            self.generation.generate(self.entities)
1085
1086    def test_disambiguated_obj(self):
1087        # Test command generation for disambiguated entry. Should produce similar
1088        # results to test_nondefault_mapping_symbol.
1089        mapping = u"""
1090[mapping:test]
1091archive: libfreertos.a
1092entries:
1093    port.c:xPortGetTickRateHz (noflash)                 #1
1094"""
1095        port = Entity('libfreertos.a', 'port.c')
1096        self.add_fragments(mapping)
1097        actual = self.generation.generate(self.entities)
1098        expected = self.generate_default_rules()
1099
1100        flash_text = expected['flash_text']
1101        iram0_text = expected['iram0_text']
1102
1103        # Generate exclusion in flash_text                                                A
1104        flash_text[0].exclusions.add(port)
1105
1106        # Generate intermediate command                                                   B
1107        # List all relevant sections except the symbol
1108        # being mapped
1109        port_sections = self.entities.get_sections('libfreertos.a', 'port.c')
1110        filtered_sections = fnmatch.filter(port_sections, '.literal.*')
1111        filtered_sections.extend(fnmatch.filter(port_sections, '.text.*'))
1112
1113        filtered_sections = [s for s in filtered_sections if not s.endswith('xPortGetTickRateHz')]
1114        filtered_sections.append('.text')
1115
1116        flash_text.append(InputSectionDesc(port, set(filtered_sections), []))
1117
1118        # Input section commands in iram_text for #1                                     C
1119        iram0_text.append(InputSectionDesc(port, set(['.text.xPortGetTickRateHz', '.literal.xPortGetTickRateHz']), []))
1120
1121        self.compare_rules(expected, actual)
1122
1123    def test_root_mapping_fragment_conflict(self):
1124        # Test that root mapping fragments are also checked for
1125        # conflicts.
1126        #
1127        # 'custom_scheme' entries conflict the 'default' scheme
1128        # entries.
1129        mapping = u"""
1130[scheme:custom_scheme]
1131entries:
1132    flash_text -> iram0_text
1133
1134[mapping:default2]
1135archive: *
1136entries:
1137    * (custom_scheme)
1138"""
1139
1140        self.add_fragments(mapping)
1141        with self.assertRaises(GenerationException):
1142            self.generation.generate(self.entities)
1143
1144    def test_root_mapping_fragment_duplicate(self):
1145        # Same root mappings have no effect.
1146        #
1147        # custom_scheme has the 'iram -> iram0_text' in common with
1148        # default scheme
1149        mapping = u"""
1150[sections:custom_section]
1151entries:
1152    .custom_section
1153
1154[scheme:custom_scheme]
1155entries:
1156    iram -> iram0_text
1157    custom_section -> iram0_text
1158
1159[mapping:default2]
1160archive: *
1161entries:
1162    * (custom_scheme)
1163"""
1164
1165        self.add_fragments(mapping)
1166        actual = self.generation.generate(self.entities)
1167        expected = self.generate_default_rules()
1168
1169        # Generate default command                              A
1170        # Since these are the same 'specificity', the commands
1171        # are arranged alphabetically.
1172        expected['iram0_text'].append(expected['iram0_text'][0])
1173        expected['iram0_text'][0] = InputSectionDesc(ROOT, ['.custom_section'], [])
1174
1175        self.compare_rules(expected, actual)
1176
1177
1178class ConfigTest(GenerationTest):
1179    # Test command generation with conditions
1180
1181    def _test_conditional_on_scheme(self, perf, alt=None):
1182        # Test that proper commands are generated if using
1183        # schemes with conditional entries.
1184        scheme = u"""
1185[sections:cond_text_data]
1186entries:
1187    if PERFORMANCE_LEVEL >= 1:
1188        .text+
1189        .literal+
1190    else:
1191        .rodata+
1192
1193[scheme:cond_noflash]
1194entries:
1195    if PERFORMANCE_LEVEL >= 1:
1196        cond_text_data -> iram0_text
1197    else:
1198        cond_text_data -> dram0_data
1199"""
1200
1201        mapping = u"""
1202[mapping:test]
1203archive: lib.a
1204entries:
1205    * (cond_noflash)
1206"""
1207        self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf))
1208        self.add_fragments(scheme)
1209        self.add_fragments(alt if alt else mapping)
1210
1211        actual = self.generation.generate(self.entities)
1212        expected = self.generate_default_rules()
1213
1214        if perf >= 1:
1215            flash_text = expected['flash_text']
1216            iram0_text = expected['iram0_text']
1217            flash_text[0].exclusions.add(Entity('lib.a'))
1218            iram0_text.append(InputSectionDesc(Entity('lib.a'), flash_text[0].sections, []))
1219        else:
1220            flash_rodata = expected['flash_rodata']
1221            dram0_data = expected['dram0_data']
1222            flash_rodata[0].exclusions.add(Entity('lib.a'))
1223            dram0_data.append(InputSectionDesc(Entity('lib.a'), flash_rodata[0].sections, []))
1224
1225        self.compare_rules(expected, actual)
1226
1227    def test_conditional_on_scheme_00(self):
1228        self._test_conditional_on_scheme(0)
1229
1230    def test_conditional_on_scheme_01(self):
1231        self._test_conditional_on_scheme(1)
1232
1233    def test_conditional_mapping(self, alt=None):
1234        # Test that proper commands are generated
1235        # in conditional mapping entries.
1236        mapping = u"""
1237[mapping:default]
1238archive: *
1239entries:
1240    * (default)
1241
1242[mapping:test]
1243archive: lib.a
1244entries:
1245    if PERFORMANCE_LEVEL = 1:
1246        obj1 (noflash)
1247    elif PERFORMANCE_LEVEL = 2:
1248        obj1 (noflash)
1249        obj2 (noflash)
1250    elif PERFORMANCE_LEVEL = 3:
1251        obj1 (noflash)
1252        obj2 (noflash)
1253        obj3 (noflash)
1254"""
1255
1256        for perf_level in range(0, 4):
1257            self.sdkconfig.config.syms['PERFORMANCE_LEVEL'].set_value(str(perf_level))
1258
1259            self.generation.mappings = {}
1260            self.add_fragments(alt if alt else mapping)
1261
1262            actual = self.generation.generate(self.entities)
1263            expected = self.generate_default_rules()
1264
1265            if perf_level < 4 and perf_level > 0:
1266                for append_no in range(1, perf_level + 1):
1267                    flash_text = expected['flash_text']
1268                    flash_rodata = expected['flash_rodata']
1269                    iram0_text = expected['iram0_text']
1270                    dram0_data = expected['dram0_data']
1271
1272                    obj_str = 'obj' + str(append_no)
1273
1274                    flash_text[0].exclusions.add(Entity('lib.a', obj_str))
1275                    flash_rodata[0].exclusions.add(Entity('lib.a', obj_str))
1276
1277                    iram0_text.append(InputSectionDesc(Entity('lib.a', obj_str), flash_text[0].sections, []))
1278                    dram0_data.append(InputSectionDesc(Entity('lib.a', obj_str), flash_rodata[0].sections, []))
1279
1280            self.compare_rules(expected, actual)
1281
1282    def test_conditional_on_scheme_legacy_mapping_00(self):
1283        # Test use of conditional scheme on legacy mapping fragment grammar.
1284        mapping = u"""
1285[mapping]
1286archive: lib.a
1287entries:
1288    * (cond_noflash)
1289"""
1290        self._test_conditional_on_scheme(0, mapping)
1291
1292    def test_conditional_on_scheme_legacy_mapping_01(self):
1293        # Test use of conditional scheme on legacy mapping fragment grammar.
1294        mapping = u"""
1295[mapping]
1296archive: lib.a
1297entries:
1298    * (cond_noflash)
1299"""
1300        self._test_conditional_on_scheme(0, mapping)
1301
1302    def test_conditional_entries_legacy_mapping_fragment(self):
1303        # Test conditional entries on legacy mapping fragment grammar.
1304        mapping = u"""
1305[mapping:default]
1306archive: *
1307entries:
1308    * (default)
1309
1310[mapping]
1311archive: lib.a
1312entries:
1313    : PERFORMANCE_LEVEL = 0
1314    : PERFORMANCE_LEVEL = 1
1315    obj1 (noflash)
1316    : PERFORMANCE_LEVEL = 2
1317    obj1 (noflash)
1318    obj2 (noflash)
1319    : PERFORMANCE_LEVEL = 3
1320    obj1 (noflash)
1321    obj2 (noflash)
1322    obj3 (noflash)
1323"""
1324        self.test_conditional_mapping(mapping)
1325
1326    def test_multiple_fragment_same_lib_conditional_legacy(self):
1327        # Test conditional entries on legacy mapping fragment grammar
1328        # across multiple fragments.
1329        mapping = u"""
1330[mapping:default]
1331archive: *
1332entries:
1333    * (default)
1334
1335[mapping]
1336archive: lib.a
1337entries:
1338    : PERFORMANCE_LEVEL = 0
1339    : PERFORMANCE_LEVEL = 1
1340    obj1 (noflash)
1341    : PERFORMANCE_LEVEL = 2
1342    obj1 (noflash)
1343    : PERFORMANCE_LEVEL = 3
1344    obj1 (noflash)
1345
1346[mapping]
1347archive: lib.a
1348entries:
1349    : PERFORMANCE_LEVEL = 1
1350    obj1 (noflash) # ignore duplicate definition
1351    : PERFORMANCE_LEVEL = 2
1352    obj2 (noflash)
1353    : PERFORMANCE_LEVEL = 3
1354    obj2 (noflash)
1355    obj3 (noflash)
1356"""
1357
1358        self.test_conditional_mapping(mapping)
1359
1360    def test_multiple_fragment_same_lib_conditional(self):
1361        # Test conditional entries on new mapping fragment grammar.
1362        # across multiple fragments.
1363        mapping = u"""
1364[mapping:default]
1365archive: *
1366entries:
1367    * (default)
1368
1369[mapping:base]
1370archive: lib.a
1371entries:
1372    if PERFORMANCE_LEVEL = 1:
1373        obj1 (noflash)
1374    elif PERFORMANCE_LEVEL = 2:
1375        obj1 (noflash)
1376    elif PERFORMANCE_LEVEL = 3:
1377        obj1 (noflash)
1378
1379[mapping:extra]
1380archive: lib.a
1381entries:
1382    if PERFORMANCE_LEVEL = 1:
1383        obj1 (noflash) # ignore duplicate definition
1384    elif PERFORMANCE_LEVEL = 2:
1385        obj2 (noflash)
1386    elif PERFORMANCE_LEVEL = 3:
1387        obj2 (noflash)
1388        obj3 (noflash)
1389"""
1390
1391        self.test_conditional_mapping(mapping)
1392
1393
1394class FlagTest(GenerationTest):
1395
1396    # Test correct generation of mapping fragment entries
1397    # with flags.
1398
1399    def test_flags_basics(self):
1400        # Test that input section commands additions are done (KEEP SORT).
1401        # Test that order dependent commands are properly generated (ALIGN, SURROUND)
1402        # Normally, if an entry has the same mapping as parent, commands.
1403        #   are not emitted for them. However, if there are flags, they should be -
1404        #   only for the scheme entries that have flags, though.
1405        # Flag entries split across multiple entries work.
1406        #
1407        # flash_text
1408        #   *((EXCLUDE_FILE(libfreertos:timers libfreertos:croutine).text ...)          A
1409        #   KEEP(* (SORT_BY_NAME(EXCLUDE_FILE(libfreertos:timers).text) ...)            B
1410        #
1411        # flash_rodata
1412        #   *((EXCLUDE_FILE(libfreertos:timers) .rodata ...)                            C
1413        #   _sym2_start                                                                 D.1
1414        #   . = ALIGN(4)                                                                E.1
1415        #   KEEP(* (EXCLUDE_FILE(libfreertos:timers) .rodata ...)                       F
1416        #   _sym2_end                                                                   D.2
1417        #   . = ALIGN(4)                                                                E.2
1418        #
1419        # iram0_text
1420        #   *(.iram .iram.*)
1421        #   . = ALIGN(4)                                                                G.1
1422        #   _sym1_start                                                                 H.1
1423        #   libfreertos.a:croutine(.text .literal ...)                                  I
1424        #   . = ALIGN(4)                                                                G.2
1425        #   _sym1_end                                                                   H.2
1426        mapping = u"""
1427[mapping:test]
1428archive: libfreertos.a
1429entries:
1430    croutine (noflash_text);
1431        text->iram0_text ALIGN(4, pre, post) SURROUND(sym1)                             #1
1432    timers (default);
1433        text->flash_text KEEP() SORT(name)                                                #2
1434    timers (default);
1435        rodata->flash_rodata SURROUND(sym2) ALIGN(4, pre, post)                         #3
1436"""
1437
1438        self.add_fragments(mapping)
1439
1440        actual = self.generation.generate(self.entities)
1441        expected = self.generate_default_rules()
1442
1443        flash_text = expected['flash_text']
1444        iram0_text = expected['iram0_text']
1445        flash_rodata = expected['flash_rodata']
1446
1447        # Exclusions in flash_text for timers and croutine                      A
1448        flash_text[0].exclusions.add(CROUTINE)
1449        flash_text[0].exclusions.add(TIMERS)
1450
1451        # Command for #3                                                        B
1452        flash_text.append(InputSectionDesc(TIMERS, flash_text[0].sections, [], keep=True, sort=('name', None)))
1453
1454        # Exclusions in flash_rodata for timers                                 C
1455        flash_rodata[0].exclusions.add(TIMERS)
1456
1457        # Commands for #3                                                       D.1, E.1, F, D.2, E.2
1458        flash_rodata.append(SymbolAtAddress('_sym2_start'))
1459        flash_rodata.append(AlignAtAddress(4))
1460        flash_rodata.append(InputSectionDesc(TIMERS, flash_rodata[0].sections, []))
1461        flash_rodata.append(SymbolAtAddress('_sym2_end'))
1462        flash_rodata.append(AlignAtAddress(4))
1463
1464        # Commands for #                                                        G.1, H.1, I, G.2, H.2
1465        iram0_text.append(AlignAtAddress(4))
1466        iram0_text.append(SymbolAtAddress('_sym1_start'))
1467        iram0_text.append(InputSectionDesc(CROUTINE, flash_text[0].sections, []))
1468        iram0_text.append(AlignAtAddress(4))
1469        iram0_text.append(SymbolAtAddress('_sym1_end'))
1470
1471        self.compare_rules(expected, actual)
1472
1473    def test_flags_intermediate_exclusion_command_root(self):
1474        # Test that intermediate exclusion commands from root-level commands
1475        # are included in the flags.
1476        #
1477        # flash_text
1478        #   _sym1_start                                                                 A.1
1479        #   KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...)                        B
1480        #   KEEP(libfreertos.a:croutine(...)))                                          C
1481        #   _sym1_end                                                                   A.2
1482        #
1483        # iram0_text
1484        #   *(.iram .iram.*)
1485        #   libfreertos.a:croutine(.text.prvCheckPendingReadyList ...)                  D
1486        mapping = u"""
1487[mapping:default]
1488archive: *
1489entries:
1490    # 1
1491    * (default);
1492        text->flash_text SURROUND(sym1) KEEP()                            #2
1493
1494[mapping:test]
1495archive: libfreertos.a
1496entries:
1497    croutine:prvCheckPendingReadyList (noflash_text)                    #3
1498"""
1499
1500        self.generation.mappings = {}
1501        self.add_fragments(mapping)
1502
1503        actual = self.generation.generate(self.entities)
1504        expected = self.generate_default_rules()
1505
1506        flash_text = expected['flash_text']
1507        iram0_text = expected['iram0_text']
1508
1509        # Command for #2, pre                                          A.1
1510        flash_text.insert(0, SymbolAtAddress('_sym1_start'))
1511
1512        # Command for #1 with KEEP()                                     B
1513        # and exclusion for #3
1514        flash_text[1].keep = True
1515        flash_text[1].exclusions.add(CROUTINE)
1516
1517        # Implicit exclusion command for #3                            C
1518        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1519        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1520        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1521
1522        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1523        filtered_sections.append('.text')
1524        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
1525
1526        # Command for #2, post                                         A.2
1527        flash_text.append(SymbolAtAddress('_sym1_end'))
1528
1529        # Command for #3                                               D
1530        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1531
1532        self.compare_rules(expected, actual)
1533
1534    def test_flags_intermediate_exclusion_command_lib(self):
1535        # Test that intermediate exclusion commands from lib-level commands
1536        # are included in the flags.
1537        #
1538        # flash_text
1539        #   *(EXCLUDE_FILE(libfreertos.a).text  ...)
1540        #   _sym1_start                                                                 A.1
1541        #   KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...))          B
1542        #   KEEP(libfreertos.a:croutine(...)))                                          C
1543        #   _sym1_end                                                                   A.2
1544        #
1545        # iram0_text
1546        #   *(.iram .iram.*)
1547        #   libfreertos.a:croutine(.text.prvCheckPendingReadyList ...)                  D
1548        mapping = u"""
1549[mapping:test]
1550archive: libfreertos.a
1551entries:
1552    # 1
1553    * (default);
1554        text->flash_text SURROUND(sym1) KEEP()                            #2
1555    croutine:prvCheckPendingReadyList (noflash_text)                    #3
1556"""
1557
1558        self.add_fragments(mapping)
1559
1560        actual = self.generation.generate(self.entities)
1561        expected = self.generate_default_rules()
1562
1563        flash_text = expected['flash_text']
1564        iram0_text = expected['iram0_text']
1565
1566        # Command for #2, pre                                          A.1
1567        flash_text.append(SymbolAtAddress('_sym1_start'))
1568        flash_text[0].exclusions.add(FREERTOS)
1569
1570        # Command for #1 with KEEP()                                     B
1571        # and exclusion for #3
1572        flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
1573
1574        # Implicit exclusion command for #3                            C
1575        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1576        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1577        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1578
1579        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1580        filtered_sections.append('.text')
1581        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
1582
1583        # Command for #2, post                                         A.2
1584        flash_text.append(SymbolAtAddress('_sym1_end'))
1585
1586        # Command for #3                                               C
1587        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1588
1589        self.compare_rules(expected, actual)
1590
1591    def test_flags_intermediate_exclusion_command_obj(self):
1592        # Test that intermediate exclusion commands from obj-level commands
1593        # are included in the flags.
1594        #
1595        # flash_text
1596        #   *(EXCLUDE_FILE(libfreertos.a).text  ...)
1597        #   _sym1_start                                                                 A.1
1598        #   KEEP(libfreertos.a:croutine(...)))                                          B
1599        #   _sym1_end                                                                   A.2
1600        #
1601        # iram0_text
1602        #   *(.iram .iram.*)
1603        #   libfreertos.a:croutine(.text.prvCheckPendingReadyList ...)                  C
1604        mapping = u"""
1605[mapping:test]
1606archive: libfreertos.a
1607entries:
1608    # 1
1609    croutine (default);
1610        text->flash_text SURROUND(sym1) KEEP()                            #2
1611    croutine:prvCheckPendingReadyList (noflash_text)                    #3
1612"""
1613
1614        self.add_fragments(mapping)
1615
1616        actual = self.generation.generate(self.entities)
1617        expected = self.generate_default_rules()
1618
1619        flash_text = expected['flash_text']
1620        iram0_text = expected['iram0_text']
1621
1622        # Command for #2, pre                                          A.1
1623        flash_text.append(SymbolAtAddress('_sym1_start'))
1624        flash_text[0].exclusions.add(CROUTINE)
1625
1626        # Implicit exclusion command for #3                            B
1627        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1628        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1629        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1630
1631        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1632        filtered_sections.append('.text')
1633        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), [], keep=True))
1634
1635        # Command for #2, post                                         A.2
1636        flash_text.append(SymbolAtAddress('_sym1_end'))
1637
1638        # Command for #3                                               C
1639        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1640
1641        self.compare_rules(expected, actual)
1642
1643    def test_flags_separate_exclusion_command_if_explicit_root(self):
1644        # Explicit commands are separated from the parent's flags.
1645        #
1646        # flash_text
1647        #   _sym1_start                                                                 A.1
1648        #   KEEP(* (EXCLUDE_FILE(libfreertos:croutine).text ...)                        B
1649        #   _sym1_end                                                                   A.2
1650        #   KEEP(libfreertos.a:croutine(...)))                                          C
1651        #
1652        # iram0_text
1653        #   *(.iram .iram.*)
1654        #   libfreertos.a:croutine(.text.prvCheckPendingReadyList ...)                  D
1655        mapping = u"""
1656[mapping:default]
1657archive: *
1658entries:
1659    # 1
1660    * (default);
1661        text->flash_text SURROUND(sym1) KEEP()                            #2
1662
1663[mapping:test]
1664archive: libfreertos.a
1665entries:
1666    croutine (default)                                                  #3
1667    croutine:prvCheckPendingReadyList (noflash_text)                    #4
1668"""
1669
1670        self.generation.mappings = {}
1671        self.add_fragments(mapping)
1672
1673        actual = self.generation.generate(self.entities)
1674        expected = self.generate_default_rules()
1675
1676        flash_text = expected['flash_text']
1677        iram0_text = expected['iram0_text']
1678
1679        # Command for #2, pre                                          A.1
1680        flash_text.insert(0, SymbolAtAddress('_sym1_start'))
1681
1682        # Command for #1 with KEEP()                                     B
1683        # and exclusion for #3
1684        flash_text[1].keep = True
1685        flash_text[1].exclusions.add(CROUTINE)
1686
1687        # Command for #2, post                                         A.2
1688        flash_text.append(SymbolAtAddress('_sym1_end'))
1689
1690        # Command for #3                                               C
1691        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1692        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1693        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1694
1695        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1696        filtered_sections.append('.text')
1697        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
1698
1699        # Command for #4                                               D
1700        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1701
1702        self.compare_rules(expected, actual)
1703
1704    def test_flags_separate_exclusion_command_if_explicit_lib(self):
1705        # Explicit commands are separated from the parent's flags.
1706        #
1707        # flash_text
1708        #   *(EXCLUDE_FILE(libfreertos.a).text  ...)
1709        #   _sym1_start                                                                 A.1
1710        #   KEEP(libfreertos.a(EXCLUDE_FILE(libfreertos:croutine).text.* ...))          B
1711        #   _sym1_end                                                                   A.2
1712        #   KEEP(libfreertos.a:croutine(...)))                                          C
1713        #
1714        # iram0_text
1715        #   *(.iram .iram.*)
1716        #   libfreertos.a:croutine(.text.prvCheckPendingReadyList ...)                  D
1717        mapping = u"""
1718[mapping:test]
1719archive: libfreertos.a
1720entries:
1721    # 1
1722    * (default);
1723        text->flash_text SURROUND(sym1) KEEP()
1724    croutine (default)                                              #2
1725    croutine:prvCheckPendingReadyList (noflash_text)                #3
1726"""
1727
1728        self.add_fragments(mapping)
1729
1730        actual = self.generation.generate(self.entities)
1731        expected = self.generate_default_rules()
1732
1733        flash_text = expected['flash_text']
1734        iram0_text = expected['iram0_text']
1735
1736        # Command for #2, pre                                          A.1
1737        flash_text.append(SymbolAtAddress('_sym1_start'))
1738        flash_text[0].exclusions.add(FREERTOS)
1739
1740        # Command for #1 with KEEP()                                     B
1741        # and exclusion for #3
1742        flash_text.append(InputSectionDesc(FREERTOS, flash_text[0].sections, [CROUTINE], keep=True))
1743
1744        # Command for #2, post                                         A.2
1745        flash_text.append(SymbolAtAddress('_sym1_end'))
1746
1747        # Implicit exclusion command for #3                            C
1748        croutine_sections = self.entities.get_sections('libfreertos.a', 'croutine')
1749        filtered_sections = fnmatch.filter(croutine_sections, '.literal.*')
1750        filtered_sections.extend(fnmatch.filter(croutine_sections, '.text.*'))
1751
1752        filtered_sections = [s for s in filtered_sections if not s.endswith('prvCheckPendingReadyList')]
1753        filtered_sections.append('.text')
1754        flash_text.append(InputSectionDesc(CROUTINE, set(filtered_sections), []))
1755
1756        # Command for #3                                               C
1757        iram0_text.append(InputSectionDesc(CROUTINE, set(['.text.prvCheckPendingReadyList', '.literal.prvCheckPendingReadyList']), []))
1758
1759        self.compare_rules(expected, actual)
1760
1761    def test_flag_additions(self):
1762        # Test ability to add flags as long as no other mapping fragments
1763        # does the same thing.
1764        mapping = u"""
1765[mapping:default_add_flag]
1766archive: *
1767entries:
1768    * (default);
1769        text->flash_text KEEP()
1770"""
1771
1772        self.add_fragments(mapping)
1773
1774        actual = self.generation.generate(self.entities)
1775        expected = self.generate_default_rules()
1776
1777        flash_text = expected['flash_text']
1778        flash_text[0].keep = True
1779
1780        self.compare_rules(expected, actual)
1781
1782    def test_flags_flag_additions_duplicate(self):
1783        # Test same flags added to same entity - these
1784        # are ignored.
1785        mapping = u"""
1786[mapping:default_add_flag_1]
1787archive: *
1788entries:
1789    * (default);
1790        text->flash_text KEEP()
1791
1792[mapping:default_add_flag_2]
1793archive: *
1794entries:
1795    * (default);
1796        text->flash_text KEEP()
1797"""
1798
1799        self.add_fragments(mapping)
1800
1801        actual = self.generation.generate(self.entities)
1802        expected = self.generate_default_rules()
1803
1804        flash_text = expected['flash_text']
1805        flash_text[0].keep = True
1806
1807        self.compare_rules(expected, actual)
1808
1809    def test_flags_flag_additions_conflict(self):
1810        # Test condition where multiple fragments specifies flags
1811        # to same entity - should generate exception.
1812        mapping = u"""
1813[mapping:default_add_flag_1]
1814archive: *
1815entries:
1816    * (default);
1817        text->flash_text ALIGN(2)
1818
1819[mapping:default_add_flag_2]
1820archive: *
1821entries:
1822    * (default);
1823        text->flash_text SURROUND(sym1)
1824"""
1825        self.add_fragments(mapping)
1826
1827        with self.assertRaises(GenerationException):
1828            self.generation.generate(self.entities)
1829
1830
1831if __name__ == '__main__':
1832    unittest.main()
1833