Skip to content

Commit 13348b0

Browse files
authored
Small coverage validation refactor (#3654)
Related #3272 commit-id:075b62ba --- **Stack**: - #3656 - #3655 - #3654⚠️ *Part of a stack created by [spr](https://github.com/ejoffe/spr). Do not merge manually using the UI - doing so may have unexpected results.*
1 parent e643809 commit 13348b0

File tree

5 files changed

+113
-69
lines changed

5 files changed

+113
-69
lines changed
Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
use anyhow::{Context, Result, ensure};
2-
use indoc::{formatdoc, indoc};
3-
use scarb_api::metadata::Metadata;
4-
use semver::Version;
2+
use indoc::indoc;
53
use shared::command::CommandExt;
64
use std::ffi::OsString;
75
use std::process::Stdio;
86
use std::{env, fs, path::PathBuf, process::Command};
9-
use toml_edit::{DocumentMut, Table};
107
use which::which;
118

129
pub const COVERAGE_DIR: &str = "coverage";
1310
pub const OUTPUT_FILE_NAME: &str = "coverage.lcov";
1411

15-
const MINIMAL_SCARB_VERSION: Version = Version::new(2, 8, 0);
16-
17-
const CAIRO_COVERAGE_REQUIRED_ENTRIES: [(&str, &str); 3] = [
18-
("unstable-add-statements-functions-debug-info", "true"),
19-
("unstable-add-statements-code-locations-debug-info", "true"),
20-
("inlining-strategy", "avoid"),
21-
];
22-
2312
pub fn run_coverage(saved_trace_data_paths: &[PathBuf], coverage_args: &[OsString]) -> Result<()> {
2413
let coverage = env::var("CAIRO_COVERAGE")
2514
.map(PathBuf::from)
@@ -67,54 +56,3 @@ pub fn run_coverage(saved_trace_data_paths: &[PathBuf], coverage_args: &[OsStrin
6756

6857
Ok(())
6958
}
70-
71-
pub fn can_coverage_be_generated(scarb_metadata: &Metadata) -> Result<()> {
72-
let manifest = fs::read_to_string(&scarb_metadata.runtime_manifest)?.parse::<DocumentMut>()?;
73-
74-
ensure!(
75-
scarb_metadata.app_version_info.version >= MINIMAL_SCARB_VERSION,
76-
"Coverage generation requires scarb version >= {MINIMAL_SCARB_VERSION}",
77-
);
78-
79-
let has_needed_entries = manifest
80-
.get("profile")
81-
.and_then(|profile| profile.get(&scarb_metadata.current_profile))
82-
.and_then(|profile| profile.get("cairo"))
83-
.and_then(|cairo| cairo.as_table())
84-
.is_some_and(|profile_cairo| {
85-
CAIRO_COVERAGE_REQUIRED_ENTRIES
86-
.iter()
87-
.all(|(key, value)| contains_entry_with_value(profile_cairo, key, value))
88-
});
89-
90-
ensure!(
91-
has_needed_entries,
92-
formatdoc! {
93-
"Scarb.toml must have the following Cairo compiler configuration to run coverage:
94-
95-
[profile.{profile}.cairo]
96-
unstable-add-statements-functions-debug-info = true
97-
unstable-add-statements-code-locations-debug-info = true
98-
inlining-strategy = \"avoid\"
99-
... other entries ...
100-
",
101-
profile = scarb_metadata.current_profile
102-
},
103-
);
104-
105-
Ok(())
106-
}
107-
108-
/// Check if the table contains an entry with the given key and value.
109-
/// Accepts only bool and string values.
110-
fn contains_entry_with_value(table: &Table, key: &str, value: &str) -> bool {
111-
table.get(key).is_some_and(|entry| {
112-
if let Some(entry) = entry.as_bool() {
113-
entry.to_string() == value
114-
} else if let Some(entry) = entry.as_str() {
115-
entry == value
116-
} else {
117-
false
118-
}
119-
})
120-
}

crates/forge/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ mod combine_configs;
2727
mod compatibility_check;
2828
mod init;
2929
mod new;
30+
mod profile_validation;
3031
pub mod run_tests;
3132
pub mod scarb;
3233
pub mod shared_cache;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use crate::profile_validation::{check_cairo_profile_entries, get_manifest};
2+
use anyhow::ensure;
3+
use indoc::formatdoc;
4+
use scarb_metadata::Metadata;
5+
use semver::Version;
6+
7+
/// Checks if coverage can be based on scarb version and profile settings extracted from the provided [`Metadata`].
8+
pub fn check_coverage_compatibility(scarb_metadata: &Metadata) -> anyhow::Result<()> {
9+
check_scarb_version(scarb_metadata)?;
10+
check_profile(scarb_metadata)?;
11+
Ok(())
12+
}
13+
14+
/// Checks if the scarb version from the provided [`Metadata`] is greater than or equal to the minimal required version.
15+
fn check_scarb_version(scarb_metadata: &Metadata) -> anyhow::Result<()> {
16+
const MINIMAL_SCARB_VERSION: Version = Version::new(2, 8, 0);
17+
ensure!(
18+
scarb_metadata.app_version_info.version >= MINIMAL_SCARB_VERSION,
19+
"Coverage generation requires scarb version >= {MINIMAL_SCARB_VERSION}",
20+
);
21+
Ok(())
22+
}
23+
24+
/// Checks if the runtime profile settings in the provided from [`Metadata`] contain the required entries for coverage generation.
25+
fn check_profile(scarb_metadata: &Metadata) -> anyhow::Result<()> {
26+
const CAIRO_COVERAGE_REQUIRED_ENTRIES: &[(&str, &str)] = &[
27+
("unstable-add-statements-functions-debug-info", "true"),
28+
("unstable-add-statements-code-locations-debug-info", "true"),
29+
("inlining-strategy", "avoid"),
30+
];
31+
32+
let manifest = get_manifest(scarb_metadata)?;
33+
34+
let has_needed_entries =
35+
check_cairo_profile_entries(&manifest, scarb_metadata, CAIRO_COVERAGE_REQUIRED_ENTRIES);
36+
37+
ensure!(
38+
has_needed_entries,
39+
formatdoc! {
40+
"Scarb.toml must have the following Cairo compiler configuration to run coverage:
41+
42+
[profile.{profile}.cairo]
43+
unstable-add-statements-functions-debug-info = true
44+
unstable-add-statements-code-locations-debug-info = true
45+
inlining-strategy = \"avoid\"
46+
... other entries ...
47+
",
48+
profile = scarb_metadata.current_profile
49+
},
50+
);
51+
52+
Ok(())
53+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
mod coverage;
2+
3+
use crate::TestArgs;
4+
use crate::profile_validation::coverage::check_coverage_compatibility;
5+
use scarb_metadata::Metadata;
6+
use std::fs;
7+
use toml_edit::{DocumentMut, Table};
8+
9+
/// Checks if current profile provided in [`Metadata`] can be used to run coverage and backtrace if applicable.
10+
pub fn check_profile_compatibility(
11+
test_args: &TestArgs,
12+
scarb_metadata: &Metadata,
13+
) -> anyhow::Result<()> {
14+
if test_args.coverage {
15+
check_coverage_compatibility(scarb_metadata)?;
16+
}
17+
Ok(())
18+
}
19+
20+
/// Gets the runtime manifest from the [`Metadata`] and parses it into a [`DocumentMut`].
21+
fn get_manifest(scarb_metadata: &Metadata) -> anyhow::Result<DocumentMut> {
22+
Ok(fs::read_to_string(&scarb_metadata.runtime_manifest)?.parse::<DocumentMut>()?)
23+
}
24+
25+
/// Check if the Cairo profile entries in the manifest contain the required entries.
26+
fn check_cairo_profile_entries(
27+
manifest: &DocumentMut,
28+
scarb_metadata: &Metadata,
29+
required_entries: &[(&str, &str)],
30+
) -> bool {
31+
manifest
32+
.get("profile")
33+
.and_then(|profile| profile.get(&scarb_metadata.current_profile))
34+
.and_then(|profile| profile.get("cairo"))
35+
.and_then(|cairo| cairo.as_table())
36+
.is_some_and(|profile_cairo| {
37+
required_entries
38+
.iter()
39+
.all(|(key, value)| contains_entry_with_value(profile_cairo, key, value))
40+
})
41+
}
42+
43+
/// Check if the table contains an entry with the given key and value.
44+
/// Accepts only bool and string values.
45+
fn contains_entry_with_value(table: &Table, key: &str, value: &str) -> bool {
46+
table.get(key).is_some_and(|entry| {
47+
if let Some(entry) = entry.as_bool() {
48+
entry.to_string() == value
49+
} else if let Some(entry) = entry.as_str() {
50+
entry == value
51+
} else {
52+
false
53+
}
54+
})
55+
}

crates/forge/src/run_tests/workspace.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::package::RunForPackageArgs;
22
use super::structs::{LatestBlocksNumbersMessage, TestsFailureSummaryMessage};
3+
use crate::profile_validation::check_profile_compatibility;
34
use crate::run_tests::structs::OverallSummaryMessage;
45
use crate::warn::{
56
error_if_snforge_std_deprecated_missing, error_if_snforge_std_deprecated_not_compatible,
@@ -13,10 +14,8 @@ use crate::{
1314
warn::warn_if_snforge_std_does_not_match_package_version,
1415
};
1516
use anyhow::{Context, Result};
17+
use forge_runner::test_case_summary::AnyTestCaseSummary;
1618
use forge_runner::{CACHE_DIR, test_target_summary::TestTargetSummary};
17-
use forge_runner::{
18-
coverage_api::can_coverage_be_generated, test_case_summary::AnyTestCaseSummary,
19-
};
2019
use foundry_ui::UI;
2120
use scarb_api::{
2221
ScarbCommand,
@@ -43,9 +42,7 @@ pub async fn run_for_workspace(args: TestArgs, ui: Arc<UI>) -> Result<ExitStatus
4342
}
4443
let scarb_metadata = metadata_command.inherit_stderr().run()?;
4544

46-
if args.coverage {
47-
can_coverage_be_generated(&scarb_metadata)?;
48-
}
45+
check_profile_compatibility(&args, &scarb_metadata)?;
4946

5047
let scarb_version = ScarbCommand::version().run()?.scarb;
5148
if scarb_version >= MINIMAL_SCARB_VERSION_FOR_V2_MACROS_REQUIREMENT {

0 commit comments

Comments
 (0)