From 22acb410153627bcde6d42815236bf2f1de494d8 Mon Sep 17 00:00:00 2001 From: Karol Sewilo Date: Wed, 11 Dec 2024 23:34:32 +0100 Subject: [PATCH] Dived cairo-coverage to crates commit-id:2f1053a5 --- Cargo.lock | 35 +++++- Cargo.toml | 4 + crates/cairo-coverage-args/Cargo.toml | 9 ++ .../cli.rs => cairo-coverage-args/src/lib.rs} | 11 +- crates/cairo-coverage-core/Cargo.toml | 24 ++++ .../src/coverage_data/function.rs | 0 .../src/coverage_data/mod.rs | 0 .../src/data_loader/loaded_data.rs | 0 .../src/data_loader/mod.rs | 0 .../src/data_loader/sierra_program.rs | 0 .../src/input/data.rs | 0 .../src/input/filter/ignore.rs | 47 +++---- .../src/input/filter/mod.rs | 0 .../input/filter/statement_category_filter.rs | 2 +- .../src/input/mod.rs | 0 .../src/input/sierra_to_cairo_map.rs | 0 .../src/input/unique_executed_sierra_ids.rs | 0 crates/cairo-coverage-core/src/lib.rs | 115 ++++++++++++++++++ .../src/merge.rs | 0 .../src/output/lcov.rs | 0 .../src/output/mod.rs | 0 .../src/types.rs | 0 crates/cairo-coverage-test-utils/Cargo.toml | 12 ++ crates/cairo-coverage-test-utils/src/lib.rs | 28 +++++ crates/cairo-coverage/Cargo.toml | 20 +-- crates/cairo-coverage/src/main.rs | 111 +---------------- 26 files changed, 262 insertions(+), 156 deletions(-) create mode 100644 crates/cairo-coverage-args/Cargo.toml rename crates/{cairo-coverage/src/cli.rs => cairo-coverage-args/src/lib.rs} (83%) create mode 100644 crates/cairo-coverage-core/Cargo.toml rename crates/{cairo-coverage => cairo-coverage-core}/src/coverage_data/function.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/coverage_data/mod.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/data_loader/loaded_data.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/data_loader/mod.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/data_loader/sierra_program.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/data.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/filter/ignore.rs (75%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/filter/mod.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/filter/statement_category_filter.rs (99%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/mod.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/sierra_to_cairo_map.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/input/unique_executed_sierra_ids.rs (100%) create mode 100644 crates/cairo-coverage-core/src/lib.rs rename crates/{cairo-coverage => cairo-coverage-core}/src/merge.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/output/lcov.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/output/mod.rs (100%) rename crates/{cairo-coverage => cairo-coverage-core}/src/types.rs (100%) create mode 100644 crates/cairo-coverage-test-utils/Cargo.toml create mode 100644 crates/cairo-coverage-test-utils/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5291fad..31ec889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,15 +205,39 @@ dependencies = [ [[package]] name = "cairo-coverage" version = "0.3.0" +dependencies = [ + "anyhow", + "assert_fs", + "cairo-coverage-args", + "cairo-coverage-core", + "cairo-coverage-test-utils", + "clap", + "snapbox", + "walkdir", +] + +[[package]] +name = "cairo-coverage-args" +version = "0.3.0" +dependencies = [ + "anyhow", + "camino", + "clap", +] + +[[package]] +name = "cairo-coverage-core" +version = "0.3.0" dependencies = [ "anyhow", "assert_fs", "cairo-annotations", + "cairo-coverage-args", + "cairo-coverage-test-utils", "cairo-lang-sierra", "cairo-lang-sierra-to-casm", "cairo-lang-starknet-classes", "camino", - "clap", "derived-deref", "ignore", "indoc", @@ -221,7 +245,14 @@ dependencies = [ "regex", "serde", "serde_json", - "snapbox", +] + +[[package]] +name = "cairo-coverage-test-utils" +version = "0.3.0" +dependencies = [ + "assert_fs", + "camino", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a22cca2..ba20958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,9 @@ resolver = "2" members = [ "crates/cairo-coverage", + "crates/cairo-coverage-args", + "crates/cairo-coverage-core", + "crates/cairo-coverage-test-utils", ] [workspace.package] @@ -25,3 +28,4 @@ serde_json = "1.0.133" snapbox = "0.6.20" indoc = "2.0.5" regex = "1.11.1" +walkdir = "2.5.0" diff --git a/crates/cairo-coverage-args/Cargo.toml b/crates/cairo-coverage-args/Cargo.toml new file mode 100644 index 0000000..fb096ff --- /dev/null +++ b/crates/cairo-coverage-args/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cairo-coverage-args" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +camino.workspace = true +clap.workspace = true diff --git a/crates/cairo-coverage/src/cli.rs b/crates/cairo-coverage-args/src/lib.rs similarity index 83% rename from crates/cairo-coverage/src/cli.rs rename to crates/cairo-coverage-args/src/lib.rs index df7f38c..115c61d 100644 --- a/crates/cairo-coverage/src/cli.rs +++ b/crates/cairo-coverage-args/src/lib.rs @@ -1,10 +1,10 @@ -use anyhow::{ensure, Result}; +use anyhow::ensure; use camino::Utf8PathBuf; use clap::{Parser, ValueEnum}; #[derive(Parser, Debug)] -#[command(version)] -pub struct Cli { +#[command(version, args_conflicts_with_subcommands = true)] +pub struct CairoCoverageArgs { /// Paths to the .json files with trace data. #[arg(value_parser = parse_trace_file, num_args = 1.., required = true)] pub trace_files: Vec, @@ -22,6 +22,7 @@ pub struct Cli { pub project_path: Option, } +/// Additional components that can be included in the coverage report. #[derive(ValueEnum, Debug, Clone, Eq, PartialEq)] pub enum IncludedComponent { /// Run coverage on functions marked with `#[test]` attribute @@ -30,7 +31,7 @@ pub enum IncludedComponent { Macros, } -fn parse_trace_file(path: &str) -> Result { +fn parse_trace_file(path: &str) -> anyhow::Result { let trace_file = Utf8PathBuf::from(path); ensure!(trace_file.exists(), "Trace file does not exist"); @@ -43,7 +44,7 @@ fn parse_trace_file(path: &str) -> Result { Ok(trace_file) } -fn parse_project_path(path: &str) -> Result { +fn parse_project_path(path: &str) -> anyhow::Result { let project_path = Utf8PathBuf::from(path); ensure!(project_path.exists(), "Project path does not exist"); diff --git a/crates/cairo-coverage-core/Cargo.toml b/crates/cairo-coverage-core/Cargo.toml new file mode 100644 index 0000000..707e803 --- /dev/null +++ b/crates/cairo-coverage-core/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "cairo-coverage-core" +version.workspace = true +edition.workspace = true + +[dependencies] +cairo-coverage-args = { path = "../cairo-coverage-args" } +anyhow.workspace = true +camino.workspace = true +cairo-annotations.workspace = true +cairo-lang-sierra.workspace = true +cairo-lang-sierra-to-casm.workspace = true +cairo-lang-starknet-classes.workspace = true +derived-deref.workspace = true +itertools.workspace = true +ignore.workspace = true +serde.workspace = true +serde_json.workspace = true +regex.workspace = true +indoc.workspace = true + +[dev-dependencies] +cairo-coverage-test-utils = { path = "../cairo-coverage-test-utils" } +assert_fs.workspace = true diff --git a/crates/cairo-coverage/src/coverage_data/function.rs b/crates/cairo-coverage-core/src/coverage_data/function.rs similarity index 100% rename from crates/cairo-coverage/src/coverage_data/function.rs rename to crates/cairo-coverage-core/src/coverage_data/function.rs diff --git a/crates/cairo-coverage/src/coverage_data/mod.rs b/crates/cairo-coverage-core/src/coverage_data/mod.rs similarity index 100% rename from crates/cairo-coverage/src/coverage_data/mod.rs rename to crates/cairo-coverage-core/src/coverage_data/mod.rs diff --git a/crates/cairo-coverage/src/data_loader/loaded_data.rs b/crates/cairo-coverage-core/src/data_loader/loaded_data.rs similarity index 100% rename from crates/cairo-coverage/src/data_loader/loaded_data.rs rename to crates/cairo-coverage-core/src/data_loader/loaded_data.rs diff --git a/crates/cairo-coverage/src/data_loader/mod.rs b/crates/cairo-coverage-core/src/data_loader/mod.rs similarity index 100% rename from crates/cairo-coverage/src/data_loader/mod.rs rename to crates/cairo-coverage-core/src/data_loader/mod.rs diff --git a/crates/cairo-coverage/src/data_loader/sierra_program.rs b/crates/cairo-coverage-core/src/data_loader/sierra_program.rs similarity index 100% rename from crates/cairo-coverage/src/data_loader/sierra_program.rs rename to crates/cairo-coverage-core/src/data_loader/sierra_program.rs diff --git a/crates/cairo-coverage/src/input/data.rs b/crates/cairo-coverage-core/src/input/data.rs similarity index 100% rename from crates/cairo-coverage/src/input/data.rs rename to crates/cairo-coverage-core/src/input/data.rs diff --git a/crates/cairo-coverage/src/input/filter/ignore.rs b/crates/cairo-coverage-core/src/input/filter/ignore.rs similarity index 75% rename from crates/cairo-coverage/src/input/filter/ignore.rs rename to crates/cairo-coverage-core/src/input/filter/ignore.rs index 846a826..4ab3ac8 100644 --- a/crates/cairo-coverage/src/input/filter/ignore.rs +++ b/crates/cairo-coverage-core/src/input/filter/ignore.rs @@ -51,35 +51,20 @@ fn find_ignore_file(start_dir: &Utf8Path) -> Option { #[cfg(test)] mod tests { use super::*; - use assert_fs::prelude::*; + use assert_fs::fixture::PathChild; use assert_fs::TempDir; - use std::path::Path; - - trait TestUtils { - fn create_ignore_file(&self) -> Utf8PathBuf; - fn to_utf8_path_buf(&self) -> Utf8PathBuf; - } - - impl> TestUtils for T { - fn create_ignore_file(&self) -> Utf8PathBuf { - let ignore_file = self.child(CAIRO_COVERAGE_IGNORE); - ignore_file.touch().unwrap(); - ignore_file.to_utf8_path_buf() - } - - fn to_utf8_path_buf(&self) -> Utf8PathBuf { - Utf8PathBuf::from_path_buf(self.as_ref().to_path_buf()).unwrap() - } - } + use cairo_coverage_test_utils::{CreateFile, Utf8PathBufConversion}; #[test] fn test_finds_ignore_file_in_same_directory() { let temp_dir = TempDir::new().unwrap(); - let ignore_file_path = temp_dir.create_ignore_file(); + let ignore_file = temp_dir + .create_file(CAIRO_COVERAGE_IGNORE) + .to_utf8_path_buf(); - let result = find_ignore_file(&ignore_file_path); + let result = find_ignore_file(&ignore_file); - assert_eq!(result, Some(ignore_file_path)); + assert_eq!(result, Some(ignore_file)); } #[test] @@ -88,11 +73,13 @@ mod tests { let parent_dir = temp_dir.child("parent"); let child_dir = parent_dir.child("child"); - let ignore_file_path = parent_dir.create_ignore_file(); + let ignore_file = parent_dir + .create_file(CAIRO_COVERAGE_IGNORE) + .to_utf8_path_buf(); let result = find_ignore_file(&child_dir.to_utf8_path_buf()); - assert_eq!(result, Some(ignore_file_path)); + assert_eq!(result, Some(ignore_file)); } #[test] @@ -102,7 +89,9 @@ mod tests { let middle_dir = root_dir.child("middle"); let child_dir = middle_dir.child("child"); - let ignore_file = root_dir.create_ignore_file(); + let ignore_file = root_dir + .create_file(CAIRO_COVERAGE_IGNORE) + .to_utf8_path_buf(); let result = find_ignore_file(&child_dir.to_utf8_path_buf()); @@ -125,8 +114,12 @@ mod tests { let middle_dir = root_dir.child("middle"); let child_dir = middle_dir.child("child"); - root_dir.create_ignore_file(); - let middle_ignore_file = middle_dir.create_ignore_file(); + root_dir + .create_file(CAIRO_COVERAGE_IGNORE) + .to_utf8_path_buf(); + let middle_ignore_file = middle_dir + .create_file(CAIRO_COVERAGE_IGNORE) + .to_utf8_path_buf(); let result = find_ignore_file(&child_dir.to_utf8_path_buf()); diff --git a/crates/cairo-coverage/src/input/filter/mod.rs b/crates/cairo-coverage-core/src/input/filter/mod.rs similarity index 100% rename from crates/cairo-coverage/src/input/filter/mod.rs rename to crates/cairo-coverage-core/src/input/filter/mod.rs diff --git a/crates/cairo-coverage/src/input/filter/statement_category_filter.rs b/crates/cairo-coverage-core/src/input/filter/statement_category_filter.rs similarity index 99% rename from crates/cairo-coverage/src/input/filter/statement_category_filter.rs rename to crates/cairo-coverage-core/src/input/filter/statement_category_filter.rs index cbf2006..4749cdd 100644 --- a/crates/cairo-coverage/src/input/filter/statement_category_filter.rs +++ b/crates/cairo-coverage-core/src/input/filter/statement_category_filter.rs @@ -1,9 +1,9 @@ -use crate::cli::IncludedComponent; use crate::data_loader::LoadedData; use crate::input::filter::ignore::CairoCoverageIgnoreMatcher; use crate::input::sierra_to_cairo_map::{SimpleLibfuncName, StatementOrigin}; use cairo_annotations::annotations::coverage::SourceFileFullPath; use cairo_annotations::annotations::profiler::FunctionName; +use cairo_coverage_args::IncludedComponent; use camino::Utf8PathBuf; use regex::Regex; use std::collections::HashSet; diff --git a/crates/cairo-coverage/src/input/mod.rs b/crates/cairo-coverage-core/src/input/mod.rs similarity index 100% rename from crates/cairo-coverage/src/input/mod.rs rename to crates/cairo-coverage-core/src/input/mod.rs diff --git a/crates/cairo-coverage/src/input/sierra_to_cairo_map.rs b/crates/cairo-coverage-core/src/input/sierra_to_cairo_map.rs similarity index 100% rename from crates/cairo-coverage/src/input/sierra_to_cairo_map.rs rename to crates/cairo-coverage-core/src/input/sierra_to_cairo_map.rs diff --git a/crates/cairo-coverage/src/input/unique_executed_sierra_ids.rs b/crates/cairo-coverage-core/src/input/unique_executed_sierra_ids.rs similarity index 100% rename from crates/cairo-coverage/src/input/unique_executed_sierra_ids.rs rename to crates/cairo-coverage-core/src/input/unique_executed_sierra_ids.rs diff --git a/crates/cairo-coverage-core/src/lib.rs b/crates/cairo-coverage-core/src/lib.rs new file mode 100644 index 0000000..44184b3 --- /dev/null +++ b/crates/cairo-coverage-core/src/lib.rs @@ -0,0 +1,115 @@ +mod coverage_data; +mod data_loader; +mod input; +mod merge; +mod output; +mod types; + +use crate::coverage_data::create_files_coverage_data_with_hits; +use crate::data_loader::LoadedDataMap; +use crate::input::{InputData, StatementCategoryFilter}; +use crate::output::lcov::LcovFormat; +use anyhow::{bail, ensure, Context, Result}; +use cairo_coverage_args::CairoCoverageArgs; +use camino::Utf8PathBuf; +use indoc::indoc; +use merge::MergeOwned; +use std::fs::OpenOptions; +use std::io::Write; + +const SNFORGE_SIERRA_DIR: &str = ".snfoundry_versioned_programs"; + +/// Run the core logic of `cairo-coverage` with the provided [`RunArgs`] arguments. +/// This command generates a coverage report in the LCOV format. +/// The coverage report is written to the file specified in the `output_path` argument. +/// # Errors +/// Fails if it can't produce the coverage report with the error message explaining the reason. +pub fn run( + CairoCoverageArgs { + trace_files, + include, + output_path, + project_path, + }: CairoCoverageArgs, +) -> Result<()> { + let coverage_data = LoadedDataMap::load(&trace_files)? + .iter() + .map(|(source_sierra_path, loaded_data)| { + let project_path = &get_project_path(source_sierra_path, project_path.as_ref())?; + let filter = StatementCategoryFilter::new(project_path, &include, loaded_data); + let input_data = InputData::new(loaded_data, &filter)?; + Ok(create_files_coverage_data_with_hits(&input_data)) + }) + .collect::>>()? + .into_iter() + // Versioned programs and contract classes can represent the same piece of code, + // so we merge the file coverage after processing them to avoid duplicate entries. + .reduce(MergeOwned::merge_owned) + .context("No elements to merge")?; + + OpenOptions::new() + .append(true) + .create(true) + .open(&output_path) + .context(format!("Failed to open output file at path: {output_path}"))? + .write_all(LcovFormat::from(coverage_data).to_string().as_bytes()) + .context("Failed to write to output file")?; + + Ok(()) +} + +fn get_project_path( + source_sierra_path: &Utf8PathBuf, + project_path: Option<&Utf8PathBuf>, +) -> Result { + if let Some(project_path) = project_path { + Ok(project_path.clone()) + } else { + find_user_project_path(source_sierra_path).context(indoc! { + r"Inference of project path failed. + Please provide the project path explicitly using the --project-path flag." + }) + } +} + +fn find_user_project_path(source_sierra_path: &Utf8PathBuf) -> Result { + ensure!( + source_sierra_path.extension() == Some("json"), + "Source sierra path should have a .json extension, got: {source_sierra_path}" + ); + + match source_sierra_path.with_extension("").extension() { + Some("sierra") => { + navigate_and_check(source_sierra_path, &["target", "dev"]) + .or_else(|| navigate_and_check(source_sierra_path, &[SNFORGE_SIERRA_DIR])) + .context(format!( + "Source sierra path should be in one of the formats: \ + /{SNFORGE_SIERRA_DIR}/.sierra.json \ + or /target/dev/.sierra.json, got: {source_sierra_path}" + )) + } + Some("contract_class") => { + navigate_and_check(source_sierra_path, &["target", "dev"]) + .context(format!( + "Source sierra path should be in the format: \ + /target/dev/.contract_class.json, got: {source_sierra_path}" + )) + } + _ => bail!( + "Source sierra path should have a .sierra or .contract_class extension, got: {source_sierra_path}" + ), + } +} + +fn navigate_and_check(path: &Utf8PathBuf, folders: &[&str]) -> Option { + folders + .iter() + .rev() + .try_fold(path.parent()?, |current, &folder| { + current + .file_name() + .filter(|name| *name == folder) + .map(|_| current.parent())? + }) + .map(Utf8PathBuf::from) +} diff --git a/crates/cairo-coverage/src/merge.rs b/crates/cairo-coverage-core/src/merge.rs similarity index 100% rename from crates/cairo-coverage/src/merge.rs rename to crates/cairo-coverage-core/src/merge.rs diff --git a/crates/cairo-coverage/src/output/lcov.rs b/crates/cairo-coverage-core/src/output/lcov.rs similarity index 100% rename from crates/cairo-coverage/src/output/lcov.rs rename to crates/cairo-coverage-core/src/output/lcov.rs diff --git a/crates/cairo-coverage/src/output/mod.rs b/crates/cairo-coverage-core/src/output/mod.rs similarity index 100% rename from crates/cairo-coverage/src/output/mod.rs rename to crates/cairo-coverage-core/src/output/mod.rs diff --git a/crates/cairo-coverage/src/types.rs b/crates/cairo-coverage-core/src/types.rs similarity index 100% rename from crates/cairo-coverage/src/types.rs rename to crates/cairo-coverage-core/src/types.rs diff --git a/crates/cairo-coverage-test-utils/Cargo.toml b/crates/cairo-coverage-test-utils/Cargo.toml new file mode 100644 index 0000000..9a7dd53 --- /dev/null +++ b/crates/cairo-coverage-test-utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cairo-coverage-test-utils" +version.workspace = true +edition.workspace = true + +[dependencies] +assert_fs.workspace = true +camino.workspace = true + + + + diff --git a/crates/cairo-coverage-test-utils/src/lib.rs b/crates/cairo-coverage-test-utils/src/lib.rs new file mode 100644 index 0000000..a523654 --- /dev/null +++ b/crates/cairo-coverage-test-utils/src/lib.rs @@ -0,0 +1,28 @@ +use assert_fs::fixture::{ChildPath, FileTouch, PathChild}; +use camino::Utf8PathBuf; +use std::path::Path; + +/// Utility trait for creating files using [`assert_fs`]. +pub trait CreateFile { + fn create_file(&self, file_name: &str) -> ChildPath; +} + +impl CreateFile for T { + fn create_file(&self, file_name: &str) -> ChildPath { + let file = self.child(file_name); + file.touch().unwrap(); + file + } +} + +/// Utility trait for converting [`Path`] to [`Utf8PathBuf`]. +/// It should only be used in tests as it panics on invalid UTF-8. +pub trait Utf8PathBufConversion { + fn to_utf8_path_buf(&self) -> Utf8PathBuf; +} + +impl> Utf8PathBufConversion for T { + fn to_utf8_path_buf(&self) -> Utf8PathBuf { + Utf8PathBuf::from_path_buf(self.as_ref().to_path_buf()).unwrap() + } +} diff --git a/crates/cairo-coverage/Cargo.toml b/crates/cairo-coverage/Cargo.toml index ef59bae..72e20c5 100644 --- a/crates/cairo-coverage/Cargo.toml +++ b/crates/cairo-coverage/Cargo.toml @@ -4,22 +4,14 @@ version.workspace = true edition.workspace = true [dependencies] +cairo-coverage-args = { path = "../cairo-coverage-args" } +cairo-coverage-core = { path = "../cairo-coverage-core" } anyhow.workspace = true -camino.workspace = true -cairo-annotations.workspace = true -cairo-lang-sierra.workspace = true -cairo-lang-sierra-to-casm.workspace = true -cairo-lang-starknet-classes.workspace = true clap.workspace = true -derived-deref.workspace = true -itertools.workspace = true -ignore.workspace = true -serde.workspace = true -serde_json.workspace = true -regex.workspace = true -indoc.workspace = true +walkdir.workspace = true + [dev-dependencies] +cairo-coverage-test-utils = { path = "../cairo-coverage-test-utils" } assert_fs.workspace = true -snapbox.workspace = true - +snapbox.workspace = true \ No newline at end of file diff --git a/crates/cairo-coverage/src/main.rs b/crates/cairo-coverage/src/main.rs index ccdac8c..e747370 100644 --- a/crates/cairo-coverage/src/main.rs +++ b/crates/cairo-coverage/src/main.rs @@ -1,112 +1,9 @@ -mod cli; -mod coverage_data; -mod data_loader; -mod input; -mod merge; -mod output; -mod types; - -use crate::coverage_data::create_files_coverage_data_with_hits; -use crate::data_loader::LoadedDataMap; -use crate::input::{InputData, StatementCategoryFilter}; -use crate::output::lcov::LcovFormat; -use anyhow::{bail, ensure, Context, Result}; -use camino::Utf8PathBuf; +use anyhow::Result; +use cairo_coverage_args::CairoCoverageArgs; use clap::Parser; -use cli::Cli; -use indoc::indoc; -use merge::MergeOwned; -use std::fs::OpenOptions; -use std::io::Write; - -const SNFORGE_SIERRA_DIR: &str = ".snfoundry_versioned_programs"; fn main() -> Result<()> { - let Cli { - trace_files, - include, - output_path, - project_path, - } = &Cli::parse(); - - let coverage_data = LoadedDataMap::load(trace_files)? - .iter() - .map(|(source_sierra_path, loaded_data)| { - let project_path = &get_project_path(source_sierra_path, project_path.as_ref())?; - let filter = StatementCategoryFilter::new(project_path, include, loaded_data); - let input_data = InputData::new(loaded_data, &filter)?; - Ok(create_files_coverage_data_with_hits(&input_data)) - }) - .collect::>>()? - .into_iter() - // Versioned programs and contract classes can represent the same piece of code, - // so we merge the file coverage after processing them to avoid duplicate entries. - .reduce(MergeOwned::merge_owned) - .context("No elements to merge")?; - - OpenOptions::new() - .append(true) - .create(true) - .open(output_path) - .context(format!("Failed to open output file at path: {output_path}"))? - .write_all(LcovFormat::from(coverage_data).to_string().as_bytes()) - .context("Failed to write to output file")?; - - Ok(()) -} - -fn get_project_path( - source_sierra_path: &Utf8PathBuf, - project_path: Option<&Utf8PathBuf>, -) -> Result { - if let Some(project_path) = project_path { - Ok(project_path.clone()) - } else { - find_user_project_path(source_sierra_path).context(indoc! { - r"Inference of project path failed. - Please provide the project path explicitly using the --project-path flag." - }) - } -} - -fn find_user_project_path(source_sierra_path: &Utf8PathBuf) -> Result { - ensure!( - source_sierra_path.extension() == Some("json"), - "Source sierra path should have a .json extension, got: {source_sierra_path}" - ); - - match source_sierra_path.with_extension("").extension() { - Some("sierra") => { - navigate_and_check(source_sierra_path, &["target", "dev"]) - .or_else(|| navigate_and_check(source_sierra_path, &[SNFORGE_SIERRA_DIR])) - .context(format!( - "Source sierra path should be in one of the formats: \ - /{SNFORGE_SIERRA_DIR}/.sierra.json \ - or /target/dev/.sierra.json, got: {source_sierra_path}" - )) - } - Some("contract_class") => { - navigate_and_check(source_sierra_path, &["target", "dev"]) - .context(format!( - "Source sierra path should be in the format: \ - /target/dev/.contract_class.json, got: {source_sierra_path}" - )) - } - _ => bail!( - "Source sierra path should have a .sierra or .contract_class extension, got: {source_sierra_path}" - ), - } -} + let cairo_coverage_args = CairoCoverageArgs::parse(); -fn navigate_and_check(path: &Utf8PathBuf, folders: &[&str]) -> Option { - folders - .iter() - .rev() - .try_fold(path.parent()?, |current, &folder| { - current - .file_name() - .filter(|name| *name == folder) - .map(|_| current.parent())? - }) - .map(Utf8PathBuf::from) + cairo_coverage_core::run(cairo_coverage_args) }