| #include "cmParseDelphiCoverage.h" |
| |
| #include <cstdio> |
| #include <cstdlib> |
| |
| #include "cmsys/FStream.hxx" |
| #include "cmsys/Glob.hxx" |
| |
| #include "cmCTest.h" |
| #include "cmCTestCoverageHandler.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(const char* 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, |
| endnamepos << "File not found " << lastoffset |
| << std::endl, |
| this->Coverage.Quiet); |
| 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, |
| "Coverage found for file: " << filename |
| << std::endl, |
| this->Coverage.Quiet); |
| break; |
| } |
| pos = lastoffset + 1; |
| } |
| /* |
| * Glob through the source directory for the |
| * file found above |
| */ |
| cmsys::Glob gl; |
| gl.RecurseOn(); |
| gl.RecurseThroughSymlinksOff(); |
| std::string glob = 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, |
| "Unable to find file matching" << glob << std::endl, |
| this->Coverage.Quiet); |
| 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, |
| "Reading HTML File " << path << std::endl, |
| this->Coverage.Quiet); |
| if (cmSystemTools::GetFilenameLastExtension(path) == ".html") { |
| if (!this->ReadDelphiHTML(path.c_str())) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool cmParseDelphiCoverage::ReadDelphiHTML(const char* file) |
| { |
| cmParseDelphiCoverage::HTMLParser parser(this->CTest, this->Coverage); |
| parser.ParseFile(file); |
| return true; |
| } |