From 23c7af799f565a027e47ab047213483a8ab03550 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 10:29:53 -0600 Subject: [PATCH 1/9] Handle multiple args per line in IAR cmd parser --- tools/memap.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index 0b468f8a59..f8f7369e4b 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -432,16 +432,15 @@ class MemapParser(object): for line in lines: if line.startswith("*"): break - is_cmdline_file = RE_CMDLINE_FILE_IAR.match(line) - if is_cmdline_file: - full_path = is_cmdline_file.group(1) - self.cmd_modules[os.path.basename(full_path)] = full_path + for arg in line.split(" "): + arg = arg.rstrip(" \n") + if (not arg.startswith("-")) and arg.endswith(".o"): + self.cmd_modules[os.path.basename(arg)] = arg common_prefix = os.path.dirname(os.path.commonprefix(self.cmd_modules.values())) self.cmd_modules = {s: os.path.relpath(f, common_prefix) for s, f in self.cmd_modules.items()} - def parse_map_file_iar(self, file_desc): """ Main logic to decode IAR map files From 003913b22f4dfd7fc8ae7e59229761b5a601c1d3 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 10:31:00 -0600 Subject: [PATCH 2/9] Use os.sep and os.join instead of string ops --- tools/memap.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index f8f7369e4b..221d6a3c64 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -136,7 +136,6 @@ class MemapParser(object): txt - the path to parse the object and module name from """ - line = line.replace('\\', '/') test_re_mbed_os_name = re.match(RE_OBJECT_FILE_GCC, line) if test_re_mbed_os_name: @@ -145,7 +144,7 @@ class MemapParser(object): # corner case: certain objects are provided by the GCC toolchain if 'arm-none-eabi' in line: - return '[lib]/misc/' + object_name + return os.path.join('[lib]', 'misc', object_name) return object_name else: @@ -153,10 +152,10 @@ class MemapParser(object): test_re_obj_name = re.match(RE_LIBRARY_OBJECT_GCC, line) if test_re_obj_name: - object_name = test_re_obj_name.group(1) + '/' + \ - test_re_obj_name.group(2) + object_name = os.path.join(test_re_obj_name.group(1), + test_re_obj_name.group(2)) - return '[lib]/' + object_name + return os.path.join('[lib]', object_name) else: print "Unknown object name found in GCC map file: %s" % line @@ -241,8 +240,9 @@ class MemapParser(object): else: is_obj = re.match(RE_OBJECT_ARMCC, line) if is_obj: - object_name = os.path.basename(is_obj.group(1)) + '/' + is_obj.group(3) - return '[lib]/' + object_name + object_name = os.path.join(os.path.basename(is_obj.group(1)), + is_obj.group(3)) + return os.path.join('[lib]', object_name) else: print "Malformed input found when parsing ARMCC map: %s" % line return '[misc]' @@ -472,7 +472,7 @@ class MemapParser(object): object_name = self.check_new_object_lib_iar(line) if object_name and current_library: - temp = '[lib]' + '/'+ current_library + '/'+ object_name + temp = os.path.join('[lib]', current_library, object_name) self.module_replace(object_name, temp) @@ -495,10 +495,10 @@ class MemapParser(object): else: self.short_modules = dict() for module_name, v in self.modules.items(): - split_name = module_name.split('/') + split_name = module_name.split(os.sep) if split_name[0] == '': split_name = split_name[1:] - new_name = "/".join(split_name[:depth]) + new_name = os.path.join(*split_name[:depth]) self.short_modules.setdefault(new_name, {}) for section_idx, value in v.items(): self.short_modules[new_name].setdefault(section_idx, 0) From fc9bd60d4c0eb21365113280f4403b8c0c454585 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 10:42:29 -0600 Subject: [PATCH 3/9] Format memap --- tools/memap.py | 67 ++++++++++---------------------------------------- 1 file changed, 13 insertions(+), 54 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index 221d6a3c64..f5574b17e0 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -50,12 +50,11 @@ class MemapParser(object): sections = ('.text', '.data', '.bss', '.heap', '.stack') def __init__(self): - """ General initialization - """ - # list of all modules and their sections - self.modules = dict() # full list - doesn't change with depth - self.short_modules = dict() # short version with specific depth + # full list - doesn't change with depth + self.modules = dict() + # short version with specific depth + self.short_modules = dict() # sections must be defined in this order to take irrelevant out self.all_sections = self.sections + self.other_sections + \ @@ -64,27 +63,27 @@ class MemapParser(object): # Memory report (sections + summary) self.mem_report = [] - # Just the memory summary section + # Memory summary self.mem_summary = dict() + # Totals of ".text", ".data" and ".bss" self.subtotal = dict() + # Flash no associated with a module self.misc_flash_mem = 0 # Modules passed to the linker on the command line # this is a dict because modules are looked up by their basename self.cmd_modules = {} - def module_add(self, object_name, size, section): - """ Adds a module / section to the list + """ Adds a module or section to the list Positional arguments: object_name - name of the entry to add size - the size of the module being added section - the section the module contributes to """ - if not object_name or not size or not section: return @@ -117,7 +116,6 @@ class MemapParser(object): Positional arguments: line - the line to check for a new section """ - for i in self.all_sections: if line.startswith(i): # should name of the section (assuming it's a known one) @@ -135,11 +133,9 @@ class MemapParser(object): Positional arguments: txt - the path to parse the object and module name from """ - test_re_mbed_os_name = re.match(RE_OBJECT_FILE_GCC, line) if test_re_mbed_os_name: - object_name = test_re_mbed_os_name.group(1) # corner case: certain objects are provided by the GCC toolchain @@ -148,15 +144,12 @@ class MemapParser(object): return object_name else: - test_re_obj_name = re.match(RE_LIBRARY_OBJECT_GCC, line) if test_re_obj_name: object_name = os.path.join(test_re_obj_name.group(1), test_re_obj_name.group(2)) - return os.path.join('[lib]', object_name) - else: print "Unknown object name found in GCC map file: %s" % line return '[misc]' @@ -171,7 +164,6 @@ class MemapParser(object): Positional arguments: line - the line to parse a section from """ - is_fill = re.match(RE_FILL_SECTION_GCC, line) if is_fill: o_name = '[fill]' @@ -193,7 +185,6 @@ class MemapParser(object): Positional arguments: file_desc - a stream object to parse as a gcc map file """ - current_section = 'unknown' with file_desc as infile: @@ -211,7 +202,6 @@ class MemapParser(object): current_section = next_section object_name, object_size = self.parse_section_gcc(line) - self.module_add(object_name, object_size, current_section) common_prefix = os.path.dirname(os.path.commonprefix([ @@ -232,9 +222,7 @@ class MemapParser(object): Positional arguments: line - the line containing the object or library """ - - # simple object (not library) - if line[-2] == '.' and line[-1] == 'o': + if line.endswith(".o"): return line else: @@ -247,8 +235,6 @@ class MemapParser(object): print "Malformed input found when parsing ARMCC map: %s" % line return '[misc]' - - def parse_section_armcc(self, line): """ Parse data from an armcc map file @@ -260,11 +246,9 @@ class MemapParser(object): Positional arguments: line - the line to parse the section data from """ - test_re_armcc = re.match(RE_ARMCC, line) if test_re_armcc: - size = int(test_re_armcc.group(2), 16) if test_re_armcc.group(4) == 'RO': @@ -283,7 +267,7 @@ class MemapParser(object): return ["", 0, ""] # check name of object or library - object_name = self.parse_object_name_armcc(\ + object_name = self.parse_object_name_armcc( test_re_armcc.group(6)) return [object_name, size, section] @@ -297,8 +281,6 @@ class MemapParser(object): Positional arguments: line - the line containing the object or library """ - - # simple object (not library) if object_name.endswith(".o"): try: return self.cmd_modules[object_name] @@ -307,7 +289,6 @@ class MemapParser(object): else: return '[misc]' - def parse_section_iar(self, line): """ Parse data from an IAR map file @@ -325,13 +306,8 @@ class MemapParser(object): Positional_arguments: line - the line to parse section data from """ - test_re_iar = re.match(RE_IAR, line) - if test_re_iar: - - size = int(test_re_iar.group(4), 16) - if (test_re_iar.group(2) == 'const' or test_re_iar.group(2) == 'ro code'): section = '.text' @@ -348,14 +324,16 @@ class MemapParser(object): section = '.data' else: print "Malformed input found when parsing IAR map: %s" % line + return ["", 0, ""] # lookup object in dictionary and return module name object_name = self.parse_object_name_iar(test_re_iar.group(5)) + size = int(test_re_iar.group(4), 16) return [object_name, size, section] else: - return ["", 0, ""] # no valid entry + return ["", 0, ""] def parse_map_file_armcc(self, file_desc): """ Main logic to decode armc5 map files @@ -363,9 +341,7 @@ class MemapParser(object): Positional arguments: file_desc - a file like object to parse as an armc5 map file """ - with file_desc as infile: - # Search area to parse for line in infile: if line.startswith(' Base Addr Size'): @@ -387,18 +363,13 @@ class MemapParser(object): new_modules[name] = stats self.modules = new_modules - - def check_new_library_iar(self, line): """ Searches for libraries and returns name. Example: m7M_tls.a: [43] """ - - test_address_line = re.match(RE_LIBRARY_IAR, line) - if test_address_line: return test_address_line.group(1) else: @@ -415,9 +386,7 @@ class MemapParser(object): I64DivZer.o 2 """ - test_address_line = re.match(RE_OBJECT_LIBRARY_IAR, line) - if test_address_line: return test_address_line.group(1) else: @@ -447,7 +416,6 @@ class MemapParser(object): Positional arguments: file_desc - a file like object to parse as an IAR map file """ - with file_desc as infile: self.parse_iar_command_line(infile) @@ -463,7 +431,6 @@ class MemapParser(object): current_library = "" for line in infile: - library = self.check_new_library_iar(line) if library: @@ -475,7 +442,6 @@ class MemapParser(object): temp = os.path.join('[lib]', current_library, object_name) self.module_replace(object_name, temp) - def reduce_depth(self, depth): """ populates the short_modules attribute with a truncated module list @@ -504,7 +470,6 @@ class MemapParser(object): self.short_modules[new_name].setdefault(section_idx, 0) self.short_modules[new_name][section_idx] += self.modules[module_name][section_idx] - export_formats = ["json", "csv-ci", "table"] def generate_output(self, export_format, depth, file_output=None): @@ -519,10 +484,8 @@ class MemapParser(object): Returns: generated string for the 'table' format, otherwise None """ - self.reduce_depth(depth) self.compute_report() - try: if file_output: file_desc = open(file_output, 'wb') @@ -550,7 +513,6 @@ class MemapParser(object): """ file_desc.write(json.dumps(self.mem_report, indent=4)) file_desc.write('\n') - return None def generate_csv(self, file_desc): @@ -577,7 +539,6 @@ class MemapParser(object): csv_writer.writerow(csv_module_section) csv_writer.writerow(csv_sizes) - return None def generate_table(self, file_desc): @@ -659,7 +620,6 @@ class MemapParser(object): mapfile - the file name of the memory map file toolchain - the toolchain used to create the file """ - result = True try: with open(mapfile, 'r') as file_input: @@ -679,7 +639,6 @@ class MemapParser(object): def main(): """Entry Point""" - version = '0.4.0' # Parser handling From 527c0a255478383393318031fb5b25ca229276b9 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 11:19:25 -0600 Subject: [PATCH 4/9] Rework parsing dispatch Parsing dispatch now uses a metaclass as a Trait and a series of classes that implement the trait for dispatching parsing. This structure gives each parser it's own namespace, avoiding tacking on suffixes to each attribute indicating which parser uses it. --- tools/memap.py | 330 ++++++++++++++++++--------------- tools/test/memap/parse_test.py | 16 +- 2 files changed, 193 insertions(+), 153 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index f5574b17e0..c582f5e218 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -2,6 +2,7 @@ """Memory Map File Analyser for ARM mbed""" +import abc import sys import os import re @@ -14,67 +15,19 @@ from prettytable import PrettyTable from utils import argparse_filestring_type, \ argparse_lowercase_hyphen_type, argparse_uppercase_type -RE_ARMCC = re.compile( - r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$') -RE_IAR = re.compile( - r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s' - r'+0x(\w{8})\s+0x(\w+)\s+(.+)\s.+$') -RE_CMDLINE_FILE_IAR = re.compile(r'^#\s+(.+\.o)') -RE_LIBRARY_IAR = re.compile(r'^(.+\.a)\:.+$') -RE_OBJECT_LIBRARY_IAR = re.compile(r'^\s+(.+\.o)\s.*') - -RE_OBJECT_FILE_GCC = re.compile(r'^(.+\/.+\.o)$') -RE_LIBRARY_OBJECT_GCC = re.compile(r'^.+\/lib(.+\.a)\((.+\.o)\)$') -RE_STD_SECTION_GCC = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') -RE_FILL_SECTION_GCC = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$') - -RE_OBJECT_ARMCC = re.compile(r'(.+\.(l|ar))\((.+\.o)\)') - - -class MemapParser(object): - """An object that represents parsed results, parses the memory map files, - and writes out different file types of memory results - """ - - print_sections = ('.text', '.data', '.bss') - - misc_flash_sections = ('.interrupts', '.flash_config') - - other_sections = ('.interrupts_ram', '.init', '.ARM.extab', +class _Parser(object): + """Internal interface for parsing""" + __metaclass__ = abc.ABCMeta + SECTIONS = ('.text', '.data', '.bss', '.heap', '.stack') + MISC_FLASH_SECTIONS = ('.interrupts', '.flash_config') + OTHER_SECTIONS = ('.interrupts_ram', '.init', '.ARM.extab', '.ARM.exidx', '.ARM.attributes', '.eh_frame', '.init_array', '.fini_array', '.jcr', '.stab', '.stabstr', '.ARM.exidx', '.ARM') - # sections to print info (generic for all toolchains) - sections = ('.text', '.data', '.bss', '.heap', '.stack') - def __init__(self): - # list of all modules and their sections - # full list - doesn't change with depth self.modules = dict() - # short version with specific depth - self.short_modules = dict() - - # sections must be defined in this order to take irrelevant out - self.all_sections = self.sections + self.other_sections + \ - self.misc_flash_sections + ('unknown', 'OUTPUT') - - # Memory report (sections + summary) - self.mem_report = [] - - # Memory summary - self.mem_summary = dict() - - # Totals of ".text", ".data" and ".bss" - self.subtotal = dict() - - # Flash no associated with a module - self.misc_flash_mem = 0 - - # Modules passed to the linker on the command line - # this is a dict because modules are looked up by their basename - self.cmd_modules = {} def module_add(self, object_name, size, section): """ Adds a module or section to the list @@ -109,14 +62,38 @@ class MemapParser(object): self.modules[new_object] = self.modules[old_object] del self.modules[old_object] - def check_new_section_gcc(self, line): - """ Check whether a new section in a map file has been detected (only - applies to gcc) + @abc.abstractmethod + def parse_mapfile(self, mapfile): + """Parse a given file object pointing to a map file + + Positional arguments: + mapfile - an open file object that reads a map file + + return value - a dict mapping from object names to section dicts, + where a section dict maps from sections to sizes + """ + raise NotImplemented + + +class _GccParser(_Parser): + RE_OBJECT_FILE = re.compile(r'^(.+\/.+\.o)$') + RE_LIBRARY_OBJECT = re.compile(r'^.+\/lib(.+\.a)\((.+\.o)\)$') + RE_STD_SECTION = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') + RE_FILL_SECTION = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$') + + ALL_SECTIONS = _Parser.SECTIONS + _Parser.OTHER_SECTIONS + \ + _Parser.MISC_FLASH_SECTIONS + ('unknown', 'OUTPUT') + + def check_new_section(self, line): + """ Check whether a new section in a map file has been detected Positional arguments: line - the line to check for a new section + + return value - A section name, if a new section was found, False + otherwise """ - for i in self.all_sections: + for i in self.ALL_SECTIONS: if line.startswith(i): # should name of the section (assuming it's a known one) return i @@ -127,13 +104,15 @@ class MemapParser(object): return False # everything else, means no change in section - def parse_object_name_gcc(self, line): + def parse_object_name(self, line): """ Parse a path to object file Positional arguments: - txt - the path to parse the object and module name from + line - the path to parse the object and module name from + + return value - an object file name """ - test_re_mbed_os_name = re.match(RE_OBJECT_FILE_GCC, line) + test_re_mbed_os_name = re.match(self.RE_OBJECT_FILE, line) if test_re_mbed_os_name: object_name = test_re_mbed_os_name.group(1) @@ -144,7 +123,7 @@ class MemapParser(object): return object_name else: - test_re_obj_name = re.match(RE_LIBRARY_OBJECT_GCC, line) + test_re_obj_name = re.match(self.RE_LIBRARY_OBJECT, line) if test_re_obj_name: object_name = os.path.join(test_re_obj_name.group(1), @@ -154,32 +133,32 @@ class MemapParser(object): print "Unknown object name found in GCC map file: %s" % line return '[misc]' - def parse_section_gcc(self, line): + def parse_section(self, line): """ Parse data from a section of gcc map file examples: 0x00004308 0x7c ./BUILD/K64F/GCC_ARM/mbed-os/hal/targets/hal/TARGET_Freescale/TARGET_KPSDK_MCUS/spi_api.o - .text 0x00000608 0x198 ./BUILD/K64F/GCC_ARM/mbed-os/core/mbed-rtos/rtx/TARGET_CORTEX_M/TARGET_RTOS_M4_M7/TOOLCHAIN_GCC/HAL_CM4.o + .text 0x00000608 0x198 ./BUILD/K64F/GCC_ARM/mbed-os/core/mbed-rtos/rtx/TARGET_CORTEX_M/TARGET_RTOS_M4_M7/TOOLCHAIN/HAL_CM4.o Positional arguments: line - the line to parse a section from """ - is_fill = re.match(RE_FILL_SECTION_GCC, line) + is_fill = re.match(self.RE_FILL_SECTION, line) if is_fill: o_name = '[fill]' o_size = int(is_fill.group(2), 16) return [o_name, o_size] - is_section = re.match(RE_STD_SECTION_GCC, line) + is_section = re.match(self.RE_STD_SECTION, line) if is_section: o_size = int(is_section.group(2), 16) if o_size: - o_name = self.parse_object_name_gcc(is_section.group(3)) + o_name = self.parse_object_name(is_section.group(3)) return [o_name, o_size] return ["", 0] - def parse_map_file_gcc(self, file_desc): + def parse_mapfile(self, file_desc): """ Main logic to decode gcc map files Positional arguments: @@ -194,14 +173,14 @@ class MemapParser(object): break for line in infile: - next_section = self.check_new_section_gcc(line) + next_section = self.check_new_section(line) if next_section == "OUTPUT": break elif next_section: current_section = next_section - object_name, object_size = self.parse_section_gcc(line) + object_name, object_size = self.parse_section(line) self.module_add(object_name, object_size, current_section) common_prefix = os.path.dirname(os.path.commonprefix([ @@ -214,9 +193,15 @@ class MemapParser(object): new_modules[os.path.relpath(name, common_prefix)] = stats else: new_modules[name] = stats - self.modules = new_modules + return new_modules - def parse_object_name_armcc(self, line): + +class _ArmccParser(_Parser): + RE = re.compile( + r'^\s+0x(\w{8})\s+0x(\w{8})\s+(\w+)\s+(\w+)\s+(\d+)\s+[*]?.+\s+(.+)$') + RE_OBJECT = re.compile(r'(.+\.(l|ar))\((.+\.o)\)') + + def parse_object_name(self, line): """ Parse object file Positional arguments: @@ -226,7 +211,7 @@ class MemapParser(object): return line else: - is_obj = re.match(RE_OBJECT_ARMCC, line) + is_obj = re.match(self.RE_OBJECT, line) if is_obj: object_name = os.path.join(os.path.basename(is_obj.group(1)), is_obj.group(3)) @@ -235,47 +220,91 @@ class MemapParser(object): print "Malformed input found when parsing ARMCC map: %s" % line return '[misc]' - def parse_section_armcc(self, line): + def parse_section(self, line): """ Parse data from an armcc map file Examples of armcc map file: Base_Addr Size Type Attr Idx E Section Name Object - 0x00000000 0x00000400 Data RO 11222 RESET startup_MK64F12.o + 0x00000000 0x00000400 Data RO 11222 self.RESET startup_MK64F12.o 0x00000410 0x00000008 Code RO 49364 * !!!main c_w.l(__main.o) Positional arguments: line - the line to parse the section data from """ - test_re_armcc = re.match(RE_ARMCC, line) + test_re = re.match(self.RE, line) - if test_re_armcc: - size = int(test_re_armcc.group(2), 16) + if test_re: + size = int(test_re.group(2), 16) - if test_re_armcc.group(4) == 'RO': + if test_re.group(4) == 'RO': section = '.text' else: - if test_re_armcc.group(3) == 'Data': + if test_re.group(3) == 'Data': section = '.data' - elif test_re_armcc.group(3) == 'Zero': + elif test_re.group(3) == 'Zero': section = '.bss' - elif test_re_armcc.group(3) == 'Code': + elif test_re.group(3) == 'Code': section = '.text' else: print "Malformed input found when parsing armcc map: %s, %r" %\ - (line, test_re_armcc.groups()) + (line, test_re.groups()) return ["", 0, ""] # check name of object or library - object_name = self.parse_object_name_armcc( - test_re_armcc.group(6)) + object_name = self.parse_object_name( + test_re.group(6)) return [object_name, size, section] else: return ["", 0, ""] - def parse_object_name_iar(self, object_name): + def parse_mapfile(self, file_desc): + """ Main logic to decode armc5 map files + + Positional arguments: + file_desc - a file like object to parse as an armc5 map file + """ + with file_desc as infile: + # Search area to parse + for line in infile: + if line.startswith(' Base Addr Size'): + break + + # Start decoding the map file + for line in infile: + self.module_add(*self.parse_section(line)) + + common_prefix = os.path.dirname(os.path.commonprefix([ + o for o in self.modules.keys() if (o.endswith(".o") and o != "anon$$obj.o" and not o.startswith("[lib]"))])) + new_modules = {} + for name, stats in self.modules.items(): + if name == "anon$$obj.o" or name.startswith("[lib]"): + new_modules[name] = stats + elif name.endswith(".o"): + new_modules[os.path.relpath(name, common_prefix)] = stats + else: + new_modules[name] = stats + return new_modules + + +class _IarParser(_Parser): + RE = re.compile( + r'^\s+(.+)\s+(zero|const|ro code|inited|uninit)\s' + r'+0x(\w{8})\s+0x(\w+)\s+(.+)\s.+$') + + RE_CMDLINE_FILE = re.compile(r'^#\s+(.+\.o)') + RE_LIBRARY = re.compile(r'^(.+\.a)\:.+$') + RE_OBJECT_LIBRARY = re.compile(r'^\s+(.+\.o)\s.*') + + def __init__(self): + _Parser.__init__(self) + # Modules passed to the linker on the command line + # this is a dict because modules are looked up by their basename + self.cmd_modules = {} + + def parse_object_name(self, object_name): """ Parse object file Positional arguments: @@ -289,7 +318,7 @@ class MemapParser(object): else: return '[misc]' - def parse_section_iar(self, line): + def parse_section(self, line): """ Parse data from an IAR map file Examples of IAR map file: @@ -306,76 +335,48 @@ class MemapParser(object): Positional_arguments: line - the line to parse section data from """ - test_re_iar = re.match(RE_IAR, line) - if test_re_iar: - if (test_re_iar.group(2) == 'const' or - test_re_iar.group(2) == 'ro code'): + test_re = re.match(self.RE, line) + if test_re: + if (test_re.group(2) == 'const' or + test_re.group(2) == 'ro code'): section = '.text' - elif (test_re_iar.group(2) == 'zero' or - test_re_iar.group(2) == 'uninit'): - if test_re_iar.group(1)[0:4] == 'HEAP': + elif (test_re.group(2) == 'zero' or + test_re.group(2) == 'uninit'): + if test_re.group(1)[0:4] == 'HEAP': section = '.heap' - elif test_re_iar.group(1)[0:6] == 'CSTACK': + elif test_re.group(1)[0:6] == 'CSTACK': section = '.stack' else: section = '.bss' # default section - elif test_re_iar.group(2) == 'inited': + elif test_re.group(2) == 'inited': section = '.data' else: print "Malformed input found when parsing IAR map: %s" % line return ["", 0, ""] # lookup object in dictionary and return module name - object_name = self.parse_object_name_iar(test_re_iar.group(5)) + object_name = self.parse_object_name(test_re.group(5)) - size = int(test_re_iar.group(4), 16) + size = int(test_re.group(4), 16) return [object_name, size, section] else: return ["", 0, ""] - def parse_map_file_armcc(self, file_desc): - """ Main logic to decode armc5 map files - - Positional arguments: - file_desc - a file like object to parse as an armc5 map file - """ - with file_desc as infile: - # Search area to parse - for line in infile: - if line.startswith(' Base Addr Size'): - break - - # Start decoding the map file - for line in infile: - self.module_add(*self.parse_section_armcc(line)) - - common_prefix = os.path.dirname(os.path.commonprefix([ - o for o in self.modules.keys() if (o.endswith(".o") and o != "anon$$obj.o" and not o.startswith("[lib]"))])) - new_modules = {} - for name, stats in self.modules.items(): - if name == "anon$$obj.o" or name.startswith("[lib]"): - new_modules[name] = stats - elif name.endswith(".o"): - new_modules[os.path.relpath(name, common_prefix)] = stats - else: - new_modules[name] = stats - self.modules = new_modules - - def check_new_library_iar(self, line): + def check_new_library(self, line): """ Searches for libraries and returns name. Example: m7M_tls.a: [43] """ - test_address_line = re.match(RE_LIBRARY_IAR, line) + test_address_line = re.match(self.RE_LIBRARY, line) if test_address_line: return test_address_line.group(1) else: return "" - def check_new_object_lib_iar(self, line): + def check_new_object_lib(self, line): """ Searches for objects within a library section and returns name. Example: rt7M_tl.a: [44] @@ -386,13 +387,13 @@ class MemapParser(object): I64DivZer.o 2 """ - test_address_line = re.match(RE_OBJECT_LIBRARY_IAR, line) + test_address_line = re.match(self.RE_OBJECT_LIBRARY, line) if test_address_line: return test_address_line.group(1) else: return "" - def parse_iar_command_line(self, lines): + def parse_command_line(self, lines): """Parse the files passed on the command line to the iar linker Positional arguments: @@ -410,37 +411,72 @@ class MemapParser(object): self.cmd_modules = {s: os.path.relpath(f, common_prefix) for s, f in self.cmd_modules.items()} - def parse_map_file_iar(self, file_desc): + def parse_mapfile(self, file_desc): """ Main logic to decode IAR map files Positional arguments: file_desc - a file like object to parse as an IAR map file """ with file_desc as infile: - self.parse_iar_command_line(infile) + self.parse_command_line(infile) for line in infile: if line.startswith(' Section '): break for line in infile: - self.module_add(*self.parse_section_iar(line)) + self.module_add(*self.parse_section(line)) if line.startswith('*** MODULE SUMMARY'): # finish section break current_library = "" for line in infile: - library = self.check_new_library_iar(line) + library = self.check_new_library(line) if library: current_library = library - object_name = self.check_new_object_lib_iar(line) + object_name = self.check_new_object_lib(line) if object_name and current_library: temp = os.path.join('[lib]', current_library, object_name) self.module_replace(object_name, temp) + return self.modules + + +class MemapParser(object): + """An object that represents parsed results, parses the memory map files, + and writes out different file types of memory results + """ + + print_sections = ('.text', '.data', '.bss') + + + # sections to print info (generic for all toolchains) + sections = _Parser.SECTIONS + misc_flash_sections = _Parser.MISC_FLASH_SECTIONS + other_sections = _Parser.OTHER_SECTIONS + + def __init__(self): + # list of all modules and their sections + # full list - doesn't change with depth + self.modules = dict() + # short version with specific depth + self.short_modules = dict() + + + # Memory report (sections + summary) + self.mem_report = [] + + # Memory summary + self.mem_summary = dict() + + # Totals of ".text", ".data" and ".bss" + self.subtotal = dict() + + # Flash no associated with a module + self.misc_flash_mem = 0 def reduce_depth(self, depth): """ @@ -620,22 +656,22 @@ class MemapParser(object): mapfile - the file name of the memory map file toolchain - the toolchain used to create the file """ - result = True + if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"): + parser = _ArmccParser() + elif toolchain == "GCC_ARM" or toolchain == "GCC_CR": + parser = _GccParser() + elif toolchain == "IAR": + parser = _IarParser() + else: + return False try: with open(mapfile, 'r') as file_input: - if toolchain in ("ARM", "ARM_STD", "ARM_MICRO", "ARMC6"): - self.parse_map_file_armcc(file_input) - elif toolchain == "GCC_ARM" or toolchain == "GCC_CR": - self.parse_map_file_gcc(file_input) - elif toolchain == "IAR": - self.parse_map_file_iar(file_input) - else: - result = False + self.modules = parser.parse_mapfile(file_input) + return True except IOError as error: print "I/O error({0}): {1}".format(error.errno, error.strerror) - result = False - return result + return False def main(): """Entry Point""" diff --git a/tools/test/memap/parse_test.py b/tools/test/memap/parse_test.py index 51eb9cae05..6047749029 100644 --- a/tools/test/memap/parse_test.py +++ b/tools/test/memap/parse_test.py @@ -5,7 +5,7 @@ import json import pytest -from tools.memap import MemapParser +from tools.memap import MemapParser, _ArmccParser from copy import deepcopy @@ -19,7 +19,9 @@ PARSED_ARM_DATA = { def test_parse_armcc(): memap = MemapParser() - memap.parse_map_file_armcc(open(join(dirname(__file__), "arm.map"))) + memap.parse(join(dirname(__file__), "arm.map"), "ARM") + assert memap.modules == PARSED_ARM_DATA + memap.parse(join(dirname(__file__), "arm.map"), "UARM") assert memap.modules == PARSED_ARM_DATA PARSED_IAR_GCC_DATA = { @@ -32,17 +34,19 @@ PARSED_IAR_GCC_DATA = { def test_parse_iar(): memap = MemapParser() - memap.parse_map_file_iar(open(join(dirname(__file__), "iar.map"))) + memap.parse(join(dirname(__file__), "iar.map"), "IAR") assert memap.modules == PARSED_IAR_GCC_DATA def test_parse_gcc(): memap = MemapParser() - memap.parse_map_file_gcc(open(join(dirname(__file__), "gcc.map"))) + memap.parse(join(dirname(__file__), "gcc.map"), "GCC_ARM") + assert memap.modules == PARSED_IAR_GCC_DATA + memap.parse(join(dirname(__file__), "gcc.map"), "GCC_CR") assert memap.modules == PARSED_IAR_GCC_DATA def test_add_empty_module(): - memap = MemapParser() + memap = _ArmccParser() old_modules = deepcopy(memap.modules) memap.module_add("", 8, ".data") assert(old_modules == memap.modules) @@ -52,7 +56,7 @@ def test_add_empty_module(): assert(old_modules == memap.modules) def test_add_full_module(): - memap = MemapParser() + memap = _ArmccParser() old_modules = deepcopy(memap.modules) memap.module_add("main.o", 8, ".data") assert(old_modules != memap.modules) From 0c08fabbae5c8e7d414e9bd31eee5db525382eba Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 13:37:25 -0600 Subject: [PATCH 5/9] Correct Gcc builtin module naming --- tools/memap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index c582f5e218..d5ad412aa5 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -77,7 +77,7 @@ class _Parser(object): class _GccParser(_Parser): RE_OBJECT_FILE = re.compile(r'^(.+\/.+\.o)$') - RE_LIBRARY_OBJECT = re.compile(r'^.+\/lib(.+\.a)\((.+\.o)\)$') + RE_LIBRARY_OBJECT = re.compile(r'^.+' + os.sep + r'lib((.+\.a)\((.+\.o)\))$') RE_STD_SECTION = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') RE_FILL_SECTION = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$') @@ -119,7 +119,7 @@ class _GccParser(_Parser): # corner case: certain objects are provided by the GCC toolchain if 'arm-none-eabi' in line: - return os.path.join('[lib]', 'misc', object_name) + return os.path.join('[lib]', 'misc', os.path.basename(object_name)) return object_name else: From 6a8be65904d30052071bc797ede081226079ded1 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 14:19:00 -0600 Subject: [PATCH 6/9] Use non-scoped imports in memap --- tools/memap.py | 62 ++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index d5ad412aa5..b5c1b68814 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -2,13 +2,14 @@ """Memory Map File Analyser for ARM mbed""" -import abc -import sys -import os +from abc import abstractmethod, ABCMeta +from sys import stdout, exit, argv +from os import sep +from os.path import basename, dirname, join, relpath, commonprefix import re import csv import json -import argparse +from argparse import ArgumentParser from copy import deepcopy from prettytable import PrettyTable @@ -18,7 +19,7 @@ from utils import argparse_filestring_type, \ class _Parser(object): """Internal interface for parsing""" - __metaclass__ = abc.ABCMeta + __metaclass__ = ABCMeta SECTIONS = ('.text', '.data', '.bss', '.heap', '.stack') MISC_FLASH_SECTIONS = ('.interrupts', '.flash_config') OTHER_SECTIONS = ('.interrupts_ram', '.init', '.ARM.extab', @@ -45,7 +46,7 @@ class _Parser(object): self.modules[object_name][section] += size return - obj_split = os.sep + os.path.basename(object_name) + obj_split = sep + basename(object_name) for module_path, contents in self.modules.items(): if module_path.endswith(obj_split) or module_path == object_name: contents.setdefault(section, 0) @@ -62,7 +63,7 @@ class _Parser(object): self.modules[new_object] = self.modules[old_object] del self.modules[old_object] - @abc.abstractmethod + @abstractmethod def parse_mapfile(self, mapfile): """Parse a given file object pointing to a map file @@ -77,7 +78,7 @@ class _Parser(object): class _GccParser(_Parser): RE_OBJECT_FILE = re.compile(r'^(.+\/.+\.o)$') - RE_LIBRARY_OBJECT = re.compile(r'^.+' + os.sep + r'lib((.+\.a)\((.+\.o)\))$') + RE_LIBRARY_OBJECT = re.compile(r'^.+' + sep + r'lib((.+\.a)\((.+\.o)\))$') RE_STD_SECTION = re.compile(r'^\s+.*0x(\w{8,16})\s+0x(\w+)\s(.+)$') RE_FILL_SECTION = re.compile(r'^\s*\*fill\*\s+0x(\w{8,16})\s+0x(\w+).*$') @@ -119,16 +120,15 @@ class _GccParser(_Parser): # corner case: certain objects are provided by the GCC toolchain if 'arm-none-eabi' in line: - return os.path.join('[lib]', 'misc', os.path.basename(object_name)) + return join('[lib]', 'misc', basename(object_name)) return object_name else: test_re_obj_name = re.match(self.RE_LIBRARY_OBJECT, line) if test_re_obj_name: - object_name = os.path.join(test_re_obj_name.group(1), - test_re_obj_name.group(2)) - return os.path.join('[lib]', object_name) + return join('[lib]', test_re_obj_name.group(2), + test_re_obj_name.group(3)) else: print "Unknown object name found in GCC map file: %s" % line return '[misc]' @@ -183,14 +183,14 @@ class _GccParser(_Parser): object_name, object_size = self.parse_section(line) self.module_add(object_name, object_size, current_section) - common_prefix = os.path.dirname(os.path.commonprefix([ + common_prefix = dirname(commonprefix([ o for o in self.modules.keys() if (o.endswith(".o") and not o.startswith("[lib]"))])) new_modules = {} for name, stats in self.modules.items(): if name.startswith("[lib]"): new_modules[name] = stats elif name.endswith(".o"): - new_modules[os.path.relpath(name, common_prefix)] = stats + new_modules[relpath(name, common_prefix)] = stats else: new_modules[name] = stats return new_modules @@ -213,9 +213,7 @@ class _ArmccParser(_Parser): else: is_obj = re.match(self.RE_OBJECT, line) if is_obj: - object_name = os.path.join(os.path.basename(is_obj.group(1)), - is_obj.group(3)) - return os.path.join('[lib]', object_name) + return join('[lib]', basename(is_obj.group(1)), is_obj.group(3)) else: print "Malformed input found when parsing ARMCC map: %s" % line return '[misc]' @@ -276,14 +274,14 @@ class _ArmccParser(_Parser): for line in infile: self.module_add(*self.parse_section(line)) - common_prefix = os.path.dirname(os.path.commonprefix([ + common_prefix = dirname(commonprefix([ o for o in self.modules.keys() if (o.endswith(".o") and o != "anon$$obj.o" and not o.startswith("[lib]"))])) new_modules = {} for name, stats in self.modules.items(): if name == "anon$$obj.o" or name.startswith("[lib]"): new_modules[name] = stats elif name.endswith(".o"): - new_modules[os.path.relpath(name, common_prefix)] = stats + new_modules[relpath(name, common_prefix)] = stats else: new_modules[name] = stats return new_modules @@ -405,10 +403,10 @@ class _IarParser(_Parser): for arg in line.split(" "): arg = arg.rstrip(" \n") if (not arg.startswith("-")) and arg.endswith(".o"): - self.cmd_modules[os.path.basename(arg)] = arg + self.cmd_modules[basename(arg)] = arg - common_prefix = os.path.dirname(os.path.commonprefix(self.cmd_modules.values())) - self.cmd_modules = {s: os.path.relpath(f, common_prefix) + common_prefix = dirname(commonprefix(self.cmd_modules.values())) + self.cmd_modules = {s: relpath(f, common_prefix) for s, f in self.cmd_modules.items()} def parse_mapfile(self, file_desc): @@ -440,7 +438,7 @@ class _IarParser(_Parser): object_name = self.check_new_object_lib(line) if object_name and current_library: - temp = os.path.join('[lib]', current_library, object_name) + temp = join('[lib]', current_library, object_name) self.module_replace(object_name, temp) return self.modules @@ -497,10 +495,10 @@ class MemapParser(object): else: self.short_modules = dict() for module_name, v in self.modules.items(): - split_name = module_name.split(os.sep) + split_name = module_name.split(sep) if split_name[0] == '': split_name = split_name[1:] - new_name = os.path.join(*split_name[:depth]) + new_name = join(*split_name[:depth]) self.short_modules.setdefault(new_name, {}) for section_idx, value in v.items(): self.short_modules[new_name].setdefault(section_idx, 0) @@ -526,7 +524,7 @@ class MemapParser(object): if file_output: file_desc = open(file_output, 'wb') else: - file_desc = sys.stdout + file_desc = stdout except IOError as error: print "I/O error({0}): {1}".format(error.errno, error.strerror) return False @@ -536,7 +534,7 @@ class MemapParser(object): 'table': self.generate_table}[export_format] output = to_call(file_desc) - if file_desc is not sys.stdout: + if file_desc is not stdout: file_desc.close() return output @@ -678,7 +676,7 @@ def main(): version = '0.4.0' # Parser handling - parser = argparse.ArgumentParser( + parser = ArgumentParser( description="Memory Map File Analyser for ARM mbed\nversion %s" % version) @@ -709,9 +707,9 @@ def main(): parser.add_argument('-v', '--version', action='version', version=version) # Parse/run command - if len(sys.argv) <= 1: + if len(argv) <= 1: parser.print_help() - sys.exit(1) + exit(1) args = parser.parse_args() @@ -721,7 +719,7 @@ def main(): # Parse and decode a map file if args.file and args.toolchain: if memap.parse(args.file, args.toolchain) is False: - sys.exit(0) + exit(0) if args.depth is None: depth = 2 # default depth level @@ -739,7 +737,7 @@ def main(): if args.export == 'table' and returned_string: print returned_string - sys.exit(0) + exit(0) if __name__ == "__main__": main() From bfd39bdccb5f9daf80415e4230f12100896f8c5f Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 14:34:10 -0600 Subject: [PATCH 7/9] Test memap IAR for multiple arguments per line --- tools/test/memap/iar.map | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tools/test/memap/iar.map b/tools/test/memap/iar.map index 0fc3aae55d..ba3112a12f 100644 --- a/tools/test/memap/iar.map +++ b/tools/test/memap/iar.map @@ -10,12 +10,9 @@ # Command line = # -f # /common/path/.link_files.txt -# (-o -# --map=/common/path/project.map -# /common/path/project.elf -# /common/path/main.o -# /common/path/startup/startup.o -# /common/path/irqs/irqs.o +# (-o /common/path/project.elf +# --map=/common/path/project.map /common/path/main.o +# /common/path/startup/startup.o /common/path/irqs/irqs.o # /common/path/data/data.o # ############################################################################### From 3db03a77d5ba6147a7a9bed12ef85114fa93c295 Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 14:35:17 -0600 Subject: [PATCH 8/9] Test gcc memap parser with compiler provided .o's --- tools/test/memap/gcc.map | 1 + tools/test/memap/parse_test.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/tools/test/memap/gcc.map b/tools/test/memap/gcc.map index 58ff289e97..01e16cb868 100644 --- a/tools/test/memap/gcc.map +++ b/tools/test/memap/gcc.map @@ -8,6 +8,7 @@ Linker script and memory map 0x000000000001b200 0xc0 /common/path/startup/startup.o 0x000000000001b200 startup() 0x0000000000024020 0x8 /usr/lib/gcc/arm-none-eabi/7.1.0/../../../../arm-none-eabi/lib/armv6-m/libd16M_tlf.a(__main.o) + 0x0000000000024020 0x8 /usr/lib/gcc/arm-none-eabi/7.1.0/../../../../arm-none-eabi/lib/armv6-m/foo.o .data 0x0000000020002ef8 0xac8 load address 0x000000000002ca38 0x0000000020002ef8 __data_start__ = . diff --git a/tools/test/memap/parse_test.py b/tools/test/memap/parse_test.py index 6047749029..210a6fa9e2 100644 --- a/tools/test/memap/parse_test.py +++ b/tools/test/memap/parse_test.py @@ -24,7 +24,7 @@ def test_parse_armcc(): memap.parse(join(dirname(__file__), "arm.map"), "UARM") assert memap.modules == PARSED_ARM_DATA -PARSED_IAR_GCC_DATA = { +PARSED_IAR_DATA = { "startup/startup.o": {".text": 0xc0}, "[lib]/d16M_tlf.a/__main.o": {".text": 8}, "irqs/irqs.o": {".text": 0x98}, @@ -35,14 +35,23 @@ PARSED_IAR_GCC_DATA = { def test_parse_iar(): memap = MemapParser() memap.parse(join(dirname(__file__), "iar.map"), "IAR") - assert memap.modules == PARSED_IAR_GCC_DATA + assert memap.modules == PARSED_IAR_DATA + +PARSED_GCC_DATA = { + "startup/startup.o": {".text": 0xc0}, + "[lib]/d16M_tlf.a/__main.o": {".text": 8}, + "[lib]/misc/foo.o": {".text": 8}, + "irqs/irqs.o": {".text": 0x98}, + "data/data.o": {".data": 0x18, ".bss": 0x198}, + "main.o": {".text": 0x36}, +} def test_parse_gcc(): memap = MemapParser() memap.parse(join(dirname(__file__), "gcc.map"), "GCC_ARM") - assert memap.modules == PARSED_IAR_GCC_DATA + assert memap.modules == PARSED_GCC_DATA memap.parse(join(dirname(__file__), "gcc.map"), "GCC_CR") - assert memap.modules == PARSED_IAR_GCC_DATA + assert memap.modules == PARSED_GCC_DATA def test_add_empty_module(): From d94776f9098ed6f9da47e84536a76d4e4af6368f Mon Sep 17 00:00:00 2001 From: Jimmy Brisson Date: Tue, 2 Jan 2018 14:36:10 -0600 Subject: [PATCH 9/9] Assert that reduce depth works and parameterize over path sep --- tools/test/memap/memap_test.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/tools/test/memap/memap_test.py b/tools/test/memap/memap_test.py index a0c84dda51..77259a721b 100644 --- a/tools/test/memap/memap_test.py +++ b/tools/test/memap/memap_test.py @@ -20,6 +20,7 @@ import json import pytest +import tools.memap from tools.memap import MemapParser from copy import deepcopy @@ -137,7 +138,7 @@ def memap_parser(): return memap_parser -def generate_test_helper(memap_parser, format, depth, file_output=None): +def generate_test_helper(memap_parser, format, depth, sep, file_output=None): """ Helper that tests that the member variables "modules" is unchanged after calling "generate_output" @@ -147,17 +148,21 @@ def generate_test_helper(memap_parser, format, depth, file_output=None): :param format: the file type to output :param file_output: the file to output to """ - old_modules = deepcopy(memap_parser.modules) + tools.memap.sep = sep memap_parser.generate_output(format, depth, file_output=file_output) assert memap_parser.modules == old_modules,\ "generate_output modified the 'modules' property" + for file_name in memap_parser.short_modules: + assert(len(file_name.split(tools.memap.sep)) <= depth) + @pytest.mark.parametrize("depth", [1, 2, 20]) -def test_report_computed(memap_parser, depth): +@pytest.mark.parametrize("sep", ["\\", "/"]) +def test_report_computed(memap_parser, depth, sep): """ Test that a report and summary are computed @@ -165,7 +170,7 @@ def test_report_computed(memap_parser, depth): :param depth: the detail of the output """ - memap_parser.generate_output('table', depth) + memap_parser.generate_output('table', depth, sep) # Report is created after generating output assert memap_parser.mem_summary @@ -173,17 +178,19 @@ def test_report_computed(memap_parser, depth): @pytest.mark.parametrize("depth", [1, 2, 20]) -def test_generate_output_table(memap_parser, depth): +@pytest.mark.parametrize("sep", ["\\", "/"]) +def test_generate_output_table(memap_parser, depth, sep): """ Test that an output of type "table" can be generated correctly :param memap_parser: Mocked parser :param depth: the detail of the output """ - generate_test_helper(memap_parser, 'table', depth) + generate_test_helper(memap_parser, 'table', depth, sep) @pytest.mark.parametrize("depth", [1, 2, 20]) -def test_generate_output_json(memap_parser, tmpdir, depth): +@pytest.mark.parametrize("sep", ["\\", "/"]) +def test_generate_output_json(memap_parser, tmpdir, depth, sep): """ Test that an output of type "json" can be generated correctly :param memap_parser: Mocked parser @@ -191,13 +198,14 @@ def test_generate_output_json(memap_parser, tmpdir, depth): :param depth: the detail of the output """ file_name = str(tmpdir.join('output.json').realpath()) - generate_test_helper(memap_parser, 'json', depth, file_name) + generate_test_helper(memap_parser, 'json', depth, sep, file_name) assert isfile(file_name), "Failed to create json file" json.load(open(file_name)) @pytest.mark.parametrize("depth", [1, 2, 20]) -def test_generate_output_csv_ci(memap_parser, tmpdir, depth): +@pytest.mark.parametrize("sep", ["\\", "/"]) +def test_generate_output_csv_ci(memap_parser, tmpdir, depth, sep): """ Test ensures that an output of type "csv-ci" can be generated correctly @@ -206,5 +214,5 @@ def test_generate_output_csv_ci(memap_parser, tmpdir, depth): :param depth: the detail of the output """ file_name = str(tmpdir.join('output.csv').realpath()) - generate_test_helper(memap_parser, 'csv-ci', depth, file_name) + generate_test_helper(memap_parser, 'csv-ci', depth, sep, file_name) assert isfile(file_name), "Failed to create csv-ci file"