Add summary for test building

When users are building tests with `mbed compile --tests` whey will by default
get memory map breakdown report.
This can be suppresed in the future with command line switch. For now it is
visible each time users build test cases.
List is sorted by project name created with `build_project` API.

Changes:
* `build_project` now returns tuple (this breaks build_api.build_project API!)
* Memmap script got a aggregation function to print summary from small 'JSON'
  partial reports.
* Report is generated by `test_api.build_tests` function only!

Example:
```
+----------------------------------------------------------------------+--------+-----------+-------------+------------+-------+-------+-----------+
| name                                                                 | target | toolchain | total_flash | static_ram | stack |  heap | total_ram |
+----------------------------------------------------------------------+--------+-----------+-------------+------------+-------+-------+-----------+
| features-feature_ipv4-tests-mbedmicro-net-nist_internet_time_service | K64F   | GCC_ARM   |      132136 |      62288 | 32768 | 65536 |    160592 |
| features-feature_ipv4-tests-mbedmicro-net-tcp_client_echo            | K64F   | GCC_ARM   |      125613 |      62448 | 32768 | 65540 |    160756 |
| features-feature_ipv4-tests-mbedmicro-net-tcp_client_hello_world     | K64F   | GCC_ARM   |      125949 |      62448 | 32768 | 65540 |    160756 |
| features-feature_ipv4-tests-mbedmicro-net-udp_echo_client            | K64F   | GCC_ARM   |      123613 |      62276 | 32768 | 65536 |    160580 |
| features-storage-tests-cfstore-add_del                               | K64F   | GCC_ARM   |       96080 |      13052 | 32768 | 65540 |    111360 |
| features-storage-tests-cfstore-close                                 | K64F   | GCC_ARM   |       95520 |      12004 | 32768 | 65540 |    110312 |
| features-storage-tests-cfstore-create                                | K64F   | GCC_ARM   |       99144 |      13036 | 32768 | 65540 |    111344 |
| features-storage-tests-cfstore-example1                              | K64F   | GCC_ARM   |       98592 |      12368 | 32768 | 65536 |    110672 |
| features-storage-tests-cfstore-example2                              | K64F   | GCC_ARM   |       95232 |      12012 | 32768 | 65540 |    110320 |
| features-storage-tests-cfstore-example3                              | K64F   | GCC_ARM   |       95264 |      11856 | 32768 | 65536 |    110160 |
| features-storage-tests-cfstore-example4                              | K64F   | GCC_ARM   |       92632 |      12012 | 32768 | 65540 |    110320 |
| features-storage-tests-cfstore-example5                              | K64F   | GCC_ARM   |       92344 |      11856 | 32768 | 65536 |    110160 |
| features-storage-tests-cfstore-find                                  | K64F   | GCC_ARM   |       96344 |      13028 | 32768 | 65540 |    111336 |
| features-storage-tests-cfstore-find2                                 | K64F   | GCC_ARM   |       93192 |      12004 | 32768 | 65540 |    110312 |
| features-storage-tests-cfstore-flash                                 | K64F   | GCC_ARM   |       97784 |      12532 | 32768 | 65540 |    110840 |
| features-storage-tests-cfstore-flush                                 | K64F   | GCC_ARM   |       96464 |      12012 | 32768 | 65540 |    110320 |
| features-storage-tests-cfstore-flush2                                | K64F   | GCC_ARM   |       95056 |      12004 | 32768 | 65540 |    110312 |
| features-storage-tests-cfstore-init                                  | K64F   | GCC_ARM   |       93120 |      12012 | 32768 | 65540 |    110320 |
| features-storage-tests-cfstore-misc                                  | K64F   | GCC_ARM   |       96808 |      12516 | 32768 | 65540 |    110824 |
| features-storage-tests-cfstore-open                                  | K64F   | GCC_ARM   |       98632 |      12540 | 32768 | 65540 |    110848 |
| features-storage-tests-cfstore-read                                  | K64F   | GCC_ARM   |       94112 |      12540 | 32768 | 65540 |    110848 |
| features-storage-tests-cfstore-write                                 | K64F   | GCC_ARM   |       94488 |      12004 | 32768 | 65540 |    110312 |
| features-storage-tests-flash_journal-basicapi                        | K64F   | GCC_ARM   |      104712 |      21236 | 32768 | 65540 |    119544 |
| frameworks-utest-tests-unit_tests-basic_test                         | K64F   | GCC_ARM   |       71534 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-case_async_validate                | K64F   | GCC_ARM   |       74598 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-case_control_async                 | K64F   | GCC_ARM   |       74630 |      11476 | 32768 | 65540 |    109784 |
| frameworks-utest-tests-unit_tests-case_control_repeat                | K64F   | GCC_ARM   |       72790 |      11452 | 32768 | 65540 |    109760 |
| frameworks-utest-tests-unit_tests-case_selection                     | K64F   | GCC_ARM   |       72302 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-case_setup_failure                 | K64F   | GCC_ARM   |       72630 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-case_teardown_failure              | K64F   | GCC_ARM   |       72790 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-control_type                       | K64F   | GCC_ARM   |       82462 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-minimal_async_scheduler            | K64F   | GCC_ARM   |       72182 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-minimal_scheduler                  | K64F   | GCC_ARM   |       71998 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-test_assertion_failure_test_setup  | K64F   | GCC_ARM   |       71710 |      11460 | 32768 | 65540 |    109768 |
| frameworks-utest-tests-unit_tests-test_setup_case_selection_failure  | K64F   | GCC_ARM   |       71702 |      11468 | 32768 | 65540 |    109776 |
| frameworks-utest-tests-unit_tests-test_setup_failure                 | K64F   | GCC_ARM   |       71710 |      11468 | 32768 | 65540 |    109776 |
| tests-integration-basic                                              | K64F   | GCC_ARM   |       67566 |      10780 | 32768 | 65540 |    109088 |
| tests-integration-threaded_blinky                                    | K64F   | GCC_ARM   |       68326 |      10780 | 32768 | 65540 |    109088 |
| tests-mbed_drivers-c_strings                                         | K64F   | GCC_ARM   |       74438 |      11468 | 32768 | 65540 |    109776 |
| tests-mbed_drivers-callback                                          | K64F   | GCC_ARM   |       88310 |      11972 | 32768 | 65540 |    110280 |
| tests-mbed_drivers-dev_null                                          | K64F   | GCC_ARM   |       90213 |      10784 | 32768 | 65540 |    109092 |
| tests-mbed_drivers-echo                                              | K64F   | GCC_ARM   |       71918 |      11468 | 32768 | 65540 |    109776 |
| tests-mbed_drivers-generic_tests                                     | K64F   | GCC_ARM   |       77624 |      11468 | 32768 | 65540 |    109776 |
| tests-mbed_drivers-rtc                                               | K64F   | GCC_ARM   |       85854 |      11308 | 32768 | 65540 |    109616 |
| tests-mbed_drivers-stl_features                                      | K64F   | GCC_ARM   |       80726 |      11476 | 32768 | 65540 |    109784 |
| tests-mbed_drivers-ticker                                            | K64F   | GCC_ARM   |       70974 |      11308 | 32768 | 65540 |    109616 |
| tests-mbed_drivers-ticker_2                                          | K64F   | GCC_ARM   |       70790 |      11308 | 32768 | 65540 |    109616 |
| tests-mbed_drivers-ticker_3                                          | K64F   | GCC_ARM   |       71038 |      11308 | 32768 | 65540 |    109616 |
| tests-mbed_drivers-timeout                                           | K64F   | GCC_ARM   |       70886 |      11308 | 32768 | 65540 |    109616 |
| tests-mbed_drivers-wait_us                                           | K64F   | GCC_ARM   |       70414 |      11308 | 32768 | 65540 |    109616 |
| tests-mbedmicro-mbed-attributes                                      | K64F   | GCC_ARM   |       71534 |      11460 | 32768 | 65540 |    109768 |
| tests-mbedmicro-mbed-call_before_main                                | K64F   | GCC_ARM   |       73112 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-mbed-cpp                                             | K64F   | GCC_ARM   |       73400 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-mbed-div                                             | K64F   | GCC_ARM   |       73176 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-basic                                      | K64F   | GCC_ARM   |       68390 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-isr                                        | K64F   | GCC_ARM   |       74480 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-mail                                       | K64F   | GCC_ARM   |       74992 |      11300 | 32768 | 65540 |    109608 |
| tests-mbedmicro-rtos-mbed-mutex                                      | K64F   | GCC_ARM   |       74048 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-queue                                      | K64F   | GCC_ARM   |       74912 |      11300 | 32768 | 65540 |    109608 |
| tests-mbedmicro-rtos-mbed-semaphore                                  | K64F   | GCC_ARM   |       74296 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-signals                                    | K64F   | GCC_ARM   |       74328 |      10780 | 32768 | 65540 |    109088 |
| tests-mbedmicro-rtos-mbed-threads                                    | K64F   | GCC_ARM   |       75214 |      11460 | 32768 | 65540 |    109768 |
| tests-mbedmicro-rtos-mbed-timer                                      | K64F   | GCC_ARM   |       68430 |      10780 | 32768 | 65540 |    109088 |
| tests-storage_abstraction-basicapi                                   | K64F   | GCC_ARM   |      107808 |      28908 | 32768 | 65540 |    127216 |
+----------------------------------------------------------------------+--------+-----------+-------------+------------+-------+-------+-----------+
```

