From d1fd1b7a8e2384072957ed544f25ce7ba8224c46 Mon Sep 17 00:00:00 2001 From: doe300 Date: Sat, 11 Jun 2022 11:52:34 +0200 Subject: [PATCH] Add support for Rust coverage via tarpaulin --- src/coverage.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- src/coverage.hpp | 11 ++------ 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/coverage.cpp b/src/coverage.cpp index 129a75b..0068b7b 100644 --- a/src/coverage.cpp +++ b/src/coverage.cpp @@ -1,4 +1,5 @@ #include "coverage.hpp" +#include "compile_commands.hpp" #include "config.hpp" #include "filesystem.hpp" #include "json.hpp" @@ -114,9 +115,75 @@ static std::vector extract_gcov(JSON &&json, const boost return {}; } -std::vector 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); +static Coverage::LineCoverage tarpaulin_extract_line(const JSON &line_json) { + auto line = static_cast(line_json.integer("line")) - 1U /* in tarpaulin, line numbers start at 1 */; + auto stats = line_json.object_optional("stats"); + if(!stats) { + return Coverage::LineCoverage(line, 0); + } + + auto count = static_cast(stats->integer_optional("Line").value_or(0)); + // branch and conditionally coverage are not yet implemented in tarpaulin (as of version 0.20.1 + return Coverage::LineCoverage(line, count); +} + +static std::vector tarpaulin_extract_lines(const std::vector &lines_json) { + std::vector lines; + lines.reserve(lines_json.size()); + std::transform(lines_json.begin(), lines_json.end(), std::back_inserter(lines), tarpaulin_extract_line); + std::sort(lines.begin(), lines.end()); + return lines; +} + +static std::vector extract_tarpaulin(JSON &&json, const boost::filesystem::path &source_file) { + auto traces = json.object_optional("traces"); + if(!traces) { + return {}; + } + + auto file_traces = traces->array_optional(source_file.string()); + if(!file_traces) { + return {}; + } + + return tarpaulin_extract_lines(*file_traces); +} + +std::vector Coverage::analyze(Project::Build &build, const boost::filesystem::path &file_path, const std::string &language_id) { + if(language_id == "cpp" || language_id == "c") { + CompileCommands commands(build.get_default_path()); + boost::filesystem::path object_file; + for(const auto &command : commands.commands) { + if(command.file == file_path) { + auto values = command.parameter_values("-o"); + if(!values.empty()) { + object_file = command.directory / values.front(); + break; + } + } + } + if(object_file.empty()) { + Terminal::get().async_print(file_path.filename().string() + ": could not find the C/C++ object file", true); + return std::vector{}; + } + + return extract_gcov(run_gcov(build.get_default_path(), object_file), file_path); + } + + if(language_id == "rust" && dynamic_cast(&build)) { + auto tarpaulin_folder = filesystem::get_canonical_path(build.get_default_path() / ".." / "tarpaulin"); + if(!boost::filesystem::exists(tarpaulin_folder) || !boost::filesystem::is_directory(tarpaulin_folder)) { + Terminal::get().async_print("Directory '" + tarpaulin_folder.string() + "' does not exist, you may need to generate the coverage report via: cargo tarpaulin", true); + return std::vector{}; + } + for(const auto &file : boost::filesystem::directory_iterator(tarpaulin_folder)) { + if(boost::filesystem::is_regular(file.path()) && file.path().extension().string() == ".json") { + return extract_tarpaulin(JSON(file.path()), file_path); + } + } + + Terminal::get().async_print("No JSON coverage file found in '" + tarpaulin_folder.string() + "', you may need to generate the coverage report via: cargo tarpaulin", true); + return std::vector{}; } return {}; } diff --git a/src/coverage.hpp b/src/coverage.hpp index 626920d..db96374 100644 --- a/src/coverage.hpp +++ b/src/coverage.hpp @@ -1,6 +1,7 @@ #pragma once #include "boost/filesystem.hpp" +#include "project_build.hpp" #include namespace Coverage { @@ -33,13 +34,5 @@ namespace Coverage { std::vector branches; }; - - struct FileInfo { - boost::filesystem::path source_path; - boost::filesystem::path object_path; - boost::filesystem::path build_path; - std::string language_id; - }; - - std::vector analyze(const FileInfo &file_info); + std::vector analyze(Project::Build &build, const boost::filesystem::path &file_path, const std::string &language_id); } // namespace Coverage