int cmCTestCoverageHandler::HandleGCovCoverage()

in Source/CTest/cmCTestCoverageHandler.cxx [851:1248]


int cmCTestCoverageHandler::HandleGCovCoverage(
  cmCTestCoverageHandlerContainer* cont)
{
  std::string gcovCommand =
    this->CTest->GetCTestConfiguration("CoverageCommand");
  if (gcovCommand.empty()) {
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "Could not find gcov." << std::endl, this->Quiet);
    return 0;
  }
  std::string gcovExtraFlags =
    this->CTest->GetCTestConfiguration("CoverageExtraFlags");

  // Immediately skip to next coverage option since codecov is only for Intel
  // compiler
  if (gcovCommand == "codecov") {
    return 0;
  }

  // Style 1
  std::string st1gcovOutputRex1 =
    "[0-9]+\\.[0-9]+% of [0-9]+ (source |)lines executed in file (.*)$";
  std::string st1gcovOutputRex2 = "^Creating (.*\\.gcov)\\.";
  cmsys::RegularExpression st1re1(st1gcovOutputRex1.c_str());
  cmsys::RegularExpression st1re2(st1gcovOutputRex2.c_str());

  // Style 2
  std::string st2gcovOutputRex1 = "^File *[`'](.*)'$";
  std::string st2gcovOutputRex2 =
    "Lines executed: *[0-9]+\\.[0-9]+% of [0-9]+$";
  std::string st2gcovOutputRex3 = "^(.*)reating [`'](.*\\.gcov)'";
  std::string st2gcovOutputRex4 = "^(.*):unexpected EOF *$";
  std::string st2gcovOutputRex5 = "^(.*):cannot open source file*$";
  std::string st2gcovOutputRex6 =
    "^(.*):source file is newer than graph file `(.*)'$";
  cmsys::RegularExpression st2re1(st2gcovOutputRex1.c_str());
  cmsys::RegularExpression st2re2(st2gcovOutputRex2.c_str());
  cmsys::RegularExpression st2re3(st2gcovOutputRex3.c_str());
  cmsys::RegularExpression st2re4(st2gcovOutputRex4.c_str());
  cmsys::RegularExpression st2re5(st2gcovOutputRex5.c_str());
  cmsys::RegularExpression st2re6(st2gcovOutputRex6.c_str());

  std::vector<std::string> files;
  this->FindGCovFiles(files);

  if (files.empty()) {
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " Cannot find any GCov coverage files." << std::endl,
                       this->Quiet);
    // No coverage files is a valid thing, so the exit code is 0
    return 0;
  }

  std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
  std::string tempDir = testingDir + "/CoverageInfo";
  if (!cmSystemTools::MakeDirectory(tempDir)) {
    cmCTestLog(this->CTest, ERROR_MESSAGE,
               "Unable to make directory: " << tempDir << std::endl);
    cont->Error++;
    return 0;
  }
  cmWorkingDirectory workdir(tempDir);

  int gcovStyle = 0;

  std::set<std::string> missingFiles;

  std::string actualSourceFile;
  cmCTestOptionalLog(
    this->CTest, HANDLER_OUTPUT,
    "   Processing coverage (each . represents one file):" << std::endl,
    this->Quiet);
  cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "    ", this->Quiet);
  int file_count = 0;

  // make sure output from gcov is in English!
  cmCTestCoverageHandlerLocale locale_C;
  static_cast<void>(locale_C);

  std::vector<std::string> basecovargs =
    cmSystemTools::ParseArguments(gcovExtraFlags);
  basecovargs.insert(basecovargs.begin(), gcovCommand);
  basecovargs.emplace_back("-o");

  // files is a list of *.da and *.gcda files with coverage data in them.
  // These are binary files that you give as input to gcov so that it will
  // give us text output we can analyze to summarize coverage.
  //
  for (std::string const& f : files) {
    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
                       this->Quiet);

    // Call gcov to get coverage data for this *.gcda file:
    //
    std::string fileDir = cmSystemTools::GetFilenamePath(f);
    std::vector<std::string> covargs = basecovargs;
    covargs.push_back(fileDir);
    covargs.push_back(f);
    std::string const command = joinCommandLine(covargs);

    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       command << std::endl, this->Quiet);

    std::string output;
    std::string errors;
    int retVal = 0;
    *cont->OFS << "* Run coverage for: " << fileDir << std::endl;
    *cont->OFS << "  Command: " << command << std::endl;
    int res = this->CTest->RunCommand(covargs, &output, &errors, &retVal,
                                      tempDir.c_str(),
                                      cmDuration::zero() /*this->TimeOut*/);

    *cont->OFS << "  Output: " << output << std::endl;
    *cont->OFS << "  Errors: " << errors << std::endl;
    if (!res) {
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Problem running coverage on file: " << f << std::endl);
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Command produced error: " << errors << std::endl);
      cont->Error++;
      continue;
    }
    if (retVal != 0) {
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Coverage command returned: "
                   << retVal << " while processing: " << f << std::endl);
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Command produced error: " << cont->Error << std::endl);
    }
    cmCTestOptionalLog(
      this->CTest, HANDLER_VERBOSE_OUTPUT,
      "--------------------------------------------------------------"
        << std::endl
        << output << std::endl
        << "--------------------------------------------------------------"
        << std::endl,
      this->Quiet);

    std::vector<std::string> lines;
    cmsys::SystemTools::Split(output, lines);

    for (std::string const& line : lines) {
      std::string sourceFile;
      std::string gcovFile;

      cmCTestOptionalLog(this->CTest, DEBUG,
                         "Line: [" << line << "]" << std::endl, this->Quiet);

      if (line.empty()) {
        // Ignore empty line; probably style 2
      } else if (st1re1.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 1;
        }
        if (gcovStyle != 1) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e1" << std::endl);
          cont->Error++;
          break;
        }

        actualSourceFile.clear();
        sourceFile = st1re1.match(2);
      } else if (st1re2.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 1;
        }
        if (gcovStyle != 1) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e2" << std::endl);
          cont->Error++;
          break;
        }

        gcovFile = st1re2.match(1);
      } else if (st2re1.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e3" << std::endl);
          cont->Error++;
          break;
        }

        actualSourceFile.clear();
        sourceFile = st2re1.match(1);
      } else if (st2re2.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e4" << std::endl);
          cont->Error++;
          break;
        }
      } else if (st2re3.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e5" << std::endl);
          cont->Error++;
          break;
        }

        gcovFile = st2re3.match(2);
      } else if (st2re4.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e6" << std::endl);
          cont->Error++;
          break;
        }

        cmCTestOptionalLog(this->CTest, WARNING,
                           "Warning: " << st2re4.match(1)
                                       << " had unexpected EOF" << std::endl,
                           this->Quiet);
      } else if (st2re5.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e7" << std::endl);
          cont->Error++;
          break;
        }

        cmCTestOptionalLog(this->CTest, WARNING,
                           "Warning: Cannot open file: " << st2re5.match(1)
                                                         << std::endl,
                           this->Quiet);
      } else if (st2re6.find(line)) {
        if (gcovStyle == 0) {
          gcovStyle = 2;
        }
        if (gcovStyle != 2) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output style e8" << std::endl);
          cont->Error++;
          break;
        }

        cmCTestOptionalLog(this->CTest, WARNING,
                           "Warning: File: " << st2re6.match(1)
                                             << " is newer than "
                                             << st2re6.match(2) << std::endl,
                           this->Quiet);
      } else {
        // gcov 4.7 can have output lines saying "No executable lines" and
        // "Removing 'filename.gcov'"... Don't log those as "errors."
        if (line != "No executable lines" &&
            !cmHasLiteralPrefix(line, "Removing ")) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Unknown gcov output line: [" << line << "]"
                                                   << std::endl);
          cont->Error++;
          // abort();
        }
      }

      // If the last line of gcov output gave us a valid value for gcovFile,
      // and we have an actualSourceFile, then insert a (or add to existing)
      // SingleFileCoverageVector for actualSourceFile:
      //
      if (!gcovFile.empty() && !actualSourceFile.empty()) {
        cmCTestCoverageHandlerContainer::SingleFileCoverageVector& vec =
          cont->TotalCoverage[actualSourceFile];

        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "   in gcovFile: " << gcovFile << std::endl,
                           this->Quiet);

        cmsys::ifstream ifile(gcovFile.c_str());
        if (!ifile) {
          cmCTestLog(this->CTest, ERROR_MESSAGE,
                     "Cannot open file: " << gcovFile << std::endl);
        } else {
          std::string nl;
          while (cmSystemTools::GetLineFromStream(ifile, nl)) {
            // Skip empty lines
            if (nl.empty()) {
              continue;
            }

            // Skip unused lines
            if (nl.size() < 12) {
              continue;
            }

            // Handle gcov 3.0 non-coverage lines
            // non-coverage lines seem to always start with something not
            // a space and don't have a ':' in the 9th position
            // TODO: Verify that this is actually a robust metric
            if (nl[0] != ' ' && nl[9] != ':') {
              continue;
            }

            // Read the coverage count from the beginning of the gcov output
            // line
            std::string prefix = nl.substr(0, 12);
            int cov = atoi(prefix.c_str());

            // Read the line number starting at the 10th character of the gcov
            // output line
            std::string lineNumber = nl.substr(10, 5);

            int lineIdx = atoi(lineNumber.c_str()) - 1;
            if (lineIdx >= 0) {
              while (vec.size() <= static_cast<size_t>(lineIdx)) {
                vec.push_back(-1);
              }

              // Initially all entries are -1 (not used). If we get coverage
              // information, increment it to 0 first.
              if (vec[lineIdx] < 0) {
                if (cov > 0 || prefix.find('#') != std::string::npos) {
                  vec[lineIdx] = 0;
                }
              }

              vec[lineIdx] += cov;
            }
          }
        }

        actualSourceFile.clear();
      }

      if (!sourceFile.empty() && actualSourceFile.empty()) {
        gcovFile.clear();

        // Is it in the source dir or the binary dir?
        //
        if (IsFileInDir(sourceFile, cont->SourceDir)) {
          cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                             "   produced s: " << sourceFile << std::endl,
                             this->Quiet);
          *cont->OFS << "  produced in source dir: " << sourceFile
                     << std::endl;
          actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile);
        } else if (IsFileInDir(sourceFile, cont->BinaryDir)) {
          cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                             "   produced b: " << sourceFile << std::endl,
                             this->Quiet);
          *cont->OFS << "  produced in binary dir: " << sourceFile
                     << std::endl;
          actualSourceFile = cmSystemTools::CollapseFullPath(sourceFile);
        }

        if (actualSourceFile.empty()) {
          if (missingFiles.find(sourceFile) == missingFiles.end()) {
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "Something went wrong" << std::endl,
                               this->Quiet);
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "Cannot find file: [" << sourceFile << "]"
                                                     << std::endl,
                               this->Quiet);
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               " in source dir: [" << cont->SourceDir << "]"
                                                   << std::endl,
                               this->Quiet);
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               " or binary dir: [" << cont->BinaryDir.size()
                                                   << "]" << std::endl,
                               this->Quiet);
            *cont->OFS << "  Something went wrong. Cannot find file: "
                       << sourceFile << " in source dir: " << cont->SourceDir
                       << " or binary dir: " << cont->BinaryDir << std::endl;

            missingFiles.insert(sourceFile);
          }
        }
      }
    }

    file_count++;

    if (file_count % 50 == 0) {
      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                         " processed: " << file_count << " out of "
                                        << files.size() << std::endl,
                         this->Quiet);
      cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "    ", this->Quiet);
    }
  }

  return file_count;
}