use phprs::{
php::{streams, streams::StreamMode},
engine::{
facade::{ValFactory, StdValFactory},
lexer::Lexer,
types::{PhpType, PhpValue},
},
};
mod lexer_tests {
use super::*;
#[test]
fn test_lexer_all_keywords() {
let keywords = [
"if", "else", "elseif", "while", "for", "foreach", "function",
"class", "return", "echo", "true", "false", "null", "array",
"new", "static", "public", "private", "protected", "const",
"break", "continue", "switch", "case", "default", "try", "catch",
"throw", "namespace", "use", "as", "abstract", "extends", "final",
"implements", "interface", "trait", "clone",
];
for keyword in &keywords {
let mut lexer = Lexer::new(keyword);
let token = lexer.next_token().unwrap();
assert!(
token.token_type != phprs::engine::lexer::TokenType::T_ERROR,
"Keyword '{}' should not produce T_ERROR, got {:?}",
keyword,
token.token_type
);
}
}
#[test]
fn test_lexer_numbers() {
let test_cases = [
("42", phprs::engine::lexer::TokenType::T_LNUMBER),
("0", phprs::engine::lexer::TokenType::T_LNUMBER),
("3.14", phprs::engine::lexer::TokenType::T_DNUMBER),
("0.5", phprs::engine::lexer::TokenType::T_DNUMBER),
("123.456", phprs::engine::lexer::TokenType::T_DNUMBER),
];
for (input, expected_type) in &test_cases {
let mut lexer = Lexer::new(input);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type, *expected_type,
"Input '{}' should produce {:?}",
input, expected_type
);
}
}
#[test]
fn test_lexer_strings() {
let test_cases = [
("\"hello\"", "hello"),
("'world'", "world"),
("\"test\\nescape\"", "test\\nescape"),
];
for (input, _expected) in &test_cases {
let mut lexer = Lexer::new(input);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type,
phprs::engine::lexer::TokenType::T_CONSTANT_ENCAPSED_STRING
);
}
}
#[test]
fn test_lexer_variables() {
let test_cases = ["$x", "$variable", "$_private", "$camelCase"];
for input in &test_cases {
let mut lexer = Lexer::new(input);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type,
phprs::engine::lexer::TokenType::T_VARIABLE,
"Input '{}' should be T_VARIABLE",
input
);
}
}
#[test]
fn test_lexer_operators() {
let test_cases = [
("+", phprs::engine::lexer::TokenType::T_PLUS),
("-", phprs::engine::lexer::TokenType::T_MINUS),
("*", phprs::engine::lexer::TokenType::T_MUL),
("/=", phprs::engine::lexer::TokenType::T_DIV_EQUAL),
("==", phprs::engine::lexer::TokenType::T_IS_EQUAL),
("===", phprs::engine::lexer::TokenType::T_IS_IDENTICAL),
("!=", phprs::engine::lexer::TokenType::T_IS_NOT_EQUAL),
("!==", phprs::engine::lexer::TokenType::T_IS_NOT_IDENTICAL),
("<=", phprs::engine::lexer::TokenType::T_IS_SMALLER_OR_EQUAL),
(">=", phprs::engine::lexer::TokenType::T_IS_GREATER_OR_EQUAL),
("&&", phprs::engine::lexer::TokenType::T_BOOLEAN_AND),
("||", phprs::engine::lexer::TokenType::T_BOOLEAN_OR),
];
for (input, expected_type) in &test_cases {
let mut lexer = Lexer::new(input);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type, *expected_type,
"Input '{}' should produce {:?}",
input, expected_type
);
}
}
#[test]
fn test_lexer_complex_expression() {
let code = "$x = 10 + $y * 3.14;";
let mut lexer = Lexer::new(code);
let mut token_count = 0;
loop {
let token = lexer.next_token().unwrap();
if token.token_type == phprs::engine::lexer::TokenType::T_EOF {
break;
}
token_count += 1;
assert!(
token_count < 20,
"Too many tokens, possible infinite loop in lexer"
);
}
}
}
mod zval_factory_tests {
use super::*;
#[test]
fn test_zval_factory_all_types() {
let bool_val = StdValFactory::bool_val(true);
assert_eq!(bool_val.get_type(), PhpType::True);
let bool_false = StdValFactory::bool_val(false);
assert_eq!(bool_false.get_type(), PhpType::False);
let long_val = StdValFactory::long_val(42);
assert_eq!(long_val.get_type(), PhpType::Long);
let double_val = StdValFactory::double_val(3.14);
assert_eq!(double_val.get_type(), PhpType::Double);
let string_val = StdValFactory::string_val("test");
assert_eq!(string_val.get_type(), PhpType::String);
let null_val = StdValFactory::null_val();
assert_eq!(null_val.get_type(), PhpType::Null);
let array_val = StdValFactory::array_val();
assert_eq!(array_val.get_type(), PhpType::Array);
}
#[test]
fn test_zval_clone() {
let original = StdValFactory::long_val(123);
let cloned = StdValFactory::clone_val(&original);
assert_eq!(original.get_type(), cloned.get_type());
match (&original.value, &cloned.value) {
(PhpValue::Long(orig), PhpValue::Long(clon)) => {
assert_eq!(orig, clon);
}
_ => panic!("Cloned value should match original"),
}
}
#[test]
fn test_zval_result_dup() {
let source = StdValFactory::double_val(2.71828);
let copy = StdValFactory::result_dup(&source);
assert_eq!(source.get_type(), copy.get_type());
match (&source.value, ©.value) {
(PhpValue::Double(s), PhpValue::Double(c)) => {
assert!((s - c).abs() < f64::EPSILON);
}
_ => panic!("Result copy should preserve value"),
}
}
}
mod stream_tests {
use super::*;
use std::fs;
use std::io::{Read, Write};
#[test]
fn test_stream_all_modes() {
let test_paths = [
("/tmp/test_read.txt", StreamMode::Read),
("/tmp/test_write.txt", StreamMode::Write),
("/tmp/test_append.txt", StreamMode::Append),
("/tmp/test_readwrite.txt", StreamMode::ReadWrite),
];
for (path, _) in &test_paths {
fs::write(path, b"test").unwrap();
}
for (path, mode) in &test_paths {
let result = streams::php_stream_open(path, *mode);
match mode {
StreamMode::Read => assert!(result.is_ok(), "Read mode should work for existing file"),
StreamMode::Write => assert!(result.is_ok(), "Write mode should work"),
StreamMode::Append => assert!(result.is_ok(), "Append mode should work"),
StreamMode::ReadWrite => assert!(result.is_ok(), "ReadWrite mode should work"),
}
}
for (path, _) in &test_paths {
let _ = fs::remove_file(path);
}
}
#[test]
fn test_stream_write_read() {
let test_path = "/tmp/test_stream_roundtrip.txt";
let test_content = b"Hello, PHP-RS!";
let mut write_stream = streams::php_stream_open(test_path, StreamMode::Write).unwrap();
write_stream.write_all(test_content).unwrap();
write_stream.flush().unwrap();
let mut read_stream = streams::php_stream_open(test_path, StreamMode::Read).unwrap();
let mut read_content = Vec::new();
read_stream.read_to_end(&mut read_content).unwrap();
assert_eq!(test_content, read_content.as_slice());
let _ = fs::remove_file(test_path);
}
#[test]
fn test_stream_nonexistent() {
let result = streams::php_stream_open("/tmp/nonexistent_file_12345.txt", StreamMode::Read);
assert!(result.is_err(), "Opening nonexistent file should fail");
}
}
mod integration_tests {
use super::*;
#[test]
fn test_lexer_to_expression() {
let code = "42 + 3.14";
let mut lexer = Lexer::new(code);
let token1 = lexer.next_token().unwrap();
assert_eq!(
token1.token_type,
phprs::engine::lexer::TokenType::T_LNUMBER
);
let token2 = lexer.next_token().unwrap();
assert_eq!(token2.token_type, phprs::engine::lexer::TokenType::T_PLUS);
let token3 = lexer.next_token().unwrap();
assert_eq!(
token3.token_type,
phprs::engine::lexer::TokenType::T_DNUMBER
);
}
#[test]
fn test_variable_assignment_pattern() {
let code = "$x = 100";
let mut lexer = Lexer::new(code);
let token1 = lexer.next_token().unwrap();
assert_eq!(
token1.token_type,
phprs::engine::lexer::TokenType::T_VARIABLE
);
let token2 = lexer.next_token().unwrap();
assert_eq!(token2.token_type, phprs::engine::lexer::TokenType::T_EQUAL);
let token3 = lexer.next_token().unwrap();
assert_eq!(
token3.token_type,
phprs::engine::lexer::TokenType::T_LNUMBER
);
}
#[test]
fn test_function_call_pattern() {
let code = "echo \"hello\"";
let mut lexer = Lexer::new(code);
let token1 = lexer.next_token().unwrap();
assert_eq!(token1.token_type, phprs::engine::lexer::TokenType::T_ECHO);
let token2 = lexer.next_token().unwrap();
assert_eq!(
token2.token_type,
phprs::engine::lexer::TokenType::T_CONSTANT_ENCAPSED_STRING
);
}
}
mod edge_case_tests {
use super::*;
#[test]
fn test_empty_input() {
let mut lexer = Lexer::new("");
let token = lexer.next_token().unwrap();
assert_eq!(token.token_type, phprs::engine::lexer::TokenType::T_EOF);
}
#[test]
fn test_whitespace_only() {
let mut lexer = Lexer::new(" \n\t ");
let token = lexer.next_token().unwrap();
assert_eq!(token.token_type, phprs::engine::lexer::TokenType::T_EOF);
}
#[test]
fn test_very_long_number() {
let big_number = "12345678901234567890";
let mut lexer = Lexer::new(big_number);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type,
phprs::engine::lexer::TokenType::T_LNUMBER
);
}
#[test]
fn test_special_characters_in_string() {
let code = "\"\\n\\r\\t\\\\\"";
let mut lexer = Lexer::new(code);
let token = lexer.next_token().unwrap();
assert_eq!(
token.token_type,
phprs::engine::lexer::TokenType::T_CONSTANT_ENCAPSED_STRING
);
}
#[test]
fn test_boolean_values() {
let mut lexer = Lexer::new("true false null");
let tokens = vec![
lexer.next_token().unwrap(),
lexer.next_token().unwrap(),
lexer.next_token().unwrap(),
];
for token in tokens {
assert_ne!(token.token_type, phprs::engine::lexer::TokenType::T_ERROR);
}
}
#[test]
fn test_consecutive_operators() {
let code = "++--+-";
let mut lexer = Lexer::new(code);
loop {
let token = lexer.next_token().unwrap();
if token.token_type == phprs::engine::lexer::TokenType::T_EOF {
break;
}
}
}
#[test]
fn test_zval_factory_edge_cases() {
let zero_long = StdValFactory::long_val(0);
let zero_double = StdValFactory::double_val(0.0);
let bool_false = StdValFactory::bool_val(false);
let bool_true = StdValFactory::bool_val(true);
assert_eq!(zero_long.get_type(), PhpType::Long);
assert_eq!(zero_double.get_type(), PhpType::Double);
assert_eq!(bool_false.get_type(), PhpType::False);
assert_eq!(bool_true.get_type(), PhpType::True);
}
#[test]
fn test_string_factory_edge_cases() {
let empty_string = StdValFactory::string_val("");
assert_eq!(empty_string.get_type(), PhpType::String);
let unicode_string = StdValFactory::string_val("Hello δΈη π");
assert_eq!(unicode_string.get_type(), PhpType::String);
}
}
#[cfg(test)]
mod performance_tests {
use super::*;
use std::time::Instant;
#[test]
fn test_lexer_performance() {
let code = "$x + $y * $z / $w";
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
let mut lexer = Lexer::new(code);
loop {
let token = lexer.next_token().unwrap();
if token.token_type == phprs::engine::lexer::TokenType::T_EOF {
break;
}
}
}
let duration = start.elapsed();
assert!(
duration.as_millis() < 1000,
"Lexer performance test took too long: {:?}",
duration
);
}
#[test]
fn test_zval_factory_performance() {
let iterations = 10000;
let start = Instant::now();
for i in 0..iterations {
let _ = StdValFactory::long_val(i);
let _ = StdValFactory::double_val(i as f64);
let _ = StdValFactory::bool_val(i % 2 == 0);
}
let duration = start.elapsed();
assert!(
duration.as_millis() < 500,
"Factory performance test took too long: {:?}",
duration
);
}
}