use insta::assert_snapshot;
use monty::MontyRun;
use monty_types::{CompileOptions, ExcType, MontyObject};
fn eval(code: &str) -> MontyObject {
let ex = MontyRun::new(code.to_owned(), "test.py", vec![], CompileOptions::default()).unwrap();
ex.run_no_limits(vec![]).unwrap()
}
fn to_json(obj: &MontyObject) -> String {
serde_json::to_string(obj).unwrap()
}
#[test]
fn json_output_primitives() {
assert_snapshot!(to_json(&MontyObject::Int(42)), @r#"{"Int":42}"#);
assert_snapshot!(to_json(&MontyObject::Float(1.5)), @r#"{"Float":1.5}"#);
assert_snapshot!(to_json(&MontyObject::String("hi".into())), @r#"{"String":"hi"}"#);
assert_snapshot!(to_json(&MontyObject::Bool(true)), @r#"{"Bool":true}"#);
assert_snapshot!(to_json(&MontyObject::None), @r#""None""#);
}
#[test]
fn json_output_list() {
assert_snapshot!(
to_json(&eval("[1, 'two', 3.0]")),
@r#"{"List":[{"Int":1},{"String":"two"},{"Float":3.0}]}"#
);
}
#[test]
fn json_output_dict() {
assert_snapshot!(
to_json(&eval("{'a': 1, 'b': 2}")),
@r#"{"Dict":[[{"String":"a"},{"Int":1}],[{"String":"b"},{"Int":2}]]}"#
);
}
#[test]
fn json_output_tuple() {
assert_snapshot!(
to_json(&eval("(1, 'two')")),
@r#"{"Tuple":[{"Int":1},{"String":"two"}]}"#
);
}
#[test]
fn json_output_bytes() {
assert_snapshot!(to_json(&eval("b'hi'")), @r#"{"Bytes":[104,105]}"#);
}
#[test]
fn json_output_ellipsis() {
assert_snapshot!(to_json(&eval("...")), @r#""Ellipsis""#);
}
#[test]
fn json_output_exception() {
let obj = MontyObject::Exception {
exc_type: ExcType::ValueError,
arg: Some("test".to_string()),
};
assert_snapshot!(to_json(&obj), @r#"{"Exception":{"exc_type":"ValueError","arg":"test"}}"#);
}
#[test]
fn json_output_repr() {
let obj = MontyObject::Repr("<function foo>".to_string());
assert_snapshot!(to_json(&obj), @r#"{"Repr":"<function foo>"}"#);
}
#[test]
fn json_output_builtin_function() {
assert_snapshot!(to_json(&eval("print")), @r#"{"BuiltinFunction":"print"}"#);
}
#[test]
fn json_deserialize_builtin_function() {
let obj: MontyObject = serde_json::from_str(r#"{"BuiltinFunction":"print"}"#).unwrap();
assert_eq!(obj, eval("print"));
}
#[test]
fn json_output_cycle_list() {
assert_snapshot!(
to_json(&eval("a = []; a.append(a); a")),
@r#"{"List":[{"Cycle":[1,"[...]"]}]}"#
);
}
#[test]
fn json_output_cycle_dict() {
assert_snapshot!(
to_json(&eval("d = {}; d['self'] = d; d")),
@r#"{"Dict":[[{"String":"self"},{"Cycle":[1,"{...}"]}]]}"#
);
}
#[test]
fn json_deserialize_primitives() {
let int: MontyObject = serde_json::from_str(r#"{"Int":42}"#).unwrap();
let float: MontyObject = serde_json::from_str(r#"{"Float":2.5}"#).unwrap();
let string: MontyObject = serde_json::from_str(r#"{"String":"hello"}"#).unwrap();
let bool_val: MontyObject = serde_json::from_str(r#"{"Bool":true}"#).unwrap();
let null: MontyObject = serde_json::from_str(r#""None""#).unwrap();
assert_eq!(int, MontyObject::Int(42));
assert_eq!(float, MontyObject::Float(2.5));
assert_eq!(string, MontyObject::String("hello".to_string()));
assert_eq!(bool_val, MontyObject::Bool(true));
assert_eq!(null, MontyObject::None);
}
#[test]
fn json_deserialize_list() {
let list: MontyObject = serde_json::from_str(r#"{"List":[{"Int":1},{"String":"two"},{"Float":3.0}]}"#).unwrap();
assert_eq!(
list,
MontyObject::List(vec![
MontyObject::Int(1),
MontyObject::String("two".to_string()),
MontyObject::Float(3.0)
])
);
}
#[test]
fn json_deserialize_dict() {
let dict: MontyObject =
serde_json::from_str(r#"{"Dict":[[{"String":"a"},{"Int":1}],[{"String":"b"},{"Int":2}]]}"#).unwrap();
if let MontyObject::Dict(pairs) = dict {
let pairs_vec: Vec<_> = pairs.into_iter().collect();
assert_eq!(pairs_vec.len(), 2);
assert_eq!(
pairs_vec[0],
(MontyObject::String("a".to_string()), MontyObject::Int(1))
);
assert_eq!(
pairs_vec[1],
(MontyObject::String("b".to_string()), MontyObject::Int(2))
);
} else {
panic!("expected Dict");
}
}
#[test]
fn json_roundtrip() {
let ex = MontyRun::new(
"{'items': [1, 'two', None], 'flag': True}".to_owned(),
"test.py",
vec![],
CompileOptions::default(),
)
.unwrap();
let result = ex.run_no_limits(vec![]).unwrap();
let json = serde_json::to_string(&result).unwrap();
let parsed: MontyObject = serde_json::from_str(&json).unwrap();
assert_eq!(result, parsed);
}
#[test]
fn json_roundtrip_empty() {
let list: MontyObject = serde_json::from_str(r#"{"List":[]}"#).unwrap();
let dict: MontyObject = serde_json::from_str(r#"{"Dict":[]}"#).unwrap();
assert_eq!(serde_json::to_string(&list).unwrap(), r#"{"List":[]}"#);
assert_eq!(serde_json::to_string(&dict).unwrap(), r#"{"Dict":[]}"#);
}
#[test]
fn cycle_equality_same_id() {
let ex = MontyRun::new(
"a = []; a.append(a); [a, a]".to_owned(),
"test.py",
vec![],
CompileOptions::default(),
)
.unwrap();
let result = ex.run_no_limits(vec![]).unwrap();
if let MontyObject::List(outer) = &result {
assert_eq!(outer.len(), 2, "outer list should have 2 elements");
if let (MontyObject::List(inner1), MontyObject::List(inner2)) = (&outer[0], &outer[1]) {
assert_eq!(inner1.len(), 1);
assert_eq!(inner2.len(), 1);
assert_eq!(inner1[0], inner2[0], "cycles referencing same object should be equal");
assert!(matches!(&inner1[0], MontyObject::Cycle(..)));
} else {
panic!("expected inner lists");
}
} else {
panic!("expected outer list");
}
}
#[test]
fn cycle_equality_different_ids() {
let ex = MontyRun::new(
"a = []; a.append(a); b = []; b.append(b); [a, b]".to_owned(),
"test.py",
vec![],
CompileOptions::default(),
)
.unwrap();
let result = ex.run_no_limits(vec![]).unwrap();
if let MontyObject::List(outer) = &result {
assert_eq!(outer.len(), 2, "outer list should have 2 elements");
if let (MontyObject::List(inner1), MontyObject::List(inner2)) = (&outer[0], &outer[1]) {
assert_eq!(inner1.len(), 1);
assert_eq!(inner2.len(), 1);
assert_ne!(
inner1[0], inner2[0],
"cycles referencing different objects should not be equal"
);
if let (MontyObject::Cycle(id1, ph1), MontyObject::Cycle(id2, ph2)) = (&inner1[0], &inner2[0]) {
assert_ne!(id1, id2, "heap IDs should differ");
assert_eq!(ph1, ph2, "placeholders should match (both are lists)");
assert_eq!(*ph1, "[...]");
} else {
panic!("expected Cycle variants");
}
} else {
panic!("expected inner lists");
}
} else {
panic!("expected outer list");
}
}