test.rs
1 // Copyright (C) 2019-2025 Alpha-Delta Network Inc. 2 // This file is part of the ADL library. 3 4 // The ADL library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 9 // The ADL library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 14 // You should have received a copy of the GNU General Public License 15 // along with the ADL library. If not, see <https://www.gnu.org/licenses/>. 16 17 use super::*; 18 19 use adl_ast::{NetworkName, TEST_PRIVATE_KEY}; 20 use adl_compiler::run; 21 use adl_package::{Package, ProgramData}; 22 use adl_span::Symbol; 23 24 use alphavm::prelude::TestnetV0; 25 26 use colored::Colorize as _; 27 use std::fs; 28 29 /// Test an ADL program. 30 #[derive(Parser, Debug)] 31 pub struct AdlTest { 32 #[clap( 33 name = "TEST_NAME", 34 help = "If specified, run only tests whose qualified name matches against this string.", 35 default_value = "" 36 )] 37 pub(crate) test_name: String, 38 39 #[clap(flatten)] 40 pub(crate) compiler_options: BuildOptions, 41 #[clap(flatten)] 42 pub(crate) env_override: EnvOptions, 43 } 44 45 impl Command for AdlTest { 46 type Input = <AdlBuild as Command>::Output; 47 type Output = (); 48 49 fn log_span(&self) -> Span { 50 tracing::span!(tracing::Level::INFO, "Adl") 51 } 52 53 fn prelude(&self, context: Context) -> Result<Self::Input> { 54 let mut options = self.compiler_options.clone(); 55 options.build_tests = true; 56 (AdlBuild { env_override: self.env_override.clone(), options }).execute(context) 57 } 58 59 fn apply(self, _: Context, input: Self::Input) -> Result<Self::Output> { 60 handle_test(self, input) 61 } 62 } 63 64 fn handle_test(command: AdlTest, package: Package) -> Result<()> { 65 // Get the private key. 66 let private_key = PrivateKey::<TestnetV0>::from_str(TEST_PRIVATE_KEY)?; 67 68 let adl_paths = collect_adl_paths(&package); 69 let alphastd_paths = collect_alphastd_paths(&package); 70 71 let (native_test_functions, interpreter_result) = adl_interpreter::find_and_run_tests( 72 &adl_paths, 73 &alphastd_paths, 74 private_key.to_string(), 75 0u32, 76 chrono::Utc::now().timestamp(), 77 &command.test_name, 78 NetworkName::TestnetV0, 79 )?; 80 81 // Now for native tests. 82 let program_name = package.manifest.program.strip_suffix(".alpha").unwrap(); 83 let program_name_symbol = Symbol::intern(program_name); 84 let build_directory = package.build_directory(); 85 86 let credits = Symbol::intern("credits"); 87 88 // Get bytecode and name for all programs, either directly or from the filesystem if they were compiled. 89 let programs: Vec<run::Program> = package 90 .programs 91 .iter() 92 .filter_map(|program| { 93 // Skip credits.alpha so we don't try to deploy it again. 94 if program.name == credits { 95 return None; 96 } 97 let bytecode = match &program.data { 98 ProgramData::Bytecode(c) => c.clone(), 99 ProgramData::SourcePath { .. } => { 100 // This was not a network dependency, so get its bytecode from the filesystem. 101 let alphastd_path = if program.name == program_name_symbol { 102 build_directory.join("main.alpha") 103 } else { 104 package.imports_directory().join(format!("{}.alpha", program.name)) 105 }; 106 fs::read_to_string(&alphastd_path) 107 .unwrap_or_else(|e| panic!("Failed to read Alpha file at {}: {}", alphastd_path.display(), e)) 108 } 109 }; 110 Some(run::Program { bytecode, name: program.name.to_string() }) 111 }) 112 .collect(); 113 114 let should_fails: Vec<bool> = native_test_functions.iter().map(|test_function| test_function.should_fail).collect(); 115 let cases: Vec<Vec<run::Case>> = native_test_functions 116 .into_iter() 117 .map(|test_function| { 118 // Note. We wrap each individual test in its own vector, so that they are run in insolation. 119 vec![run::Case { 120 program_name: format!("{}.alpha", test_function.program), 121 function: test_function.function, 122 private_key: test_function.private_key, 123 input: Vec::new(), 124 }] 125 }) 126 .collect(); 127 128 let outcomes = run::run_with_ledger(&run::Config { seed: 0, start_height: None, programs }, &cases)? 129 .into_iter() 130 .flatten() 131 .collect::<Vec<_>>(); 132 133 let native_results: Vec<_> = outcomes 134 .into_iter() 135 .zip(should_fails) 136 .map(|(outcome, should_fail)| { 137 let run::ExecutionOutcome { outcome: inner, status, .. } = outcome; 138 139 let message = match (&status, should_fail) { 140 (run::ExecutionStatus::Accepted, false) => None, 141 (run::ExecutionStatus::Accepted, true) => Some("Test succeeded when failure was expected.".to_string()), 142 (_, true) => None, 143 (_, false) => Some(format!("{} -- {}", status, inner.output)), 144 }; 145 146 (inner.program_name, inner.function, message) 147 }) 148 .collect::<Vec<_>>(); 149 150 // All tests are run. Report results. 151 let total = interpreter_result.iter().count() + native_results.len(); 152 let total_passed = interpreter_result.iter().filter(|(_, test_result)| matches!(test_result, Ok(()))).count() 153 + native_results.iter().filter(|(_, _, x)| x.is_none()).count(); 154 155 if total == 0 { 156 println!("No tests run."); 157 Ok(()) 158 } else { 159 println!("{total_passed} / {total} tests passed."); 160 let failed = "FAILED".bold().red(); 161 let passed = "PASSED".bold().green(); 162 for (id, id_result) in interpreter_result.iter() { 163 // Wasteful to make this, but fill will work. 164 let str_id = format!("{id}"); 165 if let Err(err) = id_result { 166 println!("{failed}: {str_id:<30} | {err}"); 167 } else { 168 println!("{passed}: {str_id}"); 169 } 170 } 171 172 for (program, function, case_result) in native_results { 173 let str_id = format!("{program}/{function}"); 174 if let Some(err_str) = case_result { 175 println!("{failed}: {str_id:<30} | {err_str}"); 176 } else { 177 println!("{passed}: {str_id}"); 178 } 179 } 180 181 Ok(()) 182 } 183 }