#![cfg(all(feature = "tensor", feature = "serde_json"))]
use datalogic_rs::{Engine, datavalue::TensorError};
fn eval(rule: &str) -> String {
Engine::new().eval_str(rule, "{}").expect("eval")
}
fn eval_err(rule: &str) -> datalogic_rs::Error {
Engine::new()
.eval_str(rule, "{}")
.expect_err("expected error")
}
fn tensor_error_in(err: &datalogic_rs::Error) -> TensorError {
let mut source: Option<&(dyn std::error::Error + 'static)> = std::error::Error::source(err);
while let Some(e) = source {
if let Some(te) = e.downcast_ref::<TensorError>() {
return te.clone();
}
source = std::error::Error::source(e);
}
panic!("no TensorError in the source chain of {err:?}");
}
fn tensor_error(rule: &str) -> TensorError {
tensor_error_in(&eval_err(rule))
}
#[test]
fn unknown_dtype_names_the_spelling_it_was_given() {
assert_eq!(
tensor_error(r#"{"zeros": [[2], "float32"]}"#),
TensorError::UnknownDType("float32".to_string())
);
}
#[test]
fn an_element_that_does_not_fit_is_rejected_not_truncated() {
assert!(matches!(
tensor_error(r#"{"tensor": [[1, 256], "u8"]}"#),
TensorError::Element {
expected: datalogic_rs::datavalue::DType::U8,
..
}
));
}
#[test]
fn ragged_nested_arrays_report_their_depth() {
assert!(matches!(
tensor_error(r#"{"tensor": [[[1, 2], [3]], "u8"]}"#),
TensorError::Ragged { .. }
));
}
#[test]
fn a_shape_that_overflows_is_refused_before_allocating() {
let rule = r#"{"zeros": [[9223372036854775807, 4], "u8"]}"#;
assert_eq!(tensor_error(rule), TensorError::ShapeOverflow);
}
#[test]
fn rank_is_capped() {
let dims = (0..300).map(|_| "1").collect::<Vec<_>>().join(",");
assert!(matches!(
tensor_error(&format!(r#"{{"zeros": [[{dims}], "u8"]}}"#)),
TensorError::RankTooHigh { .. }
));
}
#[test]
fn the_tagged_decoder_stays_strict_about_unknown_fields() {
let engine = Engine::new();
let data = r#"{"t": {"tensor": {"dtype": "u8", "shape": [1], "data": "AA==",
"strides": [1]}}}"#;
let err = engine
.eval_str(r#"{"tensor": [{"val": "t"}]}"#, data)
.expect_err("expected a decode error");
let found = tensor_error_in(&err);
assert!(
matches!(found, TensorError::UnexpectedField(_)),
"got {found:?}"
);
}
#[test]
fn only_the_emitters_exact_shape_is_read_as_a_wire_body_in_a_rule() {
let engine = Engine::new();
let data = r#"{"t": {"tensor": {"dtype": "u8", "shape": [2], "data": "AQI="}}}"#;
assert_eq!(
engine
.eval_str(r#"{"shape": [{"tensor": {"val": "t"}}]}"#, data)
.expect("a single-key rule object still evaluates as a rule"),
"[2]"
);
assert_eq!(
eval(r#"{"shape": [{"tensor": {"dtype": "u8", "shape": [2], "data": "AQI="}}]}"#),
"[2]"
);
}
#[test]
fn a_tensor_renders_as_the_tagged_form() {
assert_eq!(
eval(r#"{"tensor": [[1, 2], "u8"]}"#),
r#"{"tensor":{"dtype":"u8","shape":[2],"data":"AQI="}}"#
);
}
#[test]
fn a_tensor_nested_in_a_result_still_renders() {
assert_eq!(
eval(r#"[{"tensor": [[1], "u8"]}]"#),
r#"[{"tensor":{"dtype":"u8","shape":[1],"data":"AQ=="}}]"#
);
}
#[cfg(feature = "templating")]
#[test]
fn a_tensor_in_a_template_field_still_renders() {
let templated = Engine::builder()
.with_templating(true)
.build()
.eval_str(r#"{"out": {"tensor": [[1], "u8"]}}"#, "{}")
.expect("eval");
assert_eq!(
templated,
r#"{"out":{"tensor":{"dtype":"u8","shape":[1],"data":"AQ=="}}}"#
);
}
#[test]
fn the_emitted_form_evaluates_back_to_the_same_tensor() {
let once = eval(r#"{"tensor": [[[1, 2], [3, 4]], "f32"]}"#);
let twice = Engine::new().eval_str(&once, "{}").expect("eval");
assert_eq!(once, twice);
}
#[test]
fn a_tagged_tensor_in_the_data_decodes() {
let engine = Engine::new();
let data = r#"{"t": {"tensor": {"dtype": "u8", "shape": [2], "data": "AQI="}}}"#;
assert_eq!(
engine
.eval_str(r#"{"to_list": [{"tensor": [{"val": "t"}]}]}"#, data)
.expect("eval"),
"[1,2]"
);
}
#[test]
fn cross_type_equality_follows_the_loose_equality_config() {
let rule = r#"{"==": [{"tensor": [[1], "u8"]}, 1]}"#;
assert!(Engine::new().eval_str(rule, "{}").is_err());
let lenient = Engine::builder()
.with_config(datalogic_rs::EvaluationConfig::default().with_loose_equality_errors(false))
.build();
assert_eq!(lenient.eval_str(rule, "{}").expect("eval"), "false");
}
#[test]
fn strict_equality_is_structural_without_any_coercion() {
assert_eq!(
eval(r#"{"===": [{"tensor": [[1, 2], "u8"]}, {"tensor": [[1, 2], "u8"]}]}"#),
"true"
);
assert_eq!(
eval(r#"{"===": [{"tensor": [[1], "u8"]}, {"tensor": [[1], "i8"]}]}"#),
"false"
);
}
#[test]
fn a_tensor_is_never_coerced_to_a_number() {
assert!(
Engine::new()
.eval_str(r#"{"+": [{"tensor": [[1], "u8"]}, 1]}"#, "{}")
.is_err()
);
}
#[test]
fn byte_moving_operators_work_on_every_dtype() {
for dt in ["f16", "bf16", "f32", "u8", "bool"] {
let src = format!(r#"{{"zeros": [[2, 3], "{dt}"]}}"#);
assert_eq!(
eval(&format!(r#"{{"shape": [{{"transpose": [{src}]}}]}}"#)),
"[3,2]",
"transpose {dt}"
);
assert_eq!(
eval(&format!(r#"{{"shape": [{{"reshape": [{src}, [6]]}}]}}"#)),
"[6]",
"reshape {dt}"
);
assert_eq!(
eval(&format!(
r#"{{"shape": [{{"pad": [{src}, [1, 0], [0, 1]]}}]}}"#
)),
"[3,4]",
"pad {dt}"
);
}
}
#[test]
#[cfg(not(feature = "tensor-half"))]
fn element_operators_refuse_half_without_the_feature() {
assert_eq!(
tensor_error(r#"{"to_list": [{"zeros": [[1], "f16"]}]}"#),
TensorError::UnsupportedDType(datalogic_rs::datavalue::DType::F16)
);
}
#[test]
#[cfg(feature = "tensor-half")]
fn tensor_half_lifts_the_element_restriction() {
assert_eq!(
eval(r#"{"to_list": [{"full": [[2], "f16", 1.5]}]}"#),
"[1.5,1.5]"
);
assert_eq!(
eval(r#"{"to_list": [{"cast": [{"tensor": [[1.5], "f32"]}, "bf16"]}]}"#),
"[1.5]"
);
}
#[test]
fn a_constant_tensor_expression_is_not_folded_into_the_rule() {
let engine = Engine::new();
let big = r#"{"shape": [{"zeros": [[1024, 1024], "f32"]}]}"#;
let started = std::time::Instant::now();
let logic = engine.compile(big).expect("compile");
let compile_time = started.elapsed();
assert!(
compile_time < std::time::Duration::from_millis(50),
"compiling a constant tensor took {compile_time:?}, which suggests it was folded"
);
let arena = bumpalo::Bump::new();
let out = engine.evaluate(&logic, "{}", &arena).expect("eval");
assert_eq!(out.to_string(), "[1024,1024]");
}
#[test]
fn repeated_identical_tensor_subtrees_each_evaluate() {
assert_eq!(
eval(
r#"{"==": [{"shape": [{"zeros": [[3], "u8"]}]}, {"shape": [{"zeros": [[3], "u8"]}]}]}"#
),
"true"
);
}