diff --git a/tools/hermes/src/aeneas.rs b/tools/hermes/src/aeneas.rs new file mode 100644 index 0000000000..ff9c322aff --- /dev/null +++ b/tools/hermes/src/aeneas.rs @@ -0,0 +1,56 @@ +use std::process::Command; + +use anyhow::{bail, Context, Result}; + +use crate::{resolve::Roots, scanner::HermesArtifact}; + +pub fn run_aeneas(roots: &Roots, artifacts: &[HermesArtifact]) -> Result<()> { + let llbc_root = roots.llbc_root(); + let lean_generated_root = roots.lean_generated_root(); + + for artifact in artifacts { + if artifact.start_from.is_empty() { + log::debug!( + "Skipping artifact '{}' because it has no entry points", + artifact.name.target_name + ); + continue; + } + + log::debug!("Invoking Aeneas on artifact '{}'...", artifact.name.target_name); + + let llbc_path = llbc_root.join(artifact.llbc_file_name()); + let output_dir = lean_generated_root.join(artifact.artifact_slug()); + + std::fs::create_dir_all(&output_dir).context("Failed to create Aeneas output directory")?; + + let mut cmd = Command::new("aeneas"); + + cmd.arg("-backend").arg("lean"); + cmd.arg("-dest").arg(&output_dir); + cmd.arg("-split-files"); + cmd.arg("-no-lean-default-lakefile"); + cmd.arg("-decreases-clauses"); + cmd.arg("-abort-on-error"); + + // TODO: Handle opaque functions (`unsafe(axiom)`). + // We need to parse these from the AST and pass them as `-opaque module::params::...` + cmd.arg(&llbc_path); + + log::debug!("Command: {:?}", cmd); + + let output = cmd.output().context("Failed to spawn aeneas")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!( + "Aeneas failed for package '{}' with status: {}\nstderr:\n{}", + artifact.name.package_name, + output.status, + stderr + ); + } + } + + Ok(()) +} diff --git a/tools/hermes/src/charon.rs b/tools/hermes/src/charon.rs index 77ca2ef10d..bd22206548 100644 --- a/tools/hermes/src/charon.rs +++ b/tools/hermes/src/charon.rs @@ -12,9 +12,9 @@ use crate::{ }; pub fn run_charon(args: &Args, roots: &Roots, packages: &[HermesArtifact]) -> Result<()> { - let charon_root = roots.charon_root(); + let llbc_root = roots.llbc_root(); - std::fs::create_dir_all(&charon_root).context("Failed to create charon output directory")?; + std::fs::create_dir_all(&llbc_root).context("Failed to create LLBC output directory")?; for artifact in packages { if artifact.start_from.is_empty() { @@ -26,8 +26,8 @@ pub fn run_charon(args: &Args, roots: &Roots, packages: &[HermesArtifact]) -> Re let mut cmd = Command::new("charon"); cmd.arg("cargo"); - // Output artifacts to target/hermes//charon - let llbc_path = charon_root.join(artifact.llbc_file_name()); + // Output artifacts to target/hermes//llbc + let llbc_path = llbc_root.join(artifact.llbc_file_name()); log::debug!("Writing .llbc file to {}", llbc_path.display()); cmd.arg("--dest-file").arg(llbc_path); diff --git a/tools/hermes/src/main.rs b/tools/hermes/src/main.rs index 3ea66b64fa..f0de1ca4ef 100644 --- a/tools/hermes/src/main.rs +++ b/tools/hermes/src/main.rs @@ -1,3 +1,4 @@ +mod aeneas; mod charon; mod diagnostics; mod errors; @@ -37,9 +38,11 @@ fn main() -> anyhow::Result<()> { let roots = resolve::resolve_roots(&resolve_args)?; let packages = scanner::scan_workspace(&roots)?; if packages.is_empty() { - anyhow::bail!("No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify."); + log::warn!("No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify."); + return Ok(()); } - charon::run_charon(&resolve_args, &roots, &packages) + charon::run_charon(&resolve_args, &roots, &packages)?; + aeneas::run_aeneas(&roots, &packages) } } } diff --git a/tools/hermes/src/resolve.rs b/tools/hermes/src/resolve.rs index 77df8e32b7..d9a85e55d7 100644 --- a/tools/hermes/src/resolve.rs +++ b/tools/hermes/src/resolve.rs @@ -125,8 +125,12 @@ pub struct Roots { } impl Roots { - pub fn charon_root(&self) -> PathBuf { - self.hermes_run_root.join("charon") + pub fn llbc_root(&self) -> PathBuf { + self.hermes_run_root.join("llbc") + } + + pub fn lean_generated_root(&self) -> PathBuf { + self.hermes_run_root.join("lean").join("generated") } } diff --git a/tools/hermes/src/scanner.rs b/tools/hermes/src/scanner.rs index f14191de67..efa2d71879 100644 --- a/tools/hermes/src/scanner.rs +++ b/tools/hermes/src/scanner.rs @@ -22,11 +22,13 @@ pub struct HermesArtifact { } impl HermesArtifact { - /// Returns the name of the `.llbc` file to use for this artifact. + /// Returns a unique "slug" for this artifact, used for file naming. /// /// Guarantees uniqueness based on manifest path even if multiple packages /// have the same name. - pub fn llbc_file_name(&self) -> String { + /// + /// Format: `{package_name}-{target_name}-{hash:08x}` + pub fn artifact_slug(&self) -> String { fn hash(t: &T) -> u64 { let mut s = std::collections::hash_map::DefaultHasher::new(); t.hash(&mut s); @@ -40,7 +42,13 @@ impl HermesArtifact { let h1 = hash(&self.name.target_name); let h2 = hash(&self.target_kind); let h = hash(&[h0, h1, h2]); - format!("{}-{}-{:08x}.llbc", self.name.package_name, self.name.target_name, h) + + format!("{}-{}-{:08x}", self.name.package_name, self.name.target_name, h) + } + + /// Returns the name of the `.llbc` file to use for this artifact. + pub fn llbc_file_name(&self) -> String { + format!("{}.llbc", self.artifact_slug()) } } diff --git a/tools/hermes/tests/fixtures/cfg_blind_spot/expected_status.txt b/tools/hermes/tests/fixtures/cfg_blind_spot/expected_status.txt new file mode 100644 index 0000000000..7a4059ef83 --- /dev/null +++ b/tools/hermes/tests/fixtures/cfg_blind_spot/expected_status.txt @@ -0,0 +1 @@ +failure diff --git a/tools/hermes/tests/fixtures/cfg_blind_spot/expected_stderr.txt b/tools/hermes/tests/fixtures/cfg_blind_spot/expected_stderr.txt new file mode 100644 index 0000000000..673e465856 --- /dev/null +++ b/tools/hermes/tests/fixtures/cfg_blind_spot/expected_stderr.txt @@ -0,0 +1 @@ +Charon failed with status diff --git a/tools/hermes/tests/fixtures/cfg_blind_spot/hermes.toml b/tools/hermes/tests/fixtures/cfg_blind_spot/hermes.toml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/hermes/tests/fixtures/cfg_blind_spot/source/Cargo.toml b/tools/hermes/tests/fixtures/cfg_blind_spot/source/Cargo.toml new file mode 100644 index 0000000000..6593c66111 --- /dev/null +++ b/tools/hermes/tests/fixtures/cfg_blind_spot/source/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cfg_blind_spot" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/cfg_blind_spot/source/src/lib.rs b/tools/hermes/tests/fixtures/cfg_blind_spot/source/src/lib.rs new file mode 100644 index 0000000000..835b7aebef --- /dev/null +++ b/tools/hermes/tests/fixtures/cfg_blind_spot/source/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(target_os = "haiku")] +mod ghost { + /// ```lean + /// ``` + pub fn hidden() {} +} diff --git a/tools/hermes/tests/fixtures/external_path_dep/extra.rs b/tools/hermes/tests/fixtures/external_path_dep/extra.rs new file mode 100644 index 0000000000..b8b7feb77b --- /dev/null +++ b/tools/hermes/tests/fixtures/external_path_dep/extra.rs @@ -0,0 +1,3 @@ +/// ```lean +/// ``` +pub fn foo() {} diff --git a/tools/hermes/tests/fixtures/external_path_dep/hermes.toml b/tools/hermes/tests/fixtures/external_path_dep/hermes.toml new file mode 100644 index 0000000000..174ba78b66 --- /dev/null +++ b/tools/hermes/tests/fixtures/external_path_dep/hermes.toml @@ -0,0 +1,5 @@ +[[artifact]] +package = "external_path_dep" +target = "external_path_dep" +should_exist = true +kind = "llbc" diff --git a/tools/hermes/tests/fixtures/external_path_dep/source/Cargo.toml b/tools/hermes/tests/fixtures/external_path_dep/source/Cargo.toml new file mode 100644 index 0000000000..91fc9663c7 --- /dev/null +++ b/tools/hermes/tests/fixtures/external_path_dep/source/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "external_path_dep" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/external_path_dep/source/src/lib.rs b/tools/hermes/tests/fixtures/external_path_dep/source/src/lib.rs new file mode 100644 index 0000000000..5bb73daddc --- /dev/null +++ b/tools/hermes/tests/fixtures/external_path_dep/source/src/lib.rs @@ -0,0 +1,2 @@ +#[path = "../../../extra.rs"] +mod extra; diff --git a/tools/hermes/tests/fixtures/hidden_syntax_error/expected_status.txt b/tools/hermes/tests/fixtures/hidden_syntax_error/expected_status.txt new file mode 100644 index 0000000000..7a4059ef83 --- /dev/null +++ b/tools/hermes/tests/fixtures/hidden_syntax_error/expected_status.txt @@ -0,0 +1 @@ +failure diff --git a/tools/hermes/tests/fixtures/hidden_syntax_error/expected_stderr.txt b/tools/hermes/tests/fixtures/hidden_syntax_error/expected_stderr.txt new file mode 100644 index 0000000000..8276753746 --- /dev/null +++ b/tools/hermes/tests/fixtures/hidden_syntax_error/expected_stderr.txt @@ -0,0 +1 @@ +error diff --git a/tools/hermes/tests/fixtures/hidden_syntax_error/hermes.toml b/tools/hermes/tests/fixtures/hidden_syntax_error/hermes.toml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/hermes/tests/fixtures/hidden_syntax_error/source/Cargo.toml b/tools/hermes/tests/fixtures/hidden_syntax_error/source/Cargo.toml new file mode 100644 index 0000000000..ac081d203e --- /dev/null +++ b/tools/hermes/tests/fixtures/hidden_syntax_error/source/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "hidden_syntax_error" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/hidden_syntax_error/source/src/lib.rs b/tools/hermes/tests/fixtures/hidden_syntax_error/source/src/lib.rs new file mode 100644 index 0000000000..7d18c92e60 --- /dev/null +++ b/tools/hermes/tests/fixtures/hidden_syntax_error/source/src/lib.rs @@ -0,0 +1,4 @@ +#[cfg(all())] +mod broken { + fn syntax_error( { // Missing closing parenthesis +} diff --git a/tools/hermes/tests/fixtures/macro_blind_spot/expected_status.txt b/tools/hermes/tests/fixtures/macro_blind_spot/expected_status.txt index 7a4059ef83..2e9ba477f8 100644 --- a/tools/hermes/tests/fixtures/macro_blind_spot/expected_status.txt +++ b/tools/hermes/tests/fixtures/macro_blind_spot/expected_status.txt @@ -1 +1 @@ -failure +success diff --git a/tools/hermes/tests/fixtures/macro_blind_spot/source/Cargo.toml b/tools/hermes/tests/fixtures/macro_blind_spot/source/Cargo.toml index f94e741790..5289f55d8e 100644 --- a/tools/hermes/tests/fixtures/macro_blind_spot/source/Cargo.toml +++ b/tools/hermes/tests/fixtures/macro_blind_spot/source/Cargo.toml @@ -2,3 +2,6 @@ name = "macro_blind_spot" version = "0.1.0" edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/macro_blind_spot/source/src/hidden.rs b/tools/hermes/tests/fixtures/macro_blind_spot/source/src/hidden.rs new file mode 100644 index 0000000000..b8b7feb77b --- /dev/null +++ b/tools/hermes/tests/fixtures/macro_blind_spot/source/src/hidden.rs @@ -0,0 +1,3 @@ +/// ```lean +/// ``` +pub fn foo() {} diff --git a/tools/hermes/tests/fixtures/macro_blind_spot/source/src/lib.rs b/tools/hermes/tests/fixtures/macro_blind_spot/source/src/lib.rs index 86307a7a9e..0e79d8ee2a 100644 --- a/tools/hermes/tests/fixtures/macro_blind_spot/source/src/lib.rs +++ b/tools/hermes/tests/fixtures/macro_blind_spot/source/src/lib.rs @@ -1,9 +1,2 @@ -macro_rules! make_proof { - () => { - /// ```lean - /// theorem hidden_proof : True := trivial - /// ``` - pub fn hidden_proof() {} - } -} -make_proof!(); +macro_rules! gen_mod { ($n:ident) => { mod $n; } } +gen_mod!(hidden); diff --git a/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_status.txt b/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_stderr.txt b/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_canonical_mismatch/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/map_external_dep/expected_stderr.txt b/tools/hermes/tests/fixtures/map_external_dep/expected_stderr.txt index e9d8323ddd..fdedd10615 100644 --- a/tools/hermes/tests/fixtures/map_external_dep/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_external_dep/expected_stderr.txt @@ -1 +1 @@ -[External Error] external error \ No newline at end of file +Diagnostic error in charon diff --git a/tools/hermes/tests/fixtures/map_macro_expansion/expected_status.txt b/tools/hermes/tests/fixtures/map_macro_expansion/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_macro_expansion/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_macro_expansion/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_macro_expansion/expected_stderr.txt b/tools/hermes/tests/fixtures/map_macro_expansion/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_macro_expansion/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_macro_expansion/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_status.txt b/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_stderr.txt b/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_mixed_diagnostics/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/map_relative_path/expected_status.txt b/tools/hermes/tests/fixtures/map_relative_path/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_relative_path/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_relative_path/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_relative_path/expected_stderr.txt b/tools/hermes/tests/fixtures/map_relative_path/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_relative_path/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_relative_path/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/map_std_lib/expected_status.txt b/tools/hermes/tests/fixtures/map_std_lib/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_std_lib/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_std_lib/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_std_lib/expected_stderr.txt b/tools/hermes/tests/fixtures/map_std_lib/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_std_lib/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_std_lib/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/map_std_library/expected_stderr.txt b/tools/hermes/tests/fixtures/map_std_library/expected_stderr.txt index 3149ec8584..fdedd10615 100644 --- a/tools/hermes/tests/fixtures/map_std_library/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_std_library/expected_stderr.txt @@ -1 +1 @@ -[External Error] std error \ No newline at end of file +Diagnostic error in charon diff --git a/tools/hermes/tests/fixtures/map_symlinked_file/expected_stderr.txt b/tools/hermes/tests/fixtures/map_symlinked_file/expected_stderr.txt index e0410c26cc..fdedd10615 100644 --- a/tools/hermes/tests/fixtures/map_symlinked_file/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_symlinked_file/expected_stderr.txt @@ -1,6 +1 @@ - × something bad - ╭─[[PROJECT_ROOT]/src/untransformed.rs:1:1] - 1 │ pub fn untouched() {} - · ─────┬──── - · ╰── oops - ╰──── \ No newline at end of file +Diagnostic error in charon diff --git a/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_status.txt b/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_status.txt +++ b/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_stderr.txt b/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/map_workspace_path_dep/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/fixtures/no_specs/expected_status.txt b/tools/hermes/tests/fixtures/no_specs/expected_status.txt index 7a4059ef83..2e9ba477f8 100644 --- a/tools/hermes/tests/fixtures/no_specs/expected_status.txt +++ b/tools/hermes/tests/fixtures/no_specs/expected_status.txt @@ -1 +1 @@ -failure +success diff --git a/tools/hermes/tests/fixtures/orphaned_entry/hermes.toml b/tools/hermes/tests/fixtures/orphaned_entry/hermes.toml new file mode 100644 index 0000000000..38df98df34 --- /dev/null +++ b/tools/hermes/tests/fixtures/orphaned_entry/hermes.toml @@ -0,0 +1,5 @@ +[[artifact]] +package = "orphaned_entry" +target = "orphaned_entry" +should_exist = true +kind = "llbc" diff --git a/tools/hermes/tests/fixtures/orphaned_entry/source/Cargo.toml b/tools/hermes/tests/fixtures/orphaned_entry/source/Cargo.toml new file mode 100644 index 0000000000..cacad42251 --- /dev/null +++ b/tools/hermes/tests/fixtures/orphaned_entry/source/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "orphaned_entry" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/orphaned_entry/source/src/lib.rs b/tools/hermes/tests/fixtures/orphaned_entry/source/src/lib.rs new file mode 100644 index 0000000000..6022936d63 --- /dev/null +++ b/tools/hermes/tests/fixtures/orphaned_entry/source/src/lib.rs @@ -0,0 +1,3 @@ +/// ```lean +/// ``` +fn private_helper() {} diff --git a/tools/hermes/tests/fixtures/split_personality_mod/hermes.toml b/tools/hermes/tests/fixtures/split_personality_mod/hermes.toml new file mode 100644 index 0000000000..5f75793206 --- /dev/null +++ b/tools/hermes/tests/fixtures/split_personality_mod/hermes.toml @@ -0,0 +1,11 @@ +[[artifact]] +package = "split_personality_mod" +target = "split_personality_mod" # The lib target name +should_exist = true +kind = "llbc" + +[[artifact]] +package = "split_personality_mod" +target = "split_personality_mod" +should_exist = true +kind = "lean" diff --git a/tools/hermes/tests/fixtures/split_personality_mod/source/Cargo.toml b/tools/hermes/tests/fixtures/split_personality_mod/source/Cargo.toml new file mode 100644 index 0000000000..308794b85e --- /dev/null +++ b/tools/hermes/tests/fixtures/split_personality_mod/source/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "split_personality_mod" +version = "0.1.0" +edition = "2021" + +[lib] +path = "src/lib.rs" diff --git a/tools/hermes/tests/fixtures/split_personality_mod/source/src/a.rs b/tools/hermes/tests/fixtures/split_personality_mod/source/src/a.rs new file mode 100644 index 0000000000..b8b7feb77b --- /dev/null +++ b/tools/hermes/tests/fixtures/split_personality_mod/source/src/a.rs @@ -0,0 +1,3 @@ +/// ```lean +/// ``` +pub fn foo() {} diff --git a/tools/hermes/tests/fixtures/split_personality_mod/source/src/lib.rs b/tools/hermes/tests/fixtures/split_personality_mod/source/src/lib.rs new file mode 100644 index 0000000000..be6737fec6 --- /dev/null +++ b/tools/hermes/tests/fixtures/split_personality_mod/source/src/lib.rs @@ -0,0 +1,3 @@ +mod a; +#[path = "a.rs"] +mod b; diff --git a/tools/hermes/tests/fixtures/warnings_vs_errors/expected_status.txt b/tools/hermes/tests/fixtures/warnings_vs_errors/expected_status.txt index 63d06c12f0..0f0c913d55 100644 --- a/tools/hermes/tests/fixtures/warnings_vs_errors/expected_status.txt +++ b/tools/hermes/tests/fixtures/warnings_vs_errors/expected_status.txt @@ -1 +1 @@ -failure \ No newline at end of file +success \ No newline at end of file diff --git a/tools/hermes/tests/fixtures/warnings_vs_errors/expected_stderr.txt b/tools/hermes/tests/fixtures/warnings_vs_errors/expected_stderr.txt index f4caa4b4cc..d709ea7719 100644 --- a/tools/hermes/tests/fixtures/warnings_vs_errors/expected_stderr.txt +++ b/tools/hermes/tests/fixtures/warnings_vs_errors/expected_stderr.txt @@ -1 +1 @@ -Error: No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. +No Hermes annotations (/// ```lean ...) found in the selected targets. Nothing to verify. diff --git a/tools/hermes/tests/integration.rs b/tools/hermes/tests/integration.rs index 48997e52d5..c2e9530188 100644 --- a/tools/hermes/tests/integration.rs +++ b/tools/hermes/tests/integration.rs @@ -18,6 +18,8 @@ struct ArtifactExpectation { package: String, target: String, should_exist: bool, + #[serde(default)] + kind: Option, } #[derive(Deserialize)] @@ -39,6 +41,12 @@ fn run_integration_test(path: &Path) -> datatest_stable::Result<()> { let sandbox_root = temp.path().join(test_name); copy_dir_contents(&source_dir, &sandbox_root)?; + // Copy extra.rs if it exists (for external_path_dep test) + let extra_rs = test_case_root.join("extra.rs"); + if extra_rs.exists() { + fs::copy(&extra_rs, temp.path().join("extra.rs"))?; + } + // --- SETUP CHARON SHIM --- let shim_dir = temp.path().join("bin"); fs::create_dir_all(&shim_dir)?; @@ -73,6 +81,27 @@ exec "{1}" "$@" perms.set_mode(0o755); fs::set_permissions(&shim_path, perms)?; + // SHIM AENEAS + let aeneas_shim_path = shim_dir.join("aeneas"); + // For now, we mock aeneas by doing nothing but logging arguments. + // Real aeneas invocation in tests would require aeneas to be installed. + // If we want to mock aeneas output (files), we can do that in the test fixture setup, + // or improve this shim to copy files from a mock source. + let aeneas_shim_content = format!( + r#"#!/bin/sh +echo "AENEAS INVOKED" >> "{0}" +for arg in "$@"; do + echo "AENEAS_ARG:$arg" >> "{0}" +done +echo "---END-INVOCATION---" >> "{0}" +"#, + log_file.display(), + ); + fs::write(&aeneas_shim_path, aeneas_shim_content)?; + let mut perms = fs::metadata(&aeneas_shim_path)?.permissions(); + perms.set_mode(0o755); + fs::set_permissions(&aeneas_shim_path, perms)?; + let mut cmd = assert_cmd::cargo_bin_cmd!("hermes"); cmd.env("HERMES_FORCE_TTY", "1"); cmd.env("FORCE_COLOR", "1"); @@ -95,6 +124,8 @@ exec "{1}" "$@" if env_log_file.exists() { let log_level = fs::read_to_string(env_log_file)?; cmd.env("RUST_LOG", log_level.trim()); + } else { + cmd.env("RUST_LOG", "warn"); } // Mock JSON integration @@ -199,8 +230,8 @@ exec "{1}" "$@" } if !config.artifact.is_empty() { - let charon_dir = sandbox_root.join("target/hermes/hermes_test_target/charon"); - assert_artifacts_match(&charon_dir, &config.artifact)?; + let hermes_run_root = sandbox_root.join("target/hermes/hermes_test_target"); + assert_artifacts_match(&hermes_run_root, &config.artifact)?; } if !config.command.is_empty() { @@ -269,42 +300,53 @@ fn is_subsequence(haystack: &[String], needle: &[String]) -> bool { } fn assert_artifacts_match( - charon_dir: &Path, + hermes_run_root: &Path, expectations: &[ArtifactExpectation], ) -> std::io::Result<()> { - if !charon_dir.exists() { - if expectations.iter().any(|e| e.should_exist) { - panic!("Charon output directory does not exist: {:?}", charon_dir); + let llbc_root = hermes_run_root.join("llbc"); + let lean_root = hermes_run_root.join("lean").join("generated"); + + for exp in expectations { + let kind = exp.kind.as_deref().unwrap_or("llbc"); + let (scan_dir, is_dir_match, suffix) = match kind { + "llbc" => (&llbc_root, false, ".llbc"), + "lean" => (&lean_root, true, ""), + _ => panic!("Unknown artifact kind: {}", kind), + }; + + if !scan_dir.exists() { + if exp.should_exist { + panic!("Artifact directory does not exist: {:?}", scan_dir); + } + continue; } - return Ok(()); - } - // Collect all generated .llbc files - let mut generated_files = Vec::new(); - for entry in fs::read_dir(charon_dir)? { - let entry = entry?; - let name = entry.file_name().to_string_lossy().to_string(); - if name.ends_with(".llbc") { - generated_files.push(name); + let mut found_items = Vec::new(); + for entry in fs::read_dir(scan_dir)? { + let entry = entry?; + let name = entry.file_name().to_string_lossy().to_string(); + if is_dir_match { + if entry.file_type()?.is_dir() { + found_items.push(name); + } + } else if name.ends_with(suffix) { + found_items.push(name); + } } - } - for exp in expectations { - // We expect filenames format: "{package}-{target}-{hash}.llbc" - // Since hash is dynamic, we match on prefix. + // We match strictly on the slug prefix: "{package}-{target}-" let prefix = format!("{}-{}-", exp.package, exp.target); - - let found = generated_files.iter().any(|f| f.starts_with(&prefix)); + let found = found_items.iter().any(|f| f.starts_with(&prefix)); if exp.should_exist && !found { panic!( - "Missing expected artifact for package='{}', target='{}'.\nExpected prefix: '{}'\nFound files: {:?}", - exp.package, exp.target, prefix, generated_files + "Missing expected artifact for package='{}', target='{}', kind='{}'.\nExpected prefix: '{}'\nFound items in {:?}: {:?}", + exp.package, exp.target, kind, prefix, scan_dir, found_items ); } else if !exp.should_exist && found { panic!( - "Found unexpected artifact for package='{}', target='{}'.\nMatched prefix: '{}'\nFound files: {:?}", - exp.package, exp.target, prefix, generated_files + "Found unexpected artifact for package='{}', target='{}', kind='{}'.\nMatched prefix: '{}'\nFound items in {:?}: {:?}", + exp.package, exp.target, kind, prefix, scan_dir, found_items ); } }