From 6efaf2177fff916c71ef6ee9cbc7c3ec1193f23c Mon Sep 17 00:00:00 2001 From: Piotr Magiera <56825108+piotmag769@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:12:01 +0200 Subject: [PATCH] Remove snforge-test-collector (#1675) --- Cargo.lock | 43 -- Cargo.toml | 1 - .../scarb-snforge-test-collector/Cargo.toml | 50 -- .../src/compilation.rs | 45 -- .../src/compilation/test_collector.rs | 339 ----------- .../src/compilation/test_collector/config.rs | 348 ----------- .../test_collector/function_finder.rs | 86 --- .../src/compilation/test_collector/plugin.rs | 51 -- .../src/crate_collection.rs | 85 --- .../src/felt252.rs | 81 --- .../scarb-snforge-test-collector/src/main.rs | 75 --- .../src/metadata.rs | 289 ---------- .../tests/test.rs | 544 ------------------ 13 files changed, 2037 deletions(-) delete mode 100644 extensions/scarb-snforge-test-collector/Cargo.toml delete mode 100644 extensions/scarb-snforge-test-collector/src/compilation.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/compilation/test_collector.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/compilation/test_collector/config.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/compilation/test_collector/function_finder.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/compilation/test_collector/plugin.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/crate_collection.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/felt252.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/main.rs delete mode 100644 extensions/scarb-snforge-test-collector/src/metadata.rs delete mode 100644 extensions/scarb-snforge-test-collector/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index 7360dd61a..6d3211e00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4822,49 +4822,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "scarb-snforge-test-collector" -version = "2.8.4" -dependencies = [ - "anyhow", - "assert_fs", - "cairo-lang-compiler", - "cairo-lang-debug", - "cairo-lang-defs", - "cairo-lang-diagnostics", - "cairo-lang-filesystem", - "cairo-lang-lowering", - "cairo-lang-project", - "cairo-lang-semantic", - "cairo-lang-sierra", - "cairo-lang-sierra-generator", - "cairo-lang-sierra-type-size", - "cairo-lang-starknet", - "cairo-lang-syntax", - "cairo-lang-test-plugin", - "cairo-lang-utils", - "camino", - "clap", - "create-output-dir", - "fs4", - "indoc", - "itertools 0.12.1", - "num-bigint", - "num-traits 0.2.19", - "rayon", - "scarb-metadata 1.12.0", - "scarb-test-support", - "scarb-ui", - "semver", - "serde", - "serde_json", - "smol_str", - "snapbox", - "starknet-types-core", - "thiserror", - "walkdir", -] - [[package]] name = "scarb-stable-hash" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 27cb240d6..9b367ef55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "extensions/scarb-cairo-language-server", "extensions/scarb-cairo-run", "extensions/scarb-cairo-test", - "extensions/scarb-snforge-test-collector", "plugins/cairo-lang-macro", "plugins/cairo-lang-macro-attributes", "plugins/cairo-lang-macro-stable", diff --git a/extensions/scarb-snforge-test-collector/Cargo.toml b/extensions/scarb-snforge-test-collector/Cargo.toml deleted file mode 100644 index a2b160a79..000000000 --- a/extensions/scarb-snforge-test-collector/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "scarb-snforge-test-collector" -version.workspace = true -edition.workspace = true -publish = false - -authors.workspace = true - -[dependencies] -anyhow.workspace = true -cairo-lang-compiler.workspace = true -cairo-lang-debug.workspace = true -cairo-lang-defs.workspace = true -cairo-lang-diagnostics.workspace = true -cairo-lang-filesystem.workspace = true -cairo-lang-lowering.workspace = true -cairo-lang-project.workspace = true -cairo-lang-semantic.workspace = true -cairo-lang-sierra.workspace = true -cairo-lang-sierra-generator.workspace = true -cairo-lang-sierra-type-size.workspace = true -cairo-lang-starknet.workspace = true -cairo-lang-syntax.workspace = true -cairo-lang-test-plugin.workspace = true -cairo-lang-utils.workspace = true -camino.workspace = true -clap.workspace = true -create-output-dir = { path = "../../utils/create-output-dir" } -fs4.workspace = true -itertools.workspace = true -num-bigint.workspace = true -num-traits.workspace = true -rayon.workspace = true -scarb-metadata = { path = "../../scarb-metadata" } -scarb-ui = { path = "../../utils/scarb-ui" } -semver.workspace = true -serde.workspace = true -serde_json.workspace = true -smol_str.workspace = true -starknet-types-core.workspace = true -thiserror.workspace = true -walkdir.workspace = true - -[dev-dependencies] -snapbox.workspace = true -assert_fs.workspace = true -scarb-test-support = { path = "../../utils/scarb-test-support" } -indoc.workspace = true -serde.workspace = true -serde_json.workspace = true diff --git a/extensions/scarb-snforge-test-collector/src/compilation.rs b/extensions/scarb-snforge-test-collector/src/compilation.rs deleted file mode 100644 index 636ae09ad..000000000 --- a/extensions/scarb-snforge-test-collector/src/compilation.rs +++ /dev/null @@ -1,45 +0,0 @@ -use anyhow::Result; -use cairo_lang_sierra::program::VersionedProgram; -use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use serde::Serialize; - -use crate::compilation::test_collector::{collect_tests, TestCaseRaw}; -use crate::crate_collection::{CrateLocation, TestCompilationTarget}; -use crate::metadata::CompilationUnit; - -pub mod test_collector; - -#[derive(Debug, Clone, Serialize, PartialEq)] -pub struct CompiledTestCrateRaw { - pub sierra_program: VersionedProgram, - pub test_cases: Vec, - pub tests_location: CrateLocation, -} - -pub fn compile_tests( - targets: &Vec, - compilation_unit: &CompilationUnit, -) -> Result> { - targets - .par_iter() - .map(|target| target.compile_tests(compilation_unit)) - .collect() -} - -impl TestCompilationTarget { - fn compile_tests(&self, compilation_unit: &CompilationUnit) -> Result { - let (program_artifact, test_cases) = collect_tests( - &self.crate_name, - self.crate_version.clone(), - self.crate_root.as_std_path(), - &self.lib_content, - compilation_unit, - )?; - - Ok(CompiledTestCrateRaw { - sierra_program: program_artifact.into(), - test_cases, - tests_location: self.crate_location.clone(), - }) - } -} diff --git a/extensions/scarb-snforge-test-collector/src/compilation/test_collector.rs b/extensions/scarb-snforge-test-collector/src/compilation/test_collector.rs deleted file mode 100644 index efefe43a6..000000000 --- a/extensions/scarb-snforge-test-collector/src/compilation/test_collector.rs +++ /dev/null @@ -1,339 +0,0 @@ -use anyhow::{anyhow, bail, Context, Result}; -use cairo_lang_compiler::db::RootDatabase; -use cairo_lang_compiler::diagnostics::DiagnosticsReporter; -use cairo_lang_debug::DebugWithDb; -use cairo_lang_defs::db::DefsGroup; -use cairo_lang_defs::ids::{FreeFunctionId, FunctionWithBodyId, ModuleId, ModuleItemId}; -use cairo_lang_diagnostics::ToOption; -use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; -use cairo_lang_filesystem::db::{ - AsFilesGroupMut, CrateConfiguration, CrateSettings, FilesGroup, FilesGroupEx, -}; -use cairo_lang_filesystem::ids::{CrateId, CrateLongId, Directory}; -use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId; -use cairo_lang_project::{ProjectConfig, ProjectConfigContent}; -use cairo_lang_semantic::db::SemanticGroup; -use cairo_lang_semantic::items::functions::GenericFunctionId; -use cairo_lang_semantic::{ConcreteFunction, FunctionLongId}; -use cairo_lang_sierra::debug_info::{Annotations, DebugInfo}; -use cairo_lang_sierra::extensions::enm::EnumType; -use cairo_lang_sierra::extensions::NamedType; -use cairo_lang_sierra::ids::GenericTypeId; -use cairo_lang_sierra::program::{GenericArg, ProgramArtifact}; -use cairo_lang_sierra_generator::db::SierraGenGroup; -use cairo_lang_sierra_generator::program_generator::SierraProgramWithDebug; -use cairo_lang_sierra_generator::replace_ids::replace_sierra_ids_in_program; -use cairo_lang_starknet::starknet_plugin_suite; -use cairo_lang_test_plugin::test_plugin_suite; -use itertools::Itertools; -use semver::Version; -use serde::Serialize; -use smol_str::{SmolStr, ToSmolStr}; -use std::path::Path; -use std::sync::Arc; - -use crate::compilation::test_collector::config::{ExpectedTestResult, RawForkConfig}; -use crate::metadata::CompilationUnit; -use config::{forge_try_extract_test_config, FuzzerConfig, SingleTestConfig}; -use function_finder::FunctionFinder; -use plugin::snforge_test_plugin_suite; - -mod config; -mod function_finder; -mod plugin; - -fn find_all_tests( - db: &dyn SemanticGroup, - main_crate: CrateId, -) -> Vec<(FreeFunctionId, SingleTestConfig)> { - let mut tests = vec![]; - let modules = db.crate_modules(main_crate); - for module_id in modules.iter() { - let Ok(module_items) = db.module_items(*module_id) else { - continue; - }; - tests.extend(module_items.iter().filter_map(|item| { - let ModuleItemId::FreeFunction(func_id) = item else { - return None; - }; - let Ok(attrs) = db.function_with_body_attributes(FunctionWithBodyId::Free(*func_id)) - else { - return None; - }; - Some(( - *func_id, - forge_try_extract_test_config(db.upcast(), &attrs).unwrap()?, - )) - })); - } - tests -} - -#[derive(Debug, PartialEq, Clone, Serialize)] -pub struct TestCaseRaw { - pub name: String, - pub available_gas: Option, - pub ignored: bool, - pub expected_result: ExpectedTestResult, - pub fork_config: Option, - pub fuzzer_config: Option, - pub test_details: TestDetails, -} - -#[derive(Debug, PartialEq, Clone, Serialize)] -pub struct TestDetails { - pub entry_point_offset: usize, - pub parameter_types: Vec<(GenericTypeId, i16)>, - pub return_types: Vec<(GenericTypeId, i16)>, -} - -pub fn collect_tests( - crate_name: &str, - crate_version: Version, - crate_root: &Path, - lib_content: &str, - compilation_unit: &CompilationUnit, -) -> Result<(ProgramArtifact, Vec)> { - let project_config = ProjectConfig { - base_path: crate_root.into(), - corelib: Some(Directory::Real(compilation_unit.corelib_path()?)), - content: ProjectConfigContent { - crate_roots: compilation_unit.dependencies(), - crates_config: compilation_unit.crates_config_for_compilation_unit(), - }, - }; - - // code taken from crates/cairo-lang-test-runner/src/lib.rs - let db = &mut { - let mut b = RootDatabase::builder(); - b.with_cfg( - CfgSet::from_iter([Cfg::name("test")]) - .union(&compilation_unit.compilation_unit_cfg_set()), - ); - b.with_plugin_suite(snforge_test_plugin_suite()); - b.with_plugin_suite(test_plugin_suite()); - b.with_plugin_suite(starknet_plugin_suite()); - b.with_project_config(project_config); - b.build()? - }; - - let main_package_crate_settings = compilation_unit.main_package_crate_settings(); - let main_crate_id = insert_lib_entrypoint_content_into_db( - db, - crate_name, - crate_version, - crate_root, - lib_content, - main_package_crate_settings, - ); - - if build_diagnostics_reporter(compilation_unit).check(db) { - return Err(anyhow!( - "Failed to compile test artifact, for detailed information go through the logs above" - )); - } - let all_tests = find_all_tests(db, main_crate_id); - - let z: Vec = all_tests - .iter() - .filter_map(|(func_id, _cfg)| { - ConcreteFunctionWithBodyId::from_no_generics_free(db, *func_id) - }) - .collect(); - - let sierra_program = db - .get_sierra_program_for_functions(z) - .to_option() - .context("Compilation failed without any diagnostics") - .context("Failed to get sierra program")?; - - let debug_annotations: Option = - maybe_build_debug_annotations(compilation_unit, &sierra_program, db); - - let debug_info = debug_annotations.map(|annotations| DebugInfo { - type_names: Default::default(), - executables: Default::default(), - libfunc_names: Default::default(), - user_func_names: Default::default(), - annotations, - }); - - let sierra_program = replace_sierra_ids_in_program(db, &sierra_program.program); - let function_finder = FunctionFinder::new(sierra_program.clone())?; - - let collected_tests = all_tests - .into_iter() - .map(|(func_id, test)| { - ( - format!( - "{:?}", - FunctionLongId { - function: ConcreteFunction { - generic_function: GenericFunctionId::Free(func_id), - generic_args: vec![] - } - } - .debug(db) - ), - test, - ) - }) - .collect_vec() - .into_iter() - .map(|(test_name, config)| { - let test_details = build_test_details(&function_finder, &test_name).unwrap(); - TestCaseRaw { - name: test_name, - available_gas: config.available_gas, - ignored: config.ignored, - expected_result: config.expected_result, - fork_config: config.fork_config, - fuzzer_config: config.fuzzer_config, - test_details, - } - }) - .collect(); - - validate_tests(&function_finder, &collected_tests)?; - - Ok(( - ProgramArtifact { - program: sierra_program, - debug_info, - }, - collected_tests, - )) -} - -fn maybe_build_debug_annotations( - compilation_unit: &CompilationUnit, - sierra_program: &Arc, - db: &mut RootDatabase, -) -> Option { - if !compilation_unit.unstable_add_statements_functions_debug_info() - && !compilation_unit.unstable_add_statements_code_locations_debug_info() - { - return None; - }; - let mut debug_annotations: Annotations = Annotations::default(); - if compilation_unit.unstable_add_statements_functions_debug_info() { - debug_annotations.extend(Annotations::from( - sierra_program - .debug_info - .statements_locations - .extract_statements_functions(db), - )); - } - if compilation_unit.unstable_add_statements_code_locations_debug_info() { - debug_annotations.extend(Annotations::from( - sierra_program - .debug_info - .statements_locations - .extract_statements_source_code_locations(db), - )); - } - Some(debug_annotations) -} - -fn build_test_details(function_finder: &FunctionFinder, test_name: &str) -> Result { - let func = function_finder.find_function(test_name)?; - - let parameter_types = - function_finder.generic_id_and_size_from_concrete(&func.signature.param_types); - let return_types = function_finder.generic_id_and_size_from_concrete(&func.signature.ret_types); - - Ok(TestDetails { - entry_point_offset: func.entry_point.0, - parameter_types, - return_types, - }) -} - -fn build_diagnostics_reporter(compilation_unit: &CompilationUnit) -> DiagnosticsReporter<'static> { - if compilation_unit.allow_warnings() { - DiagnosticsReporter::stderr().allow_warnings() - } else { - DiagnosticsReporter::stderr() - } -} - -// inspired with cairo-lang-compiler/src/project.rs:49 (part of setup_single_project_file) -fn insert_lib_entrypoint_content_into_db( - db: &mut RootDatabase, - crate_name: &str, - crate_version: Version, - crate_root: &Path, - lib_content: &str, - main_package_crate_settings: CrateSettings, -) -> CrateId { - let main_crate_id = db.intern_crate(CrateLongId::Real { - name: SmolStr::from(crate_name), - discriminator: Some(crate_version.to_smolstr()), - }); - db.set_crate_config( - main_crate_id, - Some(CrateConfiguration { - root: Directory::Real(crate_root.to_path_buf()), - settings: main_package_crate_settings, - }), - ); - - let module_id = ModuleId::CrateRoot(main_crate_id); - let file_id = db.module_main_file(module_id).unwrap(); - db.as_files_group_mut() - .override_file_content(file_id, Some(Arc::from(lib_content))); - - main_crate_id -} - -fn validate_tests( - function_finder: &FunctionFinder, - collected_tests: &Vec, -) -> Result<(), anyhow::Error> { - for test in collected_tests { - let func = function_finder.find_function(&test.name)?; - let signature = &func.signature; - let ret_types = &signature.ret_types; - if ret_types.is_empty() { - bail!( - "The test function {} always succeeds and cannot be used as a test. Make sure to include panickable statements such as `assert` in your test", - test.name - ); - } - let tp = &ret_types[ret_types.len() - 1]; - let info = function_finder.get_info(tp); - let mut maybe_return_type_name = None; - if info.long_id.generic_id == EnumType::ID { - if let GenericArg::UserType(ut) = &info.long_id.generic_args[0] { - if let Some(name) = ut.debug_name.as_ref() { - maybe_return_type_name = Some(name.as_str()); - } - } - } - if let Some(return_type_name) = maybe_return_type_name { - if !return_type_name.starts_with("core::panics::PanicResult::") { - bail!( - "The test function {} always succeeds and cannot be used as a test. Make sure to include panickable statements such as `assert` in your test", - test.name - ); - } - if return_type_name != "core::panics::PanicResult::<((),)>" { - bail!( - "Test function {} returns a value {}, it is required that test functions do \ - not return values", - test.name, - return_type_name - ); - } - } else { - bail!( - "Couldn't read result type for test function {} possible cause: The test function {} \ - always succeeds and cannot be used as a test. Make sure to include panickable statements such as `assert` in your test", - test.name, - test.name - ); - } - } - - Ok(()) -} diff --git a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/config.rs b/extensions/scarb-snforge-test-collector/src/compilation/test_collector/config.rs deleted file mode 100644 index 23e926706..000000000 --- a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/config.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::felt252::Felt252; -use anyhow::Result; -use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_diagnostics::Severity; -use cairo_lang_syntax::attribute::structured::{Attribute, AttributeArg, AttributeArgVariant}; -use cairo_lang_syntax::node::ast::{ArgClause, Expr, PathSegment}; -use cairo_lang_syntax::node::db::SyntaxGroup; -use cairo_lang_syntax::node::helpers::GetIdentifier; -use cairo_lang_syntax::node::TypedStablePtr; -use cairo_lang_test_plugin::test_config::{PanicExpectation, TestExpectation}; -use cairo_lang_test_plugin::{try_extract_test_config, TestConfig}; -use cairo_lang_utils::OptionHelper; -use num_bigint::BigInt; -use num_traits::ToPrimitive; -use serde::Serialize; -use std::num::NonZeroU32; - -const FORK_ATTR: &str = "fork"; -const FUZZER_ATTR: &str = "fuzzer"; -const AVAILABLE_GAS_ATTR: &str = "available_gas"; -/// Expectation for a panic case. -#[derive(Debug, Clone, PartialEq, Serialize)] -pub enum ExpectedPanicValue { - /// Accept any panic value. - Any, - /// Accept only this specific vector of panics. - Exact(Vec), -} - -impl From for ExpectedPanicValue { - fn from(value: PanicExpectation) -> Self { - match value { - PanicExpectation::Any => ExpectedPanicValue::Any, - PanicExpectation::Exact(vec) => { - ExpectedPanicValue::Exact(vec.into_iter().map(Felt252::new).collect()) - } - } - } -} - -/// Expectation for a result of a test. -#[derive(Debug, Clone, PartialEq, Serialize)] -pub enum ExpectedTestResult { - /// Running the test should not panic. - Success, - /// Running the test should result in a panic. - Panics(ExpectedPanicValue), -} - -impl From for ExpectedTestResult { - fn from(value: TestExpectation) -> Self { - match value { - TestExpectation::Success => ExpectedTestResult::Success, - TestExpectation::Panics(panic_expectation) => { - ExpectedTestResult::Panics(panic_expectation.into()) - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub enum RawForkConfig { - Id(String), - Params(RawForkParams), -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub struct RawForkParams { - pub url: String, - pub block_id_type: String, - pub block_id_value: String, -} - -#[derive(Debug, Clone, PartialEq, Serialize)] -pub struct FuzzerConfig { - pub fuzzer_runs: NonZeroU32, - pub fuzzer_seed: u64, -} - -/// The configuration for running a single test. -#[derive(Debug)] -pub struct SingleTestConfig { - /// The amount of gas the test requested. - pub available_gas: Option, - /// The expected result of the run. - pub expected_result: ExpectedTestResult, - /// Should the test be ignored. - pub ignored: bool, - /// The configuration of forked network. - pub fork_config: Option, - /// Custom fuzzing configuration - pub fuzzer_config: Option, -} - -/// Extracts the configuration of a tests from attributes, or returns the diagnostics if the -/// attributes are set illegally. -pub fn forge_try_extract_test_config( - db: &dyn SyntaxGroup, - attrs: &[Attribute], -) -> Result, Vec> { - let maybe_test_config = try_extract_test_config(db, attrs.to_vec())?; - let fork_attr = attrs.iter().find(|attr| attr.id.as_str() == FORK_ATTR); - let fuzzer_attr = attrs.iter().find(|attr| attr.id.as_str() == FUZZER_ATTR); - - let mut diagnostics = vec![]; - - if maybe_test_config.is_none() { - for attr in [fork_attr, fuzzer_attr].into_iter().flatten() { - diagnostics.push(PluginDiagnostic { - severity: Severity::Error, - stable_ptr: attr.id_stable_ptr.untyped(), - message: "Attribute should only appear on tests.".into(), - }); - } - } - - let fork_config = if let Some(attr) = fork_attr { - if attr.args.is_empty() { - None - } else { - extract_fork_config(db, attr).on_none(|| { - diagnostics.push(PluginDiagnostic { - severity: Severity::Error, - stable_ptr: attr.args_stable_ptr.untyped(), - message: "Expected fork config must be of the form `url: , block_id: `." - .into(), - }); - }) - } - } else { - None - }; - - let fuzzer_config = if let Some(attr) = fuzzer_attr { - extract_fuzzer_config(db, attr).on_none(|| { - diagnostics.push(PluginDiagnostic { - severity: Severity::Error, - stable_ptr: attr.args_stable_ptr.untyped(), - message: - "Expected fuzzer config must be of the form `runs: , seed: `" - .into(), - }); - }) - } else { - None - }; - - if !diagnostics.is_empty() { - return Err(diagnostics); - } - - let result = maybe_test_config.map( - |TestConfig { - mut available_gas, - expectation, - ignored, - }| { - // Older versions will crash if the default is passed through - let available_gas_attr = attrs - .iter() - .find(|attr| attr.id.as_str() == AVAILABLE_GAS_ATTR); - - if available_gas_attr.is_none() { - available_gas = None - } - - SingleTestConfig { - available_gas, - expected_result: expectation.into(), - ignored, - fork_config, - fuzzer_config, - } - }, - ); - Ok(result) -} - -fn extract_fork_config(db: &dyn SyntaxGroup, attr: &Attribute) -> Option { - if attr.args.is_empty() { - return None; - } - - match &attr.args[0].variant { - AttributeArgVariant::Unnamed(fork_id) => extract_fork_config_from_id(fork_id, db), - _ => extract_fork_config_from_args(db, attr), - } -} - -fn extract_fuzzer_config(db: &dyn SyntaxGroup, attr: &Attribute) -> Option { - let [AttributeArg { - variant: - AttributeArgVariant::Named { - name: fuzzer_runs_name, - value: fuzzer_runs, - .. - }, - .. - }, AttributeArg { - variant: - AttributeArgVariant::Named { - name: fuzzer_seed_name, - value: fuzzer_seed, - .. - }, - .. - }] = &attr.args[..] - else { - return None; - }; - - if fuzzer_runs_name.text.as_str() != "runs" || fuzzer_seed_name.text.as_str() != "seed" { - return None; - }; - - let fuzzer_runs = extract_numeric_value(db, fuzzer_runs)? - .to_u32()? - .try_into() - .ok()?; - let fuzzer_seed = extract_numeric_value(db, fuzzer_seed)?.to_u64()?; - - Some(FuzzerConfig { - fuzzer_runs, - fuzzer_seed, - }) -} - -fn extract_numeric_value(db: &dyn SyntaxGroup, expr: &Expr) -> Option { - let Expr::Literal(literal) = expr else { - return None; - }; - - literal.numeric_value(db) -} - -fn extract_fork_config_from_id(id: &Expr, db: &dyn SyntaxGroup) -> Option { - let Expr::String(id_str) = id else { - return None; - }; - let id = id_str.string_value(db)?; - - Some(RawForkConfig::Id(id)) -} - -fn extract_fork_config_from_args(db: &dyn SyntaxGroup, attr: &Attribute) -> Option { - let [AttributeArg { - variant: - AttributeArgVariant::Named { - name: url_arg_name, - value: url, - .. - }, - .. - }, AttributeArg { - variant: - AttributeArgVariant::Named { - name: block_id_arg_name, - value: block_id, - .. - }, - .. - }] = &attr.args[..] - else { - return None; - }; - - if url_arg_name.text.as_str() != "url" { - return None; - } - let Expr::String(url_str) = url else { - return None; - }; - let url = url_str.string_value(db)?; - - if block_id_arg_name.text.as_str() != "block_id" { - return None; - } - let Expr::FunctionCall(block_id) = block_id else { - return None; - }; - - let elements: Vec = block_id - .path(db) - .elements(db) - .iter() - .map(|e| e.identifier(db).to_string()) - .collect(); - if !(elements.len() == 2 - && elements[0] == "BlockId" - && ["Number", "Hash", "Tag"].contains(&elements[1].as_str())) - { - return None; - } - - let block_id_type = elements[1].clone(); - - let args = block_id.arguments(db).arguments(db).elements(db); - let expr = match args.first()?.arg_clause(db) { - ArgClause::Unnamed(unnamed_arg_clause) => Some(unnamed_arg_clause.value(db)), - _ => None, - }?; - let block_id_value = try_get_block_id(db, &block_id_type, &expr)?; - - Some(RawForkConfig::Params(RawForkParams { - url, - block_id_type, - block_id_value, - })) -} - -fn try_get_block_id(db: &dyn SyntaxGroup, block_id_type: &str, expr: &Expr) -> Option { - match block_id_type { - "Number" => { - if let Expr::Literal(value) = expr { - return Some( - u64::try_from(value.numeric_value(db).unwrap()) - .ok()? - .to_string(), - ); - } - } - "Hash" => { - // TODO #1179: add range check - if let Expr::Literal(value) = expr { - return Some(value.numeric_value(db).unwrap().to_string()); - } - } - "Tag" => { - if let Expr::Path(block_tag) = expr { - let tag_elements = block_tag.elements(db); - if tag_path_is_valid(&tag_elements, db) { - return Some("Latest".to_string()); - } - } - } - _ => (), - }; - - None -} - -// Only valid options are `BlockTag::Latest` and `Latest` -fn tag_path_is_valid(tag_elements: &[PathSegment], db: &dyn SyntaxGroup) -> bool { - (tag_elements.len() == 1 - || (tag_elements.len() == 2 && tag_elements[0].identifier(db).as_str() == "BlockTag")) - && tag_elements.last().unwrap().identifier(db).as_str() == "Latest" -} diff --git a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/function_finder.rs b/extensions/scarb-snforge-test-collector/src/compilation/test_collector/function_finder.rs deleted file mode 100644 index 852e2e3c5..000000000 --- a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/function_finder.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Basic runner for running a Sierra program on the vm. - -use cairo_lang_sierra::extensions::core::{CoreLibfunc, CoreType}; -use cairo_lang_sierra::extensions::ConcreteType; -use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId}; -use cairo_lang_sierra::program::Function; -use cairo_lang_sierra::program_registry::{ProgramRegistry, ProgramRegistryError}; -use cairo_lang_sierra_type_size::{get_type_size_map, TypeSizeMap}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum FinderError { - #[error("Function with suffix `{suffix}` to run not found.")] - MissingFunction { suffix: String }, - #[error(transparent)] - ProgramRegistryError(#[from] Box), - #[error("Unable to create TypeSizeMap.")] - TypeSizeMapError, -} - -pub struct FunctionFinder { - /// The sierra program. - sierra_program: cairo_lang_sierra::program::Program, - /// Program registry for the Sierra program. - sierra_program_registry: ProgramRegistry, - // Mapping for the sizes of all types for sierra_program - type_size_map: TypeSizeMap, -} - -#[allow(clippy::result_large_err)] -impl FunctionFinder { - pub fn new(sierra_program: cairo_lang_sierra::program::Program) -> Result { - let sierra_program_registry = - ProgramRegistry::::new(&sierra_program)?; - let type_size_map = get_type_size_map(&sierra_program, &sierra_program_registry) - .ok_or(FinderError::TypeSizeMapError)?; - - Ok(Self { - sierra_program, - sierra_program_registry, - type_size_map, - }) - } - - // Copied from crates/cairo-lang-runner/src/lib.rs - /// Finds first function ending with `name_suffix`. - pub fn find_function(&self, name_suffix: &str) -> Result<&Function, FinderError> { - self.sierra_program - .funcs - .iter() - .find(|f| { - if let Some(name) = &f.id.debug_name { - name.ends_with(name_suffix) - } else { - false - } - }) - .ok_or_else(|| FinderError::MissingFunction { - suffix: name_suffix.to_owned(), - }) - } - - #[must_use] - pub fn get_info( - &self, - ty: &cairo_lang_sierra::ids::ConcreteTypeId, - ) -> &cairo_lang_sierra::extensions::types::TypeInfo { - self.sierra_program_registry.get_type(ty).unwrap().info() - } - - /// Converts array of `ConcreteTypeId`s into corresponding `GenericTypeId`s and their sizes - pub fn generic_id_and_size_from_concrete( - &self, - types: &[ConcreteTypeId], - ) -> Vec<(GenericTypeId, i16)> { - types - .iter() - .map(|pt| { - let info = self.get_info(pt); - let generic_id = &info.long_id.generic_id; - let size = self.type_size_map[pt]; - (generic_id.clone(), size) - }) - .collect() - } -} diff --git a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/plugin.rs b/extensions/scarb-snforge-test-collector/src/compilation/test_collector/plugin.rs deleted file mode 100644 index 837f6a410..000000000 --- a/extensions/scarb-snforge-test-collector/src/compilation/test_collector/plugin.rs +++ /dev/null @@ -1,51 +0,0 @@ -use cairo_lang_defs::plugin::{MacroPlugin, MacroPluginMetadata, PluginResult}; -use cairo_lang_semantic::plugin::PluginSuite; -use cairo_lang_syntax::attribute::structured::AttributeListStructurize; -use cairo_lang_syntax::node::ast; -use cairo_lang_syntax::node::db::SyntaxGroup; - -use super::forge_try_extract_test_config; - -/// Plugin to create diagnostics for tests attributes. -#[derive(Debug, Default)] -#[non_exhaustive] -#[allow(clippy::module_name_repetitions)] -struct TestPlugin; - -impl MacroPlugin for TestPlugin { - fn generate_code( - &self, - db: &dyn SyntaxGroup, - item_ast: ast::ModuleItem, - _metadata: &MacroPluginMetadata<'_>, - ) -> PluginResult { - PluginResult { - code: None, - diagnostics: if let ast::ModuleItem::FreeFunction(free_func_ast) = item_ast { - forge_try_extract_test_config(db, &free_func_ast.attributes(db).structurize(db)) - .err() - } else { - None - } - .unwrap_or_default(), - remove_original_item: false, - } - } - - fn declared_attributes(&self) -> Vec { - vec![ - "test".to_string(), - "ignore".to_string(), - "available_gas".to_string(), - "should_panic".to_string(), - "fork".to_string(), - "fuzzer".to_string(), - ] - } -} - -pub fn snforge_test_plugin_suite() -> PluginSuite { - let mut suite = PluginSuite::default(); - suite.add_plugin::(); - suite -} diff --git a/extensions/scarb-snforge-test-collector/src/crate_collection.rs b/extensions/scarb-snforge-test-collector/src/crate_collection.rs deleted file mode 100644 index 0cf4db7a6..000000000 --- a/extensions/scarb-snforge-test-collector/src/crate_collection.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::metadata::CompilationUnit; -use anyhow::{anyhow, Context, Result}; -use camino::{Utf8Path, Utf8PathBuf}; -use semver::Version; -use serde::Serialize; -use walkdir::WalkDir; - -#[derive(Debug, PartialEq, Clone, Serialize)] -pub enum CrateLocation { - /// Main crate in a package - Lib, - /// Crate in the `tests/` directory - Tests, -} - -#[derive(Debug, PartialEq)] -pub struct TestCompilationTarget { - pub crate_root: Utf8PathBuf, - pub crate_name: String, - pub crate_version: Version, - pub crate_location: CrateLocation, - pub lib_content: String, -} - -pub fn collect_test_compilation_targets( - package_name: &str, - package_version: Version, - package_path: &Utf8Path, - compilation_unit: &CompilationUnit, -) -> Result> { - let package_source_file_path = compilation_unit.main_package_source_file_path(); - let mut compilation_targets = vec![TestCompilationTarget { - crate_root: compilation_unit.main_package_source_root(), - crate_name: package_name.to_string(), - crate_version: package_version.clone(), - crate_location: CrateLocation::Lib, - lib_content: std::fs::read_to_string(package_source_file_path) - .with_context(|| format!("failed to read = {package_source_file_path}"))?, - }]; - - let tests_dir_path = package_path.join("tests"); - if tests_dir_path.exists() { - compilation_targets.push(TestCompilationTarget { - crate_name: "tests".to_string(), - crate_version: package_version, - crate_location: CrateLocation::Tests, - lib_content: get_or_create_test_lib_content(tests_dir_path.as_path())?, - crate_root: tests_dir_path, - }); - } - - Ok(compilation_targets) -} - -fn get_or_create_test_lib_content(tests_folder_path: &Utf8Path) -> Result { - let tests_lib_path = tests_folder_path.join("lib.cairo"); - if tests_lib_path - .try_exists() - .with_context(|| format!("Can't check the existence of file = {tests_lib_path}"))? - { - return std::fs::read_to_string(&tests_lib_path).with_context(|| { - format!("Can't read the content of the file = {tests_lib_path} to string") - }); - } - - let mut content = String::new(); - for entry in WalkDir::new(tests_folder_path) - .max_depth(1) - .sort_by_file_name() - { - let entry = entry - .with_context(|| format!("Failed to read directory at path = {tests_folder_path}"))?; - let path = Utf8Path::from_path(entry.path()) - .ok_or_else(|| anyhow!("Failed to convert path = {:?} to Utf8Path", entry.path()))?; - - if path.is_file() && path.extension().unwrap_or_default() == "cairo" { - let mod_name = path - .file_stem() - .unwrap_or_else(|| panic!("Path to test = {path} should have .cairo extension")); - - content.push_str(&format!("mod {mod_name};\n")); - } - } - Ok(content) -} diff --git a/extensions/scarb-snforge-test-collector/src/felt252.rs b/extensions/scarb-snforge-test-collector/src/felt252.rs deleted file mode 100644 index ee35c785b..000000000 --- a/extensions/scarb-snforge-test-collector/src/felt252.rs +++ /dev/null @@ -1,81 +0,0 @@ -use serde::{ - ser::{SerializeMap, SerializeSeq}, - Serialize, Serializer, -}; -use std::collections::HashMap; - -#[derive(Debug, Clone, PartialEq)] -pub struct Felt252(WrapperInner); - -impl Felt252 { - pub fn new(felt: starknet_types_core::felt::Felt) -> Self { - Self(WrapperInner(felt)) - } -} - -#[derive(Debug, Clone, PartialEq)] -struct WrapperInner(starknet_types_core::felt::Felt); - -impl Serialize for Felt252 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let sub_map: HashMap<_, _> = [("val", self.0.clone())].into_iter().collect(); - - let mut map = serializer.serialize_map(Some(1))?; - map.serialize_entry("value", &sub_map)?; - map.end() - } -} - -// this is copy-pasted BigUint (old felt implementation) inner serialization with inlining -impl Serialize for WrapperInner { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut data: Vec<_> = self - .0 - .to_bytes_le() - .chunks(8) - .map(|chunk| { - chunk - .iter() - .rev() - .fold(0, |acc, &c| (acc << 8) | u64::from(c)) - }) - .collect(); - - normalize(&mut data); - - if let Some((&last, data)) = data.split_last() { - let last_lo = last as u32; - let last_hi = (last >> 32) as u32; - let u32_len = data.len() * 2 + 1 + (last_hi != 0) as usize; - let mut seq = serializer.serialize_seq(Some(u32_len))?; - for &x in data { - seq.serialize_element(&(x as u32))?; - seq.serialize_element(&((x >> 32) as u32))?; - } - seq.serialize_element(&last_lo)?; - if last_hi != 0 { - seq.serialize_element(&last_hi)?; - } - seq.end() - } else { - let data: &[u32] = &[]; - data.serialize(serializer) - } - } -} - -fn normalize(data: &mut Vec) { - if let Some(&0) = data.last() { - let len = data.iter().rposition(|&d| d != 0).map_or(0, |i| i + 1); - data.truncate(len); - } - if data.len() < data.capacity() / 4 { - data.shrink_to_fit(); - } -} diff --git a/extensions/scarb-snforge-test-collector/src/main.rs b/extensions/scarb-snforge-test-collector/src/main.rs deleted file mode 100644 index c9e19f346..000000000 --- a/extensions/scarb-snforge-test-collector/src/main.rs +++ /dev/null @@ -1,75 +0,0 @@ -use anyhow::{Context, Result}; -use clap::Parser; -use fs4::FileExt; -use std::fs::{create_dir_all, File}; -use std::io::BufWriter; - -use scarb_metadata::MetadataCommand; -use scarb_ui::args::PackagesFilter; - -use crate::compilation::compile_tests; -use crate::crate_collection::collect_test_compilation_targets; -use crate::metadata::compilation_unit_for_package; - -mod compilation; -mod crate_collection; -mod felt252; -mod metadata; - -/// Starknet Foundry private extension for compiling test artifacts. -/// Users should not call it directly. -#[derive(Parser)] -#[command(version)] -struct Args { - #[command(flatten)] - packages_filter: PackagesFilter, -} - -fn main() -> Result<()> { - let args = Args::parse(); - - let metadata = MetadataCommand::new().inherit_stderr().exec()?; - let selected_packages_metadata = args.packages_filter.match_many(&metadata)?; - - let target_dir = metadata - .target_dir - .clone() - .unwrap_or_else(|| metadata.workspace.root.join("target")); - let snforge_target_dir = target_dir.join(&metadata.current_profile).join("snforge"); - create_output_dir::create_output_dir(&target_dir.into_std_path_buf())?; - create_dir_all(&snforge_target_dir)?; - - for package_metadata in selected_packages_metadata { - let compilation_unit = compilation_unit_for_package(&metadata, &package_metadata)?; - - let compilation_targets = collect_test_compilation_targets( - &package_metadata.name, - package_metadata.version.clone(), - &package_metadata.root, - &compilation_unit, - )?; - - let test_crates = compile_tests(&compilation_targets, &compilation_unit)?; - - // artifact saved to `{target_dir}/{profile_name}/{package_name}.sierra.json` - let output_path = - snforge_target_dir.join(format!("{}.snforge_sierra.json", package_metadata.name)); - let output_file = File::options() - .create(true) - .write(true) - .truncate(true) - .open(&output_path)?; - - output_file - .lock_exclusive() - .with_context(|| format!("Couldn't lock the output file = {output_path}"))?; - let file = BufWriter::new(&output_file); - serde_json::to_writer(file, &test_crates) - .with_context(|| format!("Failed to serialize = {output_path}"))?; - output_file - .unlock() - .with_context(|| format!("Couldn't lock the output file = {output_path}"))?; - } - - Ok(()) -} diff --git a/extensions/scarb-snforge-test-collector/src/metadata.rs b/extensions/scarb-snforge-test-collector/src/metadata.rs deleted file mode 100644 index 8350db1cb..000000000 --- a/extensions/scarb-snforge-test-collector/src/metadata.rs +++ /dev/null @@ -1,289 +0,0 @@ -use anyhow::{anyhow, ensure, Context, Result}; -use cairo_lang_filesystem::cfg::{Cfg, CfgSet}; -use cairo_lang_filesystem::db::{ - CrateSettings, DependencySettings, Edition, ExperimentalFeaturesConfig, CORELIB_CRATE_NAME, -}; -use cairo_lang_project::AllCratesConfig; -use cairo_lang_utils::ordered_hash_map::OrderedHashMap; -use camino::{Utf8Path, Utf8PathBuf}; -use itertools::Itertools; -use scarb_metadata::{ - CompilationUnitComponentMetadata, CompilationUnitMetadata, Metadata, PackageMetadata, -}; -use serde_json::json; -use smol_str::{SmolStr, ToSmolStr}; -use std::collections::BTreeMap; -use std::path::PathBuf; - -pub fn compilation_unit_for_package<'a>( - metadata: &'a Metadata, - package_metadata: &PackageMetadata, -) -> Result> { - let unit_test_cu = metadata - .compilation_units - .iter() - .find(|unit| { - unit.package == package_metadata.id - && unit.target.kind == "test" - && unit.target.params.get("test-type") == Some(&json!("unit")) - }) - .ok_or_else(|| { - anyhow!( - "Failed to find unit test compilation unit for package = {}", - package_metadata.name - ) - })?; - let all_test_cus = metadata - .compilation_units - .iter() - .filter(|unit| unit.package == package_metadata.id && unit.target.kind == "test") - .collect_vec(); - - let unit_test_deps = unit_test_cu.components.iter().collect_vec(); - - for cu in all_test_cus { - let test_type = cu - .target - .params - .get("test-type") - .expect("Test target missing test-type param") - .as_str() - .expect("test-type param is not a string"); - - let test_deps_without_tests = cu - .components - .iter() - .filter(|du| match test_type { - "unit" => true, - _ => !du.source_root().starts_with(cu.target.source_root()), - }) - .collect_vec(); - - ensure!( - unit_test_deps == test_deps_without_tests, - "Dependencies mismatch between test compilation units" - ); - } - - let main_package_metadata = unit_test_cu - .components - .iter() - .find(|comp| comp.package == package_metadata.id) - .into_iter() - .collect_vec(); - - assert_eq!( - main_package_metadata.len(), - 1, - "More than one cu component with main package id found" - ); - - Ok(CompilationUnit { - unit_metadata: unit_test_cu, - main_package_metadata: main_package_metadata[0], - metadata, - }) -} - -pub struct CompilationUnit<'a> { - unit_metadata: &'a CompilationUnitMetadata, - main_package_metadata: &'a CompilationUnitComponentMetadata, - metadata: &'a Metadata, -} - -impl CompilationUnit<'_> { - pub fn dependencies(&self) -> OrderedHashMap { - let dependencies = self - .unit_metadata - .components - .iter() - .filter(|du| &du.name != "core") - .map(|cu| { - ( - cu.name.to_smolstr(), - cu.source_root().to_owned().into_std_path_buf(), - ) - }) - .collect(); - - dependencies - } - - pub fn corelib_path(&self) -> Result { - let corelib = self - .unit_metadata - .components - .iter() - .find(|du| du.name == "core") - .context("Corelib could not be found")?; - Ok(PathBuf::from(corelib.source_root())) - } - - pub fn crates_config_for_compilation_unit(&self) -> AllCratesConfig { - let crates_config: OrderedHashMap = self - .unit_metadata - .components - .iter() - .map(|component| { - let pkg = self - .metadata - .get_package(&component.package) - .unwrap_or_else(|| { - panic!( - "Failed to find = {} package", - &component.package.to_string() - ) - }); - ( - SmolStr::from(&component.name), - get_crate_settings_for_package( - &self.metadata.packages, - &self.unit_metadata.components, - pkg, - component.cfg.as_ref().map(|cfg_vec| build_cfg_set(cfg_vec)), - ), - ) - }) - .collect(); - - AllCratesConfig { - override_map: crates_config, - ..Default::default() - } - } - - /// Retrieve `allow-warnings` flag from the compiler config. - pub fn allow_warnings(&self) -> bool { - self.unit_metadata - .compiler_config - .as_object() - .and_then(|config| config.get("allow_warnings")) - .and_then(|value| value.as_bool()) - .unwrap_or(true) - } - - pub fn unstable_add_statements_functions_debug_info(&self) -> bool { - self.unit_metadata - .compiler_config - .as_object() - .and_then(|config| config.get("unstable_add_statements_functions_debug_info")) - .and_then(|value| value.as_bool()) - .unwrap_or(false) - } - - pub fn unstable_add_statements_code_locations_debug_info(&self) -> bool { - self.unit_metadata - .compiler_config - .as_object() - .and_then(|config| config.get("unstable_add_statements_code_locations_debug_info")) - .and_then(|value| value.as_bool()) - .unwrap_or(false) - } - - pub fn main_package_source_root(&self) -> Utf8PathBuf { - self.main_package_metadata.source_root().to_path_buf() - } - - pub fn main_package_source_file_path(&self) -> &Utf8Path { - &self.main_package_metadata.source_path - } - - pub fn main_package_crate_settings(&self) -> CrateSettings { - let package = self - .metadata - .packages - .iter() - .find(|package| package.id == self.main_package_metadata.package) - .expect("Main package not found in metadata"); - - get_crate_settings_for_package( - &self.metadata.packages, - &self.unit_metadata.components, - package, - self.main_package_metadata - .cfg - .as_ref() - .map(|cfg_vec| build_cfg_set(cfg_vec)), - ) - } - - pub fn compilation_unit_cfg_set(&self) -> CfgSet { - build_cfg_set(&self.unit_metadata.cfg) - } -} - -fn get_crate_settings_for_package( - packages: &[PackageMetadata], - compilation_unit_metadata_components: &[CompilationUnitComponentMetadata], - package: &PackageMetadata, - cfg_set: Option, -) -> CrateSettings { - let edition = package - .edition - .clone() - .map_or(Edition::default(), |edition| { - let edition_value = serde_json::Value::String(edition); - serde_json::from_value(edition_value).unwrap() - }); - // TODO (#1040): replace this with a macro - let experimental_features = ExperimentalFeaturesConfig { - negative_impls: package - .experimental_features - .contains(&String::from("negative_impls")), - coupons: package - .experimental_features - .contains(&String::from("coupons")), - }; - - let mut dependencies: BTreeMap = package - .dependencies - .iter() - .filter_map(|dependency| { - compilation_unit_metadata_components - .iter() - .find(|compilation_unit_metadata_component| { - compilation_unit_metadata_component.name == dependency.name - }) - .map(|compilation_unit_metadata_component| { - let version = packages - .iter() - .find(|package| package.name == compilation_unit_metadata_component.name) - .map(|package| package.version.clone()); - let discriminator = (dependency.name != *CORELIB_CRATE_NAME) - .then_some(version) - .flatten() - .map(|v| v.to_smolstr()); - ( - dependency.name.clone(), - DependencySettings { discriminator }, - ) - }) - }) - .collect(); - - // Adds itself to dependencies - dependencies.insert( - package.name.clone(), - DependencySettings { - discriminator: (package.name != *CORELIB_CRATE_NAME) - .then_some(package.version.clone()) - .map(|v| v.to_smolstr()), - }, - ); - - CrateSettings { - edition, - cfg_set, - experimental_features, - dependencies, - version: Some(package.version.clone()), - } -} - -fn build_cfg_set(cfg: &[scarb_metadata::Cfg]) -> CfgSet { - CfgSet::from_iter(cfg.iter().map(|cfg| { - serde_json::to_value(cfg) - .and_then(serde_json::from_value::) - .expect("Cairo's `Cfg` must serialize identically as Scarb Metadata's `Cfg`.") - })) -} diff --git a/extensions/scarb-snforge-test-collector/tests/test.rs b/extensions/scarb-snforge-test-collector/tests/test.rs deleted file mode 100644 index b64b1dbb8..000000000 --- a/extensions/scarb-snforge-test-collector/tests/test.rs +++ /dev/null @@ -1,544 +0,0 @@ -use assert_fs::prelude::*; -use assert_fs::TempDir; -use cairo_lang_sierra::program::StatementIdx; -use cairo_lang_sierra_generator::statements_code_locations::SourceCodeSpan; -use std::collections::HashMap; - -use scarb_test_support::fsx::ChildPathEx; - -use indoc::indoc; - -use scarb_test_support::command::Scarb; - -use scarb_test_support::project_builder::ProjectBuilder; -use serde_json::{Number, Value}; - -const SIMPLE_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[test] - fn test() { - assert(true == true, 'it works!') - } - } - "# -}; - -#[test] -fn forge_test_locations() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(SIMPLE_TEST) - .src("tests/lib.cairo", SIMPLE_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - - assert_eq!(&json[0]["test_cases"][0]["name"], "forge_test::tests::test"); - assert_eq!(&json[0]["tests_location"], "Lib"); - assert_eq!(&json[1]["test_cases"][0]["name"], "tests::tests::test"); - assert_eq!(&json[1]["tests_location"], "Tests"); - - let case_0 = &json[0]["test_cases"][0]; - - assert_eq!(&case_0["available_gas"], &Value::Null); - assert_eq!(&case_0["expected_result"], "Success"); - assert_eq!(&case_0["fork_config"], &Value::Null); - assert_eq!(&case_0["fuzzer_config"], &Value::Null); - assert_eq!(&case_0["ignored"], false); - assert_eq!(&case_0["test_details"]["entry_point_offset"], 0); - assert_eq!( - &case_0["test_details"]["parameter_types"], - &Value::Array(vec![]) - ); - assert_eq!(&case_0["test_details"]["return_types"][0][0], "Enum"); - assert_eq!(&case_0["test_details"]["return_types"][0][1], 3); -} - -#[test] -fn forge_test_wrong_location() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .src("a/lib.cairo", SIMPLE_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - assert_eq!(&json[0]["test_cases"][0], &Value::Null); -} - -const EMPTY_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[test] - fn test() {} - } - "# -}; - -#[test] -fn forge_empty_test() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(EMPTY_TEST) - .build(&pkg1); - let output = Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .output() - .unwrap(); - - assert!(!output.status.success()); - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - assert!(stderr.contains( - "Error: The test function forge_test::tests::test always succeeds and cannot be used as a test") - ); -} - -const WITH_MANY_ATTRIBUTES_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[ignore] - #[fork(url: "http://your.rpc.url", block_id: BlockId::Number(123))] - #[should_panic] - #[fuzzer(runs: 22, seed: 38)] - #[available_gas(100)] - #[test] - fn test(a: felt252) { - let (x, y) = (1_u8, 2_u8); - let z = x + y; - assert(x < z, 'it works!') - } - } - "# -}; - -#[test] -fn forge_test_with_attributes() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(WITH_MANY_ATTRIBUTES_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - let case_0 = &json[0]["test_cases"][0]; - - assert_eq!(&case_0["available_gas"], &Value::Number(Number::from(100))); - assert_eq!(&case_0["expected_result"]["Panics"], "Any"); - assert_eq!(&case_0["fork_config"]["Params"]["block_id_type"], "Number"); - assert_eq!(&case_0["fork_config"]["Params"]["block_id_value"], "123"); - assert_eq!( - case_0["fork_config"]["Params"]["url"], - "http://your.rpc.url" - ); - assert_eq!(&case_0["fuzzer_config"]["fuzzer_runs"], 22); - assert_eq!(&case_0["fuzzer_config"]["fuzzer_seed"], 38); - assert_eq!(&case_0["ignored"], true); - assert_eq!(&case_0["name"], "forge_test::tests::test"); - assert_eq!(&case_0["test_details"]["entry_point_offset"], 0); - assert_eq!( - &case_0["test_details"]["parameter_types"][0][0], - "RangeCheck" - ); - assert_eq!(&case_0["test_details"]["parameter_types"][0][1], 1); - assert_eq!(&case_0["test_details"]["parameter_types"][1][0], "felt252"); - assert_eq!(&case_0["test_details"]["parameter_types"][1][1], 1); - assert_eq!(&case_0["test_details"]["return_types"][0][0], "RangeCheck"); - assert_eq!(&case_0["test_details"]["return_types"][0][1], 1); - assert_eq!(&case_0["test_details"]["return_types"][1][0], "Enum"); - assert_eq!(&case_0["test_details"]["return_types"][1][1], 3); -} - -const FORK_TAG_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[fork(url: "http://your.rpc.url", block_id: BlockId::Tag(Latest))] - #[test] - fn test() { - assert(true == true, 'it works!') - } - } - "# -}; - -#[test] -fn forge_test_with_fork_tag_attribute() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(FORK_TAG_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - let case_0 = &json[0]["test_cases"][0]; - - assert_eq!(&case_0["fork_config"]["Params"]["block_id_type"], "Tag"); - assert_eq!(&case_0["fork_config"]["Params"]["block_id_value"], "Latest"); - assert_eq!( - case_0["fork_config"]["Params"]["url"], - "http://your.rpc.url" - ); - - assert_eq!(&case_0["name"], "forge_test::tests::test"); -} - -const FORK_HASH_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[fork(url: "http://your.rpc.url", block_id: BlockId::Hash(123))] - #[test] - fn test() { - assert(true == true, 'it works!') - } - } - "# -}; - -#[test] -fn forge_test_with_fork_hash_attribute() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(FORK_HASH_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - let case_0 = &json[0]["test_cases"][0]; - - assert_eq!(&case_0["fork_config"]["Params"]["block_id_type"], "Hash"); - assert_eq!(&case_0["fork_config"]["Params"]["block_id_value"], "123"); - assert_eq!( - case_0["fork_config"]["Params"]["url"], - "http://your.rpc.url" - ); - - assert_eq!(&case_0["name"], "forge_test::tests::test"); -} - -const SHOULD_PANIC_TEST: &str = indoc! {r#" - #[cfg(test)] - mod tests { - #[should_panic(expected: ('panic message', 'eventual second message',))] - #[test] - fn test() { - assert(true == true, 'it works!') - } - } - "# -}; - -#[test] -fn forge_test_with_should_panic_message_attribute() { - let t = TempDir::new().unwrap(); - let pkg1 = t.child("forge"); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(SHOULD_PANIC_TEST) - .build(&pkg1); - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&pkg1) - .assert() - .success(); - - let snforge_sierra = pkg1 - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - assert_eq!( - &json[0]["test_cases"][0]["expected_result"]["Panics"].to_string(), - "{\"Exact\":[{\"value\":{\"val\":[1935763301,544040307,1634625891,112]}},{\"value\":{\"val\":[1935763301,544040307,1668247140,1814066021,1853125985,6649445]}}]}" - ); - - assert_eq!(&json[0]["test_cases"][0]["name"], "forge_test::tests::test"); -} - -#[test] -fn allows_warnings_by_default() { - let t = TempDir::new().unwrap(); - ProjectBuilder::start() - .lib_cairo(indoc! {r#" - fn hello() -> felt252 { - let a = 41; - let b = 42; - b - } - "#}) - .build(&t); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .assert() - .success(); -} - -#[test] -fn can_disallow_warnings() { - let t = TempDir::new().unwrap(); - ProjectBuilder::start() - .lib_cairo(indoc! {r#" - fn hello() -> felt252 { - let a = 41; - let b = 42; - b - } - "#}) - .manifest_extra(indoc! {r#" - [cairo] - allow-warnings = false - "#}) - .build(&t); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .assert() - .failure(); -} - -#[test] -fn uses_dev_dependencies() { - let t = TempDir::new().unwrap(); - let q = t.child("q"); - ProjectBuilder::start() - .name("q") - .lib_cairo("fn dev_dep_function() -> felt252 { 42 }") - .build(&q); - - ProjectBuilder::start() - .name("x") - .dev_dep("q", &q) - .lib_cairo(indoc! {r#" - #[cfg(test)] - mod tests { - use q::dev_dep_function; - - #[test] - fn test() { - assert(dev_dep_function() == 42, ''); - } - } - "#}) - .build(&t); - - let test_path = t.child("tests/test.cairo"); - test_path - .write_str(indoc! {r#" - use q::dev_dep_function; - - fn test() { - assert(dev_dep_function() == 42, ''); - } - "#}) - .unwrap(); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .assert() - .success(); -} - -#[test] -fn does_not_compile_tests_in_dependencies() { - let t = TempDir::new().unwrap(); - let q = t.child("q"); - ProjectBuilder::start() - .name("q") - .lib_cairo(indoc! {r#" - #[cfg(test)] - fn dev_dep_function() -> felt252 { 42 } - "#}) - .build(&q); - - ProjectBuilder::start() - .name("x") - .dev_dep("q", &q) - .lib_cairo(indoc! {r#" - #[cfg(test)] - mod tests { - use q::dev_dep_function; - - #[test] - fn test() { - assert(dev_dep_function() == 42, ''); - } - } - "#}) - .build(&t); - - let output = Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .output() - .unwrap(); - - assert!(!output.status.success()); - let stderr = String::from_utf8_lossy(&output.stderr).to_string(); - assert!(stderr.contains("error: Identifier not found.")); - assert!(stderr.contains("use q::dev_dep_function;")); - assert!(stderr.contains("Error: Failed to compile test artifact, for detailed information go through the logs above")); -} - -#[test] -fn generates_statements_functions_mappings() { - let t = TempDir::new().unwrap(); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(SIMPLE_TEST) - .manifest_extra(indoc! {r#" - [cairo] - unstable-add-statements-functions-debug-info = true - "#}) - .build(&t); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .assert() - .success(); - - let snforge_sierra = t - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - - let mappings = &json[0]["sierra_program"]["debug_info"]["annotations"] - ["github.com/software-mansion/cairo-profiler"]["statements_functions"]; - - assert!(serde_json::from_value::>>(mappings.clone()).is_ok()); -} - -#[test] -fn generates_statements_code_locations_mappings() { - let t = TempDir::new().unwrap(); - - ProjectBuilder::start() - .name("forge_test") - .lib_cairo(SIMPLE_TEST) - .manifest_extra(indoc! {r#" - [cairo] - unstable-add-statements-code-locations-debug-info = true - "#}) - .build(&t); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .current_dir(&t) - .assert() - .success(); - - let snforge_sierra = t - .child("target/dev/snforge/forge_test.snforge_sierra.json") - .read_to_string(); - - let json: Value = serde_json::from_str(&snforge_sierra).unwrap(); - - let mappings = &json[0]["sierra_program"]["debug_info"]["annotations"] - ["github.com/software-mansion/cairo-coverage"]["statements_code_locations"]; - - assert!( - serde_json::from_value::>>( - mappings.clone() - ) - .is_ok() - ); -} - -#[test] -fn features_test_build_success() { - let t = TempDir::new().unwrap(); - ProjectBuilder::start() - .name("hello") - .manifest_extra(indoc! {r#" - [features] - x = [] - "#}) - .lib_cairo(indoc! {r#" - #[cfg(feature: 'x')] - fn f() -> felt252 { 21 } - - #[cfg(test)] - mod tests { - use super::f; - - #[test] - fn it_works() { - assert(f() == 21, 'it works!'); - } - } - "#}) - .build(&t); - - Scarb::quick_snapbox() - .arg("snforge-test-collector") - .env("SCARB_FEATURES", "x") - .current_dir(&t) - .assert() - .success(); -}