Source code for gnatcoverage

# GNAThub (GNATdashboard)
# Copyright (C) 2014-2024, AdaCore
#
# This is free software;  you can redistribute it  and/or modify it  under
# terms of the  GNU General Public License as published  by the Free Soft-
# ware  Foundation;  either version 3,  or (at your option) any later ver-
# sion.  This software is distributed in the hope  that it will be useful,
# but WITHOUT ANY WARRANTY;  without even the implied warranty of MERCHAN-
# TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for  more details.  You should have  received  a copy of the GNU
# General  Public  License  distributed  with  this  software;   see  file
# COPYING3.  If not, go to http://www.gnu.org/licenses for a complete copy
# of the license.

"""GNAThub plug-in for the GNATcoverage command-line tool.

It exports the GNATcoverage class which implements the :class:`GNAThub.Plugin`
interface.  This allows GNAThub's plug-in scanner to automatically find this
module and load it as part of the GNAThub default execution.
"""

import os
from xml.dom import minidom

import GNAThub
from GNAThub import Console, Plugin, Reporter


[docs]class GNATcoverage(Plugin, Reporter): """GNATcoverage plugin for GNAThub. Retrieves .xcov generated files from the project root object directory, parses them and feeds the database with the data collected from each files. """
[docs] def __init__(self): super(GNATcoverage, self).__init__() if GNAThub.dry_run_without_project(): return self.GNATCOVERAGE_OUTPUT = os.path.join( GNAThub.Project.artifacts_dir()) self.XML_EXT = '.xml' self.tool = None # Mapping: coverage level -> issue rule for this coverage. self.issue_rules = {}
def __process_file(self, resource, filename, resources_messages): """Processe one file, adding in bulk all coverage info found. :param GNAThub.Resource resource: the resource being processed :param str filename: the name of the resource """ bulk_messages = [] file_path = os.path.join(self.GNATCOVERAGE_OUTPUT, filename) + self.XML_EXT def add_message(rule, message, ranking, line_no, col_begin, col_end): bulk_messages.append([ GNAThub.Message(rule, message, ranking=ranking), line_no, col_begin, col_end, ]) file_xml = minidom.parse(file_path) lines = file_xml.getElementsByTagName('src_mapping') for index, line in enumerate(lines, start=1): cov_rule = self.issue_rules['coverage'] cov_char = line.attributes['coverage'].value cov_status = { '.': 'NO_CODE', '0': 'NOT_COVERABLE', '?': 'UNDETERMINED_COVERAGE', 'D': 'DISABLED_COVERAGE', '-': 'NOT_COVERED', '!': 'PARTIALLY_COVERED', '+': 'COVERED', '*': 'EXEMPTED_WITH_VIOLATION', '@': 'EXEMPTED_WITH_UNDETERMINED_COV', '#': 'EXEMPTED_NO_VIOLATION' }[cov_char] line_info = line.getElementsByTagName('line') line_no = int(line_info[0].attributes['num'].value) col_begin = 1 col_end = 1 if line_info.length > 1: if line_info[1].hasAttribute('column_begin'): col_begin = int( line_info[1].attributes['column_begin'].value) if line_info[1].hasAttribute('column_end'): col_end = int(line_info[1].attributes['column_end'].value) add_message(cov_rule, cov_status, GNAThub.RANKING_LOW, line_no, 0, 0) message_info = line.getElementsByTagName('message') if message_info.length > 0: sco = message_info[0].attributes['SCO'].value.split(' ')[2] cov_level = { 'STATEMENT': 'statement', 'DECISION': 'decision', 'CONDITION': 'condition', }[sco] ranking = { 'statement': GNAThub.RANKING_HIGH, 'decision': GNAThub.RANKING_MEDIUM, 'condition': GNAThub.RANKING_LOW }[cov_level] cov_rule = self.issue_rules[cov_level] message_label = message_info[0].attributes['message'].value message_label = cov_level + " " + message_label add_message(cov_rule, message_label, ranking, line_no, col_begin, col_end) # Preparing list for tool level insertion of resources messages resources_messages.append([resource, bulk_messages])
[docs] def report(self): """Analyse the report files generated by :program:`GNATcoverage`. Finds all .xcov files in the object directory and parses them. Sets the exec_status property according to the success of the analysis: * ``GNAThub.EXEC_SUCCESS``: on successful execution and analysis * ``GNAThub.EXEC_FAILURE``: on any error """ # Clear existing references only if not incremental run if not GNAThub.incremental(): self.log.info('clear existing results if any') GNAThub.Tool.clear_references(self.name) # Check if gnatcoverage output folder exist. First check if the xml # sub-directory exists (when multiple reports are produced by gnatcov, # each one is placed in a sub-directory of the output directory), and # update the output dir is this is the case. if not os.path.exists(self.GNATCOVERAGE_OUTPUT): self.log.info('No gnatcoverage folder in object directory') return GNAThub.EXEC_FAILURE elif os.path.exists(os.path.join(self.GNATCOVERAGE_OUTPUT, "xml")): self.GNATCOVERAGE_OUTPUT = os.path.join( self.GNATCOVERAGE_OUTPUT, "xml" ) self.info('parse coverage reports (%s)' % self.XML_EXT) # Fetch all files in project object directory and retrieve only # .xml files, absolute path file_path = os.path.join(self.GNATCOVERAGE_OUTPUT, 'index.xml') if not os.path.exists(file_path): self.error('no index.xml file in object directory') return GNAThub.EXEC_FAILURE index_xml = minidom.parse(file_path) if not index_xml: self.error('no %s file in object directory' % self.XML_EXT) return GNAThub.EXEC_FAILURE files = index_xml.getElementsByTagName('file') self.tool = GNAThub.Tool(self.name) for cov_level in ('statement', 'decision', 'condition', 'coverage'): self.issue_rules[cov_level] = GNAThub.Rule( cov_level, cov_level, GNAThub.RULE_KIND, self.tool) total = files.length # List of resource messages suitable for tool level bulk insertion resources_messages = [] try: for index, file in enumerate(files, start=1): # Retrieve source fullname filename = file.attributes['name'].value src = GNAThub.Project.source_file(filename) resource = GNAThub.Resource.get(src) if resource: # Use file name to look for the related .xml fname = os.path.basename(filename) self.__process_file(resource, fname, resources_messages) Console.progress(index, total, new_line=(index == total)) # Tool level insert for resources messages self.tool.add_messages(resources_messages, []) except (IOError, ValueError) as why: self.log.exception('failed to parse reports') self.error(str(why)) return GNAThub.EXEC_FAILURE else: return GNAThub.EXEC_SUCCESS