leo_lang/cli/commands/
test.rs1use super::*;
18
19use leo_ast::NetworkName;
20use leo_compiler::run_with_ledger;
21use leo_package::{Package, ProgramData};
22use leo_span::Symbol;
23
24use snarkvm::prelude::TestnetV0;
25
26use colored::Colorize as _;
27use std::fs;
28
29#[derive(Parser, Debug)]
31pub struct LeoTest {
32 #[clap(
33 name = "TEST_NAME",
34 help = "If specified, run only tests whose qualified name matches against this string.",
35 default_value = ""
36 )]
37 pub(crate) test_name: String,
38
39 #[clap(flatten)]
40 pub(crate) compiler_options: BuildOptions,
41}
42
43impl Command for LeoTest {
44 type Input = <LeoBuild as Command>::Output;
45 type Output = ();
46
47 fn log_span(&self) -> Span {
48 tracing::span!(tracing::Level::INFO, "Leo")
49 }
50
51 fn prelude(&self, context: Context) -> Result<Self::Input> {
52 let mut options = self.compiler_options.clone();
53 options.build_tests = true;
54 (LeoBuild { env_override: Default::default(), options }).execute(context)
55 }
56
57 fn apply(self, _: Context, input: Self::Input) -> Result<Self::Output> {
58 handle_test(self, input)
59 }
60}
61
62fn handle_test(command: LeoTest, package: Package) -> Result<()> {
63 let private_key = get_private_key::<TestnetV0>(&None)?;
65 let address = Address::try_from(&private_key)?;
66
67 let leo_paths = collect_leo_paths(&package);
68 let aleo_paths = collect_aleo_paths(&package);
69
70 let (native_test_functions, interpreter_result) = leo_interpreter::find_and_run_tests(
71 &leo_paths,
72 &aleo_paths,
73 address.into(),
74 0u32,
75 &command.test_name,
76 NetworkName::TestnetV0,
77 )?;
78
79 let program_name = package.manifest.program.strip_suffix(".aleo").unwrap();
82 let program_name_symbol = Symbol::intern(program_name);
83 let build_directory = package.build_directory();
84
85 let credits = Symbol::intern("credits");
86
87 let programs: Vec<run_with_ledger::Program> = package
89 .programs
90 .iter()
91 .filter_map(|program| {
92 if program.name == credits {
94 return None;
95 }
96 let bytecode = match &program.data {
97 ProgramData::Bytecode(c) => c.clone(),
98 ProgramData::SourcePath { .. } => {
99 let aleo_path = if program.name == program_name_symbol {
101 build_directory.join("main.aleo")
102 } else {
103 package.imports_directory().join(format!("{}.aleo", program.name))
104 };
105 fs::read_to_string(&aleo_path)
106 .unwrap_or_else(|e| panic!("Failed to read Aleo file at {}: {}", aleo_path.display(), e))
107 }
108 };
109 Some(run_with_ledger::Program { bytecode, name: program.name.to_string() })
110 })
111 .collect();
112
113 let should_fails: Vec<bool> = native_test_functions.iter().map(|test_function| test_function.should_fail).collect();
114 let cases: Vec<run_with_ledger::Case> = native_test_functions
115 .into_iter()
116 .map(|test_function| run_with_ledger::Case {
117 program_name: format!("{}.aleo", test_function.program),
118 function: test_function.function,
119 private_key: test_function.private_key,
120 input: Vec::new(),
121 })
122 .collect();
123
124 let (handler, buf) = Handler::new_with_buf();
125
126 let outcomes = run_with_ledger::run_with_ledger(
127 &run_with_ledger::Config { seed: 0, start_height: None, programs },
128 &cases,
129 &handler,
130 &buf,
131 )?;
132
133 let native_results: Vec<Option<String>> = outcomes
134 .into_iter()
135 .zip(should_fails)
136 .map(|(outcome, should_fail)| match (&outcome.status, should_fail) {
137 (run_with_ledger::Status::Accepted, false) => None,
138 (run_with_ledger::Status::Accepted, true) => Some("Test succeeded when failure was expected.".to_string()),
139 (_, true) => None,
140 (_, false) => Some(format!("{} -- {}", outcome.status, outcome.errors)),
141 })
142 .collect();
143
144 let total = interpreter_result.iter().count() + native_results.len();
146 let total_passed = interpreter_result.iter().filter(|(_, test_result)| matches!(test_result, Ok(()))).count()
147 + native_results.iter().filter(|x| x.is_none()).count();
148
149 if total == 0 {
150 println!("No tests run.");
151 Ok(())
152 } else {
153 println!("{total_passed} / {total} tests passed.");
154 let failed = "FAILED".bold().red();
155 let passed = "PASSED".bold().green();
156 for (id, id_result) in interpreter_result.iter() {
157 let str_id = format!("{id}");
159 if let Err(err) = id_result {
160 println!("{failed}: {str_id:<30} | {err}");
161 } else {
162 println!("{passed}: {str_id}");
163 }
164 }
165
166 for (case, case_result) in cases.iter().zip(native_results) {
167 let str_id = format!("{}/{}", case.program_name, case.function);
168 if let Some(err_str) = case_result {
169 println!("{failed}: {str_id:<30} | {err_str}");
170 } else {
171 println!("{passed}: {str_id}");
172 }
173 }
174
175 Ok(())
176 }
177}