| File src/GcovImporter.cpp changed (mode: 100644) (index 7eb03e3..dfec389) |
| 16 |
16 |
|
|
| 17 |
17 |
#include "GcovImporter.hpp" |
#include "GcovImporter.hpp" |
| 18 |
18 |
|
|
|
19 |
|
#include <boost/algorithm/string/predicate.hpp> |
| 19 |
20 |
#include <boost/algorithm/string/trim.hpp> |
#include <boost/algorithm/string/trim.hpp> |
| 20 |
21 |
#include <boost/filesystem/operations.hpp> |
#include <boost/filesystem/operations.hpp> |
|
22 |
|
#include <boost/iostreams/filter/gzip.hpp> |
|
23 |
|
#include <boost/iostreams/filtering_streambuf.hpp> |
|
24 |
|
#include <boost/property_tree/ptree.hpp> |
|
25 |
|
#include <boost/property_tree/json_parser.hpp> |
| 21 |
26 |
|
|
| 22 |
27 |
#include <cassert> |
#include <cassert> |
| 23 |
28 |
|
|
| 24 |
29 |
#include <fstream> |
#include <fstream> |
|
30 |
|
#include <istream> |
| 25 |
31 |
#include <regex> |
#include <regex> |
| 26 |
32 |
#include <set> |
#include <set> |
| 27 |
33 |
#include <stdexcept> |
#include <stdexcept> |
| |
| ... |
... |
namespace fs = boost::filesystem; |
| 42 |
48 |
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89961 for information about |
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89961 for information about |
| 43 |
49 |
// what's wrong with some versions of `gcov` and why binning is needed. |
// what's wrong with some versions of `gcov` and why binning is needed. |
| 44 |
50 |
|
|
|
51 |
|
//! `gcov` option to generate coverage in JSON format. |
|
52 |
|
static const char GcovJsonFormat[] = "--json-format"; |
|
53 |
|
//! `gcov` option to generate coverage in plain text format. |
|
54 |
|
static const char GcovIntermediateFormat[] = "--intermediate-format"; |
|
55 |
|
|
| 45 |
56 |
//! First version of `gcov` which has broken `--preserve-paths` option. |
//! First version of `gcov` which has broken `--preserve-paths` option. |
| 46 |
57 |
static int FirstBrokenGcovVersion = 8; |
static int FirstBrokenGcovVersion = 8; |
| 47 |
58 |
|
|
| |
| ... |
... |
namespace { |
| 98 |
109 |
}; |
}; |
| 99 |
110 |
} |
} |
| 100 |
111 |
|
|
| 101 |
|
GcovInfo::GcovInfo() : employBinning(true) |
|
|
112 |
|
GcovInfo::GcovInfo() |
|
113 |
|
: employBinning(true), jsonFormat(false), intermediateFormat(false) |
| 102 |
114 |
{ |
{ |
|
115 |
|
const std::regex optionRegex("--[-a-z]+"); |
| 103 |
116 |
const std::regex versionRegex("gcov \\(GCC\\) (.*)"); |
const std::regex versionRegex("gcov \\(GCC\\) (.*)"); |
| 104 |
117 |
|
|
| 105 |
118 |
std::smatch match; |
std::smatch match; |
| 106 |
119 |
|
|
|
120 |
|
const std::string help = readProc({ "gcov", "--help" }); |
|
121 |
|
auto from = help.cbegin(); |
|
122 |
|
auto to = help.cend(); |
|
123 |
|
while (std::regex_search(from, to, match, optionRegex)) { |
|
124 |
|
const std::string str = match.str(); |
|
125 |
|
if (str == GcovJsonFormat) { |
|
126 |
|
jsonFormat = true; |
|
127 |
|
} else if (str == GcovIntermediateFormat) { |
|
128 |
|
intermediateFormat = true; |
|
129 |
|
} |
|
130 |
|
|
|
131 |
|
from += match.position() + match.length(); |
|
132 |
|
} |
|
133 |
|
|
| 107 |
134 |
const std::string version = readProc({ "gcov", "--version" }); |
const std::string version = readProc({ "gcov", "--version" }); |
| 108 |
135 |
if (std::regex_search(version, match, versionRegex)) { |
if (std::regex_search(version, match, versionRegex)) { |
| 109 |
136 |
const int majorVersion = std::stoi(match[1]); |
const int majorVersion = std::stoi(match[1]); |
| |
| ... |
... |
GcovImporter::GcovImporter(const std::string &root, |
| 138 |
165 |
".deps" // Dependency tracking of automake. |
".deps" // Dependency tracking of automake. |
| 139 |
166 |
}; |
}; |
| 140 |
167 |
|
|
|
168 |
|
if (!gcovInfo.hasJsonFormat() && !gcovInfo.hasIntermediateFormat()) { |
|
169 |
|
throw std::runtime_error("Failed to detect machine format of gcov"); |
|
170 |
|
} |
|
171 |
|
|
| 141 |
172 |
std::vector<fs::path> gcnoFiles; |
std::vector<fs::path> gcnoFiles; |
| 142 |
173 |
for (fs::recursive_directory_iterator it(fs::absolute(covoutRoot)), end; |
for (fs::recursive_directory_iterator it(fs::absolute(covoutRoot)), end; |
| 143 |
174 |
it != end; ++it) { |
it != end; ++it) { |
| |
| ... |
... |
GcovImporter::importFiles(std::vector<fs::path> gcnoFiles) |
| 243 |
274 |
} |
} |
| 244 |
275 |
} |
} |
| 245 |
276 |
|
|
|
277 |
|
std::string gcovOption; |
|
278 |
|
std::string gcovFileExt; |
|
279 |
|
if (gcovInfo.hasJsonFormat()) { |
|
280 |
|
gcovOption = GcovJsonFormat; |
|
281 |
|
gcovFileExt = ".gcov.json.gz"; |
|
282 |
|
} else { |
|
283 |
|
gcovOption = GcovIntermediateFormat; |
|
284 |
|
gcovFileExt = ".gcov"; |
|
285 |
|
} |
|
286 |
|
|
| 246 |
287 |
for (const Bin &bin : bins) { |
for (const Bin &bin : bins) { |
| 247 |
288 |
const std::vector<std::string> &paths = bin.getPaths(); |
const std::vector<std::string> &paths = bin.getPaths(); |
| 248 |
289 |
|
|
| 249 |
290 |
std::vector<std::string> cmd = { |
std::vector<std::string> cmd = { |
| 250 |
|
"gcov", "--preserve-paths", "--intermediate-format", "--" |
|
|
291 |
|
"gcov", "--preserve-paths", gcovOption, "--" |
| 251 |
292 |
}; |
}; |
| 252 |
293 |
cmd.insert(cmd.cend(), paths.cbegin(), paths.cend()); |
cmd.insert(cmd.cend(), paths.cbegin(), paths.cend()); |
| 253 |
294 |
|
|
| |
| ... |
... |
GcovImporter::importFiles(std::vector<fs::path> gcnoFiles) |
| 256 |
297 |
getRunner()(std::move(cmd), tempDirPath); |
getRunner()(std::move(cmd), tempDirPath); |
| 257 |
298 |
|
|
| 258 |
299 |
for (fs::recursive_directory_iterator it(tempDirPath), end; |
for (fs::recursive_directory_iterator it(tempDirPath), end; |
| 259 |
|
it != end; ++it) { |
|
|
300 |
|
it != end; ++it) { |
| 260 |
301 |
fs::path path = it->path(); |
fs::path path = it->path(); |
| 261 |
|
if (fs::is_regular(path) && path.extension() == ".gcov") { |
|
| 262 |
|
parseGcov(path.string()); |
|
|
302 |
|
if (fs::is_regular(path) && |
|
303 |
|
boost::ends_with(path.filename().string(), gcovFileExt)) { |
|
304 |
|
if (gcovInfo.hasJsonFormat()) { |
|
305 |
|
parseGcovJsonGz(path.string()); |
|
306 |
|
} else { |
|
307 |
|
parseGcov(path.string()); |
|
308 |
|
} |
| 263 |
309 |
} |
} |
| 264 |
310 |
} |
} |
| 265 |
311 |
} |
} |
| 266 |
312 |
} |
} |
| 267 |
313 |
|
|
|
314 |
|
void |
|
315 |
|
GcovImporter::parseGcovJsonGz(const std::string &path) |
|
316 |
|
{ |
|
317 |
|
namespace io = boost::iostreams; |
|
318 |
|
namespace pt = boost::property_tree; |
|
319 |
|
|
|
320 |
|
std::ifstream file(path, std::ios_base::in | std::ios_base::binary); |
|
321 |
|
|
|
322 |
|
io::filtering_istreambuf in; |
|
323 |
|
in.push(io::gzip_decompressor()); |
|
324 |
|
in.push(file); |
|
325 |
|
|
|
326 |
|
std::basic_istream<char> is(&in); |
|
327 |
|
|
|
328 |
|
pt::ptree props; |
|
329 |
|
pt::read_json(is, props); |
|
330 |
|
|
|
331 |
|
for (auto &file : props.get_child("files")) { |
|
332 |
|
const std::string sourcePath = |
|
333 |
|
resolveSourcePath(file.second.get<std::string>("file")); |
|
334 |
|
if (sourcePath.empty()) { |
|
335 |
|
continue; |
|
336 |
|
} |
|
337 |
|
|
|
338 |
|
std::vector<int> &coverage = mapping[sourcePath]; |
|
339 |
|
for (auto &line : file.second.get_child("lines")) { |
|
340 |
|
updateCoverage(coverage, |
|
341 |
|
line.second.get<unsigned int>("line_number"), |
|
342 |
|
line.second.get<int>("count")); |
|
343 |
|
} |
|
344 |
|
} |
|
345 |
|
} |
|
346 |
|
|
| 268 |
347 |
void |
void |
| 269 |
348 |
GcovImporter::parseGcov(const std::string &path) |
GcovImporter::parseGcov(const std::string &path) |
| 270 |
349 |
{ |
{ |
| File src/GcovImporter.hpp changed (mode: 100644) (index 26b5bdf..a076dad) |
| ... |
... |
public: |
| 43 |
43 |
*/ |
*/ |
| 44 |
44 |
bool needsBinning() const |
bool needsBinning() const |
| 45 |
45 |
{ return employBinning; } |
{ return employBinning; } |
|
46 |
|
/** |
|
47 |
|
* @brief Checks whether JSON format is available. |
|
48 |
|
* |
|
49 |
|
* @returns `true` if so. |
|
50 |
|
*/ |
|
51 |
|
bool hasJsonFormat() const |
|
52 |
|
{ return jsonFormat; } |
|
53 |
|
/** |
|
54 |
|
* @brief Checks whether plain text format is available. |
|
55 |
|
* |
|
56 |
|
* @returns `true` if so. |
|
57 |
|
*/ |
|
58 |
|
bool hasIntermediateFormat() const |
|
59 |
|
{ return intermediateFormat; } |
| 46 |
60 |
|
|
| 47 |
61 |
private: |
private: |
| 48 |
62 |
//! Whether `gcov` command doesn't handle identically-named files properly. |
//! Whether `gcov` command doesn't handle identically-named files properly. |
| 49 |
63 |
bool employBinning; |
bool employBinning; |
|
64 |
|
//! Whether JSON intermediate format is supported. |
|
65 |
|
bool jsonFormat; |
|
66 |
|
//! Whether plain text intermediate format is supported. |
|
67 |
|
bool intermediateFormat; |
| 50 |
68 |
}; |
}; |
| 51 |
69 |
|
|
| 52 |
70 |
/** |
/** |
| |
| ... |
... |
private: |
| 98 |
116 |
* @param gcnoFiles Absolute paths to `*.gcno` files. |
* @param gcnoFiles Absolute paths to `*.gcno` files. |
| 99 |
117 |
*/ |
*/ |
| 100 |
118 |
void importFiles(std::vector<boost::filesystem::path> gcnoFiles); |
void importFiles(std::vector<boost::filesystem::path> gcnoFiles); |
|
119 |
|
/** |
|
120 |
|
* @brief Parses single `*.gcov.json.gz` file. |
|
121 |
|
* |
|
122 |
|
* @param path Path of the file. |
|
123 |
|
*/ |
|
124 |
|
void parseGcovJsonGz(const std::string &path); |
| 101 |
125 |
/** |
/** |
| 102 |
126 |
* @brief Parses single `*.gcov` file. |
* @brief Parses single `*.gcov` file. |
| 103 |
127 |
* |
* |
| File tests/sub_commands.cpp changed (mode: 100644) (index 1c727eb..0727a8b) |
| 18 |
18 |
|
|
| 19 |
19 |
#include <boost/algorithm/string/predicate.hpp> |
#include <boost/algorithm/string/predicate.hpp> |
| 20 |
20 |
#include <boost/filesystem/operations.hpp> |
#include <boost/filesystem/operations.hpp> |
|
21 |
|
#include <boost/iostreams/filter/gzip.hpp> |
|
22 |
|
#include <boost/iostreams/filtering_streambuf.hpp> |
| 21 |
23 |
#include <boost/optional.hpp> |
#include <boost/optional.hpp> |
| 22 |
24 |
#include <boost/scope_exit.hpp> |
#include <boost/scope_exit.hpp> |
| 23 |
25 |
|
|
| |
| 25 |
27 |
|
|
| 26 |
28 |
#include <fstream> |
#include <fstream> |
| 27 |
29 |
#include <iostream> |
#include <iostream> |
|
30 |
|
#include <ostream> |
| 28 |
31 |
#include <stdexcept> |
#include <stdexcept> |
| 29 |
32 |
#include <string> |
#include <string> |
| 30 |
33 |
|
|
| |
| ... |
... |
private: |
| 101 |
104 |
}; |
}; |
| 102 |
105 |
|
|
| 103 |
106 |
static SubCommand * getCmd(const std::string &name); |
static SubCommand * getCmd(const std::string &name); |
|
107 |
|
static void makeGcovJsonGz(const std::string &path, |
|
108 |
|
const std::string &contents); |
| 104 |
109 |
|
|
| 105 |
110 |
const std::string build3info = |
const std::string build3info = |
| 106 |
111 |
R"(Id: #3 |
R"(Id: #3 |
| |
| ... |
... |
TEST_CASE("Gcov file is found and parsed", |
| 1218 |
1223 |
|
|
| 1219 |
1224 |
auto runner = [](std::vector<std::string> &&/*cmd*/, |
auto runner = [](std::vector<std::string> &&/*cmd*/, |
| 1220 |
1225 |
const std::string &dir) { |
const std::string &dir) { |
| 1221 |
|
std::ofstream{dir + "/test-file1.gcov"} |
|
| 1222 |
|
<< "file:test-file1.cpp\n" |
|
| 1223 |
|
<< "lcount:2,1\n" |
|
| 1224 |
|
<< "lcount:4,1\n"; |
|
|
1226 |
|
GcovInfo gcovInfo; |
|
1227 |
|
if (gcovInfo.hasJsonFormat()) { |
|
1228 |
|
makeGcovJsonGz(dir + "/test-file1.gcno.gcov.json.gz", R"({ |
|
1229 |
|
"files": [{ |
|
1230 |
|
"file": "test-file1.cpp", |
|
1231 |
|
"lines": [ |
|
1232 |
|
{ "line_number": 2, "count": 1 }, |
|
1233 |
|
{ "line_number": 4, "count": 1 } |
|
1234 |
|
] |
|
1235 |
|
}] |
|
1236 |
|
})"); |
|
1237 |
|
} else { |
|
1238 |
|
std::ofstream{dir + "/test-file1.gcov"} |
|
1239 |
|
<< "file:test-file1.cpp\n" |
|
1240 |
|
<< "lcount:2,1\n" |
|
1241 |
|
<< "lcount:4,1\n"; |
|
1242 |
|
} |
| 1225 |
1243 |
}; |
}; |
| 1226 |
1244 |
GcovImporter::setRunner(runner); |
GcovImporter::setRunner(runner); |
| 1227 |
1245 |
|
|
| |
| ... |
... |
TEST_CASE("Gcov file with broken format causes an exception", |
| 1247 |
1265 |
|
|
| 1248 |
1266 |
auto runner = [](std::vector<std::string> &&/*cmd*/, |
auto runner = [](std::vector<std::string> &&/*cmd*/, |
| 1249 |
1267 |
const std::string &dir) { |
const std::string &dir) { |
| 1250 |
|
std::ofstream{dir + "/test-file1.gcov"} |
|
| 1251 |
|
<< "file:test-file1.cpp\n" |
|
| 1252 |
|
<< "lcount:2\n"; |
|
|
1268 |
|
GcovInfo gcovInfo; |
|
1269 |
|
if (gcovInfo.hasJsonFormat()) { |
|
1270 |
|
makeGcovJsonGz(dir + "/test-file1.gcno.gcov.json.gz", R"({ |
|
1271 |
|
"files": [{ |
|
1272 |
|
"file": "test-file1.cpp", |
|
1273 |
|
"lines": [ { "line_number": 2, "count": 0 }, ] |
|
1274 |
|
}] |
|
1275 |
|
})"); |
|
1276 |
|
} else { |
|
1277 |
|
std::ofstream{dir + "/test-file1.gcov"} |
|
1278 |
|
<< "file:test-file1.cpp\n" |
|
1279 |
|
<< "lcount:2\n"; |
|
1280 |
|
} |
| 1253 |
1281 |
}; |
}; |
| 1254 |
1282 |
GcovImporter::setRunner(runner); |
GcovImporter::setRunner(runner); |
| 1255 |
1283 |
|
|
| |
| ... |
... |
getCmd(const std::string &name) |
| 1571 |
1599 |
} |
} |
| 1572 |
1600 |
throw std::invalid_argument("No such command: " + name); |
throw std::invalid_argument("No such command: " + name); |
| 1573 |
1601 |
} |
} |
|
1602 |
|
|
|
1603 |
|
static void |
|
1604 |
|
makeGcovJsonGz(const std::string &path, const std::string &contents) |
|
1605 |
|
{ |
|
1606 |
|
std::ofstream file(path, std::ios_base::out | std::ios_base::binary); |
|
1607 |
|
|
|
1608 |
|
boost::iostreams::filtering_ostreambuf out; |
|
1609 |
|
out.push(boost::iostreams::gzip_compressor()); |
|
1610 |
|
out.push(file); |
|
1611 |
|
|
|
1612 |
|
std::basic_ostream<char>(&out) << contents; |
|
1613 |
|
} |