#![allow(clippy::result_large_err)]
mod alignment_util;
use std::collections::HashMap;
use qlexpress::ql_options::QLOptions;
use qlexpress::runtime::value::DataValue;
use qlexpress::Express4Runner;
use alignment_util::{expect_err_code, expect_ok};
#[test]
fn arithmetic_basics() {
assert_eq!(expect_ok("1 + 2 * 3"), DataValue::Long(7));
assert_eq!(expect_ok("10 - 3"), DataValue::Long(7));
assert_eq!(expect_ok("100 / 4"), DataValue::Long(25));
assert_eq!(expect_ok("100 % 7"), DataValue::Long(2));
}
#[test]
fn comparison_basics() {
assert_eq!(expect_ok("1 < 2"), DataValue::Bool(true));
assert_eq!(expect_ok("2 <= 2"), DataValue::Bool(true));
assert_eq!(expect_ok("3 > 5"), DataValue::Bool(false));
assert_eq!(expect_ok("3 == 3"), DataValue::Bool(true));
assert_eq!(expect_ok("3 != 4"), DataValue::Bool(true));
}
#[test]
fn logical_and_or() {
assert_eq!(expect_ok("true && false"), DataValue::Bool(false));
assert_eq!(expect_ok("true || false"), DataValue::Bool(true));
assert_eq!(expect_ok("!false"), DataValue::Bool(true));
}
#[test]
fn bitwise_basics() {
assert_eq!(expect_ok("5 & 3"), DataValue::Long(1));
assert_eq!(expect_ok("5 | 2"), DataValue::Long(7));
assert_eq!(expect_ok("6 ^ 3"), DataValue::Long(5));
assert_eq!(expect_ok("1 << 4"), DataValue::Long(16));
assert_eq!(expect_ok("16 >> 2"), DataValue::Long(4));
}
#[test]
fn ternary_returns_value() {
assert_eq!(expect_ok("true ? 1 : 2"), DataValue::Long(1));
assert_eq!(expect_ok("false ? 1 : 2"), DataValue::Long(2));
}
#[test]
fn in_test_over_list() {
assert_eq!(
expect_ok("'ab' in ['cc','dd','ff']"),
DataValue::Bool(false)
);
assert_eq!(expect_ok("'cc' in ['cc','dd','ff']"), DataValue::Bool(true));
}
#[test]
fn typed_local_variable() {
assert_eq!(expect_ok("int a = 1; a + 2"), DataValue::Long(3));
}
#[test]
fn inferred_variable() {
assert_eq!(expect_ok("a = 11; a + 1"), DataValue::Long(12));
}
#[test]
fn multiple_declarators() {
expect_err_code("int a, b = 10; a + b", "INVALID_BINARY_OPERAND");
}
#[test]
fn if_else_if() {
let script = "if (1 == 2) { 100 } else if (2 == 3) { 200 } else { 300 }";
assert_eq!(expect_ok(script), DataValue::Long(300));
}
#[test]
fn for_loop_sum() {
let script = "int sum = 0; for (int i = 1; i <= 5; i = i + 1) { sum = sum + i; } sum";
assert_eq!(expect_ok(script), DataValue::Long(15));
}
#[test]
fn foreach_iterates_list() {
let script = "int total = 0; for (x : [1, 2, 3, 4]) { total = total + x; } total";
assert_eq!(expect_ok(script), DataValue::Long(10));
}
#[test]
fn while_loop() {
let script = "int i = 0; while (i < 5) { i = i + 1; } i";
assert_eq!(expect_ok(script), DataValue::Long(5));
}
#[test]
fn break_inside_for() {
let script = "int sum = 0;\n\
for (int i = 0; i < 10; i = i + 1) {\n\
if (i == 5) {\n\
break;\n\
}\n\
sum = sum + i;\n\
}\n\
sum";
assert_eq!(expect_ok(script), DataValue::Long(10));
}
#[test]
fn continue_inside_for() {
let script = "int sum = 0;\n\
for (int i = 0; i < 5; i = i + 1) {\n\
if (i == 2) {\n\
continue;\n\
}\n\
sum = sum + i;\n\
}\n\
sum";
assert_eq!(expect_ok(script), DataValue::Long(8));
}
#[test]
fn return_inside_if() {
let script = "int a = 0;\n\
if (true) {\n\
return 42;\n\
}\n\
a";
assert_eq!(expect_ok(script), DataValue::Long(42));
}
#[test]
fn try_catch_runtime_exception() {
let script = "try { 1/0 } catch (e) { 99 }";
assert_eq!(expect_ok(script), DataValue::Long(99));
}
#[test]
fn try_finally_runs() {
let script = "try { 1 + 1 } catch (e) { 0 } finally { 2 + 2 }";
let _ = expect_ok(script);
}
#[test]
fn add_function_with_closure() {
let runner = Express4Runner::new();
runner.add_function(
"add",
|_ctx: &mut dyn qlexpress::runtime::qcontext::QContext,
params: &qlexpress::runtime::parameters::Parameters|
-> Result<_, qlexpress::exception::QLException> {
let a = qlexpress::runtime::data::convert::to_i64(¶ms.get_value(0));
let b = qlexpress::runtime::data::convert::to_i64(¶ms.get_value(1));
Ok(DataValue::Long(a + b))
},
);
let opts = QLOptions::builder().build();
let result = runner
.execute("add(2, 3)", HashMap::new(), &opts)
.unwrap()
.into_result();
assert_eq!(result, DataValue::Long(5));
}
#[test]
fn add_varargs_function_collects_args() {
let runner = Express4Runner::new();
runner.add_varargs_function(
"join",
|params: &[DataValue]| -> Result<_, qlexpress::exception::QLException> {
let joined = params
.iter()
.map(|p| p.string_value_of())
.collect::<Vec<_>>()
.join("-");
Ok(DataValue::string(joined))
},
);
let opts = QLOptions::builder().build();
let result = runner
.execute("join('a','b','c')", HashMap::new(), &opts)
.unwrap()
.into_result();
assert_eq!(result, DataValue::Str("a-b-c".into()));
}
#[test]
fn add_operator_bifunction() {
let mut runner = Express4Runner::new();
runner.add_operator_bi("join", |left: DataValue, right: DataValue| {
DataValue::string(format!(
"{}|{}",
left.string_value_of(),
right.string_value_of()
))
});
let opts = QLOptions::builder().build();
let result = runner
.execute("'a' join 'b'", HashMap::new(), &opts)
.unwrap()
.into_result();
assert_eq!(result, DataValue::Str("a|b".into()));
}
#[test]
fn security_open_allows_method_call() {
use qlexpress::security::ql_security_strategy::QLSecurityStrategy;
let runner = Express4Runner::with_init_options(
qlexpress::init_options::InitOptions::builder()
.security_strategy(QLSecurityStrategy::open())
.build(),
);
let result = runner
.execute(
"'hello'.length()",
HashMap::new(),
&QLOptions::builder().build(),
)
.unwrap()
.into_result();
assert_eq!(result, DataValue::Int(5));
}
#[test]
fn security_isolation_blocks_method_call() {
use qlexpress::runtime::native_type::NativeType;
use qlexpress::security::ql_security_strategy::QLSecurityStrategy;
let mut nt = NativeType::named("com.example.Calc");
nt.static_methods.insert(
"mul".to_string(),
std::rc::Rc::new(|_bean, args| match args {
[qlexpress::runtime::value::DataValue::Int(a), qlexpress::runtime::value::DataValue::Int(b)] => {
Ok(qlexpress::runtime::value::DataValue::Int(a * b))
}
_ => Ok(qlexpress::runtime::value::DataValue::Null),
}),
);
let mut runner = Express4Runner::with_init_options(
qlexpress::init_options::InitOptions::builder()
.security_strategy(QLSecurityStrategy::isolation())
.build(),
);
runner.register_native_type(nt);
let result = runner.execute(
"Calc.mul(6, 7)",
HashMap::new(),
&QLOptions::builder().build(),
);
assert!(
result.is_err(),
"isolation must reject registered method calls"
);
}
#[test]
fn map_literal_access() {
let script = "m = {a: 1, 'b': 2}; m.a + m.b";
assert_eq!(expect_ok(script), DataValue::Long(3));
}
#[test]
fn list_literal_access() {
let script = "l = [10, 20, 30]; l[1]";
assert_eq!(expect_ok(script), DataValue::Long(20));
}
#[test]
fn list_literal_size() {
let script = "l = [10, 20, 30]; l.size()";
assert_eq!(expect_ok(script), DataValue::Int(3));
}
#[test]
fn short_circuit_or_skips_rhs() {
let script = "true || (1/0)";
assert_eq!(expect_ok(script), DataValue::Bool(true));
}
#[test]
fn short_circuit_disabled_evaluates_both() {
let opts = QLOptions::builder().short_circuit_disable(true).build();
let result = run_script_with("true || (1/0)", &opts);
assert!(result.is_err());
}
#[test]
fn dollar_interpolation_default() {
let opts = QLOptions::builder().build();
let script = "\"a = ${1+2}\"";
let runner = Express4Runner::new();
let result = runner
.execute(script, HashMap::new(), &opts)
.unwrap()
.into_result();
assert_eq!(result, DataValue::Str("a = 3".into()));
}
#[test]
fn lambda_expr_body() {
let script = "f = (x) -> x + 1; f(10)";
assert_eq!(expect_ok(script), DataValue::Long(11));
}
#[test]
fn lambda_block_body() {
let script = "f = (x) -> { int y = x * 2; return y + 1; }; f(5)";
assert_eq!(expect_ok(script), DataValue::Long(11));
}
#[test]
fn cache_returns_consistent_result() {
let opts = QLOptions::builder().cache(true).build();
let script = "1 + 2 + 3";
let runner = Express4Runner::new();
let r1 = runner
.execute(script, HashMap::new(), &opts)
.unwrap()
.into_result();
let r2 = runner
.execute(script, HashMap::new(), &opts)
.unwrap()
.into_result();
assert_eq!(r1, r2);
assert_eq!(r1, DataValue::Long(6));
}
#[test]
fn runtime_error_carries_diagnostic_position() {
expect_err_code("1/0", "INVALID_ARITHMETIC");
}
#[test]
fn null_division_yields_arithmetic_error() {
expect_err_code("undefinedVar + 1", "INVALID_BINARY_OPERAND");
}
fn run_script_with(
script: &str,
options: &QLOptions,
) -> Result<DataValue, qlexpress::exception::QLException> {
let runner = Express4Runner::new();
let result = runner.execute(script, HashMap::new(), options)?;
Ok(result.into_result())
}