summaryrefslogtreecommitdiffstats
path: root/Source/CTest/cmCTestCoverageHandler.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'Source/CTest/cmCTestCoverageHandler.cxx')
-rw-r--r--Source/CTest/cmCTestCoverageHandler.cxx2359
1 files changed, 2359 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx
new file mode 100644
index 0000000..7102533
--- /dev/null
+++ b/Source/CTest/cmCTestCoverageHandler.cxx
@@ -0,0 +1,2359 @@
+/*============================================================================
+ CMake - Cross Platform Makefile Generator
+ Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+ Distributed under the OSI-approved BSD License (the "License");
+ see accompanying file Copyright.txt for details.
+
+ This software is distributed WITHOUT ANY WARRANTY; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the License for more information.
+============================================================================*/
+#include "cmCTestCoverageHandler.h"
+
+#include "cmCTest.h"
+#include "cmGeneratedFileStream.h"
+#include "cmMakefile.h"
+#include "cmParseBlanketJSCoverage.h"
+#include "cmParseCacheCoverage.h"
+#include "cmParseCoberturaCoverage.h"
+#include "cmParseDelphiCoverage.h"
+#include "cmParseGTMCoverage.h"
+#include "cmParseJacocoCoverage.h"
+#include "cmParsePHPCoverage.h"
+#include "cmSystemTools.h"
+#include "cmXMLWriter.h"
+#include "cmake.h"
+
+#include <cmsys/FStream.hxx>
+#include <cmsys/Glob.hxx>
+#include <cmsys/Process.h>
+#include <cmsys/RegularExpression.hxx>
+
+#include <float.h>
+#include <math.h>
+#include <stdlib.h>
+
+#define SAFEDIV(x, y) (((y) != 0) ? ((x) / (y)) : (0))
+
+class cmCTestRunProcess
+{
+public:
+ cmCTestRunProcess()
+ {
+ this->Process = cmsysProcess_New();
+ this->PipeState = -1;
+ this->TimeOut = -1;
+ }
+ ~cmCTestRunProcess()
+ {
+ if (!(this->PipeState == -1) &&
+ !(this->PipeState == cmsysProcess_Pipe_None) &&
+ !(this->PipeState == cmsysProcess_Pipe_Timeout)) {
+ this->WaitForExit();
+ }
+ cmsysProcess_Delete(this->Process);
+ }
+ void SetCommand(const char* command)
+ {
+ this->CommandLineStrings.clear();
+ this->CommandLineStrings.push_back(command);
+ ;
+ }
+ void AddArgument(const char* arg)
+ {
+ if (arg) {
+ this->CommandLineStrings.push_back(arg);
+ }
+ }
+ void SetWorkingDirectory(const char* dir) { this->WorkingDirectory = dir; }
+ void SetTimeout(double t) { this->TimeOut = t; }
+ bool StartProcess()
+ {
+ std::vector<const char*> args;
+ for (std::vector<std::string>::iterator i =
+ this->CommandLineStrings.begin();
+ i != this->CommandLineStrings.end(); ++i) {
+ args.push_back(i->c_str());
+ }
+ args.push_back(CM_NULLPTR); // null terminate
+ cmsysProcess_SetCommand(this->Process, &*args.begin());
+ if (!this->WorkingDirectory.empty()) {
+ cmsysProcess_SetWorkingDirectory(this->Process,
+ this->WorkingDirectory.c_str());
+ }
+
+ cmsysProcess_SetOption(this->Process, cmsysProcess_Option_HideWindow, 1);
+ if (this->TimeOut != -1) {
+ cmsysProcess_SetTimeout(this->Process, this->TimeOut);
+ }
+ 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 = CM_NULLPTR)
+ {
+ this->PipeState = cmsysProcess_WaitForExit(this->Process, timeout);
+ return this->PipeState;
+ }
+ int GetProcessState() { return this->PipeState; }
+private:
+ int PipeState;
+ cmsysProcess* Process;
+ std::vector<std::string> CommandLineStrings;
+ std::string WorkingDirectory;
+ double TimeOut;
+};
+
+cmCTestCoverageHandler::cmCTestCoverageHandler()
+{
+}
+
+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 = this->CTest->GetCTestConfiguration("BuildDirectory");
+ logGlob += "/Testing/";
+ logGlob += this->CTest->GetCurrentTag();
+ logGlob += "/CoverageLog*";
+ cmsys::Glob gl;
+ gl.FindFiles(logGlob);
+ std::vector<std::string> const& files = gl.GetFiles();
+ for (std::vector<std::string>::const_iterator fi = files.begin();
+ fi != files.end(); ++fi) {
+ log << "Removing old coverage log: " << *fi << "\n";
+ cmSystemTools::RemoveFile(*fi);
+ }
+}
+
+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",
+ static_cast<unsigned int>(cmSystemTools::GetTime()));
+}
+
+void cmCTestCoverageHandler::EndCoverageLogXML(cmXMLWriter& xml)
+{
+ xml.Element("EndDateTime", this->CTest->CurrentTime());
+ xml.Element("EndTime", static_cast<unsigned int>(cmSystemTools::GetTime()));
+ xml.EndElement(); // CoverageLog
+ this->CTest->EndXML(xml);
+}
+
+bool cmCTestCoverageHandler::ShouldIDoCoverage(const char* file,
+ const char* srcDir,
+ const char* binDir)
+{
+ if (this->IsFilteredOut(file)) {
+ return false;
+ }
+
+ std::vector<cmsys::RegularExpression>::iterator sit;
+ for (sit = this->CustomCoverageExcludeRegex.begin();
+ sit != this->CustomCoverageExcludeRegex.end(); ++sit) {
+ if (sit->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.c_str(), checkDir.c_str());
+ 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.c_str(), fFile.c_str());
+ } 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.c_str(), checkDir.c_str());
+ 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() < 120) {
+ return error;
+ }
+
+ std::string coverage_start_time = this->CTest->CurrentTime();
+ unsigned int coverage_start_time_time =
+ static_cast<unsigned int>(cmSystemTools::GetTime());
+ std::string sourceDir =
+ this->CTest->GetCTestConfiguration("SourceDirectory");
+ std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory");
+
+ this->LoadLabels();
+
+ cmGeneratedFileStream ofs;
+ double elapsed_time_start = cmSystemTools::GetTime();
+ if (!this->StartLogFile("Coverage", ofs)) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot create LastCoverage.log file" << std::endl);
+ }
+
+ ofs << "Performing coverage: " << elapsed_time_start << 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();
+ std::vector<std::string>::iterator rexIt;
+ for (rexIt = this->CustomCoverageExclude.begin();
+ rexIt != this->CustomCoverageExclude.end(); ++rexIt) {
+ this->CustomCoverageExcludeRegex.push_back(
+ cmsys::RegularExpression(rexIt->c_str()));
+ }
+
+ 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);
+ cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator fileIterator;
+ 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 (fileIterator = cont.TotalCoverage.begin();
+ fileIterator != cont.TotalCoverage.end(); ++fileIterator) {
+ 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 = fileIterator->first;
+ bool shouldIDoCoverage = this->ShouldIDoCoverage(
+ fullFileName.c_str(), sourceDir.c_str(), binaryDir.c_str());
+ 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.c_str())) {
+ 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);
+ std::string shortFileName =
+ this->CTest->GetShortPathToFile(fullFileName.c_str());
+ const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov =
+ fileIterator->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.c_str()));
+ 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::set<std::string>::iterator i = uncovered.begin();
+ i != uncovered.end(); ++i) {
+ std::string fileName = cmSystemTools::GetFilenameName(*i);
+ std::string fullPath = cont.SourceDir + "/" + *i;
+
+ covLogXML.StartElement("File");
+ covLogXML.Attribute("Name", fileName);
+ covLogXML.Attribute("FullPath", *i);
+ 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++;
+ continue;
+ }
+ int untested = 0;
+ std::string line;
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ "Actually performing coverage for: " << *i << 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", *i);
+ covSumXML.Attribute("Covered", "true");
+ covSumXML.Element("LOCTested", 0);
+ covSumXML.Element("LOCUnTested", untested);
+ covSumXML.Element("PercentCoverage", 0);
+ covSumXML.Element("CoverageMetric", 0);
+ this->WriteXMLLabels(covSumXML, *i);
+ 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);
+ std::vector<std::string>::iterator erIt;
+ for (erIt = errorsWhileAccumulating.begin();
+ erIt != errorsWhileAccumulating.end(); ++erIt) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, " " << *erIt << 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",
+ static_cast<unsigned int>(cmSystemTools::GetTime()));
+ covSumXML.Element(
+ "ElapsedMinutes",
+ static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) /
+ 10.0);
+ 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);
+ std::vector<std::string>::iterator it;
+ for (it = this->CustomCoverageExclude.begin();
+ it != this->CustomCoverageExclude.end(); ++it) {
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " Add coverage exclude: " << *it << std::endl,
+ this->Quiet);
+ }
+ for (it = this->ExtraCoverageGlobs.begin();
+ it != this->ExtraCoverageGlobs.end(); ++it) {
+ cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
+ " Add coverage glob: " << *it << 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)
+#else
+#define fnc(s) s
+#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(file.substr(0, dir.size())) == fnc(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;
+ const char* covDir = cmSystemTools::GetEnv("COBERTURADIR");
+ if (covDir && strlen(covDir) != 0) {
+ coverageXMLFile = std::string(covDir);
+ } else {
+ coverageXMLFile = this->CTest->GetBinaryDir();
+ }
+ // build the find file string with the directory from above
+ coverageXMLFile += "/coverage.xml";
+
+ if (cmSystemTools::FileExists(coverageXMLFile.c_str())) {
+ // 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.c_str())) {
+ 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());
+ } else {
+ 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.c_str())) {
+ 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()
+ {
+ if (const char* l = cmSystemTools::GetEnv("LC_ALL")) {
+ lc_all = l;
+ }
+ if (lc_all != "C") {
+ cmSystemTools::PutEnv("LC_ALL=C");
+ }
+ }
+ ~cmCTestCoverageHandlerLocale()
+ {
+ if (!lc_all.empty()) {
+ cmSystemTools::PutEnv("LC_ALL=" + lc_all);
+ } else {
+ cmSystemTools::UnsetEnv("LC_ALL");
+ }
+ }
+ 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;
+ std::vector<std::string> binFiles;
+ g2.SetRecurse(true);
+ std::string binaryDir = this->CTest->GetCTestConfiguration("BuildDirectory");
+ std::string binCoverageFile = binaryDir + "/*jacoco.xml";
+ g2.FindFiles(binCoverageFile);
+ binFiles = g2.GetFiles();
+ if (!binFiles.empty()) {
+ files.insert(files.end(), binFiles.begin(), binFiles.end());
+ }
+
+ 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());
+}
+
+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 (unsigned int fileEntry = 0; fileEntry < files.size(); fileEntry++) {
+ cmsys::ifstream in(files[fileEntry].c_str());
+ cmSystemTools::GetLineFromStream(in, line);
+ cmSystemTools::GetLineFromStream(in, line);
+ if (line.find("node-jscoverage") != line.npos) {
+ blanketFiles.push_back(files[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()) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Could not find gcov."
+ << std::endl);
+ 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);
+ std::vector<std::string>::iterator it;
+
+ 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";
+ std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::MakeDirectory(tempDir.c_str());
+ cmSystemTools::ChangeDirectory(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);
+
+ // 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 (it = files.begin(); it != files.end(); ++it) {
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
+ this->Quiet);
+
+ // Call gcov to get coverage data for this *.gcda file:
+ //
+ std::string fileDir = cmSystemTools::GetFilenamePath(*it);
+ std::string command = "\"" + gcovCommand + "\" " + gcovExtraFlags + " " +
+ "-o \"" + fileDir + "\" " + "\"" + *it + "\"";
+
+ 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(command.c_str(), &output, &errors, &retVal,
+ tempDir.c_str(), 0 /*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: " << *it << 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: " << *it << 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;
+ std::vector<std::string>::iterator line;
+
+ cmSystemTools::Split(output.c_str(), lines);
+
+ for (line = lines.begin(); line != lines.end(); ++line) {
+ 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->c_str())) {
+ if (gcovStyle == 0) {
+ gcovStyle = 1;
+ }
+ if (gcovStyle != 1) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e1"
+ << std::endl);
+ cont->Error++;
+ break;
+ }
+
+ actualSourceFile = "";
+ sourceFile = st1re1.match(2);
+ } else if (st1re2.find(line->c_str())) {
+ 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->c_str())) {
+ if (gcovStyle == 0) {
+ gcovStyle = 2;
+ }
+ if (gcovStyle != 2) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style e3"
+ << std::endl);
+ cont->Error++;
+ break;
+ }
+
+ actualSourceFile = "";
+ sourceFile = st2re1.match(1);
+ } else if (st2re2.find(line->c_str())) {
+ 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->c_str())) {
+ 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->c_str())) {
+ 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->c_str())) {
+ 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->c_str())) {
+ 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" &&
+ !cmSystemTools::StringStartsWith(line->c_str(), "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++;
+
+ // TODO: Handle gcov 3.0 non-coverage lines
+
+ // Skip empty lines
+ if (nl.empty()) {
+ continue;
+ }
+
+ // Skip unused lines
+ if (nl.size() < 12) {
+ 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('#') != prefix.npos) {
+ vec[lineIdx] = 0;
+ }
+ }
+
+ vec[lineIdx] += cov;
+ }
+ }
+ }
+
+ actualSourceFile = "";
+ }
+
+ if (!sourceFile.empty() && actualSourceFile.empty()) {
+ gcovFile = "";
+
+ // 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);
+ }
+ }
+
+ cmSystemTools::ChangeDirectory(currentDirectory);
+ 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;
+ }
+ std::vector<std::string>::iterator it;
+
+ 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::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+
+ 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);
+
+ // In intel compiler we have to call codecov only once in each executable
+ // directory. It collects all *.dyn files to generate .dpi file.
+ for (it = files.begin(); it != files.end(); ++it) {
+ cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
+ this->Quiet);
+ std::string fileDir = cmSystemTools::GetFilenamePath(*it);
+ cmSystemTools::ChangeDirectory(fileDir);
+ std::string command = "\"" + lcovCommand + "\" " + lcovExtraFlags + " ";
+
+ 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(command.c_str(), &output, &errors, &retVal,
+ fileDir.c_str(), 0 /*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: " << *it << 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: " << *it << 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;
+ std::vector<std::string>::iterator line;
+
+ cmSystemTools::Split(output.c_str(), lines);
+
+ for (line = lines.begin(); line != lines.end(); ++line) {
+ 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 = dir;
+ daGlob += "/*.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
+ lcovFiles.insert(lcovFiles.end(), gl.GetFiles().begin(),
+ gl.GetFiles().end());
+
+ for (std::vector<std::string>::iterator a = lcovFiles.begin();
+ a != lcovFiles.end(); ++a) {
+ lcovFile = *a;
+ 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::vector<std::string>::iterator t = lcovFiles.begin();
+ t != lcovFiles.end(); ++t) {
+ 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('#') != prefix.npos) {
+ vec[lineIdx] = 0;
+ }
+ }
+
+ vec[lineIdx] += cov;
+ }
+ }
+ }
+
+ actualSourceFile = "";
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ cmSystemTools::ChangeDirectory(currentDirectory);
+ return file_count;
+}
+
+void cmCTestCoverageHandler::FindGCovFiles(std::vector<std::string>& files)
+{
+ cmsys::Glob gl;
+ gl.RecurseOn();
+ gl.RecurseThroughSymlinksOff();
+
+ for (LabelMapType::const_iterator lmi = this->TargetDirs.begin();
+ lmi != this->TargetDirs.end(); ++lmi) {
+ // Skip targets containing no interesting labels.
+ if (!this->IntersectsFilter(lmi->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: " << lmi->first << std::endl, this->Quiet);
+ std::string daGlob = lmi->first;
+ daGlob += "/*.da";
+ gl.FindFiles(daGlob);
+ files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
+ daGlob = lmi->first;
+ daGlob += "/*.gcda";
+ gl.FindFiles(daGlob);
+ files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
+ }
+}
+
+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 prevBinaryDir;
+ std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory");
+ if (cmSystemTools::ChangeDirectory(buildDir)) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE, "Error changing 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;
+ }
+
+ prevBinaryDir = cmSystemTools::GetCurrentWorkingDirectory();
+
+ // DPI file should appear in build directory
+ std::string daGlob;
+ daGlob = prevBinaryDir;
+ daGlob += "/*.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;
+ }
+ files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
+ 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";
+ std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::MakeDirectory(tempDir.c_str());
+ cmSystemTools::ChangeDirectory(tempDir);
+
+ cmSystemTools::ChangeDirectory(currentDirectory);
+
+ std::vector<std::string>::iterator fileIt;
+ int file_count = 0;
+ for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
+ std::string fileName = this->FindFile(cont, *fileIt);
+ if (fileName.empty()) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot find source Python file corresponding to: "
+ << *fileIt << 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: " << *fileIt << std::endl, this->Quiet);
+ cmsys::ifstream ifile(fileIt->c_str());
+ if (!ifile) {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Cannot open file: " << *fileIt << 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 prefix = nl.substr(0, 6);
+ if (prefix[5] != ' ' && prefix[5] != ':') {
+ // This is a hack. We should really do something more elaborate
+ prefix = nl.substr(0, 7);
+ if (prefix[6] != ' ' && prefix[6] != ':') {
+ prefix = nl.substr(0, 8);
+ if (prefix[7] != ' ' && prefix[7] != ':') {
+ cmCTestLog(this->CTest, ERROR_MESSAGE,
+ "Currently the limit is maximum coverage of 999999"
+ << std::endl);
+ }
+ }
+ }
+ int cov = atoi(prefix.c_str());
+ if (prefix[prefix.size() - 1] != ':') {
+ // 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.
+ cov = 0;
+ }
+ cmCTestOptionalLog(
+ this->CTest, DEBUG,
+ "Prefix: " << prefix << " cov: " << cov << std::endl, this->Quiet);
+ // 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;
+ }
+ cmSystemTools::ChangeDirectory(currentDirectory);
+ 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.c_str())) {
+ return fullName;
+ }
+ fullName = cont->BinaryDir + "/" + fileNameNoE + ".py";
+ if (cmSystemTools::FileExists(fullName.c_str())) {
+ 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. ",
+ CM_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", CM_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;
+ std::vector<std::string>::iterator fp = filesFullPath.begin();
+ for (std::vector<std::string>::iterator 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
+ }
+ std::map<std::string, std::string>::iterator 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.c_str()));
+ covLogXML.StartElement("Report");
+ // write the bullseye header
+ line = 0;
+ for (int k = 0; bullseyeHelp[k] != CM_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 = cont->BinaryDir + "/Testing/Temporary/";
+ stdoutFile += this->GetCTestInstance()->GetCurrentTag();
+ stdoutFile += "-";
+ stdoutFile += 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);
+ double elapsed_time_start = cmSystemTools::GetTime();
+ std::string coverage_start_time = this->CTest->CurrentTime();
+ xml.StartElement("Coverage");
+ xml.Element("StartDateTime", coverage_start_time);
+ xml.Element("StartTime",
+ static_cast<unsigned int>(cmSystemTools::GetTime()));
+ 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.c_str())) {
+ // file will be relative to the binary dir
+ file = cont->BinaryDir;
+ file += "/";
+ file += sourceFile;
+ }
+ file = cmSystemTools::CollapseFullPath(file);
+ bool shouldIDoCoverage = this->ShouldIDoCoverage(
+ file.c_str(), cont->SourceDir.c_str(), cont->BinaryDir.c_str());
+ 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.c_str());
+
+ 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", static_cast<unsigned int>(cmSystemTools::GetTime()));
+ xml.Element(
+ "ElapsedMinutes",
+ static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start) / 6) /
+ 10.0);
+ 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)
+{
+ const char* covfile = cmSystemTools::GetEnv("COVFILE");
+ if (!covfile || strlen(covfile) == 0) {
+ 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 == inputLine.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 == inputLine.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 != inputLine.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)
+{
+ LabelIdMapType::iterator 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 = this->CTest->GetBinaryDir();
+ fileList += cmake::GetCMakeFilesDirectory();
+ fileList += "/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 = dir;
+ fname += "/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;
+ } else 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.c_str());
+
+ // 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)
+{
+ LabelMapType::const_iterator li = this->SourceLabels.find(source);
+ if (li != this->SourceLabels.end() && !li->second.empty()) {
+ xml.StartElement("Labels");
+ for (LabelSet::const_iterator lsi = li->second.begin();
+ lsi != li->second.end(); ++lsi) {
+ xml.Element("Label", this->Labels[*lsi]);
+ }
+ xml.EndElement(); // Labels
+ }
+}
+
+void cmCTestCoverageHandler::SetLabelFilter(
+ std::set<std::string> const& labels)
+{
+ this->LabelFilter.clear();
+ for (std::set<std::string>::const_iterator li = labels.begin();
+ li != labels.end(); ++li) {
+ this->LabelFilter.insert(this->GetLabelId(*li));
+ }
+}
+
+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.c_str());
+ LabelMapType::const_iterator 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::vector<std::string>::iterator i = this->ExtraCoverageGlobs.begin();
+ i != this->ExtraCoverageGlobs.end(); ++i) {
+ cmsys::Glob gl;
+ gl.RecurseOn();
+ gl.RecurseThroughSymlinksOff();
+ std::string glob = cont->SourceDir + "/" + *i;
+ gl.FindFiles(glob);
+ std::vector<std::string> files = gl.GetFiles();
+ for (std::vector<std::string>::iterator f = files.begin();
+ f != files.end(); ++f) {
+ if (this->ShouldIDoCoverage(f->c_str(), cont->SourceDir.c_str(),
+ cont->BinaryDir.c_str())) {
+ extraMatches.insert(this->CTest->GetShortPathToFile(f->c_str()));
+ }
+ }
+ }
+
+ if (!extraMatches.empty()) {
+ for (cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator i =
+ cont->TotalCoverage.begin();
+ i != cont->TotalCoverage.end(); ++i) {
+ std::string shortPath =
+ this->CTest->GetShortPathToFile(i->first.c_str());
+ extraMatches.erase(shortPath);
+ }
+ }
+ return extraMatches;
+}