add test syntax and cli command

This commit is contained in:
collin 2020-06-04 17:39:57 -07:00
parent 13e113d100
commit accfff46e0
13 changed files with 176 additions and 11 deletions

View File

@ -2,7 +2,7 @@
use crate::{
ast,
constraints::{generate_constraints, ConstrainedValue},
constraints::{generate_constraints, generate_test_constraints, ConstrainedValue},
errors::CompilerError,
GroupType, InputValue, Program,
};
@ -10,7 +10,7 @@ use crate::{
use snarkos_errors::gadgets::SynthesisError;
use snarkos_models::{
curves::{Field, PrimeField},
gadgets::r1cs::{ConstraintSynthesizer, ConstraintSystem},
gadgets::r1cs::{ConstraintSynthesizer, ConstraintSystem, TestConstraintSystem},
};
use from_pest::FromPest;
@ -68,6 +68,13 @@ impl<F: Field + PrimeField, G: GroupType<F>> Compiler<F, G> {
generate_constraints(cs, self.program, self.program_inputs)
}
pub fn compile_test_constraints(
self,
cs: &mut TestConstraintSystem<F>,
) -> Result<(), CompilerError> {
generate_test_constraints::<F, G>(cs, self.program)
}
// pub fn compile(&self) -> Result<ast::File, CompilerError> {
// // Read in the main file as string
// let unparsed_file = fs::read_to_string(&self.main_file_path).map_err(|_| CompilerError::FileReadError(self.main_file_path.clone()))?;

View File

@ -38,7 +38,7 @@ use crate::{
use snarkos_models::{
curves::{Field, PrimeField},
gadgets::r1cs::ConstraintSystem,
gadgets::r1cs::{ConstraintSystem, TestConstraintSystem},
};
pub fn generate_constraints<F: Field + PrimeField, G: GroupType<F>, CS: ConstraintSystem<F>>(
@ -66,3 +66,40 @@ pub fn generate_constraints<F: Field + PrimeField, G: GroupType<F>, CS: Constrai
_ => Err(CompilerError::NoMainFunction),
}
}
pub fn generate_test_constraints<F: Field + PrimeField, G: GroupType<F>>(
cs: &mut TestConstraintSystem<F>,
program: Program,
) -> Result<(), CompilerError> {
let mut resolved_program = ConstrainedProgram::<F, G, TestConstraintSystem<F>>::new();
let program_name = program.get_name();
let tests = program.tests.clone();
resolved_program.resolve_definitions(cs, program)?;
log::info!("Running {} tests", tests.len());
for (test_name, test_function) in tests.into_iter() {
let full_test_name = format!("{}::{}", program_name.clone(), test_name.to_string());
let result = resolved_program.enforce_main_function(
cs,
program_name.clone(),
test_function.0,
vec![], // test functions should not take any inputs
);
if result.is_ok() {
log::info!(
"test {} passed. Constraint system satisfied: {}",
full_test_name,
cs.is_satisfied()
);
} else {
log::error!("test {} errored: {}", full_test_name, result.unwrap_err());
}
}
Ok(())
}

View File

@ -414,14 +414,16 @@ impl<F: Field + PrimeField, G: GroupType<F>, CS: ConstraintSystem<F>> Constraine
self.enforce_boolean_eq(cs, bool_1, bool_2)?
}
(ConstrainedValue::Integer(num_1), ConstrainedValue::Integer(num_2)) => {
num_1.enforce_equal(cs, &num_2)?
}
(ConstrainedValue::Field(fe_1), ConstrainedValue::Field(fe_2)) => {
fe_1.enforce_equal(cs, &fe_2)?
}
(ConstrainedValue::Group(ge_1), ConstrainedValue::Group(ge_2)) => {
ge_1.enforce_equal(cs, &ge_2)?
num_1.enforce_equal(cs, &num_2).map_err(|_| {
StatementError::AssertionFailed(num_1.to_string(), num_2.to_string())
})?
}
(ConstrainedValue::Field(fe_1), ConstrainedValue::Field(fe_2)) => fe_1
.enforce_equal(cs, &fe_2)
.map_err(|_| StatementError::AssertionFailed(fe_1.to_string(), fe_2.to_string()))?,
(ConstrainedValue::Group(ge_1), ConstrainedValue::Group(ge_2)) => ge_1
.enforce_equal(cs, &ge_2)
.map_err(|_| StatementError::AssertionFailed(ge_1.to_string(), ge_2.to_string()))?,
(ConstrainedValue::Array(arr_1), ConstrainedValue::Array(arr_2)) => {
for (left, right) in arr_1.into_iter().zip(arr_2.into_iter()) {
self.enforce_assert_eq_statement(cs, left, right)?;

View File

@ -34,6 +34,9 @@ pub enum StatementError {
#[error("Cannot assert equality between {} == {}", _0, _1)]
AssertEq(String, String),
#[error("Assertion {:?} == {:?} failed", _0, _1)]
AssertionFailed(String, String),
#[error("If, else conditional must resolve to a boolean, got {}", _0)]
IfElseConditional(String),

View File

@ -287,3 +287,9 @@ impl<F: Field + PrimeField> ToBytesGadget<F> for FieldType<F> {
self_gadget.to_bytes_strict(cs)
}
}
impl<F: Field + PrimeField> std::fmt::Display for FieldType<F> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self.get_value().ok_or(std::fmt::Error))
}
}

