mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-09-20 02:07:38 +03:00
impl test context with test inputs
This commit is contained in:
parent
fcd8de4a9d
commit
5efa131c65
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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>,
|
||||||
|
@ -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 | "_")+ }
|
||||||
|
@ -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 }
|
||||||
|
@ -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
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
|
||||||
|
@ -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>>,
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user