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