Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/analysis_extended.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
extern crate alloc;

use eldritch_core::{ExprKind, Lexer, Parser, Stmt, analysis::Node, analysis::find_node_at_offset};

fn parse(source: &str) -> Vec<Stmt> {
let mut lexer = Lexer::new(source.to_string());
let tokens = lexer.scan_tokens();
let mut parser = Parser::new(tokens);
let (ast, _errors) = parser.parse();
ast
}

#[test]
fn test_find_in_assignment_annot() {
let source = "x: int = 1";
let ast = parse(source);
// x (0..1), : (1..2), int (3..6), = (7..8), 1 (9..10)
let node = find_node_at_offset(&ast, 4).unwrap();
match node {
Node::Expr(e) => match &e.kind {
ExprKind::Identifier(s) => assert_eq!(s, "int"),
_ => panic!("Expected Identifier"),
},
_ => panic!("Expected Expr"),
}
}

#[test]
fn test_find_in_augmented_assignment() {
let source = "x += 1";
let ast = parse(source);
let node = find_node_at_offset(&ast, 0).unwrap();
match node {
Node::Expr(e) => match &e.kind {
ExprKind::Identifier(s) => assert_eq!(s, "x"),
_ => panic!("Expected Identifier"),
},
_ => panic!("Expected Expr"),
}

let node_rhs = find_node_at_offset(&ast, 5).unwrap();
match node_rhs {
Node::Expr(e) => match &e.kind {
ExprKind::Literal(i) => assert_eq!(*i, eldritch_core::Value::Int(1)),
_ => panic!("Expected Integer"),
},
_ => panic!("Expected Expr"),
}
}

#[test]
fn test_find_in_if_else() {
let source = "if True:\n pass\nelse:\n pass";
let ast = parse(source);
// True is at offset 3
let node = find_node_at_offset(&ast, 3).unwrap();
match node {
Node::Expr(e) => match &e.kind {
ExprKind::Identifier(s) => assert_eq!(s, "True"), // True is parsed as Identifier in AST (or Bool depending on version, wait, in Eldritch it might be Identifier or True). Let's check: actually it might be True literal but we can just check if it's Expr.
_ => (), // Accept anything as long as it's the condition
},
_ => panic!("Expected Expr"),
}

// Check inside else
// "if True:\n pass\nelse:\n pass"
// 012345678 901234567 890123 4567890
// else is around 18. pass is around 28.
let node_else = find_node_at_offset(&ast, 28);
// Might be Stmt::Pass
assert!(node_else.is_some());
}

#[test]
fn test_find_in_return() {
let source = "def f():\n return 42";
let ast = parse(source);
let node = find_node_at_offset(&ast, 18).unwrap();
match node {
Node::Expr(e) => match &e.kind {
ExprKind::Literal(i) => assert_eq!(*i, eldritch_core::Value::Int(42)),
_ => (),
},
_ => panic!("Expected Expr"),
}
}

#[test]
fn test_find_in_for() {
let source = "for x in [1, 2]: pass";
let ast = parse(source);
// [1, 2] is at offset 9
let node = find_node_at_offset(&ast, 10).unwrap();
match node {
Node::Expr(e) => match &e.kind {
ExprKind::List(_) => (),
ExprKind::Literal(_) => (), // Might hit the inner element
_ => panic!("Expected List or inner Expr, got {:?}", e.kind),
},
_ => panic!("Expected Expr"),
}
}

#[test]
fn test_find_in_def() {
let source = "def f(a: int = 1) -> str:\n pass";
let ast = parse(source);

// Offset for 'int'
let node_annot = find_node_at_offset(&ast, 9).unwrap();
assert!(matches!(node_annot, Node::Expr(_)));

// Offset for '1'
let node_default = find_node_at_offset(&ast, 15).unwrap();
assert!(matches!(node_default, Node::Expr(_)));

// Offset for 'str'
let node_ret = find_node_at_offset(&ast, 21).unwrap();
assert!(matches!(node_ret, Node::Expr(_)));
}

