use super::*;
use std::error::Error;
#[test]
fn test_position_new() {
let pos = Position::new(5, 10, 42);
assert_eq!(pos.line, 5); assert_eq!(pos.column, 10); assert_eq!(pos.offset, 42); }
#[test]
fn test_position_start() {
let pos = Position::start();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
assert_eq!(pos.offset, 0);
}
#[test]
fn test_position_display() {
let pos = Position::new(5, 10, 42);
assert_eq!(format!("{}", pos), "line 5, column 10");
let pos_large = Position::new(999999, 999999, 999999999);
assert_eq!(format!("{}", pos_large), "line 999999, column 999999");
}
#[test]
fn test_position_clone() {
let pos1 = Position::new(3, 7, 25);
let pos2 = pos1.clone();
assert_eq!(pos1, pos2);
assert_eq!(pos1.line, pos2.line);
assert_eq!(pos1.column, pos2.column);
assert_eq!(pos1.offset, pos2.offset);
}
#[test]
fn test_position_equality() {
let pos1 = Position::new(2, 4, 10);
let pos2 = Position::new(2, 4, 10);
let pos3 = Position::new(2, 4, 11);
assert_eq!(pos1, pos2); assert_ne!(pos1, pos3); }
#[test]
fn test_position_boundary_values() {
let pos_zero = Position::new(0, 0, 0);
assert_eq!(format!("{}", pos_zero), "line 0, column 0");
let pos_max = Position::new(usize::MAX, usize::MAX, usize::MAX);
assert_eq!(pos_max.line, usize::MAX);
assert_eq!(pos_max.column, usize::MAX);
assert_eq!(pos_max.offset, usize::MAX);
}
#[test]
fn test_parse_error_unexpected_token() {
let pos = Position::new(1, 5, 4);
let error = ParseError::UnexpectedToken {
token: "(".to_string(),
position: pos.clone(),
expected: None,
};
let display = format!("{}", error);
assert!(display.contains("Unexpected token '('"));
assert!(display.contains("line 1, column 5"));
assert!(!display.contains("Expected"));
let error_with_expected = ParseError::UnexpectedToken {
token: "(".to_string(),
position: pos,
expected: Some(vec!["atom".to_string(), "variable".to_string()]),
};
let display = format!("{}", error_with_expected);
assert!(display.contains("Expected one of: atom, variable"));
}
#[test]
fn test_parse_error_unexpected_eof() {
let pos = Position::new(10, 20, 150);
let error = ParseError::UnexpectedEof {
position: pos.clone(),
expected: None,
};
let display = format!("{}", error);
assert!(display.contains("Unexpected end of input"));
assert!(display.contains("line 10, column 20"));
let error_with_expected = ParseError::UnexpectedEof {
position: pos,
expected: Some(vec![")".to_string(), "]".to_string()]),
};
let display = format!("{}", error_with_expected);
assert!(display.contains("Expected one of: ), ]"));
}
#[test]
fn test_parse_error_invalid_number() {
let pos = Position::new(3, 8, 25);
let error = ParseError::InvalidNumber {
value: "999999999999999999999".to_string(),
position: pos,
reason: "Number too large for 64-bit integer".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Invalid number '999999999999999999999'"));
assert!(display.contains("line 3, column 8"));
assert!(display.contains("Number too large"));
}
#[test]
fn test_parse_error_invalid_syntax() {
let pos = Position::new(2, 1, 10);
let error = ParseError::InvalidSyntax {
message: "Missing operator".to_string(),
position: pos.clone(),
suggestion: None,
};
let display = format!("{}", error);
assert!(display.contains("Invalid syntax"));
assert!(display.contains("Missing operator"));
assert!(!display.contains("Suggestion"));
let error_with_suggestion = ParseError::InvalidSyntax {
message: "Missing operator".to_string(),
position: pos,
suggestion: Some("Add ':-' between head and body".to_string()),
};
let display = format!("{}", error_with_suggestion);
assert!(display.contains("Suggestion: Add ':-' between head and body"));
}
#[test]
fn test_parse_error_unclosed_delimiter() {
let open_pos = Position::new(1, 10, 9);
let current_pos = Position::new(5, 1, 100);
let error = ParseError::UnclosedDelimiter {
delimiter: '(',
open_position: open_pos,
current_position: current_pos,
};
let display = format!("{}", error);
assert!(display.contains("Unclosed '('"));
assert!(display.contains("opened at line 1, column 10"));
assert!(display.contains("reached line 5, column 1"));
}
#[test]
fn test_parse_error_invalid_variable() {
let pos = Position::new(4, 3, 30);
let error = ParseError::InvalidVariable {
name: "123Var".to_string(), position: pos,
reason: "Variable names must start with uppercase or underscore".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Invalid variable '123Var'"));
assert!(display.contains("line 4, column 3"));
assert!(display.contains("must start with uppercase"));
}
#[test]
fn test_parse_error_invalid_atom() {
let pos = Position::new(7, 15, 85);
let error = ParseError::InvalidAtom {
name: "".to_string(), position: pos,
reason: "Atom name cannot be empty".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Invalid atom ''"));
assert!(display.contains("line 7, column 15"));
assert!(display.contains("cannot be empty"));
}
#[test]
fn test_parse_error_edge_cases() {
let error = ParseError::UnexpectedToken {
token: "".to_string(),
position: Position::start(),
expected: Some(vec![]), };
let display = format!("{}", error);
assert!(display.contains("Unexpected token ''"));
assert!(display.contains("Expected one of: "));
let long_token = "a".repeat(1000);
let error = ParseError::UnexpectedToken {
token: long_token.clone(),
position: Position::new(1, 1, 0),
expected: None,
};
let display = format!("{}", error);
assert!(display.contains(&long_token));
let error = ParseError::UnexpectedToken {
token: "\n\t\r".to_string(), position: Position::new(1, 1, 0),
expected: None,
};
let display = format!("{}", error);
assert!(display.contains("\n\t\r"));
}
#[test]
fn test_levenshtein_distance_basic() {
assert_eq!(levenshtein_distance("kitten", "sitting"), 3);
assert_eq!(levenshtein_distance("append", "appendd"), 1); assert_eq!(levenshtein_distance("length", "lentgh"), 2); assert_eq!(levenshtein_distance("same", "same"), 0); }
#[test]
fn test_levenshtein_distance_empty_strings() {
assert_eq!(levenshtein_distance("", ""), 0); assert_eq!(levenshtein_distance("abc", ""), 3); assert_eq!(levenshtein_distance("", "xyz"), 3); }
#[test]
fn test_levenshtein_distance_single_char() {
assert_eq!(levenshtein_distance("a", "a"), 0); assert_eq!(levenshtein_distance("a", "b"), 1); assert_eq!(levenshtein_distance("a", "ab"), 1); assert_eq!(levenshtein_distance("ab", "a"), 1); }
#[test]
fn test_levenshtein_distance_case_sensitive() {
assert_eq!(levenshtein_distance("ABC", "abc"), 3); assert_eq!(levenshtein_distance("Test", "test"), 1); }
#[test]
fn test_levenshtein_distance_unicode() {
assert_eq!(levenshtein_distance("こんにちは", "こんにちは"), 0);
assert_eq!(levenshtein_distance("abc", "xyz"), 3);
assert_eq!(levenshtein_distance("test", "best"), 1);
assert_eq!(levenshtein_distance("hello", "hallo"), 1); assert_eq!(levenshtein_distance("world", "word"), 1); }
#[test]
fn test_levenshtein_distance_boundary_cases() {
let long1 = "a".repeat(100);
let long2 = "a".repeat(99) + "b"; assert_eq!(levenshtein_distance(&long1, &long2), 1);
assert_eq!(levenshtein_distance("abc", "xyz"), 3);
assert_eq!(levenshtein_distance("12345", "abcde"), 5);
assert_eq!(levenshtein_distance("ab", "ba"), 2);
}
#[test]
fn test_levenshtein_distance_typical_typos() {
assert_eq!(levenshtein_distance("append", "apend"), 1); assert_eq!(levenshtein_distance("member", "mebmer"), 2); assert_eq!(levenshtein_distance("length", "lenght"), 2); assert_eq!(levenshtein_distance("write", "wriet"), 2); assert_eq!(levenshtein_distance("fail", "fial"), 2); }
#[test]
fn test_parse_error_implements_error_trait() {
let error = ParseError::InvalidSyntax {
message: "test".to_string(),
position: Position::start(),
suggestion: None,
};
let _: &dyn std::error::Error = &error;
assert!(error.source().is_none());
}
#[test]
fn test_runtime_error_implements_error_trait() {
use crate::ast::Term;
let error = RuntimeError::DivisionByZero {
expression: Term::Number(0),
};
let _: &dyn std::error::Error = &error;
assert!(error.source().is_none());
}
#[test]
fn test_parse_result_type() {
fn parse_something() -> ParseResult<i32> {
Err(ParseError::InvalidSyntax {
message: "test error".to_string(),
position: Position::start(),
suggestion: None,
})
}
let result = parse_something();
assert!(result.is_err());
fn parse_success() -> ParseResult<i32> {
Ok(42)
}
let result = parse_success();
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_runtime_result_type() {
use crate::ast::Term;
fn divide(a: i64, b: i64) -> RuntimeResult<i64> {
if b == 0 {
Err(RuntimeError::DivisionByZero {
expression: Term::Number(a),
})
} else {
Ok(a / b)
}
}
let result = divide(10, 2);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 5);
let result = divide(10, 0);
assert!(result.is_err());
}
#[test]
fn test_error_display_format_string_injection() {
let error = ParseError::UnexpectedToken {
token: "{} {:?} {:.2}".to_string(), position: Position::new(1, 1, 0),
expected: Some(vec!["{:x}".to_string()]), };
let display = format!("{}", error);
assert!(display.contains("{} {:?} {:.2}"));
assert!(display.contains("{:x}"));
}
#[test]
fn test_runtime_error_display() {
use crate::ast::Term;
let error = RuntimeError::ArithmeticError {
operation: "+".to_string(),
operands: vec![Term::Number(1), Term::Number(2)],
reason: "Integer overflow".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Arithmetic error in '+'"));
assert!(display.contains("Integer overflow"));
let error = RuntimeError::DivisionByZero {
expression: Term::Number(42),
};
let display = format!("{}", error);
assert!(display.contains("Division by zero"));
assert!(display.contains("42"));
let error = RuntimeError::TypeMismatch {
expected: "number".to_string(),
found: Term::Atom("hello".to_string()),
context: "arithmetic evaluation".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Type mismatch"));
assert!(display.contains("expected number"));
assert!(display.contains("found hello"));
let error = RuntimeError::UninstantiatedVariable {
variable: "X".to_string(),
context: "arithmetic expression".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Variable 'X'"));
assert!(display.contains("not sufficiently instantiated"));
let error = RuntimeError::PredicateNotFound {
functor: "unknown".to_string(),
arity: 2,
suggestion: None,
};
let display = format!("{}", error);
assert!(display.contains("Undefined predicate: unknown/2"));
assert!(!display.contains("Did you mean"));
let error = RuntimeError::PredicateNotFound {
functor: "appned".to_string(),
arity: 3,
suggestion: Some("append/3".to_string()),
};
let display = format!("{}", error);
assert!(display.contains("Did you mean: append/3"));
let error = RuntimeError::StackOverflow {
depth: 1000,
predicate: "infinite/1".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Stack overflow"));
assert!(display.contains("depth 1000"));
assert!(display.contains("infinite/1"));
let error = RuntimeError::CutOutsideRule;
let display = format!("{}", error);
assert!(display.contains("Cut (!)"));
assert!(display.contains("inside rule bodies"));
let error = RuntimeError::InvalidListStructure {
term: Term::Atom("not_a_list".to_string()),
expected: "proper list".to_string(),
};
let display = format!("{}", error);
assert!(display.contains("Invalid list structure"));
assert!(display.contains("expected proper list"));
assert!(display.contains("not_a_list"));
}
#[test]
fn test_levenshtein_large_strings() {
let s1 = "a".repeat(500);
let s2 = "a".repeat(499) + "b";
assert_eq!(levenshtein_distance(&s1, &s2), 1);
let s3 = "x".repeat(100);
let s4 = "y".repeat(100);
assert_eq!(levenshtein_distance(&s3, &s4), 100);
}
#[test]
fn test_position_debug_trait() {
let pos = Position::new(5, 10, 42);
let debug_str = format!("{:?}", pos);
assert!(debug_str.contains("Position"));
assert!(debug_str.contains("line: 5"));
assert!(debug_str.contains("column: 10"));
assert!(debug_str.contains("offset: 42"));
}
#[test]
fn test_error_debug_trait() {
let error = ParseError::InvalidSyntax {
message: "test".to_string(),
position: Position::new(1, 1, 0),
suggestion: Some("fix it".to_string()),
};
let debug_str = format!("{:?}", error);
assert!(debug_str.contains("InvalidSyntax"));
assert!(debug_str.contains("test"));
assert!(debug_str.contains("fix it"));
}