Update compiler test framework

This commit is contained in:
Pranav Gaddamadugu 2023-04-12 11:40:00 -07:00
parent acc358bbb4
commit 98bbcacebb
3 changed files with 180 additions and 125 deletions

View File

@ -26,10 +26,11 @@ use leo_test_framework::{
use snarkvm::prelude::*; use snarkvm::prelude::*;
use crate::utilities::{get_cwd_option, hash_asts, hash_content, setup_build_directory}; use crate::utilities::{get_build_options, get_cwd_option, hash_asts, hash_content, setup_build_directory};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::Value; use serde_yaml::Value;
use std::{fs, path::Path, rc::Rc}; use std::{fs, path::Path, rc::Rc};
use leo_compiler::{CompilerOptions, OutputOptions};
struct CompileNamespace; struct CompileNamespace;
@ -61,37 +62,60 @@ fn run_test(test: Test, handler: &Handler) -> Result<Value, ()> {
// Check for CWD option: // Check for CWD option:
let cwd = get_cwd_option(&test); let cwd = get_cwd_option(&test);
// Parse the program. // Extract the compiler build configurations from the config file.
let mut parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd))?; let build_options = get_build_options(&test.config);
// Compile the program to bytecode. let mut outputs = Vec::with_capacity(build_options.len());
let program_name = format!("{}.{}", parsed.program_name, parsed.network);
let bytecode = handler.extend_if_error(compile_and_process(&mut parsed))?;
// Set up the build directory. for build in build_options {
let package = setup_build_directory(&program_name, &bytecode, handler)?; let compiler_options = CompilerOptions {
build,
output: OutputOptions {
spans_enabled: false,
initial_input_ast: true,
initial_ast: true,
unrolled_ast: true,
ssa_ast: true,
flattened_ast: true,
inlined_ast: true,
dce_ast: true,
}
};
// Get the program process and check all instructions. // Parse the program.
handler.extend_if_error(package.get_process().map_err(LeoError::Anyhow))?; let mut parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd.clone(), Some(compiler_options)))?;
// Hash the ast files. // Compile the program to bytecode.
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, inlined_ast, dce_ast) = hash_asts(); let program_name = format!("{}.{}", parsed.program_name, parsed.network);
let bytecode = handler.extend_if_error(compile_and_process(&mut parsed))?;
// Clean up the output directory. // Set up the build directory.
if fs::read_dir("/tmp/output").is_ok() { let package = setup_build_directory(&program_name, &bytecode, handler)?;
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
// Get the program process and check all instructions.
handler.extend_if_error(package.get_process().map_err(LeoError::Anyhow))?;
// Hash the ast files.
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, inlined_ast, dce_ast) = hash_asts();
// Clean up the output directory.
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
}
let final_output = CompileOutput {
initial_ast,
unrolled_ast,
ssa_ast,
flattened_ast,
inlined_ast,
dce_ast,
bytecode: hash_content(&bytecode),
};
outputs.push(final_output);
} }
Ok(serde_yaml::to_value(outputs).expect("serialization failed"))
let final_output = CompileOutput {
initial_ast,
unrolled_ast,
ssa_ast,
flattened_ast,
inlined_ast,
dce_ast,
bytecode: hash_content(&bytecode),
};
Ok(serde_yaml::to_value(final_output).expect("serialization failed"))
} }
struct TestRunner; struct TestRunner;

View File

