diff options
Diffstat (limited to 'Source/CTest/cmCTestCoverageHandler.cxx')
-rw-r--r-- | Source/CTest/cmCTestCoverageHandler.cxx | 2144 |
1 files changed, 2144 insertions, 0 deletions
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx new file mode 100644 index 000000000..81d366937 --- /dev/null +++ b/Source/CTest/cmCTestCoverageHandler.cxx @@ -0,0 +1,2144 @@ +/*============================================================================ + 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 "cmParsePHPCoverage.h" +#include "cmParseGTMCoverage.h" +#include "cmParseCacheCoverage.h" +#include "cmCTest.h" +#include "cmake.h" +#include "cmMakefile.h" +#include "cmSystemTools.h" +#include "cmGeneratedFileStream.h" +#include "cmXMLSafe.h" + +#include <cmsys/Process.h> +#include <cmsys/RegularExpression.hxx> +#include <cmsys/Glob.hxx> +#include <cmsys/stl/iterator> +#include <cmsys/stl/algorithm> + +#include <stdlib.h> +#include <math.h> +#include <float.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(0); // null terminate + cmsysProcess_SetCommand(this->Process, &*args.begin()); + if(this->WorkingDirectory.size()) + { + 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 + if(this->PipeState == cmsysProcess_State_Executing + || this->PipeState == cmsysProcess_State_Exited) + { + return true; + } + return false; + } + 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 =0) + { + 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->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.c_str()); + 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->c_str()); + } +} + +//---------------------------------------------------------------------- +bool cmCTestCoverageHandler::StartCoverageLogFile( + cmGeneratedFileStream& covLogFile, int logFileCount) +{ + char covLogFilename[1024]; + sprintf(covLogFilename, "CoverageLog-%d", logFileCount); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Open file: " + << covLogFilename << std::endl); + if(!this->StartResultingXML(cmCTest::PartCoverage, + covLogFilename, covLogFile)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open log file: " + << covLogFilename << std::endl); + return false; + } + std::string local_start_time = this->CTest->CurrentTime(); + this->CTest->StartXML(covLogFile, this->AppendXML); + covLogFile << "<CoverageLog>" << std::endl + << "\t<StartDateTime>" << local_start_time << "</StartDateTime>" + << "\t<StartTime>" + << static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</StartTime>" + << std::endl; + return true; +} + +//---------------------------------------------------------------------- +void cmCTestCoverageHandler::EndCoverageLogFile(cmGeneratedFileStream& ostr, + int logFileCount) +{ + std::string local_end_time = this->CTest->CurrentTime(); + ostr << "\t<EndDateTime>" << local_end_time << "</EndDateTime>" << std::endl + << "\t<EndTime>" << + static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</EndTime>" << std::endl + << "</CoverageLog>" << std::endl; + this->CTest->EndXML(ostr); + char covLogFilename[1024]; + sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Close file: " + << covLogFilename << std::endl); + ostr.Close(); +} + +//---------------------------------------------------------------------- +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) ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " File " << file + << " is excluded in CTestCustom.ctest" << std::endl;); + 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.c_str(), + fSrcDir.c_str()); + bool buildSubDir = cmSystemTools::IsSubDirectory(fFile.c_str(), + fBinDir.c_str()); + // Always check parent directory of the file. + std::string fileDir = cmSystemTools::GetFilenamePath(fFile.c_str()); + 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.size() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Found: " << ndc.c_str() + << " so skip coverage of " << file << std::endl); + 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.size() ) + { + 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.c_str()); + + 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.size() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Found: " << ndc.c_str() + << " so skip coverage of: " << file << std::endl); + 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); + + cmCTestLog(this->CTest, HANDLER_OUTPUT, "Performing coverage" << std::endl); + + cmCTestCoverageHandlerContainer cont; + cont.Error = error; + cont.SourceDir = sourceDir; + cont.BinaryDir = binaryDir; + cont.OFS = &ofs; + + // setup the regex exclude stuff + this->CustomCoverageExcludeRegex.clear(); + std::vector<cmStdString>::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->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->HandleMumpsCoverage(&cont); + error = cont.Error; + if ( file_count < 0 ) + { + return error; + } + + std::set<std::string> uncovered = this->FindUncoveredFiles(&cont); + + if ( file_count == 0 ) + { + cmCTestLog(this->CTest, WARNING, + " Cannot find any coverage files. Ignoring Coverage request." + << std::endl); + return error; + } + cmGeneratedFileStream covSumFile; + cmGeneratedFileStream covLogFile; + + if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open coverage summary file." << std::endl); + return -1; + } + + this->CTest->StartXML(covSumFile, this->AppendXML); + // Produce output xml files + + covSumFile << "<Coverage>" << std::endl + << "\t<StartDateTime>" << coverage_start_time << "</StartDateTime>" + << std::endl + << "\t<StartTime>" << coverage_start_time_time << "</StartTime>" + << std::endl; + int logFileCount = 0; + if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) + { + return -1; + } + cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator fileIterator; + int cnt = 0; + long total_tested = 0; + long total_untested = 0; + //std::string fullSourceDir = sourceDir + "/"; + //std::string fullBinaryDir = binaryDir + "/"; + cmCTestLog(this->CTest, HANDLER_OUTPUT, std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Accumulating results (each . represents one file):" << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + + std::vector<std::string> errorsWhileAccumulating; + + file_count = 0; + for ( fileIterator = cont.TotalCoverage.begin(); + fileIterator != cont.TotalCoverage.end(); + ++fileIterator ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + file_count ++; + if ( file_count % 50 == 0 ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " processed: " << file_count + << " out of " + << cont.TotalCoverage.size() << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + } + + const std::string fullFileName = fileIterator->first; + bool shouldIDoCoverage + = this->ShouldIDoCoverage(fullFileName.c_str(), + sourceDir.c_str(), binaryDir.c_str()); + if ( !shouldIDoCoverage ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + ".NoDartCoverage found, so skip coverage check for: " + << fullFileName.c_str() + << std::endl); + continue; + } + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Process file: " << fullFileName << std::endl); + + if ( !cmSystemTools::FileExists(fullFileName.c_str()) ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find file: " + << fullFileName.c_str() << std::endl); + continue; + } + + if ( ++cnt % 100 == 0 ) + { + this->EndCoverageLogFile(covLogFile, logFileCount); + logFileCount ++; + if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) + { + return -1; + } + } + + const std::string fileName + = cmSystemTools::GetFilenameName(fullFileName.c_str()); + std::string shortFileName = + this->CTest->GetShortPathToFile(fullFileName.c_str()); + const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov + = fileIterator->second; + covLogFile << "\t<File Name=\"" << cmXMLSafe(fileName) + << "\" FullPath=\"" << cmXMLSafe(shortFileName) << "\">\n" + << "\t\t<Report>" << std::endl; + + std::ifstream ifs(fullFileName.c_str()); + if ( !ifs) + { + cmOStringStream ostr; + ostr << "Cannot open source file: " << fullFileName.c_str(); + errorsWhileAccumulating.push_back(ostr.str()); + error ++; + continue; + } + + int tested = 0; + int untested = 0; + + cmCTestCoverageHandlerContainer::SingleFileCoverageVector::size_type cc; + std::string line; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Actually performing coverage for: " << fullFileName << std::endl); + for ( cc= 0; cc < fcov.size(); cc ++ ) + { + if ( !cmSystemTools::GetLineFromStream(ifs, line) && + cc != fcov.size() -1 ) + { + cmOStringStream ostr; + ostr << "Problem reading source file: " << fullFileName.c_str() + << " line:" << cc << " out total: " << fcov.size()-1; + errorsWhileAccumulating.push_back(ostr.str()); + error ++; + break; + } + covLogFile << "\t\t<Line Number=\"" << cc << "\" Count=\"" << fcov[cc] + << "\">" + << cmXMLSafe(line) << "</Line>" << std::endl; + if ( fcov[cc] == 0 ) + { + untested ++; + } + else if ( fcov[cc] > 0 ) + { + tested ++; + } + } + if ( cmSystemTools::GetLineFromStream(ifs, line) ) + { + cmOStringStream ostr; + ostr << "Looks like there are more lines in the file: " << line; + 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; + covLogFile << "\t\t</Report>" << std::endl + << "\t</File>" << std::endl; + covSumFile << "\t<File Name=\"" << cmXMLSafe(fileName) + << "\" FullPath=\"" << cmXMLSafe( + this->CTest->GetShortPathToFile(fullFileName.c_str())) + << "\" Covered=\"" << (tested+untested > 0 ? "true":"false") << "\">\n" + << "\t\t<LOCTested>" << tested << "</LOCTested>\n" + << "\t\t<LOCUnTested>" << untested << "</LOCUnTested>\n" + << "\t\t<PercentCoverage>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile << (cper) << "</PercentCoverage>\n" + << "\t\t<CoverageMetric>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile << (cmet) << "</CoverageMetric>\n"; + this->WriteXMLLabels(covSumFile, shortFileName); + covSumFile << "\t</File>" << std::endl; + } + + //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; + + covLogFile << "\t<File Name=\"" << cmXMLSafe(fileName) + << "\" FullPath=\"" << cmXMLSafe(*i) << "\">\n" + << "\t\t<Report>" << std::endl; + + std::ifstream ifs(fullPath.c_str()); + if (!ifs) + { + cmOStringStream ostr; + ostr << "Cannot open source file: " << fullPath.c_str(); + errorsWhileAccumulating.push_back(ostr.str()); + error ++; + continue; + } + int untested = 0; + std::string line; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Actually performing coverage for: " << i->c_str() << std::endl); + while (cmSystemTools::GetLineFromStream(ifs, line)) + { + covLogFile << "\t\t<Line Number=\"" << untested << "\" Count=\"0\">" + << cmXMLSafe(line) << "</Line>" << std::endl; + untested ++; + } + covLogFile << "\t\t</Report>\n\t</File>" << std::endl; + + total_untested += untested; + covSumFile << "\t<File Name=\"" << cmXMLSafe(fileName) + << "\" FullPath=\"" << cmXMLSafe(i->c_str()) + << "\" Covered=\"true\">\n" + << "\t\t<LOCTested>0</LOCTested>\n" + << "\t\t<LOCUnTested>" << untested << "</LOCUnTested>\n" + << "\t\t<PercentCoverage>0</PercentCoverage>\n" + << "\t\t<CoverageMetric>0</CoverageMetric>\n"; + this->WriteXMLLabels(covSumFile, *i); + covSumFile << "\t</File>" << std::endl; + } + + this->EndCoverageLogFile(covLogFile, logFileCount); + + if ( errorsWhileAccumulating.size() > 0 ) + { + 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->c_str() << 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(); + + covSumFile << "\t<LOCTested>" << total_tested << "</LOCTested>\n" + << "\t<LOCUntested>" << total_untested << "</LOCUntested>\n" + << "\t<LOC>" << total_lines << "</LOC>\n" + << "\t<PercentCoverage>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile << (percent_coverage)<< "</PercentCoverage>\n" + << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" + << "\t<EndTime>" << + static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</EndTime>\n"; + covSumFile << "<ElapsedMinutes>" << + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 + << "</ElapsedMinutes>" + << "</Coverage>" << std::endl; + this->CTest->EndXML(covSumFile); + + 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) +{ + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Add coverage exclude regular expressions." << std::endl); + this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_COVERAGE_EXCLUDE", + this->CustomCoverageExclude); + this->CTest->PopulateCustomVector(mf, "CTEST_EXTRA_COVERAGE_GLOB", + this->ExtraCoverageGlobs); + std::vector<cmStdString>::iterator it; + for ( it = this->CustomCoverageExclude.begin(); + it != this->CustomCoverageExclude.end(); + ++ it ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Add coverage exclude: " + << it->c_str() << std::endl); + } + for ( it = this->ExtraCoverageGlobs.begin(); + it != this->ExtraCoverageGlobs.end(); ++it) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " Add coverage glob: " + << it->c_str() << std::endl); + } +} + +//---------------------------------------------------------------------- +// 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.c_str()); + std::string dir = cmSystemTools::CollapseFullPath(indir.c_str()); + + if ( + file.size() > dir.size() && + (fnc(file.substr(0, dir.size())) == fnc(dir)) && + file[dir.size()] == '/' + ) + { + return true; + } + + return false; +} + +//---------------------------------------------------------------------- +int cmCTestCoverageHandler::HandlePHPCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParsePHPCoverage cov(*cont, this->CTest); + std::string coverageDir = this->CTest->GetBinaryDir() + "/xdebugCoverage"; + if(cmSystemTools::FileIsDirectory(coverageDir.c_str())) + { + cov.ReadPHPCoverageDirectory(coverageDir.c_str()); + } + 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())) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing Cache Coverage: " << coverageFile + << std::endl); + cov.ReadCoverageFile(coverageFile.c_str()); + return static_cast<int>(cont->TotalCoverage.size()); + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find foobar GTM coverage file: " << coverageFile + << std::endl); + } + cmParseCacheCoverage ccov(*cont, this->CTest); + coverageFile = this->CTest->GetBinaryDir() + + "/cache_coverage.cmcov"; + if(cmSystemTools::FileExists(coverageFile.c_str())) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing Cache Coverage: " << coverageFile + << std::endl); + ccov.ReadCoverageFile(coverageFile.c_str()); + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find Cache coverage file: " << coverageFile + << std::endl); + } + 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).c_str()); + } + else + { + cmSystemTools::UnsetEnv("LC_ALL"); + } + } + std::string lc_all; +}; + +//---------------------------------------------------------------------- +int cmCTestCoverageHandler::HandleGCovCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + std::string gcovCommand + = this->CTest->GetCTestConfiguration("CoverageCommand"); + std::string gcovExtraFlags + = this->CTest->GetCTestConfiguration("CoverageExtraFlags"); + + // 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.size() == 0 ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find any GCov coverage files." + << std::endl); + // 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.c_str()); + + int gcovStyle = 0; + + std::set<std::string> missingFiles; + + std::string actualSourceFile = ""; + cmCTestLog(this->CTest, HANDLER_OUTPUT, + " Processing coverage (each . represents one file):" << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + 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 ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush); + + // Call gcov to get coverage data for this *.gcda file: + // + std::string fileDir = cmSystemTools::GetFilenamePath(it->c_str()); + std::string command = "\"" + gcovCommand + "\" " + + gcovExtraFlags + " " + + "-o \"" + fileDir + "\" " + + "\"" + *it + "\""; + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, command.c_str() + << std::endl); + + std::string output = ""; + std::string errors = ""; + int retVal = 0; + *cont->OFS << "* Run coverage for: " << fileDir.c_str() << std::endl; + *cont->OFS << " Command: " << command.c_str() << std::endl; + int res = this->CTest->RunCommand(command.c_str(), &output, &errors, + &retVal, tempDir.c_str(), 0 /*this->TimeOut*/); + + *cont->OFS << " Output: " << output.c_str() << std::endl; + *cont->OFS << " Errors: " << errors.c_str() << std::endl; + if ( ! res ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Problem running coverage on file: " << it->c_str() << 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->c_str() << std::endl); + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Command produced error: " << cont->Error << std::endl); + } + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "--------------------------------------------------------------" + << std::endl + << output << std::endl + << "--------------------------------------------------------------" + << std::endl); + + std::vector<cmStdString> lines; + std::vector<cmStdString>::iterator line; + + cmSystemTools::Split(output.c_str(), lines); + + for ( line = lines.begin(); line != lines.end(); ++line) + { + std::string sourceFile; + std::string gcovFile; + + cmCTestLog(this->CTest, DEBUG, "Line: [" << line->c_str() << "]" + << std::endl); + + if ( line->size() == 0 ) + { + // 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; + } + + cmCTestLog(this->CTest, WARNING, "Warning: " << st2re4.match(1) + << " had unexpected EOF" << std::endl); + } + 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; + } + + cmCTestLog(this->CTest, WARNING, "Warning: Cannot open file: " + << st2re5.match(1) << std::endl); + } + 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; + } + + cmCTestLog(this->CTest, WARNING, "Warning: File: " << st2re6.match(1) + << " is newer than " << st2re6.match(2) << std::endl); + } + else + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Unknown gcov output line: [" << line->c_str() << "]" << 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]; + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " in gcovFile: " + << gcovFile << std::endl); + + std::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.size() ) + { + 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) ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " produced s: " + << sourceFile.c_str() << std::endl); + *cont->OFS << " produced in source dir: " << sourceFile.c_str() + << std::endl; + actualSourceFile + = cmSystemTools::CollapseFullPath(sourceFile.c_str()); + } + else if ( IsFileInDir(sourceFile, cont->BinaryDir) ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, " produced b: " + << sourceFile.c_str() << std::endl); + *cont->OFS << " produced in binary dir: " << sourceFile.c_str() + << std::endl; + actualSourceFile + = cmSystemTools::CollapseFullPath(sourceFile.c_str()); + } + + if ( actualSourceFile.empty() ) + { + if ( missingFiles.find(sourceFile) == missingFiles.end() ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Something went wrong" << std::endl); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Cannot find file: [" + << sourceFile.c_str() << "]" << std::endl); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in source dir: [" + << cont->SourceDir.c_str() << "]" + << std::endl); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " or binary dir: [" + << cont->BinaryDir.size() << "]" + << std::endl); + *cont->OFS << " Something went wrong. Cannot find file: " + << sourceFile.c_str() + << " in source dir: " << cont->SourceDir.c_str() + << " or binary dir: " << cont->BinaryDir.c_str() << std::endl; + + missingFiles.insert(sourceFile); + } + } + } + } + + file_count++; + + if ( file_count % 50 == 0 ) + { + cmCTestLog(this->CTest, HANDLER_OUTPUT, " processed: " << file_count + << " out of " << files.size() << std::endl); + cmCTestLog(this->CTest, HANDLER_OUTPUT, " "); + } + } + + cmSystemTools::ChangeDirectory(currentDirectory.c_str()); + 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. + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " globbing for coverage in: " << lmi->first << std::endl); + 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()); + } +} + +//---------------------------------------------------------------------- +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.size() == 0 ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Cannot find any Python Trace.py coverage files." + << std::endl); + // 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.c_str()); + + cmSystemTools::ChangeDirectory(currentDirectory.c_str()); + + 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->c_str() << std::endl); + continue; + } + + std::string actualSourceFile + = cmSystemTools::CollapseFullPath(fileName.c_str()); + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " Check coverage for file: " << actualSourceFile.c_str() + << std::endl); + cmCTestCoverageHandlerContainer::SingleFileCoverageVector* vec + = &cont->TotalCoverage[actualSourceFile]; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " in file: " << fileIt->c_str() << std::endl); + std::ifstream ifile(fileIt->c_str()); + if ( ! ifile ) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot open file: " + << fileIt->c_str() << std::endl); + } + else + { + long cnt = -1; + std::string nl; + while ( cmSystemTools::GetLineFromStream(ifile, nl) ) + { + cnt ++; + + // Skip empty lines + if ( !nl.size() ) + { + 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; + } + cmCTestLog(this->CTest, DEBUG, "Prefix: " << prefix.c_str() + << " cov: " << cov + << 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; + } + cmSystemTools::ChangeDirectory(currentDirectory.c_str()); + return file_count; +} + +//---------------------------------------------------------------------- +std::string cmCTestCoverageHandler::FindFile( + cmCTestCoverageHandlerContainer* cont, + std::string 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. ", + 0}; +} + +//---------------------------------------------------------------------- +int cmCTestCoverageHandler::RunBullseyeCoverageBranch( + cmCTestCoverageHandlerContainer* cont, + std::set<cmStdString>& 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; + int logFileCount = 0; + if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) + { + return -1; + } + // for each file run covbr on that file to get the coverage + // information for that file + std::string outputFile; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "run covbr: " + << std::endl); + + if(!this->RunBullseyeCommand(cont, "covbr", 0, outputFile)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covbr for." << "\n"); + return -1; + } + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "covbr output in " << outputFile + << std::endl); + // open the output file + std::ifstream fin(outputFile.c_str()); + if(!fin) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open coverage file: " << + outputFile.c_str() << std::endl); + return 0; + } + std::map<cmStdString, cmStdString> 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 + cmStdString 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) + { + covLogFile << "\t\t</Report>" << std::endl + << "\t</File>" << std::endl; + } + // only allow 100 files in each log file + if ( count != 0 && count % 100 == 0 ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "start a new log file: " + << count + << std::endl); + this->EndCoverageLogFile(covLogFile, logFileCount); + logFileCount ++; + if ( !this->StartCoverageLogFile(covLogFile, logFileCount) ) + { + return -1; + } + count++; // move on one + } + std::map<cmStdString, cmStdString>::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++; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Produce coverage for file: " + << file.c_str() << " " << count + << std::endl); + // start the file output + covLogFile << "\t<File Name=\"" + << cmXMLSafe(i->first) + << "\" FullPath=\"" << cmXMLSafe( + this->CTest->GetShortPathToFile( + i->second.c_str())) << "\">" << std::endl + << "\t\t<Report>" << std::endl; + // write the bullseye header + line =0; + for(int k =0; bullseyeHelp[k] != 0; ++k) + { + covLogFile << "\t\t<Line Number=\"" << line << "\" Count=\"-1\">" + << cmXMLSafe(bullseyeHelp[k]) + << "</Line>" << std::endl; + 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) + { + covLogFile << "\t\t<Line Number=\"" << line << "\" Count=\"-1\">" + << cmXMLSafe(lineIn) + << "</Line>" << std::endl; + line++; + } + } + // if we ran out of lines a valid file then close that file + if(valid) + { + covLogFile << "\t\t</Report>" << std::endl + << "\t</File>" << std::endl; + } + 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.size() == 0) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find :" << cmd << "\n"); + return 0; + } + if(arg) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run : " << program.c_str() << " " << arg << "\n"); + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Run : " << program.c_str() << "\n"); + } + // 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.c_str() << " " << 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; + if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open coverage summary file." << std::endl); + return 0; + } + this->CTest->StartXML(covSumFile, this->AppendXML); + double elapsed_time_start = cmSystemTools::GetTime(); + std::string coverage_start_time = this->CTest->CurrentTime(); + covSumFile << "<Coverage>" << std::endl + << "\t<StartDateTime>" + << coverage_start_time << "</StartDateTime>" + << std::endl + << "\t<StartTime>" + << static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</StartTime>" + << std::endl; + 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 + std::ifstream fin(outputFile.c_str()); + if(!fin) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Cannot open coverage summary file: " << + outputFile.c_str() << std::endl); + return 0; + } + std::set<cmStdString> coveredFileNames; + while(cmSystemTools::GetLineFromStream(fin, stdline)) + { + // if we have a line of output from stdout + if(stdline.size()) + { + // 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.c_str()); + bool shouldIDoCoverage + = this->ShouldIDoCoverage(file.c_str(), + cont->SourceDir.c_str(), + cont->BinaryDir.c_str()); + if ( !shouldIDoCoverage ) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + ".NoDartCoverage found, so skip coverage check for: " + << file.c_str() + << std::endl); + continue; + } + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Doing coverage for: " + << file.c_str() + << std::endl); + + 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.c_str()); + std::string shortFileName = + this->CTest->GetShortPathToFile(file.c_str()); + + float cper = static_cast<float>(percentBranch + percentFunction); + if(totalBranches > 0) + { + cper /= 2.0f; + } + percent_coverage += cper; + float cmet = static_cast<float>(percentFunction + percentBranch); + if(totalBranches > 0) + { + cmet /= 2.0f; + } + cmet /= 100.0f; + tmpLog << stdline.c_str() << "\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"; + covSumFile << "\t<File Name=\"" << cmXMLSafe(sourceFile) + << "\" FullPath=\"" << cmXMLSafe(shortFileName) + << "\" Covered=\"" << (cmet>0?"true":"false") << "\">\n" + << "\t\t<BranchesTested>" + << branchCovered + << "</BranchesTested>\n" + << "\t\t<BranchesUnTested>" + << totalBranches - branchCovered + << "</BranchesUnTested>\n" + << "\t\t<FunctionsTested>" + << functionsCalled + << "</FunctionsTested>\n" + << "\t\t<FunctionsUnTested>" + << totalFunctions - functionsCalled + << "</FunctionsUnTested>\n" + // Hack for conversion of function to loc assume a function + // has 100 lines of code + << "\t\t<LOCTested>" << functionsCalled *100 + << "</LOCTested>\n" + << "\t\t<LOCUnTested>" + << (totalFunctions - functionsCalled)*100 + << "</LOCUnTested>\n" + << "\t\t<PercentCoverage>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile << (cper) << "</PercentCoverage>\n" + << "\t\t<CoverageMetric>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile << (cmet) << "</CoverageMetric>\n"; + this->WriteXMLLabels(covSumFile, shortFileName); + covSumFile << "\t</File>" << std::endl; + } + } + std::string end_time = this->CTest->CurrentTime(); + covSumFile << "\t<LOCTested>" << total_tested << "</LOCTested>\n" + << "\t<LOCUntested>" << total_untested << "</LOCUntested>\n" + << "\t<LOC>" << total_functions << "</LOC>\n" + << "\t<PercentCoverage>"; + covSumFile.setf(std::ios::fixed, std::ios::floatfield); + covSumFile.precision(2); + covSumFile + << SAFEDIV(percent_coverage,number_files)<< "</PercentCoverage>\n" + << "\t<EndDateTime>" << end_time << "</EndDateTime>\n" + << "\t<EndTime>" << static_cast<unsigned int>(cmSystemTools::GetTime()) + << "</EndTime>\n"; + covSumFile + << "<ElapsedMinutes>" << + static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0 + << "</ElapsedMinutes>" + << "</Coverage>" << std::endl; + this->CTest->EndXML(covSumFile); + + // 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) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " COVFILE environment variable not found, not running " + " bullseye\n"); + return 0; + } + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " run covsrc with COVFILE=[" + << covfile + << "]" << std::endl); + if(!this->RunBullseyeSourceSummary(cont)) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Error running bullseye summary.\n"); + return 0; + } + cmCTestLog(this->CTest, DEBUG, "HandleBullseyeCoverage return 1 " + << std::endl); + 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.c_str() << "\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.c_str() << " 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"; + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " target directory list [" << fileList << "]\n"); + std::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"; + std::ifstream fin(fname.c_str()); + if(!fin) + { + return; + } + + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + " loading labels from [" << fname << "]\n"); + 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]; + for(std::vector<int>::const_iterator li = targetLabels.begin(); + li != targetLabels.end(); ++li) + { + labelSet.insert(*li); + } + } + } +} + +//---------------------------------------------------------------------- +void cmCTestCoverageHandler::WriteXMLLabels(std::ofstream& os, + std::string const& source) +{ + LabelMapType::const_iterator li = this->SourceLabels.find(source); + if(li != this->SourceLabels.end() && !li->second.empty()) + { + os << "\t\t<Labels>\n"; + for(LabelSet::const_iterator lsi = li->second.begin(); + lsi != li->second.end(); ++lsi) + { + os << "\t\t\t<Label>" << cmXMLSafe(this->Labels[*lsi]) << "</Label>\n"; + } + os << "\t\t</Labels>\n"; + } +} + +//---------------------------------------------------------------------------- +void +cmCTestCoverageHandler::SetLabelFilter(std::set<cmStdString> const& labels) +{ + this->LabelFilter.clear(); + for(std::set<cmStdString>::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; + cmsys_stl::set_intersection + (labels.begin(), labels.end(), + this->LabelFilter.begin(), this->LabelFilter.end(), + cmsys_stl::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<cmStdString>::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.size()) + { + 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; +} |