| #include "cmParseDelphiCoverage.h" | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include "cmsys/FStream.hxx" | |
| #include "cmsys/Glob.hxx" | |
| #include "cmCTest.h" | |
| #include "cmCTestCoverageHandler.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| class cmParseDelphiCoverage::HTMLParser | |
| { | |
| public: | |
| using FileLinesType = | |
| cmCTestCoverageHandlerContainer::SingleFileCoverageVector; | |
| HTMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont) | |
| : CTest(ctest) | |
| , Coverage(cont) | |
| { | |
| } | |
| virtual ~HTMLParser() = default; | |
| bool initializeDelphiFile( | |
| std::string const& filename, | |
| cmParseDelphiCoverage::HTMLParser::FileLinesType& coverageVector) | |
| { | |
| std::string line; | |
| size_t comPos; | |
| size_t semiPos; | |
| bool blockComFlag = false; | |
| bool lineComFlag = false; | |
| std::vector<std::string> beginSet; | |
| cmsys::ifstream in(filename.c_str()); | |
| if (!in) { | |
| return false; | |
| } | |
| while (cmSystemTools::GetLineFromStream(in, line)) { | |
| lineComFlag = false; | |
| // Unique cases found in lines. | |
| size_t beginPos = line.find("begin"); | |
| // Check that the begin is the first non-space string on the line | |
| if ((beginPos == line.find_first_not_of(' ')) && | |
| beginPos != std::string::npos) { | |
| beginSet.emplace_back("begin"); | |
| coverageVector.push_back(-1); | |
| continue; | |
| } | |
| if (line.find('{') != std::string::npos) { | |
| blockComFlag = true; | |
| } else if (line.find('}') != std::string::npos) { | |
| blockComFlag = false; | |
| coverageVector.push_back(-1); | |
| continue; | |
| } else if ((line.find("end;") != std::string::npos) && | |
| !beginSet.empty()) { | |
| beginSet.pop_back(); | |
| coverageVector.push_back(-1); | |
| continue; | |
| } | |
| // This checks for comments after lines of code, finding the | |
| // comment symbol after the ending semicolon. | |
| comPos = line.find("//"); | |
| if (comPos != std::string::npos) { | |
| semiPos = line.find(';'); | |
| if (comPos < semiPos) { | |
| lineComFlag = true; | |
| } | |
| } | |
| // Based up what was found, add a line to the coverageVector | |
| if (!beginSet.empty() && !line.empty() && !blockComFlag && | |
| !lineComFlag) { | |
| coverageVector.push_back(0); | |
| } else { | |
| coverageVector.push_back(-1); | |
| } | |
| } | |
| return true; | |
| } | |
| bool ParseFile(char const* file) | |
| { | |
| std::string line = file; | |
| std::string lineresult; | |
| std::string lastroutine; | |
| std::string filename; | |
| std::string filelineoffset; | |
| size_t afterLineNum = 0; | |
| size_t lastoffset = 0; | |
| size_t endcovpos = 0; | |
| size_t endnamepos = 0; | |
| size_t pos = 0; | |
| /* | |
| * This first 'while' section goes through the found HTML | |
| * file name and attempts to capture the source file name | |
| * which is set as part of the HTML file name: the name of | |
| * the file is found in parenthesis '()' | |
| * | |
| * See test HTML file name: UTCovTest(UTCovTest.pas).html. | |
| * | |
| * Find the text inside each pair of parenthesis and check | |
| * to see if it ends in '.pas'. If it can't be found, | |
| * exit the function. | |
| */ | |
| while (true) { | |
| lastoffset = line.find('(', pos); | |
| if (lastoffset == std::string::npos) { | |
| cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, | |
| this->Coverage.Quiet, endnamepos, | |
| "File not found ", lastoffset, '\n'); | |
| return false; | |
| } | |
| endnamepos = line.find(')', lastoffset); | |
| filename = line.substr(lastoffset + 1, (endnamepos - 1) - lastoffset); | |
| if (filename.find(".pas") != std::string::npos) { | |
| cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, | |
| this->Coverage.Quiet, | |
| "Coverage found for file: ", filename, '\n'); | |
| break; | |
| } | |
| pos = lastoffset + 1; | |
| } | |
| /* | |
| * Glob through the source directory for the | |
| * file found above | |
| */ | |
| cmsys::Glob gl; | |
| gl.RecurseOn(); | |
| gl.RecurseThroughSymlinksOff(); | |
| std::string glob = cmStrCat(this->Coverage.SourceDir, "*/", filename); | |
| gl.FindFiles(glob); | |
| std::vector<std::string> const& files = gl.GetFiles(); | |
| if (files.empty()) { | |
| /* | |
| * If that doesn't find any matching files | |
| * return a failure. | |
| */ | |
| cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, | |
| this->Coverage.Quiet, "Unable to find file matching", | |
| glob, '\n'); | |
| return false; | |
| } | |
| FileLinesType& coverageVector = this->Coverage.TotalCoverage[files[0]]; | |
| /* | |
| * Initialize the file to have all code between 'begin' and | |
| * 'end' tags marked as executable | |
| */ | |
| this->initializeDelphiFile(files[0], coverageVector); | |
| cmsys::ifstream in(file); | |
| if (!in) { | |
| return false; | |
| } | |
| /* | |
| * Now read the HTML file, looking for the lines that have an | |
| * "inline" in it. Then parse out the "class" value of that | |
| * line to determine if the line is executed or not. | |
| * | |
| * Sample HTML line: | |
| * | |
| * <tr class="covered"><td>47</td><td><pre style="display:inline;"> | |
| * CheckEquals(1,2-1);</pre></td></tr> | |
| * | |
| */ | |
| while (cmSystemTools::GetLineFromStream(in, line)) { | |
| if (line.find("inline") == std::string::npos) { | |
| continue; | |
| } | |
| lastoffset = line.find("class="); | |
| endcovpos = line.find('>', lastoffset); | |
| lineresult = line.substr(lastoffset + 7, (endcovpos - 8) - lastoffset); | |
| if (lineresult == "covered") { | |
| afterLineNum = line.find('<', endcovpos + 5); | |
| filelineoffset = | |
| line.substr(endcovpos + 5, afterLineNum - (endcovpos + 5)); | |
| coverageVector[atoi(filelineoffset.c_str()) - 1] = 1; | |
| } | |
| } | |
| return true; | |
| } | |
| private: | |
| cmCTest* CTest; | |
| cmCTestCoverageHandlerContainer& Coverage; | |
| }; | |
| cmParseDelphiCoverage::cmParseDelphiCoverage( | |
| cmCTestCoverageHandlerContainer& cont, cmCTest* ctest) | |
| : Coverage(cont) | |
| , CTest(ctest) | |
| { | |
| } | |
| bool cmParseDelphiCoverage::LoadCoverageData( | |
| std::vector<std::string> const& files) | |
| { | |
| size_t i; | |
| std::string path; | |
| size_t numf = files.size(); | |
| for (i = 0; i < numf; i++) { | |
| path = files[i]; | |
| cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT, | |
| this->Coverage.Quiet, "Reading HTML File ", path, '\n'); | |
| if (cmSystemTools::GetFilenameLastExtension(path) == ".html") { | |
| if (!this->ReadDelphiHTML(path.c_str())) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool cmParseDelphiCoverage::ReadDelphiHTML(char const* file) | |
| { | |
| cmParseDelphiCoverage::HTMLParser parser(this->CTest, this->Coverage); | |
| parser.ParseFile(file); | |
| return true; | |
| } |