/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmCTestCoverageHandler.h"

#include "cmCTest.h"
#include "cmDuration.h"
#include "cmGeneratedFileStream.h"
#include "cmParseBlanketJSCoverage.h"
#include "cmParseCacheCoverage.h"
#include "cmParseCoberturaCoverage.h"
#include "cmParseDelphiCoverage.h"
#include "cmParseGTMCoverage.h"
#include "cmParseJacocoCoverage.h"
#include "cmParsePHPCoverage.h"
#include "cmSystemTools.h"
#include "cmWorkingDirectory.h"
#include "cmXMLWriter.h"
#include "cmake.h"

#include "cmsys/FStream.hxx"
#include "cmsys/Glob.hxx"
#include "cmsys/Process.h"
#include "cmsys/RegularExpression.hxx"
#include <algorithm>
#include <chrono>
#include <cstring>
#include <iomanip>
#include <iterator>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <utility>

class cmMakefile;

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

class cmCTestRunProcess
{
public:
    cmCTestRunProcess()
    {
        this->Process   = cmsysProcess_New();
        this->PipeState = -1;
        this->TimeOut   = cmDuration(-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(cmDuration t) { this->TimeOut = t; }
    bool StartProcess()
    {
        std::vector<const char*> args;
        for(std::string const& cl : this->CommandLineStrings)
        {
            args.push_back(cl.c_str());
        }
        args.push_back(nullptr);  // null terminate
        cmsysProcess_SetCommand(this->Process, &*args.begin());
        if(!this->WorkingDirectory.empty())
        {
            cmsysProcess_SetWorkingDirectory(this->Process,
                                             this->WorkingDirectory.c_str());
        }

        cmsysProcess_SetOption(this->Process, cmsysProcess_Option_HideWindow,
                               1);
        if(this->TimeOut >= cmDuration::zero())
        {
            cmsysProcess_SetTimeout(this->Process, this->TimeOut.count());
        }
        cmsysProcess_Execute(this->Process);
        this->PipeState = cmsysProcess_GetState(this->Process);
        // if the process is running or exited return true
        return this->PipeState == cmsysProcess_State_Executing ||
               this->PipeState == cmsysProcess_State_Exited;
    }
    void SetStdoutFile(const char* fname)
    {
        cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDOUT,
                                 fname);
    }
    void SetStderrFile(const char* fname)
    {
        cmsysProcess_SetPipeFile(this->Process, cmsysProcess_Pipe_STDERR,
                                 fname);
    }
    int WaitForExit(double* timeout = nullptr)
    {
        this->PipeState = cmsysProcess_WaitForExit(this->Process, timeout);
        return this->PipeState;
    }
    int GetProcessState() { return this->PipeState; }

private:
    int                      PipeState;
    cmsysProcess*            Process;
    std::vector<std::string> CommandLineStrings;
    std::string              WorkingDirectory;
    cmDuration               TimeOut;
};

cmCTestCoverageHandler::cmCTestCoverageHandler() {}

void
cmCTestCoverageHandler::Initialize()
{
    this->Superclass::Initialize();
    this->CustomCoverageExclude.clear();
    this->SourceLabels.clear();
    this->TargetDirs.clear();
    this->LabelIdMap.clear();
    this->Labels.clear();
    this->LabelFilter.clear();
}

void
cmCTestCoverageHandler::CleanCoverageLogFiles(std::ostream& log)
{
    std::string logGlob = this->CTest->GetCTestConfiguration("BuildDirectory");
    logGlob += "/Testing/";
    logGlob += this->CTest->GetCurrentTag();
    logGlob += "/CoverageLog*";
    cmsys::Glob gl;
    gl.FindFiles(logGlob);
    std::vector<std::string> const& files = gl.GetFiles();
    for(std::string const& f : files)
    {
        log << "Removing old coverage log: " << f << "\n";
        cmSystemTools::RemoveFile(f);
    }
}

bool
cmCTestCoverageHandler::StartCoverageLogFile(cmGeneratedFileStream& covLogFile,
                                             int logFileCount)
{
    char covLogFilename[1024];
    sprintf(covLogFilename, "CoverageLog-%d", logFileCount);
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "Open file: " << covLogFilename << std::endl,
                       this->Quiet);
    if(!this->StartResultingXML(cmCTest::PartCoverage, covLogFilename,
                                covLogFile))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot open log file: " << covLogFilename << std::endl);
        return false;
    }
    return true;
}

void
cmCTestCoverageHandler::EndCoverageLogFile(cmGeneratedFileStream& ostr,
                                           int                    logFileCount)
{
    char covLogFilename[1024];
    sprintf(covLogFilename, "CoverageLog-%d.xml", logFileCount);
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "Close file: " << covLogFilename << std::endl,
                       this->Quiet);
    ostr.Close();
}

void
cmCTestCoverageHandler::StartCoverageLogXML(cmXMLWriter& xml)
{
    this->CTest->StartXML(xml, this->AppendXML);
    xml.StartElement("CoverageLog");
    xml.Element("StartDateTime", this->CTest->CurrentTime());
    xml.Element("StartTime", std::chrono::system_clock::now());
}

void
cmCTestCoverageHandler::EndCoverageLogXML(cmXMLWriter& xml)
{
    xml.Element("EndDateTime", this->CTest->CurrentTime());
    xml.Element("EndTime", std::chrono::system_clock::now());
    xml.EndElement();  // CoverageLog
    this->CTest->EndXML(xml);
}

bool
cmCTestCoverageHandler::ShouldIDoCoverage(std::string const& file,
                                          std::string const& srcDir,
                                          std::string const& binDir)
{
    if(this->IsFilteredOut(file))
    {
        return false;
    }

    for(cmsys::RegularExpression& rx : this->CustomCoverageExcludeRegex)
    {
        if(rx.find(file))
        {
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "  File " << file
                                         << " is excluded in CTestCustom.ctest"
                                         << std::endl;
                               , this->Quiet);
            return false;
        }
    }

    std::string fSrcDir      = cmSystemTools::CollapseFullPath(srcDir);
    std::string fBinDir      = cmSystemTools::CollapseFullPath(binDir);
    std::string fFile        = cmSystemTools::CollapseFullPath(file);
    bool        sourceSubDir = cmSystemTools::IsSubDirectory(fFile, fSrcDir);
    bool        buildSubDir  = cmSystemTools::IsSubDirectory(fFile, fBinDir);
    // Always check parent directory of the file.
    std::string fileDir = cmSystemTools::GetFilenamePath(fFile);
    std::string checkDir;

    // We also need to check the binary/source directory pair.
    if(sourceSubDir && buildSubDir)
    {
        if(fSrcDir.size() > fBinDir.size())
        {
            checkDir = fSrcDir;
        } else
        {
            checkDir = fBinDir;
        }
    } else if(sourceSubDir)
    {
        checkDir = fSrcDir;
    } else if(buildSubDir)
    {
        checkDir = fBinDir;
    }
    std::string ndc = cmSystemTools::FileExistsInParentDirectories(
        ".NoDartCoverage", fFile.c_str(), checkDir.c_str());
    if(!ndc.empty())
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Found: " << ndc << " so skip coverage of " << file
                                     << std::endl,
                           this->Quiet);
        return false;
    }

    // By now checkDir should be set to parent directory of the file.
    // Get the relative path to the file an apply it to the opposite directory.
    // If it is the same as fileDir, then ignore, otherwise check.
    std::string relPath;
    if(!checkDir.empty())
    {
        relPath = cmSystemTools::RelativePath(checkDir, fFile);
    } else
    {
        relPath = fFile;
    }
    if(checkDir == fSrcDir)
    {
        checkDir = fBinDir;
    } else
    {
        checkDir = fSrcDir;
    }
    fFile = checkDir + "/" + relPath;
    fFile = cmSystemTools::GetFilenamePath(fFile);

    if(fileDir == fFile)
    {
        // This is in-source build, so we trust the previous check.
        return true;
    }

    ndc = cmSystemTools::FileExistsInParentDirectories(
        ".NoDartCoverage", fFile.c_str(), checkDir.c_str());
    if(!ndc.empty())
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Found: " << ndc << " so skip coverage of: " << file
                                     << std::endl,
                           this->Quiet);
        return false;
    }
    // Ok, nothing in source tree, nothing in binary tree
    return true;
}