Refactored after code review

Refactored parse() function

Polishing

Moved memory usage reporting function call to test.py to group all reporters in one place

Bug-fix: on ARM toolchain we were not fetching statistics from last element of memap result list
pull/2047/head
Przemek Wirkus 2016-06-28 16:34:28 +01:00
parent 458b46c803
commit 12a01f61ca
5 changed files with 186 additions and 109 deletions

View File

@ -176,7 +176,7 @@ def build_project(src_path, build_path, target, toolchain_name,
if report != None: if report != None:
start = time() start = time()
# If project_id is specified, use that over the default name # If project_id is specified, use that over the default name
id_name = project_id.upper() if project_id else name.upper() id_name = project_id.upper() if project_id else name.upper()
description = project_description if project_description else name description = project_description if project_description else name
@ -232,6 +232,7 @@ def build_project(src_path, build_path, target, toolchain_name,
cur_result["elapsed_time"] = end - start cur_result["elapsed_time"] = end - start
cur_result["output"] = toolchain.get_output() cur_result["output"] = toolchain.get_output()
cur_result["result"] = "OK" cur_result["result"] = "OK"
cur_result["memory_usage"] = toolchain.map_outputs
add_result_to_report(report, cur_result) add_result_to_report(report, cur_result)
@ -294,7 +295,7 @@ def build_library(src_paths, build_path, target, toolchain_name,
if report != None: if report != None:
start = time() start = time()
# If project_id is specified, use that over the default name # If project_id is specified, use that over the default name
id_name = project_id.upper() if project_id else name.upper() id_name = project_id.upper() if project_id else name.upper()
description = name description = name
@ -377,7 +378,7 @@ def build_library(src_paths, build_path, target, toolchain_name,
toolchain.copy_files(resources.libraries, build_path, resources=resources) toolchain.copy_files(resources.libraries, build_path, resources=resources)
if resources.linker_script: if resources.linker_script:
toolchain.copy_files(resources.linker_script, build_path, resources=resources) toolchain.copy_files(resources.linker_script, build_path, resources=resources)
if resource.hex_files: if resource.hex_files:
toolchain.copy_files(resources.hex_files, build_path, resources=resources) toolchain.copy_files(resources.hex_files, build_path, resources=resources)
@ -399,12 +400,12 @@ def build_library(src_paths, build_path, target, toolchain_name,
except Exception, e: except Exception, e:
if report != None: if report != None:
end = time() end = time()
if isinstance(e, ToolException): if isinstance(e, ToolException):
cur_result["result"] = "FAIL" cur_result["result"] = "FAIL"
elif isinstance(e, NotSupportedException): elif isinstance(e, NotSupportedException):
cur_result["result"] = "NOT_SUPPORTED" cur_result["result"] = "NOT_SUPPORTED"
cur_result["elapsed_time"] = end - start cur_result["elapsed_time"] = end - start
toolchain_output = toolchain.get_output() toolchain_output = toolchain.get_output()
@ -428,7 +429,7 @@ def build_lib(lib_id, target, toolchain_name, options=None, verbose=False, clean
if not lib.is_supported(target, toolchain_name): if not lib.is_supported(target, toolchain_name):
print 'Library "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain) print 'Library "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain)
return False return False
# We need to combine macros from parameter list with macros from library definition # We need to combine macros from parameter list with macros from library definition
MACROS = lib.macros if lib.macros else [] MACROS = lib.macros if lib.macros else []
if macros: if macros:
@ -441,7 +442,7 @@ def build_lib(lib_id, target, toolchain_name, options=None, verbose=False, clean
dependencies_paths = lib.dependencies dependencies_paths = lib.dependencies
inc_dirs = lib.inc_dirs inc_dirs = lib.inc_dirs
inc_dirs_ext = lib.inc_dirs_ext inc_dirs_ext = lib.inc_dirs_ext
""" src_path: the path of the source directory """ src_path: the path of the source directory
build_path: the path of the build directory build_path: the path of the build directory
target: ['LPC1768', 'LPC11U24', 'LPC2368'] target: ['LPC1768', 'LPC11U24', 'LPC2368']
@ -522,7 +523,7 @@ def build_lib(lib_id, target, toolchain_name, options=None, verbose=False, clean
# Copy Headers # Copy Headers
for resource in resources: for resource in resources:
toolchain.copy_files(resource.headers, build_path, resources=resource) toolchain.copy_files(resource.headers, build_path, resources=resource)
dependencies_include_dir.extend(toolchain.scan_resources(build_path).inc_dirs) dependencies_include_dir.extend(toolchain.scan_resources(build_path).inc_dirs)
# Compile Sources # Compile Sources
@ -716,7 +717,7 @@ def mcu_toolchain_matrix(verbose_html=False, platform_filter=None):
perm_counter += 1 perm_counter += 1
else: else:
text = "-" text = "-"
row.append(text) row.append(text)
pt.add_row(row) pt.add_row(row)
@ -942,6 +943,49 @@ def print_build_results(result_list, build_name):
result += "\n" result += "\n"
return result return result
def print_build_memory_usage_results(report):
""" Generate result table with memory usage values for build results
Agregates (puts together) reports obtained from self.get_memory_summary()
@param report Report generated during build procedure. See
"""
from prettytable import PrettyTable
columns_text = ['name', 'target', 'toolchain']
columns_int = ['static_ram', 'stack', 'heap', 'total_ram', 'total_flash']
table = PrettyTable(columns_text + columns_int)
for col in columns_text:
table.align[col] = 'l'
for col in columns_int:
table.align[col] = 'r'
for target in report:
for toolchain in report[target]:
for name in report[target][toolchain]:
for dlist in report[target][toolchain][name]:
for dlistelem in dlist:
# Get 'memory_usage' record and build table with statistics
record = dlist[dlistelem]
if 'memory_usage' in record and record['memory_usage']:
# Note that summary should be in the last record of
# 'memory_usage' section. This is why we are grabbing
# last "[-1]" record.
row = [
record['description'],
record['target_name'],
record['toolchain_name'],
record['memory_usage'][-1]['summary']['static_ram'],
record['memory_usage'][-1]['summary']['stack'],
record['memory_usage'][-1]['summary']['heap'],
record['memory_usage'][-1]['summary']['total_ram'],
record['memory_usage'][-1]['summary']['total_flash'],
]
table.add_row(row)
result = "Memory map breakdown for built projects (values in Bytes):\n"
result += table.get_string(sortby='name')
return result
def write_build_report(build_report, template_filename, filename): def write_build_report(build_report, template_filename, filename):
build_report_failing = [] build_report_failing = []
build_report_passing = [] build_report_passing = []
@ -963,14 +1007,14 @@ def write_build_report(build_report, template_filename, filename):
def scan_for_source_paths(path, exclude_paths=None): def scan_for_source_paths(path, exclude_paths=None):
ignorepatterns = [] ignorepatterns = []
paths = [] paths = []
def is_ignored(file_path): def is_ignored(file_path):
for pattern in ignorepatterns: for pattern in ignorepatterns:
if fnmatch.fnmatch(file_path, pattern): if fnmatch.fnmatch(file_path, pattern):
return True return True
return False return False
""" os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]]) """ os.walk(top[, topdown=True[, onerror=None[, followlinks=False]]])
When topdown is True, the caller can modify the dirnames list in-place When topdown is True, the caller can modify the dirnames list in-place
(perhaps using del or slice assignment), and walk() will only recurse into (perhaps using del or slice assignment), and walk() will only recurse into

View File

@ -26,9 +26,9 @@ class MemapParser(object):
self.misc_flash_sections = ('.interrupts', '.flash_config') self.misc_flash_sections = ('.interrupts', '.flash_config')
self.other_sections = ('.interrupts_ram', '.init', '.ARM.extab', \ self.other_sections = ('.interrupts_ram', '.init', '.ARM.extab',
'.ARM.exidx', '.ARM.attributes', '.eh_frame', \ '.ARM.exidx', '.ARM.attributes', '.eh_frame',
'.init_array', '.fini_array', '.jcr', '.stab', \ '.init_array', '.fini_array', '.jcr', '.stab',
'.stabstr', '.ARM.exidx', '.ARM') '.stabstr', '.ARM.exidx', '.ARM')
# sections to print info (generic for all toolchains) # sections to print info (generic for all toolchains)
@ -43,6 +43,9 @@ class MemapParser(object):
# list of all object files and mappting to module names # list of all object files and mappting to module names
self.object_to_module = dict() self.object_to_module = dict()
# Memory usage summary structure
self.mem_summary = dict()
def module_add(self, module_name, size, section): def module_add(self, module_name, size, section):
""" """
Adds a module / section to the list Adds a module / section to the list
@ -67,7 +70,7 @@ class MemapParser(object):
return i # should name of the section (assuming it's a known one) return i # should name of the section (assuming it's a known one)
if line.startswith('.'): if line.startswith('.'):
return 'unknown' # all others are clasified are unknown return 'unknown' # all others are classified are unknown
else: else:
return False # everything else, means no change in section return False # everything else, means no change in section
@ -363,11 +366,12 @@ class MemapParser(object):
# Create table # Create table
columns = ['Module'] columns = ['Module']
for i in list(self.print_sections): columns.extend(self.print_sections)
columns.append(i)
table = PrettyTable(columns) table = PrettyTable(columns)
table.align["Module"] = "l" table.align["Module"] = "l"
for col in self.print_sections:
table.align[col] = 'r'
for i in list(self.print_sections): for i in list(self.print_sections):
table.align[i] = 'r' table.align[i] = 'r'
@ -388,8 +392,12 @@ class MemapParser(object):
for k in self.print_sections: for k in self.print_sections:
row.append(self.modules[i][k]) row.append(self.modules[i][k])
json_obj.append({"module":i, "size":{\ json_obj.append({
k:self.modules[i][k] for k in self.print_sections}}) "module":i,
"size":{
k:self.modules[i][k] for k in self.print_sections
}
})
table.add_row(row) table.add_row(row)
@ -399,16 +407,19 @@ class MemapParser(object):
table.add_row(subtotal_row) table.add_row(subtotal_row)
if export_format == 'json': summary = {
json_obj.append({\ 'summary':{
'summary':{\ 'static_ram':(subtotal['.data']+subtotal['.bss']),
'total_static_ram':(subtotal['.data']+subtotal['.bss']),\ 'heap':(subtotal['.heap']),
'allocated_heap':(subtotal['.heap']),\ 'stack':(subtotal['.stack']),
'allocated_stack':(subtotal['.stack']),\ 'total_ram':(subtotal['.data']+subtotal['.bss']+subtotal['.heap']+subtotal['.stack']),
'total_ram':(subtotal['.data']+subtotal['.bss']+subtotal['.heap']+subtotal['.stack']),\ 'total_flash':(subtotal['.text']+subtotal['.data']+misc_flash_mem),
'total_flash':(subtotal['.text']+subtotal['.data']+misc_flash_mem),}}) }
}
file_desc.write(json.dumps(json_obj, indent=4)) if export_format == 'json':
json_to_file = json_obj + [summary]
file_desc.write(json.dumps(json_to_file, indent=4))
file_desc.write('\n') file_desc.write('\n')
elif export_format == 'csv-ci': # CSV format for the CI system elif export_format == 'csv-ci': # CSV format for the CI system
@ -467,33 +478,38 @@ class MemapParser(object):
if file_desc is not sys.stdout: if file_desc is not sys.stdout:
file_desc.close() file_desc.close()
self.mem_summary = json_obj + [summary]
return True return True
def get_memory_summary(self):
"""! Object is available only after self.generate_output('json') is called
@return Return memory summary object
"""
return self.mem_summary
def parse(self, mapfile, toolchain): def parse(self, mapfile, toolchain):
""" """
Parse and decode map file depending on the toolchain Parse and decode map file depending on the toolchain
""" """
result = True
try: try:
file_input = open(mapfile, 'rt') with open(mapfile, 'rt') as file_input:
if toolchain == "ARM" or toolchain == "ARM_STD" or toolchain == "ARM_MICRO":
self.search_objects(os.path.abspath(mapfile), "ARM")
self.parse_map_file_armcc(file_input)
elif toolchain == "GCC_ARM":
self.parse_map_file_gcc(file_input)
elif toolchain == "IAR":
self.search_objects(os.path.abspath(mapfile), toolchain)
self.parse_map_file_iar(file_input)
else:
result = False
except IOError as error: except IOError as error:
print "I/O error({0}): {1}".format(error.errno, error.strerror) print "I/O error({0}): {1}".format(error.errno, error.strerror)
return False result = False
return result
if toolchain == "ARM" or toolchain == "ARM_STD" or toolchain == "ARM_MICRO":
self.search_objects(os.path.abspath(mapfile), "ARM")
self.parse_map_file_armcc(file_input)
elif toolchain == "GCC_ARM":
self.parse_map_file_gcc(file_input)
elif toolchain == "IAR":
self.search_objects(os.path.abspath(mapfile), toolchain)
self.parse_map_file_iar(file_input)
else:
return False
file_input.close()
return True
def main(): def main():

View File

@ -29,6 +29,7 @@ sys.path.insert(0, ROOT)
from tools.test_api import test_path_to_name, find_tests, print_tests, build_tests, test_spec_from_test_builds from tools.test_api import test_path_to_name, find_tests, print_tests, build_tests, test_spec_from_test_builds
from tools.options import get_default_options_parser from tools.options import get_default_options_parser
from tools.build_api import build_project, build_library from tools.build_api import build_project, build_library
from tools.build_api import print_build_memory_usage_results
from tools.targets import TARGET_MAP from tools.targets import TARGET_MAP
from tools.utils import mkdir, ToolException, NotSupportedException from tools.utils import mkdir, ToolException, NotSupportedException
from tools.test_exporters import ReportExporter, ResultExporterType from tools.test_exporters import ReportExporter, ResultExporterType
@ -37,12 +38,12 @@ if __name__ == '__main__':
try: try:
# Parse Options # Parse Options
parser = get_default_options_parser() parser = get_default_options_parser()
parser.add_option("-D", "", parser.add_option("-D", "",
action="append", action="append",
dest="macros", dest="macros",
help="Add a macro definition") help="Add a macro definition")
parser.add_option("-j", "--jobs", parser.add_option("-j", "--jobs",
type="int", type="int",
dest="jobs", dest="jobs",
@ -60,25 +61,25 @@ if __name__ == '__main__':
parser.add_option("-p", "--paths", dest="paths", parser.add_option("-p", "--paths", dest="paths",
default=None, help="Limit the tests to those within the specified comma separated list of paths") default=None, help="Limit the tests to those within the specified comma separated list of paths")
format_choices = ["list", "json"] format_choices = ["list", "json"]
format_default_choice = "list" format_default_choice = "list"
format_help = "Change the format in which tests are listed. Choices include: %s. Default: %s" % (", ".join(format_choices), format_default_choice) format_help = "Change the format in which tests are listed. Choices include: %s. Default: %s" % (", ".join(format_choices), format_default_choice)
parser.add_option("-f", "--format", type="choice", dest="format", parser.add_option("-f", "--format", type="choice", dest="format",
choices=format_choices, default=format_default_choice, help=format_help) choices=format_choices, default=format_default_choice, help=format_help)
parser.add_option("--continue-on-build-fail", action="store_true", dest="continue_on_build_fail", parser.add_option("--continue-on-build-fail", action="store_true", dest="continue_on_build_fail",
default=None, help="Continue trying to build all tests if a build failure occurs") default=None, help="Continue trying to build all tests if a build failure occurs")
parser.add_option("-n", "--names", dest="names", parser.add_option("-n", "--names", dest="names",
default=None, help="Limit the tests to a comma separated list of names") default=None, help="Limit the tests to a comma separated list of names")
parser.add_option("--test-spec", dest="test_spec", parser.add_option("--test-spec", dest="test_spec",
default=None, help="Destination path for a test spec file that can be used by the Greentea automated test tool") default=None, help="Destination path for a test spec file that can be used by the Greentea automated test tool")
parser.add_option("--build-report-junit", dest="build_report_junit", parser.add_option("--build-report-junit", dest="build_report_junit",
default=None, help="Destination path for a build report in the JUnit xml format") default=None, help="Destination path for a build report in the JUnit xml format")
parser.add_option("-v", "--verbose", parser.add_option("-v", "--verbose",
action="store_true", action="store_true",
dest="verbose", dest="verbose",
@ -87,24 +88,24 @@ if __name__ == '__main__':
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
# Filter tests by path if specified # Filter tests by path if specified
if options.paths: if options.paths:
all_paths = options.paths.split(",") all_paths = options.paths.split(",")
else: else:
all_paths = ["."] all_paths = ["."]
all_tests = {} all_tests = {}
tests = {} tests = {}
# Find all tests in the relevant paths # Find all tests in the relevant paths
for path in all_paths: for path in all_paths:
all_tests.update(find_tests(path)) all_tests.update(find_tests(path))
# Filter tests by name if specified # Filter tests by name if specified
if options.names: if options.names:
all_names = options.names.split(",") all_names = options.names.split(",")
all_names = [x.lower() for x in all_names] all_names = [x.lower() for x in all_names]
for name in all_names: for name in all_names:
if any(fnmatch.fnmatch(testname, name) for testname in all_tests): if any(fnmatch.fnmatch(testname, name) for testname in all_tests):
for testname, test in all_tests.items(): for testname, test in all_tests.items():
@ -124,16 +125,16 @@ if __name__ == '__main__':
if not options.build_dir: if not options.build_dir:
print "[ERROR] You must specify a build path" print "[ERROR] You must specify a build path"
sys.exit(1) sys.exit(1)
base_source_paths = options.source_dir base_source_paths = options.source_dir
# Default base source path is the current directory # Default base source path is the current directory
if not base_source_paths: if not base_source_paths:
base_source_paths = ['.'] base_source_paths = ['.']
target = options.mcu target = options.mcu
build_report = {} build_report = {}
build_properties = {} build_properties = {}
@ -150,7 +151,7 @@ if __name__ == '__main__':
macros=options.macros, macros=options.macros,
verbose=options.verbose, verbose=options.verbose,
archive=False) archive=False)
library_build_success = True library_build_success = True
except ToolException, e: except ToolException, e:
# ToolException output is handled by the build log # ToolException output is handled by the build log
@ -161,7 +162,7 @@ if __name__ == '__main__':
except Exception, e: except Exception, e:
# Some other exception occurred, print the error message # Some other exception occurred, print the error message
print e print e
if not library_build_success: if not library_build_success:
print "Failed to build library" print "Failed to build library"
else: else:
@ -175,32 +176,37 @@ if __name__ == '__main__':
verbose=options.verbose, verbose=options.verbose,
jobs=options.jobs, jobs=options.jobs,
continue_on_build_fail=options.continue_on_build_fail) continue_on_build_fail=options.continue_on_build_fail)
# If a path to a test spec is provided, write it to a file # If a path to a test spec is provided, write it to a file
if options.test_spec: if options.test_spec:
test_spec_data = test_spec_from_test_builds(test_build) test_spec_data = test_spec_from_test_builds(test_build)
# Create the target dir for the test spec if necessary # Create the target dir for the test spec if necessary
# mkdir will not create the dir if it already exists # mkdir will not create the dir if it already exists
test_spec_dir = os.path.dirname(options.test_spec) test_spec_dir = os.path.dirname(options.test_spec)
if test_spec_dir: if test_spec_dir:
mkdir(test_spec_dir) mkdir(test_spec_dir)
try: try:
with open(options.test_spec, 'w') as f: with open(options.test_spec, 'w') as f:
f.write(json.dumps(test_spec_data, indent=2)) f.write(json.dumps(test_spec_data, indent=2))
except IOError, e: except IOError, e:
print "[ERROR] Error writing test spec to file" print "[ERROR] Error writing test spec to file"
print e print e
# If a path to a JUnit build report spec is provided, write it to a file # If a path to a JUnit build report spec is provided, write it to a file
if options.build_report_junit: if options.build_report_junit:
report_exporter = ReportExporter(ResultExporterType.JUNIT, package="build") report_exporter = ReportExporter(ResultExporterType.JUNIT, package="build")
report_exporter.report_to_file(build_report, options.build_report_junit, test_suite_properties=build_properties) report_exporter.report_to_file(build_report, options.build_report_junit, test_suite_properties=build_properties)
# Print memory map summary on screen
if build_report:
print
print print_build_memory_usage_results(build_report)
print_report_exporter = ReportExporter(ResultExporterType.PRINT, package="build") print_report_exporter = ReportExporter(ResultExporterType.PRINT, package="build")
status = print_report_exporter.report(build_report) status = print_report_exporter.report(build_report)
if status: if status:
sys.exit(0) sys.exit(0)
else: else:

View File

@ -46,6 +46,7 @@ from tools.paths import HOST_TESTS
from tools.utils import ToolException from tools.utils import ToolException
from tools.utils import NotSupportedException from tools.utils import NotSupportedException
from tools.utils import construct_enum from tools.utils import construct_enum
from tools.memap import MemapParser
from tools.targets import TARGET_MAP from tools.targets import TARGET_MAP
from tools.test_db import BaseDBAccess from tools.test_db import BaseDBAccess
from tools.build_api import build_project, build_mbed_libs, build_lib from tools.build_api import build_project, build_mbed_libs, build_lib
@ -1970,12 +1971,12 @@ def test_path_to_name(path):
while (tail and tail != "."): while (tail and tail != "."):
name_parts.insert(0, tail) name_parts.insert(0, tail)
head, tail = os.path.split(head) head, tail = os.path.split(head)
return "-".join(name_parts).lower() return "-".join(name_parts).lower()
def find_tests(base_dir): def find_tests(base_dir):
"""Given any directory, walk through the subdirectories and find all tests""" """Given any directory, walk through the subdirectories and find all tests"""
def find_test_in_directory(directory, tests_path): def find_test_in_directory(directory, tests_path):
"""Given a 'TESTS' directory, return a dictionary of test names and test paths. """Given a 'TESTS' directory, return a dictionary of test names and test paths.
The formate of the dictionary is {"test-name": "./path/to/test"}""" The formate of the dictionary is {"test-name": "./path/to/test"}"""
@ -1989,20 +1990,20 @@ def find_tests(base_dir):
"name": test_path_to_name(directory), "name": test_path_to_name(directory),
"path": directory "path": directory
} }
return test return test
tests_path = 'TESTS' tests_path = 'TESTS'
tests = {} tests = {}
dirs = scan_for_source_paths(base_dir) dirs = scan_for_source_paths(base_dir)
for directory in dirs: for directory in dirs:
test = find_test_in_directory(directory, tests_path) test = find_test_in_directory(directory, tests_path)
if test: if test:
tests[test['name']] = test['path'] tests[test['name']] = test['path']
return tests return tests
def print_tests(tests, format="list", sort=True): def print_tests(tests, format="list", sort=True):
"""Given a dictionary of tests (as returned from "find_tests"), print them """Given a dictionary of tests (as returned from "find_tests"), print them
in the specified format""" in the specified format"""
@ -2033,12 +2034,11 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
continue_on_build_fail=False): continue_on_build_fail=False):
"""Given the data structure from 'find_tests' and the typical build parameters, """Given the data structure from 'find_tests' and the typical build parameters,
build all the tests build all the tests
Returns a tuple of the build result (True or False) followed by the test Returns a tuple of the build result (True or False) followed by the test
build data structure""" build data structure"""
execution_directory = "."
execution_directory = "."
base_path = norm_relative_path(build_path, execution_directory) base_path = norm_relative_path(build_path, execution_directory)
target_name = target if isinstance(target, str) else target.name target_name = target if isinstance(target, str) else target.name
@ -2051,9 +2051,10 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
"binary_type": "bootable", "binary_type": "bootable",
"tests": {} "tests": {}
} }
result = True result = True
map_outputs_total = list()
for test_name, test_path in tests.iteritems(): for test_name, test_path in tests.iteritems():
test_build_path = os.path.join(build_path, test_path) test_build_path = os.path.join(build_path, test_path)
src_path = base_source_paths + [test_path] src_path = base_source_paths + [test_path]
@ -2072,21 +2073,21 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
except Exception, e: except Exception, e:
if not isinstance(e, NotSupportedException): if not isinstance(e, NotSupportedException):
result = False result = False
if continue_on_build_fail: if continue_on_build_fail:
continue continue
else: else:
break break
# If a clean build was carried out last time, disable it for the next build. # If a clean build was carried out last time, disable it for the next build.
# Otherwise the previously built test will be deleted. # Otherwise the previously built test will be deleted.
if clean: if clean:
clean = False clean = False
# Normalize the path # Normalize the path
if bin_file: if bin_file:
bin_file = norm_relative_path(bin_file, execution_directory) bin_file = norm_relative_path(bin_file, execution_directory)
test_build['tests'][test_name] = { test_build['tests'][test_name] = {
"binaries": [ "binaries": [
{ {
@ -2094,15 +2095,15 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
} }
] ]
} }
print 'Image: %s'% bin_file print 'Image: %s'% bin_file
test_builds = {} test_builds = {}
test_builds["%s-%s" % (target_name, toolchain_name)] = test_build test_builds["%s-%s" % (target_name, toolchain_name)] = test_build
return result, test_builds return result, test_builds
def test_spec_from_test_builds(test_builds): def test_spec_from_test_builds(test_builds):
return { return {

View File

@ -233,28 +233,28 @@ class mbedToolchain:
def __init__(self, target, options=None, notify=None, macros=None, silent=False, extra_verbose=False): def __init__(self, target, options=None, notify=None, macros=None, silent=False, extra_verbose=False):
self.target = target self.target = target
self.name = self.__class__.__name__ self.name = self.__class__.__name__
# compile/assemble/link/binary hooks # compile/assemble/link/binary hooks
self.hook = hooks.Hook(target, self) self.hook = hooks.Hook(target, self)
# Toolchain flags # Toolchain flags
self.flags = deepcopy(self.DEFAULT_FLAGS) self.flags = deepcopy(self.DEFAULT_FLAGS)
# User-defined macros # User-defined macros
self.macros = macros or [] self.macros = macros or []
# Macros generated from toolchain and target rules/features # Macros generated from toolchain and target rules/features
self.symbols = None self.symbols = None
# Labels generated from toolchain and target rules/features (used for selective build) # Labels generated from toolchain and target rules/features (used for selective build)
self.labels = None self.labels = None
# This will hold the configuration data (as returned by Config.get_config_data()) # This will hold the configuration data (as returned by Config.get_config_data())
self.config_data = None self.config_data = None
# Non-incremental compile # Non-incremental compile
self.build_all = False self.build_all = False
# Build output dir # Build output dir
self.build_dir = None self.build_dir = None
self.timestamp = time() self.timestamp = time()
@ -265,7 +265,7 @@ class mbedToolchain:
# Number of concurrent build jobs. 0 means auto (based on host system cores) # Number of concurrent build jobs. 0 means auto (based on host system cores)
self.jobs = 0 self.jobs = 0
self.CHROOT = None self.CHROOT = None
# Ignore patterns from .mbedignore files # Ignore patterns from .mbedignore files
self.ignore_patterns = [] self.ignore_patterns = []
@ -280,12 +280,13 @@ class mbedToolchain:
self.notify_fun = self.print_notify_verbose self.notify_fun = self.print_notify_verbose
else: else:
self.notify_fun = self.print_notify self.notify_fun = self.print_notify
# Silent builds (no output) # Silent builds (no output)
self.silent = silent self.silent = silent
# Print output buffer # Print output buffer
self.output = "" self.output = str()
self.map_outputs = list() # Place to store memmap scan results in JSON like data structures
# Build options passed by -o flag # Build options passed by -o flag
self.options = options if options is not None else [] self.options = options if options is not None else []
@ -295,7 +296,7 @@ class mbedToolchain:
if self.options: if self.options:
self.info("Build Options: %s" % (', '.join(self.options))) self.info("Build Options: %s" % (', '.join(self.options)))
# uVisor spepcific rules # uVisor spepcific rules
if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels: if 'UVISOR' in self.target.features and 'UVISOR_SUPPORTED' in self.target.extra_labels:
self.target.core = re.sub(r"F$", '', self.target.core) self.target.core = re.sub(r"F$", '', self.target.core)
@ -310,10 +311,10 @@ class mbedToolchain:
if not self.VERBOSE and event['type'] == 'tool_error': if not self.VERBOSE and event['type'] == 'tool_error':
msg = event['message'] msg = event['message']
elif event['type'] in ['info', 'debug']: elif event['type'] in ['info', 'debug']:
msg = event['message'] msg = event['message']
elif event['type'] == 'cc': elif event['type'] == 'cc':
event['severity'] = event['severity'].title() event['severity'] = event['severity'].title()
event['file'] = basename(event['file']) event['file'] = basename(event['file'])
@ -615,7 +616,7 @@ class mbedToolchain:
def relative_object_path(self, build_path, base_dir, source): def relative_object_path(self, build_path, base_dir, source):
source_dir, name, _ = split_path(source) source_dir, name, _ = split_path(source)
obj_dir = join(build_path, relpath(source_dir, base_dir)) obj_dir = join(build_path, relpath(source_dir, base_dir))
mkdir(obj_dir) mkdir(obj_dir)
return join(obj_dir, name + '.o') return join(obj_dir, name + '.o')
@ -627,7 +628,7 @@ class mbedToolchain:
cmd_list = [] cmd_list = []
for c in includes: for c in includes:
if c: if c:
cmd_list.append(('-I%s' % c).replace("\\", "/")) cmd_list.append(('-I%s' % c).replace("\\", "/"))
string = " ".join(cmd_list) string = " ".join(cmd_list)
f.write(string) f.write(string)
return include_file return include_file
@ -822,12 +823,12 @@ class mbedToolchain:
if self.target.OUTPUT_NAMING == "8.3": if self.target.OUTPUT_NAMING == "8.3":
name = name[0:8] name = name[0:8]
ext = ext[0:3] ext = ext[0:3]
# Create destination directory # Create destination directory
head, tail = split(name) head, tail = split(name)
new_path = join(tmp_path, head) new_path = join(tmp_path, head)
mkdir(new_path) mkdir(new_path)
filename = name+'.'+ext filename = name+'.'+ext
elf = join(tmp_path, name + '.elf') elf = join(tmp_path, name + '.elf')
bin = join(tmp_path, filename) bin = join(tmp_path, filename)
@ -844,7 +845,7 @@ class mbedToolchain:
self.binary(r, elf, bin) self.binary(r, elf, bin)
self.mem_stats(map) self.map_outputs = self.mem_stats(map)
self.var("compile_succeded", True) self.var("compile_succeded", True)
self.var("binary", filename) self.var("binary", filename)
@ -900,7 +901,11 @@ class mbedToolchain:
self.notify({'type': 'var', 'key': key, 'val': value}) self.notify({'type': 'var', 'key': key, 'val': value})
def mem_stats(self, map): def mem_stats(self, map):
# Creates parser object """! Creates parser object
@param map Path to linker map file to parse and decode
@return Memory summary structure with memory usage statistics
None if map file can't be opened and processed
"""
toolchain = self.__class__.__name__ toolchain = self.__class__.__name__
# Create memap object # Create memap object
@ -909,7 +914,7 @@ class mbedToolchain:
# Parse and decode a map file # Parse and decode a map file
if memap.parse(abspath(map), toolchain) is False: if memap.parse(abspath(map), toolchain) is False:
self.info("Unknown toolchain for memory statistics %s" % toolchain) self.info("Unknown toolchain for memory statistics %s" % toolchain)
return return None
# Write output to stdout in text (pretty table) format # Write output to stdout in text (pretty table) format
memap.generate_output('table') memap.generate_output('table')
@ -917,11 +922,16 @@ class mbedToolchain:
# Write output to file in JSON format # Write output to file in JSON format
map_out = splitext(map)[0] + "_map.json" map_out = splitext(map)[0] + "_map.json"
memap.generate_output('json', map_out) memap.generate_output('json', map_out)
# Write output to file in CSV format for the CI # Write output to file in CSV format for the CI
map_csv = splitext(map)[0] + "_map.csv" map_csv = splitext(map)[0] + "_map.csv"
memap.generate_output('csv-ci', map_csv) memap.generate_output('csv-ci', map_csv)
# Here we return memory statistics structure (constructed after
# call to generate_output) which contains raw data in bytes
# about sections + summary
return memap.get_memory_summary()
# Set the configuration data # Set the configuration data
def set_config_data(self, config_data): def set_config_data(self, config_data):
self.config_data = config_data self.config_data = config_data