diff --git a/tools/kani-cov/src/coverage.rs b/tools/kani-cov/src/coverage.rs index 1efd0129f7db..86716aebdc1e 100644 --- a/tools/kani-cov/src/coverage.rs +++ b/tools/kani-cov/src/coverage.rs @@ -8,7 +8,7 @@ use std::{collections::BTreeMap, fmt::Display}; use std::{fmt, fs}; use tree_sitter::{Node, Parser}; -pub type LineResults = Vec>; +pub type LineResults = Vec<(usize, Option<(u32, MarkerInfo)>)>; #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "UPPERCASE")] diff --git a/tools/kani-cov/src/merge.rs b/tools/kani-cov/src/merge.rs index 48d542b4824e..98310d7b6c14 100644 --- a/tools/kani-cov/src/merge.rs +++ b/tools/kani-cov/src/merge.rs @@ -22,8 +22,8 @@ pub fn merge_main(args: &MergeArgs) -> Result<()> { Ok(()) } -// Validate arguments to the `merge` subcommand in addition to clap's -// validation. +/// Validate arguments to the `merge` subcommand in addition to clap's +/// validation. pub fn validate_merge_args(_args: &MergeArgs) -> Result<()> { // No validation is done at the moment Ok(()) diff --git a/tools/kani-cov/src/report.rs b/tools/kani-cov/src/report.rs index dd30c05bc9ac..514c3a991890 100644 --- a/tools/kani-cov/src/report.rs +++ b/tools/kani-cov/src/report.rs @@ -7,7 +7,9 @@ use std::{fs::File, io::BufReader, path::PathBuf}; use anyhow::Result; use crate::args::ReportFormat; -use crate::coverage::{function_coverage_results, function_info_from_file, CovResult, MarkerInfo}; +use crate::coverage::{ + function_coverage_results, function_info_from_file, CovResult, LineResults, MarkerInfo, +}; use crate::summary::line_coverage_results; use crate::{args::ReportArgs, coverage::CombinedCoverageResults}; @@ -41,13 +43,19 @@ pub fn report_main(args: &ReportArgs) -> Result<()> { Ok(()) } -// Validate arguments to the `report` subcommand in addition to clap's -// validation. +/// Validate arguments to the `report` subcommand in addition to clap's +/// validation. pub fn validate_report_args(_args: &ReportArgs) -> Result<()> { // No validation is done at the moment Ok(()) } +/// Checks the `format` argument for the `report` subcommand and selects to +/// another format if the one we are checking is not possible for any reason. +/// +/// For example, if the `Terminal` format is specified but we are not writing to +/// a terminal, it will auto-select the `Escapes` format to avoid character +/// issues. fn check_format(format: &ReportFormat) -> ReportFormat { let is_terminal = std::io::stdout().is_terminal(); match format { @@ -65,7 +73,7 @@ fn check_format(format: &ReportFormat) -> ReportFormat { pub fn print_coverage_results( format: &ReportFormat, filepath: PathBuf, - results: Vec)>>, + results: Vec, ) -> Result<()> { let flattened_results: Vec<(usize, Option<(u32, MarkerInfo)>)> = results.into_iter().flatten().collect(); @@ -89,10 +97,9 @@ pub fn print_coverage_results( Some(max), insert_escapes(&line, vec![(0, true), (line.len(), false)], format), ), - MarkerInfo::Markers(results) => - // Note: I'm not sure why we need to offset the columns by -1 - { + MarkerInfo::Markers(results) => { // Filter out cases where the span is a single unit AND it ends after the line + // TODO: Create issue and link let results: Vec<&CovResult> = results .iter() .filter(|m| { @@ -120,7 +127,7 @@ pub fn print_coverage_results( ] }) .collect(); - // println!("COMPLETE: {complete_escapes:?}"); + let mut starting_escapes: Vec<(usize, bool)> = results .iter() .filter(|m| { @@ -130,7 +137,7 @@ pub fn print_coverage_results( }) .flat_map(|m| vec![((m.region.start.1 - 1) as usize, true)]) .collect(); - // println!("{starting_escapes:?}"); + let mut ending_escapes: Vec<(usize, bool)> = results .iter() .filter(|m| { @@ -138,12 +145,9 @@ pub fn print_coverage_results( && m.region.start.0 as usize != idx && m.region.end.0 as usize == idx }) - .map(|m| vec![((m.region.end.1 - 1) as usize, false)]) - .flatten() + .flat_map(|m| vec![((m.region.end.1 - 1) as usize, false)]) .collect(); - // println!("{starting_escapes:?}"); - // println!("{ending_escapes:?}"); if must_highlight && !ending_escapes.is_empty() { ending_escapes.push((0_usize, true)); must_highlight = false; @@ -194,22 +198,32 @@ pub fn print_coverage_results( Ok(()) } -fn insert_escapes(str: &String, markers: Vec<(usize, bool)>, format: &ReportFormat) -> String { - let mut new_str = str.clone(); - let mut offset = 0; - +/// Inserts opening/closing escape strings into `str` given a set of `markers`. +/// Each marker is a tuple `(offset, type)` where: +/// * `offset` represents the offset in which the marker must be inserted, and +/// * `type` represents whether it is an opening (`true`) or closing (`false`) +/// escape. +/// The specific escape to be used are determined by the report format. +fn insert_escapes(str: &str, markers: Vec<(usize, bool)>, format: &ReportFormat) -> String { + // Determine the escape strings based on the format let (open_escape, close_escape) = match format { ReportFormat::Terminal => ("\x1b[41m", "\x1b[0m"), ReportFormat::Escapes => ("```", "'''"), }; - let mut sym_markers: Vec<(&usize, &str)> = + let mut escape_markers: Vec<(&usize, &str)> = markers.iter().map(|(i, b)| (i, if *b { open_escape } else { close_escape })).collect(); + escape_markers.sort(); + + let mut escaped_str = str.to_owned(); + let mut offset = 0; - sym_markers.sort(); - for (i, b) in sym_markers { - new_str.insert_str(i + offset, b); + // Iteratively insert the escape strings into the original string + for (i, b) in escape_markers { + escaped_str.insert_str(i + offset, b); + // `offset` keeps track of the bytes we've already inserted so the original + // index is shifted by the appropriate amount in subsequent insertions. offset += b.bytes().len(); } - new_str + escaped_str } diff --git a/tools/kani-cov/src/summary.rs b/tools/kani-cov/src/summary.rs index 344936a7c212..6da9d561a591 100644 --- a/tools/kani-cov/src/summary.rs +++ b/tools/kani-cov/src/summary.rs @@ -1,7 +1,12 @@ // Copyright Kani Contributors // SPDX-License-Identifier: Apache-2.0 OR MIT -use std::{cmp::max, fs::File, io::BufReader, path::PathBuf}; +use std::{ + cmp::max, + fs::File, + io::BufReader, + path::{Path, PathBuf}, +}; use anyhow::Result; @@ -9,7 +14,7 @@ use crate::{ args::{SummaryArgs, SummaryFormat}, coverage::{ function_coverage_results, function_info_from_file, CombinedCoverageResults, CovResult, - CoverageMetric, CoverageRegion, FileCoverageInfo, FunctionInfo, LineResults, MarkerInfo, + CoverageMetric, CoverageRegion, FileCoverageInfo, FunctionInfo, MarkerInfo, }, }; @@ -52,10 +57,7 @@ pub fn summary_main(args: &SummaryArgs) -> Result<()> { Ok(()) } -fn aggregate_cov_info( - file: &PathBuf, - file_cov_info: &Vec, -) -> FileCoverageInfo { +fn aggregate_cov_info(file: &Path, file_cov_info: &[FunctionCoverageResults]) -> FileCoverageInfo { let total_functions = file_cov_info.len().try_into().unwrap(); let covered_functions = file_cov_info.iter().filter(|f| f.is_covered).count().try_into().unwrap(); @@ -89,8 +91,8 @@ struct FunctionCoverageResults { total_regions: u32, } -// Validate arguments to the `summary` subcommand in addition to clap's -// validation. +/// Validate arguments to the `summary` subcommand in addition to clap's +/// validation. pub fn validate_summary_args(_args: &SummaryArgs) -> Result<()> { // No validation is done at the moment Ok(()) @@ -113,8 +115,8 @@ pub fn validate_summary_args(_args: &SummaryArgs) -> Result<()> { /// for the generation of coverage reports. pub fn line_coverage_results( info: &FunctionInfo, - fun_results: &Option<(String, Vec)>, -) -> LineResults { + fun_results: &Option<(String, Vec)>, +) -> Vec> { let start_line: u32 = info.start.0.try_into().unwrap(); let end_line: u32 = info.end.0.try_into().unwrap();