// Copyright (C) 2019-2021 Aleo Systems Inc. // This file is part of the Leo library. // The Leo library is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // The Leo library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . use std::path::{Path, PathBuf}; use leo_asg::*; use leo_synthesizer::{CircuitSynthesizer, SerializedCircuit, SummarizedCircuit}; use leo_test_framework::{ runner::{Namespace, ParseType, Runner}, Test, }; use serde_yaml::Value; use snarkvm_curves::{bls12_377::Bls12_377, edwards_bls12::Fq}; use leo_compiler::{compiler::Compiler, errors::CompilerError, targets::edwards_bls12::EdwardsGroupType, Output}; pub type EdwardsTestCompiler = Compiler<'static, Fq, EdwardsGroupType>; // pub type EdwardsConstrainedValue = ConstrainedValue<'static, Fq, EdwardsGroupType>; //convenience function for tests, leaks memory pub(crate) fn make_test_context() -> AsgContext<'static> { let allocator = Box::leak(Box::new(new_alloc_context())); new_context(allocator) } fn new_compiler(path: PathBuf) -> EdwardsTestCompiler { let program_name = "test".to_string(); let output_dir = PathBuf::from("/output/"); EdwardsTestCompiler::new(program_name, path, output_dir, make_test_context(), None) } pub(crate) fn parse_program(program_string: &str, cwd: PathBuf) -> Result { let mut compiler = new_compiler(cwd); compiler.parse_program_from_string(program_string)?; Ok(compiler) } struct CompileNamespace; #[derive(serde::Deserialize, serde::Serialize)] struct OutputItem { pub input_file: String, pub output: Output, } #[derive(serde::Deserialize, serde::Serialize)] struct CompileOutput { pub circuit: SummarizedCircuit, pub output: Vec, } impl Namespace for CompileNamespace { fn parse_type(&self) -> ParseType { ParseType::Whole } fn run_test(&self, test: Test) -> Result { // Check for CWD option: // ``` cwd: import ``` // When set, uses different working directory for current file. // If not, uses file path as current working directory. let cwd = test .config .get("cwd") .map(|val| { let mut cwd = test.path.clone(); cwd.pop(); cwd.join(&val.as_str().unwrap()) }) .unwrap_or(test.path.clone()); let parsed = parse_program(&test.content, cwd).map_err(|x| x.to_string())?; // (name, content) let mut inputs = vec![]; if let Some(input) = test.config.get("inputs") { if let Value::Sequence(field) = input { for map in field { for (name, value) in map.as_mapping().unwrap().iter() { // Try to parse string from 'inputs' map, else fail let value = if let serde_yaml::Value::String(value) = value { value } else { return Err("Expected string in 'inputs' map".to_string()); }; inputs.push((name.as_str().unwrap().to_string(), value.clone())); } } } } if let Some(input) = test.config.get("input_file") { let input_file: PathBuf = test.path.parent().expect("no test parent dir").into(); if let Some(name) = input.as_str() { let mut input_file = input_file; input_file.push(input.as_str().expect("input_file was not a string or array")); inputs.push(( name.to_string(), std::fs::read_to_string(&input_file).expect("failed to read test input file"), )); } else if let Some(seq) = input.as_sequence() { for name in seq { let mut input_file = input_file.clone(); input_file.push(name.as_str().expect("input_file was not a string")); inputs.push(( name.as_str().expect("input_file item was not a string").to_string(), std::fs::read_to_string(&input_file).expect("failed to read test input file"), )); } } } if inputs.is_empty() { inputs.push(("empty".to_string(), "".to_string())); } let state = if let Some(input) = test.config.get("state_file") { let mut input_file: PathBuf = test.path.parent().expect("no test parent dir").into(); input_file.push(input.as_str().expect("state_file was not a string")); std::fs::read_to_string(&input_file).expect("failed to read test state file") } else { "".to_string() }; let mut output_items = vec![]; let mut last_circuit = None; for input in inputs { let mut parsed = parsed.clone(); parsed .parse_input(&input.1, Path::new("input"), &state, Path::new("state")) .map_err(|x| x.to_string())?; let mut cs: CircuitSynthesizer = Default::default(); let output = parsed.compile_constraints(&mut cs).map_err(|x| x.to_string())?; let circuit: SummarizedCircuit = SerializedCircuit::from(cs).into(); if let Some(last_circuit) = last_circuit.as_ref() { if last_circuit != &circuit { eprintln!( "{}\n{}", serde_yaml::to_string(last_circuit).unwrap(), serde_yaml::to_string(&circuit).unwrap() ); return Err("circuit changed on different input files".to_string()); } } else { last_circuit = Some(circuit); } output_items.push(OutputItem { input_file: input.0, output, }); } let final_output = CompileOutput { circuit: last_circuit.unwrap(), output: output_items, }; Ok(serde_yaml::to_value(&final_output).expect("serialization failed")) } } struct TestRunner; impl Runner for TestRunner { fn resolve_namespace(&self, name: &str) -> Option> { Some(match name { "Compile" => Box::new(CompileNamespace), _ => return None, }) } } #[test] pub fn compiler_tests() { leo_test_framework::run_tests(&TestRunner, "compiler"); }