Initial commit.

 - qa-tools public release which includes:
    - trace-based coverage tool
    - quality metrics measurement and tracking setup
    - associated in-source documentation.

Signed-off-by: Basil Eljuse <basil.eljuse@arm.com>
diff --git a/coverage-tool/coverage-reporting/generate_info_file.py b/coverage-tool/coverage-reporting/generate_info_file.py
new file mode 100755
index 0000000..0c0f39a
--- /dev/null
+++ b/coverage-tool/coverage-reporting/generate_info_file.py
@@ -0,0 +1,410 @@
+# !/usr/bin/env python
+##############################################################################
+# Copyright (c) 2020, ARM Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+##############################################################################
+
+import os
+import sys
+import json
+import re
+import argparse
+
+
+def function_coverage(function_tuples, info_file):
+    """
+    Parses and get information from intermediate json file to info
+    file for function coverage
+
+    :param function_tuples: List of tuples with function name
+                            and its data as pairs.
+    :param info_file: Handler to for file writing coverage
+    """
+    total_func = 0
+    covered_func = 0
+    function_names = []
+    function_cov = []
+    for func_name, func_data in function_tuples:
+        function_names.append(
+            'FN:{},{}\n'.format(
+                func_data["line_number"],
+                func_name))
+        total_func += 1
+        if func_data["covered"]:
+            covered_func += 1
+            function_cov.append('FNDA:1,{}\n'.format(func_name))
+        else:
+            function_cov.append('FNDA:0,{}\n'.format(func_name))
+    info_file.write("\n".join(function_names))
+    info_file.write("\n".join(function_cov))
+    info_file.write('FNF:{}\n'.format(total_func))
+    info_file.write('FNH:{}\n'.format(covered_func))
+
+
+def line_coverage(lines_dict, info_file):
+    """
+    Parses and get information from intermediate json file to info
+    file for line coverage
+
+    :param lines_dict: Dictionary of lines with line number as key
+                       and its data as value
+    :param info_file: Handler to for file writing coverage
+    """
+    total_lines = 0
+    covered_lines = 0
+    for line in lines_dict:
+        total_lines += 1
+        if lines_dict[line]['covered']:
+            covered_lines += 1
+            info_file.write('DA:' + line + ',1\n')
+        else:
+            info_file.write('DA:' + line + ',0\n')
+    info_file.write('LF:' + str(total_lines) + '\n')
+    info_file.write('LH:' + str(covered_lines) + '\n')
+
+
+def sanity_check(branch_line, lines_dict, abs_path_file):
+    """
+    Check if the 'branch_line' line of the C source corresponds to actual
+    branching instructions in the assembly code. Also, check if that
+    line is covered. If it's not covered, this branching statement can
+    be omitted from the report.
+    Returns False and prints an error message if check is not successful,
+    True otherwise
+
+    :param branch_line: Source code line with the branch instruction
+    :param lines_dict: Dictionary of lines with line number as key
+                        and its data as value
+    :param abs_path_file: File name of the source file
+    """
+    if str(branch_line) not in lines_dict:
+        return False
+    found_branching = False
+    for i in lines_dict[str(branch_line)]['elf_index']:
+        for j in lines_dict[str(branch_line)]['elf_index'][i]:
+            string = lines_dict[str(branch_line)]['elf_index'][i][j][0]
+            # these cover all the possible branching instructions
+            if ('\tb' in string or
+                '\tcbnz' in string or
+                '\tcbz' in string or
+                '\ttbnz' in string or
+                    '\ttbz' in string):
+                # '\tbl' in string or  # already covered by '\tb'
+                # '\tblr' in string or  # already covered by '\tb'
+                # '\tbr' in string or  # already covered by '\tb'
+                found_branching = True
+    if not found_branching:
+        error_log.write(
+            '\nSomething possibly wrong:\n\tFile ' +
+            abs_path_file +
+            ', line ' +
+            str(branch_line) +
+            '\n\tshould be a branching statement but couldn\'t ' +
+            'find correspondence in assembly code')
+    return True
+
+
+def manage_if_branching(branch_line, lines_dict, info_file, abs_path_file):
+    """
+    Takes care of branch coverage, branch_line is the source code
+    line in which the 'if' statement is located the function produces
+    branch coverage info based on C source code and json file content
+
+    :param branch_line: Source code line with the 'if' instruction
+    :param lines_dict: Dictionary of lines with line number as key
+                        and its data as value
+    :param info_file: Handler to for file writing coverage
+    :param abs_path_file: File name of the source file
+    """
+    total_branch_local = 0
+    covered_branch_local = 0
+
+    if not sanity_check(branch_line, lines_dict, abs_path_file):
+        return(total_branch_local, covered_branch_local)
+    total_branch_local += 2
+    current_line = branch_line  # used to read lines one by one
+    # check for multiline if-condition and update current_line accordingly
+    parenthesis_count = 0
+    while True:
+        end_of_condition = False
+        for char in lines[current_line]:
+            if char == ')':
+                parenthesis_count -= 1
+                if parenthesis_count == 0:
+                    end_of_condition = True
+            elif char == '(':
+                parenthesis_count += 1
+        if end_of_condition:
+            break
+        current_line += 1
+    # first branch
+    # simple case: 'if' statements with no braces
+    if '{' not in lines[current_line] and '{' not in lines[current_line + 1]:
+
+        if (str(current_line + 1) in lines_dict and
+                lines_dict[str(current_line + 1)]['covered']):
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '0,' + '1\n')
+            covered_branch_local += 1
+        else:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '0,' + '0\n')
+        current_line += 1
+
+    # more complex case: '{' after the 'if' statement
+    else:
+        if '{' in lines[current_line]:
+            current_line += 1
+        else:
+            current_line += 2
+
+        # we need to check whether at least one line in the block is covered
+        found_covered_line = False
+
+        # this is a simpler version of a stack used to check when a code block
+        # ends at the moment, it just checks for '{' and '}', doesn't take into
+        # account the presence of commented braces
+        brace_counter = 1
+        while True:
+            end_of_block = False
+            for char in lines[current_line]:
+                if char == '}':
+                    brace_counter -= 1
+                    if brace_counter == 0:
+                        end_of_block = True
+                elif char == '{':
+                    brace_counter += 1
+            if end_of_block:
+                break
+            if (str(current_line) in lines_dict and
+                    lines_dict[str(current_line)]['covered']):
+                found_covered_line = True
+
+            current_line += 1
+
+        if found_covered_line:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '0,' + '1\n')
+            covered_branch_local += 1
+        else:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '0,' + '0\n')
+
+    # second branch (if present). If not present, second branch is covered by
+    # default
+    current_line -= 1
+    candidate_else_line = current_line
+    while 'else' not in lines[current_line] and candidate_else_line + \
+            2 >= current_line:
+        current_line += 1
+        if current_line == len(lines):
+            break
+
+    # no 'else': branch covered by default
+    if current_line == candidate_else_line + 3:
+        info_file.write('BRDA:' + str(branch_line) + ',0,' + '1,' + '1\n')
+        covered_branch_local += 1
+        return(total_branch_local, covered_branch_local)
+
+    # 'else' found: check if opening braces are present
+    if '{' not in lines[current_line - 1] and '{' not in lines[current_line]:
+        if str(current_line + 1) in lines_dict:
+            if lines_dict[str(current_line + 1)]['covered']:
+                info_file.write(
+                    'BRDA:' +
+                    str(branch_line) +
+                    ',0,' +
+                    '1,' +
+                    '1\n')
+                covered_branch_local += 1
+            else:
+                info_file.write(
+                    'BRDA:' +
+                    str(branch_line) +
+                    ',0,' +
+                    '1,' +
+                    '0\n')
+        else:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '1,' + '0\n')
+
+    else:
+        if '{' in lines[current_line]:
+            current_line += 1
+        else:
+            current_line += 2
+        found_covered_line = False
+        while '}' not in lines[current_line]:
+            if (str(current_line) in lines_dict and
+                    lines_dict[str(current_line)]['covered']):
+                found_covered_line = True
+                break
+            current_line += 1
+        if found_covered_line:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '1,' + '1\n')
+            covered_branch_local += 1
+        else:
+            info_file.write('BRDA:' + str(branch_line) + ',0,' + '1,' + '0\n')
+
+    return(total_branch_local, covered_branch_local)
+
+
+def manage_switch_branching(switch_line, lines_dict, info_file, abs_path_file):
+    """
+    Takes care of branch coverage, branch_line is the source code
+    line in which the 'switch' statement is located the function produces
+    branch coverage info based on C source code and json file content
+
+    :param switch_line: Source code line with the 'switch' instruction
+    :param lines_dict: Dictionary of lines with line number as key
+                        and its data as value
+    :param info_file: Handler to for file writing coverage
+    :param abs_path_file: File name of the source file
+    """
+
+    total_branch_local = 0
+    covered_branch_local = 0
+
+    if not sanity_check(switch_line, lines_dict, abs_path_file):
+        return(total_branch_local, covered_branch_local)
+
+    current_line = switch_line  # used to read lines one by one
+    branch_counter = 0          # used to count the number of switch branches
+    brace_counter = 0
+
+    # parse the switch-case line by line, checking if every 'case' is covered
+    # the switch-case ends with a '}'
+    while True:
+        if '{' in lines[current_line]:
+            brace_counter += 1
+        if '}' in lines[current_line]:
+            brace_counter -= 1
+        if brace_counter == 0:
+            return(total_branch_local, covered_branch_local)
+        if 'case' in lines[current_line] or 'default' in lines[current_line]:
+            covered = False
+            total_branch_local += 1
+            inner_brace = 0
+            current_line += 1
+            while (('case' not in lines[current_line]
+                   and 'default' not in lines[current_line]) or
+                   inner_brace > 0):
+                if (str(current_line) in lines_dict and
+                        lines_dict[str(current_line)]['covered']):
+                    covered = True
+                if '{' in lines[current_line]:
+                    inner_brace += 1
+                    brace_counter += 1
+                if '}' in lines[current_line]:
+                    inner_brace -= 1
+                    brace_counter -= 1
+                if brace_counter == 0:
+                    break
+                current_line += 1
+            if covered:
+                info_file.write(
+                    'BRDA:' +
+                    str(switch_line) +
+                    ',0,' +
+                    str(branch_counter) +
+                    ',1\n')
+                covered_branch_local += 1
+            else:
+                info_file.write(
+                    'BRDA:' +
+                    str(switch_line) +
+                    ',0,' +
+                    str(branch_counter) +
+                    ',0\n')
+            if brace_counter == 0:
+                return(total_branch_local, covered_branch_local)
+            branch_counter += 1
+        else:
+            current_line += 1
+
+    return(total_branch_local, covered_branch_local)
+
+
+def branch_coverage(abs_path_file, info_file, lines_dict):
+    """
+    Produces branch coverage information, using the functions
+    'manage_if_branching' and 'manage_switch_branching'
+
+    :param abs_path_file: File name of the source file
+    :param info_file: Handler to for file writing coverage
+    :param lines_dict: Dictionary of lines with line number as key
+                       and its data as value
+    """
+    total_branch = 0
+    covered_branch = 0
+
+    # branch coverage: if statements
+    branching_lines = []
+
+    # regex: find all the lines starting with 'if' or 'else if'
+    # (possibly preceded by whitespaces/tabs)
+    pattern = re.compile(r"^\s+if|^\s+} else if|^\s+else if")
+    for i, line in enumerate(open(abs_path_file)):
+        for match in re.finditer(pattern, line):
+            branching_lines.append(i + 1)
+    while branching_lines:
+        t = manage_if_branching(branching_lines.pop(0), lines_dict,
+                                info_file, abs_path_file)
+        total_branch += t[0]
+        covered_branch += t[1]
+
+    # branch coverage: switch statements
+    switch_lines = []
+
+    # regex: find all the lines starting with 'switch'
+    # (possibly preceded by whitespaces/tabs)
+    pattern = re.compile(r"^\s+switch")
+    for i, line in enumerate(open(abs_path_file)):
+        for match in re.finditer(pattern, line):
+            switch_lines.append(i + 1)
+    while switch_lines:
+        t = manage_switch_branching(switch_lines.pop(0), lines_dict,
+                                    info_file, abs_path_file)
+        total_branch += t[0]
+        covered_branch += t[1]
+
+    info_file.write('BRF:' + str(total_branch) + '\n')
+    info_file.write('BRH:' + str(covered_branch) + '\n')
+
+
+parser = argparse.ArgumentParser(
+    description="Script to convert intermediate json file to LCOV info file")
+parser.add_argument('--workspace', metavar='PATH',
+                    help='Folder with source files structure',
+                    required=True)
+parser.add_argument('--json', metavar='PATH',
+                    help='Intermediate json file name',
+                    required=True)
+parser.add_argument('--info', metavar='PATH',
+                    help='Output info file name',
+                    default="coverage.info")
+args = parser.parse_args()
+with open(args.json) as json_file:
+    json_data = json.load(json_file)
+info_file = open(args.info, "w+")
+error_log = open("error_log.txt", "w+")
+file_list = json_data['source_files'].keys()
+
+for relative_path in file_list:
+    abs_path_file = os.path.join(args.workspace, relative_path)
+    if not os.path.exists(abs_path_file):
+        continue
+    source = open(abs_path_file)
+    lines = source.readlines()
+    info_file.write('TN:\n')
+    info_file.write('SF:' + os.path.abspath(abs_path_file) + '\n')
+    lines = [-1] + lines  # shifting the lines indexes to the right
+    function_coverage(
+        json_data['source_files'][relative_path]['functions'].items(),
+        info_file)
+    branch_coverage(abs_path_file, info_file,
+                    json_data['source_files'][relative_path]['lines'])
+    line_coverage(json_data['source_files'][relative_path]['lines'],
+                  info_file)
+    info_file.write('end_of_record\n\n')
+    source.close()
+
+json_file.close()
+info_file.close()
+error_log.close()