impl test context with test inputs

This commit is contained in:
collin 2020-08-15 22:09:22 -07:00
parent fcd8de4a9d
commit 5efa131c65
14 changed files with 125 additions and 51 deletions

1
Cargo.lock generated
View File

@ -1014,6 +1014,7 @@ dependencies = [
"leo-ast", "leo-ast",
"leo-gadgets", "leo-gadgets",
"leo-input", "leo-input",
"leo-package",
"leo-typed", "leo-typed",
"log", "log",
"num-bigint", "num-bigint",

View File

@ -1,4 +1,7 @@
use crate::{ast::Rule, common::Identifier, SpanDef}; use crate::{
ast::{span_into_string, Rule},
SpanDef,
};
use pest::Span; use pest::Span;
use pest_ast::FromPest; use pest_ast::FromPest;
@ -7,7 +10,17 @@ use serde::Serialize;
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)] #[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::annotation_arguments))] #[pest_ast(rule(Rule::annotation_arguments))]
pub struct AnnotationArguments<'ast> { pub struct AnnotationArguments<'ast> {
pub arguments: Vec<Identifier<'ast>>, pub arguments: Vec<AnnotationArgument<'ast>>,
#[pest_ast(outer())]
#[serde(with = "SpanDef")]
pub span: Span<'ast>,
}
#[derive(Clone, Debug, FromPest, PartialEq, Serialize)]
#[pest_ast(rule(Rule::annotation_argument))]
pub struct AnnotationArgument<'ast> {
#[pest_ast(outer(with(span_into_string)))]
pub value: String,
#[pest_ast(outer())] #[pest_ast(outer())]
#[serde(with = "SpanDef")] #[serde(with = "SpanDef")]
pub span: Span<'ast>, pub span: Span<'ast>,

View File

