You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2364 lines
81 KiB
2364 lines
81 KiB
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmCTestCoverageHandler.h"
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iomanip>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <utility>
|
|
|
|
#include <cmext/algorithm>
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
#include "cmsys/Glob.hxx"
|
|
#include "cmsys/Process.h"
|
|
#include "cmsys/RegularExpression.hxx"
|
|
|
|
#include "cmCTest.h"
|
|
#include "cmDuration.h"
|
|
#include "cmGeneratedFileStream.h"
|
|
#include "cmParseBlanketJSCoverage.h"
|
|
#include "cmParseCacheCoverage.h"
|
|
#include "cmParseCoberturaCoverage.h"
|
|
#include "cmParseDelphiCoverage.h"
|
|
#include "cmParseGTMCoverage.h"
|
|
#include "cmParseJacocoCoverage.h"
|
|
#include "cmParsePHPCoverage.h"
|
|
#include "cmStringAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmWorkingDirectory.h"
|
|
#include "cmXMLWriter.h"
|
|
|
|
class cmMakefile;
|
|
|
|
#define SAFEDIV(x, y) (((y) != 0) ? ((x) / (y)) : (0))
|
|
|
|
class cmCTestRunProcess
|
|
{
|
|
public:
|
|
cmCTestRunProcess()
|
|
{
|
|
this->Process = cmsysProcess_New();
|
|
this->PipeState = -1;
|
|
this->TimeOut = cmDuration(-1);
|
|
}
|
|
~cmCTestRunProcess()
|
|
{
|
|
if (!(this->PipeState == -1) &&
|
|
!(this->PipeState == cmsysProcess_Pipe_None) &&
|
|
!(this->PipeState == cmsysProcess_Pipe_Timeout)) {
|
|
this->WaitForExit();
|
|
}
|
|
cmsysProcess_Delete(this->Process);
|
|
}
|
|
cmCTestRunProcess(const cmCTestRunProcess&) = delete;
|
|
cmCTestRunProcess& operator=(const cmCTestRunProcess&) = delete;
|
|
void SetCommand(const char* command)
|
|
{
|
|
this->CommandLineStrings.clear();
|
|
this->CommandLineStrings.emplace_back(command);
|
|
}
|
|
void AddArgument(const char* arg)
|
|
{
|
|
if (arg) {
|
|
this->CommandLineStrings.emplace_back(arg);
|
|
}
|
|
}
|
|
void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir; }
|
|
void SetTimeout(cmDuration t) { this->TimeOut = t; }
|
|
bool StartProcess()
|
|
{
|
|
std::vector<const char*> args;
|
|
for (std::string const& cl : this->CommandLineStrings) {
|
|
args.push_back(cl.c_str());
|
|
}
|
|
args.push_back(nullptr); // null terminate
|
|
cmsysProcess_SetCommand(this->Process, args.data());
|
|
if (!this->WorkingDirectory.empty()) {
|
|
cmsysProcess_SetWorkingDirectory(this->Process,
|
|
this->WorkingDirectory.c_str());
|
|
}
|
|
|
|
cmsysProcess_SetOption(this->Process, cmsysProcess_Option_HideWindow, 1);
|
|
if (this->TimeOut >= cmDuration::zero()) {
|
|
cmsysProcess_SetTimeout(this->Process, this->TimeOut.count());
|
|
}
|
|
cmsysProcess_Execute(this->Process);
|
|
this->PipeState = cmsysProcess_GetState(this->Process);
|
|
// if the process is running or exited return true
|
|
return this->PipeState == cmsysProcess_State_Executing ||
|
|
this->PipeState == cmsysProcess_State_Exited;
|
|
}
|
|
void SetStdoutFile(const char* fname)
|
|
{
|
|
cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDOUT, fname);
|
|
}
|
|
void SetStderrFile(const char* fname)
|
|
{
|
|
cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDERR, fname);
|
|
}
|
|
int WaitForExit(double* timeout = nullptr)
|
|
{
|
|
this->PipeState = cmsysProcess_WaitForExit(this->Process, timeout);
|
|
return this->PipeState;
|
|
}
|
|
int GetProcessState() const { return this->PipeState; }
|
|
|
|
private:
|
|
int PipeState;
|
|
cmsysProcess* Process;
|
|
std::vector<std::string> CommandLineStrings;
|
|
std::string WorkingDirectory;
|
|
cmDuration TimeOut;
|
|
};
|
|
|
|
cmCTestCoverageHandler::cmCTestCoverageHandler() = default;
|
|
|
|
void cmCTestCoverageHandler::Initialize()
|
|
{
|
|
this->Superclass::Initialize();
|
|
this->CustomCoverageExclude.clear();
|
|
this->SourceLabels.clear();
|
|
this->TargetDirs.clear();
|
|
this->LabelIdMap.clear();
|
|
this->Labels.clear();
|
|
this->LabelFilter.clear();
|
|
}
|
|
|
|
void cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log)
|
|
{
|
|
std::string logGlob =
|
|
cmStrCat(this->CTest->GetCTestConfiguration("BuildDirectory"), "/Testing/",
|
|
this->CTest->GetCurrentTag(), "/CoverageLog*");
|
|
cmsys::Glob gl;
|
|
gl.FindFiles(logGlob);
|
|
std::vector<std::string> const& files = gl.GetFiles();
|
|
for (std::string const& f : files) {
|
|
log << "Removing old coverage log: " << f << "\n";
|
|
cmSystemTools::RemoveFile(f);
|
|
}
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::StartCoverageLogFile(
|
|
cmGeneratedFileStream& covLogFile, int logFileCount)
|
|
{
|
|
char covLogFilename[1024];
|
|
sprintf(covLogFilename, "CoverageLog-%d", logFileCount);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Open file: " << covLogFilename << std::endl,
|
|
this->Quiet);
|
|
if (!this->StartResultingXML(cmCTest::PartCoverage, covLogFilename,
|
|
covLogFile)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open log file: " << covLogFilename << std::endl);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void cmCTestCoverageHandler::EndCoverageLogFile(cmGeneratedFileStream& ostr,
|
|
int logFileCount)
|
|
{
|
|
char covLogFilename[1024];
|
|
sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Close file: " << covLogFilename << std::endl,
|
|
this->Quiet);
|
|
ostr.Close();
|
|
}
|
|
|
|
void cmCTestCoverageHandler::StartCoverageLogXML(cmXMLWriter& xml)
|
|
{
|
|
this->CTest->StartXML(xml, this->AppendXML);
|
|
xml.StartElement("CoverageLog");
|
|
xml.Element("StartDateTime", this->CTest->CurrentTime());
|
|
xml.Element("StartTime", std::chrono::system_clock::now());
|
|
}
|
|
|
|
void cmCTestCoverageHandler::EndCoverageLogXML(cmXMLWriter& xml)
|
|
{
|
|
xml.Element("EndDateTime", this->CTest->CurrentTime());
|
|
xml.Element("EndTime", std::chrono::system_clock::now());
|
|
xml.EndElement(); // CoverageLog
|
|
this->CTest->EndXML(xml);
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::ShouldIDoCoverage(std::string const& file,
|
|
std::string const& srcDir,
|
|
std::string const& binDir)
|
|
{
|
|
if (this->IsFilteredOut(file)) {
|
|
return false;
|
|
}
|
|
|
|
for (cmsys::RegularExpression& rx : this->CustomCoverageExcludeRegex) {
|
|
if (rx.find(file)) {
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" File " << file << " is excluded in CTestCustom.ctest" << std::endl;
|
|
, this->Quiet);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string fSrcDir = cmSystemTools::CollapseFullPath(srcDir);
|
|
std::string fBinDir = cmSystemTools::CollapseFullPath(binDir);
|
|
std::string fFile = cmSystemTools::CollapseFullPath(file);
|
|
bool sourceSubDir = cmSystemTools::IsSubDirectory(fFile, fSrcDir);
|
|
bool buildSubDir = cmSystemTools::IsSubDirectory(fFile, fBinDir);
|
|
// Always check parent directory of the file.
|
|
std::string fileDir = cmSystemTools::GetFilenamePath(fFile);
|
|
std::string checkDir;
|
|
|
|
// We also need to check the binary/source directory pair.
|
|
if (sourceSubDir && buildSubDir) {
|
|
if (fSrcDir.size() > fBinDir.size()) {
|
|
checkDir = fSrcDir;
|
|
} else {
|
|
checkDir = fBinDir;
|
|
}
|
|
} else if (sourceSubDir) {
|
|
checkDir = fSrcDir;
|
|
} else if (buildSubDir) {
|
|
checkDir = fBinDir;
|
|
}
|
|
std::string ndc = cmSystemTools::FileExistsInParentDirectories(
|
|
".NoDartCoverage", fFile, checkDir);
|
|
if (!ndc.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found: " << ndc << " so skip coverage of " << file
|
|
<< std::endl,
|
|
this->Quiet);
|
|
return false;
|
|
}
|
|
|
|
// By now checkDir should be set to parent directory of the file.
|
|
// Get the relative path to the file an apply it to the opposite directory.
|
|
// If it is the same as fileDir, then ignore, otherwise check.
|
|
std::string relPath;
|
|
if (!checkDir.empty()) {
|
|
relPath = cmSystemTools::RelativePath(checkDir, fFile);
|
|
} else {
|
|
relPath = fFile;
|
|
}
|
|
if (checkDir == fSrcDir) {
|
|
checkDir = fBinDir;
|
|
} else {
|
|
checkDir = fSrcDir;
|
|
}
|
|
fFile = checkDir + "/" + relPath;
|
|
fFile = cmSystemTools::GetFilenamePath(fFile);
|
|
|
|
if (fileDir == fFile) {
|
|
// This is in-source build, so we trust the previous check.
|
|
return true;
|
|
}
|
|
|
|
ndc = cmSystemTools::FileExistsInParentDirectories(".NoDartCoverage", fFile,
|
|
checkDir);
|
|
if (!ndc.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found: " << ndc << " so skip coverage of: " << file
|
|
<< std::endl,
|
|
this->Quiet);
|
|
return false;
|
|
}
|
|
// Ok, nothing in source tree, nothing in binary tree
|
|
return true;
|
|
}
|
|
|
|
// clearly it would be nice if this were broken up into a few smaller
|
|
// functions and commented...
|
|
int cmCTestCoverageHandler::ProcessHandler()
|
|
{
|
|
this->CTest->ClearSubmitFiles(cmCTest::PartCoverage);
|
|
int error = 0;
|
|
// do we have time for this
|
|
if (this->CTest->GetRemainingTimeAllowed() < std::chrono::minutes(2)) {
|
|
return error;
|
|
}
|
|
|
|
std::string coverage_start_time = this->CTest->CurrentTime();
|
|
auto coverage_start_time_time = std::chrono::system_clock::now();
|
|
std::string sourceDir =
|
|
this->CTest->GetCTestConfiguration("SourceDirectory");
|
|
std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory");
|
|
|
|
if (binaryDir.empty()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Binary directory is not set. "
|
|
"No coverage checking will be performed."
|
|
<< std::endl);
|
|
return 0;
|
|
}
|
|
this->LoadLabels();
|
|
|
|
cmGeneratedFileStream ofs;
|
|
auto elapsed_time_start = std::chrono::steady_clock::now();
|
|
if (!this->StartLogFile("Coverage", ofs)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot create LastCoverage.log file" << std::endl);
|
|
}
|
|
|
|
ofs << "Performing coverage: "
|
|
<< elapsed_time_start.time_since_epoch().count() << std::endl;
|
|
this->CleanCoverageLogFiles(ofs);
|
|
|
|
cmSystemTools::ConvertToUnixSlashes(sourceDir);
|
|
cmSystemTools::ConvertToUnixSlashes(binaryDir);
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
"Performing coverage" << std::endl, this->Quiet);
|
|
|
|
cmCTestCoverageHandlerContainer cont;
|
|
cont.Error = error;
|
|
cont.SourceDir = sourceDir;
|
|
cont.BinaryDir = binaryDir;
|
|
cont.OFS = &ofs;
|
|
cont.Quiet = this->Quiet;
|
|
|
|
// setup the regex exclude stuff
|
|
this->CustomCoverageExcludeRegex.clear();
|
|
for (std::string const& rex : this->CustomCoverageExclude) {
|
|
this->CustomCoverageExcludeRegex.emplace_back(rex);
|
|
}
|
|
|
|
if (this->HandleBullseyeCoverage(&cont)) {
|
|
return cont.Error;
|
|
}
|
|
int file_count = 0;
|
|
file_count += this->HandleGCovCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
file_count += this->HandleLCovCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
file_count += this->HandleTracePyCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
file_count += this->HandlePHPCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
file_count += this->HandleCoberturaCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
|
|
file_count += this->HandleMumpsCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
|
|
file_count += this->HandleJacocoCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
|
|
file_count += this->HandleBlanketJSCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
|
|
file_count += this->HandleDelphiCoverage(&cont);
|
|
error = cont.Error;
|
|
if (file_count < 0) {
|
|
return error;
|
|
}
|
|
std::set<std::string> uncovered = this->FindUncoveredFiles(&cont);
|
|
|
|
if (file_count == 0 && this->ExtraCoverageGlobs.empty()) {
|
|
cmCTestOptionalLog(
|
|
this->CTest, WARNING,
|
|
" Cannot find any coverage files. Ignoring Coverage request."
|
|
<< std::endl,
|
|
this->Quiet);
|
|
return error;
|
|
}
|
|
cmGeneratedFileStream covSumFile;
|
|
cmGeneratedFileStream covLogFile;
|
|
cmXMLWriter covSumXML(covSumFile);
|
|
cmXMLWriter covLogXML(covLogFile);
|
|
|
|
if (!this->StartResultingXML(cmCTest::PartCoverage, "Coverage",
|
|
covSumFile)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open coverage summary file." << std::endl);
|
|
return -1;
|
|
}
|
|
covSumFile.setf(std::ios::fixed, std::ios::floatfield);
|
|
covSumFile.precision(2);
|
|
|
|
this->CTest->StartXML(covSumXML, this->AppendXML);
|
|
// Produce output xml files
|
|
|
|
covSumXML.StartElement("Coverage");
|
|
covSumXML.Element("StartDateTime", coverage_start_time);
|
|
covSumXML.Element("StartTime", coverage_start_time_time);
|
|
int logFileCount = 0;
|
|
if (!this->StartCoverageLogFile(covLogFile, logFileCount)) {
|
|
return -1;
|
|
}
|
|
this->StartCoverageLogXML(covLogXML);
|
|
int cnt = 0;
|
|
long total_tested = 0;
|
|
long total_untested = 0;
|
|
// std::string fullSourceDir = sourceDir + "/";
|
|
// std::string fullBinaryDir = binaryDir + "/";
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl, this->Quiet);
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_OUTPUT,
|
|
" Accumulating results (each . represents one file):" << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
|
|
std::vector<std::string> errorsWhileAccumulating;
|
|
|
|
file_count = 0;
|
|
for (auto const& file : cont.TotalCoverage) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
|
|
this->Quiet);
|
|
file_count++;
|
|
if (file_count % 50 == 0) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
" processed: " << file_count << " out of "
|
|
<< cont.TotalCoverage.size()
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
}
|
|
|
|
const std::string fullFileName = file.first;
|
|
bool shouldIDoCoverage =
|
|
this->ShouldIDoCoverage(fullFileName, sourceDir, binaryDir);
|
|
if (!shouldIDoCoverage) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
".NoDartCoverage found, so skip coverage check for: "
|
|
<< fullFileName << std::endl,
|
|
this->Quiet);
|
|
continue;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Process file: " << fullFileName << std::endl,
|
|
this->Quiet);
|
|
|
|
if (!cmSystemTools::FileExists(fullFileName)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot find file: " << fullFileName << std::endl);
|
|
continue;
|
|
}
|
|
|
|
if (++cnt % 100 == 0) {
|
|
this->EndCoverageLogXML(covLogXML);
|
|
this->EndCoverageLogFile(covLogFile, logFileCount);
|
|
logFileCount++;
|
|
if (!this->StartCoverageLogFile(covLogFile, logFileCount)) {
|
|
return -1;
|
|
}
|
|
this->StartCoverageLogXML(covLogXML);
|
|
}
|
|
|
|
const std::string fileName = cmSystemTools::GetFilenameName(fullFileName);
|
|
const std::string shortFileName =
|
|
this->CTest->GetShortPathToFile(fullFileName);
|
|
const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov =
|
|
file.second;
|
|
covLogXML.StartElement("File");
|
|
covLogXML.Attribute("Name", fileName);
|
|
covLogXML.Attribute("FullPath", shortFileName);
|
|
covLogXML.StartElement("Report");
|
|
|
|
cmsys::ifstream ifs(fullFileName.c_str());
|
|
if (!ifs) {
|
|
std::ostringstream ostr;
|
|
ostr << "Cannot open source file: " << fullFileName;
|
|
errorsWhileAccumulating.push_back(ostr.str());
|
|
error++;
|
|
continue;
|
|
}
|
|
|
|
int tested = 0;
|
|
int untested = 0;
|
|
|
|
cmCTestCoverageHandlerContainer::SingleFileCoverageVector::size_type cc;
|
|
std::string line;
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Actually performing coverage for: " << fullFileName
|
|
<< std::endl,
|
|
this->Quiet);
|
|
for (cc = 0; cc < fcov.size(); cc++) {
|
|
if (!cmSystemTools::GetLineFromStream(ifs, line) &&
|
|
cc != fcov.size() - 1) {
|
|
std::ostringstream ostr;
|
|
ostr << "Problem reading source file: " << fullFileName
|
|
<< " line:" << cc << " out total: " << fcov.size() - 1;
|
|
errorsWhileAccumulating.push_back(ostr.str());
|
|
error++;
|
|
break;
|
|
}
|
|
covLogXML.StartElement("Line");
|
|
covLogXML.Attribute("Number", cc);
|
|
covLogXML.Attribute("Count", fcov[cc]);
|
|
covLogXML.Content(line);
|
|
covLogXML.EndElement(); // Line
|
|
if (fcov[cc] == 0) {
|
|
untested++;
|
|
} else if (fcov[cc] > 0) {
|
|
tested++;
|
|
}
|
|
}
|
|
if (cmSystemTools::GetLineFromStream(ifs, line)) {
|
|
std::ostringstream ostr;
|
|
ostr << "Looks like there are more lines in the file: " << fullFileName;
|
|
errorsWhileAccumulating.push_back(ostr.str());
|
|
}
|
|
float cper = 0;
|
|
float cmet = 0;
|
|
if (tested + untested > 0) {
|
|
cper = (100 *
|
|
SAFEDIV(static_cast<float>(tested),
|
|
static_cast<float>(tested + untested)));
|
|
cmet = (SAFEDIV(static_cast<float>(tested + 10),
|
|
static_cast<float>(tested + untested + 10)));
|
|
}
|
|
total_tested += tested;
|
|
total_untested += untested;
|
|
covLogXML.EndElement(); // Report
|
|
covLogXML.EndElement(); // File
|
|
covSumXML.StartElement("File");
|
|
covSumXML.Attribute("Name", fileName);
|
|
covSumXML.Attribute("FullPath",
|
|
this->CTest->GetShortPathToFile(fullFileName));
|
|
covSumXML.Attribute("Covered", tested + untested > 0 ? "true" : "false");
|
|
covSumXML.Element("LOCTested", tested);
|
|
covSumXML.Element("LOCUnTested", untested);
|
|
covSumXML.Element("PercentCoverage", cper);
|
|
covSumXML.Element("CoverageMetric", cmet);
|
|
this->WriteXMLLabels(covSumXML, shortFileName);
|
|
covSumXML.EndElement(); // File
|
|
}
|
|
|
|
// Handle all the files in the extra coverage globs that have no cov data
|
|
for (std::string const& u : uncovered) {
|
|
std::string fileName = cmSystemTools::GetFilenameName(u);
|
|
std::string fullPath = cont.SourceDir + "/" + u;
|
|
|
|
covLogXML.StartElement("File");
|
|
covLogXML.Attribute("Name", fileName);
|
|
covLogXML.Attribute("FullPath", u);
|
|
covLogXML.StartElement("Report");
|
|
|
|
cmsys::ifstream ifs(fullPath.c_str());
|
|
if (!ifs) {
|
|
std::ostringstream ostr;
|
|
ostr << "Cannot open source file: " << fullPath;
|
|
errorsWhileAccumulating.push_back(ostr.str());
|
|
error++;
|
|
covLogXML.EndElement(); // Report
|
|
covLogXML.EndElement(); // File
|
|
continue;
|
|
}
|
|
int untested = 0;
|
|
std::string line;
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Actually performing coverage for: " << u << std::endl,
|
|
this->Quiet);
|
|
while (cmSystemTools::GetLineFromStream(ifs, line)) {
|
|
covLogXML.StartElement("Line");
|
|
covLogXML.Attribute("Number", untested);
|
|
covLogXML.Attribute("Count", 0);
|
|
covLogXML.Content(line);
|
|
covLogXML.EndElement(); // Line
|
|
untested++;
|
|
}
|
|
covLogXML.EndElement(); // Report
|
|
covLogXML.EndElement(); // File
|
|
|
|
total_untested += untested;
|
|
covSumXML.StartElement("File");
|
|
covSumXML.Attribute("Name", fileName);
|
|
covSumXML.Attribute("FullPath", u);
|
|
covSumXML.Attribute("Covered", "true");
|
|
covSumXML.Element("LOCTested", 0);
|
|
covSumXML.Element("LOCUnTested", untested);
|
|
covSumXML.Element("PercentCoverage", 0);
|
|
covSumXML.Element("CoverageMetric", 0);
|
|
this->WriteXMLLabels(covSumXML, u);
|
|
covSumXML.EndElement(); // File
|
|
}
|
|
|
|
this->EndCoverageLogXML(covLogXML);
|
|
this->EndCoverageLogFile(covLogFile, logFileCount);
|
|
|
|
if (!errorsWhileAccumulating.empty()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl);
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error(s) while accumulating results:" << std::endl);
|
|
for (std::string const& er : errorsWhileAccumulating) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, " " << er << std::endl);
|
|
}
|
|
}
|
|
|
|
long total_lines = total_tested + total_untested;
|
|
float percent_coverage = 100 *
|
|
SAFEDIV(static_cast<float>(total_tested), static_cast<float>(total_lines));
|
|
if (total_lines == 0) {
|
|
percent_coverage = 0;
|
|
}
|
|
|
|
std::string end_time = this->CTest->CurrentTime();
|
|
|
|
covSumXML.Element("LOCTested", total_tested);
|
|
covSumXML.Element("LOCUntested", total_untested);
|
|
covSumXML.Element("LOC", total_lines);
|
|
covSumXML.Element("PercentCoverage", percent_coverage);
|
|
covSumXML.Element("EndDateTime", end_time);
|
|
covSumXML.Element("EndTime", std::chrono::system_clock::now());
|
|
covSumXML.Element("ElapsedMinutes",
|
|
std::chrono::duration_cast<std::chrono::minutes>(
|
|
std::chrono::steady_clock::now() - elapsed_time_start)
|
|
.count());
|
|
covSumXML.EndElement(); // Coverage
|
|
this->CTest->EndXML(covSumXML);
|
|
|
|
cmCTestLog(this->CTest, HANDLER_OUTPUT,
|
|
"" << std::endl
|
|
<< "\tCovered LOC: " << total_tested << std::endl
|
|
<< "\tNot covered LOC: " << total_untested << std::endl
|
|
<< "\tTotal LOC: " << total_lines << std::endl
|
|
<< "\tPercentage Coverage: "
|
|
<< std::setiosflags(std::ios::fixed) << std::setprecision(2)
|
|
<< (percent_coverage) << "%" << std::endl);
|
|
|
|
ofs << "\tCovered LOC: " << total_tested << std::endl
|
|
<< "\tNot covered LOC: " << total_untested << std::endl
|
|
<< "\tTotal LOC: " << total_lines << std::endl
|
|
<< "\tPercentage Coverage: " << std::setiosflags(std::ios::fixed)
|
|
<< std::setprecision(2) << (percent_coverage) << "%" << std::endl;
|
|
|
|
if (error) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile* mf)
|
|
{
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Add coverage exclude regular expressions." << std::endl,
|
|
this->Quiet);
|
|
this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_COVERAGE_EXCLUDE",
|
|
this->CustomCoverageExclude);
|
|
this->CTest->PopulateCustomVector(mf, "CTEST_EXTRA_COVERAGE_GLOB",
|
|
this->ExtraCoverageGlobs);
|
|
for (std::string const& cce : this->CustomCoverageExclude) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Add coverage exclude: " << cce << std::endl,
|
|
this->Quiet);
|
|
}
|
|
for (std::string const& ecg : this->ExtraCoverageGlobs) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Add coverage glob: " << ecg << std::endl,
|
|
this->Quiet);
|
|
}
|
|
}
|
|
|
|
// Fix for issue #4971 where the case of the drive letter component of
|
|
// the filenames might be different when analyzing gcov output.
|
|
//
|
|
// Compare file names: fnc(fn1) == fnc(fn2) // fnc == file name compare
|
|
//
|
|
#ifdef _WIN32
|
|
# define fnc(s) cmSystemTools::LowerCase(s)
|
|
# define fnc_prefix(s, t) fnc(s.substr(0, t.size())) == fnc(t)
|
|
#else
|
|
# define fnc_prefix(s, t) cmHasPrefix(s, t)
|
|
#endif
|
|
|
|
bool IsFileInDir(const std::string& infile, const std::string& indir)
|
|
{
|
|
std::string file = cmSystemTools::CollapseFullPath(infile);
|
|
std::string dir = cmSystemTools::CollapseFullPath(indir);
|
|
|
|
return file.size() > dir.size() && fnc_prefix(file, dir) &&
|
|
file[dir.size()] == '/';
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandlePHPCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmParsePHPCoverage cov(*cont, this->CTest);
|
|
std::string coverageDir = this->CTest->GetBinaryDir() + "/xdebugCoverage";
|
|
if (cmSystemTools::FileIsDirectory(coverageDir)) {
|
|
cov.ReadPHPCoverageDirectory(coverageDir.c_str());
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleCoberturaCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmParseCoberturaCoverage cov(*cont, this->CTest);
|
|
|
|
// Assume the coverage.xml is in the binary directory
|
|
// check for the COBERTURADIR environment variable,
|
|
// if it doesn't exist or is empty, assume the
|
|
// binary directory is used.
|
|
std::string coverageXMLFile;
|
|
if (!cmSystemTools::GetEnv("COBERTURADIR", coverageXMLFile) ||
|
|
coverageXMLFile.empty()) {
|
|
coverageXMLFile = this->CTest->GetBinaryDir();
|
|
}
|
|
// build the find file string with the directory from above
|
|
coverageXMLFile += "/coverage.xml";
|
|
|
|
if (cmSystemTools::FileExists(coverageXMLFile)) {
|
|
// If file exists, parse it
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Parsing Cobertura XML file: " << coverageXMLFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cov.ReadCoverageXML(coverageXMLFile.c_str());
|
|
} else {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find Cobertura XML file: " << coverageXMLFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleMumpsCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
// try gtm coverage
|
|
cmParseGTMCoverage cov(*cont, this->CTest);
|
|
std::string coverageFile =
|
|
this->CTest->GetBinaryDir() + "/gtm_coverage.mcov";
|
|
if (cmSystemTools::FileExists(coverageFile)) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Parsing Cache Coverage: " << coverageFile << std::endl,
|
|
this->Quiet);
|
|
cov.ReadCoverageFile(coverageFile.c_str());
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find GTM coverage file: " << coverageFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cmParseCacheCoverage ccov(*cont, this->CTest);
|
|
coverageFile = this->CTest->GetBinaryDir() + "/cache_coverage.cmcov";
|
|
if (cmSystemTools::FileExists(coverageFile)) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Parsing Cache Coverage: " << coverageFile << std::endl,
|
|
this->Quiet);
|
|
ccov.ReadCoverageFile(coverageFile.c_str());
|
|
} else {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find Cache coverage file: " << coverageFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
|
|
struct cmCTestCoverageHandlerLocale
|
|
{
|
|
cmCTestCoverageHandlerLocale()
|
|
{
|
|
std::string l;
|
|
if (cmSystemTools::GetEnv("LC_ALL", l)) {
|
|
this->lc_all = l;
|
|
}
|
|
if (this->lc_all != "C") {
|
|
cmSystemTools::PutEnv("LC_ALL=C");
|
|
}
|
|
}
|
|
~cmCTestCoverageHandlerLocale()
|
|
{
|
|
if (!this->lc_all.empty()) {
|
|
cmSystemTools::PutEnv("LC_ALL=" + this->lc_all);
|
|
} else {
|
|
cmSystemTools::UnsetEnv("LC_ALL");
|
|
}
|
|
}
|
|
cmCTestCoverageHandlerLocale(const cmCTestCoverageHandlerLocale&) = delete;
|
|
cmCTestCoverageHandlerLocale& operator=(
|
|
const cmCTestCoverageHandlerLocale&) = delete;
|
|
std::string lc_all;
|
|
};
|
|
|
|
int cmCTestCoverageHandler::HandleJacocoCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmParseJacocoCoverage cov = cmParseJacocoCoverage(*cont, this->CTest);
|
|
|
|
// Search in the source directory.
|
|
cmsys::Glob g1;
|
|
std::vector<std::string> files;
|
|
g1.SetRecurse(true);
|
|
|
|
std::string SourceDir =
|
|
this->CTest->GetCTestConfiguration("SourceDirectory");
|
|
std::string coverageFile = SourceDir + "/*jacoco.xml";
|
|
|
|
g1.FindFiles(coverageFile);
|
|
files = g1.GetFiles();
|
|
|
|
// ...and in the binary directory.
|
|
cmsys::Glob g2;
|
|
g2.SetRecurse(true);
|
|
std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory");
|
|
std::string binCoverageFile = binaryDir + "/*jacoco.xml";
|
|
g2.FindFiles(binCoverageFile);
|
|
cm::append(files, g2.GetFiles());
|
|
|
|
if (!files.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found Jacoco Files, Performing Coverage" << std::endl,
|
|
this->Quiet);
|
|
cov.LoadCoverageData(files);
|
|
} else {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find Jacoco coverage files: " << coverageFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleDelphiCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmParseDelphiCoverage cov = cmParseDelphiCoverage(*cont, this->CTest);
|
|
cmsys::Glob g;
|
|
std::vector<std::string> files;
|
|
g.SetRecurse(true);
|
|
|
|
std::string BinDir = this->CTest->GetBinaryDir();
|
|
std::string coverageFile = BinDir + "/*(*.pas).html";
|
|
|
|
g.FindFiles(coverageFile);
|
|
files = g.GetFiles();
|
|
if (!files.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found Delphi HTML Files, Performing Coverage"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cov.LoadCoverageData(files);
|
|
} else {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find Delphi coverage files: " << coverageFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
|
|
static std::string joinCommandLine(const std::vector<std::string>& args)
|
|
{
|
|
std::string ret;
|
|
|
|
for (std::string const& s : args) {
|
|
if (s.find(' ') == std::string::npos) {
|
|
ret += s + ' ';
|
|
} else {
|
|
ret += "\"" + s + "\" ";
|
|
}
|
|
}
|
|
|
|
// drop trailing whitespace
|
|
ret.erase(ret.size() - 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleBlanketJSCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmParseBlanketJSCoverage cov = cmParseBlanketJSCoverage(*cont, this->CTest);
|
|
std::string SourceDir =
|
|
this->CTest->GetCTestConfiguration("SourceDirectory");
|
|
|
|
// Look for something other than output.json, still JSON extension.
|
|
std::string coverageFile = SourceDir + "/*.json";
|
|
cmsys::Glob g;
|
|
std::vector<std::string> files;
|
|
std::vector<std::string> blanketFiles;
|
|
g.FindFiles(coverageFile);
|
|
files = g.GetFiles();
|
|
// Ensure that the JSON files found are the result of the
|
|
// Blanket.js output. Check for the "node-jscoverage"
|
|
// string on the second line
|
|
std::string line;
|
|
for (std::string const& fileEntry : files) {
|
|
cmsys::ifstream in(fileEntry.c_str());
|
|
cmSystemTools::GetLineFromStream(in, line);
|
|
cmSystemTools::GetLineFromStream(in, line);
|
|
if (line.find("node-jscoverage") != std::string::npos) {
|
|
blanketFiles.push_back(fileEntry);
|
|
}
|
|
}
|
|
// Take all files with the node-jscoverage string and parse those
|
|
if (!blanketFiles.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found BlanketJS output JSON, Performing Coverage"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cov.LoadCoverageData(files);
|
|
} else {
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find BlanketJS coverage files: " << coverageFile << std::endl,
|
|
this->Quiet);
|
|
}
|
|
return static_cast<int>(cont->TotalCoverage.size());
|
|
}
|
|
int cmCTestCoverageHandler::HandleGCovCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
std::string gcovCommand =
|
|
this->CTest->GetCTestConfiguration("CoverageCommand");
|
|
if (gcovCommand.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Could not find gcov." << std::endl, this->Quiet);
|
|
return 0;
|
|
}
|
|
std::string gcovExtraFlags =
|
|
this->CTest->GetCTestConfiguration("CoverageExtraFlags");
|
|
|
|
// Immediately skip to next coverage option since codecov is only for Intel
|
|
// compiler
|
|
if (gcovCommand == "codecov") {
|
|
return 0;
|
|
}
|
|
|
|
// Style 1
|
|
std::string st1gcovOutputRex1 =
|
|
"[0-9]+\\.[0-9]+% of [0-9]+ (source |)lines executed in file (.*)$";
|
|
std::string st1gcovOutputRex2 = "^Creating (.*\\.gcov)\\.";
|
|
cmsys::RegularExpression st1re1(st1gcovOutputRex1.c_str());
|
|
cmsys::RegularExpression st1re2(st1gcovOutputRex2.c_str());
|
|
|
|
// Style 2
|
|
std::string st2gcovOutputRex1 = "^File *[`'](.*)'$";
|
|
std::string st2gcovOutputRex2 =
|
|
"Lines executed: *[0-9]+\\.[0-9]+% of [0-9]+$";
|
|
std::string st2gcovOutputRex3 = "^(.*)reating [`'](.*\\.gcov)'";
|
|
std::string st2gcovOutputRex4 = "^(.*):unexpected EOF *$";
|
|
std::string st2gcovOutputRex5 = "^(.*):cannot open source file*$";
|
|
std::string st2gcovOutputRex6 =
|
|
"^(.*):source file is newer than graph file `(.*)'$";
|
|
cmsys::RegularExpression st2re1(st2gcovOutputRex1.c_str());
|
|
cmsys::RegularExpression st2re2(st2gcovOutputRex2.c_str());
|
|
cmsys::RegularExpression st2re3(st2gcovOutputRex3.c_str());
|
|
cmsys::RegularExpression st2re4(st2gcovOutputRex4.c_str());
|
|
cmsys::RegularExpression st2re5(st2gcovOutputRex5.c_str());
|
|
cmsys::RegularExpression st2re6(st2gcovOutputRex6.c_str());
|
|
|
|
std::vector<std::string> files;
|
|
this->FindGCovFiles(files);
|
|
|
|
if (files.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find any GCov coverage files." << std::endl,
|
|
this->Quiet);
|
|
// No coverage files is a valid thing, so the exit code is 0
|
|
return 0;
|
|
}
|
|
|
|
std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
|
|
std::string tempDir = testingDir + "/CoverageInfo";
|
|
if (!cmSystemTools::MakeDirectory(tempDir)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unable to make directory: " << tempDir << std::endl);
|
|
cont->Error++;
|
|
return 0;
|
|
}
|
|
cmWorkingDirectory workdir(tempDir);
|
|
|
|
int gcovStyle = 0;
|
|
|
|
std::set<std::string> missingFiles;
|
|
|
|
std::string actualSourceFile;
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_OUTPUT,
|
|
" Processing coverage (each . represents one file):" << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
int file_count = 0;
|
|
|
|
// make sure output from gcov is in English!
|
|
cmCTestCoverageHandlerLocale locale_C;
|
|
static_cast<void>(locale_C);
|
|
|
|
std::vector<std::string> basecovargs =
|
|
cmSystemTools::ParseArguments(gcovExtraFlags);
|
|
basecovargs.insert(basecovargs.begin(), gcovCommand);
|
|
basecovargs.emplace_back("-o");
|
|
|
|
// files is a list of *.da and *.gcda files with coverage data in them.
|
|
// These are binary files that you give as input to gcov so that it will
|
|
// give us text output we can analyze to summarize coverage.
|
|
//
|
|
for (std::string const& f : files) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
|
|
this->Quiet);
|
|
|
|
// Call gcov to get coverage data for this *.gcda file:
|
|
//
|
|
std::string fileDir = cmSystemTools::GetFilenamePath(f);
|
|
std::vector<std::string> covargs = basecovargs;
|
|
covargs.push_back(fileDir);
|
|
covargs.push_back(f);
|
|
const std::string command = joinCommandLine(covargs);
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
command << std::endl, this->Quiet);
|
|
|
|
std::string output;
|
|
std::string errors;
|
|
int retVal = 0;
|
|
*cont->OFS << "* Run coverage for: " << fileDir << std::endl;
|
|
*cont->OFS << " Command: " << command << std::endl;
|
|
int res = this->CTest->RunCommand(covargs, &output, &errors, &retVal,
|
|
tempDir.c_str(),
|
|
cmDuration::zero() /*this->TimeOut*/);
|
|
|
|
*cont->OFS << " Output: " << output << std::endl;
|
|
*cont->OFS << " Errors: " << errors << std::endl;
|
|
if (!res) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Problem running coverage on file: " << f << std::endl);
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Command produced error: " << errors << std::endl);
|
|
cont->Error++;
|
|
continue;
|
|
}
|
|
if (retVal != 0) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Coverage command returned: "
|
|
<< retVal << " while processing: " << f << std::endl);
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Command produced error: " << cont->Error << std::endl);
|
|
}
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"--------------------------------------------------------------"
|
|
<< std::endl
|
|
<< output << std::endl
|
|
<< "--------------------------------------------------------------"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
|
|
std::vector<std::string> lines;
|
|
cmsys::SystemTools::Split(output, lines);
|
|
|
|
for (std::string const& line : lines) {
|
|
std::string sourceFile;
|
|
std::string gcovFile;
|
|
|
|
cmCTestOptionalLog(this->CTest, DEBUG,
|
|
"Line: [" << line << "]" << std::endl, this->Quiet);
|
|
|
|
if (line.empty()) {
|
|
// Ignore empty line; probably style 2
|
|
} else if (st1re1.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 1;
|
|
}
|
|
if (gcovStyle != 1) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e1" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
actualSourceFile.clear();
|
|
sourceFile = st1re1.match(2);
|
|
} else if (st1re2.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 1;
|
|
}
|
|
if (gcovStyle != 1) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e2" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
gcovFile = st1re2.match(1);
|
|
} else if (st2re1.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e3" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
actualSourceFile.clear();
|
|
sourceFile = st2re1.match(1);
|
|
} else if (st2re2.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e4" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
} else if (st2re3.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e5" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
gcovFile = st2re3.match(2);
|
|
} else if (st2re4.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e6" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, WARNING,
|
|
"Warning: " << st2re4.match(1)
|
|
<< " had unexpected EOF" << std::endl,
|
|
this->Quiet);
|
|
} else if (st2re5.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e7" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, WARNING,
|
|
"Warning: Cannot open file: " << st2re5.match(1)
|
|
<< std::endl,
|
|
this->Quiet);
|
|
} else if (st2re6.find(line)) {
|
|
if (gcovStyle == 0) {
|
|
gcovStyle = 2;
|
|
}
|
|
if (gcovStyle != 2) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output style e8" << std::endl);
|
|
cont->Error++;
|
|
break;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, WARNING,
|
|
"Warning: File: " << st2re6.match(1)
|
|
<< " is newer than "
|
|
<< st2re6.match(2) << std::endl,
|
|
this->Quiet);
|
|
} else {
|
|
// gcov 4.7 can have output lines saying "No executable lines" and
|
|
// "Removing 'filename.gcov'"... Don't log those as "errors."
|
|
if (line != "No executable lines" &&
|
|
!cmHasLiteralPrefix(line, "Removing ")) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unknown gcov output line: [" << line << "]"
|
|
<< std::endl);
|
|
cont->Error++;
|
|
// abort();
|
|
}
|
|
}
|
|
|
|
// If the last line of gcov output gave us a valid value for gcovFile,
|
|
// and we have an actualSourceFile, then insert a (or add to existing)
|
|
// SingleFileCoverageVector for actualSourceFile:
|
|
//
|
|
if (!gcovFile.empty() && !actualSourceFile.empty()) {
|
|
cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec =
|
|
cont->TotalCoverage[actualSourceFile];
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" in gcovFile: " << gcovFile << std::endl,
|
|
this->Quiet);
|
|
|
|
cmsys::ifstream ifile(gcovFile.c_str());
|
|
if (!ifile) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open file: " << gcovFile << std::endl);
|
|
} else {
|
|
long cnt = -1;
|
|
std::string nl;
|
|
while (cmSystemTools::GetLineFromStream(ifile, nl)) {
|
|
cnt++;
|
|
|
|
// Skip empty lines
|
|
if (nl.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Skip unused lines
|
|
if (nl.size() < 12) {
|
|
continue;
|
|
}
|
|
|
|
// Handle gcov 3.0 non-coverage lines
|
|
// non-coverage lines seem to always start with something not
|
|
// a space and don't have a ':' in the 9th position
|
|
// TODO: Verify that this is actually a robust metric
|
|
if (nl[0] != ' ' && nl[9] != ':') {
|
|
continue;
|
|
}
|
|
|
|
// Read the coverage count from the beginning of the gcov output
|
|
// line
|
|
std::string prefix = nl.substr(0, 12);
|
|
int cov = atoi(prefix.c_str());
|
|
|
|
// Read the line number starting at the 10th character of the gcov
|
|
// output line
|
|
std::string lineNumber = nl.substr(10, 5);
|
|
|
|
int lineIdx = atoi(lineNumber.c_str()) - 1;
|
|
if (lineIdx >= 0) {
|
|
while (vec.size() <= static_cast<size_t>(lineIdx)) {
|
|
vec.push_back(-1);
|
|
}
|
|
|
|
// Initially all entries are -1 (not used). If we get coverage
|
|
// information, increment it to 0 first.
|
|
if (vec[lineIdx] < 0) {
|
|
if (cov > 0 || prefix.find('#') != std::string::npos) {
|
|
vec[lineIdx] = 0;
|
|
}
|
|
}
|
|
|
|
vec[lineIdx] += cov;
|
|
}
|
|
}
|
|
}
|
|
|
|
actualSourceFile.clear();
|
|
}
|
|
|
|
if (!sourceFile.empty() && actualSourceFile.empty()) {
|
|
gcovFile.clear();
|
|
|
|
// Is it in the source dir or the binary dir?
|
|
//
|
|
if (IsFileInDir(sourceFile, cont->SourceDir)) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" produced s: " << sourceFile << std::endl,
|
|
this->Quiet);
|
|
*cont->OFS << " produced in source dir: " << sourceFile
|
|
<< std::endl;
|
|
actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile);
|
|
} else if (IsFileInDir(sourceFile, cont->BinaryDir)) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" produced b: " << sourceFile << std::endl,
|
|
this->Quiet);
|
|
*cont->OFS << " produced in binary dir: " << sourceFile
|
|
<< std::endl;
|
|
actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile);
|
|
}
|
|
|
|
if (actualSourceFile.empty()) {
|
|
if (missingFiles.find(sourceFile) == missingFiles.end()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Something went wrong" << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Cannot find file: [" << sourceFile << "]"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" in source dir: [" << cont->SourceDir << "]"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" or binary dir: [" << cont->BinaryDir.size()
|
|
<< "]" << std::endl,
|
|
this->Quiet);
|
|
*cont->OFS << " Something went wrong. Cannot find file: "
|
|
<< sourceFile << " in source dir: " << cont->SourceDir
|
|
<< " or binary dir: " << cont->BinaryDir << std::endl;
|
|
|
|
missingFiles.insert(sourceFile);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
file_count++;
|
|
|
|
if (file_count % 50 == 0) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
" processed: " << file_count << " out of "
|
|
<< files.size() << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
}
|
|
}
|
|
|
|
return file_count;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleLCovCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
std::string lcovCommand =
|
|
this->CTest->GetCTestConfiguration("CoverageCommand");
|
|
std::string lcovExtraFlags =
|
|
this->CTest->GetCTestConfiguration("CoverageExtraFlags");
|
|
if (lcovCommand != "codecov") {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Not a valid Intel Coverage command." << std::endl,
|
|
this->Quiet);
|
|
return 0;
|
|
}
|
|
// There is only percentage completed output from LCOV
|
|
std::string st2lcovOutputRex3 = "[0-9]+%";
|
|
cmsys::RegularExpression st2re3(st2lcovOutputRex3.c_str());
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" This is coverage command: " << lcovCommand << std::endl,
|
|
this->Quiet);
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" These are coverage command flags: " << lcovExtraFlags
|
|
<< std::endl,
|
|
this->Quiet);
|
|
|
|
std::vector<std::string> files;
|
|
if (!this->FindLCovFiles(files)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error while finding LCov files.\n");
|
|
return 0;
|
|
}
|
|
|
|
if (files.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find any LCov coverage files." << std::endl,
|
|
this->Quiet);
|
|
// No coverage files is a valid thing, so the exit code is 0
|
|
return 0;
|
|
}
|
|
std::string testingDir = this->CTest->GetBinaryDir();
|
|
|
|
std::set<std::string> missingFiles;
|
|
|
|
std::string actualSourceFile;
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_OUTPUT,
|
|
" Processing coverage (each . represents one file):" << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
int file_count = 0;
|
|
|
|
// make sure output from lcov is in English!
|
|
cmCTestCoverageHandlerLocale locale_C;
|
|
static_cast<void>(locale_C);
|
|
|
|
std::vector<std::string> covargs =
|
|
cmSystemTools::ParseArguments(lcovExtraFlags);
|
|
covargs.insert(covargs.begin(), lcovCommand);
|
|
const std::string command = joinCommandLine(covargs);
|
|
|
|
// In intel compiler we have to call codecov only once in each executable
|
|
// directory. It collects all *.dyn files to generate .dpi file.
|
|
for (std::string const& f : files) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
|
|
this->Quiet);
|
|
std::string fileDir = cmSystemTools::GetFilenamePath(f);
|
|
cmWorkingDirectory workdir(fileDir);
|
|
if (workdir.Failed()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unable to change working directory to "
|
|
<< fileDir << " : "
|
|
<< std::strerror(workdir.GetLastResult()) << std::endl);
|
|
cont->Error++;
|
|
continue;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Current coverage dir: " << fileDir << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
command << std::endl, this->Quiet);
|
|
|
|
std::string output;
|
|
std::string errors;
|
|
int retVal = 0;
|
|
*cont->OFS << "* Run coverage for: " << fileDir << std::endl;
|
|
*cont->OFS << " Command: " << command << std::endl;
|
|
int res = this->CTest->RunCommand(covargs, &output, &errors, &retVal,
|
|
fileDir.c_str(),
|
|
cmDuration::zero() /*this->TimeOut*/);
|
|
|
|
*cont->OFS << " Output: " << output << std::endl;
|
|
*cont->OFS << " Errors: " << errors << std::endl;
|
|
if (!res) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Problem running coverage on file: " << f << std::endl);
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Command produced error: " << errors << std::endl);
|
|
cont->Error++;
|
|
continue;
|
|
}
|
|
if (retVal != 0) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Coverage command returned: "
|
|
<< retVal << " while processing: " << f << std::endl);
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Command produced error: " << cont->Error << std::endl);
|
|
}
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"--------------------------------------------------------------"
|
|
<< std::endl
|
|
<< output << std::endl
|
|
<< "--------------------------------------------------------------"
|
|
<< std::endl,
|
|
this->Quiet);
|
|
|
|
std::vector<std::string> lines;
|
|
cmsys::SystemTools::Split(output, lines);
|
|
|
|
for (std::string const& line : lines) {
|
|
std::string sourceFile;
|
|
std::string lcovFile;
|
|
|
|
if (line.empty()) {
|
|
// Ignore empty line
|
|
}
|
|
// Look for LCOV files in binary directory
|
|
// Intel Compiler creates a CodeCoverage dir for each subfolder and
|
|
// each subfolder has LCOV files
|
|
cmsys::Glob gl;
|
|
gl.RecurseOn();
|
|
gl.RecurseThroughSymlinksOff();
|
|
std::string dir;
|
|
std::vector<std::string> lcovFiles;
|
|
dir = this->CTest->GetBinaryDir();
|
|
std::string daGlob;
|
|
daGlob = cmStrCat(dir, "/*.LCOV");
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" looking for LCOV files in: " << daGlob << std::endl, this->Quiet);
|
|
gl.FindFiles(daGlob);
|
|
// Keep a list of all LCOV files
|
|
cm::append(lcovFiles, gl.GetFiles());
|
|
|
|
for (std::string const& file : lcovFiles) {
|
|
lcovFile = file;
|
|
cmsys::ifstream srcead(lcovFile.c_str());
|
|
if (!srcead) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open file: " << lcovFile << std::endl);
|
|
}
|
|
std::string srcname;
|
|
|
|
int success = cmSystemTools::GetLineFromStream(srcead, srcname);
|
|
if (!success) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error while parsing lcov file '"
|
|
<< lcovFile << "':"
|
|
<< " No source file name found!" << std::endl);
|
|
return 0;
|
|
}
|
|
srcname = srcname.substr(18);
|
|
// We can directly read found LCOV files to determine the source
|
|
// files
|
|
sourceFile = srcname;
|
|
actualSourceFile = srcname;
|
|
|
|
for (std::string const& t : lcovFiles) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Found LCOV File: " << t << std::endl,
|
|
this->Quiet);
|
|
}
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"SourceFile: " << sourceFile << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"lCovFile: " << lcovFile << std::endl, this->Quiet);
|
|
|
|
// If we have some LCOV files to process
|
|
if (!lcovFile.empty() && !actualSourceFile.empty()) {
|
|
cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec =
|
|
cont->TotalCoverage[actualSourceFile];
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" in lcovFile: " << lcovFile << std::endl,
|
|
this->Quiet);
|
|
|
|
cmsys::ifstream ifile(lcovFile.c_str());
|
|
if (!ifile) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open file: " << lcovFile << std::endl);
|
|
} else {
|
|
long cnt = -1;
|
|
std::string nl;
|
|
|
|
// Skip the first line
|
|
cmSystemTools::GetLineFromStream(ifile, nl);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"File is ready, start reading." << std::endl,
|
|
this->Quiet);
|
|
while (cmSystemTools::GetLineFromStream(ifile, nl)) {
|
|
cnt++;
|
|
|
|
// Skip empty lines
|
|
if (nl.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Skip unused lines
|
|
if (nl.size() < 12) {
|
|
continue;
|
|
}
|
|
|
|
// Read the coverage count from the beginning of the lcov
|
|
// output line
|
|
std::string prefix = nl.substr(0, 17);
|
|
int cov = atoi(prefix.c_str());
|
|
|
|
// Read the line number starting at the 17th character of the
|
|
// lcov output line
|
|
std::string lineNumber = nl.substr(17, 7);
|
|
|
|
int lineIdx = atoi(lineNumber.c_str()) - 1;
|
|
if (lineIdx >= 0) {
|
|
while (vec.size() <= static_cast<size_t>(lineIdx)) {
|
|
vec.push_back(-1);
|
|
}
|
|
|
|
// Initially all entries are -1 (not used). If we get coverage
|
|
// information, increment it to 0 first.
|
|
if (vec[lineIdx] < 0) {
|
|
if (cov > 0 || prefix.find('#') != std::string::npos) {
|
|
vec[lineIdx] = 0;
|
|
}
|
|
}
|
|
|
|
vec[lineIdx] += cov;
|
|
}
|
|
}
|
|
}
|
|
|
|
actualSourceFile.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
file_count++;
|
|
|
|
if (file_count % 50 == 0) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
|
|
" processed: " << file_count << " out of "
|
|
<< files.size() << std::endl,
|
|
this->Quiet);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
|
|
}
|
|
}
|
|
|
|
return file_count;
|
|
}
|
|
|
|
void cmCTestCoverageHandler::FindGCovFiles(std::vector<std::string>& files)
|
|
{
|
|
cmsys::Glob gl;
|
|
gl.RecurseOn();
|
|
gl.RecurseThroughSymlinksOff();
|
|
|
|
for (auto const& lm : this->TargetDirs) {
|
|
// Skip targets containing no interesting labels.
|
|
if (!this->IntersectsFilter(lm.second)) {
|
|
continue;
|
|
}
|
|
|
|
// Coverage files appear next to their object files in the target
|
|
// support directory.
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" globbing for coverage in: " << lm.first << std::endl, this->Quiet);
|
|
std::string daGlob = cmStrCat(lm.first, "/*.da");
|
|
gl.FindFiles(daGlob);
|
|
cm::append(files, gl.GetFiles());
|
|
daGlob = cmStrCat(lm.first, "/*.gcda");
|
|
gl.FindFiles(daGlob);
|
|
cm::append(files, gl.GetFiles());
|
|
}
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::FindLCovFiles(std::vector<std::string>& files)
|
|
{
|
|
cmsys::Glob gl;
|
|
gl.RecurseOff(); // No need of recurse if -prof_dir${BUILD_DIR} flag is
|
|
// used while compiling.
|
|
gl.RecurseThroughSymlinksOff();
|
|
std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory");
|
|
cmWorkingDirectory workdir(buildDir);
|
|
if (workdir.Failed()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Unable to change working directory to " << buildDir
|
|
<< std::endl);
|
|
return false;
|
|
}
|
|
|
|
// Run profmerge to merge all *.dyn files into dpi files
|
|
if (!cmSystemTools::RunSingleCommand("profmerge")) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, "Error while running profmerge.\n");
|
|
return false;
|
|
}
|
|
|
|
// DPI file should appear in build directory
|
|
std::string daGlob;
|
|
daGlob = cmStrCat(buildDir, "/*.dpi");
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" looking for dpi files in: " << daGlob << std::endl,
|
|
this->Quiet);
|
|
if (!gl.FindFiles(daGlob)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error while finding files matching " << daGlob << std::endl);
|
|
return false;
|
|
}
|
|
cm::append(files, gl.GetFiles());
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Now searching in: " << daGlob << std::endl, this->Quiet);
|
|
return true;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleTracePyCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
cmsys::Glob gl;
|
|
gl.RecurseOn();
|
|
gl.RecurseThroughSymlinksOff();
|
|
std::string daGlob = cont->BinaryDir + "/*.cover";
|
|
gl.FindFiles(daGlob);
|
|
std::vector<std::string> files = gl.GetFiles();
|
|
|
|
if (files.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Cannot find any Python Trace.py coverage files."
|
|
<< std::endl,
|
|
this->Quiet);
|
|
// No coverage files is a valid thing, so the exit code is 0
|
|
return 0;
|
|
}
|
|
|
|
std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
|
|
std::string tempDir = testingDir + "/CoverageInfo";
|
|
cmSystemTools::MakeDirectory(tempDir);
|
|
|
|
int file_count = 0;
|
|
for (std::string const& file : files) {
|
|
std::string fileName = this->FindFile(cont, file);
|
|
if (fileName.empty()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot find source Python file corresponding to: "
|
|
<< file << std::endl);
|
|
continue;
|
|
}
|
|
|
|
std::string actualSourceFile = cmSystemTools::CollapseFullPath(fileName);
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" Check coverage for file: " << actualSourceFile
|
|
<< std::endl,
|
|
this->Quiet);
|
|
cmCTestCoverageHandlerContainer::SingleFileCoverageVector* vec =
|
|
&cont->TotalCoverage[actualSourceFile];
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" in file: " << file << std::endl, this->Quiet);
|
|
cmsys::ifstream ifile(file.c_str());
|
|
if (!ifile) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open file: " << file << std::endl);
|
|
} else {
|
|
long cnt = -1;
|
|
std::string nl;
|
|
while (cmSystemTools::GetLineFromStream(ifile, nl)) {
|
|
cnt++;
|
|
|
|
// Skip empty lines
|
|
if (nl.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Skip unused lines
|
|
if (nl.size() < 12) {
|
|
continue;
|
|
}
|
|
|
|
// Read the coverage count from the beginning of the Trace.py output
|
|
// line
|
|
std::string::size_type pos;
|
|
int cov = 0;
|
|
// This is a hack. We should really do something more elaborate
|
|
for (pos = 5; pos < 8; pos++) {
|
|
if (nl[pos] == ' ') {
|
|
// This line does not have ':' so no coverage here. That said,
|
|
// Trace.py does not handle not covered lines versus comments etc.
|
|
// So, this will be set to 0.
|
|
break;
|
|
}
|
|
if (nl[pos] == ':') {
|
|
cov = atoi(nl.substr(0, pos - 1).c_str());
|
|
break;
|
|
}
|
|
}
|
|
if (pos == 8) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Currently the limit is maximum coverage of 999999"
|
|
<< std::endl);
|
|
}
|
|
// Read the line number starting at the 10th character of the gcov
|
|
// output line
|
|
long lineIdx = cnt;
|
|
if (lineIdx >= 0) {
|
|
while (vec->size() <= static_cast<size_t>(lineIdx)) {
|
|
vec->push_back(-1);
|
|
}
|
|
// Initially all entries are -1 (not used). If we get coverage
|
|
// information, increment it to 0 first.
|
|
if ((*vec)[lineIdx] < 0) {
|
|
if (cov >= 0) {
|
|
(*vec)[lineIdx] = 0;
|
|
}
|
|
}
|
|
(*vec)[lineIdx] += cov;
|
|
}
|
|
}
|
|
}
|
|
++file_count;
|
|
}
|
|
return file_count;
|
|
}
|
|
|
|
std::string cmCTestCoverageHandler::FindFile(
|
|
cmCTestCoverageHandlerContainer* cont, std::string const& fileName)
|
|
{
|
|
std::string fileNameNoE =
|
|
cmSystemTools::GetFilenameWithoutLastExtension(fileName);
|
|
// First check in source and binary directory
|
|
std::string fullName = cont->SourceDir + "/" + fileNameNoE + ".py";
|
|
if (cmSystemTools::FileExists(fullName)) {
|
|
return fullName;
|
|
}
|
|
fullName = cont->BinaryDir + "/" + fileNameNoE + ".py";
|
|
if (cmSystemTools::FileExists(fullName)) {
|
|
return fullName;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// This is a header put on each marked up source file
|
|
namespace {
|
|
const char* bullseyeHelp[] = {
|
|
" Coverage produced by bullseye covbr tool: ",
|
|
" www.bullseye.com/help/ref_covbr.html",
|
|
" * An arrow --> indicates incomplete coverage.",
|
|
" * An X indicates a function that was invoked, a switch label that ",
|
|
" was exercised, a try-block that finished, or an exception handler ",
|
|
" that was invoked.",
|
|
" * A T or F indicates a boolean decision that evaluated true or false,",
|
|
" respectively.",
|
|
" * A t or f indicates a boolean condition within a decision if the ",
|
|
" condition evaluated true or false, respectively.",
|
|
" * A k indicates a constant decision or condition.",
|
|
" * The slash / means this probe is excluded from summary results. ",
|
|
nullptr
|
|
};
|
|
}
|
|
|
|
int cmCTestCoverageHandler::RunBullseyeCoverageBranch(
|
|
cmCTestCoverageHandlerContainer* cont,
|
|
std::set<std::string>& coveredFileNames, std::vector<std::string>& files,
|
|
std::vector<std::string>& filesFullPath)
|
|
{
|
|
if (files.size() != filesFullPath.size()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Files and full path files not the same size?:\n");
|
|
return 0;
|
|
}
|
|
// create the output stream for the CoverageLog-N.xml file
|
|
cmGeneratedFileStream covLogFile;
|
|
cmXMLWriter covLogXML(covLogFile);
|
|
int logFileCount = 0;
|
|
if (!this->StartCoverageLogFile(covLogFile, logFileCount)) {
|
|
return -1;
|
|
}
|
|
this->StartCoverageLogXML(covLogXML);
|
|
// for each file run covbr on that file to get the coverage
|
|
// information for that file
|
|
std::string outputFile;
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"run covbr: " << std::endl, this->Quiet);
|
|
|
|
if (!this->RunBullseyeCommand(cont, "covbr", nullptr, outputFile)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"error running covbr for."
|
|
<< "\n");
|
|
return -1;
|
|
}
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"covbr output in " << outputFile << std::endl,
|
|
this->Quiet);
|
|
// open the output file
|
|
cmsys::ifstream fin(outputFile.c_str());
|
|
if (!fin) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open coverage file: " << outputFile << std::endl);
|
|
return 0;
|
|
}
|
|
std::map<std::string, std::string> fileMap;
|
|
auto fp = filesFullPath.begin();
|
|
for (auto f = files.begin(); f != files.end(); ++f, ++fp) {
|
|
fileMap[*f] = *fp;
|
|
}
|
|
|
|
int count = 0; // keep count of the number of files
|
|
// Now parse each line from the bullseye cov log file
|
|
std::string lineIn;
|
|
bool valid = false; // are we in a valid output file
|
|
int line = 0; // line of the current file
|
|
std::string file;
|
|
while (cmSystemTools::GetLineFromStream(fin, lineIn)) {
|
|
bool startFile = false;
|
|
if (lineIn.size() > 1 && lineIn[lineIn.size() - 1] == ':') {
|
|
file = lineIn.substr(0, lineIn.size() - 1);
|
|
if (coveredFileNames.find(file) != coveredFileNames.end()) {
|
|
startFile = true;
|
|
}
|
|
}
|
|
if (startFile) {
|
|
// if we are in a valid file close it because a new one started
|
|
if (valid) {
|
|
covLogXML.EndElement(); // Report
|
|
covLogXML.EndElement(); // File
|
|
}
|
|
// only allow 100 files in each log file
|
|
if (count != 0 && count % 100 == 0) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"start a new log file: " << count << std::endl,
|
|
this->Quiet);
|
|
this->EndCoverageLogXML(covLogXML);
|
|
this->EndCoverageLogFile(covLogFile, logFileCount);
|
|
logFileCount++;
|
|
if (!this->StartCoverageLogFile(covLogFile, logFileCount)) {
|
|
return -1;
|
|
}
|
|
this->StartCoverageLogXML(covLogXML);
|
|
count++; // move on one
|
|
}
|
|
auto i = fileMap.find(file);
|
|
// if the file should be covered write out the header for that file
|
|
if (i != fileMap.end()) {
|
|
// we have a new file so count it in the output
|
|
count++;
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Produce coverage for file: " << file << " "
|
|
<< count << std::endl,
|
|
this->Quiet);
|
|
// start the file output
|
|
covLogXML.StartElement("File");
|
|
covLogXML.Attribute("Name", i->first);
|
|
covLogXML.Attribute("FullPath",
|
|
this->CTest->GetShortPathToFile(i->second));
|
|
covLogXML.StartElement("Report");
|
|
// write the bullseye header
|
|
line = 0;
|
|
for (int k = 0; bullseyeHelp[k] != nullptr; ++k) {
|
|
covLogXML.StartElement("Line");
|
|
covLogXML.Attribute("Number", line);
|
|
covLogXML.Attribute("Count", -1);
|
|
covLogXML.Content(bullseyeHelp[k]);
|
|
covLogXML.EndElement(); // Line
|
|
line++;
|
|
}
|
|
valid = true; // we are in a valid file section
|
|
} else {
|
|
// this is not a file that we want coverage for
|
|
valid = false;
|
|
}
|
|
}
|
|
// we are not at a start file, and we are in a valid file output the line
|
|
else if (valid) {
|
|
covLogXML.StartElement("Line");
|
|
covLogXML.Attribute("Number", line);
|
|
covLogXML.Attribute("Count", -1);
|
|
covLogXML.Content(lineIn);
|
|
covLogXML.EndElement(); // Line
|
|
line++;
|
|
}
|
|
}
|
|
// if we ran out of lines a valid file then close that file
|
|
if (valid) {
|
|
covLogXML.EndElement(); // Report
|
|
covLogXML.EndElement(); // File
|
|
}
|
|
this->EndCoverageLogXML(covLogXML);
|
|
this->EndCoverageLogFile(covLogFile, logFileCount);
|
|
return 1;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::RunBullseyeCommand(
|
|
cmCTestCoverageHandlerContainer* cont, const char* cmd, const char* arg,
|
|
std::string& outputFile)
|
|
{
|
|
std::string program = cmSystemTools::FindProgram(cmd);
|
|
if (program.empty()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find :" << cmd << "\n");
|
|
return 0;
|
|
}
|
|
if (arg) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Run : " << program << " " << arg << "\n", this->Quiet);
|
|
} else {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Run : " << program << "\n", this->Quiet);
|
|
}
|
|
// create a process object and start it
|
|
cmCTestRunProcess runCoverageSrc;
|
|
runCoverageSrc.SetCommand(program.c_str());
|
|
runCoverageSrc.AddArgument(arg);
|
|
std::string stdoutFile =
|
|
cmStrCat(cont->BinaryDir, "/Testing/Temporary/",
|
|
this->GetCTestInstance()->GetCurrentTag(), '-', cmd);
|
|
std::string stderrFile = stdoutFile;
|
|
stdoutFile += ".stdout";
|
|
stderrFile += ".stderr";
|
|
runCoverageSrc.SetStdoutFile(stdoutFile.c_str());
|
|
runCoverageSrc.SetStderrFile(stderrFile.c_str());
|
|
if (!runCoverageSrc.StartProcess()) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Could not run : " << program << " " << arg << "\n"
|
|
<< "kwsys process state : "
|
|
<< runCoverageSrc.GetProcessState());
|
|
return 0;
|
|
}
|
|
// since we set the output file names wait for it to end
|
|
runCoverageSrc.WaitForExit();
|
|
outputFile = stdoutFile;
|
|
return 1;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::RunBullseyeSourceSummary(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
// Run the covsrc command and create a temp outputfile
|
|
std::string outputFile;
|
|
if (!this->RunBullseyeCommand(cont, "covsrc", "-c", outputFile)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covsrc:\n");
|
|
return 0;
|
|
}
|
|
|
|
std::ostream& tmpLog = *cont->OFS;
|
|
// copen the Coverage.xml file in the Testing directory
|
|
cmGeneratedFileStream covSumFile;
|
|
cmXMLWriter xml(covSumFile);
|
|
if (!this->StartResultingXML(cmCTest::PartCoverage, "Coverage",
|
|
covSumFile)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open coverage summary file." << std::endl);
|
|
return 0;
|
|
}
|
|
this->CTest->StartXML(xml, this->AppendXML);
|
|
auto elapsed_time_start = std::chrono::steady_clock::now();
|
|
std::string coverage_start_time = this->CTest->CurrentTime();
|
|
xml.StartElement("Coverage");
|
|
xml.Element("StartDateTime", coverage_start_time);
|
|
xml.Element("StartTime", std::chrono::system_clock::now());
|
|
std::string stdline;
|
|
std::string errline;
|
|
// expected output:
|
|
// first line is:
|
|
// "Source","Function Coverage","out of","%","C/D Coverage","out of","%"
|
|
// after that data follows in that format
|
|
std::string sourceFile;
|
|
int functionsCalled = 0;
|
|
int totalFunctions = 0;
|
|
int percentFunction = 0;
|
|
int branchCovered = 0;
|
|
int totalBranches = 0;
|
|
int percentBranch = 0;
|
|
double total_tested = 0;
|
|
double total_untested = 0;
|
|
double total_functions = 0;
|
|
double percent_coverage = 0;
|
|
double number_files = 0;
|
|
std::vector<std::string> coveredFiles;
|
|
std::vector<std::string> coveredFilesFullPath;
|
|
// Read and parse the summary output file
|
|
cmsys::ifstream fin(outputFile.c_str());
|
|
if (!fin) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Cannot open coverage summary file: " << outputFile
|
|
<< std::endl);
|
|
return 0;
|
|
}
|
|
std::set<std::string> coveredFileNames;
|
|
while (cmSystemTools::GetLineFromStream(fin, stdline)) {
|
|
// if we have a line of output from stdout
|
|
if (!stdline.empty()) {
|
|
// parse the comma separated output
|
|
this->ParseBullsEyeCovsrcLine(
|
|
stdline, sourceFile, functionsCalled, totalFunctions, percentFunction,
|
|
branchCovered, totalBranches, percentBranch);
|
|
// The first line is the header
|
|
if (sourceFile == "Source" || sourceFile == "Total") {
|
|
continue;
|
|
}
|
|
std::string file = sourceFile;
|
|
coveredFileNames.insert(file);
|
|
if (!cmSystemTools::FileIsFullPath(sourceFile)) {
|
|
// file will be relative to the binary dir
|
|
file = cmStrCat(cont->BinaryDir, '/', sourceFile);
|
|
}
|
|
file = cmSystemTools::CollapseFullPath(file);
|
|
bool shouldIDoCoverage =
|
|
this->ShouldIDoCoverage(file, cont->SourceDir, cont->BinaryDir);
|
|
if (!shouldIDoCoverage) {
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
".NoDartCoverage found, so skip coverage check for: " << file
|
|
<< std::endl,
|
|
this->Quiet);
|
|
continue;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
"Doing coverage for: " << file << std::endl,
|
|
this->Quiet);
|
|
|
|
coveredFiles.push_back(sourceFile);
|
|
coveredFilesFullPath.push_back(file);
|
|
|
|
number_files++;
|
|
total_functions += totalFunctions;
|
|
total_tested += functionsCalled;
|
|
total_untested += (totalFunctions - functionsCalled);
|
|
|
|
std::string fileName = cmSystemTools::GetFilenameName(file);
|
|
std::string shortFileName = this->CTest->GetShortPathToFile(file);
|
|
|
|
float cper = static_cast<float>(percentBranch + percentFunction);
|
|
if (totalBranches > 0) {
|
|
cper /= 2.0f;
|
|
}
|
|
percent_coverage += static_cast<double>(cper);
|
|
float cmet = static_cast<float>(percentFunction + percentBranch);
|
|
if (totalBranches > 0) {
|
|
cmet /= 2.0f;
|
|
}
|
|
cmet /= 100.0f;
|
|
tmpLog << stdline << "\n";
|
|
tmpLog << fileName << "\n";
|
|
tmpLog << "functionsCalled: " << functionsCalled / 100 << "\n";
|
|
tmpLog << "totalFunctions: " << totalFunctions / 100 << "\n";
|
|
tmpLog << "percentFunction: " << percentFunction << "\n";
|
|
tmpLog << "branchCovered: " << branchCovered << "\n";
|
|
tmpLog << "totalBranches: " << totalBranches << "\n";
|
|
tmpLog << "percentBranch: " << percentBranch << "\n";
|
|
tmpLog << "percentCoverage: " << percent_coverage << "\n";
|
|
tmpLog << "coverage metric: " << cmet << "\n";
|
|
xml.StartElement("File");
|
|
xml.Attribute("Name", sourceFile);
|
|
xml.Attribute("FullPath", shortFileName);
|
|
xml.Attribute("Covered", cmet > 0 ? "true" : "false");
|
|
xml.Element("BranchesTested", branchCovered);
|
|
xml.Element("BranchesUnTested", totalBranches - branchCovered);
|
|
xml.Element("FunctionsTested", functionsCalled);
|
|
xml.Element("FunctionsUnTested", totalFunctions - functionsCalled);
|
|
// Hack for conversion of function to loc assume a function
|
|
// has 100 lines of code
|
|
xml.Element("LOCTested", functionsCalled * 100);
|
|
xml.Element("LOCUnTested", (totalFunctions - functionsCalled) * 100);
|
|
xml.Element("PercentCoverage", cper);
|
|
xml.Element("CoverageMetric", cmet);
|
|
this->WriteXMLLabels(xml, shortFileName);
|
|
xml.EndElement(); // File
|
|
}
|
|
}
|
|
std::string end_time = this->CTest->CurrentTime();
|
|
xml.Element("LOCTested", total_tested);
|
|
xml.Element("LOCUntested", total_untested);
|
|
xml.Element("LOC", total_functions);
|
|
xml.Element("PercentCoverage", SAFEDIV(percent_coverage, number_files));
|
|
xml.Element("EndDateTime", end_time);
|
|
xml.Element("EndTime", std::chrono::system_clock::now());
|
|
xml.Element("ElapsedMinutes",
|
|
std::chrono::duration_cast<std::chrono::minutes>(
|
|
std::chrono::steady_clock::now() - elapsed_time_start)
|
|
.count());
|
|
xml.EndElement(); // Coverage
|
|
this->CTest->EndXML(xml);
|
|
|
|
// Now create the coverage information for each file
|
|
return this->RunBullseyeCoverageBranch(cont, coveredFileNames, coveredFiles,
|
|
coveredFilesFullPath);
|
|
}
|
|
|
|
int cmCTestCoverageHandler::HandleBullseyeCoverage(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
std::string covfile;
|
|
if (!cmSystemTools::GetEnv("COVFILE", covfile) || covfile.empty()) {
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" COVFILE environment variable not found, not running "
|
|
" bullseye\n",
|
|
this->Quiet);
|
|
return 0;
|
|
}
|
|
cmCTestOptionalLog(
|
|
this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" run covsrc with COVFILE=[" << covfile << "]" << std::endl, this->Quiet);
|
|
if (!this->RunBullseyeSourceSummary(cont)) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error running bullseye summary.\n");
|
|
return 0;
|
|
}
|
|
cmCTestOptionalLog(this->CTest, DEBUG,
|
|
"HandleBullseyeCoverage return 1 " << std::endl,
|
|
this->Quiet);
|
|
return 1;
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::GetNextInt(std::string const& inputLine,
|
|
std::string::size_type& pos,
|
|
int& value)
|
|
{
|
|
std::string::size_type start = pos;
|
|
pos = inputLine.find(',', start);
|
|
value = atoi(inputLine.substr(start, pos).c_str());
|
|
if (pos == std::string::npos) {
|
|
return true;
|
|
}
|
|
pos++;
|
|
return true;
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::ParseBullsEyeCovsrcLine(
|
|
std::string const& inputLine, std::string& sourceFile, int& functionsCalled,
|
|
int& totalFunctions, int& percentFunction, int& branchCovered,
|
|
int& totalBranches, int& percentBranch)
|
|
{
|
|
// find the first comma
|
|
std::string::size_type pos = inputLine.find(',');
|
|
if (pos == std::string::npos) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error parsing string : " << inputLine << "\n");
|
|
return false;
|
|
}
|
|
// the source file has "" around it so extract out the file name
|
|
sourceFile = inputLine.substr(1, pos - 2);
|
|
pos++;
|
|
if (!this->GetNextInt(inputLine, pos, functionsCalled)) {
|
|
return false;
|
|
}
|
|
if (!this->GetNextInt(inputLine, pos, totalFunctions)) {
|
|
return false;
|
|
}
|
|
if (!this->GetNextInt(inputLine, pos, percentFunction)) {
|
|
return false;
|
|
}
|
|
if (!this->GetNextInt(inputLine, pos, branchCovered)) {
|
|
return false;
|
|
}
|
|
if (!this->GetNextInt(inputLine, pos, totalBranches)) {
|
|
return false;
|
|
}
|
|
if (!this->GetNextInt(inputLine, pos, percentBranch)) {
|
|
return false;
|
|
}
|
|
// should be at the end now
|
|
if (pos != std::string::npos) {
|
|
cmCTestLog(this->CTest, ERROR_MESSAGE,
|
|
"Error parsing input : "
|
|
<< inputLine << " last pos not npos = " << pos << "\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int cmCTestCoverageHandler::GetLabelId(std::string const& label)
|
|
{
|
|
auto i = this->LabelIdMap.find(label);
|
|
if (i == this->LabelIdMap.end()) {
|
|
int n = int(this->Labels.size());
|
|
this->Labels.push_back(label);
|
|
LabelIdMapType::value_type entry(label, n);
|
|
i = this->LabelIdMap.insert(entry).first;
|
|
}
|
|
return i->second;
|
|
}
|
|
|
|
void cmCTestCoverageHandler::LoadLabels()
|
|
{
|
|
std::string fileList =
|
|
cmStrCat(this->CTest->GetBinaryDir(), "/CMakeFiles/TargetDirectories.txt");
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" target directory list [" << fileList << "]\n",
|
|
this->Quiet);
|
|
cmsys::ifstream finList(fileList.c_str());
|
|
std::string line;
|
|
while (cmSystemTools::GetLineFromStream(finList, line)) {
|
|
this->LoadLabels(line.c_str());
|
|
}
|
|
}
|
|
|
|
void cmCTestCoverageHandler::LoadLabels(const char* dir)
|
|
{
|
|
LabelSet& dirLabels = this->TargetDirs[dir];
|
|
std::string fname = cmStrCat(dir, "/Labels.txt");
|
|
cmsys::ifstream fin(fname.c_str());
|
|
if (!fin) {
|
|
return;
|
|
}
|
|
|
|
cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
|
|
" loading labels from [" << fname << "]\n", this->Quiet);
|
|
bool inTarget = true;
|
|
std::string source;
|
|
std::string line;
|
|
std::vector<int> targetLabels;
|
|
while (cmSystemTools::GetLineFromStream(fin, line)) {
|
|
if (line.empty() || line[0] == '#') {
|
|
// Ignore blank and comment lines.
|
|
continue;
|
|
}
|
|
if (line[0] == ' ') {
|
|
// Label lines appear indented by one space.
|
|
std::string label = line.substr(1);
|
|
int id = this->GetLabelId(label);
|
|
dirLabels.insert(id);
|
|
if (inTarget) {
|
|
targetLabels.push_back(id);
|
|
} else {
|
|
this->SourceLabels[source].insert(id);
|
|
}
|
|
} else {
|
|
// Non-indented lines specify a source file name. The first one
|
|
// is the end of the target-wide labels.
|
|
inTarget = false;
|
|
|
|
source = this->CTest->GetShortPathToFile(line);
|
|
|
|
// Label the source with the target labels.
|
|
LabelSet& labelSet = this->SourceLabels[source];
|
|
labelSet.insert(targetLabels.begin(), targetLabels.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
void cmCTestCoverageHandler::WriteXMLLabels(cmXMLWriter& xml,
|
|
std::string const& source)
|
|
{
|
|
auto li = this->SourceLabels.find(source);
|
|
if (li != this->SourceLabels.end() && !li->second.empty()) {
|
|
xml.StartElement("Labels");
|
|
for (auto const& ls : li->second) {
|
|
xml.Element("Label", this->Labels[ls]);
|
|
}
|
|
xml.EndElement(); // Labels
|
|
}
|
|
}
|
|
|
|
void cmCTestCoverageHandler::SetLabelFilter(
|
|
std::set<std::string> const& labels)
|
|
{
|
|
this->LabelFilter.clear();
|
|
for (std::string const& l : labels) {
|
|
this->LabelFilter.insert(this->GetLabelId(l));
|
|
}
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::IntersectsFilter(LabelSet const& labels)
|
|
{
|
|
// If there is no label filter then nothing is filtered out.
|
|
if (this->LabelFilter.empty()) {
|
|
return true;
|
|
}
|
|
|
|
std::vector<int> ids;
|
|
std::set_intersection(labels.begin(), labels.end(),
|
|
this->LabelFilter.begin(), this->LabelFilter.end(),
|
|
std::back_inserter(ids));
|
|
return !ids.empty();
|
|
}
|
|
|
|
bool cmCTestCoverageHandler::IsFilteredOut(std::string const& source)
|
|
{
|
|
// If there is no label filter then nothing is filtered out.
|
|
if (this->LabelFilter.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// The source is filtered out if it does not have any labels in
|
|
// common with the filter set.
|
|
std::string shortSrc = this->CTest->GetShortPathToFile(source);
|
|
auto li = this->SourceLabels.find(shortSrc);
|
|
if (li != this->SourceLabels.end()) {
|
|
return !this->IntersectsFilter(li->second);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::set<std::string> cmCTestCoverageHandler::FindUncoveredFiles(
|
|
cmCTestCoverageHandlerContainer* cont)
|
|
{
|
|
std::set<std::string> extraMatches;
|
|
|
|
for (std::string const& ecg : this->ExtraCoverageGlobs) {
|
|
cmsys::Glob gl;
|
|
gl.RecurseOn();
|
|
gl.RecurseThroughSymlinksOff();
|
|
std::string glob = cont->SourceDir + "/" + ecg;
|
|
gl.FindFiles(glob);
|
|
std::vector<std::string> files = gl.GetFiles();
|
|
for (std::string const& f : files) {
|
|
if (this->ShouldIDoCoverage(f, cont->SourceDir, cont->BinaryDir)) {
|
|
extraMatches.insert(this->CTest->GetShortPathToFile(f));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!extraMatches.empty()) {
|
|
for (auto const& i : cont->TotalCoverage) {
|
|
std::string shortPath = this->CTest->GetShortPathToFile(i.first);
|
|
extraMatches.erase(shortPath);
|
|
}
|
|
}
|
|
return extraMatches;
|
|
}
|