#![allow(clippy::cyclomatic_complexity)] extern crate num_bigint; extern crate num_rational; extern crate num_traits; #[macro_use] extern crate over; mod errors; use num_traits::ToPrimitive; use over::obj::Obj; use over::types::Type; use over::value::Value; // Display nicely-formatted values on failure. macro_rules! test_eq { ($left:expr, $right:expr) => {{ if $left != $right { panic!(format!( "Left did not equal right.\nLeft: {:?}\nRight: {:?}\n", $left, $right )); } }}; } // Make comparisons with ints a bit more concise. fn get_int(obj: &Obj, field: &str) -> i64 { obj.get_int(field).unwrap().to_i64().unwrap() } // Test parsing of empty file. #[test] fn empty() { let obj = Obj::from_file("tests/test_files/empty.over").unwrap(); test_eq!(obj.len(), 0); } // Test reading basic Ints, Strs, Bools, and Null. // Also test that whitespace and comments are correctly ignored. #[test] fn basic() { let obj = Obj::from_file("tests/test_files/basic.over").unwrap(); test_eq!(get_int(&obj, "_a1"), 1); test_eq!(get_int(&obj, "a2"), 2); test_eq!(get_int(&obj, "_"), 0); test_eq!(obj.get("b").unwrap(), "Smörgåsbord"); test_eq!(get_int(&obj, "c"), 10); test_eq!(get_int(&obj, "d"), 20); test_eq!(get_int(&obj, "eee"), 2); test_eq!(get_int(&obj, "f"), 3); test_eq!(get_int(&obj, "g_"), 4); test_eq!(obj.get("Hello").unwrap(), "Hello"); test_eq!(obj.get("i_robot").unwrap(), "not #a comment"); test_eq!(get_int(&obj, "j"), 4); test_eq!(obj.get("k").unwrap(), "hi"); test_eq!(obj.get("l").unwrap(), "$\\\""); test_eq!(obj.get("m").unwrap(), "m"); test_eq!(obj.get("n").unwrap(), true); test_eq!(obj.get("o").unwrap(), false); test_eq!(obj.get("p").unwrap(), "Hello"); test_eq!(get_int(&obj, "q"), 0); test_eq!(obj.get("r").unwrap(), Value::Null); test_eq!(obj.get("s").unwrap(), '\''); test_eq!(obj.get("t").unwrap(), '\n'); test_eq!(obj.get("u").unwrap(), ' '); test_eq!(obj.get("v").unwrap(), '\''); test_eq!(obj.get("w").unwrap(), '$'); test_eq!(obj.get_frac("x").unwrap(), frac!(1, 1)); test_eq!(obj.get("x").unwrap().get_frac().unwrap(), frac!(1, 1)); } // Test the example from the README. #[test] fn example() { let obj = Obj::from_file("tests/test_files/example.over").unwrap(); assert_eq!(obj.get("receipt").unwrap(), "Oz-Ware Purchase Invoice"); assert_eq!(obj.get("date").unwrap(), "2012-08-06"); assert_eq!( obj.get("customer").unwrap(), obj! { "first_name" => "Dorothy", "family_name" => "Gale" } ); assert_eq!( obj.get("items").unwrap(), arr![ obj! { "part_no" => "A4786", "descrip" => "Water Bucket (Filled)", "price" => frac!(147,100), "quantity" => 4 }, obj! { "part_no" => "E1628", "descrip" => "High Heeled \"Ruby\" Slippers", "size" => 8, "price" => frac!(1337,10), "quantity" => 1 }, ] ); assert!( obj.get("bill_to").unwrap() == obj! { "street" => "123 Tornado Alley\nSuite 16", "city" => "East Centerville", "state" => "KS", } || obj.get("bill_to").unwrap() == obj! { "street" => "123 Tornado Alley\r\nSuite 16", "city" => "East Centerville", "state" => "KS", } ); assert_eq!(obj.get("ship_to").unwrap(), obj.get("bill_to").unwrap()); assert_eq!( obj.get("specialDelivery").unwrap(), "Follow the Yellow Brick Road to the Emerald City. \ Pay no attention to the man behind the curtain." ); } // Test parsing of sub-Objs. #[test] fn obj() { let obj = Obj::from_file("tests/test_files/obj.over").unwrap(); test_eq!(obj.get_obj("empty").unwrap().len(), 0); test_eq!(obj.get_obj("empty2").unwrap().len(), 0); assert!(!obj.contains("bools")); let bools = obj! {"t" => true, "f" => false}; let outie = obj.get_obj("outie").unwrap(); test_eq!(outie.get_parent().unwrap(), bools); test_eq!(get_int(&outie, "z"), 0); let inner = outie.get_obj("inner").unwrap(); test_eq!(get_int(&inner, "z"), 1); let innie = inner.get_obj("innie").unwrap(); test_eq!(get_int(&innie, "a"), 1); test_eq!(inner.get("b").unwrap(), tup!(1, 2,)); test_eq!(get_int(&outie, "c"), 3); test_eq!(outie.get("d").unwrap(), obj! {}); let obj_arr = obj.get_obj("obj_arr").unwrap(); test_eq!(obj_arr.get("arr").unwrap(), arr![1, 2, 3]); test_eq!(obj.get_int("dot").unwrap(), int!(1)); test_eq!(obj.get_bool("dot_glob").unwrap(), true); test_eq!(obj.get_int("dot_tup1").unwrap(), int!(1)); test_eq!(obj.get_int("dot_tup2").unwrap(), int!(2)); test_eq!(obj.get_int("dot_arr").unwrap(), int!(1)); test_eq!(obj.get_int("dot_op").unwrap(), int!(4)); test_eq!(obj.get_str("dot_var").unwrap(), "test"); assert_eq!(obj.iter().count(), 13); let value = obj.values().last(); assert!(!value.unwrap().is_null()); } // Test that globals are referenced correctly and don't get included as fields. #[test] fn globals() { let obj = Obj::from_file("tests/test_files/globals.over").unwrap(); let sub = obj.get_obj("sub").unwrap(); test_eq!(sub.get_int("a").unwrap(), int!(1)); test_eq!(get_int(&sub, "b"), 2); test_eq!(sub.len(), 2); test_eq!(get_int(&obj, "c"), 2); test_eq!(obj.len(), 2); } // Test parsing of numbers. #[test] fn numbers() { let obj = Obj::from_file("tests/test_files/numbers.over").unwrap(); test_eq!(get_int(&obj, "neg"), -4); test_eq!(obj.get_frac("pos").unwrap(), frac!(4, 1)); test_eq!(obj.get_frac("neg_zero").unwrap(), frac!(0, 1)); test_eq!(obj.get_frac("pos_zero").unwrap(), frac!(0, 1)); test_eq!(obj.get("frac_from_dec").unwrap(), frac!(13, 10)); test_eq!(obj.get("neg_ffd").unwrap(), frac!(-13, 10)); test_eq!(obj.get("pos_ffd").unwrap(), frac!(13, 10)); test_eq!(obj.get("add_dec").unwrap(), frac!(3, 1)); test_eq!(obj.get("sub_dec").unwrap(), frac!(-3, 1)); let frac = obj.get_frac("big_frac").unwrap(); assert!(frac > frac!(91_000_000, 1)); assert!(frac < frac!(92_000_000, 1)); test_eq!(obj.get("frac1").unwrap(), frac!(1, 2)); test_eq!(obj.get("frac2").unwrap(), frac!(1, 2)); test_eq!(obj.get("frac3").unwrap(), frac!(0, 10)); test_eq!(obj.get("frac4").unwrap(), frac!(-5, 4)); test_eq!(obj.get("frac5").unwrap(), frac!(1, 1)); test_eq!(obj.get("whole_frac").unwrap(), frac!(3, 2)); test_eq!(obj.get("neg_whole_frac").unwrap(), frac!(-21, 4)); test_eq!(obj.get("dec_frac").unwrap(), frac!(1, 2)); test_eq!(obj.get("dec_frac2").unwrap(), frac!(-1, 2)); test_eq!( obj.get("array").unwrap(), arr![ obj.get_frac("whole_frac").unwrap(), frac!(-1, 2), frac!(3, 2), frac!(1, 1), ] ); test_eq!( obj.get("tup").unwrap(), tup!( frac!(-1, 2), obj.get_frac("whole_frac").unwrap(), frac!(1, 1), frac!(3, 2), ) ); test_eq!(obj.get("var_frac").unwrap(), frac!(-1, 2)); } #[test] fn operations() { let obj = Obj::from_file("tests/test_files/operations.over").unwrap(); test_eq!(obj.get("mod1").unwrap(), int!(5)); test_eq!(obj.get("mod2").unwrap(), int!(0)); test_eq!(obj.get("arr1").unwrap(), arr![3, 4]); test_eq!(obj.get("arr2").unwrap(), arr![3, 4]); test_eq!(obj.get("arr3").unwrap(), arr![3, 4]); test_eq!(obj.get("arr4").unwrap(), arr![arr![1]]); test_eq!( obj.get("arr_complex").unwrap(), arr![arr![arr![1, 2]], arr![arr![3]]] ); test_eq!(obj.get("str1").unwrap(), "cat"); test_eq!(obj.get("str2").unwrap(), "cat"); test_eq!(obj.get("str3").unwrap(), "cat"); test_eq!(obj.get("str4").unwrap(), "cat"); test_eq!( obj.get("tup_complex").unwrap(), tup!(arr![arr![], arr![arr![], arr![1, 2, 3], arr![]]]) ); } #[test] fn any_type() { let obj = Obj::from_file("tests/test_files/any_type.over").unwrap(); let arr1 = obj.get("arr1").unwrap(); let arr2 = arr![arr![arr![]], arr![arr![2]], arr![arr![]]]; test_eq!(arr1.get_type(), Type::Arr(Box::new(arr2.inner_type()))); test_eq!(arr1, arr2); test_eq!( obj.get("arr2").unwrap(), arr![ tup!(arr![arr![]], arr![arr![2]]), tup!(arr![arr![2]], arr![arr![]]), ] ); } #[test] fn includes() { let obj = Obj::from_file("tests/test_files/includes.over").unwrap(); // Test both \n and \r\n line endings. let s = "Multi-line string\nwhich should be included verbatim\r\n\ in another file. \"Quotes\" and $$$\ndon't need to be escaped.\n"; test_eq!(obj.get("include").unwrap(), s); test_eq!(obj.get("include2").unwrap(), obj.get("include").unwrap()); test_eq!(obj.get("include_arr").unwrap(), arr![1, 2, 3, 4, 5]); test_eq!( obj.get("include_tup").unwrap(), tup!("hello", 1, 'c', frac!(3, 3)) ); let o = obj.get_obj("include_obj").unwrap(); test_eq!( o, obj! { "obj2" => obj!{"test" => 1}, "obj3" => obj!{"test" => 2}, "dup" => obj!{"test" => 2}, } ); assert!(o.ptr_eq(&obj.get_obj("include_obj2").unwrap())); } // TODO: Test multi-line.over (need substitution) // Test writing objects to files. #[test] fn write() { let write_path = "tests/test_files/write.over"; macro_rules! write_helper { ($filename:expr) => {{ let obj1 = Obj::from_file($filename).unwrap(); obj1.write_to_file(write_path).unwrap(); let obj2 = Obj::from_file(write_path).unwrap(); test_eq!(obj1, obj2); }}; } write_helper!("tests/test_files/basic.over"); write_helper!("tests/test_files/empty.over"); write_helper!("tests/test_files/includes.over"); write_helper!("tests/test_files/obj.over"); write_helper!("tests/test_files/numbers.over"); write_helper!("tests/test_files/example.over"); write_helper!("tests/test_files/fuzz1.over"); write_helper!("tests/test_files/fuzz2.over"); write_helper!("tests/test_files/fuzz3.over"); }