test.rs
1 // Copyright (C) 2019-2025 ADnet Contributors 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 //! These tests compare interpreter runs against previous interpreter runs. 18 19 use crate::{Interpreter, InterpreterAction}; 20 21 use adl_ast::NetworkName; 22 use adl_span::create_session_if_not_set_then; 23 24 use snarkvm::prelude::{PrivateKey, TestnetV0}; 25 26 use serial_test::serial; 27 use std::{fs, path::PathBuf, str::FromStr as _}; 28 29 pub static TEST_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"; 30 31 /// A special token used to separate modules in test input source code. 32 const MODULE_SEPARATOR: &str = "// --- Next Module:"; 33 34 fn run_and_format_output(interpreter: &mut Interpreter) -> String { 35 let action_result = interpreter.action(InterpreterAction::LeoInterpretOver("test.alpha/main()".into())); 36 37 let futures = 38 interpreter.cursor.futures.iter().map(|f| format!(" async call to {f}")).collect::<Vec<_>>().join("\n"); 39 40 let futures_section = if futures.is_empty() { "# Futures".to_string() } else { format!("# Futures\n{futures}") }; 41 42 let output_section = match action_result { 43 Err(e) => format!("# Output\n{e}"), 44 Ok(None) => "# Output\nno value received".to_string(), 45 Ok(Some(v)) => format!("# Output\n{v}"), 46 }; 47 48 format!("{futures_section}\n\n{output_section}\n") 49 } 50 51 /// Runs a Leo test case provided as a string, with optional inlined module definitions. 52 /// 53 /// # Behavior 54 /// - If the source contains no `MODULE_SEPARATOR`, it is treated as a standalone ADL file and executed directly. 55 /// - If the source contains inlined modules separated by `MODULE_SEPARATOR`, it will: 56 /// - Split the input into a main source and its modules, 57 /// - Write each source to a temporary file structure, 58 /// - Compile and interpret them as a single ADL program. 59 /// 60 /// # Arguments 61 /// - `test`: The input ADL program as a string. Can include inlined modules using `MODULE_SEPARATOR`. 62 /// 63 /// # Returns 64 /// - A string containing either the result of interpretation, an error message, or "no value received". 65 /// 66 /// # Panics 67 /// - Panics on file I/O failure or if the Leo interpreter setup fails. 68 /// - Panics if the hardcoded private key is invalid. 69 fn runner_adl_test(test: &str) -> String { 70 if !test.contains(MODULE_SEPARATOR) { 71 // === Simple case: single source file === 72 create_session_if_not_set_then(|_| { 73 let tempdir = tempfile::tempdir().expect("tempdir"); 74 75 // Write source to temporary main.adl file 76 let filename = tempdir.path().join("main.adl"); 77 fs::write(&filename, test).expect("write failed"); 78 79 // Set up interpreter using testnet private key 80 let private_key: PrivateKey<TestnetV0> = 81 PrivateKey::from_str(TEST_PRIVATE_KEY).expect("should parse private key"); 82 83 let empty: [&PathBuf; 0] = []; 84 let mut interpreter = Interpreter::new( 85 &[(filename, vec![])], 86 empty, 87 private_key.to_string(), 88 0, 89 chrono::Utc::now().timestamp(), 90 NetworkName::AlphaTestnetV0, 91 ) 92 .expect("creating interpreter"); 93 94 run_and_format_output(&mut interpreter) 95 }) 96 } else { 97 // === Multi-module case === 98 create_session_if_not_set_then(|_| { 99 let tempdir = tempfile::tempdir().expect("tempdir"); 100 101 // === Step 1: Parse test source into main and modules === 102 let lines = test.lines().peekable(); 103 let mut main_source = String::new(); 104 let mut modules = Vec::new(); 105 106 let mut current_module_path: Option<String> = None; 107 let mut current_module_source = String::new(); 108 109 for line in lines { 110 if let Some(rest) = line.strip_prefix(MODULE_SEPARATOR) { 111 // Save previous module or main 112 if let Some(path) = current_module_path.take() { 113 modules.push((current_module_source.clone(), path)); 114 current_module_source.clear(); 115 } else { 116 main_source = current_module_source.clone(); 117 current_module_source.clear(); 118 } 119 120 // Prepare the new module path 121 let path = rest.trim().trim_end_matches(" --- //").to_string(); 122 current_module_path = Some(path); 123 } else { 124 current_module_source.push_str(line); 125 current_module_source.push('\n'); 126 } 127 } 128 129 // Save last module or main source 130 if let Some(path) = current_module_path { 131 modules.push((current_module_source.clone(), path)); 132 } else { 133 main_source = current_module_source; 134 } 135 136 // === Step 2: Write all source files into the temp directory === 137 let mut module_paths = Vec::new(); 138 139 // Write main source to main.adl 140 let main_path = tempdir.path().join("main.adl"); 141 std::fs::write(&main_path, main_source).expect("write main failed"); 142 143 // Write module files to appropriate relative paths 144 for (source, path) in modules { 145 let full_path = tempdir.path().join(&path); 146 147 // Ensure parent directories exist 148 if let Some(parent) = full_path.parent() { 149 std::fs::create_dir_all(parent).expect("create_dir_all failed"); 150 } 151 152 std::fs::write(&full_path, source).expect("write module failed"); 153 module_paths.push(full_path); 154 } 155 156 // === Step 3: Run interpreter on main() === 157 let private_key: PrivateKey<TestnetV0> = 158 PrivateKey::from_str(TEST_PRIVATE_KEY).expect("should parse private key"); 159 160 let empty: [&PathBuf; 0] = []; 161 162 let mut interpreter = Interpreter::new( 163 &[(main_path, module_paths)], 164 empty, 165 private_key.to_string(), 166 0, 167 chrono::Utc::now().timestamp(), 168 NetworkName::AlphaTestnetV0, 169 ) 170 .expect("creating interpreter"); 171 172 run_and_format_output(&mut interpreter) 173 }) 174 } 175 } 176 177 #[test] 178 #[serial] 179 fn test_interpreter() { 180 adl_test_framework::run_tests("interpreter-leo", runner_adl_test); 181 }