cmCTestCoverageHandler.cxx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. /*=========================================================================
  2. Program: CMake - Cross-Platform Makefile Generator
  3. Module: $RCSfile$
  4. Language: C++
  5. Date: $Date$
  6. Version: $Revision$
  7. Copyright (c) 2002 Kitware, Inc., Insight Consortium. All rights reserved.
  8. See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.
  9. This software is distributed WITHOUT ANY WARRANTY; without even
  10. the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  11. PURPOSE. See the above copyright notices for more information.
  12. =========================================================================*/
  13. #include "cmCTestCoverageHandler.h"
  14. #include "cmCTest.h"
  15. #include "cmake.h"
  16. #include "cmSystemTools.h"
  17. #include "cmGeneratedFileStream.h"
  18. #include "cmGlob.h"
  19. #include <cmsys/Process.h>
  20. #include <cmsys/RegularExpression.hxx>
  21. #include <stdlib.h>
  22. #include <math.h>
  23. #include <float.h>
  24. #define SAFEDIV(x,y) (((y)!=0)?((x)/(y)):(0))
  25. //----------------------------------------------------------------------
  26. cmCTestCoverageHandler::cmCTestCoverageHandler()
  27. {
  28. m_Verbose = false;
  29. m_CTest = 0;
  30. }
  31. //----------------------------------------------------------------------
  32. bool cmCTestCoverageHandler::StartLogFile(cmGeneratedFileStream& covLogFile, int logFileCount)
  33. {
  34. char covLogFilename[1024];
  35. sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount);
  36. std::cout << "Open file: " << covLogFilename << std::endl;
  37. if (!m_CTest->OpenOutputFile(m_CTest->GetCurrentTag(),
  38. covLogFilename, covLogFile, true))
  39. {
  40. std::cerr << "Cannot open log file: " << covLogFilename << std::endl;
  41. return false;
  42. }
  43. std::string local_start_time = m_CTest->CurrentTime();
  44. m_CTest->StartXML(covLogFile);
  45. covLogFile << "<CoverageLog>" << std::endl
  46. << "\t<StartDateTime>" << local_start_time << "</StartDateTime>" << std::endl;
  47. return true;
  48. }
  49. //----------------------------------------------------------------------
  50. void cmCTestCoverageHandler::EndLogFile(cmGeneratedFileStream& ostr, int logFileCount)
  51. {
  52. std::string local_end_time = m_CTest->CurrentTime();
  53. ostr << "\t<EndDateTime>" << local_end_time << "</EndDateTime>" << std::endl
  54. << "</CoverageLog>" << std::endl;
  55. m_CTest->EndXML(ostr);
  56. char covLogFilename[1024];
  57. sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount);
  58. std::cout << "Close file: " << covLogFilename << std::endl;
  59. ostr.close();
  60. }
  61. //----------------------------------------------------------------------
  62. bool cmCTestCoverageHandler::ShouldIDoCoverage(const char* file, const char* srcDir,
  63. const char* binDir, bool verbose)
  64. {
  65. std::string fSrcDir = cmSystemTools::CollapseFullPath(srcDir);
  66. std::string fBinDir = cmSystemTools::CollapseFullPath(binDir);
  67. std::string fFile = cmSystemTools::CollapseFullPath(file);
  68. bool sourceSubDir = cmSystemTools::IsSubDirectory(fFile.c_str(), fSrcDir.c_str());
  69. bool buildSubDir = cmSystemTools::IsSubDirectory(fFile.c_str(), fBinDir.c_str());
  70. // Always check parent directory of the file.
  71. std::string fileDir = cmSystemTools::GetFilenamePath(fFile.c_str());
  72. std::string checkDir;
  73. // We also need to check the binary/source directory pair.
  74. if ( sourceSubDir && buildSubDir )
  75. {
  76. if ( fSrcDir.size() > fBinDir.size() )
  77. {
  78. checkDir = fSrcDir;
  79. }
  80. else
  81. {
  82. checkDir = fBinDir;
  83. }
  84. }
  85. else if ( sourceSubDir )
  86. {
  87. checkDir = fSrcDir;
  88. }
  89. else if ( buildSubDir )
  90. {
  91. checkDir = fBinDir;
  92. }
  93. std::string ndc
  94. = cmSystemTools::FileExistsInParentDirectories(".NoDartCoverage",
  95. fFile.c_str(), checkDir.c_str());
  96. if ( ndc.size() )
  97. {
  98. if ( verbose )
  99. {
  100. std::cout << "Found: " << ndc.c_str() << " so skip coverage of " << file << std::endl;
  101. }
  102. return false;
  103. }
  104. // By now checkDir should be set to parent directory of the file.
  105. // Get the relative path to the file an apply it to the opposite directory.
  106. // If it is the same as fileDir, then ignore, otherwise check.
  107. std::string relPath = cmSystemTools::RelativePath(checkDir.c_str(),
  108. fFile.c_str());
  109. if ( checkDir == fSrcDir )
  110. {
  111. checkDir = fBinDir;
  112. }
  113. else
  114. {
  115. checkDir = fSrcDir;
  116. }
  117. fFile = checkDir + "/" + relPath;
  118. fFile = cmSystemTools::GetFilenamePath(fFile.c_str());
  119. if ( fileDir == fFile )
  120. {
  121. // This is in-source build, so we trust the previous check.
  122. return true;
  123. }
  124. ndc = cmSystemTools::FileExistsInParentDirectories(".NoDartCoverage",
  125. fFile.c_str(), checkDir.c_str());
  126. if ( ndc.size() )
  127. {
  128. if ( verbose )
  129. {
  130. std::cout << "Found: " << ndc.c_str() << " so skip coverage of: " << file << std::endl;
  131. }
  132. return false;
  133. }
  134. // Ok, nothing in source tree, nothing in binary tree
  135. return true;
  136. }
  137. //----------------------------------------------------------------------
  138. //clearly it would be nice if this were broken up into a few smaller
  139. //functions and commented...
  140. int cmCTestCoverageHandler::ProcessHandler()
  141. {
  142. int error = 0;
  143. std::string sourceDir = m_CTest->GetCTestConfiguration("SourceDirectory");
  144. std::string binaryDir = m_CTest->GetCTestConfiguration("BuildDirectory");
  145. std::string gcovCommand = m_CTest->GetCTestConfiguration("CoverageCommand");
  146. cmSystemTools::ConvertToUnixSlashes(sourceDir);
  147. cmSystemTools::ConvertToUnixSlashes(binaryDir);
  148. std::string asfGlob = sourceDir + "/*";
  149. std::string abfGlob = binaryDir + "/*";
  150. std::string daGlob = binaryDir + "/*.da";
  151. std::string gcovOutputRex = "[0-9]+\\.[0-9]+% of [0-9]+ (source |)lines executed in file (.*)$";
  152. std::string gcovOutputRex2 = "^Creating (.*\\.gcov)\\.";
  153. std::cout << "Performing coverage" << std::endl;
  154. double elapsed_time_start = cmSystemTools::GetTime();
  155. std::string coverage_start_time = m_CTest->CurrentTime();
  156. std::string testingDir = m_CTest->GetBinaryDir() + "/Testing";
  157. std::string tempDir = testingDir + "/CoverageInfo";
  158. std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
  159. cmSystemTools::MakeDirectory(tempDir.c_str());
  160. cmSystemTools::ChangeDirectory(tempDir.c_str());
  161. cmGlob gl;
  162. gl.RecurseOn();
  163. gl.FindFiles(daGlob);
  164. std::vector<std::string> files = gl.GetFiles();
  165. std::vector<std::string>::iterator it;
  166. if ( files.size() == 0 )
  167. {
  168. std::cerr << " Cannot find any coverage files." << std::endl;
  169. // No coverage files is a valid thing, so the exit code is 0
  170. return 0;
  171. }
  172. // Regular expressions for output of gcov
  173. cmsys::RegularExpression re(gcovOutputRex.c_str());
  174. cmsys::RegularExpression re2(gcovOutputRex2.c_str());
  175. typedef std::vector<int> singleFileCoverageVector;
  176. typedef std::map<std::string, singleFileCoverageVector> totalCoverageMap;
  177. totalCoverageMap totalCoverage;
  178. std::string cfile = "";
  179. for ( it = files.begin(); it != files.end(); ++ it )
  180. {
  181. std::string fileDir = cmSystemTools::GetFilenamePath(it->c_str());
  182. std::string command = "\"" + gcovCommand + "\" -l -o \"" + fileDir + "\" \"" + *it + "\"";
  183. if ( m_Verbose )
  184. {
  185. std::cout << command.c_str() << std::endl;
  186. }
  187. std::string output = "";
  188. int retVal = 0;
  189. int res = cmSystemTools::RunSingleCommand(command.c_str(), &output,
  190. &retVal, tempDir.c_str(),
  191. false, 0 /*m_TimeOut*/);
  192. if ( ! res )
  193. {
  194. std::cerr << "Problem running coverage on file: " << it->c_str() << std::endl;
  195. error ++;
  196. continue;
  197. }
  198. if ( retVal != 0 )
  199. {
  200. std::cerr << "Coverage command returned: " << retVal << " while processing: " << it->c_str() << std::endl;
  201. }
  202. std::vector<cmStdString> lines;
  203. std::vector<cmStdString>::iterator line;
  204. cmSystemTools::Split(output.c_str(), lines);
  205. for ( line = lines.begin(); line != lines.end(); ++line)
  206. {
  207. if ( re.find(line->c_str()) )
  208. {
  209. cfile = "";
  210. std::string file = re.match(2);
  211. // Is it in the source dir?
  212. if ( file.size() > sourceDir.size() &&
  213. file.substr(0, sourceDir.size()) == sourceDir &&
  214. file[sourceDir.size()] == '/' )
  215. {
  216. if ( m_Verbose )
  217. {
  218. std::cout << " produced s: " << file << std::endl;
  219. }
  220. cfile = file;
  221. }
  222. // Binary dir?
  223. if ( file.size() > binaryDir.size() &&
  224. file.substr(0, binaryDir.size()) == binaryDir &&
  225. file[binaryDir.size()] == '/' )
  226. {
  227. if ( m_Verbose )
  228. {
  229. std::cout << " produce b: " << file << std::endl;
  230. }
  231. cfile = file;
  232. }
  233. if ( cfile.empty() )
  234. {
  235. std::cerr << "Something went wrong" << std::endl;
  236. std::cerr << "File: [" << file << "]" << std::endl;
  237. std::cerr << "s: [" << file.substr(0, sourceDir.size()) << "]" << std::endl;
  238. std::cerr << "b: [" << file.substr(0, binaryDir.size()) << "]" << std::endl;
  239. }
  240. }
  241. else if ( re2.find(line->c_str() ) )
  242. {
  243. std::string fname = re2.match(1);
  244. if ( cfile.size() )
  245. {
  246. singleFileCoverageVector* vec = &totalCoverage[cfile];
  247. if ( m_Verbose )
  248. {
  249. std::cout << " in file: " << fname << std::endl;
  250. }
  251. std::ifstream ifile(fname.c_str());
  252. if ( ! ifile )
  253. {
  254. std::cerr << "Cannot open file: " << fname << std::endl;
  255. }
  256. else
  257. {
  258. long cnt = -1;
  259. std::string nl;
  260. while ( cmSystemTools::GetLineFromStream(ifile, nl) )
  261. {
  262. cnt ++;
  263. if ( vec->size() <= static_cast<singleFileCoverageVector::size_type>(cnt) )
  264. {
  265. vec->push_back(-1);
  266. }
  267. //TODO: Handle gcov 3.0 non-coverage lines
  268. // Skip empty lines
  269. if ( !nl.size() )
  270. {
  271. continue;
  272. }
  273. // Skip unused lines
  274. if ( nl[0] == '\t' || nl.size() < 12 )
  275. {
  276. continue;
  277. }
  278. std::string prefix = nl.substr(0, 12);
  279. int cov = atoi(prefix.c_str());
  280. (*vec)[cnt] += cov;
  281. }
  282. }
  283. }
  284. }
  285. else
  286. {
  287. std::cerr << "Unknown line: " << line->c_str() << std::endl;
  288. error ++;
  289. }
  290. }
  291. }
  292. cmGeneratedFileStream covSumFile;
  293. cmGeneratedFileStream covLogFile;
  294. if (!m_CTest->OpenOutputFile(m_CTest->GetCurrentTag(),
  295. "Coverage.xml", covSumFile, true))
  296. {
  297. std::cerr << "Cannot open coverage summary file: Coverage.xml" << std::endl;
  298. return -1;
  299. }
  300. m_CTest->StartXML(covSumFile);
  301. // Produce output xml files
  302. covSumFile << "<Coverage>" << std::endl
  303. << "\t<StartDateTime>" << coverage_start_time << "</StartDateTime>" << std::endl;
  304. int logFileCount = 0;
  305. if ( !this->StartLogFile(covLogFile, logFileCount) )
  306. {
  307. return -1;
  308. }
  309. totalCoverageMap::iterator fileIterator;
  310. int cnt = 0;
  311. long total_tested = 0;
  312. long total_untested = 0;
  313. std::string fullSourceDir = sourceDir + "/";
  314. std::string fullBinaryDir = binaryDir + "/";
  315. for ( fileIterator = totalCoverage.begin();
  316. fileIterator != totalCoverage.end();
  317. ++fileIterator )
  318. {
  319. if ( cnt == 100 )
  320. {
  321. cnt = 0;
  322. this->EndLogFile(covLogFile, logFileCount);
  323. logFileCount ++;
  324. if ( !this->StartLogFile(covLogFile, logFileCount) )
  325. {
  326. return -1;
  327. }
  328. }
  329. const std::string fullFileName = fileIterator->first;
  330. const std::string fileName = cmSystemTools::GetFilenameName(fullFileName.c_str());
  331. std::string fullFilePath = cmSystemTools::GetFilenamePath(fullFileName.c_str());
  332. if ( m_Verbose )
  333. {
  334. std::cerr << "Process file: " << fullFileName << std::endl;
  335. }
  336. cmSystemTools::ConvertToUnixSlashes(fullFilePath);
  337. if ( !cmSystemTools::FileExists(fullFileName.c_str()) )
  338. {
  339. std::cerr << "Cannot find file: " << fullFileName.c_str() << std::endl;
  340. continue;
  341. }
  342. bool shouldIDoCoverage
  343. = this->ShouldIDoCoverage(fullFileName.c_str(),
  344. sourceDir.c_str(), binaryDir.c_str(), m_Verbose);
  345. if ( !shouldIDoCoverage )
  346. {
  347. if ( m_Verbose )
  348. {
  349. std::cerr << ".NoDartCoverage found, so skip coverage check for: "
  350. << fullFileName.c_str()
  351. << std::endl;
  352. }
  353. continue;
  354. }
  355. const singleFileCoverageVector& fcov = fileIterator->second;
  356. covLogFile << "\t<File Name=\""
  357. << m_CTest->MakeXMLSafe(fileName.c_str())
  358. << "\" FullPath=\"" << m_CTest->MakeXMLSafe(m_CTest->GetShortPathToFile(
  359. fileIterator->first.c_str())) << "\">" << std::endl
  360. << "\t\t<Report>" << std::endl;
  361. std::ifstream ifs(fullFileName.c_str());
  362. if ( !ifs)
  363. {
  364. std::cerr << "Cannot open source file: " << fullFileName.c_str() << std::endl;
  365. error ++;
  366. continue;
  367. }
  368. int tested = 0;
  369. int untested = 0;
  370. singleFileCoverageVector::size_type cc;
  371. std::string line;
  372. for ( cc= 0; cc < fcov.size(); cc ++ )
  373. {
  374. if ( !cmSystemTools::GetLineFromStream(ifs, line) )
  375. {
  376. std::cerr << "Problem reading source file: " << fullFileName.c_str() << " line:" << cc << std::endl;
  377. error ++;
  378. break;
  379. }
  380. covLogFile << "\t\t<Line Number=\"" << cc << "\" Count=\"" << fcov[cc] << "\">"
  381. << m_CTest->MakeXMLSafe(line.c_str()) << "</Line>" << std::endl;
  382. if ( fcov[cc] == 0 )
  383. {
  384. untested ++;
  385. }
  386. else if ( fcov[cc] > 0 )
  387. {
  388. tested ++;
  389. }
  390. }
  391. if ( cmSystemTools::GetLineFromStream(ifs, line) )
  392. {
  393. std::cerr << "Looks like there are more lines in the file: " << line << std::endl;
  394. }
  395. float cper = 0;
  396. float cmet = 0;
  397. if ( tested + untested > 0 )
  398. {
  399. cper = (100 * SAFEDIV(static_cast<float>(tested),
  400. static_cast<float>(tested + untested)));
  401. cmet = ( SAFEDIV(static_cast<float>(tested + 10),
  402. static_cast<float>(tested + untested + 10)));
  403. }
  404. total_tested += tested;
  405. total_untested += untested;
  406. covLogFile << "\t\t</Report>" << std::endl
  407. << "\t</File>" << std::endl;
  408. covSumFile << "\t<File Name=\"" << m_CTest->MakeXMLSafe(fileName)
  409. << "\" FullPath=\"" << m_CTest->MakeXMLSafe(
  410. m_CTest->GetShortPathToFile(fullFileName.c_str()))
  411. << "\" Covered=\"" << (cmet>0?"true":"false") << "\">\n"
  412. << "\t\t<LOCTested>" << tested << "</LOCTested>\n"
  413. << "\t\t<LOCUnTested>" << untested << "</LOCUnTested>\n"
  414. << "\t\t<PercentCoverage>";
  415. covSumFile.setf(std::ios::fixed, std::ios::floatfield);
  416. covSumFile.precision(2);
  417. covSumFile << (cper) << "</PercentCoverage>\n"
  418. << "\t\t<CoverageMetric>";
  419. covSumFile.setf(std::ios::fixed, std::ios::floatfield);
  420. covSumFile.precision(2);
  421. covSumFile << (cmet) << "</CoverageMetric>\n"
  422. << "\t</File>" << std::endl;
  423. cnt ++;
  424. }
  425. this->EndLogFile(covLogFile, logFileCount);
  426. int total_lines = total_tested + total_untested;
  427. float percent_coverage = 100 * SAFEDIV(static_cast<float>(total_tested),
  428. static_cast<float>(total_lines));
  429. if ( total_lines == 0 )
  430. {
  431. percent_coverage = 0;
  432. }
  433. std::string end_time = m_CTest->CurrentTime();
  434. covSumFile << "\t<LOCTested>" << total_tested << "</LOCTested>\n"
  435. << "\t<LOCUntested>" << total_untested << "</LOCUntested>\n"
  436. << "\t<LOC>" << total_lines << "</LOC>\n"
  437. << "\t<PercentCoverage>";
  438. covSumFile.setf(std::ios::fixed, std::ios::floatfield);
  439. covSumFile.precision(2);
  440. covSumFile << (percent_coverage)<< "</PercentCoverage>\n"
  441. << "\t<EndDateTime>" << end_time << "</EndDateTime>\n";
  442. covSumFile << "<ElapsedMinutes>" <<
  443. static_cast<int>((cmSystemTools::GetTime() - elapsed_time_start)/6)/10.0
  444. << "</ElapsedMinutes>"
  445. << "</Coverage>" << std::endl;
  446. m_CTest->EndXML(covSumFile);
  447. std::cout << "\tCovered LOC: " << total_tested << std::endl
  448. << "\tNot covered LOC: " << total_untested << std::endl
  449. << "\tTotal LOC: " << total_lines << std::endl
  450. << "\tPercentage Coverage: ";
  451. std::cout.setf(std::ios::fixed, std::ios::floatfield);
  452. std::cout.precision(2);
  453. std::cout << (percent_coverage) << "%" << std::endl;
  454. cmSystemTools::ChangeDirectory(currentDirectory.c_str());
  455. if ( error )
  456. {
  457. return -1;
  458. }
  459. return 0;
  460. }