use leo_ast::{NodeBuilder, Stub};
use leo_disassembler::disassemble_from_str;
use leo_errors::{BufferEmitter, Handler};
use leo_span::{Symbol, create_session_if_not_set_then};
use snarkvm::prelude::{Process, TestnetV0};
use indexmap::IndexMap;
use itertools::Itertools as _;
use serial_test::serial;
use std::rc::Rc;
#[derive(PartialEq)]
enum StubType {
FromLeo,
FromAleo,
}
fn run_test(stub_type: StubType, test: &str, handler: &Handler, node_builder: &Rc<NodeBuilder>) -> Result<String, ()> {
let mut process = Process::<TestnetV0>::load().unwrap();
let mut import_stubs = IndexMap::new();
let mut bytecodes = Vec::<String>::new();
let sources: Vec<&str> = test.split(super::test_utils::PROGRAM_DELIMITER).collect();
let (last, rest) = sources.split_last().expect("non-empty sources");
for source in rest {
if let Some(aleo_source) = super::test_utils::extract_aleo_stub_header(source) {
let program = handler.extend_if_error(disassemble_from_str::<TestnetV0>("", aleo_source, &mut process))?;
let name = program.stub_id.as_symbol();
import_stubs.insert(name, program.into());
continue;
}
if let Some((library_name, library_source)) = super::test_utils::extract_library_header(source) {
let library = handler.extend_if_error(super::test_utils::parse_library(
&library_name,
library_source,
handler,
node_builder,
))?;
import_stubs.insert(Symbol::intern(&library_name), library.into());
if handler.err_count() != 0 {
return Err(());
}
continue;
}
match stub_type {
StubType::FromLeo => {
let (program, program_name) = handler.extend_if_error(super::test_utils::parse_program(
source,
handler,
node_builder,
import_stubs.clone(),
))?;
import_stubs.insert(Symbol::intern(&program_name), program.into());
if handler.err_count() != 0 {
return Err(());
}
}
StubType::FromAleo => {
let program_name_for_parents =
handler.extend_if_error(super::test_utils::extract_program_name(source, handler))?;
let program_symbol = Symbol::intern(&program_name_for_parents);
for stub in import_stubs.values_mut() {
if stub.is_library() {
stub.add_parent(program_symbol);
}
}
let (programs, program_name) = handler.extend_if_error(super::test_utils::whole_compile(
source,
handler,
node_builder,
import_stubs.clone(),
))?;
let program = handler.extend_if_error(disassemble_from_str::<TestnetV0>(
&program_name,
&programs.primary.bytecode,
&mut process,
))?;
import_stubs.insert(Symbol::intern(&program_name), program.into());
if handler.err_count() != 0 {
return Err(());
}
bytecodes.push(programs.primary.bytecode);
}
}
}
let final_program_name = handler.extend_if_error(super::test_utils::extract_program_name(last, handler))?;
let final_symbol = Symbol::intern(&final_program_name);
{
let symbols: Vec<Symbol> = import_stubs.keys().copied().collect();
for (i, symbol) in symbols.iter().enumerate() {
if let Some(Stub::FromLibrary { parents, .. }) = import_stubs.get_mut(symbol) {
parents.extend(symbols[i + 1..].iter().copied());
parents.insert(final_symbol);
}
}
}
let (compiled, _program_name) =
handler.extend_if_error(super::test_utils::whole_compile(last, handler, node_builder, import_stubs.clone()))?;
if handler.err_count() != 0 {
return Err(());
}
if stub_type == StubType::FromLeo {
for import in &compiled.imports {
handler.extend_if_error(disassemble_from_str::<TestnetV0>("", &import.bytecode, &mut process))?;
bytecodes.push(import.bytecode.clone());
}
}
let primary_bytecode = compiled.primary.bytecode.clone();
handler.extend_if_error(disassemble_from_str::<TestnetV0>("", &primary_bytecode, &mut process))?;
bytecodes.push(primary_bytecode);
Ok(bytecodes.iter().format(&format!("{}\n", super::test_utils::PROGRAM_DELIMITER)).to_string())
}
fn runner(source: &str) -> String {
let from_leo_output = run_with_stub(StubType::FromLeo, source);
let from_aleo_output = run_with_stub(StubType::FromAleo, source);
if from_leo_output != from_aleo_output {
format!(
"{from_leo_output}\n\n\
---\n\
Note: Treating dependencies as Aleo produces different results:\n\n\
{from_aleo_output}"
)
} else {
from_leo_output
}
}
fn run_with_stub(stub: StubType, source: &str) -> String {
let buf = BufferEmitter::new();
let handler = Handler::new(buf.clone());
let node_builder = Rc::new(NodeBuilder::default());
create_session_if_not_set_then(|_| {
match run_test(stub, source, &handler, &node_builder) {
Ok(output) => {
format!("{}{}", buf.extract_warnings(), output)
}
Err(()) => {
format!("{}{}", buf.extract_errs(), buf.extract_warnings())
}
}
})
}
#[test]
#[serial]
fn test_compiler() {
leo_test_framework::run_tests("compiler", runner);
}
fn abi_runner(source: &str) -> String {
let buf = BufferEmitter::new();
let handler = Handler::new(buf.clone());
let node_builder = Rc::new(NodeBuilder::default());
create_session_if_not_set_then(|_| {
let mut import_stubs = IndexMap::new();
let sources: Vec<&str> = source.split(super::test_utils::PROGRAM_DELIMITER).collect();
let (last, rest) = sources.split_last().expect("non-empty sources");
for dep_source in rest {
if let Some((library_name, library_source)) = super::test_utils::extract_library_header(dep_source) {
let library =
match super::test_utils::parse_library(&library_name, library_source, &handler, &node_builder) {
Ok(lib) => lib,
Err(_) => return format!("{}{}", buf.extract_errs(), buf.extract_warnings()),
};
import_stubs.insert(Symbol::intern(&library_name), library.into());
continue;
}
let (program, program_name) =
match super::test_utils::parse_program(dep_source, &handler, &node_builder, import_stubs.clone()) {
Ok(p) => p,
Err(_) => return format!("{}{}", buf.extract_errs(), buf.extract_warnings()),
};
import_stubs.insert(Symbol::intern(&program_name), program.into());
}
let compiled = match super::test_utils::whole_compile(last, &handler, &node_builder, import_stubs) {
Ok((compiled, _name)) => compiled,
Err(_) => return format!("{}{}", buf.extract_errs(), buf.extract_warnings()),
};
let mut interfaces = compiled.interfaces;
interfaces.sort_by(|a, b| {
let owner_a = match &a.owner {
leo_abi::interfaces::InterfaceOwner::Local => "",
leo_abi::interfaces::InterfaceOwner::External { owner_program } => owner_program,
};
let owner_b = match &b.owner {
leo_abi::interfaces::InterfaceOwner::Local => "",
leo_abi::interfaces::InterfaceOwner::External { owner_program } => owner_program,
};
owner_a.cmp(owner_b).then_with(|| a.abi.path.cmp(&b.abi.path))
});
let mut output = String::new();
for (i, ci) in interfaces.iter().enumerate() {
if i > 0 {
output.push('\n');
}
let owner_label = match &ci.owner {
leo_abi::interfaces::InterfaceOwner::Local => "local".to_string(),
leo_abi::interfaces::InterfaceOwner::External { owner_program } => owner_program.clone(),
};
output.push_str(&format!("// owner: {owner_label}\n"));
output.push_str(&serde_json::to_string_pretty(&ci.abi).unwrap());
output.push('\n');
}
format!("{}{output}", buf.extract_warnings())
})
}
#[test]
#[serial]
fn test_interface_abi() {
leo_test_framework::run_tests("interface_abi", abi_runner);
}