mirror of https://gitlab.com/cppit/jucipp
5 changed files with 172 additions and 1 deletions
@ -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 {}; |
||||
} |
||||
@ -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…
Reference in new issue