/*=========================================================================

  Program:   CMake - Cross-Platform Makefile Generator
  Module:    $RCSfile$
  Language:  C++
  Date:      $Date$
  Version:   $Revision$

  Copyright (c) 2002 Kitware, Inc., Insight Consortium.  All rights reserved.
  See Copyright.txt or http://www.cmake.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/

#include "cmCTestCoverageHandler.h"

#include "cmCTest.h"
#include "cmake.h"
#include "cmSystemTools.h"
#include "cmGeneratedFileStream.h"

#include <cmsys/Process.h>
#include <cmsys/RegularExpression.hxx>
#include <cmsys/Glob.hxx>

#include <stdlib.h>
#include <math.h>
#include <float.h>

#define SAFEDIV(x,y) (((y)!=0)?((x)/(y)):(0))

//----------------------------------------------------------------------
cmCTestCoverageHandler::cmCTestCoverageHandler()
{
}

//----------------------------------------------------------------------
void cmCTestCoverageHandler::Initialize()
{
  this->Superclass::Initialize();
  this->CustomCoverageExclude.empty();
}

//----------------------------------------------------------------------
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(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);
  covLogFile << "<CoverageLog>" << std::endl
    << "\t<StartDateTime>" << local_start_time << "</StartDateTime>"
    << 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
    << "</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)
{
  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 = cmSystemTools::RelativePath(checkDir.c_str(),
    fFile.c_str());
  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()
{
  int error = 0;

  std::string sourceDir
    = this->CTest->GetCTestConfiguration("SourceDirectory");
  std::string binaryDir
    = this->CTest->GetCTestConfiguration("BuildDirectory");
  std::string gcovCommand
    = this->CTest->GetCTestConfiguration("CoverageCommand");

  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;

  cmSystemTools::ConvertToUnixSlashes(sourceDir);
  cmSystemTools::ConvertToUnixSlashes(binaryDir);

  std::string asfGlob = sourceDir + "/*";
  std::string abfGlob = binaryDir + "/*";


  // 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 = "^(.*):creating [`'](.*\\.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());

  cmCTestLog(this->CTest, HANDLER_OUTPUT, "Performing coverage" << std::endl);

  std::string coverage_start_time = this->CTest->CurrentTime();

  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());

  cmsys::Glob gl;
  gl.RecurseOn();
  std::string daGlob = binaryDir + "/*.da";
  gl.FindFiles(daGlob);
  std::vector<std::string> files = gl.GetFiles();
  daGlob = binaryDir + "/*.gcda";
  gl.FindFiles(daGlob);
  std::vector<std::string>& moreFiles = gl.GetFiles();
  files.insert(files.end(), moreFiles.begin(), moreFiles.end());
  std::vector<std::string>::iterator it;

  if ( files.size() == 0 )
    {
    cmCTestLog(this->CTest, ERROR_MESSAGE, " Cannot find any coverage files."
      << std::endl);
    // No coverage files is a valid thing, so the exit code is 0
    return 0;
    }

  this->CustomCoverageExcludeRegex.empty();
  std::vector<cmStdString>::iterator rexIt;
  for ( rexIt = this->CustomCoverageExclude.begin();
    rexIt != this->CustomCoverageExclude.end();
    ++ rexIt )
    {
    this->CustomCoverageExcludeRegex.push_back(
      cmsys::RegularExpression(rexIt->c_str()));
    }

  typedef std::vector<int> singleFileCoverageVector;
  typedef std::map<std::string, singleFileCoverageVector> totalCoverageMap;

  totalCoverageMap totalCoverage;

  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;
  for ( it = files.begin(); it != files.end(); ++ it )
    {
    cmCTestLog(this->CTest, HANDLER_OUTPUT, "." << std::flush);
    std::string fileDir = cmSystemTools::GetFilenamePath(it->c_str());
    std::string command = "\"" + gcovCommand + "\" -l -o \"" + fileDir
      + "\" \"" + *it + "\"";
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, command.c_str()
      << std::endl);
    std::string output = "";
    std::string errors = "";
    int retVal = 0;
    ofs << "* Run coverage for: " << fileDir.c_str() << std::endl;
    ofs << "  Command: " << command.c_str() << std::endl;
    int res = this->CTest->RunCommand(command.c_str(), &output, &errors,
      &retVal, tempDir.c_str(), 0 /*this->TimeOut*/);

    ofs << "  Output: " << output.c_str() << std::endl;
    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);
      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: " << 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;


    // Globals for storing current source file and current gcov file;
    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 )
          {
          if ( gcovStyle != 1 )
            {
            cmCTestLog(this->CTest, ERROR_MESSAGE, "Unknown gcov output style"
              << std::endl);
            error ++;
            break;
            }
          gcovStyle = 1;
          }

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

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

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

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

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

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

        cmCTestLog(this->CTest, WARNING, "Warning: File: " << st2re6.match(1)
          << " is newer than " << st2re6.match(2) << std::endl);
        }
      else
        {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
          "Unknown line: [" << line->c_str() << "]" << std::endl);
        error ++;
        //abort();
        }
      if ( !gcovFile.empty() && actualSourceFile.size() )
        {
        singleFileCoverageVector* vec = &totalCoverage[actualSourceFile];
        cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "   in file: "
          << 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<singleFileCoverageVector::size_type>(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?
        if ( sourceFile.size() > sourceDir.size() &&
          sourceFile.substr(0, sourceDir.size()) == sourceDir &&
          sourceFile[sourceDir.size()] == '/' )
          {
          cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "   produced s: "
            << sourceFile.c_str() << std::endl);
          ofs << "  produced in source dir: " << sourceFile.c_str()
            << std::endl;
          actualSourceFile
            = cmSystemTools::CollapseFullPath(sourceFile.c_str());
          }
        // Binary dir?
        if ( sourceFile.size() > binaryDir.size() &&
          sourceFile.substr(0, binaryDir.size()) == binaryDir &&
          sourceFile[binaryDir.size()] == '/' )
          {
          cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "   produced b: "
            << sourceFile.c_str() << std::endl);
          ofs << "  produced in binary dir: " << sourceFile.c_str()
            << std::endl;
          actualSourceFile
            = cmSystemTools::CollapseFullPath(sourceFile.c_str());
          }
        if ( actualSourceFile.empty() )
          {
          if ( missingFiles.find(actualSourceFile) == missingFiles.end() )
            {
            cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
              "Something went wrong" << std::endl);
            cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "File: ["
              << sourceFile.c_str() << "]" << std::endl);
            cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "s: ["
              << sourceFile.substr(0, sourceDir.size()) << "]" << std::endl);
            cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "b: ["
              << sourceFile.substr(0, binaryDir.size()) << "]" << std::endl);
            ofs << "  Something went wrong. Cannot find: "
              << sourceFile.c_str()
              << " in source dir: " << sourceDir.c_str()
              << " or binary dir: " << binaryDir.c_str() << std::endl;
            missingFiles.insert(actualSourceFile);
            }
          }
        }
      }
    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, "    ");
      }
    }

  cmGeneratedFileStream covSumFile;
  cmGeneratedFileStream covLogFile;

  if (!this->StartResultingXML("Coverage", covSumFile))
    {
    cmCTestLog(this->CTest, ERROR_MESSAGE,
      "Cannot open coverage summary file." << std::endl);

    return -1;
    }

  this->CTest->StartXML(covSumFile);
  // Produce output xml files

  covSumFile << "<Coverage>" << std::endl
    << "\t<StartDateTime>" << coverage_start_time << "</StartDateTime>"
    << std::endl;
  int logFileCount = 0;
  if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
    {
    return -1;
    }
  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,
    "   Acumulating results (each . represents one file):" << std::endl);
  cmCTestLog(this->CTest, HANDLER_OUTPUT, "    ");

  std::vector<std::string> errorsWhileAccumulating;

  file_count = 0;
  for ( fileIterator = totalCoverage.begin();
    fileIterator != 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 "
        << totalCoverage.size() << std::endl);
      cmCTestLog(this->CTest, HANDLER_OUTPUT, "    ");
      }
    if ( cnt % 100 == 0 )
      {
      this->EndCoverageLogFile(covLogFile, logFileCount);
      logFileCount ++;
      if ( !this->StartCoverageLogFile(covLogFile, logFileCount) )
        {
        return -1;
        }
      }
    const std::string fullFileName = fileIterator->first;
    const std::string fileName
      = cmSystemTools::GetFilenameName(fullFileName.c_str());
    std::string fullFilePath
      = cmSystemTools::GetFilenamePath(fullFileName.c_str());
    cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Process file: "
      << fullFileName << std::endl);

    cmSystemTools::ConvertToUnixSlashes(fullFilePath);

    if ( !cmSystemTools::FileExists(fullFileName.c_str()) )
      {
      cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find file: "
        << fullFileName.c_str() << std::endl);
      continue;
      }

    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;
      }

    const singleFileCoverageVector& fcov = fileIterator->second;
    covLogFile << "\t<File Name=\""
      << this->CTest->MakeXMLSafe(fileName.c_str())
      << "\" FullPath=\"" << this->CTest->MakeXMLSafe(
        this->CTest->GetShortPathToFile(
          fileIterator->first.c_str())) << "\">" << std::endl
      << "\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;

    singleFileCoverageVector::size_type cc;
    std::string line;
    for ( cc= 0; cc < fcov.size(); cc ++ )
      {
      if ( !cmSystemTools::GetLineFromStream(ifs, line) )
        {
        cmOStringStream ostr;
        ostr << "Problem reading source file: " << fullFileName.c_str()
          << " line:" << cc;
        errorsWhileAccumulating.push_back(ostr.str());
        error ++;
        break;
        }
      covLogFile << "\t\t<Line Number=\"" << cc << "\" Count=\"" << fcov[cc]
        << "\">"
        << this->CTest->MakeXMLSafe(line.c_str()) << "</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=\"" << this->CTest->MakeXMLSafe(fileName)
      << "\" FullPath=\"" << this->CTest->MakeXMLSafe(
        this->CTest->GetShortPathToFile(fullFileName.c_str()))
      << "\" Covered=\"" << (cmet>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"
      << "\t</File>" << std::endl;
    cnt ++;
    }
  this->EndCoverageLogFile(covLogFile, logFileCount);

  if ( errorsWhileAccumulating.size() > 0 )
    {
    cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl);
    cmCTestLog(this->CTest, ERROR_MESSAGE,
      "Error(s) while acumulating 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);
      }
    }

  int 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";
  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, "\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;

  cmSystemTools::ChangeDirectory(currentDirectory.c_str());

  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);
  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);
    }
}