// clearly it would be nice if this were broken up into a few smaller
// functions and commented...
int
cmCTestCoverageHandler::ProcessHandler()
{
    this->CTest->ClearSubmitFiles(cmCTest::PartCoverage);
    int error = 0;
    // do we have time for this
    if(this->CTest->GetRemainingTimeAllowed() < std::chrono::minutes(2))
    {
        return error;
    }

    std::string coverage_start_time      = this->CTest->CurrentTime();
    auto        coverage_start_time_time = std::chrono::system_clock::now();
    std::string sourceDir =
        this->CTest->GetCTestConfiguration("SourceDirectory");
    std::string binaryDir =
        this->CTest->GetCTestConfiguration("BuildDirectory");

    this->LoadLabels();

    cmGeneratedFileStream ofs;
    auto                  elapsed_time_start = std::chrono::steady_clock::now();
    if(!this->StartLogFile("Coverage", ofs))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot create LastCoverage.log file" << std::endl);
    }

    ofs << "Performing coverage: "
        << elapsed_time_start.time_since_epoch().count() << std::endl;
    this->CleanCoverageLogFiles(ofs);

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

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

    cmCTestCoverageHandlerContainer cont;
    cont.Error     = error;
    cont.SourceDir = sourceDir;
    cont.BinaryDir = binaryDir;
    cont.OFS       = &ofs;
    cont.Quiet     = this->Quiet;

    // setup the regex exclude stuff
    this->CustomCoverageExcludeRegex.clear();
    for(std::string const& rex : this->CustomCoverageExclude)
    {
        this->CustomCoverageExcludeRegex.push_back(
            cmsys::RegularExpression(rex.c_str()));
    }

    if(this->HandleBullseyeCoverage(&cont))
    {
        return cont.Error;
    }
    int file_count = 0;
    file_count += this->HandleGCovCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }
    file_count += this->HandleLCovCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }
    file_count += this->HandleTracePyCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }
    file_count += this->HandlePHPCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }
    file_count += this->HandleCoberturaCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }

    file_count += this->HandleMumpsCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }

    file_count += this->HandleJacocoCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }

    file_count += this->HandleBlanketJSCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }

    file_count += this->HandleDelphiCoverage(&cont);
    error = cont.Error;
    if(file_count < 0)
    {
        return error;
    }
    std::set<std::string> uncovered = this->FindUncoveredFiles(&cont);

    if(file_count == 0 && this->ExtraCoverageGlobs.empty())
    {
        cmCTestOptionalLog(
            this->CTest, WARNING,
            " Cannot find any coverage files. Ignoring Coverage request."
                << std::endl,
            this->Quiet);
        return error;
    }
    cmGeneratedFileStream covSumFile;
    cmGeneratedFileStream covLogFile;
    cmXMLWriter           covSumXML(covSumFile);
    cmXMLWriter           covLogXML(covLogFile);

    if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot open coverage summary file." << std::endl);
        return -1;
    }
    covSumFile.setf(std::ios::fixed, std::ios::floatfield);
    covSumFile.precision(2);

    this->CTest->StartXML(covSumXML, this->AppendXML);
    // Produce output xml files

    covSumXML.StartElement("Coverage");
    covSumXML.Element("StartDateTime", coverage_start_time);
    covSumXML.Element("StartTime", coverage_start_time_time);
    int logFileCount = 0;
    if(!this->StartCoverageLogFile(covLogFile, logFileCount))
    {
        return -1;
    }
    this->StartCoverageLogXML(covLogXML);
    int  cnt            = 0;
    long total_tested   = 0;
    long total_untested = 0;
    // std::string fullSourceDir = sourceDir + "/";
    // std::string fullBinaryDir = binaryDir + "/";
    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl, this->Quiet);
    cmCTestOptionalLog(
        this->CTest, HANDLER_OUTPUT,
        "   Accumulating results (each . represents one file):" << std::endl,
        this->Quiet);
    cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "    ", this->Quiet);

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

    file_count = 0;
    for(auto const& file : cont.TotalCoverage)
    {
        cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
                           this->Quiet);
        file_count++;
        if(file_count % 50 == 0)
        {
            cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                               " processed: " << file_count << " out of "
                                              << cont.TotalCoverage.size()
                                              << std::endl,
                               this->Quiet);
            cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "    ",
                               this->Quiet);
        }

        const std::string fullFileName = file.first;
        bool              shouldIDoCoverage =
            this->ShouldIDoCoverage(fullFileName, sourceDir, binaryDir);
        if(!shouldIDoCoverage)
        {
            cmCTestOptionalLog(
                this->CTest, HANDLER_VERBOSE_OUTPUT,
                ".NoDartCoverage found, so skip coverage check for: "
                    << fullFileName << std::endl,
                this->Quiet);
            continue;
        }

        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Process file: " << fullFileName << std::endl,
                           this->Quiet);

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

        if(++cnt % 100 == 0)
        {
            this->EndCoverageLogXML(covLogXML);
            this->EndCoverageLogFile(covLogFile, logFileCount);
            logFileCount++;
            if(!this->StartCoverageLogFile(covLogFile, logFileCount))
            {
                return -1;
            }
            this->StartCoverageLogXML(covLogXML);
        }

        const std::string fileName =
            cmSystemTools::GetFilenameName(fullFileName);
        std::string shortFileName =
            this->CTest->GetShortPathToFile(fullFileName.c_str());
        const cmCTestCoverageHandlerContainer::SingleFileCoverageVector& fcov =
            file.second;
        covLogXML.StartElement("File");
        covLogXML.Attribute("Name", fileName);
        covLogXML.Attribute("FullPath", shortFileName);
        covLogXML.StartElement("Report");

        cmsys::ifstream ifs(fullFileName.c_str());
        if(!ifs)
        {
            std::ostringstream ostr;
            ostr << "Cannot open source file: " << fullFileName;
            errorsWhileAccumulating.push_back(ostr.str());
            error++;
            continue;
        }

        int tested   = 0;
        int untested = 0;

        cmCTestCoverageHandlerContainer::SingleFileCoverageVector::size_type cc;
        std::string line;
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Actually performing coverage for: " << fullFileName
                                                                << std::endl,
                           this->Quiet);
        for(cc = 0; cc < fcov.size(); cc++)
        {
            if(!cmSystemTools::GetLineFromStream(ifs, line) &&
               cc != fcov.size() - 1)
            {
                std::ostringstream ostr;
                ostr << "Problem reading source file: " << fullFileName
                     << " line:" << cc << "  out total: " << fcov.size() - 1;
                errorsWhileAccumulating.push_back(ostr.str());
                error++;
                break;
            }
            covLogXML.StartElement("Line");
            covLogXML.Attribute("Number", cc);
            covLogXML.Attribute("Count", fcov[cc]);
            covLogXML.Content(line);
            covLogXML.EndElement();  // Line
            if(fcov[cc] == 0)
            {
                untested++;
            } else if(fcov[cc] > 0)
            {
                tested++;
            }
        }
        if(cmSystemTools::GetLineFromStream(ifs, line))
        {
            std::ostringstream ostr;
            ostr << "Looks like there are more lines in the file: "
                 << fullFileName;
            errorsWhileAccumulating.push_back(ostr.str());
        }
        float cper = 0;
        float cmet = 0;
        if(tested + untested > 0)
        {
            cper = (100 * SAFEDIV(static_cast<float>(tested),
                                  static_cast<float>(tested + untested)));
            cmet = (SAFEDIV(static_cast<float>(tested + 10),
                            static_cast<float>(tested + untested + 10)));
        }
        total_tested += tested;
        total_untested += untested;
        covLogXML.EndElement();  // Report
        covLogXML.EndElement();  // File
        covSumXML.StartElement("File");
        covSumXML.Attribute("Name", fileName);
        covSumXML.Attribute(
            "FullPath", this->CTest->GetShortPathToFile(fullFileName.c_str()));
        covSumXML.Attribute("Covered",
                            tested + untested > 0 ? "true" : "false");
        covSumXML.Element("LOCTested", tested);
        covSumXML.Element("LOCUnTested", untested);
        covSumXML.Element("PercentCoverage", cper);
        covSumXML.Element("CoverageMetric", cmet);
        this->WriteXMLLabels(covSumXML, shortFileName);
        covSumXML.EndElement();  // File
    }

    // Handle all the files in the extra coverage globs that have no cov data
    for(std::string const& u : uncovered)
    {
        std::string fileName = cmSystemTools::GetFilenameName(u);
        std::string fullPath = cont.SourceDir + "/" + u;

        covLogXML.StartElement("File");
        covLogXML.Attribute("Name", fileName);
        covLogXML.Attribute("FullPath", u);
        covLogXML.StartElement("Report");

        cmsys::ifstream ifs(fullPath.c_str());
        if(!ifs)
        {
            std::ostringstream ostr;
            ostr << "Cannot open source file: " << fullPath;
            errorsWhileAccumulating.push_back(ostr.str());
            error++;
            covLogXML.EndElement();  // Report
            covLogXML.EndElement();  // File
            continue;
        }
        int         untested = 0;
        std::string line;
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Actually performing coverage for: " << u
                                                                << std::endl,
                           this->Quiet);
        while(cmSystemTools::GetLineFromStream(ifs, line))
        {
            covLogXML.StartElement("Line");
            covLogXML.Attribute("Number", untested);
            covLogXML.Attribute("Count", 0);
            covLogXML.Content(line);
            covLogXML.EndElement();  // Line
            untested++;
        }
        covLogXML.EndElement();  // Report
        covLogXML.EndElement();  // File

        total_untested += untested;
        covSumXML.StartElement("File");
        covSumXML.Attribute("Name", fileName);
        covSumXML.Attribute("FullPath", u);
        covSumXML.Attribute("Covered", "true");
        covSumXML.Element("LOCTested", 0);
        covSumXML.Element("LOCUnTested", untested);
        covSumXML.Element("PercentCoverage", 0);
        covSumXML.Element("CoverageMetric", 0);
        this->WriteXMLLabels(covSumXML, u);
        covSumXML.EndElement();  // File
    }

    this->EndCoverageLogXML(covLogXML);
    this->EndCoverageLogFile(covLogFile, logFileCount);

    if(!errorsWhileAccumulating.empty())
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, std::endl);
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error(s) while accumulating results:" << std::endl);
        for(std::string const& er : errorsWhileAccumulating)
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE, "  " << er << std::endl);
        }
    }

    long  total_lines      = total_tested + total_untested;
    float percent_coverage = 100 * SAFEDIV(static_cast<float>(total_tested),
                                           static_cast<float>(total_lines));
    if(total_lines == 0)
    {
        percent_coverage = 0;
    }

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

    covSumXML.Element("LOCTested", total_tested);
    covSumXML.Element("LOCUntested", total_untested);
    covSumXML.Element("LOC", total_lines);
    covSumXML.Element("PercentCoverage", percent_coverage);
    covSumXML.Element("EndDateTime", end_time);
    covSumXML.Element("EndTime", std::chrono::system_clock::now());
    covSumXML.Element("ElapsedMinutes",
                      std::chrono::duration_cast<std::chrono::minutes>(
                          std::chrono::steady_clock::now() - elapsed_time_start)
                          .count());
    covSumXML.EndElement();  // Coverage
    this->CTest->EndXML(covSumXML);

    cmCTestLog(this->CTest, HANDLER_OUTPUT,
               "" << std::endl
                  << "\tCovered LOC:         " << total_tested << std::endl
                  << "\tNot covered LOC:     " << total_untested << std::endl
                  << "\tTotal LOC:           " << total_lines << std::endl
                  << "\tPercentage Coverage: "
                  << std::setiosflags(std::ios::fixed) << std::setprecision(2)
                  << (percent_coverage) << "%" << std::endl);

    ofs << "\tCovered LOC:         " << total_tested << std::endl
        << "\tNot covered LOC:     " << total_untested << std::endl
        << "\tTotal LOC:           " << total_lines << std::endl
        << "\tPercentage Coverage: " << std::setiosflags(std::ios::fixed)
        << std::setprecision(2) << (percent_coverage) << "%" << std::endl;

    if(error)
    {
        return -1;
    }
    return 0;
}

