summaryrefslogtreecommitdiffstats
path: root/Source/CTest/cmParseCacheCoverage.cxx
blob: 23176b52462dc53bae4eb49e1a9c0c7b4c2c0ce5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#include "cmParseCacheCoverage.h"

#include "cmCTest.h"
#include "cmCTestCoverageHandler.h"
#include "cmSystemTools.h"

#include <cmsys/Directory.hxx>
#include <cmsys/FStream.hxx>
#include <map>
#include <stdio.h>
#include <stdlib.h>
#include <utility>

cmParseCacheCoverage::cmParseCacheCoverage(
  cmCTestCoverageHandlerContainer& cont, cmCTest* ctest)
  : cmParseMumpsCoverage(cont, ctest)
{
}

bool cmParseCacheCoverage::LoadCoverageData(const char* d)
{
  // load all the .mcov files in the specified directory
  cmsys::Directory dir;
  if (!dir.Load(d)) {
    return false;
  }
  size_t numf;
  unsigned int i;
  numf = dir.GetNumberOfFiles();
  for (i = 0; i < numf; i++) {
    std::string file = dir.GetFile(i);
    if (file != "." && file != ".." && !cmSystemTools::FileIsDirectory(file)) {
      std::string path = d;
      path += "/";
      path += file;
      if (cmSystemTools::GetFilenameLastExtension(path) == ".cmcov") {
        if (!this->ReadCMCovFile(path.c_str())) {
          return false;
        }
      }
    }
  }
  return true;
}

// not currently used, but leave it in case we want it in the future
void cmParseCacheCoverage::RemoveUnCoveredFiles()
{
  // loop over the coverage data computed and remove all files
  // that only have -1 or 0 for the lines.
  cmCTestCoverageHandlerContainer::TotalCoverageMap::iterator ci =
    this->Coverage.TotalCoverage.begin();
  while (ci != this->Coverage.TotalCoverage.end()) {
    cmCTestCoverageHandlerContainer::SingleFileCoverageVector& v = ci->second;
    bool nothing = true;
    for (cmCTestCoverageHandlerContainer::SingleFileCoverageVector::iterator
           i = v.begin();
         i != v.end(); ++i) {
      if (*i > 0) {
        nothing = false;
        break;
      }
    }
    if (nothing) {
      cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                         "No coverage found in: " << ci->first << std::endl,
                         this->Coverage.Quiet);
      this->Coverage.TotalCoverage.erase(ci++);
    } else {
      ++ci;
    }
  }
}

bool cmParseCacheCoverage::SplitString(std::vector<std::string>& args,
                                       std::string const& line)
{
  std::string::size_type pos1 = 0;
  std::string::size_type pos2 = line.find(',', 0);
  if (pos2 == std::string::npos) {
    return false;
  }
  std::string arg;
  while (pos2 != std::string::npos) {
    arg = line.substr(pos1, pos2 - pos1);
    args.push_back(arg);
    pos1 = pos2 + 1;
    pos2 = line.find(',', pos1);
  }
  arg = line.substr(pos1);
  args.push_back(arg);
  return true;
}

