test_interpreter.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 ledger runs. 18 19 use adl_ast::{NetworkName, Stub, interpreter_value::Value}; 20 use adl_compiler::{Compiler, run}; 21 use adl_disassembler::disassemble_from_str; 22 use adl_errors::{BufferEmitter, Handler, Result}; 23 use adl_span::{Symbol, create_session_if_not_set_then, source_map::FileName}; 24 25 use snarkvm::prelude::{PrivateKey, TestnetV0}; 26 27 use indexmap::IndexMap; 28 use itertools::Itertools as _; 29 use std::{ 30 fs, 31 path::{Path, PathBuf}, 32 str::FromStr, 33 }; 34 use walkdir::WalkDir; 35 36 use crate::interpreter::{Interpreter, InterpreterAction}; 37 38 const PROGRAM_DELIMITER: &str = "// --- Next Program --- //"; 39 40 type CurrentNetwork = TestnetV0; 41 42 fn whole_compile(source: &str, handler: &Handler, import_stubs: IndexMap<Symbol, Stub>) -> Result<(String, String)> { 43 let mut compiler = Compiler::new( 44 None, 45 /* is_test (a Leo test) */ false, 46 handler.clone(), 47 "/fakedirectory-wont-use".into(), 48 None, 49 import_stubs, 50 NetworkName::AlphaTestnetV0, 51 ); 52 53 let filename = FileName::Custom("execution-test".into()); 54 55 let bytecode = compiler.compile(source, filename, &Vec::new())?; 56 57 Ok((bytecode, compiler.program_name.unwrap())) 58 } 59 60 fn parse_cases(source: &str) -> (Vec<run::Case>, Vec<String>) { 61 let mut cases: Vec<run::Case> = Vec::new(); 62 63 // Captures quote-delimited strings. 64 let re_input = regex::Regex::new(r#""([^"]+)""#).unwrap(); 65 66 for line in source.lines() { 67 if line.starts_with("[case]") { 68 cases.push(Default::default()); 69 } else if let Some(rest) = line.strip_prefix("program = ") { 70 cases.last_mut().unwrap().program_name = rest.trim_matches('"').into(); 71 } else if let Some(rest) = line.strip_prefix("function = ") { 72 cases.last_mut().unwrap().function = rest.trim_matches('"').into(); 73 } else if let Some(rest) = line.strip_prefix("private_key = ") { 74 cases.last_mut().unwrap().private_key = Some(rest.trim_matches('"').into()); 75 } else if let Some(rest) = line.strip_prefix("input = ") { 76 // Get quote-delimited strings. 77 cases.last_mut().unwrap().input = re_input.captures_iter(rest).map(|s| s[1].to_string()).collect(); 78 } 79 } 80 81 let sources = source.split(PROGRAM_DELIMITER).map(|s| s.trim().to_string()).collect(); 82 (cases, sources) 83 } 84 85 #[derive(Debug)] 86 pub struct TestResult { 87 ledger_result: Vec<Value>, 88 interpreter_result: Vec<Value>, 89 } 90 91 fn run_test(path: &Path, handler: &Handler, _buf: &BufferEmitter) -> Result<TestResult, ()> { 92 let source = fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read file {}: {e}.", path.display())); 93 let (cases, sources) = parse_cases(&source); 94 let mut import_stubs = IndexMap::new(); 95 let mut ledger_config = run::Config { seed: 2, start_height: None, programs: Vec::new() }; 96 97 let mut requires_ledger = false; 98 for source in &sources { 99 let (bytecode, name) = handler.extend_if_error(whole_compile(source, handler, import_stubs.clone()))?; 100 requires_ledger = bytecode.contains("async"); 101 102 let stub = handler 103 .extend_if_error(disassemble_from_str::<CurrentNetwork>(&name, &bytecode).map_err(|err| err.into()))?; 104 import_stubs.insert(Symbol::intern(&name), stub); 105 106 ledger_config.programs.push(run::Program { bytecode, name }); 107 } 108 109 // Extract only the outputs, ignoring status, execution, etc. 110 let outputs: Vec<Value> = if requires_ledger { 111 // Note. We wrap the cases in a slice to run them on a single ledger instance. 112 // This is just to be consistent with previous semantics. 113 handler 114 .extend_if_error(run::run_with_ledger(&ledger_config, std::slice::from_ref(&cases)))? 115 .into_iter() 116 .flatten() 117 .map(|exec| exec.outcome.output) // only extract the inner output 118 .collect() 119 } else { 120 handler 121 .extend_if_error(run::run_without_ledger(&ledger_config, &cases))? 122 .into_iter() 123 .map(|eval| eval.outcome.output) // only extract the inner output 124 .collect() 125 }; 126 127 let private_key = PrivateKey::<CurrentNetwork>::from_str(adl_ast::TEST_PRIVATE_KEY) 128 .expect("Should be able to parse private key."); 129 130 let tempdir = tempfile::tempdir().expect("tempdir"); 131 assert_eq!(sources.len(), 1, "For now we only support one program."); 132 let paths: Vec<PathBuf> = sources 133 .iter() 134 .map(|source| { 135 let path = tempdir.path().join("main.adl"); 136 fs::write(&path, source).expect("write failed"); 137 path 138 }) 139 .collect(); 140 let empty: [&PathBuf; 0] = []; 141 let mut interpreter = Interpreter::new( 142 &[(paths[0].clone(), Vec::new())], 143 empty, 144 private_key.to_string(), 145 0, 146 chrono::Utc::now().timestamp(), 147 NetworkName::AlphaTestnetV0, 148 ) 149 .expect("creating interpreter"); 150 let interpreter_result = handler.extend_if_error( 151 cases 152 .iter() 153 .map(|case| { 154 interpreter 155 .action(InterpreterAction::LeoInterpretOver(format!( 156 "{}/{}({})", 157 case.program_name, 158 case.function, 159 case.input.iter().format(", ") 160 ))) 161 .map(|opt_value| opt_value.unwrap_or(Value::make_unit())) 162 }) 163 .collect::<Result<Vec<_>>>(), 164 )?; 165 Ok(TestResult { ledger_result: outputs, interpreter_result }) 166 } 167 168 #[test] 169 fn test_interpreter() { 170 let tests_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "..", "tests", "tests", "interpreter"] 171 .iter() 172 .collect::<PathBuf>() 173 .canonicalize() 174 .unwrap(); 175 176 let filter_string = std::env::var("TEST_FILTER").unwrap_or_default(); 177 178 let paths: Vec<PathBuf> = WalkDir::new(&tests_dir) 179 .into_iter() 180 .flatten() 181 .filter_map(|entry| { 182 let path = entry.path(); 183 184 let path_str = path.to_str().unwrap_or_else(|| panic!("Path not unicode: {}.", path.display())); 185 186 if !path_str.contains(&filter_string) || !path_str.ends_with(".adl") { 187 return None; 188 } 189 190 Some(path.into()) 191 }) 192 .collect(); 193 194 create_session_if_not_set_then(|_| { 195 for path in paths.iter() { 196 let mut test_result = { 197 let buf = BufferEmitter::new(); 198 let handler = Handler::new(buf.clone()); 199 match run_test(path, &handler, &buf) { 200 Ok(result) => result, 201 Err(..) => { 202 let errs = buf.extract_errs(); 203 panic!("{} {} ", errs.len(), errs); 204 } 205 } 206 }; 207 208 // Clear the `id`, for comparison against what snarkvm produced. 209 test_result.interpreter_result.iter_mut().for_each(|value| value.id = None); 210 if test_result.ledger_result != test_result.interpreter_result { 211 println!("TEST {} Failed", path.display()); 212 println!("LEDGER: {:?}", test_result.ledger_result); 213 println!("INTERPRETER: {:?}", test_result.interpreter_result); 214 panic!("Test failure"); 215 } 216 } 217 }) 218 }