void
cmCTestCoverageHandler::PopulateCustomVectors(cmMakefile* mf)
{
    cmCTestOptionalLog(
        this->CTest, HANDLER_VERBOSE_OUTPUT,
        " Add coverage exclude regular expressions." << std::endl, this->Quiet);
    this->CTest->PopulateCustomVector(mf, "CTEST_CUSTOM_COVERAGE_EXCLUDE",
                                      this->CustomCoverageExclude);
    this->CTest->PopulateCustomVector(mf, "CTEST_EXTRA_COVERAGE_GLOB",
                                      this->ExtraCoverageGlobs);
    for(std::string const& cce : this->CustomCoverageExclude)
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           " Add coverage exclude: " << cce << std::endl,
                           this->Quiet);
    }
    for(std::string const& ecg : this->ExtraCoverageGlobs)
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           " Add coverage glob: " << ecg << std::endl,
                           this->Quiet);
    }
}

// Fix for issue #4971 where the case of the drive letter component of
// the filenames might be different when analyzing gcov output.
//
// Compare file names: fnc(fn1) == fnc(fn2) // fnc == file name compare
//
#ifdef _WIN32
#    define fnc(s) cmSystemTools::LowerCase(s)
#else
#    define fnc(s) s
#endif

bool
IsFileInDir(const std::string& infile, const std::string& indir)
{
    std::string file = cmSystemTools::CollapseFullPath(infile);
    std::string dir  = cmSystemTools::CollapseFullPath(indir);

    return file.size() > dir.size() &&
           fnc(file.substr(0, dir.size())) == fnc(dir) &&
           file[dir.size()] == '/';
}

int
cmCTestCoverageHandler::HandlePHPCoverage(cmCTestCoverageHandlerContainer* cont)
{
    cmParsePHPCoverage cov(*cont, this->CTest);
    std::string coverageDir = this->CTest->GetBinaryDir() + "/xdebugCoverage";
    if(cmSystemTools::FileIsDirectory(coverageDir))
    {
        cov.ReadPHPCoverageDirectory(coverageDir.c_str());
    }
    return static_cast<int>(cont->TotalCoverage.size());
}

int
cmCTestCoverageHandler::HandleCoberturaCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    cmParseCoberturaCoverage cov(*cont, this->CTest);

    // Assume the coverage.xml is in the binary directory
    // check for the COBERTURADIR environment variable,
    // if it doesn't exist or is empty, assume the
    // binary directory is used.
    std::vector<std::string> fnames;
    fnames.push_back("/coverage.xml");
    fnames.push_back("/.coverage.xml");
    fnames.push_back("/.coverage");
    for(unsigned i = 0; i < fnames.size(); ++i)
    {
        std::string coverageXMLFile;
        if(!cmSystemTools::GetEnv("COBERTURADIR", coverageXMLFile) ||
           coverageXMLFile.empty())
        {
            coverageXMLFile = this->CTest->GetBinaryDir();
        }
        // build the find file string with the directory from above
        coverageXMLFile += fnames[i];

        if(cmSystemTools::FileExists(coverageXMLFile))
        {
            // If file exists, parse it
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "Parsing Cobertura XML file: " << coverageXMLFile
                                                              << std::endl,
                               this->Quiet);
            cov.ReadCoverageXML(coverageXMLFile.c_str());
        } else
        {
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               " Cannot find Cobertura XML file: "
                                   << coverageXMLFile << std::endl,
                               this->Quiet);
        }
    }
    return static_cast<int>(cont->TotalCoverage.size());
}