@ -26,7 +26,7 @@ use utilities::{
Network, Network,
}; };
use crate::utilities::{hash_asts, hash_content}; use crate::utilities::{get_build_options, hash_asts, hash_content};
use leo_errors::emitter::Handler; use leo_errors::emitter::Handler;
use leo_span::symbol::create_session_if_not_set_then; use leo_span::symbol::create_session_if_not_set_then;
@ -42,6 +42,7 @@ use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::Value; use serde_yaml::Value;
use std::{collections::BTreeMap, fs, path::Path, rc::Rc}; use std::{collections::BTreeMap, fs, path::Path, rc::Rc};
use leo_compiler::{CompilerOptions, OutputOptions};
// TODO: Evaluate namespace. // TODO: Evaluate namespace.
struct ExecuteNamespace; struct ExecuteNamespace;
@ -81,103 +82,125 @@ fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Va
// Check for CWD option: // Check for CWD option:
let cwd = get_cwd_option(&test); let cwd = get_cwd_option(&test);
// Parse the program. // Extract the compiler build configurations from the config file.
let mut parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd))?; let build_options = get_build_options(&test.config);
// Compile the program to bytecode. let mut outputs = Vec::with_capacity(build_options.len());
let program_name = format!("{}.{}", parsed.program_name, parsed.network);
let bytecode = handler.extend_if_error(compile_and_process(&mut parsed))?;
// Extract the cases from the test config. for build in build_options {
let all_cases = let compiler_options = CompilerOptions {
test.config.extra.get("cases").expect("An `Execute` config must have a `cases` field.").as_mapping().unwrap(); build,
output: OutputOptions {
spans_enabled: false,
initial_input_ast: true,
initial_ast: true,
unrolled_ast: true,
ssa_ast: true,
flattened_ast: true,
inlined_ast: true,
dce_ast: true,
}
};
// Initialize a map for the expected results. // Parse the program.
let mut results = BTreeMap::new(); let mut parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd.clone(), Some(compiler_options)))?;
// Setup the build directory. // Compile the program to bytecode.
let package = setup_build_directory(&program_name, &bytecode, handler)?; let program_name = format!("{}.{}", parsed.program_name, parsed.network);
let bytecode = handler.extend_if_error(compile_and_process(&mut parsed))?;
// Initialize an rng. // Extract the cases from the test config.
let rng = &mut rand::thread_rng(); let all_cases =
test.config.extra.get("cases").expect("An `Execute` config must have a `cases` field.").as_mapping().unwrap();
// Run each test case for each function. // Initialize a map for the expected results.
for (function_name, function_cases) in all_cases { let mut results = BTreeMap::new();
let function_name = Identifier::from_str(function_name.as_str().unwrap()).unwrap();
let cases = function_cases.as_sequence().unwrap();
let mut function_results = Vec::with_capacity(cases.len());
for case in cases { // Setup the build directory.
let case = case.as_mapping().unwrap(); let package = setup_build_directory(&program_name, &bytecode, handler)?;
let inputs: Vec<_> = case
.get(&Value::from("input"))
.unwrap()
.as_sequence()
.unwrap()
.iter()
.map(|input| console::program::Value::<Network>::from_str(input.as_str().unwrap()).unwrap())
.collect();
let input_string = format!("[{}]", inputs.iter().map(|input| input.to_string()).join(", "));
// TODO: Add support for custom config like custom private keys. // Initialize an rng.
// Execute the program and get the outputs. let rng = &mut rand::thread_rng();
let output_string = match package.run::<Aleo, _>(
None, // Run each test case for each function.
package.manifest_file().development_private_key(), for (function_name, function_cases) in all_cases {
function_name, let function_name = Identifier::from_str(function_name.as_str().unwrap()).unwrap();
&inputs, let cases = function_cases.as_sequence().unwrap();
rng, let mut function_results = Vec::with_capacity(cases.len());
) {
Ok((response, _, _, _)) => format!( for case in cases {
"[{}]", let case = case.as_mapping().unwrap();
response let inputs: Vec<_> = case
.outputs() .get(&Value::from("input"))
.iter() .unwrap()
.map(|output| { .as_sequence()
match output { .unwrap()
// Remove the `_nonce` from the record string. .iter()
console::program::Value::Record(record) => { .map(|input| console::program::Value::<Network>::from_str(input.as_str().unwrap()).unwrap())
let pattern = Regex::new(r"_nonce: \d+group.public").unwrap(); .collect();
pattern.replace(&record.to_string(), "").to_string() let input_string = format!("[{}]", inputs.iter().map(|input| input.to_string()).join(", "));
// TODO: Add support for custom config like custom private keys.
// Execute the program and get the outputs.
let output_string = match package.run::<Aleo, _>(
None,
package.manifest_file().development_private_key(),
function_name,
&inputs,
rng,
) {
Ok((response, _, _, _)) => format!(
"[{}]",
response
.outputs()
.iter()
.map(|output| {
match output {
// Remove the `_nonce` from the record string.
console::program::Value::Record(record) => {
let pattern = Regex::new(r"_nonce: \d+group.public").unwrap();
pattern.replace(&record.to_string(), "").to_string()
}
_ => output.to_string(),
} }
_ => output.to_string(), })
} .join(", ")
}) ),
.join(", ") Err(err) => format!("SnarkVMError({err})"),
), };
Err(err) => format!("SnarkVMError({err})"),
};
// Store the inputs and outputs in a map. // Store the inputs and outputs in a map.
let mut result = BTreeMap::new(); let mut result = BTreeMap::new();
result.insert("input".to_string(), input_string); result.insert("input".to_string(), input_string);
result.insert("output".to_string(), output_string); result.insert("output".to_string(), output_string);
// Add the hashes of the inputs and outputs to the function results. // Add the hashes of the inputs and outputs to the function results.
function_results.push(result); function_results.push(result);
}
results.insert(function_name.to_string(), function_results);
} }
results.insert(function_name.to_string(), function_results);
// Hash the ast files.
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, inlined_ast, dce_ast) = hash_asts();
// Clean up the output directory.
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
}
let final_output = ExecuteOutput {
initial_ast,
unrolled_ast,
ssa_ast,
flattened_ast,
inlined_ast,
dce_ast,
bytecode: hash_content(&bytecode),
results,
};
outputs.push(final_output);
} }
Ok(serde_yaml::to_value(outputs).expect("serialization failed"))
// Hash the ast files.
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, inlined_ast, dce_ast) = hash_asts();
// Clean up the output directory.
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
}
let final_output = ExecuteOutput {
initial_ast,
unrolled_ast,
ssa_ast,
flattened_ast,
inlined_ast,
dce_ast,
bytecode: hash_content(&bytecode),
results,
};
Ok(serde_yaml::to_value(final_output).expect("serialization failed"))
} }
struct TestRunner; struct TestRunner;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>. // along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use leo_compiler::{Compiler, CompilerOptions}; use leo_compiler::{BuildOptions, Compiler, CompilerOptions};
use leo_errors::{ use leo_errors::{
emitter::{Buffer, Emitter, Handler}, emitter::{Buffer, Emitter, Handler},
LeoError, LeoError,
@ -34,6 +34,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
}; };
use leo_test_framework::test::TestConfig;
pub type Network = Testnet3; pub type Network = Testnet3;
#[allow(unused)] #[allow(unused)]
@ -62,6 +63,22 @@ pub fn get_cwd_option(test: &Test) -> Option<PathBuf> {
}) })
} }
pub fn get_build_options(test_config: &TestConfig) -> Vec<BuildOptions> {
match test_config.extra.get("configs") {
Some(configs) => {
// Parse the sequence of compiler configurations.
configs.as_sequence().unwrap().iter().map(|config| {
let config = config.as_mapping().unwrap();
assert_eq!(config.len(), 1, "A compiler configuration must have exactly one key-value pair. e.g. `dce_enabled`: true");
BuildOptions {
dce_enabled: config.get(&serde_yaml::Value::String("dce_enabled".to_string())).expect("Expected key `dce_enabled`").as_bool().unwrap(),
}
}).collect()
}
None => vec![ BuildOptions { dce_enabled: true } ],
}
}
pub fn setup_build_directory(program_name: &str, bytecode: &String, handler: &Handler) -> Result<Package<Network>, ()> { pub fn setup_build_directory(program_name: &str, bytecode: &String, handler: &Handler) -> Result<Package<Network>, ()> {
// Initialize a temporary directory. // Initialize a temporary directory.
let directory = temp_dir(); let directory = temp_dir();
@ -85,7 +102,7 @@ pub fn setup_build_directory(program_name: &str, bytecode: &String, handler: &Ha
handler.extend_if_error(Package::<Testnet3>::open(&directory).map_err(LeoError::Anyhow)) handler.extend_if_error(Package::<Testnet3>::open(&directory).map_err(LeoError::Anyhow))
} }
pub fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_> { pub fn new_compiler(handler: &Handler, main_file_path: PathBuf, compiler_options: Option<CompilerOptions>) -> Compiler<'_> {
let output_dir = PathBuf::from("/tmp/output/"); let output_dir = PathBuf::from("/tmp/output/");
fs::create_dir_all(output_dir.clone()).unwrap(); fs::create_dir_all(output_dir.clone()).unwrap();
@ -95,17 +112,7 @@ pub fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_>
handler, handler,
main_file_path, main_file_path,
output_dir, output_dir,
Some(CompilerOptions { compiler_options
spans_enabled: false,
dce_enabled: true,
initial_input_ast: true,
initial_ast: true,
unrolled_ast: true,
ssa_ast: true,
flattened_ast: true,
inlined_ast: true,
dce_ast: true,
}),
) )
} }
@ -113,8 +120,9 @@ pub fn parse_program<'a>(
handler: &'a Handler, handler: &'a Handler,
program_string: &str, program_string: &str,
cwd: Option<PathBuf>, cwd: Option<PathBuf>,
compiler_options: Option<CompilerOptions>,
) -> Result<Compiler<'a>, LeoError> { ) -> Result<Compiler<'a>, LeoError> {
let mut compiler = new_compiler(handler, cwd.clone().unwrap_or_else(|| "compiler-test".into())); let mut compiler = new_compiler(handler, cwd.clone().unwrap_or_else(|| "compiler-test".into()), compiler_options);
let name = cwd.map_or_else(|| FileName::Custom("compiler-test".into()), FileName::Real); let name = cwd.map_or_else(|| FileName::Custom("compiler-test".into()), FileName::Real);
compiler.parse_program_from_string(program_string, name)?; compiler.parse_program_from_string(program_string, name)?;