#! /usr/bin/env python # Copyright (c) 2017 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # import os import subprocess import re import sys import pprint import FileUtil import Language_C class GCov: def __init__(self): return def canonicalizeLines(lines): result = [] accumulatedLine = "" for line in lines: line = line.strip() if len(line) == 0: if len(accumulatedLine.strip()) > 0: result.append(accumulatedLine.strip()) accumulatedLine = "" elif "creating" in line: if len(accumulatedLine.strip()) > 0: result.append(accumulatedLine.strip()) accumulatedLine = "" result.append(line) else: accumulatedLine = accumulatedLine + " " + line return result def executeGCovCommand(testExecutableFileName): currentDirectory = os.getcwd() targetDirectory = os.path.dirname(os.path.abspath(testExecutableFileName)) testExecutableBaseName = os.path.basename(testExecutableFileName) os.chdir(targetDirectory) objects = Language_C.findFiles("./", testExecutableBaseName+"*.o") if not objects: return objdir = os.path.dirname(objects[0]) gcdas = Language_C.findFiles("./", testExecutableBaseName+"*.gcda") if not gcdas: return gcda = gcdas[0] gcnos = Language_C.findFiles("./", testExecutableBaseName+"*.gcno") if not gcnos: return gcno = gcnos[0] proc = subprocess.Popen(['gcov', '-af', '-o='+objdir, '-gcda='+gcda, '-gcno='+gcno, testExecutableBaseName], stdout=subprocess.PIPE, stderr=subprocess.PIPE) os.chdir(currentDirectory) inputLines = map(lambda line: line.strip(), proc.stdout) return canonicalizeLines(inputLines) def parseFunctionLine(line): # Function 'TestFixture_Global_TearDown' Lines executed:71.43% of 7" search = re.search("Function '(.*)' Lines executed:(.*)% of (.*)", line, re.IGNORECASE) result = [] if search: functionName = search.group(1) percentage = search.group(2) totalLines = search.group(3) result = { functionName : { "coverage" : float(percentage), "numberOfLines" : int(totalLines) } } return result def parseFileLine(testExecutableDirectoryName, line): # File './../parc_Buffer.c' Lines executed:92.69% of 424 search = re.search("File '(.*)' Lines executed:(.*)% of (.*)", line, re.IGNORECASE) result = { } if search: baseName = os.path.basename(search.group(1)); fileName = os.path.abspath(testExecutableDirectoryName + "/" + baseName) percentage = search.group(2) totalLines = search.group(3) result = { fileName : { "coverage" : float(percentage), "totalLines" : int(totalLines) } } return result def parseCreatingLine(testExecutableDirectoryName, line): search = re.search("(.*):creating '(.*)'", line, re.IGNORECASE) result = None if search: baseName = os.path.basename(search.group(1)); fileName = os.path.abspath(testExecutableDirectoryName + "/" + baseName) baseName = os.path.basename(search.group(2)); gcovFileName = os.path.abspath(testExecutableDirectoryName + "/" + baseName) result = { "fileName" : fileName, "gcovFileName" : gcovFileName, "gcovLines" : FileUtil.readFileLines(gcovFileName) } return result def computeCoverageFromGCovLines(testExecutableDirectoryName, testExecutableFileName, lines): ''' This produces a dictionary consisting of: 'testedFiles' : dictionary containing as keys 'functions' and the name of a file that was tested The value of the key that is the name of a file that was tested is a dictionary containing the keys, 'coverage', 'gcovFileName', and 'gcovLines' 'coverage' is the percentage of code executed 'testedFunctions' is a list containing lists consisting of the function name, the percent executed, and the number of lines in the function. ''' testedFiles = { } testedFunctions = { } gcovFileNames = [] for line in lines: if line.startswith("Function"): element = parseFunctionLine(line) testedFunctions.update(element) elif line.startswith("File"): element = parseFileLine(testExecutableDirectoryName, line) testedFiles.update(element) else: element = parseCreatingLine(testExecutableDirectoryName, line) if element != None: fileName = element["fileName"] del element["fileName"] testedFiles[fileName].update(element) pass result = { testExecutableFileName : { "testedFunctions" : testedFunctions, "testedFiles" : testedFiles } } return result def noCoverage(): result = { "testedFiles" : { }, "testedFunctions" : { } } return result def getCoverage(testExecutableFileName): ''' ''' if testExecutableFileName == None: return None testExecutableFileName = os.path.abspath(testExecutableFileName) testExecutableDirectoryName = os.path.dirname(testExecutableFileName) gcovLines = executeGCovCommand(testExecutableFileName) return computeCoverageFromGCovLines(testExecutableDirectoryName, testExecutableFileName, gcovLines) def selectGreaterCoverage(testedFileA, testedFileB): result = testedFileB if testedFileA["coverage"] >= testedFileB["coverage"]: result = testedFileA return result def computeSummary(filesAndTests, newGCovResults): ''' First, for each target file named in the gcov results, find the corresponding testedFile and report the maximum coverage. If the target file is not in any of the testedFiles { targetFileName : { "coverage": percent, "veracity" : "direct" / "indirect" } } ''' newGCovResults = filter(lambda entry: entry != None, newGCovResults) result = dict() for entry in newGCovResults: for testExecutableName in entry: testExecutableCSourceName = Language_C.Module(testExecutableName).getCSourceName() for testedFileName in entry[testExecutableName]["testedFiles"]: testedFile = entry[testExecutableName]["testedFiles"][testedFileName] if Language_C.Module(testedFileName).getTestExecutableName() == os.path.basename(testExecutableName): result[testedFileName] = testedFile result[testedFileName]["direct"] = "direct" elif testedFileName in result: bestCoverage = selectGreaterCoverage(testedFile, result[testedFileName]) if result[testedFileName] != bestCoverage: result[testedFileName] = bestCoverage result[testedFileName]["direct"] = "indirect" else: result[testedFileName] = testedFile result[testedFileName]["direct"] = "indirect" return result def computeAverage(filesAndTests, gcovResults): summary = computeSuperSummary(filesAndTests, gcovResults) filesToAverage = removeTestSourceFiles(summary) score = 0.0 if len(filesToAverage) > 0: sum = reduce(lambda x, y: x + y, map(lambda entry: summary[entry]["coverage"], filesToAverage)) score = sum / float(len(filesToAverage)) return score if __name__ == '__main__': pp = pprint.PrettyPrinter(indent=4, width=132) if True: gcovResult = getCoverage("/Users/gscott/Documents/workspace/Distillery/Libparc/parc/algol/test/test_parc_JSON") else: lines = sys.stdin.readlines() lines = canonicalizeLines(lines) pp.pprint(lines) gcovResult = computeCoverageFromGCovLines("/Users/gscott/Documents/workspace/Distillery/Libparc/parc/algol/test/", lines) pp.pprint(gcovResult)