use crate::context::Context;
use crate::python::Python;
#[derive(Debug, PartialEq, serde::Deserialize)]
struct Foo {
foo: String,
}
fn expected() -> Foo {
Foo {
foo: "bar".to_string(),
}
}
async fn py() -> &'static Python {
static CELL: tokio::sync::OnceCell<Python> = tokio::sync::OnceCell::const_new();
CELL.get_or_init(|| async {
let bin_dir = std::env::temp_dir().join("objectiveai-python-tests");
Python::initialize(bin_dir)
.await
.expect("initialize WASI python runtime")
})
.await
}
fn ctx() -> Context {
Context::new(crate::run::ConfigBuilder::default().build())
}
async fn exec<T: serde::de::DeserializeOwned>(
code: &str,
) -> Result<Option<T>, crate::error::Error> {
py().await.exec_code::<(), T>(&ctx(), code, None).await
}
#[tokio::test]
async fn eval_dict_literal() {
let result: Foo = exec(r#"{"foo": "bar"}"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn print_dict() {
let result: Foo = exec(r#"import json; print(json.dumps({"foo": "bar"}))"#)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn main_returns() {
let result: Foo = exec(
r#"
import json
def main():
return {"foo": "bar"}
if __name__ == "__main__":
print(json.dumps(main()))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn starlark_style_in_main_guard() {
let result: Foo = exec(
r#"
import json
if __name__ == "__main__":
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn print_main_return() {
let result: Foo = exec(
r#"
import json
def main():
return {"foo": "bar"}
print(json.dumps(main()))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn unused_fn_then_print() {
let result: Foo = exec(
r#"
import json
def add(a, b):
return a + b
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn sys_stdout_write() {
let result: Foo = exec(
r#"
import json, sys
sys.stdout.write(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn bare_function_call() {
let result: Foo = exec(
r#"
def get():
return {"foo": "bar"}
get()
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn assign_then_bare_variable() {
let result: Foo = exec(
r#"
x = {"foo": "bar"}
x
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn multiline_dict_expression() {
let result: Foo = exec(
r#"
{
"foo": "bar",
}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn dict_comprehension() {
let result: Foo = exec(r#"{k: v for k, v in [("foo", "bar")]}"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn ternary_expression() {
let result: Foo = exec(r#"{"foo": "bar"} if True else None"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn walrus_operator() {
let result: Foo = exec(r#"(x := {"foo": "bar"})"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn lambda_call() {
let result: Foo = exec(r#"(lambda: {"foo": "bar"})()"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn semicolons_one_line() {
let result: Foo = exec(r#"x = 1; {"foo": "bar"}"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn nested_function_call_dict() {
let result: Foo = exec(r#"dict(foo="bar")"#).await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn stderr_debug_then_bare_expression() {
let result: Foo = exec(
r#"
import sys
print("debug info", file=sys.stderr)
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn stdout_noise_then_bare_expression() {
let result: Foo = exec(
r#"
print("some random debug output")
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn class_method_call() {
let result: Foo = exec(
r#"
class C:
def get(self):
return {"foo": "bar"}
C().get()
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn print_no_newline() {
let result: Foo = exec(r#"import json; print(json.dumps({"foo": "bar"}), end="")"#)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn multiple_bare_expressions_last_wins() {
let result: Foo = exec(
r#"
1
2
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn trailing_blank_lines() {
let result: Foo = exec("{\"foo\": \"bar\"}\n\n\n").await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn trailing_comment_after_expression() {
let result: Foo = exec("{\"foo\": \"bar\"}\n# done").await.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn trailing_pass_after_expression() {
let result: Foo = exec(
r#"
import json
print(json.dumps({"foo": "bar"}))
pass
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn user_defines_underscore_variables() {
let result: Foo = exec(
r#"
import json
_json = None
_capture = "oops"
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn user_sets_oai_prefix_variable() {
let result: Foo = exec(
r#"
import json
__oai_capture = "sabotage"
__oai_json = None
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn user_deletes_sys() {
let result: Foo = exec(
r#"
import json, sys
del sys
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn redefine_print_use_builtins() {
let result: Foo = exec(
r#"
import json, builtins
print = lambda *a: None
builtins.print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn global_variable_in_expression() {
let result: Foo = exec(
r#"
x = "bar"
{"foo": x}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn nested_exec_in_user_code() {
let result: Foo = exec(
r#"
import json
exec('result = {"foo": "bar"}')
print(json.dumps(result))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn try_except_then_bare_expression() {
let result: Foo = exec(
r#"
try:
raise ValueError("oops")
except:
pass
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn name_equals_main_in_exec() {
let result: Foo = exec(
r#"
import json
if __name__ == "__main__":
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn bare_expression_after_for_loop() {
let result: Foo = exec(
r#"
items = []
for i in range(3):
items.append(i)
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn bare_expression_after_with() {
let result: Foo = exec(
r#"
import io
with io.StringIO() as f:
f.write("ignored")
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn bare_expression_after_try_finally() {
let result: Foo = exec(
r#"
x = None
try:
x = 1
finally:
x = 2
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn globals_dict_then_bare_expression() {
let result: Foo = exec(
r#"
globals()["x"] = "bar"
{"foo": x}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn dynamic_import() {
let result: Foo = exec(
r#"
json = __import__("json")
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn deeply_nested_dict() {
let result: serde_json::Value = exec(
r#"
{"a": {"b": {"c": {"foo": "bar"}}}}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result["a"]["b"]["c"]["foo"], "bar");
}
#[tokio::test]
async fn multiline_string_then_bare_expression() {
let result: Foo = exec(
r#"
code = """
def fake():
return {"wrong": "value"}
print("this is not executed")
"""
{"foo": "bar"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn unicode_emoji_value() {
#[derive(Debug, PartialEq, serde::Deserialize)]
struct Uni {
msg: String,
}
let result: Uni = exec(
r#"
{"msg": "hello 🦀 world àéîõü"}
"#,
)
.await
.unwrap().unwrap();
assert_eq!(
result,
Uni {
msg: "hello 🦀 world àéîõü".to_string()
}
);
}
#[tokio::test]
async fn args_kwargs_then_bare_expression() {
let result: Foo = exec(
r#"
def make(*args, **kwargs):
return kwargs
make("ignored", foo="bar")
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn dynamic_class_creation() {
let result: Foo = exec(
r#"
MyClass = type("MyClass", (), {"get": lambda self: {"foo": "bar"}})
MyClass().get()
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn list_comprehension_indexed() {
let result: Foo = exec(
r#"
[{"foo": v} for v in ["bar"]][0]
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn conditional_with_no_exit() {
let result: Foo = exec(
r#"
import json
should_exit = False
if should_exit:
exit(1)
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, expected());
}
#[tokio::test]
async fn garbage_stdout_before_correct_print() {
let err = exec::<Foo>(
r#"
import json
print("here is some random garbage!!!")
print(json.dumps({"foo": "bar"}))
"#,
)
.await
.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_syntax_error() {
let err = exec::<Foo>("def").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_name_error() {
let err = exec::<Foo>("undefined_variable").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_zero_division() {
let err = exec::<Foo>("1 / 0").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_import_error() {
let err = exec::<Foo>("import nonexistent_module_xyz").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_recursion_error() {
let err = exec::<Foo>(
r#"
def f(): f()
f()
"#,
)
.await
.unwrap_err();
assert!(matches!(
err,
crate::error::Error::PythonException(_) | crate::error::Error::PythonWasm(_)
));
}
#[tokio::test]
async fn error_explicit_raise() {
let err = exec::<Foo>(r#"raise RuntimeError("boom")"#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_sys_exit_nonzero() {
let err = exec::<Foo>("import sys; sys.exit(1)").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_sys_exit_zero_breaks_envelope() {
let err = exec::<Foo>("import sys; sys.exit(0)").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonHarnessBroken(_)));
}
#[tokio::test]
async fn error_type_error() {
let err = exec::<Foo>(r#""hello" + 5"#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_exception_in_eval_expression() {
let err = exec::<Foo>(
r#"
x = 1
1 / 0
"#,
)
.await
.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_assert_false() {
let err = exec::<Foo>("assert False, 'nope'").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_key_error() {
let err = exec::<Foo>("{}['missing']").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_index_error() {
let err = exec::<Foo>("[][0]").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn error_attribute_error() {
let err = exec::<Foo>("'hello'.nonexistent_method()").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn sandbox_no_filesystem() {
let err = exec::<Foo>(r#"open("Cargo.toml").read()"#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonException(_)));
}
#[tokio::test]
async fn sandbox_empty_environ() {
let result: serde_json::Value = exec(
r#"
import os
dict(os.environ)
"#,
)
.await
.unwrap().unwrap();
assert_eq!(result, serde_json::json!({}));
}
#[tokio::test]
async fn error_deser_bare_int() {
let err = exec::<Foo>("42").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_bare_string() {
let err = exec::<Foo>(r#""hello""#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_bare_list() {
let err = exec::<Foo>("[1, 2, 3]").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn no_output_bare_none() {
let out = exec::<serde_json::Value>("None").await.unwrap();
assert_eq!(out, None);
}
#[tokio::test]
async fn error_deser_python_repr_not_json() {
let err = exec::<Foo>(r#"print({"foo": "bar"})"#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_wrong_shape() {
let err = exec::<Foo>(r#"{"baz": "bar"}"#).await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn no_output_empty_code() {
let out = exec::<serde_json::Value>("").await.unwrap();
assert_eq!(out, None);
}
#[tokio::test]
async fn no_output_only_comments() {
let out = exec::<serde_json::Value>("# nothing here").await.unwrap();
assert_eq!(out, None);
}
#[tokio::test]
async fn output_explicit_json_null() {
let out = exec::<serde_json::Value>("import json; print(json.dumps(None))")
.await
.unwrap();
assert_eq!(out, Some(serde_json::Value::Null));
}
#[tokio::test]
async fn error_deser_array_of_foos() {
let err = exec::<Foo>(
r#"
import json
print(json.dumps([{"foo": "bar"}, {"foo": "baz"}]))
"#,
)
.await
.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_bare_bool() {
let err = exec::<Foo>("True").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_bare_float() {
let err = exec::<Foo>("3.14").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_deser_bare_tuple() {
let err = exec::<Foo>("(1, 2, 3)").await.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonDeserialize(_)));
}
#[tokio::test]
async fn error_file_not_found() {
let err = py()
.await
.exec_file::<(), Foo>(&ctx(), std::path::Path::new("/nonexistent/path/script.py"), None)
.await
.unwrap_err();
assert!(matches!(err, crate::error::Error::PythonFileRead(_, _)));
}