int
cmCTestCoverageHandler::HandleMumpsCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    // try gtm coverage
    cmParseGTMCoverage cov(*cont, this->CTest);
    std::string        coverageFile =
        this->CTest->GetBinaryDir() + "/gtm_coverage.mcov";
    if(cmSystemTools::FileExists(coverageFile))
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Parsing Cache Coverage: " << coverageFile
                                                      << std::endl,
                           this->Quiet);
        cov.ReadCoverageFile(coverageFile.c_str());
        return static_cast<int>(cont->TotalCoverage.size());
    }
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " Cannot find GTM coverage file: " << coverageFile
                                                          << std::endl,
                       this->Quiet);
    cmParseCacheCoverage ccov(*cont, this->CTest);
    coverageFile = this->CTest->GetBinaryDir() + "/cache_coverage.cmcov";
    if(cmSystemTools::FileExists(coverageFile))
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Parsing Cache Coverage: " << coverageFile
                                                      << std::endl,
                           this->Quiet);
        ccov.ReadCoverageFile(coverageFile.c_str());
    } else
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           " Cannot find Cache coverage file: " << coverageFile
                                                                << std::endl,
                           this->Quiet);
    }
    return static_cast<int>(cont->TotalCoverage.size());
}

struct cmCTestCoverageHandlerLocale
{
    cmCTestCoverageHandlerLocale()
    {
        std::string l;
        if(cmSystemTools::GetEnv("LC_ALL", l))
        {
            lc_all = l;
        }
        if(lc_all != "C")
        {
            cmSystemTools::PutEnv("LC_ALL=C");
        }
    }
    ~cmCTestCoverageHandlerLocale()
    {
        if(!lc_all.empty())
        {
            cmSystemTools::PutEnv("LC_ALL=" + lc_all);
        } else
        {
            cmSystemTools::UnsetEnv("LC_ALL");
        }
    }
    std::string lc_all;
};

int
cmCTestCoverageHandler::HandleJacocoCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    cmParseJacocoCoverage cov = cmParseJacocoCoverage(*cont, this->CTest);

    // Search in the source directory.
    cmsys::Glob              g1;
    std::vector<std::string> files;
    g1.SetRecurse(true);

    std::string SourceDir =
        this->CTest->GetCTestConfiguration("SourceDirectory");
    std::string coverageFile = SourceDir + "/*jacoco.xml";

    g1.FindFiles(coverageFile);
    files = g1.GetFiles();

    // ...and in the binary directory.
    cmsys::Glob              g2;
    std::vector<std::string> binFiles;
    g2.SetRecurse(true);
    std::string binaryDir =
        this->CTest->GetCTestConfiguration("BuildDirectory");
    std::string binCoverageFile = binaryDir + "/*jacoco.xml";
    g2.FindFiles(binCoverageFile);
    binFiles = g2.GetFiles();
    if(!binFiles.empty())
    {
        files.insert(files.end(), binFiles.begin(), binFiles.end());
    }

    if(!files.empty())
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Found Jacoco Files, Performing Coverage"
                               << std::endl,
                           this->Quiet);
        cov.LoadCoverageData(files);
    } else
    {
        cmCTestOptionalLog(
            this->CTest, HANDLER_VERBOSE_OUTPUT,
            " Cannot find Jacoco coverage files: " << coverageFile << std::endl,
            this->Quiet);
    }
    return static_cast<int>(cont->TotalCoverage.size());
}

int
cmCTestCoverageHandler::HandleDelphiCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    cmParseDelphiCoverage    cov = cmParseDelphiCoverage(*cont, this->CTest);
    cmsys::Glob              g;
    std::vector<std::string> files;
    g.SetRecurse(true);

    std::string BinDir       = this->CTest->GetBinaryDir();
    std::string coverageFile = BinDir + "/*(*.pas).html";

    g.FindFiles(coverageFile);
    files = g.GetFiles();
    if(!files.empty())
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Found Delphi HTML Files, Performing Coverage"
                               << std::endl,
                           this->Quiet);
        cov.LoadCoverageData(files);
    } else
    {
        cmCTestOptionalLog(
            this->CTest, HANDLER_VERBOSE_OUTPUT,
            " Cannot find Delphi coverage files: " << coverageFile << std::endl,
            this->Quiet);
    }
    return static_cast<int>(cont->TotalCoverage.size());
}

static std::string
joinCommandLine(const std::vector<std::string>& args)
{
    std::string ret;

    for(std::string const& s : args)
    {
        if(s.find(' ') == std::string::npos)
        {
            ret += s + ' ';
        } else
        {
            ret += "\"" + s + "\" ";
        }
    }

    // drop trailing whitespace
    ret.erase(ret.size() - 1);

    return ret;
}

