xaizek / uncov (License: AGPLv3+) (since 2018-12-07)
Uncov(er) is a tool that collects and processes code coverage reports.
Commit db6e195f42f44335df2afaf84549da20db258189

Support `gcov --json-format` by `new-gcovi`
Author: xaizek
Author date (UTC): 2021-06-05 13:53
Committer name: xaizek
Committer date (UTC): 2021-06-05 13:53
Parent(s): 68397dc05a5ba208fad46a86e987d4ef87116f5d
Signing key: 99DC5E4DB05F6BE2
Tree: 2238c294587e9aef558355369493863171db3a79
File Lines added Lines deleted
docs/uncov.1 0 2
docs/uncov/06-subcommand-list.md 1 2
src/GcovImporter.cpp 84 5
src/GcovImporter.hpp 24 0
tests/sub_commands.cpp 47 7
File docs/uncov.1 changed (mode: 100644) (index b283304..ab23ebe)
... ... when greater than zero \-\- line is covered and was hit that many times.
375 375 .SS new\-gcovi .SS new\-gcovi
376 376 .PP .PP
377 377 Generates coverage via \f[C]gcov\f[] and imports it. Generates coverage via \f[C]gcov\f[] and imports it.
378 Doesn\[aq]t understand JSON version of intermediate format and is thus
379 unusable for GCC newer than GCC 8.
380 378 .PP .PP
381 379 \f[B]Usage: new\-gcovi [options...] [covoutroot]\f[] \f[B]Usage: new\-gcovi [options...] [covoutroot]\f[]
382 380 .PP .PP
File docs/uncov/06-subcommand-list.md changed (mode: 100644) (index 5613653..d185ba2)
... ... Integers have the following meaning:
197 197 new-gcovi new-gcovi
198 198 --------- ---------
199 199
200 Generates coverage via `gcov` and imports it. Doesn't understand JSON version
201 of intermediate format and is thus unusable for GCC newer than GCC 8.
200 Generates coverage via `gcov` and imports it.
202 201
203 202 **Usage: new-gcovi [options...] [covoutroot]** **Usage: new-gcovi [options...] [covoutroot]**
204 203
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 }
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/uncov

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/uncov

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master