/ compiler / compiler / src / test_execution.rs
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  }