Bläddra i källkod

ldgen: additional tests for generation support classes

Renz Bagaporo 5 år sedan
förälder
incheckning
0142676cbf

+ 4 - 2
tools/ci/config/host-test.yml

@@ -72,8 +72,10 @@ test_ldgen_on_host:
   extends: .host_test_template
   script:
     - cd tools/ldgen/test
-    - ./test_fragments.py
-    - ./test_generation.py
+    - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_fragments.py
+    - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_generation.py
+    - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_entity.py
+    - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_output_commands.py
   variables:
     LC_ALL: C.UTF-8
 

+ 2 - 0
tools/ci/executable-list.txt

@@ -88,8 +88,10 @@ tools/kconfig_new/test/confserver/test_confserver.py
 tools/kconfig_new/test/gen_kconfig_doc/test_kconfig_out.py
 tools/kconfig_new/test/gen_kconfig_doc/test_target_visibility.py
 tools/ldgen/ldgen.py
+tools/ldgen/test/test_entity.py
 tools/ldgen/test/test_fragments.py
 tools/ldgen/test/test_generation.py
+tools/ldgen/test/test_output_commands.py
 tools/mass_mfg/mfg_gen.py
 tools/mkdfu.py
 tools/mkuf2.py

+ 78 - 0
tools/ldgen/output_commands.py

@@ -0,0 +1,78 @@
+#
+# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from entity import Entity
+
+
+class InputSectionDesc():
+
+    def __init__(self, entity, sections, exclusions=None):
+        assert(entity.specificity != Entity.Specificity.SYMBOL)
+
+        self.entity = entity
+        self.sections = set(sections)
+
+        self.exclusions = set()
+
+        if exclusions:
+            assert(not [e for e in exclusions if e.specificity == Entity.Specificity.SYMBOL or
+                        e.specificity == Entity.Specificity.NONE])
+            self.exclusions = set(exclusions)
+        else:
+            self.exclusions = set()
+
+    def __str__(self):
+        if self.sections:
+            exclusion_strings = []
+
+            for exc in sorted(self.exclusions):
+                if exc.specificity == Entity.Specificity.ARCHIVE:
+                    exc_string = '*%s' % (exc.archive)
+                else:
+                    exc_string = '*%s:%s.*' % (exc.archive, exc.obj)
+
+                exclusion_strings.append(exc_string)
+
+            section_strings = []
+
+            if exclusion_strings:
+                exclusion_string = 'EXCLUDE_FILE(%s)' % ' '.join(exclusion_strings)
+
+                for section in sorted(self.sections):
+                    section_strings.append('%s %s' % (exclusion_string, section))
+            else:
+                for section in sorted(self.sections):
+                    section_strings.append(section)
+
+            sections_string = '(%s)' % ' '.join(section_strings)
+        else:
+            sections_string = '( )'
+
+        command = None
+
+        if self.entity.specificity == Entity.Specificity.NONE:
+            command = '*%s' % (sections_string)
+        elif self.entity.specificity == Entity.Specificity.ARCHIVE:
+            command = '*%s:%s' % (self.entity.archive, sections_string)
+        else:
+            command = '*%s:%s.*%s' % (self.entity.archive, self.entity.obj, sections_string)
+
+        return command
+
+    def __eq__(self, other):
+        return (self.entity == other.entity and
+                self.sections == other.sections and
+                self.exclusions == other.exclusions)

+ 0 - 0
tools/ldgen/test/data/sample.lf → tools/ldgen/test/data/base.lf


+ 105 - 0
tools/ldgen/test/data/sections.info → tools/ldgen/test/data/libfreertos.a.txt

@@ -343,6 +343,111 @@ Idx Name          Size      VMA       LMA       File off  Algn
  49 .xt.prop      00000408  00000000  00000000  00002f3e  2**0
                   CONTENTS, RELOC, READONLY
 