int
cmCTestCoverageHandler::HandleBlanketJSCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    cmParseBlanketJSCoverage cov = cmParseBlanketJSCoverage(*cont, this->CTest);
    std::string              SourceDir =
        this->CTest->GetCTestConfiguration("SourceDirectory");

    // Look for something other than output.json, still JSON extension.
    std::string              coverageFile = SourceDir + "/*.json";
    cmsys::Glob              g;
    std::vector<std::string> files;
    std::vector<std::string> blanketFiles;
    g.FindFiles(coverageFile);
    files = g.GetFiles();
    // Ensure that the JSON files found are the result of the
    // Blanket.js output. Check for the "node-jscoverage"
    // string on the second line
    std::string line;
    for(std::string const& fileEntry : files)
    {
        cmsys::ifstream in(fileEntry.c_str());
        cmSystemTools::GetLineFromStream(in, line);
        cmSystemTools::GetLineFromStream(in, line);
        if(line.find("node-jscoverage") != std::string::npos)
        {
            blanketFiles.push_back(fileEntry);
        }
    }
    //  Take all files with the node-jscoverage string and parse those
    if(!blanketFiles.empty())
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Found BlanketJS output JSON, Performing Coverage"
                               << std::endl,
                           this->Quiet);
        cov.LoadCoverageData(files);
    } else
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           " Cannot find BlanketJS coverage files: "
                               << coverageFile << std::endl,
                           this->Quiet);
    }
    return static_cast<int>(cont->TotalCoverage.size());
}
int
cmCTestCoverageHandler::HandleGCovCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    std::string gcovCommand =
        this->CTest->GetCTestConfiguration("CoverageCommand");
    if(gcovCommand.empty())
    {
        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.c_str());
    basecovargs.insert(basecovargs.begin(), gcovCommand);
    basecovargs.push_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);
        const std::string 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;

        cmSystemTools::Split(output.c_str(), 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" &&
                   !cmSystemTools::StringStartsWith(line.c_str(), "Removing "))
                {
                    cmCTestLog(this->CTest, ERROR_MESSAGE,
                               "Unknown gcov output line: [" << line << "]"
                                                             << std::endl);
                    cont->Error++;
                    // abort();
                }
            }

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

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

                cmsys::ifstream ifile(gcovFile.c_str());
                if(!ifile)
                {
                    cmCTestLog(this->CTest, ERROR_MESSAGE,
                               "Cannot open file: " << gcovFile << std::endl);
                } else
                {
                    long        cnt = -1;
                    std::string nl;
                    while(cmSystemTools::GetLineFromStream(ifile, nl))
                    {
                        cnt++;

                        // TODO: Handle gcov 3.0 non-coverage lines

                        // Skip empty lines
                        if(nl.empty())
                        {
                            continue;
                        }

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

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

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

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

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

int
cmCTestCoverageHandler::HandleLCovCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    std::string lcovCommand =
        this->CTest->GetCTestConfiguration("CoverageCommand");
    std::string lcovExtraFlags =
        this->CTest->GetCTestConfiguration("CoverageExtraFlags");
    if(lcovCommand != "codecov")
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           " Not a valid Intel Coverage command." << std::endl,
                           this->Quiet);
        return 0;
    }
    // There is only percentage completed output from LCOV
    std::string              st2lcovOutputRex3 = "[0-9]+%";
    cmsys::RegularExpression st2re3(st2lcovOutputRex3.c_str());

    cmCTestOptionalLog(
        this->CTest, HANDLER_VERBOSE_OUTPUT,
        " This is coverage command: " << lcovCommand << std::endl, this->Quiet);

    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " These are coverage command flags: " << lcovExtraFlags
                                                             << std::endl,
                       this->Quiet);

    std::vector<std::string> files;
    if(!this->FindLCovFiles(files))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error while finding LCov files.\n");
        return 0;
    }

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

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

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

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

    std::vector<std::string> covargs =
        cmSystemTools::ParseArguments(lcovExtraFlags.c_str());
    covargs.insert(covargs.begin(), lcovCommand);
    const std::string command = joinCommandLine(covargs);

    // In intel compiler we have to call codecov only once in each executable
    // directory. It collects all *.dyn files to generate .dpi file.
    for(std::string const& f : files)
    {
        cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
                           this->Quiet);
        std::string        fileDir = cmSystemTools::GetFilenamePath(f);
        cmWorkingDirectory workdir(fileDir);
        if(workdir.Failed())
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "Unable to change working directory to "
                           << fileDir << " : "
                           << std::strerror(workdir.GetLastResult())
                           << std::endl);
            cont->Error++;
            continue;
        }

        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Current coverage dir: " << fileDir << std::endl,
                           this->Quiet);
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           command << std::endl, this->Quiet);

        std::string output;
        std::string errors;
        int         retVal = 0;
        *cont->OFS << "* Run coverage for: " << fileDir << std::endl;
        *cont->OFS << "  Command: " << command << std::endl;
        int res = this->CTest->RunCommand(covargs, &output, &errors, &retVal,
                                          fileDir.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;

        cmSystemTools::Split(output.c_str(), lines);

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

            if(line.empty())
            {
                // Ignore empty line
            }
            // Look for LCOV files in binary directory
            // Intel Compiler creates a CodeCoverage dir for each subfolder and
            // each subfolder has LCOV files
            cmsys::Glob gl;
            gl.RecurseOn();
            gl.RecurseThroughSymlinksOff();
            std::string              dir;
            std::vector<std::string> lcovFiles;
            dir = this->CTest->GetBinaryDir();
            std::string daGlob;
            daGlob = dir;
            daGlob += "/*.LCOV";
            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "   looking for LCOV files in: " << daGlob
                                                                << std::endl,
                               this->Quiet);
            gl.FindFiles(daGlob);
            // Keep a list of all LCOV files
            lcovFiles.insert(lcovFiles.end(), gl.GetFiles().begin(),
                             gl.GetFiles().end());

            for(std::string const& file : lcovFiles)
            {
                lcovFile = file;
                cmsys::ifstream srcead(lcovFile.c_str());
                if(!srcead)
                {
                    cmCTestLog(this->CTest, ERROR_MESSAGE,
                               "Cannot open file: " << lcovFile << std::endl);
                }
                std::string srcname;

                int success = cmSystemTools::GetLineFromStream(srcead, srcname);
                if(!success)
                {
                    cmCTestLog(this->CTest, ERROR_MESSAGE,
                               "Error while parsing lcov file '"
                                   << lcovFile << "':"
                                   << " No source file name found!"
                                   << std::endl);
                    return 0;
                }
                srcname = srcname.substr(18);
                // We can directly read found LCOV files to determine the source
                // files
                sourceFile       = srcname;
                actualSourceFile = srcname;

                for(std::string const& t : lcovFiles)
                {
                    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                       "Found LCOV File: " << t << std::endl,
                                       this->Quiet);
                }
                cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                   "SourceFile: " << sourceFile << std::endl,
                                   this->Quiet);
                cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                   "lCovFile: " << lcovFile << std::endl,
                                   this->Quiet);

                // If we have some LCOV files to process
                if(!lcovFile.empty() && !actualSourceFile.empty())
                {
                    cmCTestCoverageHandlerContainer::SingleFileCoverageVector&
                        vec = cont->TotalCoverage[actualSourceFile];

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

                    cmsys::ifstream ifile(lcovFile.c_str());
                    if(!ifile)
                    {
                        cmCTestLog(this->CTest, ERROR_MESSAGE,
                                   "Cannot open file: " << lcovFile
                                                        << std::endl);
                    } else
                    {
                        long        cnt = -1;
                        std::string nl;

                        // Skip the first line
                        cmSystemTools::GetLineFromStream(ifile, nl);
                        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                           "File is ready, start reading."
                                               << std::endl,
                                           this->Quiet);
                        while(cmSystemTools::GetLineFromStream(ifile, nl))
                        {
                            cnt++;

                            // Skip empty lines
                            if(nl.empty())
                            {
                                continue;
                            }

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

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

                            // Read the line number starting at the 17th
                            // character of the lcov output line
                            std::string lineNumber = nl.substr(17, 7);

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

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

                                vec[lineIdx] += cov;
                            }
                        }
                    }

                    actualSourceFile.clear();
                }
            }
        }

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

void
cmCTestCoverageHandler::FindGCovFiles(std::vector<std::string>& files)
{
    cmsys::Glob gl;
    gl.RecurseOn();
    gl.RecurseThroughSymlinksOff();

    for(auto const& lm : this->TargetDirs)
    {
        // Skip targets containing no interesting labels.
        if(!this->IntersectsFilter(lm.second))
        {
            continue;
        }

        // Coverage files appear next to their object files in the target
        // support directory.
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "   globbing for coverage in: " << lm.first
                                                           << std::endl,
                           this->Quiet);
        std::string daGlob = lm.first;
        daGlob += "/*.da";
        gl.FindFiles(daGlob);
        files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
        daGlob = lm.first;
        daGlob += "/*.gcda";
        gl.FindFiles(daGlob);
        files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
    }
}

bool
cmCTestCoverageHandler::FindLCovFiles(std::vector<std::string>& files)
{
    cmsys::Glob gl;
    gl.RecurseOff();  // No need of recurse if -prof_dir${BUILD_DIR} flag is
                      // used while compiling.
    gl.RecurseThroughSymlinksOff();
    std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory");
    cmWorkingDirectory workdir(buildDir);
    if(workdir.Failed())
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Unable to change working directory to " << buildDir
                                                            << std::endl);
        return false;
    }

    // Run profmerge to merge all *.dyn files into dpi files
    if(!cmSystemTools::RunSingleCommand("profmerge"))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error while running profmerge.\n");
        return false;
    }

    // DPI file should appear in build directory
    std::string daGlob;
    daGlob = buildDir;
    daGlob += "/*.dpi";
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "   looking for dpi files in: " << daGlob << std::endl,
                       this->Quiet);
    if(!gl.FindFiles(daGlob))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error while finding files matching " << daGlob
                                                         << std::endl);
        return false;
    }
    files.insert(files.end(), gl.GetFiles().begin(), gl.GetFiles().end());
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "Now searching in: " << daGlob << std::endl,
                       this->Quiet);
    return true;
}

