| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- """
- Tests for the Log Viewer UI component (Story 7.15).
- """
- import pytest
- from pathlib import Path
- import tempfile
- from datetime import datetime, timedelta
- from PyQt6.QtWidgets import QApplication
- from PyQt6.QtCore import Qt
- from src.ui.log_viewer import (
- LogLevel,
- LogEntry,
- InMemoryLogHandler,
- LogListModel,
- LogTableModel,
- LogViewer,
- get_log_handler,
- log_info,
- log_warning,
- log_error,
- )
- @pytest.fixture
- def app(qtbot):
- """Create QApplication for tests."""
- return QApplication.instance() or QApplication([])
- @pytest.fixture
- def clean_handler():
- """Create a fresh log handler for testing."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- handler = InMemoryLogHandler()
- return handler
- @pytest.fixture
- def sample_entries():
- """Create sample log entries."""
- now = datetime.now()
- return [
- LogEntry(
- level=LogLevel.INFO,
- timestamp=now - timedelta(minutes=3),
- message="Application started",
- module="main"
- ),
- LogEntry(
- level=LogLevel.DEBUG,
- timestamp=now - timedelta(minutes=2),
- message="Loading configuration",
- module="config"
- ),
- LogEntry(
- level=LogLevel.WARNING,
- timestamp=now - timedelta(minutes=1),
- message="Configuration file not found, using defaults",
- module="config"
- ),
- LogEntry(
- level=LogLevel.ERROR,
- timestamp=now - timedelta(seconds=30),
- message="Failed to connect to server",
- module="network"
- ),
- LogEntry(
- level=LogLevel.INFO,
- timestamp=now,
- message="Translation started",
- module="translator"
- ),
- ]
- class TestLogLevel:
- """Tests for LogLevel enum."""
- def test_level_values(self):
- """Test LogLevel enum values."""
- assert LogLevel.DEBUG.value == "DEBUG"
- assert LogLevel.INFO.value == "INFO"
- assert LogLevel.WARNING.value == "WARNING"
- assert LogLevel.ERROR.value == "ERROR"
- assert LogLevel.CRITICAL.value == "CRITICAL"
- def test_display_names(self):
- """Test Chinese display names."""
- assert LogLevel.DEBUG.display_name == "调试"
- assert LogLevel.INFO.display_name == "信息"
- assert LogLevel.WARNING.display_name == "警告"
- assert LogLevel.ERROR.display_name == "错误"
- assert LogLevel.CRITICAL.display_name == "严重"
- def test_colors(self):
- """Test level colors."""
- assert LogLevel.DEBUG.color == "#888888"
- assert LogLevel.INFO.color == "#3498db"
- assert LogLevel.WARNING.color == "#f39c12"
- assert LogLevel.ERROR.color == "#e74c3c"
- assert LogLevel.CRITICAL.color == "#8e44ad"
- class TestLogEntry:
- """Tests for LogEntry dataclass."""
- def test_entry_creation(self):
- """Test creating a log entry."""
- entry = LogEntry(
- level=LogLevel.INFO,
- timestamp=datetime.now(),
- message="Test message"
- )
- assert entry.level == LogLevel.INFO
- assert entry.message == "Test message"
- def test_str_representation(self):
- """Test string representation of entry."""
- now = datetime.now()
- entry = LogEntry(
- level=LogLevel.INFO,
- timestamp=now,
- message="Test message"
- )
- str_repr = str(entry)
- assert "[INFO]" in str_repr
- assert "Test message" in str_repr
- def test_timestamp_display(self):
- """Test timestamp display formatting."""
- now = datetime(2024, 3, 15, 14, 30, 45)
- entry = LogEntry(
- level=LogLevel.INFO,
- timestamp=now,
- message="Test"
- )
- assert entry.timestamp_display == "14:30:45"
- assert entry.date_display == "2024-03-15"
- def test_level_properties(self):
- """Test level-related properties."""
- entry = LogEntry(
- level=LogLevel.ERROR,
- timestamp=datetime.now(),
- message="Error message"
- )
- assert entry.level_display == "错误"
- assert entry.level_color == "#e74c3c"
- class TestInMemoryLogHandler:
- """Tests for InMemoryLogHandler."""
- def test_initialization(self, clean_handler):
- """Test handler initialization."""
- assert clean_handler.entry_count == 0
- def test_emit_entry(self, clean_handler):
- """Test emitting a log entry."""
- clean_handler.emit(LogLevel.INFO, "Test message")
- assert clean_handler.entry_count == 1
- def test_emit_with_metadata(self, clean_handler):
- """Test emitting with metadata."""
- clean_handler.emit(
- LogLevel.ERROR,
- "Error occurred",
- module="test_module",
- function="test_func",
- line=42
- )
- assert clean_handler.entry_count == 1
- def test_get_all_entries(self, clean_handler):
- """Test getting all entries."""
- clean_handler.emit(LogLevel.INFO, "Message 1")
- clean_handler.emit(LogLevel.WARNING, "Message 2")
- entries = clean_handler.get_entries()
- assert len(entries) == 2
- def test_filter_by_level(self, clean_handler):
- """Test filtering by log level."""
- clean_handler.emit(LogLevel.DEBUG, "Debug message")
- clean_handler.emit(LogLevel.INFO, "Info message")
- clean_handler.emit(LogLevel.WARNING, "Warning message")
- clean_handler.emit(LogLevel.ERROR, "Error message")
- # Get only INFO and above
- entries = clean_handler.get_entries(min_level=LogLevel.INFO)
- assert len(entries) == 3
- # Get only ERROR and above
- entries = clean_handler.get_entries(min_level=LogLevel.ERROR)
- assert len(entries) == 1
- def test_filter_by_search_text(self, clean_handler):
- """Test filtering by search text."""
- clean_handler.emit(LogLevel.INFO, "Application started")
- clean_handler.emit(LogLevel.INFO, "Configuration loaded")
- clean_handler.emit(LogLevel.ERROR, "Failed to connect")
- entries = clean_handler.get_entries(search_text="failed")
- assert len(entries) == 1
- entries = clean_handler.get_entries(search_text="app")
- assert len(entries) == 1
- def test_limit_entries(self, clean_handler):
- """Test limiting number of entries."""
- for i in range(10):
- clean_handler.emit(LogLevel.INFO, f"Message {i}")
- entries = clean_handler.get_entries(limit=5)
- assert len(entries) == 5
- def test_max_entries_trimming(self):
- """Test that handler trims old entries when over max."""
- handler = InMemoryLogHandler(max_entries=5)
- for i in range(10):
- handler.emit(LogLevel.INFO, f"Message {i}")
- # Should only keep last 5
- assert handler.entry_count == 5
- entries = handler.get_entries()
- assert entries[0].message == "Message 5"
- assert entries[-1].message == "Message 9"
- def test_clear(self, clean_handler):
- """Test clearing all entries."""
- clean_handler.emit(LogLevel.INFO, "Message 1")
- clean_handler.emit(LogLevel.INFO, "Message 2")
- assert clean_handler.entry_count == 2
- clean_handler.clear()
- assert clean_handler.entry_count == 0
- def test_listeners(self, clean_handler):
- """Test entry listeners."""
- received = []
- def listener(entry):
- received.append(entry)
- clean_handler.add_listener(listener)
- clean_handler.emit(LogLevel.INFO, "Test message")
- assert len(received) == 1
- assert received[0].message == "Test message"
- # Remove listener
- clean_handler.remove_listener(listener)
- clean_handler.emit(LogLevel.INFO, "Another message")
- assert len(received) == 1 # Should not have received the second message
- class TestLogTableModel:
- """Tests for LogTableModel."""
- def test_initialization(self, app, sample_entries):
- """Test model initialization."""
- model = LogTableModel(sample_entries)
- assert model.rowCount() == 5
- assert model.columnCount() == 4
- def test_data_retrieval(self, app, sample_entries):
- """Test data retrieval from model."""
- model = LogTableModel(sample_entries)
- # Check first row
- index = model.index(0, 1) # Level column
- assert model.data(index) == "信息"
- index = model.index(0, 2) # Message column
- assert "Application started" in model.data(index)
- def test_header_data(self, app, sample_entries):
- """Test header data."""
- model = LogTableModel(sample_entries)
- assert model.headerData(0, Qt.Orientation.Horizontal) == "时间"
- assert model.headerData(1, Qt.Orientation.Horizontal) == "级别"
- assert model.headerData(2, Qt.Orientation.Horizontal) == "消息"
- assert model.headerData(3, Qt.Orientation.Horizontal) == "模块"
- def test_get_entry_at_row(self, app, sample_entries):
- """Test getting entry at specific row."""
- model = LogTableModel(sample_entries)
- entry = model.get_entry_at_row(0)
- assert entry is not None
- assert entry.message == "Application started"
- entry = model.get_entry_at_row(999)
- assert entry is None
- class TestLogFunctions:
- """Tests for module-level logging functions."""
- def test_log_info(self):
- """Test log_info function."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- log_info("Info message")
- handler = get_log_handler()
- assert handler.entry_count == 1
- def test_log_warning(self):
- """Test log_warning function."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- log_warning("Warning message")
- handler = get_log_handler()
- assert handler.entry_count == 1
- def test_log_error(self):
- """Test log_error function."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- log_error("Error message")
- handler = get_log_handler()
- assert handler.entry_count == 1
- class TestLogHandlerSingleton:
- """Tests for the log handler singleton."""
- def test_get_singleton(self):
- """Test getting singleton instance."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- handler = get_log_handler()
- assert handler is not None
- assert isinstance(handler, InMemoryLogHandler)
- def test_singleton_persistence(self):
- """Test that singleton returns same instance."""
- import src.ui.log_viewer as lv
- lv._global_log_handler = None
- handler1 = get_log_handler()
- handler2 = get_log_handler()
- assert handler1 is handler2
|