+port.cpp.obj:     file format elf32-xtensa-le
+
+Sections:
+Idx Name          Size      VMA       LMA       File off  Algn
+  0 .literal.pxPortInitialiseStack 00000018  00000000  00000000  00000034  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  1 .literal.xPortStartScheduler 00000014  00000000  00000000  0000004c  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  2 .literal.xPortSysTickHandler 00000008  00000000  00000000  00000060  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  3 .literal.vPortYieldOtherCore 00000004  00000000  00000000  00000068  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  4 .literal.vPortReleaseTaskMPUSettings 00000004  00000000  00000000  0000006c  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  5 .literal.xPortInIsrContext 00000008  00000000  00000000  00000070  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  6 .iram1.literal 00000004  00000000  00000000  00000078  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  7 .literal.vPortAssertIfInISR 00000018  00000000  00000000  0000007c  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  8 .literal.vPortCPUInitializeMutex 00000004  00000000  00000000  00000094  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+  9 .literal.vPortCPUAcquireMutex 00000030  00000000  00000000  00000098  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 10 .literal.vPortCPUAcquireMutexTimeout 00000030  00000000  00000000  000000c8  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 11 .literal.vPortCPUReleaseMutex 00000028  00000000  00000000  000000f8  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 12 .literal.vPortSetStackWatchpoint 00000008  00000000  00000000  00000120  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 13 .text         00000000  00000000  00000000  00000128  2**0
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 14 .data         00000000  00000000  00000000  00000128  2**0
+                  CONTENTS, ALLOC, LOAD, DATA
+ 15 .bss          00000000  00000000  00000000  00000128  2**0
+                  ALLOC
+ 16 .text.pxPortInitialiseStack 00000086  00000000  00000000  00000128  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 17 .text.vPortEndScheduler 00000005  00000000  00000000  000001b0  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 18 .text.xPortStartScheduler 0000002e  00000000  00000000  000001b8  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 19 .text.xPortSysTickHandler 00000016  00000000  00000000  000001e8  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 20 .text.vPortYieldOtherCore 0000000e  00000000  00000000  00000200  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 21 .text.vPortStoreTaskMPUSettings 00000013  00000000  00000000  00000210  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 22 .text.vPortReleaseTaskMPUSettings 0000000e  00000000  00000000  00000224  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 23 .text.xPortInIsrContext 00000026  00000000  00000000  00000234  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 24 .iram1        0000001a  00000000  00000000  0000025c  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 25 .rodata.str1.4 0000013b  00000000  00000000  00000278  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, DATA
+ 26 .text.vPortAssertIfInISR 00000025  00000000  00000000  000003b4  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 27 .text.vPortCPUInitializeMutex 0000000e  00000000  00000000  000003dc  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 28 .text.vPortCPUAcquireMutex 00000088  00000000  00000000  000003ec  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 29 .text.vPortCPUAcquireMutexTimeout 000000ab  00000000  00000000  00000474  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 30 .text.vPortCPUReleaseMutex 00000061  00000000  00000000  00000520  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 31 .text.vPortSetStackWatchpoint 0000001a  00000000  00000000  00000584  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 32 .text.xPortGetTickRateHz 00000008  00000000  00000000  000005a0  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, CODE
+ 33 .rodata.__func__$5264 00000029  00000000  00000000  000005a8  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, DATA
+ 34 .rodata.__func__$5259 00000029  00000000  00000000  000005d4  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, DATA
+ 35 .rodata.__FUNCTION__$5243 00000013  00000000  00000000  00000600  2**2
+                  CONTENTS, ALLOC, LOAD, READONLY, DATA
+ 36 .bss.port_interruptNesting 00000008  00000000  00000000  00000614  2**2
+                  ALLOC
+ 37 .bss.port_xSchedulerRunning 00000008  00000000  00000000  00000614  2**2
+                  ALLOC
+ 38 .debug_frame  00000190  00000000  00000000  00000614  2**2
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 39 .debug_info   00000e78  00000000  00000000  000007a4  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 40 .debug_abbrev 00000404  00000000  00000000  0000161c  2**0
+                  CONTENTS, READONLY, DEBUGGING
+ 41 .debug_loc    000005f1  00000000  00000000  00001a20  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 42 .debug_aranges 00000098  00000000  00000000  00002011  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 43 .debug_ranges 000000a0  00000000  00000000  000020a9  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 44 .debug_line   000005fb  00000000  00000000  00002149  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 45 .debug_str    0000071f  00000000  00000000  00002744  2**0
+                  CONTENTS, READONLY, DEBUGGING
+ 46 .comment      0000003b  00000000  00000000  00002e63  2**0
+                  CONTENTS, READONLY
+ 47 .xtensa.info  00000038  00000000  00000000  00002e9e  2**0
+                  CONTENTS, READONLY
+ 48 .xt.lit       00000068  00000000  00000000  00002ed6  2**0
+                  CONTENTS, RELOC, READONLY
+ 49 .xt.prop      00000408  00000000  00000000  00002f3e  2**0
+                  CONTENTS, RELOC, READONLY
+
 portasm.S.obj:     file format elf32-xtensa-le
 
 Sections:

