From 09a09f70b7dcae95f2e911a9b34bda2a67415ff4 Mon Sep 17 00:00:00 2001 From: Brian Daniels Date: Tue, 11 Oct 2016 14:33:24 -0500 Subject: [PATCH] Handling output of parallelized test building. This makes use of the reports generated by the building of tests to prevent output from interleaving when the build is parallelized. This required some changes to memap to return a generated string from the 'generate_output' function. I also had an option to stop the prints from memap to prevent text from interleaving --- tools/memap.py | 68 +++++++++++++++++++++++------------- tools/test_api.py | 29 ++++++++++++--- tools/toolchains/__init__.py | 2 +- 3 files changed, 69 insertions(+), 30 deletions(-) diff --git a/tools/memap.py b/tools/memap.py index f26e97a819..0b9e91f6a1 100644 --- a/tools/memap.py +++ b/tools/memap.py @@ -9,6 +9,7 @@ import csv import json import argparse from prettytable import PrettyTable +from StringIO import StringIO from utils import argparse_filestring_type, \ argparse_lowercase_hyphen_type, argparse_uppercase_type @@ -396,7 +397,7 @@ class MemapParser(object): export_formats = ["json", "csv-ci", "table"] - def generate_output(self, export_format, file_output=None): + def generate_output(self, export_format, file_output=None, silent=False): """ Generates summary of memory map data Positional arguments: @@ -407,10 +408,13 @@ class MemapParser(object): """ try: - if file_output: - file_desc = open(file_output, 'wb') + if silent: + file_desc = None else: - file_desc = sys.stdout + if file_output: + file_desc = open(file_output, 'wb') + else: + file_desc = sys.stdout except IOError as error: print "I/O error({0}): {1}".format(error.errno, error.strerror) return False @@ -418,19 +422,25 @@ class MemapParser(object): to_call = {'json': self.generate_json, 'csv-ci': self.generate_csv, 'table': self.generate_table}[export_format] - to_call(file_desc) + output_string = to_call(file_desc) - if file_desc is not sys.stdout: + if file_desc is not sys.stdout and file_desc is not None: file_desc.close() + return output_string + def generate_json(self, file_desc): """Generate a json file from a memory map Positional arguments: file_desc - the file to write out the final report to """ - file_desc.write(json.dumps(self.mem_report, indent=4)) - file_desc.write('\n') + output = json.dumps(self.mem_report, indent=4) + if file_desc: + file_desc.write(output) + file_desc.write('\n') + + return output def generate_csv(self, file_desc): """Generate a CSV file from a memoy map @@ -438,7 +448,8 @@ class MemapParser(object): Positional arguments: file_desc - the file to write out the final report to """ - csv_writer = csv.writer(file_desc, delimiter=',', + string_io = StringIO() + csv_writer = csv.writer(string_io, delimiter=',', quoting=csv.QUOTE_MINIMAL) csv_module_section = [] @@ -472,6 +483,11 @@ class MemapParser(object): csv_writer.writerow(csv_module_section) csv_writer.writerow(csv_sizes) + if file_desc: + file_desc.write(string_io.getvalue()) + + return string_io.getvalue() + def generate_table(self, file_desc): """Generate a table from a memoy map @@ -504,28 +520,32 @@ class MemapParser(object): table.add_row(subtotal_row) - file_desc.write(table.get_string()) - file_desc.write('\n') + output = table.get_string() + output += '\n' if self.mem_summary['heap'] == 0: - file_desc.write("Allocated Heap: unknown\n") + output += "Allocated Heap: unknown\n" else: - file_desc.write("Allocated Heap: %s bytes\n" % - str(self.mem_summary['heap'])) + output += "Allocated Heap: %s bytes\n" % \ + str(self.mem_summary['heap']) if self.mem_summary['stack'] == 0: - file_desc.write("Allocated Stack: unknown\n") + output += "Allocated Stack: unknown\n" else: - file_desc.write("Allocated Stack: %s bytes\n" % - str(self.mem_summary['stack'])) + output += "Allocated Stack: %s bytes\n" % \ + str(self.mem_summary['stack']) - file_desc.write("Total Static RAM memory (data + bss): %s bytes\n" % - (str(self.mem_summary['static_ram']))) - file_desc.write( - "Total RAM memory (data + bss + heap + stack): %s bytes\n" - % (str(self.mem_summary['total_ram']))) - file_desc.write("Total Flash memory (text + data + misc): %s bytes\n" % - (str(self.mem_summary['total_flash']))) + output += "Total Static RAM memory (data + bss): %s bytes\n" % \ + str(self.mem_summary['static_ram']) + output += "Total RAM memory (data + bss + heap + stack): %s bytes\n" % \ + str(self.mem_summary['total_ram']) + output += "Total Flash memory (text + data + misc): %s bytes\n" % \ + str(self.mem_summary['total_flash']) + + if file_desc: + file_desc.write(output) + + return output toolchains = ["ARM", "ARM_STD", "ARM_MICRO", "GCC_ARM", "IAR"] diff --git a/tools/test_api.py b/tools/test_api.py index 9b0e01aafc..af503316eb 100644 --- a/tools/test_api.py +++ b/tools/test_api.py @@ -2071,6 +2071,19 @@ def norm_relative_path(path, start): def build_test_worker(*args, **kwargs): + """This is a worker function for the parallel building of tests. The `args` + and `kwargs` are passed directly to `build_project`. It returns a dictionary + with the following structure: + + { + 'result': `True` if no exceptions were thrown, `False` otherwise + 'reason': Instance of exception that was thrown on failure + 'bin_file': Path to the created binary if `build_project` was + successful. Not present otherwise + 'kwargs': The keyword arguments that were passed to `build_project`. + This includes arguments that were modified (ex. report) + } + """ bin_file = None ret = { 'result': False, @@ -2138,7 +2151,8 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, 'properties': properties, 'verbose': verbose, 'app_config': app_config, - 'build_profile': build_profile + 'build_profile': build_profile, + 'silent': True } results.append(p.apply_async(build_test_worker, args, kwargs)) @@ -2161,13 +2175,16 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, worker_result = r.get() results.remove(r) + # Take report from the kwargs and merge it into existing report + report_entry = worker_result['kwargs']['report'][target_name][toolchain_name] + for test_key in report_entry.keys(): + report[target_name][toolchain_name][test_key] = report_entry[test_key] - for test_key in worker_result['kwargs']['report'][target_name][toolchain_name].keys(): - report[target_name][toolchain_name][test_key] = worker_result['kwargs']['report'][target_name][toolchain_name][test_key] - + # Set the overall result to a failure if a build failure occurred if not worker_result['result'] and not isinstance(worker_result['reason'], NotSupportedException): result = False + # Adding binary path to test build result if worker_result['result'] and 'bin_file' in worker_result: bin_file = norm_relative_path(worker_result['bin_file'], execution_directory) @@ -2179,7 +2196,9 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name, ] } - # TODO: add 'Image: bin_file' print statement here + test_key = worker_result['kwargs']['project_id'].upper() + print report[target_name][toolchain_name][test_key][0][0]['output'].rstrip() + print 'Image: %s\n' % bin_file except ToolException, err: if p._taskqueue.queue: diff --git a/tools/toolchains/__init__.py b/tools/toolchains/__init__.py index c57dda55e6..595f220769 100644 --- a/tools/toolchains/__init__.py +++ b/tools/toolchains/__init__.py @@ -1076,7 +1076,7 @@ class mbedToolchain: return None # Write output to stdout in text (pretty table) format - memap.generate_output('table') + self.info(memap.generate_output('table', silent=True)) # Write output to file in JSON format map_out = splitext(map)[0] + "_map.json"