#[test]
fn test_find_in_lambda() {
let source = "f = lambda x: x + 1";
let ast = parse(source);

// Offset for 'x' in body
let node = find_node_at_offset(&ast, 14).unwrap();
assert!(matches!(node, Node::Expr(_)));
}

#[test]
fn test_find_in_comprehension() {
let source = "l = [x for x in y if x > 0]";
let ast = parse(source);

// 'y' is around 16
let node = find_node_at_offset(&ast, 16).unwrap();
assert!(matches!(node, Node::Expr(_)));

// 'x > 0' is around 21
let node_cond = find_node_at_offset(&ast, 21).unwrap();
assert!(matches!(node_cond, Node::Expr(_)));
}

#[test]
fn test_find_in_dict_comp() {
let source = "d = {k: v for k in x if k > 0}";
let ast = parse(source);

let node_key = find_node_at_offset(&ast, 5).unwrap();
assert!(matches!(node_key, Node::Expr(_)));

let node_val = find_node_at_offset(&ast, 8).unwrap();
assert!(matches!(node_val, Node::Expr(_)));
}

#[test]
fn test_find_in_fstring() {
let source = "f'hello {x}'";
let ast = parse(source);

// 'x' is at offset 9
let node = find_node_at_offset(&ast, 9).unwrap();
assert!(matches!(node, Node::Expr(_)));
}
154 changes: 154 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/conversion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
extern crate alloc;

use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use eldritch_core::Value;
use eldritch_core::conversion::{FromValue, IntoEldritchResult, ToValue};

#[test]
fn test_conversion_i64() {
let val = 42i64.to_value();
assert!(matches!(val, Value::Int(42)));
let extracted: i64 = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, 42);

// Invalid conversion
let err = i64::from_value(&Value::String(String::from("not int"))).unwrap_err();
assert!(err.contains("Expected Int, got str"));
}

#[test]
fn test_conversion_f64() {
let val = 42.5f64.to_value();
assert!(matches!(val, Value::Float(f) if f == 42.5));
let extracted: f64 = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, 42.5);

// Coercion from Int
let extracted_from_int: f64 = FromValue::from_value(&Value::Int(42)).unwrap();
assert_eq!(extracted_from_int, 42.0);

// Invalid conversion
let err = f64::from_value(&Value::String(String::from("not float"))).unwrap_err();
assert!(err.contains("Expected Float or Int, got str"));
}

#[test]
fn test_conversion_string() {
let val = String::from("hello").to_value();
assert!(matches!(val, Value::String(ref s) if s == "hello"));
let extracted: String = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, "hello");

let err = String::from_value(&Value::Int(1)).unwrap_err();
assert!(err.contains("Expected String, got int"));
}

#[test]
fn test_conversion_bool() {
let val = true.to_value();
assert!(matches!(val, Value::Bool(true)));
let extracted: bool = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, true);

let err = bool::from_value(&Value::Int(1)).unwrap_err();
assert!(err.contains("Expected Bool, got int"));
}

#[test]
fn test_conversion_bytes() {
let bytes = vec![1, 2, 3];
let val = bytes.clone().to_value();
assert!(matches!(val, Value::Bytes(ref b) if b == &bytes));
let extracted: Vec<u8> = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, bytes);

let err = Vec::<u8>::from_value(&Value::Int(1)).unwrap_err();
assert!(err.contains("Expected Bytes, got int"));
}

#[test]
fn test_conversion_vec() {
let vec = vec![1i64, 2, 3];
let val = vec.clone().to_value();
if let Value::List(l) = &val {
let list = l.read();
assert_eq!(list.len(), 3);
} else {
panic!("Expected list");
}

let extracted: Vec<i64> = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, vec);