int
cmCTestCoverageHandler::HandleTracePyCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    cmsys::Glob gl;
    gl.RecurseOn();
    gl.RecurseThroughSymlinksOff();
    std::string daGlob = cont->BinaryDir + "/*.cover";
    gl.FindFiles(daGlob);
    std::vector<std::string> files = gl.GetFiles();

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

    std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
    std::string tempDir    = testingDir + "/CoverageInfo";
    cmSystemTools::MakeDirectory(tempDir);

    int file_count = 0;
    for(std::string const& file : files)
    {
        std::string fileName = this->FindFile(cont, file);
        if(fileName.empty())
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "Cannot find source Python file corresponding to: "
                           << file << std::endl);
            continue;
        }

        std::string actualSourceFile =
            cmSystemTools::CollapseFullPath(fileName);
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "   Check coverage for file: " << actualSourceFile
                                                          << std::endl,
                           this->Quiet);
        cmCTestCoverageHandlerContainer::SingleFileCoverageVector* vec =
            &cont->TotalCoverage[actualSourceFile];
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "   in file: " << file << std::endl, this->Quiet);
        cmsys::ifstream ifile(file.c_str());
        if(!ifile)
        {
            cmCTestLog(this->CTest, ERROR_MESSAGE,
                       "Cannot open file: " << file << std::endl);
        } else
        {
            long        cnt = -1;
            std::string nl;
            while(cmSystemTools::GetLineFromStream(ifile, nl))
            {
                cnt++;

                // Skip empty lines
                if(nl.empty())
                {
                    continue;
                }

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

                // Read the coverage count from the beginning of the Trace.py
                // output line
                std::string prefix = nl.substr(0, 6);
                if(prefix[5] != ' ' && prefix[5] != ':')
                {
                    // This is a hack. We should really do something more
                    // elaborate
                    prefix = nl.substr(0, 7);
                    if(prefix[6] != ' ' && prefix[6] != ':')
                    {
                        prefix = nl.substr(0, 8);
                        if(prefix[7] != ' ' && prefix[7] != ':')
                        {
                            cmCTestLog(this->CTest, ERROR_MESSAGE,
                                       "Currently the limit is maximum "
                                       "coverage of 999999"
                                           << std::endl);
                        }
                    }
                }
                int cov = atoi(prefix.c_str());
                if(prefix[prefix.size() - 1] != ':')
                {
                    // This line does not have ':' so no coverage here. That
                    // said, Trace.py does not handle not covered lines versus
                    // comments etc. So, this will be set to 0.
                    cov = 0;
                }
                cmCTestOptionalLog(this->CTest, DEBUG,
                                   "Prefix: " << prefix << " cov: " << cov
                                              << std::endl,
                                   this->Quiet);
                // Read the line number starting at the 10th character of the
                // gcov output line
                long lineIdx = cnt;
                if(lineIdx >= 0)
                {
                    while(vec->size() <= static_cast<size_t>(lineIdx))
                    {
                        vec->push_back(-1);
                    }
                    // Initially all entries are -1 (not used). If we get
                    // coverage information, increment it to 0 first.
                    if((*vec)[lineIdx] < 0)
                    {
                        if(cov >= 0)
                        {
                            (*vec)[lineIdx] = 0;
                        }
                    }
                    (*vec)[lineIdx] += cov;
                }
            }
        }
        ++file_count;
    }
    return file_count;
}

std::string
cmCTestCoverageHandler::FindFile(cmCTestCoverageHandlerContainer* cont,
                                 std::string const&               fileName)
{
    std::string fileNameNoE =
        cmSystemTools::GetFilenameWithoutLastExtension(fileName);
    // First check in source and binary directory
    std::string fullName = cont->SourceDir + "/" + fileNameNoE + ".py";
    if(cmSystemTools::FileExists(fullName))
    {
        return fullName;
    }
    fullName = cont->BinaryDir + "/" + fileNameNoE + ".py";
    if(cmSystemTools::FileExists(fullName))
    {
        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. ",
    nullptr
};
}

int
cmCTestCoverageHandler::RunBullseyeCoverageBranch(
    cmCTestCoverageHandlerContainer* cont,
    std::set<std::string>& coveredFileNames, std::vector<std::string>& files,
    std::vector<std::string>& filesFullPath)
{
    if(files.size() != filesFullPath.size())
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Files and full path files not the same size?:\n");
        return 0;
    }
    // create the output stream for the CoverageLog-N.xml file
    cmGeneratedFileStream covLogFile;
    cmXMLWriter           covLogXML(covLogFile);
    int                   logFileCount = 0;
    if(!this->StartCoverageLogFile(covLogFile, logFileCount))
    {
        return -1;
    }
    this->StartCoverageLogXML(covLogXML);
    // for each file run covbr on that file to get the coverage
    // information for that file
    std::string outputFile;
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "run covbr: " << std::endl, this->Quiet);

    if(!this->RunBullseyeCommand(cont, "covbr", nullptr, outputFile))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "error running covbr for."
                       << "\n");
        return -1;
    }
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       "covbr output in  " << outputFile << std::endl,
                       this->Quiet);
    // open the output file
    cmsys::ifstream fin(outputFile.c_str());
    if(!fin)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot open coverage file: " << outputFile << std::endl);
        return 0;
    }
    std::map<std::string, std::string> fileMap;
    std::vector<std::string>::iterator fp = filesFullPath.begin();
    for(std::vector<std::string>::iterator f = files.begin(); f != files.end();
        ++f, ++fp)
    {
        fileMap[*f] = *fp;
    }

    int count = 0;  // keep count of the number of files
    // Now parse each line from the bullseye cov log file
    std::string lineIn;
    bool        valid = false;  // are we in a valid output file
    int         line  = 0;      // line of the current file
    std::string file;
    while(cmSystemTools::GetLineFromStream(fin, lineIn))
    {
        bool startFile = false;
        if(lineIn.size() > 1 && lineIn[lineIn.size() - 1] == ':')
        {
            file = lineIn.substr(0, lineIn.size() - 1);
            if(coveredFileNames.find(file) != coveredFileNames.end())
            {
                startFile = true;
            }
        }
        if(startFile)
        {
            // if we are in a valid file close it because a new one started
            if(valid)
            {
                covLogXML.EndElement();  // Report
                covLogXML.EndElement();  // File
            }
            // only allow 100 files in each log file
            if(count != 0 && count % 100 == 0)
            {
                cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                   "start a new log file: " << count
                                                            << std::endl,
                                   this->Quiet);
                this->EndCoverageLogXML(covLogXML);
                this->EndCoverageLogFile(covLogFile, logFileCount);
                logFileCount++;
                if(!this->StartCoverageLogFile(covLogFile, logFileCount))
                {
                    return -1;
                }
                this->StartCoverageLogXML(covLogXML);
                count++;  // move on one
            }
            std::map<std::string, std::string>::iterator i = fileMap.find(file);
            // if the file should be covered write out the header for that file
            if(i != fileMap.end())
            {
                // we have a new file so count it in the output
                count++;
                cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                                   "Produce coverage for file: "
                                       << file << " " << count << std::endl,
                                   this->Quiet);
                // start the file output
                covLogXML.StartElement("File");
                covLogXML.Attribute("Name", i->first);
                covLogXML.Attribute("FullPath", this->CTest->GetShortPathToFile(
                                                    i->second.c_str()));
                covLogXML.StartElement("Report");
                // write the bullseye header
                line = 0;
                for(int k = 0; bullseyeHelp[k] != nullptr; ++k)
                {
                    covLogXML.StartElement("Line");
                    covLogXML.Attribute("Number", line);
                    covLogXML.Attribute("Count", -1);
                    covLogXML.Content(bullseyeHelp[k]);
                    covLogXML.EndElement();  // Line
                    line++;
                }
                valid = true;  // we are in a valid file section
            } else
            {
                // this is not a file that we want coverage for
                valid = false;
            }
        }
        // we are not at a start file, and we are in a valid file output the
        // line
        else if(valid)
        {
            covLogXML.StartElement("Line");
            covLogXML.Attribute("Number", line);
            covLogXML.Attribute("Count", -1);
            covLogXML.Content(lineIn);
            covLogXML.EndElement();  // Line
            line++;
        }
    }
    // if we ran out of lines a valid file then close that file
    if(valid)
    {
        covLogXML.EndElement();  // Report
        covLogXML.EndElement();  // File
    }
    this->EndCoverageLogXML(covLogXML);
    this->EndCoverageLogFile(covLogFile, logFileCount);
    return 1;
}

