use std::collections::HashMap;
use interpretthis::{Interpreter, InterpreterConfig, InterpreterDeps, Tools};
fn interpreter() -> Interpreter {
Interpreter::new(InterpreterDeps { tools: Tools::new() }, InterpreterConfig::default())
}
fn no_tools() -> Tools {
Tools::new()
}
#[tokio::test]
async fn state_basic_round_trip_preserves_variables() {
let interp = interpreter();
let resp = interp.execute("x = 42\ny = 'hello'", &no_tools(), HashMap::new()).await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(f'{x},{y}')", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "42,hello");
}
#[tokio::test]
async fn state_function_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r#"
def greet(name):
return "Hello, " + name
"#,
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(greet('World'))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "Hello, World");
}
#[tokio::test]
async fn state_nested_function_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r"
def outer(x):
def inner(y):
return x + y
return inner(10)
",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(outer(5))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "15");
}
#[tokio::test]
async fn state_recursive_function_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r"
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(factorial(6))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "720");
}
#[tokio::test]
async fn state_function_with_default_args_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r#"
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
"#,
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(greet('World'))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "Hello, World!");
}
#[tokio::test]
async fn state_function_with_kwargs_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r"
def make_dict(**kwargs):
return str(kwargs)
",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(make_dict(a=1, b=2))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert!(!resp2.stdout.trim().is_empty());
}
#[tokio::test]
async fn state_closure_persistence() {
let interp = interpreter();
let resp = interp
.execute(
r"
multiplier = 10
def scale(x):
return x * multiplier
",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2.execute("print(scale(5))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "50");
}
#[tokio::test]
async fn state_keys_after_export_import() {
let interp = interpreter();
let resp = interp.execute("x = 1\ny = 2\n_internal = 3", &no_tools(), HashMap::new()).await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let keys = interp2.state_keys();
assert!(keys.contains(&"x".to_string()));
assert!(keys.contains(&"y".to_string()));
assert!(!keys.contains(&"_internal".to_string()));
}
#[tokio::test]
async fn state_data_structures_persistence() {
let interp = interpreter();
let resp = interp
.execute(
"my_list = [1, 2, 3]\nmy_dict = {'a': 1}\nmy_tuple = (4, 5)",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2
.execute("print(f'{my_list},{my_dict},{my_tuple}')", &no_tools(), HashMap::new())
.await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert!(!resp2.stdout.trim().is_empty());
}
#[tokio::test]
async fn state_function_reuse_across_calls() {
let interp = interpreter();
let resp1 = interp
.execute(
r"
def add(a, b):
return a + b
",
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp1.error.is_none(), "error: {:?}", resp1.error);
let resp2 = interp.execute("print(add(3, 4))", &no_tools(), HashMap::new()).await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(resp2.stdout.trim(), "7");
}
#[tokio::test]
async fn state_bigint_and_exception_group_round_trip() {
let interp = interpreter();
let resp = interp
.execute(
r#"
big = 2 ** 100
eg = ExceptionGroup("g", [ValueError("a"), TypeError("b")])
print(big)
print(len(eg.exceptions))
"#,
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp.error.is_none(), "error: {:?}", resp.error);
assert_eq!(resp.stdout.trim(), "1267650600228229401496703205376\n2");
let state_bytes = interp.export_state().expect("export failed");
let interp2 = interpreter();
interp2.import_state(&state_bytes).expect("import failed");
let resp2 = interp2
.execute(
r#"
print(big)
print(type(eg).__name__)
print(len(eg.exceptions))
print(type(eg.exceptions[0]).__name__)
"#,
&no_tools(),
HashMap::new(),
)
.await;
assert!(resp2.error.is_none(), "error: {:?}", resp2.error);
assert_eq!(
resp2.stdout.trim(),
"1267650600228229401496703205376\nExceptionGroup\n2\nValueError"
);
}