// From Tuple
let tuple_val = Value::Tuple(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
let extracted_tuple: Vec<i64> = FromValue::from_value(&tuple_val).unwrap();
assert_eq!(extracted_tuple, vec);

let err = Vec::<i64>::from_value(&Value::Int(1)).unwrap_err();
assert!(err.contains("Expected List or Tuple, got int"));
}

#[test]
fn test_conversion_btreemap() {
let mut map = BTreeMap::new();
map.insert(String::from("a"), 1i64);
map.insert(String::from("b"), 2i64);

let val = map.clone().to_value();
if let Value::Dictionary(d) = &val {
let dict = d.read();
assert_eq!(dict.len(), 2);
} else {
panic!("Expected dict");
}

let extracted: BTreeMap<String, i64> = FromValue::from_value(&val).unwrap();
assert_eq!(extracted, map);

let err = BTreeMap::<String, i64>::from_value(&Value::Int(1)).unwrap_err();
assert!(err.contains("Expected Dictionary, got int"));
}

#[test]
fn test_conversion_option() {
let val_some = Some(42i64).to_value();
assert!(matches!(val_some, Value::Int(42)));
let extracted_some: Option<i64> = FromValue::from_value(&val_some).unwrap();
assert_eq!(extracted_some, Some(42));

let val_none: Option<i64> = None;
let val_none_val = val_none.to_value();
assert!(matches!(val_none_val, Value::None));
let extracted_none: Option<i64> = FromValue::from_value(&val_none_val).unwrap();
assert_eq!(extracted_none, None);
}

#[test]
fn test_conversion_unit() {
let val = ().to_value();
assert!(matches!(val, Value::None));
}

#[test]
fn test_conversion_value() {
let val = Value::Int(42);
let to_val = val.clone().to_value();
assert!(matches!(to_val, Value::Int(42)));
let from_val: Value = FromValue::from_value(&val).unwrap();
assert!(matches!(from_val, Value::Int(42)));
}

#[test]
fn test_into_eldritch_result() {
let res: Result<i64, String> = Ok(42);
let val = res.into_eldritch_result().unwrap();
assert!(matches!(val, Value::Int(42)));

let err: Result<i64, String> = Err(String::from("error"));
let val_err = err.into_eldritch_result().unwrap_err();
assert_eq!(val_err, "error");
}
68 changes: 68 additions & 0 deletions implants/lib/eldritch/eldritch-core/tests/tprint_extended.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
extern crate alloc;

use alloc::sync::Arc;
use eldritch_core::{BufferPrinter, Interpreter};

fn check_output_contains(code: &str, expected: &[&str]) {
let printer = Arc::new(BufferPrinter::new());
let mut interp = Interpreter::new_with_printer(printer.clone());

let code_trimmed = code
.lines()
.map(|l| l.trim())
.collect::<alloc::vec::Vec<_>>()
.join("\n");

if let Err(e) = interp.interpret(&code_trimmed) {
panic!("Interpretation failed for code:\n{}\nError: {}", code, e);
}

let output = printer.read();

for fragment in expected {
if !output.contains(fragment) {
panic!(
"Output did not contain '{}'. Output was:\n{}",
fragment, output
);
}
}
}

fn check_error(code: &str, error_fragment: &str) {
let mut interp = Interpreter::new();
match interp.interpret(code) {
Ok(_) => panic!(
"Expected error containing '{}', but succeeded.",
error_fragment
),
Err(e) => {
if !e.contains(error_fragment) {
panic!(
"Expected error containing '{}', but got: '{}'",
error_fragment, e
);
}
}
}
}

#[test]
fn test_tprint_invalid_args() {
check_error("tprint([1, 2])", "must contain only dictionaries");
}

#[test]
fn test_tprint_success() {
check_output_contains("tprint([{\"a\": 1}])", &["| a |", "| 1 |"]);
}

#[test]
fn test_tprint_empty_args() {
check_error("tprint()", "takes at least 1 argument");
}

#[test]
fn test_tprint_invalid_type() {
check_error("tprint(1)", "argument must be a list");
}
Loading