use neorusticus::PrologEngine;
#[test]
fn test_parse_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_and_add("parent(tom bob)."); assert!(result.is_err());
if let Err(e) = result {
assert!(format!("{}", e).contains("Unexpected token"));
}
let result = engine.parse_and_add("parent(tom, bob"); assert!(result.is_err());
if let Err(e) = result {
assert!(format!("{}", e).contains("Unclosed"));
}
let result = engine.parse_and_add("parent(tom, bob)"); assert!(result.is_err());
if let Err(e) = result {
assert!(format!("{}", e).contains("Expected"));
}
let result = engine.parse_and_add("parent(tom, 999999999999999999999).");
assert!(result.is_err());
if let Err(e) = result {
assert!(format!("{}", e).contains("Invalid number") || format!("{}", e).contains("too large"));
}
}
#[test]
fn test_arithmetic_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_query("X is 5 // 0?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Division by zero") || error_msg.contains("division"));
}
let result = engine.parse_query("X is Y + 1?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("uninstantiated") || error_msg.contains("Variable"));
}
let result = engine.parse_query("X is atom + 1?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Type mismatch") || error_msg.contains("mismatch"));
}
engine.parse_and_add("big_num(999999999).").unwrap();
let _result = engine.parse_query("big_num(X), Y is X * X * X * X?");
}
#[test]
fn test_list_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_query("member(not_in_list, [1, 2, 3])?");
match result {
Ok(solutions) => assert_eq!(solutions.len(), 0),
Err(_) => panic!("member/2 with ground terms should not error"),
}
let _result1 = engine.parse_query("length([], 0)?");
let _result2 = engine.parse_query("length([1, 2, 3], 3)?");
let _result3 = engine.parse_query("length([1, 2], 3)?");
let _result4 = engine.parse_query("append([], [1, 2], [1, 2])?");
let _result5 = engine.parse_query("append([1], [2], [1, 2])?");
let _result6 = engine.parse_query("member(1, [1, 2, 3])?");
let _result7 = engine.parse_query("member(4, [1, 2, 3])?");
let result = engine.parse_query("X is atom + 1?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Type mismatch") || error_msg.contains("mismatch") || error_msg.contains("arithmetic"));
}
}
#[test]
fn test_predicate_suggestions() {
let mut engine = PrologEngine::new();
let undefined_queries = vec![
"lengthh([1, 2, 3], X)?",
"appnd([1], [2], X)?",
"totally_fake_predicate_xyz(X)?",
"nonexistent(A, B, C)?",
"made_up_predicate()?",
];
for query in undefined_queries {
match engine.parse_query(query) {
Ok(solutions) => {
assert_eq!(solutions.len(), 0,
"Undefined predicate {} should not find solutions", query);
}
Err(_) => {
}
}
}
let result = engine.parse_query("append([1], [2], X)?");
assert!(result.is_ok(), "Known predicate append/3 should work");
let solutions = result.unwrap();
assert_eq!(solutions.len(), 1, "append/3 should find exactly one solution");
let _result1 = engine.parse_query("unknown_pred()?");
let _result2 = engine.parse_query("fake(a, b, c, d, e)?");
let _result3 = engine.parse_query("xyz123()?");
}
#[test]
fn test_stack_overflow_protection() {
let mut engine = PrologEngine::with_limits(10);
engine.set_max_stack_depth(5);
engine.parse_and_add("infinite(X) :- infinite(X).").unwrap();
let result = engine.parse_query("infinite(test)?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Stack overflow") || error_msg.contains("overflow") || error_msg.contains("depth"));
}
}
#[test]
fn test_cut_errors() {
let mut engine = PrologEngine::new();
engine.parse_and_add("test_cut :- !, true.").unwrap();
let result = engine.parse_query("test_cut?");
assert!(result.is_ok());
engine.parse_and_add("choice(1).").unwrap();
engine.parse_and_add("choice(2).").unwrap();
engine.parse_and_add("no_choice(X) :- choice(X), !.").unwrap();
let solutions = engine.parse_query("no_choice(Y)?").unwrap();
assert_eq!(solutions.len(), 1); }
#[test]
fn test_unification_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_query("X = f(X)?");
match result {
Ok(solutions) => {
assert_eq!(solutions.len(), 0); }
Err(_) => {
}
}
let result = engine.parse_query("f(a, b) = f(c, d)?");
assert!(result.is_ok());
let solutions = result.unwrap();
assert_eq!(solutions.len(), 0); }
#[test]
fn test_variable_scoping() {
let mut engine = PrologEngine::new();
engine.parse_and_add("test(X) :- X = 1.").unwrap();
engine.parse_and_add("test(X) :- X = 2.").unwrap();
let solutions = engine.parse_query("test(Y)?").unwrap();
assert_eq!(solutions.len(), 2);
}
#[test]
fn test_error_recovery() {
let mut engine = PrologEngine::new();
engine.parse_and_add("parent(tom, bob).").unwrap();
let result = engine.parse_and_add("invalid syntax here");
assert!(result.is_err());
let solutions = engine.parse_query("parent(tom, X)?").unwrap();
assert_eq!(solutions.len(), 1);
let result = engine.parse_query("X is 1 / 0?");
assert!(result.is_err());
let solutions = engine.parse_query("parent(tom, bob)?").unwrap();
assert_eq!(solutions.len(), 1);
}
#[test]
fn test_solution_limits() {
let mut engine = PrologEngine::with_limits(5);
for i in 1..=20 {
engine.parse_and_add(&format!("number({}).", i)).unwrap();
}
let result = engine.parse_query("number(X)?");
match result {
Ok(solutions) => {
assert!(solutions.len() <= 5);
}
Err(e) => {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Too many") || error_msg.contains("limit"));
}
}
}
#[test]
fn test_complex_error_scenarios() {
let mut engine = PrologEngine::new();
engine.parse_and_add("valid(clause).").unwrap();
let result = engine.parse_and_add("test :- 1 === 2."); assert!(result.is_err());
let result = engine.parse_and_add("append(X, Y) :- true."); assert!(result.is_ok());
engine.parse_and_add("runtime_error :- X is Y.").unwrap();
let result = engine.parse_query("runtime_error?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("uninstantiated") || error_msg.contains("Variable"));
}
}
#[test]
fn test_type_checking_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_query("var(123)?"); assert!(result.is_ok());
let solutions = result.unwrap();
assert_eq!(solutions.len(), 0);
let result = engine.parse_query("atom(X)?"); assert!(result.is_ok());
let solutions = result.unwrap();
assert_eq!(solutions.len(), 0); }
#[test]
fn test_built_in_predicate_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_query("X > atom?");
assert!(result.is_err());
let result = engine.parse_query("X > Y?");
assert!(result.is_err());
let result = engine.parse_query("atom = 123?");
assert!(result.is_ok());
let solutions = result.unwrap();
assert_eq!(solutions.len(), 0);
}
#[test]
fn test_parser_vs_runtime_errors() {
let mut engine = PrologEngine::new();
let result = engine.parse_and_add("foo(bar baz)."); assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Unexpected") || error_msg.contains("Expected"));
}
engine.parse_and_add("divide_error :- X is 5 // 0.").unwrap();
let result = engine.parse_query("divide_error?");
assert!(result.is_err());
if let Err(e) = result {
let error_msg = format!("{}", e);
assert!(error_msg.contains("Division by zero") || error_msg.contains("zero"));
}
}