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/merge.py b/coverage-tool/coverage-reporting/merge.py
new file mode 100755
index 0000000..e3d9d65
--- /dev/null
+++ b/coverage-tool/coverage-reporting/merge.py
@@ -0,0 +1,179 @@
+# !/usr/bin/env python
+###############################################################################
+# Copyright (c) 2020, ARM Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+###############################################################################
+
+###############################################################################
+# FILE: merge.py
+#
+# DESCRIPTION: Merge two or more .info and json files, sanitizing source file
+#              paths.
+#              If different .info files contain the same source code duplicated
+#              in different directories, we use the absolute paths of the
+#              first .info file.
+#
+###############################################################################
+
+
+import os
+import sys
+import argparse
+from argparse import RawTextHelpFormatter
+import subprocess
+import json
+
+
+# Define an argument parser using the argparse library
+parser = argparse.ArgumentParser(epilog="""Example of usage:
+python3 merge.py -a coverage_1.info -a coverage_2.info -o coverage_merge.info \
+-j input_file1.json -j input_file2.json -m merge_file.json
+
+It is possible to merge any number of files at once.
+If metadata json files are defined then they must pair with their
+corresponding info file, i.e. have the same name.
+If a local workspace is defined then the paths in the info files will
+be translated from the original test workspace to the local workspace
+to enable the usage of LCOV, but the original files will be kept intact.
+By default, the output file must be a new file.
+To overwrite an existing file, use the "--force" option.
+
+Note: the user is expected to merge .info files referring to the same project.
+If merging .info files from different projects, LCOV can be exploited directly
+using a command such as "lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
+-a coverage_2.info -o coverage_merge.info."
+""", formatter_class=RawTextHelpFormatter)
+requiredNamed = parser.add_argument_group('required named arguments')
+requiredNamed.add_argument("-a", "--add-file",
+                           help="Input info file to be merged.",
+                           action='append', required=True)
+requiredNamed.add_argument("-o", "--output",
+                           help="Name of the output info (merged) file.",
+                           required=False)
+parser.add_argument("-j", "--json-file", action='append',
+                    help="Input json file to be merged.")
+parser.add_argument("-m", "--output-json",
+                    help="Name of the output json (merged) file.")
+parser.add_argument("--force", dest='force', action='store_true',
+                    help="force overwriting of output file.")
+parser.add_argument("--local-workspace", dest='local_workspace',
+                    help='Local workspace where source files reside.')
+
+options = parser.parse_args(sys.argv[1:])
+# At least two .info files are expected
+if len(options.add_file) < 2:
+    print('Error: too few input files.\n')
+    sys.exit(1)
+# The same number of info and json files expected
+if options.json_file:
+    if len(options.json_file) != len(options.add_file):
+        print('Umatched number of info and json files.\n')
+        sys.exit(1)
+
+file_groups = []
+info_files_to_merge = []
+# Check if files exist
+for file_name in options.add_file:
+    print("Merging '{}'".format(file_name))
+    if not os.path.isfile(file_name):
+        print('Error: file "' + file_name + '" not found.\n')
+        sys.exit(1)
+    if not file_name[-5:] == '.info':
+        print('Error: file "' + file_name +
+              '" has wrong extension. Expected .info file.\n')
+        sys.exit(1)
+    if file_name in info_files_to_merge:
+        print("Error: Duplicated info file '{}'".format(file_name))
+        sys.exit(1)
+    info_files_to_merge.append(file_name)
+    file_group = {"info": file_name, "locations": [], "json": ""}
+    info_name = os.path.basename(file_name).split(".")[0]
+    if options.json_file:
+        json_name = [i for i in options.json_file
+                     if os.path.basename(i).split(".")[0] == info_name]
+        if not json_name:
+            print("Umatched json file name for '{}'".format(file_name))
+            sys.exit(1)
+        json_name = json_name.pop()
+        if not json_name[-5:] == '.json':
+            print('Error: file "' + json_name +
+                  '" has wrong extension. Expected .json file.\n')
+            sys.exit(1)
+        if not os.path.isfile(json_name):
+            print('Error: file "' + json_name + '" not found.\n')
+            sys.exit(1)
+        # Now we have to extract the location folders for each info
+        # this is needed if we want translation to local workspace
+        file_group["json"] = json_name
+        with open(json_name) as json_file:
+            json_data = json.load(json_file)
+        locations = []
+        for source in json_data["configuration"]["sources"]:
+            locations.append(source["LOCATION"])
+        file_group["locations"] = locations
+    file_groups.append(file_group)
+
+# Check the extension of the output file
+if not options.output[-5:] == '.info':
+    print('Error: file "' + options.output +
+          '" has wrong extension. Expected .info file.\n')
+    sys.exit(1)
+
+if options.local_workspace is not None:
+    # Translation from test to local workspace
+    i = 0
+    while i < len(info_files_to_merge):
+        info_file = open(info_files_to_merge[i], "r")
+        print("Translating workspace for '{}'...".format(
+              info_files_to_merge[i]))
+        info_lines = info_file.readlines()
+        info_file.close()
+        common_prefix = os.path.normpath(
+            os.path.commonprefix([line[3:] for line in info_lines
+                                  if 'SF:' in line]))
+        temp_file = 'temporary_' + str(i) + '.info'
+        with open(temp_file, "w+") as f:
+            for line in info_lines:
+                cf = common_prefix
+                if os.path.basename(common_prefix) in file_groups[i]["locations"]:
+                    cf = os.path.dirname(common_prefix)
+                f.write(line.replace(cf, options.local_workspace))
+        info_files_to_merge[i] = temp_file  # Replace info file to be merged
+        i += 1
+
+# Merge json files
+if len(options.json_file):
+    json_merged_list = []
+    json_merged = {}
+    j = 0
+    while j < len(options.json_file):
+        json_file = options.json_file[j]
+        with open(json_file) as f:
+            data = json.load(f)
+        for source in data['configuration']['sources']:
+            if source not in json_merged_list:
+                json_merged_list.append(source)
+        j += 1
+    json_merged = {'configuration': {'sources': json_merged_list}}
+    with open(options.output_json, 'w') as f:
+        json.dump(json_merged, f)
+
+
+# Exploit LCOV merging capabilities
+# Example of LCOV usage: lcov -rc lcov_branch_coverage=1 -a coverage_1.info \
+# -a coverage_2.info -o coverage_merge.info
+command = ['lcov', '-rc', 'lcov_branch_coverage=1']
+
+for file_name in info_files_to_merge:
+    command.append('-a')
+    command.append(file_name)
+command.append('-o')
+command.append(options.output)
+
+subprocess.call(command)
+
+# Delete the temporary files
+if options.local_workspace is not None:
+    for f in info_files_to_merge:
+        os.remove(f)