Files
Simon Diesenreiter e824475872
Some checks failed
Upload Python Package / Create Release (push) Successful in 37s
Upload Python Package / deploy (push) Failing after 38s
feat: initial release, refs NOISSUE
2026-04-02 01:43:16 +02:00

400 lines
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Test logging utility for validating agent responses and system outputs."""
import re
from typing import Optional, Dict, Any, List
from datetime import datetime
# Color codes for terminal output
class Colors:
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
CYAN = '\033[96m'
RESET = '\033[0m'
class TestLogger:
"""Utility class for logging test results and assertions."""
def __init__(self):
self.assertions: List[Dict[str, Any]] = []
self.errors: List[Dict[str, Any]] = []
self.logs: List[str] = []
def log(self, message: str, level: str = 'INFO') -> None:
"""Log an informational message."""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
formatted = f"[{timestamp}] [{level}] {message}"
self.logs.append(formatted)
print(formatted)
def success(self, message: str) -> None:
"""Log a success message with green color."""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
formatted = f"{Colors.GREEN}[{timestamp}] [✓ PASS] {message}{Colors.RESET}"
self.logs.append(formatted)
print(formatted)
def error(self, message: str) -> None:
"""Log an error message with red color."""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
formatted = f"{Colors.RED}[{timestamp}] [✗ ERROR] {message}{Colors.RESET}"
self.logs.append(formatted)
print(formatted)
def warning(self, message: str) -> None:
"""Log a warning message with yellow color."""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
formatted = f"{Colors.YELLOW}[{timestamp}] [!] WARN {message}{Colors.RESET}"
self.logs.append(formatted)
print(formatted)
def info(self, message: str) -> None:
"""Log an info message with blue color."""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
formatted = f"{Colors.BLUE}[{timestamp}] [ INFO] {message}{Colors.RESET}"
self.logs.append(formatted)
print(formatted)
def assert_contains(self, text: str, expected: str, message: str = '') -> bool:
"""Assert that text contains expected substring."""
try:
contains = expected in text
if contains:
self.success(f"'{expected}' found in text")
self.assertions.append({
'type': 'assert_contains',
'result': 'pass',
'expected': expected,
'message': message or f"'{expected}' in text"
})
return True
else:
self.error(f"✗ Expected '{expected}' not found in text")
self.assertions.append({
'type': 'assert_contains',
'result': 'fail',
'expected': expected,
'message': message or f"'{expected}' in text"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_contains',
'result': 'error',
'expected': expected,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_not_contains(self, text: str, unexpected: str, message: str = '') -> bool:
"""Assert that text does not contain expected substring."""
try:
contains = unexpected in text
if not contains:
self.success(f"'{unexpected}' not found in text")
self.assertions.append({
'type': 'assert_not_contains',
'result': 'pass',
'unexpected': unexpected,
'message': message or f"'{unexpected}' not in text"
})
return True
else:
self.error(f"✗ Unexpected '{unexpected}' found in text")
self.assertions.append({
'type': 'assert_not_contains',
'result': 'fail',
'unexpected': unexpected,
'message': message or f"'{unexpected}' not in text"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_not_contains',
'result': 'error',
'unexpected': unexpected,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_equal(self, actual: str, expected: str, message: str = '') -> bool:
"""Assert that two strings are equal."""
try:
if actual == expected:
self.success(f"✓ Strings equal")
self.assertions.append({
'type': 'assert_equal',
'result': 'pass',
'expected': expected,
'message': message or f"actual == expected"
})
return True
else:
self.error(f"✗ Strings not equal. Expected: '{expected}', Got: '{actual}'")
self.assertions.append({
'type': 'assert_equal',
'result': 'fail',
'expected': expected,
'actual': actual,
'message': message or "actual == expected"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_equal',
'result': 'error',
'expected': expected,
'actual': actual,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_starts_with(self, text: str, prefix: str, message: str = '') -> bool:
"""Assert that text starts with expected prefix."""
try:
starts_with = text.startswith(prefix)
if starts_with:
self.success(f"✓ Text starts with '{prefix}'")
self.assertions.append({
'type': 'assert_starts_with',
'result': 'pass',
'prefix': prefix,
'message': message or f"text starts with '{prefix}'"
})
return True
else:
self.error(f"✗ Text does not start with '{prefix}'")
self.assertions.append({
'type': 'assert_starts_with',
'result': 'fail',
'prefix': prefix,
'message': message or f"text starts with '{prefix}'"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_starts_with',
'result': 'error',
'prefix': prefix,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_ends_with(self, text: str, suffix: str, message: str = '') -> bool:
"""Assert that text ends with expected suffix."""
try:
ends_with = text.endswith(suffix)
if ends_with:
self.success(f"✓ Text ends with '{suffix}'")
self.assertions.append({
'type': 'assert_ends_with',
'result': 'pass',
'suffix': suffix,
'message': message or f"text ends with '{suffix}'"
})
return True
else:
self.error(f"✗ Text does not end with '{suffix}'")
self.assertions.append({
'type': 'assert_ends_with',
'result': 'fail',
'suffix': suffix,
'message': message or f"text ends with '{suffix}'"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_ends_with',
'result': 'error',
'suffix': suffix,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_regex(self, text: str, pattern: str, message: str = '') -> bool:
"""Assert that text matches a regex pattern."""
try:
if re.search(pattern, text):
self.success(f"✓ Regex pattern matched")
self.assertions.append({
'type': 'assert_regex',
'result': 'pass',
'pattern': pattern,
'message': message or f"text matches regex '{pattern}'"
})
return True
else:
self.error(f"✗ Regex pattern did not match")
self.assertions.append({
'type': 'assert_regex',
'result': 'fail',
'pattern': pattern,
'message': message or f"text matches regex '{pattern}'"
})
return False
except re.error as e:
self.error(f"✗ Invalid regex pattern: {e}")
self.assertions.append({
'type': 'assert_regex',
'result': 'error',
'pattern': pattern,
'message': message or f"Invalid regex: {e}"
})
return False
except Exception as ex:
self.error(f"Assertion failed with exception: {ex}")
self.assertions.append({
'type': 'assert_regex',
'result': 'error',
'pattern': pattern,
'message': message or f"Assertion failed: {ex}"
})
return False
def assert_length(self, text: str, expected_length: int, message: str = '') -> bool:
"""Assert that text has expected length."""
try:
length = len(text)
if length == expected_length:
self.success(f"✓ Length is {expected_length}")
self.assertions.append({
'type': 'assert_length',
'result': 'pass',
'expected_length': expected_length,
'message': message or f"len(text) == {expected_length}"
})
return True
else:
self.error(f"✗ Length is {length}, expected {expected_length}")
self.assertions.append({
'type': 'assert_length',
'result': 'fail',
'expected_length': expected_length,
'actual_length': length,
'message': message or f"len(text) == {expected_length}"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_length',
'result': 'error',
'expected_length': expected_length,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_key_exists(self, text: str, key: str, message: str = '') -> bool:
"""Assert that a key exists in a JSON-like text."""
try:
if f'"{key}":' in text or f"'{key}':" in text:
self.success(f"✓ Key '{key}' exists")
self.assertions.append({
'type': 'assert_key_exists',
'result': 'pass',
'key': key,
'message': message or f"key '{key}' exists"
})
return True
else:
self.error(f"✗ Key '{key}' not found")
self.assertions.append({
'type': 'assert_key_exists',
'result': 'fail',
'key': key,
'message': message or f"key '{key}' exists"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_key_exists',
'result': 'error',
'key': key,
'message': message or f"Assertion failed: {e}"
})
return False
def assert_substring_count(self, text: str, substring: str, count: int, message: str = '') -> bool:
"""Assert that substring appears count times in text."""
try:
actual_count = text.count(substring)
if actual_count == count:
self.success(f"✓ Substring appears {count} time(s)")
self.assertions.append({
'type': 'assert_substring_count',
'result': 'pass',
'substring': substring,
'expected_count': count,
'actual_count': actual_count,
'message': message or f"'{substring}' appears {count} times"
})
return True
else:
self.error(f"✗ Substring appears {actual_count} time(s), expected {count}")
self.assertions.append({
'type': 'assert_substring_count',
'result': 'fail',
'substring': substring,
'expected_count': count,
'actual_count': actual_count,
'message': message or f"'{substring}' appears {count} times"
})
return False
except Exception as e:
self.error(f"Assertion failed with exception: {e}")
self.assertions.append({
'type': 'assert_substring_count',
'result': 'error',
'substring': substring,
'expected_count': count,
'message': message or f"Assertion failed: {e}"
})
return False
def get_assertion_count(self) -> int:
"""Get total number of assertions made."""
return len(self.assertions)
def get_failure_count(self) -> int:
"""Get number of failed assertions."""
return sum(1 for assertion in self.assertions if assertion.get('result') == 'fail')
def get_success_count(self) -> int:
"""Get number of passed assertions."""
return sum(1 for assertion in self.assertions if assertion.get('result') == 'pass')
def get_logs(self) -> List[str]:
"""Get all log messages."""
return self.logs.copy()
def get_errors(self) -> List[Dict[str, Any]]:
"""Get all error records."""
return self.errors.copy()
def clear(self) -> None:
"""Clear all logs and assertions."""
self.assertions.clear()
self.errors.clear()
self.logs.clear()
def __enter__(self):
"""Context manager entry."""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit."""
return False
# Convenience function for context manager usage
def test_logger():
"""Create and return a TestLogger instance."""
return TestLogger()