int
cmCTestCoverageHandler::RunBullseyeCommand(
    cmCTestCoverageHandlerContainer* cont, const char* cmd, const char* arg,
    std::string& outputFile)
{
    std::string program = cmSystemTools::FindProgram(cmd);
    if(program.empty())
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "Cannot find :" << cmd << "\n");
        return 0;
    }
    if(arg)
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Run : " << program << " " << arg << "\n",
                           this->Quiet);
    } else
    {
        cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                           "Run : " << program << "\n", this->Quiet);
    }
    // create a process object and start it
    cmCTestRunProcess runCoverageSrc;
    runCoverageSrc.SetCommand(program.c_str());
    runCoverageSrc.AddArgument(arg);
    std::string stdoutFile = cont->BinaryDir + "/Testing/Temporary/";
    stdoutFile += this->GetCTestInstance()->GetCurrentTag();
    stdoutFile += "-";
    stdoutFile += cmd;
    std::string stderrFile = stdoutFile;
    stdoutFile += ".stdout";
    stderrFile += ".stderr";
    runCoverageSrc.SetStdoutFile(stdoutFile.c_str());
    runCoverageSrc.SetStderrFile(stderrFile.c_str());
    if(!runCoverageSrc.StartProcess())
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Could not run : " << program << " " << arg << "\n"
                                      << "kwsys process state : "
                                      << runCoverageSrc.GetProcessState());
        return 0;
    }
    // since we set the output file names wait for it to end
    runCoverageSrc.WaitForExit();
    outputFile = stdoutFile;
    return 1;
}

int
cmCTestCoverageHandler::RunBullseyeSourceSummary(
    cmCTestCoverageHandlerContainer* cont)
{
    // Run the covsrc command and create a temp outputfile
    std::string outputFile;
    if(!this->RunBullseyeCommand(cont, "covsrc", "-c", outputFile))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "error running covsrc:\n");
        return 0;
    }

    std::ostream& tmpLog = *cont->OFS;
    // copen the Coverage.xml file in the Testing directory
    cmGeneratedFileStream covSumFile;
    cmXMLWriter           xml(covSumFile);
    if(!this->StartResultingXML(cmCTest::PartCoverage, "Coverage", covSumFile))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot open coverage summary file." << std::endl);
        return 0;
    }
    this->CTest->StartXML(xml, this->AppendXML);
    auto        elapsed_time_start  = std::chrono::steady_clock::now();
    std::string coverage_start_time = this->CTest->CurrentTime();
    xml.StartElement("Coverage");
    xml.Element("StartDateTime", coverage_start_time);
    xml.Element("StartTime", std::chrono::system_clock::now());
    std::string stdline;
    std::string errline;
    // expected output:
    // first line is:
    // "Source","Function Coverage","out of","%","C/D Coverage","out of","%"
    // after that data follows in that format
    std::string              sourceFile;
    int                      functionsCalled  = 0;
    int                      totalFunctions   = 0;
    int                      percentFunction  = 0;
    int                      branchCovered    = 0;
    int                      totalBranches    = 0;
    int                      percentBranch    = 0;
    double                   total_tested     = 0;
    double                   total_untested   = 0;
    double                   total_functions  = 0;
    double                   percent_coverage = 0;
    double                   number_files     = 0;
    std::vector<std::string> coveredFiles;
    std::vector<std::string> coveredFilesFullPath;
    // Read and parse the summary output file
    cmsys::ifstream fin(outputFile.c_str());
    if(!fin)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Cannot open coverage summary file: " << outputFile
                                                         << std::endl);
        return 0;
    }
    std::set<std::string> coveredFileNames;
    while(cmSystemTools::GetLineFromStream(fin, stdline))
    {
        // if we have a line of output from stdout
        if(!stdline.empty())
        {
            // parse the comma separated output
            this->ParseBullsEyeCovsrcLine(
                stdline, sourceFile, functionsCalled, totalFunctions,
                percentFunction, branchCovered, totalBranches, percentBranch);
            // The first line is the header
            if(sourceFile == "Source" || sourceFile == "Total")
            {
                continue;
            }
            std::string file = sourceFile;
            coveredFileNames.insert(file);
            if(!cmSystemTools::FileIsFullPath(sourceFile))
            {
                // file will be relative to the binary dir
                file = cont->BinaryDir;
                file += "/";
                file += sourceFile;
            }
            file = cmSystemTools::CollapseFullPath(file);
            bool shouldIDoCoverage =
                this->ShouldIDoCoverage(file, cont->SourceDir, cont->BinaryDir);
            if(!shouldIDoCoverage)
            {
                cmCTestOptionalLog(
                    this->CTest, HANDLER_VERBOSE_OUTPUT,
                    ".NoDartCoverage found, so skip coverage check for: "
                        << file << std::endl,
                    this->Quiet);
                continue;
            }

            cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                               "Doing coverage for: " << file << std::endl,
                               this->Quiet);

            coveredFiles.push_back(sourceFile);
            coveredFilesFullPath.push_back(file);

            number_files++;
            total_functions += totalFunctions;
            total_tested += functionsCalled;
            total_untested += (totalFunctions - functionsCalled);

            std::string fileName = cmSystemTools::GetFilenameName(file);
            std::string shortFileName =
                this->CTest->GetShortPathToFile(file.c_str());

            float cper = static_cast<float>(percentBranch + percentFunction);
            if(totalBranches > 0)
            {
                cper /= 2.0f;
            }
            percent_coverage += static_cast<double>(cper);
            float cmet = static_cast<float>(percentFunction + percentBranch);
            if(totalBranches > 0)
            {
                cmet /= 2.0f;
            }
            cmet /= 100.0f;
            tmpLog << stdline << "\n";
            tmpLog << fileName << "\n";
            tmpLog << "functionsCalled: " << functionsCalled / 100 << "\n";
            tmpLog << "totalFunctions: " << totalFunctions / 100 << "\n";
            tmpLog << "percentFunction: " << percentFunction << "\n";
            tmpLog << "branchCovered: " << branchCovered << "\n";
            tmpLog << "totalBranches: " << totalBranches << "\n";
            tmpLog << "percentBranch: " << percentBranch << "\n";
            tmpLog << "percentCoverage: " << percent_coverage << "\n";
            tmpLog << "coverage metric: " << cmet << "\n";
            xml.StartElement("File");
            xml.Attribute("Name", sourceFile);
            xml.Attribute("FullPath", shortFileName);
            xml.Attribute("Covered", cmet > 0 ? "true" : "false");
            xml.Element("BranchesTested", branchCovered);
            xml.Element("BranchesUnTested", totalBranches - branchCovered);
            xml.Element("FunctionsTested", functionsCalled);
            xml.Element("FunctionsUnTested", totalFunctions - functionsCalled);
            // Hack for conversion of function to loc assume a function
            // has 100 lines of code
            xml.Element("LOCTested", functionsCalled * 100);
            xml.Element("LOCUnTested",
                        (totalFunctions - functionsCalled) * 100);
            xml.Element("PercentCoverage", cper);
            xml.Element("CoverageMetric", cmet);
            this->WriteXMLLabels(xml, shortFileName);
            xml.EndElement();  // File
        }
    }
    std::string end_time = this->CTest->CurrentTime();
    xml.Element("LOCTested", total_tested);
    xml.Element("LOCUntested", total_untested);
    xml.Element("LOC", total_functions);
    xml.Element("PercentCoverage", SAFEDIV(percent_coverage, number_files));
    xml.Element("EndDateTime", end_time);
    xml.Element("EndTime", std::chrono::system_clock::now());
    xml.Element("ElapsedMinutes",
                std::chrono::duration_cast<std::chrono::minutes>(
                    std::chrono::steady_clock::now() - elapsed_time_start)
                    .count());
    xml.EndElement();  // Coverage
    this->CTest->EndXML(xml);

    // Now create the coverage information for each file
    return this->RunBullseyeCoverageBranch(cont, coveredFileNames, coveredFiles,
                                           coveredFilesFullPath);
}

