Browse Source

Add support for collecting C/C++ code coverage via gcov

merge-requests/413/head
doe300 4 years ago
parent
commit
551a301809
  1. 1
      src/CMakeLists.txt
  2. 4
      src/config.cpp
  3. 1
      src/config.hpp
  4. 122
      src/coverage.cpp
  5. 45
      src/coverage.hpp

1
src/CMakeLists.txt

@ -5,6 +5,7 @@ set(JUCI_SHARED_FILES
commands.cpp commands.cpp
config.cpp config.cpp
compile_commands.cpp compile_commands.cpp
coverage.cpp
ctags.cpp ctags.cpp
dispatcher.cpp dispatcher.cpp
documentation.cpp documentation.cpp

4
src/config.cpp

@ -189,6 +189,7 @@ void Config::read(const JSON &cfg) {
project.cargo_command = project_json.string("cargo_command"); project.cargo_command = project_json.string("cargo_command");
project.python_command = project_json.string("python_command"); project.python_command = project_json.string("python_command");
project.markdown_command = project_json.string("markdown_command"); project.markdown_command = project_json.string("markdown_command");
project.gcov_command = project_json.string("gcov_command");
auto terminal_json = cfg.object("terminal"); auto terminal_json = cfg.object("terminal");
terminal.history_size = terminal_json.integer("history_size", JSON::ParseOptions::accept_string); terminal.history_size = terminal_json.integer("history_size", JSON::ParseOptions::accept_string);
@ -347,7 +348,8 @@ std::string Config::default_config() {
"grep_command": "grep", "grep_command": "grep",
"cargo_command": "cargo", "cargo_command": "cargo",
"python_command": "python -u", "python_command": "python -u",
"markdown_command": "grip -b" "markdown_command": "grip -b",
"gcov_command": "gcov -abcmtj"
}, },
"keybindings": { "keybindings": {
"preferences": "<primary>comma", "preferences": "<primary>comma",

1
src/config.hpp

@ -54,6 +54,7 @@ public:
std::string cargo_command; std::string cargo_command;
std::string python_command; std::string python_command;
std::string markdown_command; std::string markdown_command;
std::string gcov_command;
}; };
class Source { class Source {

122
src/coverage.cpp

@ -0,0 +1,122 @@
#include "coverage.hpp"
#include "config.hpp"
#include "filesystem.hpp"
#include "json.hpp"
#include "terminal.hpp"
#include <algorithm>
bool Coverage::BranchCoverage::operator<(const BranchCoverage &other) const noexcept {
if(count < other.count) {
return true;
}
if(count > other.count) {
return false;
}
// orders fall-through and throw branches to the end
if(is_throw < other.is_throw) {
return true;
}
if(is_throw > other.is_throw) {
return false;
}
return is_fallthrough < other.is_fallthrough;
}
bool Coverage::LineCoverage::operator<(const LineCoverage &other) const noexcept {
if(line < other.line) {
return true;
}
if(line > other.line) {
return false;
}
if(count < other.count) {
return true;
}
if(count > other.count) {
return false;
}
return branches < other.branches;
}
static bool is_not_covered(const Coverage::BranchCoverage &branch) {
return branch.count > 0;
}
bool Coverage::LineCoverage::fully_covered() const noexcept {
return count > 0 && !has_unexecuted_statements && (branches.empty() || std::none_of(branches.begin(), branches.end(), is_not_covered));
}
bool Coverage::LineCoverage::partially_covered() const noexcept {
return count > 0 && (has_unexecuted_statements || (!branches.empty() && std::any_of(branches.begin(), branches.end(), is_not_covered)));
}
bool Coverage::LineCoverage::not_covered() const noexcept {
return count == 0;
}
static JSON run_gcov(const boost::filesystem::path &build_path, const boost::filesystem::path &object_file) {
auto command = Config::get().project.gcov_command + ' ' + filesystem::escape_argument(filesystem::get_short_path(object_file).string());
std::stringstream stdin_stream, stdout_stream, stderr_stream;
auto exit_status = Terminal::get().process(stdin_stream, stdout_stream, command, build_path, &stderr_stream);
if(exit_status == 127) {
Terminal::get().print("\e[31mError\e[m: executable not found: " + command + "\n", true);
}
if(!stderr_stream.eof()) {
Terminal::get().async_print("\e[31mWarnings/Errors in gcov\e[m: " + stderr_stream.str(), true);
}
return JSON(stdout_stream);
}
static Coverage::BranchCoverage gcov_extract_branch(const JSON &branch_json) {
auto count = static_cast<unsigned long>(branch_json.integer("count"));
auto is_fallthrough = branch_json.boolean_or("fallthrough", false);
auto is_throw = branch_json.boolean_or("throw", false);
return Coverage::BranchCoverage(count, is_fallthrough, is_throw);
}
static Coverage::LineCoverage gcov_extract_line(const JSON &line_json) {
auto line = static_cast<unsigned long>(line_json.integer("line_number")) - 1U /* in gcov, line numbers start at 1 */;
auto count = static_cast<unsigned long>(line_json.integer("count"));
auto skipped_blocks = line_json.boolean_or("unexecuted_block", false);
std::vector<Coverage::BranchCoverage> branches;
if(auto branch_json = line_json.array_optional("branches")) {
branches.reserve(branch_json->size());
std::transform(branch_json->begin(), branch_json->end(), std::back_inserter(branches), gcov_extract_branch);
}
std::sort(branches.begin(), branches.end());
return Coverage::LineCoverage(line, count, skipped_blocks, std::move(branches));
}
static std::vector<Coverage::LineCoverage> gcov_extract_lines(const std::vector<JSON> &lines_json) {
std::vector<Coverage::LineCoverage> lines;
lines.reserve(lines_json.size());
std::transform(lines_json.begin(), lines_json.end(), std::back_inserter(lines), gcov_extract_line);
std::sort(lines.begin(), lines.end());
return lines;
}
static std::vector<Coverage::LineCoverage> extract_gcov(JSON &&json, const boost::filesystem::path &source_file) {
auto files = json.array_optional("files");
if(!files) {
return {};
}
for(const auto &file : *files) {
auto file_path = file.string_optional("file");
auto lines = file.array_optional("lines");
if(lines && file_path && *file_path == source_file) {
return gcov_extract_lines(*lines);
}
}
return {};
}
std::vector<Coverage::LineCoverage> Coverage::analyze(const FileInfo &file_info) {
if(file_info.language_id == "cpp" || file_info.language_id == "c") {
return extract_gcov(run_gcov(file_info.build_path, file_info.object_path), file_info.source_path);
}
return {};
}

45
src/coverage.hpp

@ -0,0 +1,45 @@
#pragma once
#include "boost/filesystem.hpp"
#include <vector>
namespace Coverage {
class BranchCoverage {
public:
explicit BranchCoverage(unsigned long num_calls, bool fallthrough = false, bool exception = false) noexcept : count(num_calls), is_fallthrough(fallthrough), is_throw(exception) {}
bool operator<(const BranchCoverage &other) const noexcept;
unsigned long count;
bool is_fallthrough;
bool is_throw;
};
class LineCoverage {
public:
LineCoverage(unsigned long code_line, unsigned long num_calls, bool skipped_statements = false, std::vector<BranchCoverage> &&jumps = {}) noexcept : line(code_line), count(num_calls), has_unexecuted_statements(skipped_statements), branches(std::move(jumps)) {}
bool operator<(const LineCoverage &other) const noexcept;
bool fully_covered() const noexcept;
bool partially_covered() const noexcept;
bool not_covered() const noexcept;
unsigned long line;
unsigned long count;
bool has_unexecuted_statements;
std::vector<BranchCoverage> branches;
};
struct FileInfo {
boost::filesystem::path source_path;
boost::filesystem::path object_path;
boost::filesystem::path build_path;
std::string language_id;
};
std::vector<LineCoverage> analyze(const FileInfo &file_info);
} // namespace Coverage
Loading…
Cancel
Save