@ -464,10 +464,10 @@ error = {"error"}
/// Annotations /// Annotations
// Declared in annotations/annotation.rs // Declared in annotations/annotation.rs
annotation = @{annotation_symbol ~ annotation_name ~ annotation_arguments} annotation = ${annotation_symbol ~ annotation_name ~ annotation_arguments}
// Declared in annotations/annotation_symbol.rs // Declared in annotations/annotation_symbol.rs
annotation_symbol = @{"@"} annotation_symbol = ${"@"}
// Declared in annotations/annotation_name.rs // Declared in annotations/annotation_name.rs
annotation_name = { annotation_name = {
@ -478,5 +478,6 @@ annotation_name = {
context = {"context"} context = {"context"}
// Declared in annotations/annotation_argument.rs // Declared in annotations/annotation_argument.rs
annotation_arguments = !{"(" ~ NEWLINE* ~ identifier ~ ("," ~ NEWLINE* ~ identifier)* ~ ","? ~ NEWLINE* ~ ")"} annotation_arguments = !{"(" ~ NEWLINE* ~ annotation_argument ~ ("," ~ NEWLINE* ~ annotation_argument)* ~ ","? ~ NEWLINE* ~ ")"}
annotation_argument = @{ (ASCII_ALPHANUMERIC | "_")+ }

View File

@ -8,6 +8,7 @@ edition = "2018"
leo-ast = { path = "../ast", version = "0.1.0" } leo-ast = { path = "../ast", version = "0.1.0" }
leo-gadgets = { path = "../gadgets", version = "0.1.0" } leo-gadgets = { path = "../gadgets", version = "0.1.0" }
leo-input = { path = "../input", version = "0.1.0" } leo-input = { path = "../input", version = "0.1.0" }
leo-package = { path = "../package", version = "0.1.0"}
leo-typed = { path = "../typed", version = "0.1.0" } leo-typed = { path = "../typed", version = "0.1.0" }
snarkos-curves = { git = "ssh://git@github.com/AleoHQ/snarkOS.git", package = "snarkos-curves", default-features = false } snarkos-curves = { git = "ssh://git@github.com/AleoHQ/snarkOS.git", package = "snarkos-curves", default-features = false }

View File

@ -10,6 +10,7 @@ use crate::{
}; };
use leo_ast::LeoAst; use leo_ast::LeoAst;
use leo_input::LeoInputParser; use leo_input::LeoInputParser;
use leo_package::inputs::InputPairs;
use leo_typed::{Input, LeoTypedAst, MainInput, Program}; use leo_typed::{Input, LeoTypedAst, MainInput, Program};
use snarkos_errors::gadgets::SynthesisError; use snarkos_errors::gadgets::SynthesisError;
@ -151,8 +152,8 @@ impl<F: Field + PrimeField, G: GroupType<F>> Compiler<F, G> {
} }
/// Synthesizes the circuit for test functions with program input. /// Synthesizes the circuit for test functions with program input.
pub fn compile_test_constraints(self) -> Result<(), CompilerError> { pub fn compile_test_constraints(self, input_pairs: InputPairs) -> Result<(), CompilerError> {
generate_test_constraints::<F, G>(self.program, self.program_input, &self.imported_programs) generate_test_constraints::<F, G>(self.program, input_pairs, &self.imported_programs)
} }
/// Calls the internal generate_constraints method with arguments /// Calls the internal generate_constraints method with arguments

View File

@ -11,6 +11,8 @@ use crate::{
}; };
use leo_typed::{Input, Program}; use leo_typed::{Input, Program};
use leo_input::LeoInputParser;
use leo_package::inputs::InputPairs;
use snarkos_models::{ use snarkos_models::{
curves::{Field, PrimeField}, curves::{Field, PrimeField},
gadgets::r1cs::{ConstraintSystem, TestConstraintSystem}, gadgets::r1cs::{ConstraintSystem, TestConstraintSystem},
@ -43,7 +45,7 @@ pub fn generate_constraints<F: Field + PrimeField, G: GroupType<F>, CS: Constrai
pub fn generate_test_constraints<F: Field + PrimeField, G: GroupType<F>>( pub fn generate_test_constraints<F: Field + PrimeField, G: GroupType<F>>(
program: Program, program: Program,
input: Input, input: InputPairs,
imported_programs: &ImportParser, imported_programs: &ImportParser,
) -> Result<(), CompilerError> { ) -> Result<(), CompilerError> {
let mut resolved_program = ConstrainedProgram::<F, G>::new(); let mut resolved_program = ConstrainedProgram::<F, G>::new();
@ -51,19 +53,45 @@ pub fn generate_test_constraints<F: Field + PrimeField, G: GroupType<F>>(
let tests = program.tests.clone(); let tests = program.tests.clone();
// Store definitions
resolved_program.store_definitions(program, imported_programs)?; resolved_program.store_definitions(program, imported_programs)?;
// Get default input
let default = input.pairs.get(&program_name);
log::info!("Running {} tests", tests.len()); log::info!("Running {} tests", tests.len());
for (test_name, test_function) in tests.into_iter() { for (test_name, test) in tests.into_iter() {
let cs = &mut TestConstraintSystem::<F>::new(); let cs = &mut TestConstraintSystem::<F>::new();
let full_test_name = format!("{}::{}", program_name.clone(), test_name.to_string()); let full_test_name = format!("{}::{}", program_name.clone(), test_name.to_string());
let result = resolved_program.enforce_main_function( // get input file name from annotation or use test_name
let input_pair = match test.input_file {
Some(file_name) => match input.pairs.get(&file_name.name) {
Some(pair) => pair.to_owned(),
None => return Err(CompilerError::InvalidTestContext(file_name.name)),
},
None => default.ok_or(CompilerError::NoTestInput)?,
};
// parse input files to abstract syntax trees
let input_file = &input_pair.input_file;
let state_file = &input_pair.state_file;
let input_ast = LeoInputParser::parse_file(input_file)?;
let state_ast = LeoInputParser::parse_file(state_file)?;
// parse input files into input struct
let mut input = Input::new();
input.parse_input(input_ast)?;
input.parse_state(state_ast)?;
// run test function on new program with input
let result = resolved_program.clone().enforce_main_function(
cs, cs,
program_name.clone(), program_name.clone(),
test_function.0, test.function,
input.clone(), // pass program input into every test input, // pass program input into every test
); );
if result.is_ok() { if result.is_ok() {
@ -72,6 +100,8 @@ pub fn generate_test_constraints<F: Field + PrimeField, G: GroupType<F>>(
full_test_name, full_test_name,
cs.is_satisfied() cs.is_satisfied()
); );
// write result to file
} else { } else {
log::error!("test {} errored: {}", full_test_name, result.unwrap_err()); log::error!("test {} errored: {}", full_test_name, result.unwrap_err());
} }

View File

@ -13,6 +13,9 @@ pub enum CompilerError {
#[error("{}", _0)] #[error("{}", _0)]
InputParserError(#[from] InputParserError), InputParserError(#[from] InputParserError),
#[error("Cannot find input files with context name `{}`", _0)]
InvalidTestContext(String),
#[error("{}", _0)] #[error("{}", _0)]
FunctionError(#[from] FunctionError), FunctionError(#[from] FunctionError),
@ -25,6 +28,9 @@ pub enum CompilerError {
#[error("`main` must be a function")] #[error("`main` must be a function")]
NoMainFunction, NoMainFunction,
#[error("Failed to find input files for the current test")]
NoTestInput,
#[error("{}", _0)] #[error("{}", _0)]
OutputError(#[from] OutputFileError), OutputError(#[from] OutputFileError),

View File

@ -6,6 +6,7 @@ use snarkos_models::curves::{Field, PrimeField};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone)]
pub struct ConstrainedProgram<F: Field + PrimeField, G: GroupType<F>> { pub struct ConstrainedProgram<F: Field + PrimeField, G: GroupType<F>> {
pub identifiers: HashMap<String, ConstrainedValue<F, G>>, pub identifiers: HashMap<String, ConstrainedValue<F, G>>,
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
cli::*, cli::*,
cli_types::*, cli_types::*,
commands::{BuildCommand, LoginCommand}, commands::LoginCommand,
errors::{ errors::{
commands::PublishError::{ConnectionUnavalaible, PackageNotPublished}, commands::PublishError::{ConnectionUnavalaible, PackageNotPublished},
CLIError, CLIError,

View File

@ -63,27 +63,20 @@ impl CLI for TestCommand {
let mut output_directory = package_path.clone(); let mut output_directory = package_path.clone();
output_directory.push(OUTPUTS_DIRECTORY_NAME); output_directory.push(OUTPUTS_DIRECTORY_NAME);
// Load the input file at `package_name` // Parse the current main program file
let input_string = InputFile::new(&package_name).read_from(&path)?; let program = Compiler::<Fq, EdwardsGroupType>::parse_program_without_input(
// Load the state file at `package_name.in`
let state_string = StateFile::new(&package_name).read_from(&path)?;
// Compute the current program checksum
let program = Compiler::<Fq, EdwardsGroupType>::parse_program_with_input(
package_name.clone(), package_name.clone(),
main_file_path.clone(), main_file_path.clone(),
output_directory, output_directory,
&input_string,
&state_string,
)?; )?;
// Generate the program on the constraint system and verify correctness // Parse all inputs as input pairs
{ let pairs = InputPairs::try_from(&package_path)?;
let temporary_program = program.clone();
let output = temporary_program.compile_test_constraints()?; // Run tests
log::debug!("Compiled constraints - {:#?}", output); let temporary_program = program.clone();
} let output = temporary_program.compile_test_constraints(pairs)?;
log::debug!("Compiled constraints - {:#?}", output);
Ok(()) Ok(())
} }

View File

@ -1,6 +1,6 @@
use crate::errors::InputsDirectoryError; use crate::errors::InputsDirectoryError;
use std::{fs, path::PathBuf}; use std::{fs, fs::ReadDir, path::PathBuf};
pub static INPUTS_DIRECTORY_NAME: &str = "inputs/"; pub static INPUTS_DIRECTORY_NAME: &str = "inputs/";
@ -24,24 +24,35 @@ impl InputsDirectory {
let directory = fs::read_dir(&path).map_err(InputsDirectoryError::Reading)?; let directory = fs::read_dir(&path).map_err(InputsDirectoryError::Reading)?;
let mut file_paths = Vec::new(); let mut file_paths = Vec::new();
for file_entry in directory.into_iter() { parse_file_paths(directory, &mut file_paths)?;
let file_entry = file_entry.map_err(InputsDirectoryError::GettingFileEntry)?;
let file_path = file_entry.path();
// Verify that the entry is structured as a valid file
let file_type = file_entry
.file_type()
.map_err(|error| InputsDirectoryError::GettingFileType(file_path.as_os_str().to_owned(), error))?;
if !file_type.is_file() {
return Err(InputsDirectoryError::InvalidFileType(
file_path.as_os_str().to_owned(),
file_type,
));
}
file_paths.push(file_path);
}
Ok(file_paths) Ok(file_paths)
} }
} }
fn parse_file_paths(directory: ReadDir, file_paths: &mut Vec<PathBuf>) -> Result<(), InputsDirectoryError> {
for file_entry in directory.into_iter() {
let file_entry = file_entry.map_err(InputsDirectoryError::GettingFileEntry)?;
let file_path = file_entry.path();
// Verify that the entry is structured as a valid file or directory
let file_type = file_entry
.file_type()
.map_err(|error| InputsDirectoryError::GettingFileType(file_path.as_os_str().to_owned(), error))?;
if file_type.is_dir() {
let directory = fs::read_dir(&file_path).map_err(InputsDirectoryError::Reading)?;
parse_file_paths(directory, file_paths)?;
continue;
} else if !file_type.is_file() {
return Err(InputsDirectoryError::InvalidFileType(
file_path.as_os_str().to_owned(),
file_type,
));
}
file_paths.push(file_path);
}
Ok(())
}

View File

@ -35,12 +35,12 @@ impl TryFrom<&PathBuf> for InputPairs {
.ok_or_else(|| InputsDirectoryError::GettingFileExtension(file.as_os_str().to_owned()))?; .ok_or_else(|| InputsDirectoryError::GettingFileExtension(file.as_os_str().to_owned()))?;
let file_name = file let file_name = file
.file_name() .file_stem()
.ok_or(InputsDirectoryError::GettingFileName(file.as_os_str().to_owned()))? .ok_or(InputsDirectoryError::GettingFileName(file.as_os_str().to_owned()))?
.to_str() .to_str()
.ok_or(InputsDirectoryError::GettingFileName(file.as_os_str().to_owned()))?; .ok_or(InputsDirectoryError::GettingFileName(file.as_os_str().to_owned()))?;
if file_extension == INPUT_FILE_EXTENSION { if file_extension == INPUT_FILE_EXTENSION.trim_start_matches(".") {
let input_file = InputFile::new(file_name).read_from(&file)?; let input_file = InputFile::new(file_name).read_from(&file)?;
if pairs.contains_key(file_name) { if pairs.contains_key(file_name) {
@ -53,7 +53,7 @@ impl TryFrom<&PathBuf> for InputPairs {
}; };
pairs.insert(file_name.to_owned(), pair); pairs.insert(file_name.to_owned(), pair);
} }
} else if file_extension == STATE_FILE_EXTENSION { } else if file_extension == STATE_FILE_EXTENSION.trim_start_matches(".") {
let state_file = StateFile::new(file_name).read_from(&file)?; let state_file = StateFile::new(file_name).read_from(&file)?;
if pairs.contains_key(file_name) { if pairs.contains_key(file_name) {

View File

@ -1,5 +1,9 @@
use crate::Span; use crate::Span;
use leo_ast::{common::Identifier as AstIdentifier, imports::PackageName as AstPackageName}; use leo_ast::{
annotations::AnnotationArgument,
common::Identifier as AstIdentifier,
imports::PackageName as AstPackageName,
};
use leo_input::common::Identifier as InputAstIdentifier; use leo_input::common::Identifier as InputAstIdentifier;
use serde::{ use serde::{
@ -55,6 +59,15 @@ impl<'ast> From<InputAstIdentifier<'ast>> for Identifier {
} }
} }
impl<'ast> From<AnnotationArgument<'ast>> for Identifier {
fn from(argument: AnnotationArgument<'ast>) -> Self {
Self {
name: argument.value,
span: Span::from(argument.span),
}
}
}
impl fmt::Display for Identifier { impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name) write!(f, "{}", self.name)

View File

@ -6,6 +6,7 @@ use leo_input::{
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Input { pub struct Input {
name: String,
program_input: ProgramInput, program_input: ProgramInput,
program_state: ProgramState, program_state: ProgramState,
} }
@ -13,6 +14,7 @@ pub struct Input {
impl Input { impl Input {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
name: "default".to_owned(),
program_input: ProgramInput::new(), program_input: ProgramInput::new(),
program_state: ProgramState::new(), program_state: ProgramState::new(),
} }
@ -25,6 +27,7 @@ impl Input {
let state = self.program_state.empty(); let state = self.program_state.empty();
Self { Self {
name: self.name.clone(),
program_input: input, program_input: input,
program_state: state, program_state: state,
} }