int
cmCTestCoverageHandler::HandleBullseyeCoverage(
    cmCTestCoverageHandlerContainer* cont)
{
    std::string covfile;
    if(!cmSystemTools::GetEnv("COVFILE", covfile) || covfile.empty())
    {
        cmCTestOptionalLog(
            this->CTest, HANDLER_VERBOSE_OUTPUT,
            " COVFILE environment variable not found, not running "
            " bullseye\n",
            this->Quiet);
        return 0;
    }
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " run covsrc with COVFILE=[" << covfile << "]"
                                                    << std::endl,
                       this->Quiet);
    if(!this->RunBullseyeSourceSummary(cont))
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error running bullseye summary.\n");
        return 0;
    }
    cmCTestOptionalLog(this->CTest, DEBUG,
                       "HandleBullseyeCoverage return 1 " << std::endl,
                       this->Quiet);
    return 1;
}

bool
cmCTestCoverageHandler::GetNextInt(std::string const&      inputLine,
                                   std::string::size_type& pos, int& value)
{
    std::string::size_type start = pos;
    pos                          = inputLine.find(',', start);
    value                        = atoi(inputLine.substr(start, pos).c_str());
    if(pos == std::string::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 == std::string::npos)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error parsing string : " << inputLine << "\n");
        return false;
    }
    // the source file has "" around it so extract out the file name
    sourceFile = inputLine.substr(1, pos - 2);
    pos++;
    if(!this->GetNextInt(inputLine, pos, functionsCalled))
    {
        return false;
    }
    if(!this->GetNextInt(inputLine, pos, totalFunctions))
    {
        return false;
    }
    if(!this->GetNextInt(inputLine, pos, percentFunction))
    {
        return false;
    }
    if(!this->GetNextInt(inputLine, pos, branchCovered))
    {
        return false;
    }
    if(!this->GetNextInt(inputLine, pos, totalBranches))
    {
        return false;
    }
    if(!this->GetNextInt(inputLine, pos, percentBranch))
    {
        return false;
    }
    // should be at the end now
    if(pos != std::string::npos)
    {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Error parsing input : "
                       << inputLine << " last pos not npos =  " << pos << "\n");
    }
    return true;
}

int
cmCTestCoverageHandler::GetLabelId(std::string const& label)
{
    LabelIdMapType::iterator i = this->LabelIdMap.find(label);
    if(i == this->LabelIdMap.end())
    {
        int n = int(this->Labels.size());
        this->Labels.push_back(label);
        LabelIdMapType::value_type entry(label, n);
        i = this->LabelIdMap.insert(entry).first;
    }
    return i->second;
}

void
cmCTestCoverageHandler::LoadLabels()
{
    std::string fileList = this->CTest->GetBinaryDir();
    fileList += cmake::GetCMakeFilesDirectory();
    fileList += "/TargetDirectories.txt";
    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " target directory list [" << fileList << "]\n",
                       this->Quiet);
    cmsys::ifstream finList(fileList.c_str());
    std::string     line;
    while(cmSystemTools::GetLineFromStream(finList, line))
    {
        this->LoadLabels(line.c_str());
    }
}

void
cmCTestCoverageHandler::LoadLabels(const char* dir)
{
    LabelSet&   dirLabels = this->TargetDirs[dir];
    std::string fname     = dir;
    fname += "/Labels.txt";
    cmsys::ifstream fin(fname.c_str());
    if(!fin)
    {
        return;
    }

    cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                       " loading labels from [" << fname << "]\n", this->Quiet);
    bool             inTarget = true;
    std::string      source;
    std::string      line;
    std::vector<int> targetLabels;
    while(cmSystemTools::GetLineFromStream(fin, line))
    {
        if(line.empty() || line[0] == '#')
        {
            // Ignore blank and comment lines.
            continue;
        }
        if(line[0] == ' ')
        {
            // Label lines appear indented by one space.
            std::string label = line.substr(1);
            int         id    = this->GetLabelId(label);
            dirLabels.insert(id);
            if(inTarget)
            {
                targetLabels.push_back(id);
            } else
            {
                this->SourceLabels[source].insert(id);
            }
        } else
        {
            // Non-indented lines specify a source file name.  The first one
            // is the end of the target-wide labels.
            inTarget = false;

            source = this->CTest->GetShortPathToFile(line.c_str());

            // Label the source with the target labels.
            LabelSet& labelSet = this->SourceLabels[source];
            labelSet.insert(targetLabels.begin(), targetLabels.end());
        }
    }
}

void
cmCTestCoverageHandler::WriteXMLLabels(cmXMLWriter&       xml,
                                       std::string const& source)
{
    LabelMapType::const_iterator li = this->SourceLabels.find(source);
    if(li != this->SourceLabels.end() && !li->second.empty())
    {
        xml.StartElement("Labels");
        for(auto const& ls : li->second)
        {
            xml.Element("Label", this->Labels[ls]);
        }
        xml.EndElement();  // Labels
    }
}

void
cmCTestCoverageHandler::SetLabelFilter(std::set<std::string> const& labels)
{
    this->LabelFilter.clear();
    for(std::string const& l : labels)
    {
        this->LabelFilter.insert(this->GetLabelId(l));
    }
}

bool
cmCTestCoverageHandler::IntersectsFilter(LabelSet const& labels)
{
    // If there is no label filter then nothing is filtered out.
    if(this->LabelFilter.empty())
    {
        return true;
    }

    std::vector<int> ids;
    std::set_intersection(labels.begin(), labels.end(),
                          this->LabelFilter.begin(), this->LabelFilter.end(),
                          std::back_inserter(ids));
    return !ids.empty();
}

bool
cmCTestCoverageHandler::IsFilteredOut(std::string const& source)
{
    // If there is no label filter then nothing is filtered out.
    if(this->LabelFilter.empty())
    {
        return false;
    }

    // The source is filtered out if it does not have any labels in
    // common with the filter set.
    std::string shortSrc = this->CTest->GetShortPathToFile(source.c_str());
    LabelMapType::const_iterator li = this->SourceLabels.find(shortSrc);
    if(li != this->SourceLabels.end())
    {
        return !this->IntersectsFilter(li->second);
    }
    return true;
}

std::set<std::string>
cmCTestCoverageHandler::FindUncoveredFiles(
    cmCTestCoverageHandlerContainer* cont)
{
    std::set<std::string> extraMatches;

    for(std::string const& ecg : this->ExtraCoverageGlobs)
    {
        cmsys::Glob gl;
        gl.RecurseOn();
        gl.RecurseThroughSymlinksOff();
        std::string glob = cont->SourceDir + "/" + ecg;
        gl.FindFiles(glob);
        std::vector<std::string> files = gl.GetFiles();
        for(std::string const& f : files)
        {
            if(this->ShouldIDoCoverage(f, cont->SourceDir, cont->BinaryDir))
            {
                extraMatches.insert(this->CTest->GetShortPathToFile(f.c_str()));
            }
        }
    }

    if(!extraMatches.empty())
    {
        for(auto const& i : cont->TotalCoverage)
        {
            std::string shortPath =
                this->CTest->GetShortPathToFile(i.first.c_str());
            extraMatches.erase(shortPath);
        }
    }
    return extraMatches;
}
