test_execution.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 use crate::run; 18 19 use adl_disassembler::disassemble_from_str; 20 use adl_errors::{BufferEmitter, Handler, Result}; 21 use adl_span::{Symbol, create_session_if_not_set_then}; 22 23 use snarkvm::prelude::TestnetV0; 24 25 use indexmap::IndexMap; 26 use itertools::Itertools as _; 27 use serial_test::serial; 28 use std::fmt::Write as _; 29 30 type CurrentNetwork = TestnetV0; 31 32 // Execution test configuration. 33 #[derive(Debug)] 34 struct Config { 35 seed: u64, 36 start_height: Option<u32>, 37 sources: Vec<String>, 38 } 39 40 impl Default for Config { 41 fn default() -> Self { 42 Self { seed: 1234567890, start_height: None, sources: Vec::new() } 43 } 44 } 45 46 fn execution_run_test(config: &Config, cases: &[run::Case], handler: &Handler) -> Result<String> { 47 let mut import_stubs = IndexMap::new(); 48 49 let mut ledger_config = run::Config { seed: config.seed, start_height: config.start_height, programs: Vec::new() }; 50 51 let mut requires_ledger = false; 52 53 // Compile each source file. 54 for source in &config.sources { 55 let (bytecode, name) = super::test_utils::whole_compile(source, handler, import_stubs.clone())?; 56 requires_ledger = bytecode.contains("async"); 57 58 let stub = disassemble_from_str::<CurrentNetwork>(&name, &bytecode)?; 59 import_stubs.insert(Symbol::intern(&name), stub); 60 61 ledger_config.programs.push(run::Program { bytecode, name }); 62 } 63 64 let mut result = ledger_config 65 .programs 66 .clone() 67 .into_iter() 68 .map(|program| program.bytecode) 69 .format(&format!("{}\n", super::test_utils::PROGRAM_DELIMITER)) 70 .to_string(); 71 72 if requires_ledger { 73 // Note: We wrap cases in a slice to run them all in one ledger instance. 74 let outcomes = 75 run::run_with_ledger(&ledger_config, &[cases.to_vec()])?.into_iter().flatten().collect::<Vec<_>>(); 76 77 assert_eq!(outcomes.len(), cases.len()); 78 79 for outcome in outcomes { 80 write!(result, "verified: {}\nstatus: {}\n", outcome.verified, outcome.status).unwrap(); 81 writeln!(result, "{}\n", outcome.execution).unwrap(); 82 } 83 } else { 84 let outcomes = run::run_without_ledger(&ledger_config, cases)?; 85 assert_eq!(outcomes.len(), cases.len()); 86 87 for outcome in outcomes { 88 write!(result, "status: {}\noutput: {}\n", outcome.status, outcome.outcome.output).unwrap(); 89 } 90 } 91 92 Ok(result) 93 } 94 95 fn execution_runner(source: &str) -> String { 96 let buf = BufferEmitter::new(); 97 let handler = Handler::new(buf.clone()); 98 99 let mut config = Config::default(); 100 let mut cases = Vec::<run::Case>::new(); 101 102 // Captures quote-delimited strings. 103 let re_input = regex::Regex::new(r#""([^"]+)""#).unwrap(); 104 105 for line in source.lines() { 106 if line.starts_with("[case]") { 107 cases.push(Default::default()); 108 } else if let Some(rest) = line.strip_prefix("program = ") { 109 cases.last_mut().unwrap().program_name = rest.trim_matches('"').into(); 110 } else if let Some(rest) = line.strip_prefix("function = ") { 111 cases.last_mut().unwrap().function = rest.trim_matches('"').into(); 112 } else if let Some(rest) = line.strip_prefix("private_key = ") { 113 cases.last_mut().unwrap().private_key = Some(rest.trim_matches('"').into()); 114 } else if let Some(rest) = line.strip_prefix("input = ") { 115 // Get quote-delimited strings. 116 cases.last_mut().unwrap().input = re_input.captures_iter(rest).map(|s| s[1].to_string()).collect(); 117 } else if let Some(rest) = line.strip_prefix("seed = ") { 118 config.seed = rest.parse::<u64>().unwrap(); 119 } else if let Some(rest) = line.strip_prefix("start_height = ") { 120 config.start_height = Some(rest.parse::<u32>().unwrap()) 121 } 122 } 123 124 // Split the sources and add them to the config. 125 config.sources = source.split(super::test_utils::PROGRAM_DELIMITER).map(|s| s.trim().to_string()).collect(); 126 127 create_session_if_not_set_then(|_| match execution_run_test(&config, &cases, &handler) { 128 Ok(s) => s, 129 Err(e) => { 130 format!("Error while running execution tests:\n{e}\n\nErrors:\n{}", buf.extract_errs()) 131 } 132 }) 133 } 134 135 #[test] 136 #[serial] 137 fn test_execution() { 138 adl_test_framework::run_tests("execution", execution_runner); 139 }