+ 0 - 0
tools/ldgen/test/data/template.ld → tools/ldgen/test/data/linker_script.ld


+ 39 - 0
tools/ldgen/test/data/test_entity/libfreertos.a.txt

@@ -0,0 +1,39 @@
+In archive /home/user/build/esp-idf/freertos/libfreertos.a:
+
+croutine.c.obj:     file format elf32-xtensa-le
+
+Sections:
+Idx Name          Size      VMA       LMA       File off  Algn
+  0 .literal.prvCheckPendingReadyList 00000018  00000000  00000000  00000034  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  1 .literal.prvCheckDelayedList 0000002c  00000000  00000000  0000004c  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+
+croutine.cpp.obj:     file format elf32-xtensa-le
+
+Sections:
+Idx Name          Size      VMA       LMA       File off  Algn
+  9 .text.prvCheckPendingReadyList 00000056  00000000  00000000  000000d8  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+ 10 .text.prvCheckDelayedList 000000ac  00000000  00000000  00000130  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+
+croutine.S.obj:     file format elf32-xtensa-le
+
+Sections:
+Idx Name          Size      VMA       LMA       File off  Algn
+ 26 .debug_frame  000000a0  00000000  00000000  00000394  2**2
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 27 .debug_info   000006b8  00000000  00000000  00000434  2**0
+                  CONTENTS, RELOC, READONLY, DEBUGGING
+ 28 .debug_abbrev 00000233  00000000  00000000  00000aec  2**0
+                  CONTENTS, READONLY, DEBUGGING
+
+timers.o:     file format elf32-xtensa-le
+
+Sections:
+Idx Name          Size      VMA       LMA       File off  Algn
+  0 .literal.prvGetNextExpireTime 00000004  00000000  00000000  00000034  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
+  1 .literal.prvInsertTimerInActiveList 00000010  00000000  00000000  00000038  2**2
+                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

+ 3 - 3
tools/ldgen/test/data/sections_parse.info → tools/ldgen/test/data/test_entity/parse_test.txt

@@ -1,4 +1,4 @@
-In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/libsections_parse.a:
+In archive /home/user/ãóç+ěščřžýáíé/build/esp-idf/freertos/ěščřžýáíé.a:
 
 croutine.c.obj:     file format elf32-littleriscv
 
@@ -11,9 +11,9 @@ Idx Name          Size      VMA       LMA       File off  Algn
   2 .bss          00000000  00000000  00000000  00000034  2**0
                   ALLOC
 
-FreeRTOS-openocd.c.obj:     file format elf32-xtensa-le // 'F' should not get included in match for 'CONTENTS, ALLOC, LOAD ...' prior
+FreeRTOS-ěščřžýáíé.c.obj:     file format elf32-xtensa-le // 'F' should not get included in match for 'CONTENTS, ALLOC, LOAD ...' prior
 
 Sections:
 Idx Name          Size      VMA       LMA       File off  Algn
-  0 .literal.prvCheckPendingReadyList 00000018  00000000  00000000  00000034  2**2
+  0 .literal.ěščřžýáíé 00000018  00000000  00000000  00000034  2**2
                   CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

+ 251 - 0
tools/ldgen/test/test_entity.py

