From bda63abb409a68755fc964906e6b95e67cd6cc12 Mon Sep 17 00:00:00 2001 From: antti Date: Thu, 2 Mar 2017 17:51:25 +0800 Subject: [PATCH] CI: modify unit test parser to parse unit test cases from build files Previously test cases were parsed from test files --- tools/unit-test-app/CreateSectionTable.py | 145 ++++++++++++++++++++++ tools/unit-test-app/UnitTestParser.py | 63 ++++------ 2 files changed, 172 insertions(+), 36 deletions(-) create mode 100644 tools/unit-test-app/CreateSectionTable.py diff --git a/tools/unit-test-app/CreateSectionTable.py b/tools/unit-test-app/CreateSectionTable.py new file mode 100644 index 000000000..38285b9ee --- /dev/null +++ b/tools/unit-test-app/CreateSectionTable.py @@ -0,0 +1,145 @@ +# This file is used to process section data generated by `objdump -s` +import re + + +class SectionTable(object): + + class Section(object): + SECTION_START_PATTERN = re.compile("Contents of section (.+?):") + DATA_PATTERN = re.compile("([0-9a-f]{4,8})") + + def __init__(self, name, start_address, data): + self.name = name + self.start_address = start_address + self.data = data + + def __contains__(self, item): + if (item["region"] == self.name or item["region"] == "any") \ + and (self.start_address <= item["address"] < (self.start_address + len(self.data))): + return True + else: + return False + + def __getitem__(self, item): + if isinstance(item, int): + return self.data[item - self.start_address] + elif isinstance(item, slice): + start = item.start if item.start is None else item.start - self.start_address + stop = item.stop if item.stop is None else item.stop - self.start_address + return self.data[start:stop] + return self.data[item] + + def __str__(self): + return "%s [%08x - %08x]" % (self.name, self.start_address, self.start_address+len(self.data)) + + __repr__ = __str__ + + @classmethod + def parse_raw_data(cls, raw_data): + name = "" + data = "" + start_address = 0 + # first find start line + for i, line in enumerate(raw_data): + if line.find("Contents of section ") != -1: # do strcmp first to speed up + match = cls.SECTION_START_PATTERN.search(line) + if match is not None: + name = match.group(1) + raw_data = raw_data[i+1:] + break + else: + # do some error handling + raw_data = [""] # add a dummy first data line + pass + + def process_data_line(line_to_process): + # first remove the ascii part + hex_part = line_to_process.split(" ")[0] + # process rest part + data_list = cls.DATA_PATTERN.findall(hex_part) + try: + _address = int(data_list[0], base=16) + except IndexError: + _address = -1 + + def hex_to_str(hex_data): + if len(hex_data) % 2 == 1: + hex_data = "0" + hex_data # append zero at the beginning + _length = len(hex_data) + return "".join([chr(int(hex_data[_i:_i+2], base=16)) + for _i in range(0, _length, 2)]) + pass + + return _address, "".join([hex_to_str(x) for x in data_list[1:]]) + + # handle first line: + address, _data = process_data_line(raw_data[0]) + if address != -1: + start_address = address + data += _data + raw_data = raw_data[1:] + for i, line in enumerate(raw_data): + address, _data = process_data_line(line) + if address == -1: + raw_data = raw_data[i:] + break + else: + data += _data + else: + # do error handling + raw_data = [] + pass + return cls(name, start_address, data) if start_address != -1 else None,\ + None if len(raw_data) == 0 else raw_data + pass + + def __init__(self, file_name): + with open(file_name, "rb") as f: + raw_data = f.readlines() + self.table = [] + while raw_data: + section, raw_data = self.Section.parse_raw_data(raw_data) + self.table.append(section) + + def get_unsigned_int(self, region, address, size=4, endian="LE"): + if address % 4 != 0 or size % 4 != 0: + print "warning: try to access without 4 bytes aligned" + key = {"address": address, "region": region} + for section in self.table: + if key in section: + tmp = section[address:address+size] + value = 0 + for i in range(size): + if endian == "LE": + value += ord(tmp[i]) << (i*8) + elif endian == "BE": + value += ord(tmp[i]) << ((size - i - 1) * 8) + else: + print "only support LE or BE for parameter endian" + assert False + break + else: + value = None + return value + + def get_string(self, region, address): + value = None + key = {"address": address, "region": region} + for section in self.table: + if key in section: + value = section[address:] + for i, c in enumerate(value): + if c == '\0': + value = value[:i] + break + break + return value + pass + + +def main(): + pass + + +if __name__ == '__main__': + main() diff --git a/tools/unit-test-app/UnitTestParser.py b/tools/unit-test-app/UnitTestParser.py index d3fa1efb0..8096f8839 100644 --- a/tools/unit-test-app/UnitTestParser.py +++ b/tools/unit-test-app/UnitTestParser.py @@ -1,9 +1,9 @@ import yaml import os -import os.path import re import sys import shutil +import CreateSectionTable MODULE_MAP = yaml.load(open("ModuleDefinition.yml", "r")) @@ -39,40 +39,28 @@ IDF_PATH = os.getcwd() class Parser(object): @classmethod - def parse_test_folders(cls): - test_folder_paths = list() - os.chdir(os.path.join(IDF_PATH, "components")) - component_dirs = [d for d in os.listdir(".") if os.path.isdir(d)] - for dir in component_dirs: - os.chdir(dir) - if "test" in os.listdir("."): - test_folder_paths.append(os.path.join(os.getcwd(), "test")) - os.chdir("..") - Parser.parse_test_files(test_folder_paths) - - @classmethod - def parse_test_files(cls, test_folder_paths): - for path in test_folder_paths: - os.chdir(path) - for file_path in os.listdir("."): - if file_path[-2:] == ".c": - Parser.read_test_file(os.path.join(os.getcwd(), file_path), len(test_cases)+1) + def parse_test_addresses(cls): + table = CreateSectionTable.SectionTable(os.path.join(IDF_PATH, "tools", "unit-test-app", "build", "tmp")) + file_index = 1 + test_index = 1 + with open(os.path.join(IDF_PATH, "tools", "unit-test-app", "build", "tests"), "r") as file: + for line in file: + line = line.split() + test = int(line[0],16) + section = line[3] + name_addr = table.get_unsigned_int(section, test, 4) + desc_addr = table.get_unsigned_int(section, test + 4, 4) + name = table.get_string("any", name_addr) + desc = table.get_string("any", desc_addr) + Parser.parse_test_cases(file_index, test_index, "%s, %s" % (name, desc)) + file_index += 1 + test_index += 1 os.chdir(os.path.join("..", "..")) Parser.dump_test_cases(test_cases) - @classmethod - def read_test_file(cls, test_file_path, file_index): - test_index = 0 - with open(test_file_path, "r") as file: - for line in file: - if re.match("TEST_CASE", line): - test_index += 1 - tags = re.split(r"[\[\]\"]", line) - Parser.parse_test_cases(file_index, test_index, tags) - - @classmethod def parse_test_cases(cls, file_index, test_index, tags): + tags = re.split(r"[\[\]\"]", tags) ci_ready = "Yes" test_env = "UT_T1_1" for tag in tags: @@ -80,7 +68,7 @@ class Parser(object): ci_ready = "No" if re.match("test_env=", tag): test_env = tag[9:] - module_name = tags[4] + module_name = tags[1] try: MODULE_MAP[module_name] except KeyError: @@ -91,14 +79,14 @@ class Parser(object): test_case = dict(TEST_CASE_PATTERN) test_case.update({"module": MODULE_MAP[module_name]['module'], "CI ready": ci_ready, - "cmd set": ["IDFUnitTest/UnitTest", [tags[1]]], + "cmd set": ["IDFUnitTest/UnitTest", [tags[0][:-2]]], "ID": id, "test point 2": module_name, - "steps": tags[1], - "comment": tags[1], + "steps": tags[0][:-2], + "comment": tags[0][:-2], "test environment": test_env, "sub module": MODULE_MAP[module_name]['sub module'], - "summary": tags[1]}) + "summary": tags[0][:-2]}) if test_case["CI ready"] == "Yes": if test_ids.has_key(test_env): test_ids[test_env].append(id) @@ -160,7 +148,10 @@ class Parser(object): def main(): - Parser.parse_test_folders() + os.chdir(os.path.join(IDF_PATH, "tools", "unit-test-app", "build")) + os.system('xtensa-esp32-elf-objdump -t unit-test-app.elf | grep test_desc > tests') + os.system('xtensa-esp32-elf-objdump -s unit-test-app.elf > tmp') + Parser.parse_test_addresses() Parser.parse_gitlab_ci() Parser.dump_ci_config() Parser.copy_module_def_file()