leo_lang/cli/commands/
test.rs1use super::*;
18
19use leo_ast::{NetworkName, TEST_PRIVATE_KEY};
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 #[clap(flatten)]
42 pub(crate) env_override: EnvOptions,
43}
44
45impl Command for LeoTest {
46 type Input = <LeoBuild as Command>::Output;
47 type Output = ();
48
49 fn log_span(&self) -> Span {
50 tracing::span!(tracing::Level::INFO, "Leo")
51 }
52
53 fn prelude(&self, context: Context) -> Result<Self::Input> {
54 let mut options = self.compiler_options.clone();
55 options.build_tests = true;
56 (LeoBuild { env_override: self.env_override.clone(), options }).execute(context)
57 }
58
59 fn apply(self, _: Context, input: Self::Input) -> Result<Self::Output> {
60 handle_test(self, input)
61 }
62}
63
64fn handle_test(command: LeoTest, package: Package) -> Result<()> {
65 let private_key = PrivateKey::<TestnetV0>::from_str(TEST_PRIVATE_KEY)?;
67
68 let leo_paths = collect_leo_paths(&package);
69 let aleo_paths = collect_aleo_paths(&package);
70
71 let (native_test_functions, interpreter_result) = leo_interpreter::find_and_run_tests(
72 &leo_paths,
73 &aleo_paths,
74 private_key.to_string(),
75 0u32,
76 chrono::Utc::now().timestamp(),
77 &command.test_name,
78 NetworkName::TestnetV0,
79 )?;
80
81 let program_name = package.manifest.program.strip_suffix(".aleo").unwrap();
83 let program_name_symbol = Symbol::intern(program_name);
84 let build_directory = package.build_directory();
85
86 let credits = Symbol::intern("credits");
87
88 let programs: Vec<run_with_ledger::Program> = package
90 .programs
91 .iter()
92 .filter_map(|program| {
93 if program.name == credits {
95 return None;
96 }
97 let bytecode = match &program.data {
98 ProgramData::Bytecode(c) => c.clone(),
99 ProgramData::SourcePath { .. } => {
100 let aleo_path = if program.name == program_name_symbol {
102 build_directory.join("main.aleo")
103 } else {
104 package.imports_directory().join(format!("{}.aleo", program.name))
105 };
106 fs::read_to_string(&aleo_path)
107 .unwrap_or_else(|e| panic!("Failed to read Aleo file at {}: {}", aleo_path.display(), e))
108 }
109 };
110 Some(run_with_ledger::Program { bytecode, name: program.name.to_string() })
111 })
112 .collect();
113
114 let should_fails: Vec<bool> = native_test_functions.iter().map(|test_function| test_function.should_fail).collect();
115 let cases: Vec<Vec<run_with_ledger::Case>> = native_test_functions
116 .into_iter()
117 .map(|test_function| {
118 vec![run_with_ledger::Case {
120 program_name: format!("{}.aleo", test_function.program),
121 function: test_function.function,
122 private_key: test_function.private_key,
123 input: Vec::new(),
124 }]
125 })
126 .collect();
127
128 let outcomes =
129 run_with_ledger::run_with_ledger(&run_with_ledger::Config { seed: 0, start_height: None, programs }, &cases)?
130 .into_iter()
131 .flatten()
132 .collect::<Vec<_>>();
133
134 let native_results: Vec<_> = outcomes
135 .into_iter()
136 .zip(should_fails)
137 .map(|(outcome, should_fail)| {
138 let message = match (&outcome.status, should_fail) {
139 (run_with_ledger::Status::Accepted, false) => None,
140 (run_with_ledger::Status::Accepted, true) => {
141 Some("Test succeeded when failure was expected.".to_string())
142 }
143 (_, true) => None,
144 (_, false) => Some(format!("{} -- {}", outcome.status, outcome.output)),
145 };
146 (outcome.program_name, outcome.function, message)
147 })
148 .collect();
149
150 let total = interpreter_result.iter().count() + native_results.len();
152 let total_passed = interpreter_result.iter().filter(|(_, test_result)| matches!(test_result, Ok(()))).count()
153 + native_results.iter().filter(|(_, _, x)| x.is_none()).count();
154
155 if total == 0 {
156 println!("No tests run.");
157 Ok(())
158 } else {
159 println!("{total_passed} / {total} tests passed.");
160 let failed = "FAILED".bold().red();
161 let passed = "PASSED".bold().green();
162 for (id, id_result) in interpreter_result.iter() {
163 let str_id = format!("{id}");
165 if let Err(err) = id_result {
166 println!("{failed}: {str_id:<30} | {err}");
167 } else {
168 println!("{passed}: {str_id}");
169 }
170 }
171
172 for (program, function, case_result) in native_results {
173 let str_id = format!("{program}/{function}");
174 if let Some(err_str) = case_result {
175 println!("{failed}: {str_id:<30} | {err_str}");
176 } else {
177 println!("{passed}: {str_id}");
178 }
179 }
180
181 Ok(())
182 }
183}