using GFramework.Core.Abstractions.Logging; using GFramework.Core.Logging.Appenders; namespace GFramework.Core.Tests.Logging; /// /// 测试 AsyncLogAppender 的功能和行为 /// [TestFixture] public class AsyncLogAppenderTests { [Test] public void Constructor_WithNullInnerAppender_ShouldThrowArgumentNullException() { Assert.Throws(() => new AsyncLogAppender(null!)); } [Test] public void Constructor_WithInvalidBufferSize_ShouldThrowArgumentException() { var innerAppender = new TestAppender(); Assert.Throws(() => new AsyncLogAppender(innerAppender, bufferSize: 0)); Assert.Throws(() => new AsyncLogAppender(innerAppender, bufferSize: -1)); } [Test] public void Append_ShouldNotBlock() { var innerAppender = new SlowAppender(delayMs: 100); using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000); var startTime = DateTime.UtcNow; for (int i = 0; i < 10; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; // 异步写入应该非常快(< 100ms),不应该等待内部 Appender Assert.That(elapsed, Is.LessThan(100)); } [Test] public void Append_ShouldEventuallyWriteToInnerAppender() { var innerAppender = new TestAppender(); using (var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000)) { for (int i = 0; i < 10; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } asyncAppender.Flush(); } Assert.That(innerAppender.Entries.Count, Is.EqualTo(10)); } [Test] public void Flush_ShouldWaitForAllEntriesToBeProcessed() { var innerAppender = new TestAppender(); using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000); for (int i = 0; i < 100; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } asyncAppender.Flush(); Assert.That(innerAppender.Entries.Count, Is.EqualTo(100)); } [Test] public void Dispose_ShouldProcessRemainingEntries() { var innerAppender = new TestAppender(); using (var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000)) { for (int i = 0; i < 50; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } } // Dispose 会等待所有日志处理完成 Assert.That(innerAppender.Entries.Count, Is.EqualTo(50)); } [Test] public void Append_AfterDispose_ShouldThrowObjectDisposedException() { var innerAppender = new TestAppender(); var asyncAppender = new AsyncLogAppender(innerAppender); asyncAppender.Dispose(); var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Test", null, null); Assert.Throws(() => asyncAppender.Append(entry)); } [Test] public void PendingCount_ShouldReflectQueuedEntries() { var innerAppender = new SlowAppender(delayMs: 50); using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000); for (int i = 0; i < 10; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } // 应该有一些待处理的条目 Assert.That(asyncAppender.PendingCount, Is.GreaterThanOrEqualTo(0)); } [Test] public async Task Append_FromMultipleThreads_ShouldHandleConcurrency() { var innerAppender = new TestAppender(); using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10000); var tasks = new Task[10]; for (int t = 0; t < 10; t++) { int threadId = t; tasks[t] = Task.Run(() => { for (int i = 0; i < 100; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Thread {threadId} Message {i}", null, null); asyncAppender.Append(entry); } }); } await Task.WhenAll(tasks); asyncAppender.Flush(); Assert.That(innerAppender.Entries.Count, Is.EqualTo(1000)); } [Test] public void Append_WhenInnerAppenderThrows_ShouldNotCrash() { var reportedExceptions = new List(); var innerAppender = new ThrowingAppender(); using var asyncAppender = new AsyncLogAppender( innerAppender, bufferSize: 1000, processingErrorHandler: reportedExceptions.Add); // 即使内部 Appender 抛出异常,也不应该影响调用线程 Assert.DoesNotThrow(() => { for (int i = 0; i < 10; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } }); asyncAppender.Flush(); Assert.That(reportedExceptions, Has.Count.EqualTo(10)); Assert.That(reportedExceptions, Has.All.TypeOf()); Assert.That(reportedExceptions.Select(static exception => exception.Message), Has.All.EqualTo("Test exception")); } [Test] public void Append_WhenProcessingErrorHandlerThrows_ShouldStillNotCrash() { var innerAppender = new ThrowingAppender(); using var asyncAppender = new AsyncLogAppender( innerAppender, bufferSize: 1000, processingErrorHandler: static _ => throw new InvalidOperationException("Observer failure")); Assert.DoesNotThrow(() => { for (int i = 0; i < 10; i++) { var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null); asyncAppender.Append(entry); } }); Assert.That(asyncAppender.Flush(), Is.True); } // 辅助测试类 private class TestAppender : ILogAppender { public List Entries { get; } = new(); public void Append(LogEntry entry) { lock (Entries) { Entries.Add(entry); } } public void Flush() { } public void Dispose() { } } private class SlowAppender : ILogAppender { private readonly int _delayMs; public SlowAppender(int delayMs) { _delayMs = delayMs; } public void Append(LogEntry entry) { Thread.Sleep(_delayMs); } public void Flush() { } public void Dispose() { } } private class ThrowingAppender : ILogAppender { public void Append(LogEntry entry) { throw new InvalidOperationException("Test exception"); } public void Flush() { } public void Dispose() { } } }