bool cmParseCacheCoverage::ReadCMCovFile(const char* file)
{
  cmsys::ifstream in(file);
  if (!in) {
    cmCTestLog(this->CTest, ERROR_MESSAGE, "Can not open : " << file << "\n");
    return false;
  }
  std::string line;
  std::vector<std::string> separateLine;
  if (!cmSystemTools::GetLineFromStream(in, line)) {
    cmCTestLog(this->CTest, ERROR_MESSAGE, "Empty file : "
                 << file << "  referenced in this line of cmcov data:\n"
                            "["
                 << line << "]\n");
    return false;
  }
  separateLine.clear();
  this->SplitString(separateLine, line);
  if (separateLine.size() != 4 || separateLine[0] != "Routine" ||
      separateLine[1] != "Line" || separateLine[2] != "RtnLine" ||
      separateLine[3] != "Code") {
    cmCTestLog(this->CTest, ERROR_MESSAGE,
               "Bad first line of cmcov file : " << file << "  line:\n"
                                                            "["
                                                 << line << "]\n");
  }
  std::string routine;
  std::string filepath;
  while (cmSystemTools::GetLineFromStream(in, line)) {
    // clear out line argument vector
    separateLine.clear();
    // parse the comma separated line
    this->SplitString(separateLine, line);
    // might have more because code could have a quoted , in it
    // but we only care about the first 3 args anyway
    if (separateLine.size() < 4) {
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Bad line of cmcov file expected at least 4 found: "
                   << separateLine.size() << " " << file << "  line:\n"
                                                            "["
                   << line << "]\n");
      for (std::string::size_type i = 0; i < separateLine.size(); ++i) {
        cmCTestLog(this->CTest, ERROR_MESSAGE, "" << separateLine[1] << " ");
      }
      cmCTestLog(this->CTest, ERROR_MESSAGE, "\n");
      return false;
    }
    // if we do not have a routine yet, then it should be
    // the first argument in the vector
    if (routine.empty()) {
      routine = separateLine[0];
      // Find the full path to the file
      if (!this->FindMumpsFile(routine, filepath)) {
        cmCTestLog(this->CTest, ERROR_MESSAGE,
                   "Could not find mumps file for routine: " << routine
                                                             << "\n");
        filepath = "";
        continue; // move to next line
      }
    }
    // if we have a routine name, check for end of routine
    else {
      // Totals in arg 0 marks the end of a routine
      if (separateLine[0].substr(0, 6) == "Totals") {
        routine = ""; // at the end of this routine
        filepath = "";
        continue; // move to next line
      }
    }
    // if the file path was not found for the routine
    // move to next line. We should have already warned
    // after the call to FindMumpsFile that we did not find
    // it, so don't report again to cut down on output
    if (filepath.empty()) {
      continue;
    }
    // now we are ready to set the coverage from the line of data
    cmCTestCoverageHandlerContainer::SingleFileCoverageVector& coverageVector =
      this->Coverage.TotalCoverage[filepath];
    std::string::size_type linenumber = atoi(separateLine[1].c_str()) - 1;
    int count = atoi(separateLine[2].c_str());
    if (linenumber > coverageVector.size()) {
      cmCTestLog(this->CTest, ERROR_MESSAGE,
                 "Parse error line is greater than number of lines in file: "
                   << linenumber << " " << filepath << "\n");
      continue; // skip setting count to avoid crash
    }
    // now add to count for linenumber
    // for some reason the cache coverage adds extra lines to the
    // end of the file in some cases. Since they do not exist, we will
    // mark them as non executable
    while (linenumber >= coverageVector.size()) {
      coverageVector.push_back(-1);
    }
    // Accounts for lines that were previously marked
    // as non-executable code (-1). if the parser comes back with
    // a non-zero count, increase the count by 1 to push the line
    // into the executable code set in addition to the count found.
    if (coverageVector[linenumber] == -1 && count > 0) {
      coverageVector[linenumber] += count + 1;
    } else {
      coverageVector[linenumber] += count;
    }
  }
  return true;
}
">} switch (this->Directive) { case DirectiveNone: break; case DirectiveParsedLiteral: this->ProcessDirectiveParsedLiteral(); break; case DirectiveLiteralBlock: this->ProcessDirectiveLiteralBlock(); break; case DirectiveCodeBlock: this->ProcessDirectiveCodeBlock(); break; case DirectiveReplace: this->ProcessDirectiveReplace(); break; case DirectiveTocTree: this->ProcessDirectiveTocTree(); break; } this->Markup = MarkupNone; this->Directive = DirectiveNone; this->MarkupLines.clear(); } void cmRST::ProcessLine(std::string const& line) { bool lastLineEndedInColonColon = this->LastLineEndedInColonColon; this->LastLineEndedInColonColon = false; // A line starting in .. is an explicit markup start. if (line == ".." || (line.size() >= 3 && line[0] == '.' && line[1] == '.' && isspace(line[2]))) { this->Reset(); this->Markup = (line.find_first_not_of(" \t", 2) == std::string::npos ? MarkupEmpty : MarkupNormal); // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165 // NOLINTNEXTLINE(bugprone-branch-clone) if (this->CMakeDirective.find(line)) { // Output cmake domain directives and their content normally. this->NormalLine(line); } else if (this->CMakeModuleDirective.find(line)) { // Process cmake-module directive: scan .cmake file comments. std::string file = this->CMakeModuleDirective.match(1); if (file.empty() || !this->ProcessInclude(file, IncludeModule)) { this->NormalLine(line); } } else if (this->ParsedLiteralDirective.find(line)) { // Record the literal lines to output after whole block. this->Directive = DirectiveParsedLiteral; this->MarkupLines.push_back(this->ParsedLiteralDirective.match(1)); } else if (this->CodeBlockDirective.find(line)) { // Record the literal lines to output after whole block. // Ignore the language spec and record the opening line as blank. this->Directive = DirectiveCodeBlock; this->MarkupLines.emplace_back(); } else if (this->ReplaceDirective.find(line)) { // Record the replace directive content. this->Directive = DirectiveReplace; this->ReplaceName = this->ReplaceDirective.match(1); this->MarkupLines.push_back(this->ReplaceDirective.match(2)); } else if (this->IncludeDirective.find(line)) { // Process the include directive or output the directive and its // content normally if it fails. std::string file = this->IncludeDirective.match(1); if (file.empty() || !this->ProcessInclude(file, IncludeNormal)) { this->NormalLine(line); } } else if (this->TocTreeDirective.find(line)) { // Record the toctree entries to process after whole block. this->Directive = DirectiveTocTree; this->MarkupLines.push_back(this->TocTreeDirective.match(1)); } else if (this->ProductionListDirective.find(line)) { // Output productionlist directives and their content normally. this->NormalLine(line); } else if (this->NoteDirective.find(line)) { // Output note directives and their content normally. this->NormalLine(line); } else if (this->VersionDirective.find(line)) { // Output versionadded and versionchanged directives and their content // normally. this->NormalLine(line); } } // An explicit markup start followed nothing but whitespace and a // blank line does not consume any indented text following. else if (this->Markup == MarkupEmpty && line.empty()) { this->NormalLine(line); } // Indented lines following an explicit markup start are explicit markup. else if (this->Markup && (line.empty() || isspace(line[0]))) { this->Markup = MarkupNormal; // Record markup lines if the start line was recorded. if (!this->MarkupLines.empty()) { this->MarkupLines.push_back(line); } } // A blank line following a paragraph ending in "::" starts a literal block. else if (lastLineEndedInColonColon && line.empty()) { // Record the literal lines to output after whole block. this->Markup = MarkupNormal; this->Directive = DirectiveLiteralBlock; this->MarkupLines.emplace_back(); this->OutputLine("", false); } // Print non-markup lines. else { this->NormalLine(line); this->LastLineEndedInColonColon = (line.size() >= 2 && line[line.size() - 2] == ':' && line.back() == ':'); } } void cmRST::NormalLine(std::string const& line) { this->Reset(); this->OutputLine(line, true); } void cmRST::OutputLine(std::string const& line_in, bool inlineMarkup) { if (this->OutputLinePending) { this->OS << "\n"; this->OutputLinePending = false; } if (inlineMarkup) { std::string line = this->ReplaceSubstitutions(line_in); std::string::size_type pos = 0; for (;;) { std::string::size_type* first = nullptr; std::string::size_type role_start = std::string::npos; std::string::size_type link_start = std::string::npos; std::string::size_type lit_start = std::string::npos; if (this->CMakeRole.find(line.c_str() + pos)) { role_start = this->CMakeRole.start(); first = &role_start; } if (this->InlineLiteral.find(line.c_str() + pos)) { lit_start = this->InlineLiteral.start(); if (!first || lit_start < *first) { first = &lit_start; } } if (this->InlineLink.find(line.c_str() + pos)) { link_start = this->InlineLink.start(); if (!first || link_start < *first) { first = &link_start; } } if (first == &role_start) { this->OS << line.substr(pos, role_start); std::string text = this->CMakeRole.match(3); // If a command reference has no explicit target and // no explicit "(...)" then add "()" to the text. if (this->CMakeRole.match(2) == "command" && this->CMakeRole.match(5).empty() && text.find_first_of("()") == std::string::npos) { text += "()"; } this->OS << "``" << text << "``"; pos += this->CMakeRole.end(); } else if (first == &lit_start) { this->OS << line.substr(pos, lit_start); std::string text = this->InlineLiteral.match(1); pos += this->InlineLiteral.end(); this->OS << "``" << text << "``"; } else if (first == &link_start) { this->OS << line.substr(pos, link_start); std::string text = this->InlineLink.match(1); bool escaped = false; for (char c : text) { if (escaped) { escaped = false; this->OS << c; } else if (c == '\\') { escaped = true; } else { this->OS << c; } } pos += this->InlineLink.end(); } else { break; } } this->OS << line.substr(pos) << "\n"; } else { this->OS << line_in << "\n"; } } std::string cmRST::ReplaceSubstitutions(std::string const& line) { std::string out; std::string::size_type pos = 0; while (this->Substitution.find(line.c_str() + pos)) { std::string::size_type start = this->Substitution.start(2); std::string::size_type end = this->Substitution.end(2); std::string substitute = this->Substitution.match(3); auto replace = this->Replace.find(substitute); if (replace != this->Replace.end()) { std::pair<std::set<std::string>::iterator, bool> replaced = this->Replaced.insert(substitute); if (replaced.second) { substitute = this->ReplaceSubstitutions(replace->second); this->Replaced.erase(replaced.first); } } out += line.substr(pos, start); out += substitute; pos += end; } out += line.substr(pos); return out; } void cmRST::OutputMarkupLines(bool inlineMarkup) { for (auto line : this->MarkupLines) { if (!line.empty()) { line = cmStrCat(" ", line); } this->OutputLine(line, inlineMarkup); } this->OutputLinePending = true; } bool cmRST::ProcessInclude(std::string file, IncludeType type) { bool found = false; if (this->IncludeDepth < 10) { cmRST r(this->OS, this->DocRoot); r.IncludeDepth = this->IncludeDepth + 1; r.OutputLinePending = this->OutputLinePending; if (type != IncludeTocTree) { r.Replace = this->Replace; } if (file[0] == '/') { file = this->DocRoot + file; } else { file = this->DocDir + "/" + file; } found = r.ProcessFile(file, type == IncludeModule); if (type != IncludeTocTree) { this->Replace = r.Replace; } this->OutputLinePending = r.OutputLinePending; } return found; } void cmRST::ProcessDirectiveParsedLiteral() { this->OutputMarkupLines(true); } void cmRST::ProcessDirectiveLiteralBlock() { this->OutputMarkupLines(false); } void cmRST::ProcessDirectiveCodeBlock() { this->OutputMarkupLines(false); } void cmRST::ProcessDirectiveReplace() { // Record markup lines as replacement text. std::string& replacement = this->Replace[this->ReplaceName]; replacement += cmJoin(this->MarkupLines, " "); this->ReplaceName.clear(); } void cmRST::ProcessDirectiveTocTree() { // Process documents referenced by toctree directive. for (std::string const& line : this->MarkupLines) { if (!line.empty() && line[0] != ':') { if (this->TocTreeLink.find(line)) { std::string const& link = this->TocTreeLink.match(1); this->ProcessInclude(link + ".rst", IncludeTocTree); } else { this->ProcessInclude(line + ".rst", IncludeTocTree); } } } } void cmRST::UnindentLines(std::vector<std::string>& lines) { // Remove the common indentation from the second and later lines. std::string indentText; std::string::size_type indentEnd = 0; bool first = true; for (size_t i = 1; i < lines.size(); ++i) { std::string const& line = lines[i]; // Do not consider empty lines. if (line.empty()) { continue; } // Record indentation on first non-empty line. if (first) { first = false; indentEnd = line.find_first_not_of(" \t"); indentText = line.substr(0, indentEnd); continue; } // Truncate indentation to match that on this line. indentEnd = std::min(indentEnd, line.size()); for (std::string::size_type j = 0; j != indentEnd; ++j) { if (line[j] != indentText[j]) { indentEnd = j; break; } } } // Update second and later lines. for (size_t i = 1; i < lines.size(); ++i) { std::string& line = lines[i]; if (!line.empty()) { line = line.substr(indentEnd); } } auto it = lines.cbegin(); size_t leadingEmpty = std::distance(it, cmFindNot(lines, std::string())); auto rit = lines.crbegin(); size_t trailingEmpty = std::distance(rit, cmFindNot(cmReverseRange(lines), std::string())); if ((leadingEmpty + trailingEmpty) >= lines.size()) { // All lines are empty. The markup block is empty. Leave only one. lines.resize(1); return; } auto contentEnd = cmRotate(lines.begin(), lines.begin() + leadingEmpty, lines.end() - trailingEmpty); lines.erase(contentEnd, lines.end()); }