View File

@ -304,3 +304,12 @@ impl ToBytesGadget<Fq> for EdwardsGroupType {
self_gadget.to_bytes_strict(cs)
}
}
impl std::fmt::Display for EdwardsGroupType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
EdwardsGroupType::Constant(constant) => write!(f, "{:?}", constant),
EdwardsGroupType::Allocated(allocated) => write!(f, "{:?}", allocated),
}
}
}

View File

@ -14,7 +14,7 @@ use snarkos_models::{
},
},
};
use std::fmt::Debug;
use std::fmt::{Debug, Display};
pub mod edwards_bls12;
@ -22,6 +22,7 @@ pub trait GroupType<F: Field>:
Sized
+ Clone
+ Debug
+ Display
+ EqGadget<F>
+ ConditionalEqGadget<F>
+ AllocGadget<String, F>

View File

@ -25,5 +25,8 @@ pub use self::run::*;
pub mod setup;
pub use self::setup::*;
pub mod test;
pub use self::test::*;
pub mod unload;
pub use self::unload::*;

74
leo/commands/test.rs Normal file
View File

@ -0,0 +1,74 @@
use crate::directories::source::SOURCE_DIRECTORY_NAME;
use crate::errors::{CLIError, TestError};
use crate::files::{MainFile, Manifest, MAIN_FILE_NAME};
use crate::{cli::*, cli_types::*};
use leo_compiler::compiler::Compiler;
use leo_compiler::group::edwards_bls12::EdwardsGroupType;
use snarkos_curves::edwards_bls12::Fq;
use snarkos_models::gadgets::r1cs::TestConstraintSystem;
use clap::ArgMatches;
use std::convert::TryFrom;
use std::env::current_dir;
#[derive(Debug)]
pub struct TestCommand;
impl CLI for TestCommand {
type Options = ();
type Output = ();
const NAME: NameType = "test";
const ABOUT: AboutType = "Compile and run all tests in the current package";
const ARGUMENTS: &'static [ArgumentType] = &[];
const FLAGS: &'static [FlagType] = &[];
const OPTIONS: &'static [OptionType] = &[];
const SUBCOMMANDS: &'static [SubCommandType] = &[];
#[cfg_attr(tarpaulin, skip)]
fn parse(_arguments: &ArgMatches) -> Result<Self::Options, CLIError> {
Ok(())
}
#[cfg_attr(tarpaulin, skip)]
fn output(_options: Self::Options) -> Result<Self::Output, CLIError> {
let path = current_dir()?;
// Get the package name
let manifest = Manifest::try_from(&path)?;
let package_name = manifest.get_package_name();
// Sanitize the package path to the root directory
let mut package_path = path.clone();
if package_path.is_file() {
package_path.pop();
}
// Verify the main file exists
if !MainFile::exists_at(&package_path) {
return Err(
TestError::MainFileDoesNotExist(package_path.as_os_str().to_owned()).into(),
);
}
// Construct the path to the main file in the source directory
let mut main_file_path = package_path.clone();
main_file_path.push(SOURCE_DIRECTORY_NAME);
main_file_path.push(MAIN_FILE_NAME);
// Compute the current program checksum
let program =
Compiler::<Fq, EdwardsGroupType>::init(package_name.clone(), main_file_path.clone())?;
// Generate the program on the constraint system and verify correctness
{
let mut cs = TestConstraintSystem::<Fq>::new();
let temporary_program = program.clone();
let output = temporary_program.compile_test_constraints(&mut cs)?;
log::debug!("Compiled constraints - {:#?}", output);
}
Ok(())
}
}

View File

@ -44,6 +44,9 @@ pub enum CLIError {
#[error("{}", _0)]
SourceDirectoryError(#[from] SourceDirectoryError),
#[error("{}", _0)]
TestError(#[from] TestError),
#[error("{}", _0)]
VerificationKeyFileError(#[from] VerificationKeyFileError),
}

View File

@ -9,3 +9,6 @@ pub use self::new::*;
pub mod run;
pub use self::run::*;
pub mod test;
pub use self::test::*;

View File

@ -0,0 +1,12 @@
use crate::errors::ManifestError;
use std::ffi::OsString;
#[derive(Debug, Error)]
pub enum TestError {
#[error("main file {:?} does not exist", _0)]
MainFileDoesNotExist(OsString),
#[error("{}", _0)]
ManifestError(#[from] ManifestError),
}

View File

@ -28,6 +28,7 @@ fn main() -> Result<(), CLIError> {
RunCommand::new().display_order(7),
PublishCommand::new().display_order(8),
DeployCommand::new().display_order(9),
TestCommand::new().display_order(10),
])
.set_term_width(0)
.get_matches();
@ -52,6 +53,10 @@ fn main() -> Result<(), CLIError> {
("run", Some(arguments)) => RunCommand::output(RunCommand::parse(arguments)?),
("publish", Some(arguments)) => PublishCommand::output(PublishCommand::parse(arguments)?),
("deploy", Some(arguments)) => DeployCommand::output(DeployCommand::parse(arguments)?),
("test", Some(arguments)) => {
TestCommand::output(TestCommand::parse(arguments)?)?;
Ok(())
}
_ => unreachable!(),
}
}