/ interpreter / src / test.rs
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  }