Add initial version of c-picker
Introduce the following features to c-picker:
* Picking of the elements from C source files:
* Include directives
* Functions
* Variables
* Removing 'static' keyword from declarations
* Mapping coverage to the original source
* Documentation of the system
Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: Ia5cb90d3096b16b15aafb86363b8cabfe7d2ab72
diff --git a/c_picker/coverage.py b/c_picker/coverage.py
new file mode 100644
index 0000000..1c79db0
--- /dev/null
+++ b/c_picker/coverage.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+# Copyright (c) 2020-2021, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+"""
+This module can map the coverage of c-picker generated files to the original source files.
+"""
+
+import argparse
+import json
+import os
+import re
+import sys
+
+class MappingDescriptor:
+ """ Storage class for file-line number data """
+
+ def __init__(self, file=None, line=None):
+ self.descriptor = {"file": file, "line": line}
+
+ def get_file(self):
+ """ Queries file name """
+ return self.descriptor["file"]
+
+ def get_line(self):
+ """ Queries line number """
+ return self.descriptor["line"]
+
+ @staticmethod
+ def serialize(mapping_descriptor):
+ """ Serializes the descriptor into a C comment containing JSON. """
+ return "/* C-PICKER-MAPPING " + json.dumps(mapping_descriptor.descriptor) + " */"
+
+ @staticmethod
+ def deserialize(line):
+ """
+ Deserializes the descriptor from a C comment containing JSON.
+ It returns None if the line is not matching the required pattern.
+ """
+ match = re.match(r"/\* C-PICKER-MAPPING ({.*}) \*/", line)
+ if not match:
+ return None
+
+ mapping_descriptor = MappingDescriptor()
+ mapping_descriptor.descriptor = json.loads(match.group(1))
+ return mapping_descriptor
+
+class CoverageMapper:
+ """ The class maps the coverage of the c-picker generated source files to the original ones. """
+
+ def __init__(self):
+ self.mapping_path = None
+ self.output = sys.stdout
+
+ self.test_name = None
+ self.mapping_enabled = False
+ self.mapping_descriptors = {}
+ self.mapped_source_file = None
+
+ def read_mapping_descriptors(self, filename):
+ """ Reads the mapping descriptors from the c-picker generated file. """
+ self.mapping_enabled = True
+
+ with open(filename, "r") as source_file:
+ source_lines = source_file.read().split("\n")
+ source_line_index = 1
+
+ for source_line in source_lines:
+ mapping_descriptor = MappingDescriptor.deserialize(source_line)
+
+ if mapping_descriptor:
+ # +1: the elements start at the following line after the descriptor comment
+ self.mapping_descriptors[source_line_index + 1] = mapping_descriptor
+ source_line_index += 1
+
+ def clear_mapping_descriptors(self):
+ """ Resets the mapping descriptor database. """
+ self.mapping_enabled = False
+ self.mapping_descriptors = {}
+ self.mapped_source_file = None
+
+ def find_mapping(self, line_number):
+ """ Find the mapping descriptor of a line number and also returns the mapped line. """
+ mapping_descriptor_index = 0
+ for i in self.mapping_descriptors:
+ if i > line_number:
+ break
+ mapping_descriptor_index = i
+
+ if not mapping_descriptor_index:
+ raise Exception("Invalid mapping for line %d" % line_number)
+
+ mapping_descriptor = self.mapping_descriptors[mapping_descriptor_index]
+ mapped_line = line_number - mapping_descriptor_index + mapping_descriptor.get_line()
+ return mapping_descriptor, mapped_line
+
+ def output_line(self, line):
+ """ Outputs a single line to the output """
+ self.output.write(line + "\n")
+
+ def process_line(self, trace_line):
+ """
+ The function processes a single line of the trace file and maintains the internal state of
+ the state matchine.
+ """
+ if not trace_line:
+ return
+
+ if trace_line == "end_of_record":
+ # End of record, exit mapping mode
+ self.clear_mapping_descriptors()
+ self.output_line(trace_line)
+ return
+
+ command, params = trace_line.split(":", 1)
+ if command == "TN":
+ # Test name TN:<test name>
+ self.test_name = params
+ elif command == "SF" and params.startswith(self.mapping_path):
+ # Source file SF:<absolute path to the source file>
+ # Matching source file, switch into mapping mode
+ self.read_mapping_descriptors(params)
+ return
+
+ if self.mapping_enabled and (command in ("FN", "BRDA", "DA")):
+ # Function FN:<line number of function start>,<function name>
+ # Branch coverage BRDA:<line number>,<block number>,<branch number>,<taken>
+ # Line coverage DA:<line number>,<execution count>[,<checksum>]
+ line_number, remaining_params = params.split(",", 1)
+ mapping_descriptor, mapped_line_number = self.find_mapping(int(line_number))
+
+ if mapping_descriptor.get_file() != self.mapped_source_file:
+ # Change in the name of the mapped source file, starting new record
+ if self.mapped_source_file is not None:
+ # No need for this part if it was the first mapped file
+ # because TN has been alread printed
+ self.output_line("end_of_record")
+ self.output_line("TN:%s" % self.test_name)
+ self.mapped_source_file = mapping_descriptor.get_file()
+ self.output_line("SF:%s" % self.mapped_source_file)
+
+ self.output_line("%s:%d,%s" % (command, mapped_line_number, remaining_params))
+ return
+
+ self.output_line(trace_line)
+
+ def main(self):
+ """ Runs coverage mapper configured by the command line arguments """
+
+ try:
+ parser = argparse.ArgumentParser(prog="c-picker-coverage-mapper", description=__doc__)
+ parser.add_argument("--input", help="Input trace file", required=True)
+ parser.add_argument("--output", help="Input file")
+ parser.add_argument("--mapping-path", help="Directory of generated files",
+ required=True)
+ args = parser.parse_args()
+
+ output_fp = open(args.output, "w") if args.output else None
+
+ self.output = output_fp if output_fp else sys.stdout
+ self.mapping_path = os.path.abspath(args.mapping_path)
+
+ with open(args.input, "r") as tracefile:
+ trace_lines = tracefile.read().split("\n")
+ for trace_line in trace_lines:
+ self.process_line(trace_line)
+
+ if output_fp:
+ output_fp.close()
+
+ return 0
+ except FileNotFoundError as exception:
+ print("File not found: %s" % str(exception), file=sys.stderr)
+ except ValueError as exception:
+ print("Invalid format: %s" % str(exception), file=sys.stderr)
+ except Exception as exception: # pylint: disable=broad-except
+ print("Exception: %s" % exception, file=sys.stderr)
+
+ return 1
+
+def main():
+ """ Command line main function """
+ coverage_mapper = CoverageMapper()
+ result = coverage_mapper.main()
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()