Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass args to coverage and profiler #2592

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Forge

#### Changed

- You can now pass arguments to `cairo-profiler` or `cairo-coverage`,
e.g. `snforge test --coverage --include tests-functions`. If flags name duplicates a flag in `snforge test`, you can
use `--` to separate them, e.g. `snforge test --coverage -- --help`
- You can't use now `--coverage` and `--build-profile` flags at the same time. If you want to use both, you need to run
ksew1 marked this conversation as resolved.
Show resolved Hide resolved
`snforge test` twice with different flags.

## [0.32.0] - 2024-10-16

### Cast
Expand Down
22 changes: 14 additions & 8 deletions crates/forge-runner/src/coverage_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use indoc::{formatdoc, indoc};
use scarb_api::metadata::Metadata;
use semver::Version;
use shared::command::CommandExt;
use std::ffi::OsString;
use std::process::Stdio;
use std::{env, fs, path::PathBuf, process::Command};
use toml_edit::{DocumentMut, Table};
Expand All @@ -19,7 +20,7 @@ const CAIRO_COVERAGE_REQUIRED_ENTRIES: [(&str, &str); 3] = [
("inlining-strategy", "\"avoid\""),
];

pub fn run_coverage(saved_trace_data_paths: &[PathBuf]) -> Result<()> {
pub fn run_coverage(saved_trace_data_paths: &[PathBuf], coverage_args: &[OsString]) -> Result<()> {
let coverage = env::var("CAIRO_COVERAGE")
.map(PathBuf::from)
.ok()
Expand All @@ -34,10 +35,6 @@ pub fn run_coverage(saved_trace_data_paths: &[PathBuf]) -> Result<()> {
}
);

let dir_to_save_coverage = PathBuf::from(COVERAGE_DIR);
fs::create_dir_all(&dir_to_save_coverage).context("Failed to create a coverage dir")?;
let path_to_save_coverage = dir_to_save_coverage.join(OUTPUT_FILE_NAME);

let trace_files: Vec<&str> = saved_trace_data_paths
.iter()
.map(|trace_data_path| {
Expand All @@ -47,10 +44,19 @@ pub fn run_coverage(saved_trace_data_paths: &[PathBuf]) -> Result<()> {
})
.collect();

Command::new(coverage)
.arg("--output-path")
.arg(&path_to_save_coverage)
let mut command = Command::new(coverage);

if coverage_args.iter().all(|arg| arg != "--output-path") {
let dir_to_save_coverage = PathBuf::from(COVERAGE_DIR);
fs::create_dir_all(&dir_to_save_coverage).context("Failed to create a coverage dir")?;
let path_to_save_coverage = dir_to_save_coverage.join(OUTPUT_FILE_NAME);

command.arg("--output-path").arg(&path_to_save_coverage);
}
cptartur marked this conversation as resolved.
Show resolved Hide resolved

command
.args(trace_files)
.args(coverage_args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output_checked()
Expand Down
12 changes: 10 additions & 2 deletions crates/forge-runner/src/forge_config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use camino::Utf8PathBuf;
use cheatnet::runtime_extensions::forge_runtime_extension::contracts_data::ContractsData;
use std::collections::HashMap;
use std::ffi::OsString;
use std::num::NonZeroU32;
use std::sync::Arc;

Expand Down Expand Up @@ -29,20 +30,27 @@ pub struct OutputConfig {
pub versioned_programs_dir: Utf8PathBuf,
}

#[derive(Debug, PartialEq, Clone, Copy, Default)]
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ExecutionDataToSave {
pub trace: bool,
pub profile: bool,
pub coverage: bool,
pub additional_args: Vec<OsString>,
}

impl ExecutionDataToSave {
#[must_use]
pub fn from_flags(save_trace_data: bool, build_profile: bool, coverage: bool) -> Self {
pub fn from_flags(
save_trace_data: bool,
build_profile: bool,
coverage: bool,
additional_args: &[OsString],
) -> Self {
Self {
trace: save_trace_data,
profile: build_profile,
coverage,
additional_args: additional_args.to_vec(),
}
}
#[must_use]
Expand Down
13 changes: 8 additions & 5 deletions crates/forge-runner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub trait TestCaseFilter {

pub fn maybe_save_trace_and_profile(
result: &AnyTestCaseSummary,
execution_data_to_save: ExecutionDataToSave,
execution_data_to_save: &ExecutionDataToSave,
) -> Result<Option<PathBuf>> {
if let AnyTestCaseSummary::Single(TestCaseSummary::Passed {
name, trace_data, ..
Expand All @@ -69,7 +69,7 @@ pub fn maybe_save_trace_and_profile(
if execution_data_to_save.is_vm_trace_needed() {
let trace_path = save_trace_data(name, trace_data)?;
if execution_data_to_save.profile {
run_profiler(name, &trace_path)?;
run_profiler(name, &trace_path, &execution_data_to_save.additional_args)?;
}
return Ok(Some(trace_path));
}
Expand All @@ -78,21 +78,24 @@ pub fn maybe_save_trace_and_profile(
}

pub fn maybe_generate_coverage(
execution_data_to_save: ExecutionDataToSave,
execution_data_to_save: &ExecutionDataToSave,
saved_trace_data_paths: &[PathBuf],
) -> Result<()> {
if execution_data_to_save.coverage {
if saved_trace_data_paths.is_empty() {
print_as_warning(&anyhow!("No trace data to generate coverage from"));
} else {
run_coverage(saved_trace_data_paths)?;
run_coverage(
saved_trace_data_paths,
&execution_data_to_save.additional_args,
)?;
}
}
Ok(())
}

pub fn maybe_save_versioned_program(
execution_data_to_save: ExecutionDataToSave,
execution_data_to_save: &ExecutionDataToSave,
test_target: &TestTargetWithResolvedConfig,
versioned_programs_dir: &Utf8Path,
package_name: &str,
Expand Down
25 changes: 18 additions & 7 deletions crates/forge-runner/src/profiler_api.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
use anyhow::{Context, Result};
use shared::command::CommandExt;
use std::ffi::OsString;
use std::process::Stdio;
use std::{env, fs, path::PathBuf, process::Command};

pub const PROFILE_DIR: &str = "profile";

pub fn run_profiler(test_name: &str, trace_path: &PathBuf) -> Result<()> {
pub fn run_profiler(
test_name: &str,
trace_path: &PathBuf,
profiler_args: &[OsString],
) -> Result<()> {
let profiler = env::var("CAIRO_PROFILER")
.map(PathBuf::from)
.ok()
.unwrap_or_else(|| PathBuf::from("cairo-profiler"));
let dir_to_save_profile = PathBuf::from(PROFILE_DIR);
fs::create_dir_all(&dir_to_save_profile).context("Failed to create a profile dir")?;
let path_to_save_profile = dir_to_save_profile.join(format!("{test_name}.pb.gz"));

Command::new(profiler)
let mut command = Command::new(profiler);

if profiler_args.iter().all(|arg| arg != "--output-path") {
let dir_to_save_profile = PathBuf::from(PROFILE_DIR);
fs::create_dir_all(&dir_to_save_profile).context("Failed to create a profile dir")?;
let path_to_save_profile = dir_to_save_profile.join(format!("{test_name}.pb.gz"));

command.arg("--output-path").arg(&path_to_save_profile);
}

command
.arg(trace_path)
.arg("--output-path")
.arg(&path_to_save_profile)
.args(profiler_args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.output_checked()
Expand Down
10 changes: 10 additions & 0 deletions crates/forge/src/combine_configs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use forge_runner::forge_config::{
};
use rand::{thread_rng, RngCore};
use std::env;
use std::ffi::OsString;
use std::num::NonZeroU32;
use std::sync::Arc;

Expand All @@ -24,11 +25,13 @@ pub fn combine_configs(
cache_dir: Utf8PathBuf,
versioned_programs_dir: Utf8PathBuf,
forge_config_from_scarb: &ForgeConfigFromScarb,
coverage_or_profiler_args: &[OsString],
) -> ForgeConfig {
let execution_data_to_save = ExecutionDataToSave::from_flags(
save_trace_data || forge_config_from_scarb.save_trace_data,
build_profile || forge_config_from_scarb.build_profile,
coverage || forge_config_from_scarb.coverage,
coverage_or_profiler_args,
);

ForgeConfig {
Expand Down Expand Up @@ -73,6 +76,7 @@ mod tests {
Default::default(),
Default::default(),
&Default::default(),
&[],
);
let config2 = combine_configs(
false,
Expand All @@ -87,6 +91,7 @@ mod tests {
Default::default(),
Default::default(),
&Default::default(),
&[],
);

assert_ne!(config.test_runner_config.fuzzer_seed, 0);
Expand All @@ -112,6 +117,7 @@ mod tests {
Default::default(),
Default::default(),
&Default::default(),
&[],
);
assert_eq!(
config,
Expand Down Expand Up @@ -162,6 +168,7 @@ mod tests {
Default::default(),
Default::default(),
&config_from_scarb,
&[],
);
assert_eq!(
config,
Expand All @@ -182,6 +189,7 @@ mod tests {
trace: true,
profile: true,
coverage: true,
additional_args: vec![],
},
versioned_programs_dir: Default::default(),
}),
Expand Down Expand Up @@ -215,6 +223,7 @@ mod tests {
Default::default(),
Default::default(),
&config_from_scarb,
&[],
);

assert_eq!(
Expand All @@ -236,6 +245,7 @@ mod tests {
trace: true,
profile: true,
coverage: true,
additional_args: vec![],
},
versioned_programs_dir: Default::default(),
}),
Expand Down
21 changes: 18 additions & 3 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use forge_runner::CACHE_DIR;
use run_tests::workspace::run_for_workspace;
use scarb_api::{metadata::MetadataCommandExt, ScarbCommand};
use scarb_ui::args::{FeaturesSpec, PackagesFilter};
use std::ffi::OsString;
use std::{fs, num::NonZeroU32, thread::available_parallelism};
use tokio::runtime::Builder;
use universal_sierra_compiler_api::UniversalSierraCompilerCommand;
Expand Down Expand Up @@ -84,6 +85,7 @@ enum ColorOption {
#[allow(clippy::struct_excessive_bools)]
pub struct TestArgs {
/// Name used to filter tests
#[clap(allow_hyphen_values = true)]
test_filter: Option<String>,
/// Use exact matches for `test_filter`
#[arg(short, long)]
Expand Down Expand Up @@ -127,11 +129,11 @@ pub struct TestArgs {
save_trace_data: bool,

/// Build profiles of all tests which have passed and are not fuzz tests using the cairo-profiler
#[arg(long)]
#[arg(long, conflicts_with = "coverage")]
build_profile: bool,

/// Generate a coverage report for the executed tests which have passed and are not fuzz tests using the cairo-coverage
#[arg(long)]
#[arg(long, conflicts_with = "build_profile")]
coverage: bool,

/// Number of maximum steps during a single test. For fuzz tests this value is applied to each subtest separately.
Expand All @@ -145,6 +147,10 @@ pub struct TestArgs {
/// Build contracts separately in the scarb starknet contract target
#[arg(long)]
no_optimization: bool,

/// Additional arguments for coverage or profiler
#[clap(allow_hyphen_values = true)]
coverage_or_profiler_args: Vec<OsString>,
}

pub enum ExitStatus {
Expand Down Expand Up @@ -173,7 +179,16 @@ pub fn main_execution() -> Result<ExitStatus> {

Ok(ExitStatus::Success)
}
ForgeSubcommand::Test { args } => {
ForgeSubcommand::Test { mut args } => {
// clap can capture the first flag in test filter,
// let's move it to coverage_or_profiler_args
if let Some(test_filter) = &args.test_filter {
if test_filter.starts_with('-') {
args.coverage_or_profiler_args.insert(0, test_filter.into());
args.test_filter = None;
}
}
Comment on lines +182 to +190
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that such heuristic can generate problems in the future, and introducing such condition disturbs the readability of the code. Maybe we should just add coverage subcommand to catch arguments in simpler and cleaner way? What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could at least check if --coverage or --build-profile were actually passed. Because I think this heuristic should be only enabled in this case, otherwise, we shouldn't be doing anything with the filter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider some warning when it runs, but not sure what it would be,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could at least check if --coverage or --build-profile were actually passed. Because I think this heuristic should be only enabled in this case, otherwise, we shouldn't be doing anything with the filter.

yes, it's the correct one, but it is going to be the same thing as if there was just a subcommand.

Copy link
Collaborator Author

@ksew1 ksew1 Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that such heuristic can generate problems in the future, and introducing such condition disturbs the readability of the code. Maybe we should just add coverage subcommand to catch arguments in simpler and cleaner way? What do you think?

I guess we were discussing this earlier i don't know why we bring it here now and block it with the request changes 😅

We could consider some warning when it runs, but not sure what it would be

There shouldn't be any warning. snforge test --coverage --my-coverage-flag is perfectly fine and user doesn't do anything wrong and yet it caputers --my-coverage-flag in test filter

I think we could at least check if --coverage or --build-profile were actually passed. Because I think this heuristic should be only enabled in this case, otherwise, we shouldn't be doing anything with the filter.

What about some meaningful validation of argument passed to filter? I don't think we should allow passing test filter argument starting -.


let cores = if let Ok(available_cores) = available_parallelism() {
available_cores.get()
} else {
Expand Down
1 change: 1 addition & 0 deletions crates/forge/src/run_tests/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl RunForPackageArgs {
cache_dir.clone(),
versioned_programs_dir,
&forge_config_from_scarb,
&args.coverage_or_profiler_args,
));

let test_filter = TestsFilter::from_flags(
Expand Down
6 changes: 3 additions & 3 deletions crates/forge/src/run_tests/test_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub async fn run_for_test_target(
let (send, mut rec) = channel(1);

let maybe_versioned_program_path = Arc::new(maybe_save_versioned_program(
forge_config.output_config.execution_data_to_save,
&forge_config.output_config.execution_data_to_save,
&tests,
&forge_config.output_config.versioned_programs_dir,
package_name,
Expand Down Expand Up @@ -95,7 +95,7 @@ pub async fn run_for_test_target(

let trace_path = maybe_save_trace_and_profile(
&result,
forge_config.output_config.execution_data_to_save,
&forge_config.output_config.execution_data_to_save,
)?;
if let Some(path) = trace_path {
saved_trace_data_paths.push(path);
Expand All @@ -110,7 +110,7 @@ pub async fn run_for_test_target(
}

maybe_generate_coverage(
forge_config.output_config.execution_data_to_save,
&forge_config.output_config.execution_data_to_save,
&saved_trace_data_paths,
)?;

Expand Down
14 changes: 14 additions & 0 deletions crates/forge/tests/e2e/build_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,17 @@ fn simple_package_build_profile() {
// Check if it doesn't crash in case some data already exists
test_runner(&temp).arg("--build-profile").assert().code(1);
}

#[test]
fn simple_package_build_profile_and_pass_args() {
let temp = setup_package("simple_package");

test_runner(&temp)
.arg("--build-profile")
.arg("--output-path")
.arg("my_file.pb.gz")
.assert()
.code(1);

assert!(temp.join("my_file.pb.gz").is_file());
}
Loading
Loading