@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+# coding=utf-8
+#
+# Copyright 2018-2020 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import sys
+import unittest
+
+try:
+    from entity import Entity, EntityDB
+except ImportError:
+    sys.path.append('../')
+    from entity import Entity, EntityDB
+
+
+class EntityTest(unittest.TestCase):
+
+    def test_create_none(self):
+        entity = Entity(Entity.ALL)
+        self.assertEqual(Entity.Specificity.NONE, entity.specificity)
+
+        entity = Entity(None)
+        self.assertEqual(Entity.Specificity.NONE, entity.specificity)
+
+        entity = Entity()
+        self.assertEqual(Entity.Specificity.NONE, entity.specificity)
+
+    def test_create_archive(self):
+        entity = Entity('libfreertos.a')
+        self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity)
+
+        entity = Entity('libfreertos.a', Entity.ALL, Entity.ALL)
+        self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity)
+
+        entity = Entity('libfreertos.a', None, None)
+        self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity)
+
+        entity = Entity('libfreertos.a', Entity.ALL, None)
+        self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity)
+
+        entity = Entity('libfreertos.a', None, Entity.ALL)
+        self.assertEqual(Entity.Specificity.ARCHIVE, entity.specificity)
+
+    def test_create_obj(self):
+        entity = Entity('libfreertos.a', 'croutine')
+        self.assertEqual(Entity.Specificity.OBJ, entity.specificity)
+
+        entity = Entity('libfreertos.a', 'croutine', Entity.ALL)
+        self.assertEqual(Entity.Specificity.OBJ, entity.specificity)
+
+        entity = Entity('libfreertos.a', 'croutine', None)
+        self.assertEqual(Entity.Specificity.OBJ, entity.specificity)
+
+    def test_create_symbol(self):
+        entity = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')
+        self.assertEqual(Entity.Specificity.SYMBOL, entity.specificity)
+
+    def test_create_invalid(self):
+        with self.assertRaises(ValueError):
+            Entity(None, 'croutine')
+
+        with self.assertRaises(ValueError):
+            Entity(Entity.ALL, 'croutine')
+
+        with self.assertRaises(ValueError):
+            Entity(None, None, 'prvCheckPendingReadyList')
+
+        with self.assertRaises(ValueError):
+            Entity(Entity.ALL, Entity.ALL, 'prvCheckPendingReadyList')
+
+        with self.assertRaises(ValueError):
+            Entity(None, Entity.ALL, 'prvCheckPendingReadyList')
+
+        with self.assertRaises(ValueError):
+            Entity(Entity.ALL, None, 'prvCheckPendingReadyList')
+
+        with self.assertRaises(ValueError):
+            Entity('libfreertos.a', None, 'prvCheckPendingReadyList')
+
+        with self.assertRaises(ValueError):
+            Entity('libfreertos.a', Entity.ALL, 'prvCheckPendingReadyList')
+
+    def test_compare_different_specificity(self):
+        # Different specificity: NONE < ARCHIVE < OBJ < SYMBOL
+        entity_a = Entity()
+        entity_b = Entity('libfreertos.a')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a')
+        entity_b = Entity('libfreertos.a', 'croutine')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine')
+        entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity(Entity.ALL)
+        entity_b = Entity('libfreertos.a')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', Entity.ALL)
+        entity_b = Entity('libfreertos.a', 'croutine')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine', Entity.ALL)
+        entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')
+        self.assertLess(entity_a, entity_b)
+
+    def test_compare_equal(self):
+        # Compare equal specificities and members
+        entity_a = Entity()
+        entity_b = Entity()
+        self.assertEqual(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a')
+        entity_b = Entity('libfreertos.a')
+        self.assertEqual(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine')
+        entity_b = Entity('libfreertos.a', 'croutine')
+        self.assertEqual(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')
+        entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')
+        self.assertEqual(entity_a, entity_b)
+
+    def test_compare_none_vs_all(self):
+        # Two entities might have the same specifity whether
+        # Entity.ALL is used or not specified; the latter is
+        # considered less than the former.
+        entity_a = Entity()
+        entity_b = Entity(Entity.ALL)
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a')
+        entity_b = Entity('libfreertos.a', Entity.ALL, Entity.ALL)
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine')
+        entity_b = Entity('libfreertos.a', 'croutine', Entity.ALL)
+        self.assertLess(entity_a, entity_b)
+
+    def test_compare_same_specificity(self):
+        # Test that entities will be compared alphabetically
+        # when the specificities are the same.
+        entity_a = Entity('libfreertos_a.a')
+        entity_b = Entity('libfreertos_b.a')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos_b.a', 'croutine_a')
+        entity_b = Entity('libfreertos_a.a', 'croutine_b')
+        self.assertLess(entity_b, entity_a)
+
+        entity_a = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList_a')
+        entity_b = Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList_b')
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine_b', 'prvCheckPendingReadyList_a')
+        entity_b = Entity('libfreertos.a', 'croutine_a', 'prvCheckPendingReadyList_b')
+        self.assertLess(entity_b, entity_a)
+
+        entity_a = Entity('libfreertos_a.a', 'croutine_b', 'prvCheckPendingReadyList_a')
+        entity_b = Entity('libfreertos_b.a', 'croutine_a', 'prvCheckPendingReadyList_b')
+        self.assertLess(entity_a, entity_b)
+
+    def test_compare_all_non_character(self):
+        # Test that Entity.ALL is not treated as an
+        # ordinary character in comparisons.
+        entity_a = Entity(Entity.ALL)
+        entity_b = Entity(chr(ord(Entity.ALL[0]) - 1))
+
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', Entity.ALL)
+        entity_b = Entity('libfreertos.a', chr(ord(Entity.ALL[0]) - 1))
+
+        self.assertLess(entity_a, entity_b)
+
+        entity_a = Entity('libfreertos.a', 'croutine', '*')
+        entity_b = Entity('libfreertos.a', 'croutine', chr(ord(Entity.ALL[0]) - 1))
+
+        self.assertLess(entity_a, entity_b)
+
+
+class EntityDBTest(unittest.TestCase):
+
+    def setUp(self):
+        self.entities = EntityDB()
+
+        with open('data/test_entity/libfreertos.a.txt') as objdump:
+            self.entities.add_sections_info(objdump)
+
+    def test_get_archives(self):
+        archives = self.entities.get_archives()
+        self.assertEqual(set(archives), set(['libfreertos.a']))
+
+    def test_get_objs(self):
+        objs = self.entities.get_objects('libfreertos.a')
+        self.assertEqual(set(objs), set(['croutine.S.obj', 'croutine.c.obj', 'croutine.cpp.obj', 'timers.o']))
+
+    def test_get_sections(self):
+        # Needs disambugation between possible matches: croutine.S, croutine.c, croutine.cpp
+        with self.assertRaises(ValueError):
+            self.entities.get_sections('libfreertos.a', 'croutine')
+
+        # Test disambugation works
+        sections = self.entities.get_sections('libfreertos.a', 'croutine.c')
+        expected = set(['.literal.prvCheckPendingReadyList', '.literal.prvCheckDelayedList'])
+        self.assertEqual(set(sections), expected)
+
+        sections = self.entities.get_sections('libfreertos.a', 'croutine.S')
+        expected = set(['.debug_frame', '.debug_info', '.debug_abbrev'])
+        self.assertEqual(set(sections), expected)
+
+        # Test .o extension works
+        sections = self.entities.get_sections('libfreertos.a', 'timers')
+        expected = set(['.literal.prvGetNextExpireTime', '.literal.prvInsertTimerInActiveList'])
+        self.assertEqual(set(sections), expected)
+
+    def test_parsing(self):
+        # Tests parsing objdump with the following:
+        #
+        # - non-ascii characters
+        # - different architecture string
+        # - different column entries for each sections
+        # - unexpected 'comments'
+        with open('data/test_entity/parse_test.txt') as objdump:
+            self.entities.add_sections_info(objdump)
+
+        sections = self.entities.get_sections('ěščřžýáíé.a', 'croutine')
+        self.assertEqual(set(sections), set(['.text', '.data', '.bss']))
+
+        sections = self.entities.get_sections('ěščřžýáíé.a', 'FreeRTOS-ěščřžýáíé')
+        self.assertEqual(set(sections), set(['.literal.ěščřžýáíé']))
+
+
+if __name__ == '__main__':
+    unittest.main()

+ 141 - 0
tools/ldgen/test/test_output_commands.py

@@ -0,0 +1,141 @@
+#!/usr/bin/env python
+#
+# Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import sys
+import unittest
+
+try:
+    from output_commands import InputSectionDesc
+except ImportError:
+    sys.path.append('../')
+    from output_commands import InputSectionDesc
+
+from entity import Entity
+
+SECTIONS = ['.text', '.text.*', '.literal', '.literal.*']
+
+FREERTOS = Entity('libfreertos.a')
+CROUTINE = Entity('libfreertos.a', 'croutine')
+
+
+class InputSectionDescTest(unittest.TestCase):
+
+    def test_output_00(self):
+        # Test default (catch-all) command
+        expected = '*(.literal .literal.* .text .text.*)'
+
+        desc = InputSectionDesc(Entity(), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity(Entity.ALL), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+    def test_output_01(self):
+        # Test library placement command
+        expected = '*libfreertos.a:(.literal .literal.* .text .text.*)'
+
+        desc = InputSectionDesc(Entity('libfreertos.a'), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity('libfreertos.a', Entity.ALL), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity('libfreertos.a', None, Entity.ALL), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity('libfreertos.a', Entity.ALL, Entity.ALL), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+    def test_output_02(self):
+        # Test object placement command
+        expected = '*libfreertos.a:croutine.*(.literal .literal.* .text .text.*)'
+
+        desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        desc = InputSectionDesc(Entity('libfreertos.a', 'croutine', Entity.ALL), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+        # Disambugated placement
+        expected = '*libfreertos.a:croutine.c.*(.literal .literal.* .text .text.*)'
+
+        desc = InputSectionDesc(Entity('libfreertos.a', 'croutine.c'), SECTIONS)
+        self.assertEqual(expected, str(desc))
+
+    def test_output_03(self):
+        # Invalid entity specification
+        with self.assertRaises(AssertionError):
+            InputSectionDesc(Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList'), SECTIONS)
+
+        with self.assertRaises(AssertionError):
+            InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS, [Entity()])
+
+        with self.assertRaises(AssertionError):
+            InputSectionDesc(Entity('libfreertos.a', 'croutine'), SECTIONS, [Entity('libfreertos.a', 'croutine', 'prvCheckPendingReadyList')])
+
+    def test_output_04(self):
+        # Test exclusions
+
+        # Library
+        expected = ('*libfreertos.a:croutine.*'
+                    '(EXCLUDE_FILE(*libfreertos.a) '
+                    '.literal EXCLUDE_FILE(*libfreertos.a) '
+                    '.literal.* EXCLUDE_FILE(*libfreertos.a) '
+                    '.text EXCLUDE_FILE(*libfreertos.a) .text.*)')
+        desc = InputSectionDesc(CROUTINE, SECTIONS, [FREERTOS])
+        self.assertEqual(expected, str(desc))
+
+        # Object
+        expected = ('*libfreertos.a:croutine.*'
+                    '(EXCLUDE_FILE(*libfreertos.a:croutine.*) '
+                    '.literal EXCLUDE_FILE(*libfreertos.a:croutine.*) '
+                    '.literal.* EXCLUDE_FILE(*libfreertos.a:croutine.*) '
+                    '.text EXCLUDE_FILE(*libfreertos.a:croutine.*) .text.*)')
+        desc = InputSectionDesc(CROUTINE, SECTIONS, [CROUTINE])
+        self.assertEqual(expected, str(desc))
+
+        # Multiple exclusions
+        expected = ('*libfreertos.a:croutine.*'
+                    '(EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) '
+                    '.literal EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) '
+                    '.literal.* EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) '
+                    '.text EXCLUDE_FILE(*libfreertos.a *libfreertos.a:croutine.*) .text.*)')
+        desc = InputSectionDesc(CROUTINE, SECTIONS, [FREERTOS, CROUTINE])
+        self.assertEqual(expected, str(desc))
+
+        # Disambugated exclusion
+        expected = ('*libfreertos.a:croutine.*'
+                    '(EXCLUDE_FILE(*libfreertos.a:croutine.c.*) '
+                    '.literal EXCLUDE_FILE(*libfreertos.a:croutine.c.*) '
+                    '.literal.* EXCLUDE_FILE(*libfreertos.a:croutine.c.*) '
+                    '.text EXCLUDE_FILE(*libfreertos.a:croutine.c.*) .text.*)')
+        desc = InputSectionDesc(CROUTINE, SECTIONS, [Entity('libfreertos.a', 'croutine.c')])
+        self.assertEqual(expected, str(desc))
+
+    def test_output_05(self):
+        # Test empty sections
+        expected = '*libfreertos.a:croutine.*( )'
+
+        desc = InputSectionDesc(Entity('libfreertos.a', 'croutine'), [])
+        self.assertEqual(expected, str(desc))
+
+
+if __name__ == '__main__':
+    unittest.main()