From eb4b2e3e542e17b3e131dd7d1957b853c242cd85 Mon Sep 17 00:00:00 2001 From: thesayyn Date: Sun, 26 May 2024 13:18:09 -0700 Subject: [PATCH] rework test spec --- cel-rs/Cargo.toml | 1 + cel-rs/src/eval.rs | 46 +++++---- cel-rs/src/lib.rs | 3 +- cel-rs/src/parser/cel.lalrpop | 23 +++-- cel-rs/src/parser/mod.rs | 1 + cel-rs/src/parser/parse.rs | 5 + cel-rs/src/program.rs | 12 +++ cel-rs/src/value/bool.rs | 11 +- cel-rs/src/value/error.rs | 8 ++ cel-rs/src/value/map.rs | 47 +++++++++ cel-rs/src/value/mod.rs | 1 + cel-rs/src/value/value.rs | 92 +++++++++++++++-- cel-rs/tests/test.rs | 19 ++-- cel-spec/Cargo.toml | 5 +- cel-spec/build.rs | 75 +++++++++++++- cel-spec/src/lib.rs | 183 +++++++++++++++++----------------- 16 files changed, 384 insertions(+), 148 deletions(-) create mode 100644 cel-rs/src/parser/parse.rs create mode 100644 cel-rs/src/value/map.rs diff --git a/cel-rs/Cargo.toml b/cel-rs/Cargo.toml index eb8525b..72382ee 100644 --- a/cel-rs/Cargo.toml +++ b/cel-rs/Cargo.toml @@ -15,6 +15,7 @@ ordered_hash_map = "0.4.0" regex = "1.4.2" lalrpop-util = "0.19.1" lazy_static = "1.4.0" +unescape = "0.1.0" [dev-dependencies] cel-spec = {path = "../cel-spec"} diff --git a/cel-rs/src/eval.rs b/cel-rs/src/eval.rs index c27f2a2..fb34991 100644 --- a/cel-rs/src/eval.rs +++ b/cel-rs/src/eval.rs @@ -1,5 +1,8 @@ +use std::collections::HashMap; +use std::rc::Rc; + use crate::parser::{Atom, Expression, RelationOp}; -use crate::value::value::{Val}; +use crate::value::value::Val; use crate::Context; #[derive(Default)] @@ -47,24 +50,7 @@ impl Eval { // crate::parser::Member::Fields(_) => todo!("Fields"), // } // } - // fn eval_map(&self, entries: Vec<(Expression, Expression)>, ctx: &mut Context) -> Value { - // let mut map = OrderedHashMap::with_capacity(entries.len()); - // for (kexpr, vexpr) in entries { - // let k = self.eval(kexpr, ctx).unpack(); - // let v = self.eval(vexpr, ctx).unpack(); - // map.insert(k, v); - // } - // Value::Map(Rc::new(map)) - // } - // fn eval_list(&self, elems: Vec, ctx: &mut Context) -> Value { - // let mut list = Vec::with_capacity(elems.len()); - // for expr in elems { - // let v = self.eval(expr, ctx).unpack(); - // list.push(v); - // } - // Value::List(Rc::new(list)) - // } pub fn eval(&self, expr: Expression, ctx: &mut Context) -> Val { match expr { @@ -88,8 +74,8 @@ impl Eval { Expression::Unary(_, _) => todo!(), Expression::Member(_, _) => todo!(), Expression::FunctionCall(_) => todo!(), - Expression::List(_) => todo!(), - Expression::Map(_) => todo!(), + Expression::List(values) => self.eval_list(values, ctx), + Expression::Map(entries) => self.eval_map(entries, ctx), Expression::Atom(atom) => self.eval_atom(atom, ctx), Expression::Ident(ident) => ctx .resolve(&ident) @@ -98,6 +84,26 @@ impl Eval { } } + fn eval_map(&self, entries: Vec<(Expression, Expression)>, ctx: &mut Context) -> Val { + let mut map = HashMap::with_capacity(entries.len()); + for (kexpr, vexpr) in entries { + let k = self.eval(kexpr, ctx); + let v = self.eval(vexpr, ctx); + map.insert(k, v); + } + Val::new_map(Rc::new(map)) + } + + + fn eval_list(&self, elems: Vec, ctx: &mut Context) -> Val { + let mut list = Vec::with_capacity(elems.len()); + for expr in elems { + let v = self.eval(expr, ctx); + list.push(v); + } + Val::new_list(Rc::new(list)) + } + pub fn eval_atom(&self, atom: Atom, ctx: &mut Context) -> Val { match atom { Atom::Int(i) => Val::new_int(i), diff --git a/cel-rs/src/lib.rs b/cel-rs/src/lib.rs index 673d7ed..a7268ba 100644 --- a/cel-rs/src/lib.rs +++ b/cel-rs/src/lib.rs @@ -6,4 +6,5 @@ mod parser; // public api pub use crate::program::Program; -pub use crate::context::Context; \ No newline at end of file +pub use crate::context::Context; +pub use value::value::{Val, Value}; \ No newline at end of file diff --git a/cel-rs/src/parser/cel.lalrpop b/cel-rs/src/parser/cel.lalrpop index 1a3c04e..dbf4767 100644 --- a/cel-rs/src/parser/cel.lalrpop +++ b/cel-rs/src/parser/cel.lalrpop @@ -1,4 +1,5 @@ use crate::parser::{RelationOp, ArithmeticOp, Expression, UnaryOp, Member, Atom}; +use crate::parser::parse; use std::rc::Rc; grammar; @@ -90,11 +91,15 @@ RelationOp: RelationOp = { Atom: Atom = { // Integer literals - r"-?[0-9]+" => Atom::Int(<>.parse().unwrap()), - r"-?0[xX]([0-9a-fA-F]+)" => Atom::Int(i64::from_str_radix(<>, 16).unwrap()), + + r"-?[0-9]+" => Atom::Int(<>.parse().expect("failed to parse int")), + + r"0[xX]([0-9a-fA-F]+)" => Atom::Int(i64::from_str_radix(<>.trim_start_matches("0x").trim_start_matches("0X"), 16).unwrap()), + r"-0[xX]([0-9a-fA-F]+)" => Atom::Int(-i64::from_str_radix(<>.trim_start_matches("-0x").trim_start_matches("-0x"), 16).unwrap()), + // LALRPOP does not support regex capture groups. https://github.com/lalrpop/lalrpop/issues/575 r"-?[0-9]+[uU]" => Atom::UInt(<>.trim_end_matches(|c| c == 'u' || c == 'U').parse().unwrap()), - r"-?0[xX]([0-9a-fA-F]+)[uU]" => Atom::UInt(u64::from_str_radix(<>.trim_end_matches(|c| c == 'u' || c == 'U'), 16).unwrap()), + r"0[xX]([0-9a-fA-F]+)[uU]" => Atom::UInt(u64::from_str_radix(<>.trim_start_matches("0x").trim_start_matches("0X").trim_end_matches(|c| c == 'u' || c == 'U'), 16).expect("heyo")), // Float with decimals and optional exponent r"([-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?)" => Atom::Float(<>.parse().unwrap()), @@ -102,12 +107,12 @@ Atom: Atom = { r"[-+]?[0-9]+[eE][-+]?[0-9]+" => Atom::Float(<>.parse().unwrap()), // Double quoted string - "r"? => Atom::String(Rc::new(s[1..s.len()-1].into())), - "r"? => Atom::String(Rc::new(s[3..s.len()-3].into())), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[1..s.len()-1]))), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[3..s.len()-3]))), // Single quoted string - "r"? => Atom::String(Rc::new(s[1..s.len()-1].into())), - "r"? => Atom::String(Rc::new(s[3..s.len()-3].into())), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[1..s.len()-1]))), + "r"? => Atom::String(Rc::new(parse::parse_str(&s[3..s.len()-3]))), // Double quoted bytes @@ -115,8 +120,8 @@ Atom: Atom = { r#"[bB]"""(\\.|[^"{3}])*""""# => Atom::Bytes(Vec::from(<>[4..<>.len()-3].as_bytes()).into()), // Single quoted bytes - r#"[bB]'(\\.|[^'\n])*'"# =>Atom::Bytes(Vec::from(<>[2..<>.len()-1].as_bytes()).into()), - r#"[bB]'''(\\.|[^'{3}])*'''"# => Atom::Bytes(Vec::from(<>[4..<>.len()-3].as_bytes()).into()), + r#"[bB]'(\\.|[^'\n])*'"# =>Atom::Bytes(Vec::from(parse::parse_str(&<>[2..<>.len()-1]).as_bytes()).into()), + r#"[bB]'''(\\.|[^'{3}])*'''"# => Atom::Bytes(Vec::from(parse::parse_str(&<>[4..<>.len()-3]).as_bytes()).into()), "true" => Atom::Bool(true), "false" => Atom::Bool(false), diff --git a/cel-rs/src/parser/mod.rs b/cel-rs/src/parser/mod.rs index 3d5750a..c2cc842 100644 --- a/cel-rs/src/parser/mod.rs +++ b/cel-rs/src/parser/mod.rs @@ -1,6 +1,7 @@ use lalrpop_util::lalrpop_mod; pub mod ast; +pub mod parse; pub use ast::*; lalrpop_mod!( diff --git a/cel-rs/src/parser/parse.rs b/cel-rs/src/parser/parse.rs new file mode 100644 index 0000000..9644cae --- /dev/null +++ b/cel-rs/src/parser/parse.rs @@ -0,0 +1,5 @@ +use unescape::unescape; + +pub fn parse_str(str: &str) -> String { + unescape(str).unwrap_or(String::new()) +} \ No newline at end of file diff --git a/cel-rs/src/program.rs b/cel-rs/src/program.rs index 0d3bbf3..7cd5566 100644 --- a/cel-rs/src/program.rs +++ b/cel-rs/src/program.rs @@ -113,6 +113,18 @@ pub mod tests { assert_eq!(eval_program!(r#"2 == 2"#), Val::new_bool(true)); } + #[test] + fn self_eval_int_hex_negative() { + let expr = r#"-0x55555555"#; + let program = crate::Program::new(expr); + assert!(program.is_ok(), "failed to parse '{}'", expr); + let program = program.unwrap(); + let mut ctx = crate::Context::default(); + let value = program.eval(&mut ctx); + let expected_value = crate::Val::new_int(-1431655765); + assert_eq!(value, expected_value, r#""{:?}" did not match "{:?}""#, value, expected_value); + } + // fn calc_string_string(args: Vec) -> Value { // println!("{:?}", args); diff --git a/cel-rs/src/value/bool.rs b/cel-rs/src/value/bool.rs index 1cc1546..e7082b1 100644 --- a/cel-rs/src/value/bool.rs +++ b/cel-rs/src/value/bool.rs @@ -32,12 +32,13 @@ impl Value for Bool { } fn compare(&self, other: &Val) -> Option { - other.as_bool().map(|ob| { - (&self.0).cmp(ob).into() - }) + other.as_bool().map(|ob| (&self.0).cmp(ob).into()) } - fn equals(&self, other: &Val) -> Option { - other.as_bool().map(|f| Val::new_bool(&self.0 == f)) + fn equals(&self, other: &Val) -> Val { + other + .as_bool() + .map(|f| Val::new_bool(&self.0 == f)) + .unwrap_or(Val::new_bool(false)) } } diff --git a/cel-rs/src/value/error.rs b/cel-rs/src/value/error.rs index bb27bbf..fef55d7 100644 --- a/cel-rs/src/value/error.rs +++ b/cel-rs/src/value/error.rs @@ -1,3 +1,5 @@ +use core::fmt; + use super::{value::{Value, Val}, ty::Ty}; #[derive(Eq, PartialEq)] @@ -6,6 +8,12 @@ pub struct Error { error: String, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "error(id = {:?}, message = {})", self.id, self.error) + } +} + impl Error { pub fn new(error: String) -> Val { return Val::new(Self { id: None, error }); diff --git a/cel-rs/src/value/map.rs b/cel-rs/src/value/map.rs new file mode 100644 index 0000000..e23197f --- /dev/null +++ b/cel-rs/src/value/map.rs @@ -0,0 +1,47 @@ +use super::ty::Ty; +use crate::{Val, Value}; +use std::{collections::HashMap, rc::Rc}; + +pub struct Map(Rc>); + +impl Map { + pub fn new(h: Rc>) -> Self { + Self(h) + } +} + +impl Value for Map { + fn ty(&self) -> super::ty::Ty { + Ty::Map + } + + fn native_value(&self) -> &dyn std::any::Any { + &self.0 + } + + fn equals(&self, other: &Val) -> Val { + other + .native_value() + .downcast_ref::>>() + .map(|other| { + if other.len() != self.0.len() { + return Val::new_bool(false); + } + + for (k, v) in self.0.iter() { + let ov = other.get(k); + if let Some(ov) = ov { + // TODO: use value.equals once all types support it. + if !ov.compare(v).is_some_and(|o| o.as_int() == Some(&0)) { + return Val::new_bool(false); + } + } else { + return Val::new_bool(false); + } + } + + Val::new_bool(true) + }) + .unwrap_or(Val::new_bool(false)) + } +} diff --git a/cel-rs/src/value/mod.rs b/cel-rs/src/value/mod.rs index 2a77172..608f5ae 100644 --- a/cel-rs/src/value/mod.rs +++ b/cel-rs/src/value/mod.rs @@ -12,3 +12,4 @@ pub mod bytes; pub mod double; pub mod uint; pub mod int; +pub mod map; diff --git a/cel-rs/src/value/value.rs b/cel-rs/src/value/value.rs index 7f8453d..3a32a99 100644 --- a/cel-rs/src/value/value.rs +++ b/cel-rs/src/value/value.rs @@ -1,4 +1,5 @@ use std::cmp; +use std::collections::HashMap; use std::{fmt, rc::Rc}; use crate::value::{error::Error, ty::Ty}; @@ -7,6 +8,7 @@ use super::bool::Bool; use super::bytes::Bytes; use super::double::Double; use super::int::Int; +use super::map::Map; use super::null::Null; use super::string::String as CELString; use super::uint::Uint; @@ -28,7 +30,7 @@ pub trait Value { unimplemented!("compare {:?} {:?}", self.ty(), other.ty()) } - fn equals(&self, other: &Val) -> Option { + fn equals(&self, other: &Val) -> Val { unimplemented!("equals {:?} {:?}", self.ty(), other.ty()) } } @@ -41,19 +43,72 @@ impl cmp::PartialOrd for Val { } } +impl std::hash::Hash for Val { + fn hash(&self, state: &mut H) { + state.write(format!("TODO:{:?}", &self).as_bytes()); + } +} + +impl Eq for Val {} + impl PartialEq for Val { fn eq(&self, other: &Self) -> bool { - self.partial_cmp(other).is_some_and(|ord| ord == cmp::Ordering::Equal) + // TODO: switch other types to use equals instead. + if self.ty() == Ty::Map { + eprintln!("equals map"); + return self.equals(other).as_bool().expect("equals did not return bool").to_owned(); + } + self.partial_cmp(other) + .is_some_and(|ord| ord == cmp::Ordering::Equal) } } impl fmt::Debug for Val { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Val(ty = {:?}", self.ty())?; + // TODO: maybe replace this with a call to self.to_type(Ty::String).as_string()? match self.ty() { Ty::Bool => write!(f, ", value = {}", self.as_bool().unwrap()), - Ty::Int => write!(f, ", value = {}", self.native_value().downcast_ref::().unwrap()), - _ => Ok(()) + Ty::Int => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::UInt => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Double => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::String => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Bytes => write!( + f, + ", value = {:?}", + self.native_value().downcast_ref::>>().unwrap() + ), + Ty::List => write!(f, ", value = TODO"), + Ty::Map => write!(f, ", value = {:?}", self.native_value().downcast_ref::>>().unwrap()), + Ty::Null => write!(f, ", value = null"), + Ty::Type => write!( + f, + ", value = {:?}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Unknown => write!(f, ", value = ?"), + Ty::Error => write!( + f, + ", value = {}", + self.native_value().downcast_ref::().unwrap() + ), + Ty::Dyn => write!(f, ", value = dyn"), }?; write!(f, ")") } @@ -65,7 +120,6 @@ impl Clone for Val { } } - impl Val { pub fn new(v: impl Value + 'static) -> Self { Self(Rc::new(v)) @@ -95,10 +149,18 @@ impl Val { pub fn new_int(i: i64) -> Self { Self::new(Int::new(i)) } - + pub fn new_map(h: Rc>) -> Self { + Self::new(Map::new(h)) + } + pub fn new_list(b: Rc>) -> Self { + Self::new(Null::new()) + } pub fn as_bool(&self) -> Option<&bool> { return self.0.native_value().downcast_ref::(); } + pub fn as_int(&self) -> Option<&i64> { + return self.0.native_value().downcast_ref::(); + } } impl Value for Val { @@ -112,7 +174,23 @@ impl Value for Val { self.0.native_value() } + #[inline] fn compare(&self, other: &Val) -> Option { self.0.compare(other) - } + } + + #[inline] + fn equals(&self, other: &Val) -> Val { + self.0.equals(other) + } + + #[inline] + fn to_bool(&self) -> Val { + self.0.to_bool() + } + + #[inline] + fn to_type(&self, ty: Ty) -> Val { + self.0.to_type(ty) + } } diff --git a/cel-rs/tests/test.rs b/cel-rs/tests/test.rs index 6239304..e03d16b 100644 --- a/cel-rs/tests/test.rs +++ b/cel-rs/tests/test.rs @@ -1,11 +1,12 @@ -// use cel_spec; +use cel_spec; -// cel_spec::suite!( -// name = "basic", -// include = "self_eval_zeroish", -// include = "self_eval_nonzeroish", -// include = "variables", -// // include = "ffunctions", -// // include = "reserved_const", -// ); +cel_spec::suite!( + name = "basic", + // TODO: fix these + skip_section = "variables", + skip_section = "functions", + skip_test = "self_eval_ascii_escape_seq", + skip_test = "self_eval_bytes_invalid_utf8", + skip_test = "self_eval_unicode_escape_eight" +); diff --git a/cel-spec/Cargo.toml b/cel-spec/Cargo.toml index d511d0f..ffc3d18 100644 --- a/cel-spec/Cargo.toml +++ b/cel-spec/Cargo.toml @@ -13,6 +13,9 @@ prost-reflect = { version = "0.13.0", features = ["text-format"] } syn = { version = "2.0.55", features = ["parsing"] } static_init = "1.0.3" darling = "0.20.8" +lazy_static = "1.4.0" -[build_dependencies] +[build-dependencies] prost-build = "0.12.3" +protoc = "2.28.0" +protoc-prebuilt = "0.3.0" diff --git a/cel-spec/build.rs b/cel-spec/build.rs index e755986..b8d90c5 100644 --- a/cel-spec/build.rs +++ b/cel-spec/build.rs @@ -1,13 +1,78 @@ -use std::{env, io::Result, path::PathBuf}; +use std::{ + env, + fs::File, + io::{Result, Write}, + path::PathBuf, + process::{Command, Stdio}, +}; +use std::fs; fn main() -> Result<()> { - let out = env::var("OUT_DIR").expect("OUT_DIR environment variable not set"); - let out = PathBuf::from(out).join("cel.bin"); + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set")); + let mut config = prost_build::Config::new(); config.disable_comments(&["."]); - config.file_descriptor_set_path(out); + config.file_descriptor_set_path(out.join("cel.bin")); config.compile_protos( &["cel-spec/proto/test/v1/simple.proto"], &["cel-spec/proto/", "googleapis/"], - ) + )?; + + let protoc = protoc_prebuilt::init("27.0").expect("failed to download protoc"); + + let mut stamp = String::from("use std::collections::HashMap;\n"); + stamp.push_str("use std::sync::Mutex;\n"); + + let mut items = String::new(); + + for s in fs::read_dir("cel-spec/tests/simple/testdata").unwrap() { + let filename = PathBuf::from("cel-spec/tests/simple/testdata") + .join(s.unwrap().file_name().to_str().unwrap()); + + if !filename.is_file() { + continue; + } + if !filename.extension().is_some_and(|f| f == "textproto") { + continue; + } + + let output = File::create(out.join(filename.with_extension("bin").file_name().unwrap()))?; + let name = filename.with_extension(""); + let name = name.file_name().unwrap().to_string_lossy(); + stamp.push_str("\n"); + stamp.push_str( + format!( + r#"const {name}: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/{out}.bin"));"#, + name = name.to_uppercase(), + out = name + ) + .as_str(), + ); + + items.push_str("\n\t"); + items.push_str(format!(r#"("{name}", {var}),"#, name = name, var = name.to_uppercase()).as_str()); + + let input = File::open(filename)?; + + Command::new(&protoc.0) + .arg("-Icel-spec/proto/") + .arg("-Igoogleapis/") + .arg("--encode=google.api.expr.test.v1.SimpleTestFile") + .arg("cel-spec/proto/test/v1/simple.proto") + .stdin(Stdio::from(input)) + .stdout(Stdio::from(output)) + .stderr(Stdio::inherit()) + .spawn()? + .wait()?; + } + + stamp.push_str("\n"); + stamp.push_str("lazy_static::lazy_static! {\n"); + stamp.push_str("static ref TESTS: Mutex> = Mutex::new(HashMap::from(["); + + stamp.extend(items.chars().into_iter()); + stamp.push_str("\n]));\n}"); + File::create(out.join("tests.rs"))?.write_all(stamp.as_bytes())?; + + Ok(()) } diff --git a/cel-spec/src/lib.rs b/cel-spec/src/lib.rs index 103e943..6d87e3d 100644 --- a/cel-spec/src/lib.rs +++ b/cel-spec/src/lib.rs @@ -1,131 +1,132 @@ -use std::io::Read; -use proc_macro::TokenStream; -use static_init::dynamic; -use darling::{Error, FromMeta}; +mod google { + mod rpc { + include!(concat!(env!("OUT_DIR"), "/google.rpc.rs")); + } + pub mod api { + pub mod expr { + pub mod v1alpha1 { + include!(concat!(env!("OUT_DIR"), "/google.api.expr.v1alpha1.rs")); + } + pub mod test { + pub mod v1 { + include!(concat!(env!("OUT_DIR"), "/google.api.expr.test.v1.rs")); + } + } + } + } +} + +include!(concat!(env!("OUT_DIR"), "/tests.rs")); + use darling::ast::NestedMeta; -use prost_reflect::{DescriptorPool, MessageDescriptor, DynamicMessage}; - -const BYTES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/cel.bin")); -#[dynamic] -static MESSAGE_DESCRIPTOR: MessageDescriptor = { - let pool = DescriptorPool::decode(BYTES.as_ref()).unwrap(); - pool - .get_message_by_name("google.api.expr.test.v1.SimpleTestFile") - .unwrap() -}; - -fn get_expected_value(value: &DynamicMessage) -> String { - let field = value.fields().next().expect("get expected value"); - let (m, v, c) = match field.0.name() { - "string_value" => ( - "String(std::rc::Rc::new(String::from_utf8(", - format!("{:?}", field.1.as_str().unwrap().as_bytes().to_vec()), - ".to_vec()).unwrap()))", - ), - "bool_value" => ("Bool(", format!("{}", field.1.as_bool().unwrap()), ")"), - "int64_value" => ("Int(", format!("{}", field.1.as_i64().unwrap()), ")"), - "uint64_value" => ("UInt(", format!("{}", field.1.as_u64().unwrap()), ")"), - "double_value" => ( - "Float(", - format!("({}).into()", field.1.as_f64().unwrap()), - ")", - ), - "map_value" => ( - "Map(", - String::from("ordered_hash_map::OrderedHashMap::new().into()"), - ")", +use darling::{Error, FromMeta}; +use google::api::expr::test::v1::{simple_test::ResultMatcher, SimpleTestFile}; +use google::api::expr::v1alpha1::value::Kind; +use google::api::expr::v1alpha1::Value; +use proc_macro::TokenStream; +use prost::Message; + +fn expand_value(val: Value) -> String { + match val.kind.unwrap() { + Kind::NullValue(_) => "cel_rs::Val::new_null()".to_string(), + Kind::BoolValue(b) => format!("cel_rs::Val::new_bool({})", b), + Kind::Int64Value(i) => format!("cel_rs::Val::new_int({})", i), + Kind::Uint64Value(ui) => format!("cel_rs::Val::new_uint({})", ui), + Kind::DoubleValue(db) => format!("cel_rs::Val::new_double({}f64)", db), + Kind::StringValue(str) => format!( + "cel_rs::Val::new_string(std::rc::Rc::new(String::from_utf8({:?}.to_vec()).unwrap()))", + str.as_bytes() ), - "list_value" => ("List(", String::from("Vec::new().into()"), ")"), - "bytes_value" => ( - "Bytes(std::rc::Rc::new(Vec::from(", - format!("{:?}", field.1.as_bytes().unwrap().to_vec()), - ")))", + Kind::BytesValue(bytes) => { + format!("cel_rs::Val::new_bytes(Vec::from({:?}).into())", bytes) + } + Kind::MapValue(map) => format!( + "cel_rs::Val::new_map(std::collections::HashMap::::from([{}]).into())", + map.entries.iter().map(|entry| { + let key = entry.key.clone().unwrap(); + let value = entry.value.clone().unwrap(); + + format!("({}, {}),", expand_value(key), expand_value(value)) + }).collect::>().join("\n") ), - _ => ("Null", String::new(), ""), - }; - format!("cel_rs::value::Value::{}{}{}", m, v, c) + Kind::ListValue(list) => format!("cel_rs::Val::new_list({})", "Vec::new().into()"), + Kind::EnumValue(en) => "TODO".to_string(), + Kind::ObjectValue(obj) => "TODO".to_string(), + Kind::TypeValue(ty) => "TODO".to_string(), + } +} + +fn expand_result_matcher(rm: Option) -> String { + if rm.is_none() { + panic!("result matcher is none."); + } + if let ResultMatcher::Value(val) = rm.unwrap() { + expand_value(val) + } else { + String::from("TODO") + } } #[derive(Debug, FromMeta)] struct MacroArgs { name: String, - #[darling(multiple, rename = "include")] - includes: Vec, + #[darling(multiple, rename = "skip_section")] + skip_sections: Vec, + #[darling(multiple, rename = "skip_test")] + skip_tests: Vec, } - #[proc_macro] pub fn suite(rargs: TokenStream) -> TokenStream { - let attr_args = match NestedMeta::parse_meta_list(rargs.into()) { Ok(v) => v, - Err(e) => { return TokenStream::from(Error::from(e).write_errors()); } + Err(e) => { + return TokenStream::from(Error::from(e).write_errors()); + } }; let args = match MacroArgs::from_list(&attr_args) { Ok(v) => v, - Err(e) => { return TokenStream::from(e.write_errors()); } + Err(e) => { + return TokenStream::from(e.write_errors()); + } }; - let mut file = std::fs::File::open(format!( - "{}/cel-spec/tests/simple/testdata/{}.textproto", - env!("CARGO_MANIFEST_DIR"), - args.name - )) - .expect("could not find the suite"); - - let mut content = String::new(); - file.read_to_string(&mut content) - .expect("can not read the suite file"); - let suite = DynamicMessage::parse_text_format(MESSAGE_DESCRIPTOR.to_owned(), &content).unwrap(); + let mut lock = TESTS.lock().unwrap(); + let mut buf = lock.get_mut(args.name.as_str()).unwrap(); + let testfile = SimpleTestFile::decode(&mut buf).expect("msg"); let mut ast = String::new(); - for section in suite - .get_field_by_name("section") - .unwrap() - .as_list() - .unwrap() - { - let section = section.as_message().unwrap(); - let sname = section.get_field_by_name("name").unwrap(); - let sname = sname.as_str().unwrap(); - let includes = &args.includes; - if includes.into_iter().find(|p| p.as_str() == sname).is_none(){ - println!("skip {}", &sname); + for section in testfile.section { + + if args.skip_sections.contains(§ion.name){ continue; } - ast.push_str(format!("pub mod {}{{", sname).as_str()); + ast.push_str("pub mod "); + ast.push_str(section.name.as_str()); + ast.push_str("{"); - for case in section - .get_field_by_name("test") - .expect("test") - .as_list() - .expect("test as list") - { - let case = case.as_message().expect("message as case"); - let name = case.get_field_by_name("name").expect("expected name"); - let expr = case.get_field_by_name("expr").expect("expected expr"); - let value = case.get_field_by_name("value").expect("expected value"); + for test in section.test { + if args.skip_tests.contains(&test.name){ + continue; + } - let name = name.as_str().expect("name as str"); - let expr = expr.as_str().expect("expr as str"); - let value = value.as_message().expect("value as message"); - let expected_value = get_expected_value(value); + let expected_value = expand_result_matcher(test.result_matcher); ast.push_str(&format!(r##" #[test] - fn {name}() {{ + fn r#{name}() {{ let expr = r#"{expr}"#; let program = cel_rs::Program::new(expr); assert!(program.is_ok(), "failed to parse '{{}}'", expr); let program = program.unwrap(); - let mut ctx = cel_rs::context::Context::default(); + let mut ctx = cel_rs::Context::default(); let value = program.eval(&mut ctx); let expected_value = {expected_value}; - assert_eq!(value, expected_value, r#""{{}}" did not match "{{}}""#, value, expected_value); + assert_eq!(value, expected_value); }} - "##, name = name, expr = expr, expected_value = expected_value ).to_string()); + "##, name = test.name, expr = test.expr, expected_value = expected_value ).to_string()); } ast.push_str("}");