From d7d53eca2bdeed3e78a52e90d436fa76d81a51e8 Mon Sep 17 00:00:00 2001 From: schaeff Date: Fri, 21 Dec 2018 17:27:44 +0100 Subject: [PATCH 1/8] improve interpreter api, add e2e tests to zokrates_core --- zokrates_core/src/ir/interpreter.rs | 59 +++++++++++++++++++++++++---- zokrates_core/tests/add.code | 2 + zokrates_core/tests/integration.rs | 30 +++++++++++++++ zokrates_core/tests/utils/mod.rs | 8 ++++ 4 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 zokrates_core/tests/add.code create mode 100644 zokrates_core/tests/integration.rs create mode 100644 zokrates_core/tests/utils/mod.rs diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index 913f1d7d..11ae1e38 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -3,17 +3,32 @@ use helpers::Executable; use ir::*; use std::collections::BTreeMap; +type ExecutionResult = Result, Error>; + +pub struct Witness(BTreeMap); + +impl Witness { + pub fn return_values(&self) -> Vec { + self.0 + .clone() + .into_iter() + .filter(|(k, _)| k.is_output()) + .map(|(_, v)| v) + .collect() + } +} + impl Prog { - pub fn execute(self, inputs: Vec) -> Result, Error> { - let main = self.main; - assert_eq!(main.arguments.len(), inputs.len()); + pub fn execute + Clone>(&self, inputs: &Vec) -> ExecutionResult { + let main = &self.main; + self.check_inputs(&inputs)?; let mut witness = BTreeMap::new(); witness.insert(FlatVariable::one(), T::one()); for (arg, value) in main.arguments.iter().zip(inputs.iter()) { - witness.insert(arg.clone(), value.clone()); + witness.insert(arg.clone(), value.clone().into()); } - for statement in main.statements { + for statement in &main.statements { match statement { Statement::Constraint(quad, lin) => match lin.is_assignee(&witness) { true => { @@ -24,7 +39,12 @@ impl Prog { let lhs_value = quad.evaluate(&witness); let rhs_value = lin.evaluate(&witness); if lhs_value != rhs_value { - return Err(Error::Constraint(quad, lin, lhs_value, rhs_value)); + return Err(Error::Constraint( + quad.clone(), + lin.clone(), + lhs_value, + rhs_value, + )); } } }, @@ -44,7 +64,15 @@ impl Prog { } } - Ok(witness) + Ok(Witness(witness)) + } + + fn check_inputs(&self, inputs: &Vec) -> Result<(), Error> { + if self.main.arguments.len() == inputs.len() { + Ok(()) + } else { + Err(Error::Inputs(self.main.arguments.len(), inputs.len())) + } } } @@ -69,10 +97,11 @@ impl QuadComb { } } -#[derive(PartialEq, Debug)] +#[derive(PartialEq)] pub enum Error { Constraint(QuadComb, LinComb, T, T), Solver, + Inputs(usize, usize), } impl fmt::Display for Error { @@ -84,6 +113,20 @@ impl fmt::Display for Error { quad, lin, left_value, right_value ), Error::Solver => write!(f, ""), + Error::Inputs(expected, received) => write!( + f, + "Program takes {} input{} but was passed {} value{}", + expected, + if expected == 1 { "" } else { "s" }, + received, + if received == 1 { "" } else { "s" } + ), } } } + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self) + } +} diff --git a/zokrates_core/tests/add.code b/zokrates_core/tests/add.code new file mode 100644 index 00000000..d2277712 --- /dev/null +++ b/zokrates_core/tests/add.code @@ -0,0 +1,2 @@ +def main(field a, field b) -> (field): + return a + b \ No newline at end of file diff --git a/zokrates_core/tests/integration.rs b/zokrates_core/tests/integration.rs new file mode 100644 index 00000000..b767dee9 --- /dev/null +++ b/zokrates_core/tests/integration.rs @@ -0,0 +1,30 @@ +extern crate zokrates_core; + +mod utils; + +use utils::compile_test; +use zokrates_core::field::FieldPrime; + +#[test] +fn add() { + let code = include_str!("add.code"); + let bin = compile_test(code).unwrap(); + + let input = vec![21, 33]; + let expected_output = vec![FieldPrime::from(21 + 33)]; + + let output = bin.execute(&input).unwrap().return_values(); + + assert_eq!( + output, expected_output, + " + +{} + +Called with {:?} +Expected {:?} +Returned {:?} +", + code, input, expected_output, output + ); +} diff --git a/zokrates_core/tests/utils/mod.rs b/zokrates_core/tests/utils/mod.rs new file mode 100644 index 00000000..832ff0c1 --- /dev/null +++ b/zokrates_core/tests/utils/mod.rs @@ -0,0 +1,8 @@ +use std::io; +use zokrates_core::compile::{compile, CompileError}; +use zokrates_core::field::FieldPrime; +use zokrates_core::ir; + +pub fn compile_test(code: &str) -> Result, CompileError> { + compile::(&mut code.as_bytes(), None, None) +} From baec52e239a32f2b79b1d33ea91b56ad6a96b650 Mon Sep 17 00:00:00 2001 From: schaeff Date: Sun, 30 Dec 2018 21:27:37 +0100 Subject: [PATCH 2/8] use macro to generate tests --- zokrates_core/src/ir/interpreter.rs | 2 +- zokrates_core/src/ir/mod.rs | 2 + zokrates_core/tests/{ => bench}/add.code | 0 zokrates_core/tests/bench/add.json | 24 +++++ zokrates_core/tests/bench/sub.code | 2 + zokrates_core/tests/bench/sub.json | 24 +++++ zokrates_core/tests/integration.rs | 35 +++---- zokrates_core/tests/utils/mod.rs | 114 ++++++++++++++++++++++- 8 files changed, 175 insertions(+), 28 deletions(-) rename zokrates_core/tests/{ => bench}/add.code (100%) create mode 100644 zokrates_core/tests/bench/add.json create mode 100644 zokrates_core/tests/bench/sub.code create mode 100644 zokrates_core/tests/bench/sub.json diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index 11ae1e38..4abc00a8 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -3,7 +3,7 @@ use helpers::Executable; use ir::*; use std::collections::BTreeMap; -type ExecutionResult = Result, Error>; +pub type ExecutionResult = Result, Error>; pub struct Witness(BTreeMap); diff --git a/zokrates_core/src/ir/mod.rs b/zokrates_core/src/ir/mod.rs index c073e46a..03c79bcf 100644 --- a/zokrates_core/src/ir/mod.rs +++ b/zokrates_core/src/ir/mod.rs @@ -13,6 +13,8 @@ mod interpreter; use self::expression::LinComb; use self::expression::QuadComb; +pub use self::interpreter::ExecutionResult; + #[derive(Debug, Serialize, Deserialize, Clone)] pub enum Statement { Constraint(QuadComb, LinComb), diff --git a/zokrates_core/tests/add.code b/zokrates_core/tests/bench/add.code similarity index 100% rename from zokrates_core/tests/add.code rename to zokrates_core/tests/bench/add.code diff --git a/zokrates_core/tests/bench/add.json b/zokrates_core/tests/bench/add.json new file mode 100644 index 00000000..4965f3d7 --- /dev/null +++ b/zokrates_core/tests/bench/add.json @@ -0,0 +1,24 @@ +{ + "tests": [ + { + "input": { + "values": ["1", "2"] + }, + "output": { + "success": { + "values": ["3"] + } + } + }, + { + "input": { + "values": ["0", "1"] + }, + "output": { + "success": { + "values": ["1"] + } + } + } + ] +} \ No newline at end of file diff --git a/zokrates_core/tests/bench/sub.code b/zokrates_core/tests/bench/sub.code new file mode 100644 index 00000000..668c350d --- /dev/null +++ b/zokrates_core/tests/bench/sub.code @@ -0,0 +1,2 @@ +def main(field a, field b) -> (field): + return a - \ No newline at end of file diff --git a/zokrates_core/tests/bench/sub.json b/zokrates_core/tests/bench/sub.json new file mode 100644 index 00000000..a09ea4cf --- /dev/null +++ b/zokrates_core/tests/bench/sub.json @@ -0,0 +1,24 @@ +{ + "tests": [ + { + "input": { + "values": ["2", "1"] + }, + "output": { + "success": { + "values": ["1"] + } + } + }, + { + "input": { + "values": ["1", "0"] + }, + "output": { + "success": { + "values": ["1"] + } + } + } + ] +} \ No newline at end of file diff --git a/zokrates_core/tests/integration.rs b/zokrates_core/tests/integration.rs index b767dee9..bf63d440 100644 --- a/zokrates_core/tests/integration.rs +++ b/zokrates_core/tests/integration.rs @@ -1,30 +1,19 @@ +extern crate serde_json; extern crate zokrates_core; +#[macro_use] +extern crate serde_derive; +#[macro_use] mod utils; -use utils::compile_test; +use utils::compare; +use utils::compile; +use utils::read_file; +use utils::Tests; +use zokrates_core::field::Field; use zokrates_core::field::FieldPrime; -#[test] -fn add() { - let code = include_str!("add.code"); - let bin = compile_test(code).unwrap(); - - let input = vec![21, 33]; - let expected_output = vec![FieldPrime::from(21 + 33)]; - - let output = bin.execute(&input).unwrap().return_values(); - - assert_eq!( - output, expected_output, - " - -{} - -Called with {:?} -Expected {:?} -Returned {:?} -", - code, input, expected_output, output - ); +zokrates_test! { + add, + sub, } diff --git a/zokrates_core/tests/utils/mod.rs b/zokrates_core/tests/utils/mod.rs index 832ff0c1..72501ae2 100644 --- a/zokrates_core/tests/utils/mod.rs +++ b/zokrates_core/tests/utils/mod.rs @@ -1,8 +1,114 @@ +extern crate serde_json; + use std::io; -use zokrates_core::compile::{compile, CompileError}; -use zokrates_core::field::FieldPrime; +use zokrates_core::compile::{compile as generic_compile, CompileError}; +use zokrates_core::field::{Field, FieldPrime}; use zokrates_core::ir; -pub fn compile_test(code: &str) -> Result, CompileError> { - compile::(&mut code.as_bytes(), None, None) +#[derive(Serialize, Deserialize)] +pub struct Tests { + pub tests: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct Test { + pub input: Input, + pub output: Output, +} + +#[derive(Serialize, Deserialize)] +pub struct Input { + pub values: Vec, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_camel_case_types)] +pub enum Output { + success { values: Vec }, + error(String), +} + +type Val = String; + +pub fn compare(result: &ir::ExecutionResult, expected: &Output) -> Result<(), String> { + match (result, expected) { + (Ok(output), Output::success { values }) => { + let expected_output: Vec<_> = values + .iter() + .map(|o| FieldPrime::from_dec_string(o.clone())) + .collect(); + let output = output.return_values(); + + if output != expected_output { + Err(format!( + " +Expected {:?} +Returned {:?} + ", + expected_output, output + )) + } else { + Ok(()) + } + } + (Err(..), Output::error(..)) => { + // TODO check errors match + Ok(()) + } + (Ok(output), Output::error(..)) => Err(format!( + " +Expected an error +Returned {:?} + ", + output.return_values() + )), + (Err(..), Output::success { .. }) => panic!(), + } +} + +pub fn read_file(path: &str) -> String { + use std::fs::File; + use std::io::Read; + let mut file = File::open(format!("./tests/bench/{}", path)).expect("Unable to open the file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("Unable to read the file"); + + contents +} + +pub fn compile(code: &str) -> Result, CompileError> { + generic_compile::(&mut code.as_bytes(), None, None) +} + +macro_rules! zokrates_test { + ($($name:ident,)*) => { + $( + #[test] + fn $name() { + let code = read_file(&format!("./{}.code", stringify!($name))); + let bin = compile(&code).unwrap(); + + let test_str = read_file(&format!("./{}.json", stringify!($name))); + + let t: Tests = serde_json::from_str(&test_str).unwrap(); + + for test in t.tests.into_iter() { + let input = &test.input.values; + let output = bin.execute(&input.iter().map(|i| FieldPrime::from_dec_string(i.clone())).collect()); + + let context = format!(" +{} + +Called with input ({}) + ", code, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); + + match compare(&output, &test.output) { + Err(e) => panic!("{}{}", context, e), + Ok(..) => {} + }; + } + } + )* + }; } From 1f71554584e5457eb880a69f0892d55664c90d3d Mon Sep 17 00:00:00 2001 From: schaeff Date: Mon, 31 Dec 2018 08:10:33 +0100 Subject: [PATCH 3/8] hide witness operations away from cli --- zokrates_cli/src/bin.rs | 23 +++++------------------ zokrates_core/src/ir/interpreter.rs | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/zokrates_cli/src/bin.rs b/zokrates_cli/src/bin.rs index cc0521de..77eabe2a 100644 --- a/zokrates_cli/src/bin.rs +++ b/zokrates_cli/src/bin.rs @@ -20,7 +20,7 @@ use std::io::{stdin, BufRead, BufReader, BufWriter, Write}; use std::path::{Path, PathBuf}; use std::string::String; use zokrates_core::compile::compile; -use zokrates_core::field::{Field, FieldPrime}; +use zokrates_core::field::FieldPrime; use zokrates_core::ir; #[cfg(feature = "libsnark")] use zokrates_core::ir::r1cs_program; @@ -379,21 +379,11 @@ fn main() { }) .collect(); - let witness_map = program_ast - .execute(arguments) + let witness = program_ast + .execute(&arguments) .unwrap_or_else(|e| panic!(format!("Execution failed: {}", e))); - println!( - "\nWitness: \n\n{}", - witness_map - .iter() - .filter_map(|(variable, value)| match variable { - variable if variable.is_output() => Some(format!("{} {}", variable, value)), - _ => None, - }) - .collect::>() - .join("\n") - ); + println!("\nWitness: \n\n{}", witness.format_outputs()); // write witness to file let output_path = Path::new(sub_matches.value_of("output").unwrap()); @@ -402,10 +392,7 @@ fn main() { Err(why) => panic!("couldn't create {}: {}", output_path.display(), why), }; let mut bw = BufWriter::new(output_file); - for (var, val) in &witness_map { - write!(&mut bw, "{} {}\n", var, val.to_dec_string()) - .expect("Unable to write data to file."); - } + write!(&mut bw, "{}", witness).expect("Unable to write data to file."); bw.flush().expect("Unable to flush buffer."); } #[cfg(feature = "libsnark")] diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index 4abc00a8..b7a2ec68 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -16,6 +16,31 @@ impl Witness { .map(|(_, v)| v) .collect() } + + pub fn format_outputs(&self) -> String { + self.0 + .iter() + .filter_map(|(variable, value)| match variable { + variable if variable.is_output() => Some(format!("{} {}", variable, value)), + _ => None, + }) + .collect::>() + .join("\n") + } +} + +impl fmt::Display for Witness { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + self.0 + .iter() + .map(|(k, v)| format!("{} {}", k, v.to_dec_string())) + .collect::>() + .join("\n") + ) + } } impl Prog { From e196d67b079ef01aae9bb66e563527700a02caf4 Mon Sep 17 00:00:00 2001 From: schaeff Date: Mon, 31 Dec 2018 08:14:17 +0100 Subject: [PATCH 4/8] put trait in scope --- zokrates_cli/src/bin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zokrates_cli/src/bin.rs b/zokrates_cli/src/bin.rs index 77eabe2a..7cc8d8bf 100644 --- a/zokrates_cli/src/bin.rs +++ b/zokrates_cli/src/bin.rs @@ -20,7 +20,7 @@ use std::io::{stdin, BufRead, BufReader, BufWriter, Write}; use std::path::{Path, PathBuf}; use std::string::String; use zokrates_core::compile::compile; -use zokrates_core::field::FieldPrime; +use zokrates_core::field::{Field, FieldPrime}; use zokrates_core::ir; #[cfg(feature = "libsnark")] use zokrates_core::ir::r1cs_program; From 9f10aae2ba9844097ad06b4fe90998f95ad50aff Mon Sep 17 00:00:00 2001 From: schaeff Date: Mon, 31 Dec 2018 08:18:35 +0100 Subject: [PATCH 5/8] fix tests --- zokrates_cli/src/bin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zokrates_cli/src/bin.rs b/zokrates_cli/src/bin.rs index 7cc8d8bf..a6ee9454 100644 --- a/zokrates_cli/src/bin.rs +++ b/zokrates_cli/src/bin.rs @@ -642,7 +642,7 @@ mod tests { let (..) = r1cs_program(program_flattened.clone()); let _ = program_flattened - .execute(vec![FieldPrime::from(0)]) + .execute(&vec![FieldPrime::from(0)]) .unwrap(); } } @@ -676,7 +676,7 @@ mod tests { let result = std::panic::catch_unwind(|| { let _ = program_flattened - .execute(vec![FieldPrime::from(0)]) + .execute(&vec![FieldPrime::from(0)]) .unwrap(); }); assert!(result.is_err()); From 3169d6da0cb3fc8af1e470855ecdd8425de9a35a Mon Sep 17 00:00:00 2001 From: schaeff Date: Mon, 31 Dec 2018 09:32:54 +0100 Subject: [PATCH 6/8] make computaion test pass --- zokrates_core/tests/bench/sub.code | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zokrates_core/tests/bench/sub.code b/zokrates_core/tests/bench/sub.code index 668c350d..8721f695 100644 --- a/zokrates_core/tests/bench/sub.code +++ b/zokrates_core/tests/bench/sub.code @@ -1,2 +1,2 @@ def main(field a, field b) -> (field): - return a - \ No newline at end of file + return a - b \ No newline at end of file From c570e3a06e5ab61920e48e723dab2506f92aa930 Mon Sep 17 00:00:00 2001 From: schaeff Date: Tue, 1 Jan 2019 23:10:58 +0100 Subject: [PATCH 7/8] use absolute paths in macro definition to make it self contained --- zokrates_core/tests/integration.rs | 7 ------- zokrates_core/tests/utils/mod.rs | 32 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/zokrates_core/tests/integration.rs b/zokrates_core/tests/integration.rs index bf63d440..015ec9cb 100644 --- a/zokrates_core/tests/integration.rs +++ b/zokrates_core/tests/integration.rs @@ -6,13 +6,6 @@ extern crate serde_derive; #[macro_use] mod utils; -use utils::compare; -use utils::compile; -use utils::read_file; -use utils::Tests; -use zokrates_core::field::Field; -use zokrates_core::field::FieldPrime; - zokrates_test! { add, sub, diff --git a/zokrates_core/tests/utils/mod.rs b/zokrates_core/tests/utils/mod.rs index 72501ae2..0487be75 100644 --- a/zokrates_core/tests/utils/mod.rs +++ b/zokrates_core/tests/utils/mod.rs @@ -44,7 +44,7 @@ pub fn compare(result: &ir::ExecutionResult, expected: &Output) -> R " Expected {:?} Returned {:?} - ", + ", expected_output, output )) } else { @@ -86,28 +86,30 @@ macro_rules! zokrates_test { $( #[test] fn $name() { - let code = read_file(&format!("./{}.code", stringify!($name))); - let bin = compile(&code).unwrap(); + use zokrates_core::field::Field; - let test_str = read_file(&format!("./{}.json", stringify!($name))); + let code = $crate::utils::read_file(&format!("./{}.code", stringify!($name))); + let bin = $crate::utils::compile(&code).unwrap(); - let t: Tests = serde_json::from_str(&test_str).unwrap(); + let test_str = $crate::utils::read_file(&format!("./{}.json", stringify!($name))); - for test in t.tests.into_iter() { - let input = &test.input.values; - let output = bin.execute(&input.iter().map(|i| FieldPrime::from_dec_string(i.clone())).collect()); + let t: $crate::utils::Tests = serde_json::from_str(&test_str).unwrap(); - let context = format!(" + for test in t.tests.into_iter() { + let input = &test.input.values; + let output = bin.execute(&input.iter().map(|i| zokrates_core::field::FieldPrime::from_dec_string(i.clone())).collect()); + + let context = format!(" {} Called with input ({}) - ", code, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); + ", code, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); - match compare(&output, &test.output) { - Err(e) => panic!("{}{}", context, e), - Ok(..) => {} - }; - } + match $crate::utils::compare(&output, &test.output) { + Err(e) => panic!("{}{}", context, e), + Ok(..) => {} + }; + } } )* }; From 3cfbd7852966a050b4b1a2662bafca367fd5753c Mon Sep 17 00:00:00 2001 From: schaeff Date: Wed, 2 Jan 2019 13:19:08 +0100 Subject: [PATCH 8/8] simplify, use strings for constraint errors, compare errors --- zokrates_core/src/ir/interpreter.rs | 42 ++++----- zokrates_core/src/ir/mod.rs | 1 + zokrates_core/tests/bench/add.json | 11 ++- zokrates_core/tests/bench/assert_one.code | 3 + zokrates_core/tests/bench/assert_one.json | 17 ++++ zokrates_core/tests/bench/sub.code | 2 - zokrates_core/tests/bench/sub.json | 24 ----- zokrates_core/tests/integration.rs | 2 +- zokrates_core/tests/utils/mod.rs | 106 +++++++++++----------- 9 files changed, 105 insertions(+), 103 deletions(-) create mode 100644 zokrates_core/tests/bench/assert_one.code create mode 100644 zokrates_core/tests/bench/assert_one.json delete mode 100644 zokrates_core/tests/bench/sub.code delete mode 100644 zokrates_core/tests/bench/sub.json diff --git a/zokrates_core/src/ir/interpreter.rs b/zokrates_core/src/ir/interpreter.rs index b7a2ec68..2ced804d 100644 --- a/zokrates_core/src/ir/interpreter.rs +++ b/zokrates_core/src/ir/interpreter.rs @@ -3,7 +3,7 @@ use helpers::Executable; use ir::*; use std::collections::BTreeMap; -pub type ExecutionResult = Result, Error>; +pub type ExecutionResult = Result, Error>; pub struct Witness(BTreeMap); @@ -64,12 +64,10 @@ impl Prog { let lhs_value = quad.evaluate(&witness); let rhs_value = lin.evaluate(&witness); if lhs_value != rhs_value { - return Err(Error::Constraint( - quad.clone(), - lin.clone(), - lhs_value, - rhs_value, - )); + return Err(Error::UnsatisfiedConstraint { + left: lhs_value.to_dec_string(), + right: rhs_value.to_dec_string(), + }); } } }, @@ -92,11 +90,14 @@ impl Prog { Ok(Witness(witness)) } - fn check_inputs(&self, inputs: &Vec) -> Result<(), Error> { + fn check_inputs(&self, inputs: &Vec) -> Result<(), Error> { if self.main.arguments.len() == inputs.len() { Ok(()) } else { - Err(Error::Inputs(self.main.arguments.len(), inputs.len())) + Err(Error::WrongInputCount { + expected: self.main.arguments.len(), + received: inputs.len(), + }) } } } @@ -122,23 +123,22 @@ impl QuadComb { } } -#[derive(PartialEq)] -pub enum Error { - Constraint(QuadComb, LinComb, T, T), +#[derive(PartialEq, Serialize, Deserialize)] +pub enum Error { + UnsatisfiedConstraint { left: String, right: String }, Solver, - Inputs(usize, usize), + WrongInputCount { expected: usize, received: usize }, } -impl fmt::Display for Error { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - Error::Constraint(ref quad, ref lin, ref left_value, ref right_value) => write!( - f, - "Expected {} to equal {}, but {} != {}", - quad, lin, left_value, right_value - ), + Error::UnsatisfiedConstraint { + ref left, + ref right, + } => write!(f, "Expected {} to equal {}", left, right), Error::Solver => write!(f, ""), - Error::Inputs(expected, received) => write!( + Error::WrongInputCount { expected, received } => write!( f, "Program takes {} input{} but was passed {} value{}", expected, @@ -150,7 +150,7 @@ impl fmt::Display for Error { } } -impl fmt::Debug for Error { +impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self) } diff --git a/zokrates_core/src/ir/mod.rs b/zokrates_core/src/ir/mod.rs index 03c79bcf..a2501c43 100644 --- a/zokrates_core/src/ir/mod.rs +++ b/zokrates_core/src/ir/mod.rs @@ -13,6 +13,7 @@ mod interpreter; use self::expression::LinComb; use self::expression::QuadComb; +pub use self::interpreter::Error; pub use self::interpreter::ExecutionResult; #[derive(Debug, Serialize, Deserialize, Clone)] diff --git a/zokrates_core/tests/bench/add.json b/zokrates_core/tests/bench/add.json index 4965f3d7..f7407d67 100644 --- a/zokrates_core/tests/bench/add.json +++ b/zokrates_core/tests/bench/add.json @@ -5,18 +5,21 @@ "values": ["1", "2"] }, "output": { - "success": { + "Ok": { "values": ["3"] } } }, { "input": { - "values": ["0", "1"] + "values": ["1", "2", "42"] }, "output": { - "success": { - "values": ["1"] + "Err": { + "WrongInputCount": { + "expected": 2, + "received": 3 + } } } } diff --git a/zokrates_core/tests/bench/assert_one.code b/zokrates_core/tests/bench/assert_one.code new file mode 100644 index 00000000..03dafa87 --- /dev/null +++ b/zokrates_core/tests/bench/assert_one.code @@ -0,0 +1,3 @@ +def main(field a) -> (field): + a == 1 + return 1 \ No newline at end of file diff --git a/zokrates_core/tests/bench/assert_one.json b/zokrates_core/tests/bench/assert_one.json new file mode 100644 index 00000000..5709a87f --- /dev/null +++ b/zokrates_core/tests/bench/assert_one.json @@ -0,0 +1,17 @@ +{ + "tests": [ + { + "input": { + "values": ["0"] + }, + "output": { + "Err": { + "UnsatisfiedConstraint": { + "left": "1", + "right": "0" + } + } + } + } + ] +} \ No newline at end of file diff --git a/zokrates_core/tests/bench/sub.code b/zokrates_core/tests/bench/sub.code deleted file mode 100644 index 8721f695..00000000 --- a/zokrates_core/tests/bench/sub.code +++ /dev/null @@ -1,2 +0,0 @@ -def main(field a, field b) -> (field): - return a - b \ No newline at end of file diff --git a/zokrates_core/tests/bench/sub.json b/zokrates_core/tests/bench/sub.json deleted file mode 100644 index a09ea4cf..00000000 --- a/zokrates_core/tests/bench/sub.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "tests": [ - { - "input": { - "values": ["2", "1"] - }, - "output": { - "success": { - "values": ["1"] - } - } - }, - { - "input": { - "values": ["1", "0"] - }, - "output": { - "success": { - "values": ["1"] - } - } - } - ] -} \ No newline at end of file diff --git a/zokrates_core/tests/integration.rs b/zokrates_core/tests/integration.rs index 015ec9cb..af824ffc 100644 --- a/zokrates_core/tests/integration.rs +++ b/zokrates_core/tests/integration.rs @@ -8,5 +8,5 @@ mod utils; zokrates_test! { add, - sub, + assert_one, } diff --git a/zokrates_core/tests/utils/mod.rs b/zokrates_core/tests/utils/mod.rs index 0487be75..b66ce26d 100644 --- a/zokrates_core/tests/utils/mod.rs +++ b/zokrates_core/tests/utils/mod.rs @@ -10,62 +10,65 @@ pub struct Tests { pub tests: Vec, } -#[derive(Serialize, Deserialize)] -pub struct Test { - pub input: Input, - pub output: Output, -} - #[derive(Serialize, Deserialize)] pub struct Input { pub values: Vec, } #[derive(Serialize, Deserialize)] -#[allow(non_camel_case_types)] -pub enum Output { - success { values: Vec }, - error(String), +pub struct Test { + pub input: Input, + pub output: TestResult, +} + +pub type TestResult = Result; + +#[derive(PartialEq, Debug)] +pub struct ComparableResult(Result, ir::Error>); + +#[derive(Serialize, Deserialize)] +pub struct Output { + values: Vec, } type Val = String; -pub fn compare(result: &ir::ExecutionResult, expected: &Output) -> Result<(), String> { - match (result, expected) { - (Ok(output), Output::success { values }) => { - let expected_output: Vec<_> = values - .iter() - .map(|o| FieldPrime::from_dec_string(o.clone())) - .collect(); - let output = output.return_values(); - - if output != expected_output { - Err(format!( - " -Expected {:?} -Returned {:?} - ", - expected_output, output - )) - } else { - Ok(()) - } - } - (Err(..), Output::error(..)) => { - // TODO check errors match - Ok(()) - } - (Ok(output), Output::error(..)) => Err(format!( - " -Expected an error -Returned {:?} - ", - output.return_values() - )), - (Err(..), Output::success { .. }) => panic!(), +impl From> for ComparableResult { + fn from(r: ir::ExecutionResult) -> ComparableResult { + ComparableResult(r.map(|v| v.return_values())) } } +impl From for ComparableResult { + fn from(r: TestResult) -> ComparableResult { + ComparableResult(r.map(|v| { + v.values + .into_iter() + .map(|v| FieldPrime::from_dec_string(v)) + .collect() + })) + } +} + +pub fn compare( + result: ir::ExecutionResult, + expected: TestResult, +) -> Result<(), String> { + // extract outputs from result + let result = ComparableResult::from(result); + // deserialize expected result + let expected = ComparableResult::from(expected); + + if result != expected { + return Err(format!( + "Expected {:?} but found {:?}", + expected.0, result.0 + )); + } + + Ok(()) +} + pub fn read_file(path: &str) -> String { use std::fs::File; use std::io::Read; @@ -86,26 +89,27 @@ macro_rules! zokrates_test { $( #[test] fn $name() { - use zokrates_core::field::Field; - let code = $crate::utils::read_file(&format!("./{}.code", stringify!($name))); - let bin = $crate::utils::compile(&code).unwrap(); + use zokrates_core::field::{FieldPrime, Field}; - let test_str = $crate::utils::read_file(&format!("./{}.json", stringify!($name))); + let code_string = $crate::utils::read_file(&format!("./{}.code", stringify!($name))); + let test_string = $crate::utils::read_file(&format!("./{}.json", stringify!($name))); - let t: $crate::utils::Tests = serde_json::from_str(&test_str).unwrap(); + let bin = $crate::utils::compile(&code_string).unwrap(); + + let t: $crate::utils::Tests = serde_json::from_str(&test_string).unwrap(); for test in t.tests.into_iter() { let input = &test.input.values; - let output = bin.execute(&input.iter().map(|i| zokrates_core::field::FieldPrime::from_dec_string(i.clone())).collect()); + let output = bin.execute(&input.iter().map(|v| FieldPrime::from_dec_string(v.clone())).collect()); let context = format!(" {} Called with input ({}) - ", code, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); + ", code_string, input.iter().map(|i| format!("{}", i)).collect::>().join(", ")); - match $crate::utils::compare(&output, &test.output) { + match $crate::utils::compare(output, test.output) { Err(e) => panic!("{}{}", context, e), Ok(..) => {} };