diff --git a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs index ddec08c8..f191863d 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureAdditionalCqrsHandlersTests.cs @@ -174,8 +174,15 @@ internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandl /// public void Register(IServiceCollection services, ILogger logger) { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(logger); + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (logger is null) + { + throw new ArgumentNullException(nameof(logger)); + } services.AddTransient>(_ => CreateHandler()); logger.Debug( diff --git a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs index 584090ab..685655a5 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs @@ -339,7 +339,7 @@ public class ArchitectureContextTests { workersReady.Signal(); startGate.Wait(); - return await context.SendRequestAsync(new TestCqrsRequest()); + return await context.SendRequestAsync(new TestCqrsRequest()).ConfigureAwait(false); })) .ToArray(); diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs index 05f98acc..b3717a56 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs @@ -95,14 +95,14 @@ public class ArchitectureLifecycleBehaviorTests /// 验证用户初始化失败时,等待 Ready 的任务会失败并进入 FailedInitialization 阶段。 /// [Test] - public async Task InitializeAsync_When_OnInitialize_Throws_Should_Mark_FailedInitialization() + public void InitializeAsync_When_OnInitialize_Throws_Should_Mark_FailedInitialization() { var architecture = new PhaseTrackingArchitecture(() => throw new InvalidOperationException("boom")); - var exception = Assert.ThrowsAsync(async () => await architecture.InitializeAsync()); + var exception = Assert.ThrowsAsync(() => architecture.InitializeAsync()); Assert.That(exception, Is.Not.Null); Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.FailedInitialization)); - Assert.ThrowsAsync(async () => await architecture.WaitUntilReadyAsync()); + Assert.ThrowsAsync(() => architecture.WaitUntilReadyAsync()); } /// @@ -139,7 +139,7 @@ public class ArchitectureLifecycleBehaviorTests var destroyOrder = new List(); var architecture = new FailingInitializationArchitecture(destroyOrder); - var exception = Assert.ThrowsAsync(async () => await architecture.InitializeAsync()); + var exception = Assert.ThrowsAsync(() => architecture.InitializeAsync()); Assert.That(exception, Is.Not.Null); Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.FailedInitialization)); diff --git a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs index 52c2ecbd..f1474403 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureServicesTests.cs @@ -351,12 +351,12 @@ public class TestArchitectureContextV3 : IArchitectureContext public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public TResponse SendRequest(IRequest request) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -366,12 +366,12 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendCommandAsync( GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -380,10 +380,10 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 命令响应类型。 /// 要发送的命令。 /// 命令响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -393,12 +393,12 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 要发送的查询。 /// 取消令牌。 /// 查询结果任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendQueryAsync( GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -407,35 +407,35 @@ public class TestArchitectureContextV3 : IArchitectureContext /// 查询结果类型。 /// 要发送的查询。 /// 查询结果。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : IRequest { - throw new NotImplementedException(); + throw new NotSupportedException(); } public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void SendCommand(ICommand command) diff --git a/GFramework.Core.Tests/Architectures/GameContextTests.cs b/GFramework.Core.Tests/Architectures/GameContextTests.cs index 9b990e78..fd6e2bbb 100644 --- a/GFramework.Core.Tests/Architectures/GameContextTests.cs +++ b/GFramework.Core.Tests/Architectures/GameContextTests.cs @@ -401,11 +401,11 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的请求。 /// 取消令牌。 /// 请求响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendRequestAsync(IRequest request, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -414,10 +414,10 @@ public class TestArchitectureContext : IArchitectureContext /// 响应类型。 /// 要发送的请求。 /// 请求响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendRequest(IRequest request) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -427,12 +427,12 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendCommandAsync( GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -441,10 +441,10 @@ public class TestArchitectureContext : IArchitectureContext /// 命令响应类型。 /// 要发送的命令。 /// 命令响应。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendCommand(GFramework.Cqrs.Abstractions.Cqrs.Command.ICommand command) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -454,12 +454,12 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的查询。 /// 取消令牌。 /// 查询结果任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendQueryAsync( GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -468,10 +468,10 @@ public class TestArchitectureContext : IArchitectureContext /// 查询结果类型。 /// 要发送的查询。 /// 查询结果。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public TResponse SendQuery(GFramework.Cqrs.Abstractions.Cqrs.Query.IQuery query) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -481,11 +481,11 @@ public class TestArchitectureContext : IArchitectureContext /// 要发布的通知。 /// 取消令牌。 /// 通知发布任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -495,12 +495,12 @@ public class TestArchitectureContext : IArchitectureContext /// 流式请求。 /// 取消令牌。 /// 异步响应流。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -510,11 +510,11 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的命令。 /// 取消令牌。 /// 命令发送任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendAsync(TCommand command, CancellationToken cancellationToken = default) where TCommand : IRequest { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// @@ -524,11 +524,11 @@ public class TestArchitectureContext : IArchitectureContext /// 要发送的请求。 /// 取消令牌。 /// 请求响应任务。 - /// 该测试桩未实现此成员。 + /// 该测试桩不支持此成员。 public ValueTask SendAsync(IRequest command, CancellationToken cancellationToken = default) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// diff --git a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs index 6882ced4..b3a3975f 100644 --- a/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs +++ b/GFramework.Core.Tests/Architectures/RegistryInitializationHookBaseTests.cs @@ -168,52 +168,52 @@ public class TestArchitectureWithRegistry : IArchitecture T IArchitecture.RegisterSystem(T system) { - throw new NotImplementedException(); + throw new NotSupportedException(); } T IArchitecture.RegisterModel(T model) { - throw new NotImplementedException(); + throw new NotSupportedException(); } T IArchitecture.RegisterUtility(T utility) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterCqrsPipelineBehavior() where TBehavior : class { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 - /// 该测试替身不参与 CQRS 程序集接入路径验证。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 /// /// 要接入的程序集集合。 - /// 该测试替身不参与 CQRS 程序集接入路径验证。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IArchitectureModule InstallModule(IArchitectureModule module) { - throw new NotImplementedException(); + throw new NotSupportedException(); } IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook) { - throw new NotImplementedException(); + throw new NotSupportedException(); } Task IArchitecture.WaitUntilReadyAsync() @@ -223,17 +223,17 @@ public class TestArchitectureWithRegistry : IArchitecture public void RegisterUtility(Action? onCreated = default(Action?)) where T : class, IUtility { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterModel(Action? onCreated = default(Action?)) where T : class, IModel { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterSystem(Action? onCreated = default(Action?)) where T : class, ISystem { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void Initialize() @@ -242,7 +242,7 @@ public class TestArchitectureWithRegistry : IArchitecture public void Destroy() { - throw new NotImplementedException(); + throw new NotSupportedException(); } Task IAsyncInitializable.InitializeAsync() @@ -257,7 +257,7 @@ public class TestArchitectureWithRegistry : IArchitecture public Task WaitUntilReadyAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterLifecycleHook(IArchitectureLifecycleHook hook) @@ -266,12 +266,12 @@ public class TestArchitectureWithRegistry : IArchitecture public Task InitializeAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public ValueTask DestroyAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } } @@ -287,7 +287,7 @@ public class TestArchitectureContextWithRegistry : TestArchitectureContext _registry = registry; } - public override TUtility GetUtility() + public override TUtility? GetUtility() where TUtility : class { if (typeof(TUtility) == typeof(TestRegistry)) { @@ -313,72 +313,72 @@ public class TestArchitectureWithoutRegistry : IArchitecture T IArchitecture.RegisterSystem(T system) { - throw new NotImplementedException(); + throw new NotSupportedException(); } T IArchitecture.RegisterModel(T model) { - throw new NotImplementedException(); + throw new NotSupportedException(); } T IArchitecture.RegisterUtility(T utility) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterCqrsPipelineBehavior() where TBehavior : class { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 /// /// 包含 CQRS 处理器或生成注册器的程序集。 - /// 该测试替身不参与 CQRS 程序集接入路径验证。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssembly(Assembly assembly) { - throw new NotImplementedException(); + throw new NotSupportedException(); } /// /// 测试替身未实现显式程序集 CQRS 处理器接入入口。 /// /// 要接入的程序集集合。 - /// 该测试替身不参与 CQRS 程序集接入路径验证。 + /// 该测试替身不参与 CQRS 程序集接入路径验证。 public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IArchitectureModule InstallModule(IArchitectureModule module) { - throw new NotImplementedException(); + throw new NotSupportedException(); } IArchitectureLifecycleHook IArchitecture.RegisterLifecycleHook(IArchitectureLifecycleHook hook) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public Task WaitUntilReadyAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterUtility(Action? onCreated = default(Action?)) where T : class, IUtility { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterModel(Action? onCreated = default(Action?)) where T : class, IModel { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterSystem(Action? onCreated = default(Action?)) where T : class, ISystem { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void Initialize() @@ -387,17 +387,17 @@ public class TestArchitectureWithoutRegistry : IArchitecture public Task InitializeAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public ValueTask DestroyAsync() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void Destroy() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void RegisterLifecycleHook(IArchitectureLifecycleHook hook) diff --git a/GFramework.Core.Tests/Command/AbstractAsyncCommandTests.cs b/GFramework.Core.Tests/Command/AbstractAsyncCommandTests.cs index 2cc2ec13..5c02898b 100644 --- a/GFramework.Core.Tests/Command/AbstractAsyncCommandTests.cs +++ b/GFramework.Core.Tests/Command/AbstractAsyncCommandTests.cs @@ -110,7 +110,7 @@ public class AbstractAsyncCommandTests var command = new TestAsyncCommandWithExceptionV3(input); var asyncCommand = (IAsyncCommand)command; - Assert.ThrowsAsync(async () => await asyncCommand.ExecuteAsync()); + Assert.ThrowsAsync(() => asyncCommand.ExecuteAsync()); } /// diff --git a/GFramework.Core.Tests/Command/CommandExecutorTests.cs b/GFramework.Core.Tests/Command/CommandExecutorTests.cs index caacc464..f12130ad 100644 --- a/GFramework.Core.Tests/Command/CommandExecutorTests.cs +++ b/GFramework.Core.Tests/Command/CommandExecutorTests.cs @@ -94,7 +94,7 @@ public class CommandExecutorTests [Test] public void SendAsync_WithNullCommand_Should_ThrowArgumentNullException() { - Assert.ThrowsAsync(async () => await _commandExecutor.SendAsync(null!)); + Assert.ThrowsAsync(() => _commandExecutor.SendAsync(null!)); } /// @@ -118,7 +118,7 @@ public class CommandExecutorTests [Test] public void SendAsync_WithResult_AndNullCommand_Should_ThrowArgumentNullException() { - Assert.ThrowsAsync(async () => await _commandExecutor.SendAsync(null!)); + Assert.ThrowsAsync(() => _commandExecutor.SendAsync(null!)); } } diff --git a/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs b/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs index e249013f..4709a5bd 100644 --- a/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs +++ b/GFramework.Core.Tests/Concurrency/AsyncKeyLockManagerTests.cs @@ -25,7 +25,7 @@ public sealed class AsyncKeyLockManagerTests using var manager = new AsyncKeyLockManager(); // Act - await using var handle = await manager.AcquireLockAsync("test-key"); + await using var handle = await manager.AcquireLockAsync("test-key").ConfigureAwait(false); // Assert Assert.That(handle, Is.Not.Null); @@ -47,13 +47,13 @@ public sealed class AsyncKeyLockManagerTests var index = i; tasks.Add(Task.Run(async () => { - await using var handle = await manager.AcquireLockAsync("same-key"); + await using var handle = await manager.AcquireLockAsync("same-key").ConfigureAwait(false); executionOrder.Add(index); - await Task.Delay(10); + await Task.Delay(10).ConfigureAwait(false); })); } - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); // Assert Assert.That(executionOrder.Count, Is.EqualTo(5)); @@ -75,15 +75,15 @@ public sealed class AsyncKeyLockManagerTests var key = $"key-{i}"; tasks.Add(Task.Run(async () => { - await using var handle = await manager.AcquireLockAsync(key); + await using var handle = await manager.AcquireLockAsync(key).ConfigureAwait(false); var current = Interlocked.Increment(ref concurrentCount); maxConcurrent = Math.Max(maxConcurrent, current); - await Task.Delay(50); + await Task.Delay(50).ConfigureAwait(false); Interlocked.Decrement(ref concurrentCount); })); } - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); // Assert Assert.That(maxConcurrent, Is.GreaterThan(1)); @@ -94,13 +94,13 @@ public sealed class AsyncKeyLockManagerTests { // Arrange using var manager = new AsyncKeyLockManager(); - var handle = await manager.AcquireLockAsync("test-key"); + var handle = await manager.AcquireLockAsync("test-key").ConfigureAwait(false); // Act - await handle.DisposeAsync(); + await handle.DisposeAsync().ConfigureAwait(false); // Assert - 应该能再次获取锁 - await using var handle2 = await manager.AcquireLockAsync("test-key"); + await using var handle2 = await manager.AcquireLockAsync("test-key").ConfigureAwait(false); Assert.That(handle2, Is.Not.Null); } @@ -117,13 +117,13 @@ public sealed class AsyncKeyLockManagerTests var key = $"key-{i % 10}"; tasks.Add(Task.Run(async () => { - await using var handle = await manager.AcquireLockAsync(key); - await Task.Delay(1); + await using var handle = await manager.AcquireLockAsync(key).ConfigureAwait(false); + await Task.Delay(1).ConfigureAwait(false); })); } // Assert - Assert.DoesNotThrowAsync(async () => await Task.WhenAll(tasks)); + await Task.WhenAll(tasks).ConfigureAwait(false); } [Test] @@ -139,14 +139,14 @@ public sealed class AsyncKeyLockManagerTests { tasks.Add(Task.Run(async () => { - await using var handle = await manager.AcquireLockAsync("same-key"); + await using var handle = await manager.AcquireLockAsync("same-key").ConfigureAwait(false); var temp = counter; - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); counter = temp + 1; })); } - await Task.WhenAll(tasks); + await Task.WhenAll(tasks).ConfigureAwait(false); // Assert Assert.That(counter, Is.EqualTo(100)); @@ -161,13 +161,13 @@ public sealed class AsyncKeyLockManagerTests lockTimeout: TimeSpan.FromMilliseconds(200)); // Act - await using (var handle = await manager.AcquireLockAsync("temp-key")) + await using (var handle = await manager.AcquireLockAsync("temp-key").ConfigureAwait(false)) { // 持有锁 } // 等待清理 - await Task.Delay(400); + await Task.Delay(400).ConfigureAwait(false); var stats = manager.GetStatistics(); @@ -184,10 +184,10 @@ public sealed class AsyncKeyLockManagerTests lockTimeout: TimeSpan.FromMilliseconds(200)); // Act - await using var handle = await manager.AcquireLockAsync("active-key"); + await using var handle = await manager.AcquireLockAsync("active-key").ConfigureAwait(false); // 等待清理尝试 - await Task.Delay(400); + await Task.Delay(400).ConfigureAwait(false); var activeLocks = manager.GetActiveLocks(); @@ -202,9 +202,9 @@ public sealed class AsyncKeyLockManagerTests using var manager = new AsyncKeyLockManager(); // Act - await using (await manager.AcquireLockAsync("key1")) + await using (await manager.AcquireLockAsync("key1").ConfigureAwait(false)) { - await using var handle2 = await manager.AcquireLockAsync("key2"); + await using var handle2 = await manager.AcquireLockAsync("key2").ConfigureAwait(false); var stats = manager.GetStatistics(); // Assert @@ -223,8 +223,8 @@ public sealed class AsyncKeyLockManagerTests using var manager = new AsyncKeyLockManager(); // Act - await using var handle1 = await manager.AcquireLockAsync("key1"); - await using var handle2 = await manager.AcquireLockAsync("key2"); + await using var handle1 = await manager.AcquireLockAsync("key1").ConfigureAwait(false); + await using var handle2 = await manager.AcquireLockAsync("key2").ConfigureAwait(false); var activeLocks = manager.GetActiveLocks(); @@ -243,7 +243,7 @@ public sealed class AsyncKeyLockManagerTests manager.Dispose(); // Act & Assert - Assert.ThrowsAsync(async () => await manager.AcquireLockAsync("test-key")); + Assert.ThrowsAsync(() => manager.AcquireLockAsync("test-key").AsTask()); } [Test] @@ -254,14 +254,14 @@ public sealed class AsyncKeyLockManagerTests using var cts = new CancellationTokenSource(); // 先获取锁 - await using var handle = await manager.AcquireLockAsync("test-key", cts.Token); + await using var handle = await manager.AcquireLockAsync("test-key", cts.Token).ConfigureAwait(false); // Act - await cts.CancelAsync(); + await cts.CancelAsync().ConfigureAwait(false); // Assert - Assert.CatchAsync(async () => - await manager.AcquireLockAsync("test-key", cts.Token)); + Assert.CatchAsync(() => + manager.AcquireLockAsync("test-key", cts.Token).AsTask()); } [Test] @@ -295,14 +295,14 @@ public sealed class AsyncKeyLockManagerTests { for (var j = 0; j < 10; j++) { - await using var handle = await manager.AcquireLockAsync($"key-{j % 5}"); - await Task.Delay(10); + await using var handle = await manager.AcquireLockAsync($"key-{j % 5}").ConfigureAwait(false); + await Task.Delay(10).ConfigureAwait(false); } })); } // Assert - Assert.DoesNotThrowAsync(async () => await Task.WhenAll(tasks)); + await Task.WhenAll(tasks).ConfigureAwait(false); } [Test] @@ -324,14 +324,14 @@ public sealed class AsyncKeyLockManagerTests { // Arrange using var manager = new AsyncKeyLockManager(); - var handle = await manager.AcquireLockAsync("test-key"); + var handle = await manager.AcquireLockAsync("test-key").ConfigureAwait(false); // Act - await handle.DisposeAsync(); - await handle.DisposeAsync(); + await handle.DisposeAsync().ConfigureAwait(false); + await handle.DisposeAsync().ConfigureAwait(false); handle.Dispose(); // Assert - 不应该抛出异常 Assert.Pass(); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/AsyncOperationTests.cs b/GFramework.Core.Tests/Coroutine/AsyncOperationTests.cs index d246c7cf..6f3b080b 100644 --- a/GFramework.Core.Tests/Coroutine/AsyncOperationTests.cs +++ b/GFramework.Core.Tests/Coroutine/AsyncOperationTests.cs @@ -104,7 +104,7 @@ public class AsyncOperationTests op.SetException(expectedException); - Assert.That(async () => await op.Task, Throws.InstanceOf()); + Assert.That(() => op.Task, Throws.InstanceOf()); } /// @@ -339,4 +339,4 @@ public class AsyncOperationTests Assert.DoesNotThrow(() => op.SetCompleted()); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/CommandCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/CommandCoroutineExtensionsTests.cs index c6b100b4..494688bd 100644 --- a/GFramework.Core.Tests/Coroutine/CommandCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/CommandCoroutineExtensionsTests.cs @@ -6,6 +6,7 @@ using GFramework.Core.Abstractions.Rule; using GFramework.Core.Coroutine.Extensions; using GFramework.Core.Coroutine.Instructions; using Moq; +using NUnit.Framework; namespace GFramework.Core.Tests.Coroutine; @@ -18,6 +19,8 @@ namespace GFramework.Core.Tests.Coroutine; [TestFixture] public class CommandCoroutineExtensionsTests { + private static readonly TimeSpan WaitForTaskTimeout = TimeSpan.FromSeconds(1); + /// /// 测试用的简单命令类 /// @@ -83,15 +86,10 @@ public class CommandCoroutineExtensionsTests var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, ex => capturedException = ex); - // 迭代协程直到完成 - while (coroutine.MoveNext()) - { - if (coroutine.Current is WaitForTask waitForTask) - { - // 等待任务完成 - await Task.Delay(10); - } - } + Assert.That(coroutine.MoveNext(), Is.True); + Assert.That(coroutine.Current, Is.TypeOf()); + await WaitForTaskAsync((WaitForTask)coroutine.Current); + Assert.That(coroutine.MoveNext(), Is.False); Assert.That(capturedException, Is.Null); } @@ -114,15 +112,10 @@ public class CommandCoroutineExtensionsTests var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command, ex => capturedException = ex); - // 迭代协程直到完成 - while (coroutine.MoveNext()) - { - if (coroutine.Current is WaitForTask waitForTask) - { - // 等待任务完成 - await Task.Delay(10); - } - } + Assert.That(coroutine.MoveNext(), Is.True); + Assert.That(coroutine.Current, Is.TypeOf()); + await WaitForTaskAsync((WaitForTask)coroutine.Current); + Assert.That(coroutine.MoveNext(), Is.False); Assert.That(capturedException, Is.Not.Null); // 异常被包装为 AggregateException @@ -148,17 +141,12 @@ public class CommandCoroutineExtensionsTests var coroutine = contextAware.SendCommandCoroutineWithErrorHandler(command); - // 迭代协程应该抛出异常 - Assert.Throws(() => - { - while (coroutine.MoveNext()) - { - if (coroutine.Current is WaitForTask waitForTask) - { - Task.Delay(10).Wait(); - } - } - }); + Assert.That(coroutine.MoveNext(), Is.True); + Assert.That(coroutine.Current, Is.TypeOf()); + Assert.That( + SpinWait.SpinUntil(() => ((WaitForTask)coroutine.Current).IsDone, WaitForTaskTimeout), + Is.True); + Assert.Throws(() => coroutine.MoveNext()); } /// @@ -201,8 +189,9 @@ public class CommandCoroutineExtensionsTests }); // 启动协程并等待命令执行完成 - coroutine.MoveNext(); // 进入命令发送阶段 - if (coroutine.Current is WaitForTask waitForTask) await Task.Delay(10); // 等待命令任务完成 + Assert.That(coroutine.MoveNext(), Is.True); // 进入命令发送阶段 + Assert.That(coroutine.Current, Is.TypeOf()); + await WaitForTaskAsync((WaitForTask)coroutine.Current); // 此时协程应该在等待事件 Assert.That(coroutine.MoveNext(), Is.True); // 等待事件阶段 @@ -296,15 +285,16 @@ public class CommandCoroutineExtensionsTests command); // null回调 // 启动协程 - coroutine.MoveNext(); // 进入命令发送阶段 - if (coroutine.Current is WaitForTask waitForTask) await Task.Delay(10); // 等待命令任务完成 + Assert.That(coroutine.MoveNext(), Is.True); // 进入命令发送阶段 + Assert.That(coroutine.Current, Is.TypeOf()); + await WaitForTaskAsync((WaitForTask)coroutine.Current); // 触发事件 var testEvent = new TestEvent { Data = "TestData" }; eventCallback?.Invoke(testEvent); // 协程应该能正常完成 - Assert.That(() => coroutine.MoveNext(), Throws.Nothing); + Assert.DoesNotThrow(() => coroutine.MoveNext()); } /// @@ -340,8 +330,9 @@ public class CommandCoroutineExtensionsTests _ => { }); // 启动协程 - 命令失败时协程仍然继续 - coroutine.MoveNext(); // 进入命令发送阶段 - if (coroutine.Current is WaitForTask waitForTask) await Task.Delay(10); // 等待命令任务完成 + Assert.That(coroutine.MoveNext(), Is.True); // 进入命令发送阶段 + Assert.That(coroutine.Current, Is.TypeOf()); + await WaitForTaskAsync((WaitForTask)coroutine.Current); // 命令执行失败后,协程继续执行 Assert.Pass(); @@ -441,4 +432,18 @@ public class CommandCoroutineExtensionsTests // 调用 MoveNext 时应该抛出 InvalidOperationException Assert.Throws(() => coroutine.MoveNext()); } -} \ No newline at end of file + + private static async Task WaitForTaskAsync(WaitForTask waitForTask) + { + var timeoutAt = DateTime.UtcNow + WaitForTaskTimeout; + + // 协程通过轮询 IsDone 观察异步命令完成,这里保持相同语义但避免固定延时。 + while (!waitForTask.IsDone) + { + if (DateTime.UtcNow >= timeoutAt) + Assert.Fail("WaitForTask did not complete within the expected time."); + + await Task.Yield(); + } + } +} diff --git a/GFramework.Core.Tests/Coroutine/QueryCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/QueryCoroutineExtensionsTests.cs index 7e38dfdf..78b97394 100644 --- a/GFramework.Core.Tests/Coroutine/QueryCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/QueryCoroutineExtensionsTests.cs @@ -254,7 +254,7 @@ public class QueryCoroutineExtensionsTests { Name = "ComplexName", Values = new List { 1, 2, 3 }, - Metadata = new Dictionary { { "key", "value" } } + Metadata = new Dictionary(StringComparer.Ordinal) { { "key", "value" } } }; ComplexResult? receivedResult = null; @@ -362,7 +362,7 @@ internal class ComplexQuery : IQuery private IArchitectureContext? _context; public string Name { get; set; } = string.Empty; public List Values { get; set; } = new(); - public Dictionary Metadata { get; set; } = new(); + public Dictionary Metadata { get; set; } = new(StringComparer.Ordinal); public void SetContext(IArchitectureContext context) { @@ -388,4 +388,4 @@ internal class ComplexResult public string ProcessedName { get; set; } = string.Empty; public int Sum { get; set; } public int Count { get; set; } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs index 946ba9a4..9c46223c 100644 --- a/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs +++ b/GFramework.Core.Tests/Coroutine/TaskCoroutineExtensionsTests.cs @@ -66,8 +66,6 @@ public class TaskCoroutineExtensionsTests var task = Task.FromResult(42); var instruction = task.AsCoroutineInstruction(); - task.Wait(); - Assert.That(instruction.Result, Is.EqualTo(42)); } @@ -165,7 +163,7 @@ public class TaskCoroutineExtensionsTests Assert.That(completed, Is.False); tcs.SetResult(null); - Task.Delay(50).Wait(); + Task.Delay(50).ConfigureAwait(false).GetAwaiter().GetResult(); scheduler.Update(); scheduler.Update(); @@ -189,7 +187,7 @@ public class TaskCoroutineExtensionsTests Assert.That(scheduler.ActiveCoroutineCount, Is.EqualTo(1)); tcs.SetResult(42); - Task.Delay(50).Wait(); + Task.Delay(50).ConfigureAwait(false).GetAwaiter().GetResult(); scheduler.Update(); scheduler.Update(); @@ -265,7 +263,7 @@ public class TaskCoroutineExtensionsTests tcs.SetResult(null); tcs2.SetResult(42); - Task.Delay(50).Wait(); + Task.Delay(50).ConfigureAwait(false).GetAwaiter().GetResult(); scheduler.Update(); scheduler.Update(); @@ -286,4 +284,4 @@ public class TaskCoroutineExtensionsTests CurrentTime += DeltaTime; } } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/WaitForTaskTTests.cs b/GFramework.Core.Tests/Coroutine/WaitForTaskTTests.cs index b04ef283..1e3fc8f4 100644 --- a/GFramework.Core.Tests/Coroutine/WaitForTaskTTests.cs +++ b/GFramework.Core.Tests/Coroutine/WaitForTaskTTests.cs @@ -14,7 +14,7 @@ namespace GFramework.Core.Tests.Coroutine } [Test] - public async Task Constructor_WithCompletedTask_IsDoneImmediately() + public void Constructor_WithCompletedTask_IsDoneImmediately() { // Arrange var completedTask = Task.FromResult("test"); @@ -28,7 +28,7 @@ namespace GFramework.Core.Tests.Coroutine } [Test] - public async Task Constructor_WithIncompleteTask_IsNotDoneInitially() + public void Constructor_WithIncompleteTask_IsNotDoneInitially() { // Arrange var tcs = new TaskCompletionSource(); @@ -54,7 +54,7 @@ namespace GFramework.Core.Tests.Coroutine // Act tcs.SetResult("completed"); - await Task.Delay(10); // Allow time for continuation + await Task.Delay(10).ConfigureAwait(false); // Allow time for continuation // Assert final state Assert.That(waitForTask.IsDone, Is.True); @@ -62,7 +62,7 @@ namespace GFramework.Core.Tests.Coroutine } [Test] - public async Task Update_DoesNotChangeState() + public void Update_DoesNotChangeState() { // Arrange var completedTask = Task.FromResult("test"); @@ -85,7 +85,7 @@ namespace GFramework.Core.Tests.Coroutine // Act tcs.SetException(new InvalidOperationException("Test exception")); - await Task.Delay(10); // Allow time for continuation + await Task.Delay(10).ConfigureAwait(false); // Allow time for continuation // Assert Assert.That(waitForTask.IsDone, Is.True); @@ -93,4 +93,4 @@ namespace GFramework.Core.Tests.Coroutine Assert.That(waitForTask.Exception?.InnerException, Is.TypeOf()); } } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs index c6565bbf..c8d50b39 100644 --- a/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs +++ b/GFramework.Core.Tests/Coroutine/WaitForTaskTests.cs @@ -284,15 +284,14 @@ public class WaitForTaskTests var expectedValue = 123; var task = Task.Run(async () => { - await Task.Delay(50); + await Task.Delay(50).ConfigureAwait(false); return expectedValue; }); var wait = new WaitForTask(task); - await task; - - Task.Delay(100).Wait(); + await task.ConfigureAwait(false); + await Task.Delay(100).ConfigureAwait(false); Assert.That(wait.IsDone, Is.True); Assert.That(wait.Result, Is.EqualTo(expectedValue)); @@ -313,4 +312,4 @@ public class WaitForTaskTests Assert.That(wait.IsDone, Is.True); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs b/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs index 13d4933c..0b96d022 100644 --- a/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs +++ b/GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs @@ -33,11 +33,11 @@ public class AsyncExtensionsTests public void WithTimeout_Should_Throw_TimeoutException_When_Task_Exceeds_Timeout() { // Act & Assert - Assert.ThrowsAsync(async () => - await AsyncExtensions.WithTimeoutAsync( + Assert.ThrowsAsync(() => + AsyncExtensions.WithTimeoutAsync( async ct => { - await Task.Delay(TimeSpan.FromSeconds(2), ct); + await Task.Delay(TimeSpan.FromSeconds(2), ct).ConfigureAwait(false); return 42; }, TimeSpan.FromMilliseconds(100))); @@ -53,11 +53,11 @@ public class AsyncExtensionsTests using var cts = new CancellationTokenSource(); cts.Cancel(); // Act & Assert - Assert.ThrowsAsync(async () => - await AsyncExtensions.WithTimeoutAsync( + Assert.ThrowsAsync(() => + AsyncExtensions.WithTimeoutAsync( async ct => { - await Task.Delay(TimeSpan.FromSeconds(2), ct); + await Task.Delay(TimeSpan.FromSeconds(2), ct).ConfigureAwait(false); return 42; }, TimeSpan.FromSeconds(1), @@ -74,13 +74,13 @@ public class AsyncExtensionsTests var innerTaskCanceled = false; // Act & Assert - Assert.ThrowsAsync(async () => - await AsyncExtensions.WithTimeoutAsync( + Assert.ThrowsAsync(() => + AsyncExtensions.WithTimeoutAsync( async ct => { try { - await Task.Delay(TimeSpan.FromSeconds(5), ct); + await Task.Delay(TimeSpan.FromSeconds(5), ct).ConfigureAwait(false); return 0; } catch (OperationCanceledException) @@ -121,8 +121,8 @@ public class AsyncExtensionsTests public void WithTimeout_NoResult_Should_Throw_TimeoutException_When_Task_Exceeds_Timeout() { // Act & Assert - Assert.ThrowsAsync(async () => - await AsyncExtensions.WithTimeoutAsync( + Assert.ThrowsAsync(() => + AsyncExtensions.WithTimeoutAsync( ct => Task.Delay(TimeSpan.FromSeconds(2), ct), TimeSpan.FromMilliseconds(100))); } @@ -137,13 +137,13 @@ public class AsyncExtensionsTests var innerTaskCanceled = false; // Act & Assert - Assert.ThrowsAsync(async () => - await AsyncExtensions.WithTimeoutAsync( + Assert.ThrowsAsync(() => + AsyncExtensions.WithTimeoutAsync( async ct => { try { - await Task.Delay(TimeSpan.FromSeconds(5), ct); + await Task.Delay(TimeSpan.FromSeconds(5), ct).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -217,8 +217,8 @@ public class AsyncExtensionsTests }; // Act & Assert - Assert.ThrowsAsync(async () => - await taskFactory.WithRetryAsync(2, TimeSpan.FromMilliseconds(10))); + Assert.ThrowsAsync(() => + taskFactory.WithRetryAsync(2, TimeSpan.FromMilliseconds(10))); } /// @@ -236,11 +236,11 @@ public class AsyncExtensionsTests }; // Act & Assert - Assert.ThrowsAsync(async () => - await taskFactory.WithRetryAsync(3, TimeSpan.FromMilliseconds(10), + Assert.ThrowsAsync(() => + taskFactory.WithRetryAsync(3, TimeSpan.FromMilliseconds(10), ex => ex is not ArgumentException)); - await Task.Delay(50); // 等待任务完成 + await Task.Delay(50).ConfigureAwait(false); // 等待任务完成 Assert.That(attemptCount, Is.EqualTo(1)); // 不应该重试 } @@ -330,4 +330,4 @@ public class AsyncExtensionsTests // Assert Assert.That(capturedEx, Is.SameAs(expectedException)); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs b/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs index 042065ef..8b9bce5c 100644 --- a/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs +++ b/GFramework.Core.Tests/Extensions/CollectionExtensionsTests.cs @@ -173,7 +173,7 @@ public class CollectionExtensionsTests { var method = typeof(GFramework.Core.Extensions.CollectionExtensions) .GetMethods() - .Single(static method => method.Name == nameof(GFramework.Core.Extensions.CollectionExtensions.ToDictionarySafe)); + .Single(static method => string.Equals(method.Name, nameof(GFramework.Core.Extensions.CollectionExtensions.ToDictionarySafe), StringComparison.Ordinal)); var methodGenericArguments = method.GetGenericArguments(); var returnTypeGenericArguments = method.ReturnType.GetGenericArguments(); diff --git a/GFramework.Core.Tests/Extensions/ResultExtensionsTests.cs b/GFramework.Core.Tests/Extensions/ResultExtensionsTests.cs index 9d363da5..6de644d3 100644 --- a/GFramework.Core.Tests/Extensions/ResultExtensionsTests.cs +++ b/GFramework.Core.Tests/Extensions/ResultExtensionsTests.cs @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Globalization; using GFramework.Core.Functional; namespace GFramework.Core.Tests.Extensions; @@ -122,7 +123,7 @@ public class ResultExtensionsTests public void Map_Should_Transform_Success_Value() { var result = Result.Succeed(42); - var mapped = result.Map(x => x.ToString()); + var mapped = result.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsSuccess, Is.True); Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42")); } @@ -135,7 +136,7 @@ public class ResultExtensionsTests { var exception = new Exception("Error"); var result = Result.Fail(exception); - var mapped = result.Map(x => x.ToString()); + var mapped = result.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsFaulted, Is.True); Assert.That(mapped.Exception, Is.SameAs(exception)); } @@ -157,7 +158,7 @@ public class ResultExtensionsTests public void Bind_Should_Chain_Success_Results() { var result = Result.Succeed(42); - var bound = result.Bind(x => Result.Succeed(x.ToString())); + var bound = result.Bind(x => Result.Succeed(x.ToString(CultureInfo.InvariantCulture))); Assert.That(bound.IsSuccess, Is.True); Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42")); } @@ -170,7 +171,7 @@ public class ResultExtensionsTests { var exception = new Exception("Error"); var result = Result.Fail(exception); - var bound = result.Bind(x => Result.Succeed(x.ToString())); + var bound = result.Bind(x => Result.Succeed(x.ToString(CultureInfo.InvariantCulture))); Assert.That(bound.IsFaulted, Is.True); Assert.That(bound.Exception, Is.SameAs(exception)); } @@ -449,9 +450,9 @@ public class ResultExtensionsTests { var result = await ResultExtensions.TryAsync(async () => { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); return 42; - }); + }).ConfigureAwait(false); Assert.That(result.IsSuccess, Is.True); Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42)); } @@ -464,9 +465,9 @@ public class ResultExtensionsTests { var result = await ResultExtensions.TryAsync(async () => { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); throw new InvalidOperationException("Error"); - }); + }).ConfigureAwait(false); Assert.That(result.IsFaulted, Is.True); Assert.That(result.Exception, Is.TypeOf()); } @@ -477,7 +478,8 @@ public class ResultExtensionsTests [Test] public async Task TryAsync_Should_Handle_Synchronous_Exceptions() { - var result = await ResultExtensions.TryAsync(() => throw new InvalidOperationException("Sync error")); + var result = await ResultExtensions.TryAsync(() => throw new InvalidOperationException("Sync error")) + .ConfigureAwait(false); Assert.That(result.IsFaulted, Is.True); } @@ -487,7 +489,7 @@ public class ResultExtensionsTests [Test] public void TryAsync_Should_Throw_ArgumentNullException_When_Function_Is_Null() { - Assert.ThrowsAsync(async () => await ResultExtensions.TryAsync(null!)); + Assert.ThrowsAsync(() => ResultExtensions.TryAsync(null!)); } /// diff --git a/GFramework.Core.Tests/Functional/OptionTests.cs b/GFramework.Core.Tests/Functional/OptionTests.cs index ab5a10fe..43999fbb 100644 --- a/GFramework.Core.Tests/Functional/OptionTests.cs +++ b/GFramework.Core.Tests/Functional/OptionTests.cs @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Globalization; using GFramework.Core.Functional; using NUnit.Framework; @@ -122,7 +123,7 @@ public class OptionTests public void Map_WithSome_Should_Map_Value() { var option = Option.Some(42); - var mapped = option.Map(x => x.ToString()); + var mapped = option.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsSome, Is.True); Assert.That(mapped.GetOrElse(""), Is.EqualTo("42")); } @@ -134,7 +135,7 @@ public class OptionTests public void Map_WithNone_Should_Return_None() { var option = Option.None; - var mapped = option.Map(x => x.ToString()); + var mapped = option.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsNone, Is.True); } @@ -155,7 +156,7 @@ public class OptionTests public void Bind_WithSome_Should_Bind_Value() { var option = Option.Some("42"); - var bound = option.Bind(s => int.TryParse(s, out var i) + var bound = option.Bind(s => int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? Option.Some(i) : Option.None); @@ -170,7 +171,7 @@ public class OptionTests public void Bind_WithSome_Can_Return_None() { var option = Option.Some("invalid"); - var bound = option.Bind(s => int.TryParse(s, out var i) + var bound = option.Bind(s => int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var i) ? Option.Some(i) : Option.None); @@ -505,4 +506,4 @@ public class OptionTests var result = option.ToString(); Assert.That(result, Is.EqualTo("None")); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Functional/Pipe/PipeExtensionsTests.cs b/GFramework.Core.Tests/Functional/Pipe/PipeExtensionsTests.cs index 780356cb..1d79749e 100644 --- a/GFramework.Core.Tests/Functional/Pipe/PipeExtensionsTests.cs +++ b/GFramework.Core.Tests/Functional/Pipe/PipeExtensionsTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using GFramework.Core.Functional.Pipe; using NUnit.Framework; @@ -123,7 +124,7 @@ public class PipeExtensionsTests var result = value .Pipe(x => x * 2) .Pipe(x => x + 10) - .Pipe(x => x.ToString()); + .Pipe(x => x.ToString(CultureInfo.InvariantCulture)); // Assert Assert.That(result, Is.EqualTo("20")); @@ -139,7 +140,7 @@ public class PipeExtensionsTests var value = 42; // Act - var result = value.Let(x => x.ToString()); + var result = value.Let(x => x.ToString(CultureInfo.InvariantCulture)); // Assert Assert.That(result, Is.EqualTo("42")); @@ -171,7 +172,7 @@ public class PipeExtensionsTests var result = value.Let(s => new { Original = s, - Upper = s.ToUpper(), + Upper = s.ToUpperInvariant(), Length = s.Length }); @@ -280,4 +281,4 @@ public class PipeExtensionsTests // Assert Assert.That(result, Is.EqualTo("Large: 20")); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Functional/ResultTTests.cs b/GFramework.Core.Tests/Functional/ResultTTests.cs index ec2d86c6..993a3e57 100644 --- a/GFramework.Core.Tests/Functional/ResultTTests.cs +++ b/GFramework.Core.Tests/Functional/ResultTTests.cs @@ -11,6 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Globalization; using GFramework.Core.Functional; using NUnit.Framework; @@ -314,7 +315,7 @@ public class ResultTTests public void Map_Should_Transform_Value_When_Success() { var result = Result.Succeed(42); - var mapped = result.Map(x => x.ToString()); + var mapped = result.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsSuccess, Is.True); Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42")); } @@ -327,7 +328,7 @@ public class ResultTTests { var exception = new Exception("Error"); var result = Result.Fail(exception); - var mapped = result.Map(x => x.ToString()); + var mapped = result.Map(x => x.ToString(CultureInfo.InvariantCulture)); Assert.That(mapped.IsFaulted, Is.True); Assert.That(mapped.Exception, Is.SameAs(exception)); } @@ -360,7 +361,7 @@ public class ResultTTests public void Bind_Should_Chain_Success_Results() { var result = Result.Succeed(42); - var bound = result.Bind(x => Result.Succeed(x.ToString())); + var bound = result.Bind(x => Result.Succeed(x.ToString(CultureInfo.InvariantCulture))); Assert.That(bound.IsSuccess, Is.True); Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42")); } @@ -373,7 +374,7 @@ public class ResultTTests { var exception = new Exception("Error"); var result = Result.Fail(exception); - var bound = result.Bind(x => Result.Succeed(x.ToString())); + var bound = result.Bind(x => Result.Succeed(x.ToString(CultureInfo.InvariantCulture))); Assert.That(bound.IsFaulted, Is.True); Assert.That(bound.Exception, Is.SameAs(exception)); } @@ -413,9 +414,9 @@ public class ResultTTests var result = Result.Succeed(42); var mapped = await result.MapAsync(async x => { - await Task.Delay(1); - return x.ToString(); - }); + await Task.Delay(1).ConfigureAwait(false); + return x.ToString(CultureInfo.InvariantCulture); + }).ConfigureAwait(false); Assert.That(mapped.IsSuccess, Is.True); Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42")); } @@ -430,9 +431,9 @@ public class ResultTTests var result = Result.Fail(exception); var mapped = await result.MapAsync(async x => { - await Task.Delay(1); - return x.ToString(); - }); + await Task.Delay(1).ConfigureAwait(false); + return x.ToString(CultureInfo.InvariantCulture); + }).ConfigureAwait(false); Assert.That(mapped.IsFaulted, Is.True); Assert.That(mapped.Exception, Is.SameAs(exception)); } @@ -446,9 +447,9 @@ public class ResultTTests var result = Result.Succeed(42); var mapped = await result.MapAsync(async _ => { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); throw new InvalidOperationException("Async error"); - }); + }).ConfigureAwait(false); Assert.That(mapped.IsFaulted, Is.True); Assert.That(mapped.Exception, Is.TypeOf()); } @@ -550,7 +551,7 @@ public class ResultTTests public void Equals_Should_Return_False_When_Exception_Types_Differ() { var result1 = Result.Fail(new InvalidOperationException("Error")); - var result2 = Result.Fail(new ArgumentException("Error")); + var result2 = Result.Fail(new InvalidCastException("Error")); Assert.That(result1.Equals(result2), Is.False); } @@ -804,4 +805,4 @@ public class ResultTTests var bottom2 = Result.Bottom; Assert.That(bottom1.Equals(bottom2), Is.True); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Functional/ResultTests.cs b/GFramework.Core.Tests/Functional/ResultTests.cs index e3a3ad28..cb5ed8bd 100644 --- a/GFramework.Core.Tests/Functional/ResultTests.cs +++ b/GFramework.Core.Tests/Functional/ResultTests.cs @@ -329,7 +329,7 @@ public class ResultTests { // Arrange var result1 = Result.Failure(new InvalidOperationException("Error")); - var result2 = Result.Failure(new ArgumentException("Error")); + var result2 = Result.Failure(new InvalidCastException("Error")); // Act & Assert Assert.That(result1.Equals(result2), Is.False); diff --git a/GFramework.Core.Tests/Localization/LocalizationTableTests.cs b/GFramework.Core.Tests/Localization/LocalizationTableTests.cs index d86b7759..838ab147 100644 --- a/GFramework.Core.Tests/Localization/LocalizationTableTests.cs +++ b/GFramework.Core.Tests/Localization/LocalizationTableTests.cs @@ -9,7 +9,7 @@ public class LocalizationTableTests public void GetRawText_ShouldReturnCorrectText() { // Arrange - var data = new Dictionary + var data = new Dictionary(StringComparer.Ordinal) { ["test.key"] = "Test Value" }; @@ -26,13 +26,13 @@ public class LocalizationTableTests public void GetRawText_WithFallback_ShouldReturnFallbackValue() { // Arrange - var fallbackData = new Dictionary + var fallbackData = new Dictionary(StringComparer.Ordinal) { ["test.key"] = "Fallback Value" }; var fallbackTable = new LocalizationTable("test", "eng", fallbackData); - var data = new Dictionary(); + var data = new Dictionary(StringComparer.Ordinal); var table = new LocalizationTable("test", "zhs", data, fallbackTable); // Act @@ -46,7 +46,7 @@ public class LocalizationTableTests public void ContainsKey_ShouldReturnTrue_WhenKeyExists() { // Arrange - var data = new Dictionary + var data = new Dictionary(StringComparer.Ordinal) { ["test.key"] = "Test Value" }; @@ -63,13 +63,13 @@ public class LocalizationTableTests public void Merge_ShouldOverrideExistingValues() { // Arrange - var data = new Dictionary + var data = new Dictionary(StringComparer.Ordinal) { ["test.key"] = "Original Value" }; var table = new LocalizationTable("test", "eng", data); - var overrides = new Dictionary + var overrides = new Dictionary(StringComparer.Ordinal) { ["test.key"] = "Override Value" }; @@ -81,4 +81,4 @@ public class LocalizationTableTests // Assert Assert.That(result, Is.EqualTo("Override Value")); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Logging/DefaultLogFormatterTests.cs b/GFramework.Core.Tests/Logging/DefaultLogFormatterTests.cs index 7d363e98..9d607520 100644 --- a/GFramework.Core.Tests/Logging/DefaultLogFormatterTests.cs +++ b/GFramework.Core.Tests/Logging/DefaultLogFormatterTests.cs @@ -48,7 +48,7 @@ public class DefaultLogFormatterTests [Test] public void Format_WithProperties_ShouldIncludeProperties() { - var properties = new Dictionary + var properties = new Dictionary(StringComparer.Ordinal) { ["UserId"] = 12345, ["UserName"] = "TestUser" @@ -66,7 +66,7 @@ public class DefaultLogFormatterTests [Test] public void Format_WithNullProperty_ShouldHandleNull() { - var properties = new Dictionary + var properties = new Dictionary(StringComparer.Ordinal) { ["Key1"] = null }; @@ -114,4 +114,4 @@ public class DefaultLogFormatterTests Assert.That(result, Does.Contain(message)); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Logging/JsonLogFormatterTests.cs b/GFramework.Core.Tests/Logging/JsonLogFormatterTests.cs index 3808770f..138b7dc3 100644 --- a/GFramework.Core.Tests/Logging/JsonLogFormatterTests.cs +++ b/GFramework.Core.Tests/Logging/JsonLogFormatterTests.cs @@ -54,7 +54,7 @@ public class JsonLogFormatterTests [Test] public void Format_WithProperties_ShouldIncludePropertiesObject() { - var properties = new Dictionary + var properties = new Dictionary(StringComparer.Ordinal) { ["UserId"] = 12345, ["UserName"] = "TestUser", @@ -93,7 +93,7 @@ public class JsonLogFormatterTests [Test] public void Format_WithNullProperty_ShouldHandleNull() { - var properties = new Dictionary + var properties = new Dictionary(StringComparer.Ordinal) { ["Key1"] = null, ["Key2"] = "value" @@ -170,7 +170,7 @@ public class JsonLogFormatterTests [Test] public void Format_WithComplexProperties_ShouldSerializeCorrectly() { - var properties = new Dictionary + var properties = new Dictionary(StringComparer.Ordinal) { ["Number"] = 123, ["String"] = "test", @@ -184,4 +184,4 @@ public class JsonLogFormatterTests Assert.That(() => JsonDocument.Parse(result), Throws.Nothing); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Logging/LogContextTests.cs b/GFramework.Core.Tests/Logging/LogContextTests.cs index 63b6c2e4..cdd1f731 100644 --- a/GFramework.Core.Tests/Logging/LogContextTests.cs +++ b/GFramework.Core.Tests/Logging/LogContextTests.cs @@ -152,22 +152,22 @@ public class LogContextTests var task1Values = new List(); var task2Values = new List(); - var task1 = Task.Run(() => + var task1 = Task.Run(async () => { using (LogContext.Push("TaskId", "Task1")) { task1Values.Add(LogContext.Current["TaskId"]); - Task.Delay(50).Wait(); + await Task.Delay(50); task1Values.Add(LogContext.Current["TaskId"]); } }); - var task2 = Task.Run(() => + var task2 = Task.Run(async () => { using (LogContext.Push("TaskId", "Task2")) { task2Values.Add(LogContext.Current["TaskId"]); - Task.Delay(50).Wait(); + await Task.Delay(50); task2Values.Add(LogContext.Current["TaskId"]); } }); @@ -201,4 +201,4 @@ public class LogContextTests Assert.That(LogContext.Current.Count, Is.EqualTo(0)); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Logging/LogEntryTests.cs b/GFramework.Core.Tests/Logging/LogEntryTests.cs index 76d8a0eb..bd7f2e9b 100644 --- a/GFramework.Core.Tests/Logging/LogEntryTests.cs +++ b/GFramework.Core.Tests/Logging/LogEntryTests.cs @@ -13,7 +13,7 @@ public class LogEntryTests public void Constructor_WithAllParameters_ShouldCreateEntry() { var timestamp = DateTime.UtcNow; - var properties = new Dictionary { ["Key1"] = "Value1" }; + var properties = new Dictionary(StringComparer.Ordinal) { ["Key1"] = "Value1" }; var exception = new InvalidOperationException("Test"); var entry = new LogEntry(timestamp, LogLevel.Info, "TestLogger", "Test message", exception, properties); @@ -63,7 +63,7 @@ public class LogEntryTests public void GetAllProperties_WithProperties_ShouldReturnOnlyProperties() { LogContext.Clear(); - var properties = new Dictionary { ["PropKey"] = "PropValue" }; + var properties = new Dictionary(StringComparer.Ordinal) { ["PropKey"] = "PropValue" }; var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Test message", null, properties); var allProps = entry.GetAllProperties(); @@ -78,7 +78,7 @@ public class LogEntryTests LogContext.Clear(); using (LogContext.Push("ContextKey", "ContextValue")) { - var properties = new Dictionary { ["PropKey"] = "PropValue" }; + var properties = new Dictionary(StringComparer.Ordinal) { ["PropKey"] = "PropValue" }; var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Test message", null, properties); var allProps = entry.GetAllProperties(); @@ -97,7 +97,7 @@ public class LogEntryTests LogContext.Clear(); using (LogContext.Push("Key1", "ContextValue")) { - var properties = new Dictionary { ["Key1"] = "PropValue" }; + var properties = new Dictionary(StringComparer.Ordinal) { ["Key1"] = "PropValue" }; var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Test message", null, properties); var allProps = entry.GetAllProperties(); @@ -124,7 +124,7 @@ public class LogEntryTests public void RecordEquality_WithSameValues_ShouldBeEqual() { var timestamp = DateTime.UtcNow; - var properties = new Dictionary { ["Key1"] = "Value1" }; + var properties = new Dictionary(StringComparer.Ordinal) { ["Key1"] = "Value1" }; var entry1 = new LogEntry(timestamp, LogLevel.Info, "TestLogger", "Test message", null, properties); var entry2 = new LogEntry(timestamp, LogLevel.Info, "TestLogger", "Test message", null, properties); @@ -142,4 +142,4 @@ public class LogEntryTests Assert.That(entry1, Is.Not.EqualTo(entry2)); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs index 7efdd02f..5cce91e3 100644 --- a/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs +++ b/GFramework.Core.Tests/Logging/RollingFileAppenderTests.cs @@ -68,7 +68,7 @@ public class RollingFileAppenderTests } // 检查是否生成了多个文件 - var files = Directory.GetFiles(_testDir, "*.log").OrderBy(f => f).ToArray(); + var files = Directory.GetFiles(_testDir, "*.log"); Assert.That(files.Length, Is.GreaterThan(1)); } @@ -108,13 +108,21 @@ public class RollingFileAppenderTests appender.Flush(); } - var files = Directory.GetFiles(_testDir, "*.log").Select(Path.GetFileName).OrderBy(f => f).ToArray(); + var files = Directory.GetFiles(_testDir, "*.log") + .Select(static path => Path.GetFileName(path) ?? string.Empty) + .OrderBy(f => f, System.StringComparer.Ordinal) + .ToArray(); // 应该有 app.log, app.1.log, app.2.log 等 Assert.That(files, Does.Contain("app.log")); if (files.Length > 1) { - Assert.That(files.Any(f => f.StartsWith("app.") && f.EndsWith(".log") && f != "app.log"), Is.True); + Assert.That( + files.Any(f => + f.StartsWith("app.", System.StringComparison.Ordinal) && + f.EndsWith(".log", System.StringComparison.Ordinal) && + !string.Equals(f, "app.log", System.StringComparison.Ordinal)), + Is.True); } } @@ -163,4 +171,4 @@ public class RollingFileAppenderTests var content = File.ReadAllText(_testFilePath); Assert.That(content, Does.Contain("Test message")); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Model/AsyncTestModel.cs b/GFramework.Core.Tests/Model/AsyncTestModel.cs index bf2ee9f2..f2b2d56c 100644 --- a/GFramework.Core.Tests/Model/AsyncTestModel.cs +++ b/GFramework.Core.Tests/Model/AsyncTestModel.cs @@ -20,7 +20,7 @@ public sealed class AsyncTestModel : AbstractModel, IAsyncInitializable /// 表示异步操作的Task public async Task InitializeAsync() { - await Task.Delay(10); + await Task.Delay(10).ConfigureAwait(false); Initialized = true; } @@ -45,4 +45,4 @@ public sealed class AsyncTestModel : AbstractModel, IAsyncInitializable protected override void OnInit() { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Pause/PauseStackManagerTests.cs b/GFramework.Core.Tests/Pause/PauseStackManagerTests.cs index 6e7b0581..8ad83233 100644 --- a/GFramework.Core.Tests/Pause/PauseStackManagerTests.cs +++ b/GFramework.Core.Tests/Pause/PauseStackManagerTests.cs @@ -22,9 +22,9 @@ public class PauseStackManagerTests /// 在每个测试方法执行后清理资源 /// [TearDown] - public void TearDown() + public async Task TearDown() { - _manager.DestroyAsync(); + await _manager.DestroyAsync().ConfigureAwait(false); } private PauseStackManager _manager = null!; @@ -416,7 +416,7 @@ public class PauseStackManagerTests _manager.Push("Gameplay", PauseGroup.Gameplay); mockHandler.Reset(); - await _manager.DestroyAsync(); + await _manager.DestroyAsync().ConfigureAwait(false); Assert.That(mockHandler.CallCount, Is.EqualTo(2)); Assert.That(mockHandler.LastIsPaused, Is.False); diff --git a/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs b/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs index bf6397b9..a7312baa 100644 --- a/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs +++ b/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs @@ -80,7 +80,7 @@ public class AbstractAsyncQueryTests var query = new TestAsyncQueryWithExceptionV4(input); var asyncQuery = (IAsyncQuery)query; - Assert.ThrowsAsync(async () => await asyncQuery.DoAsync()); + Assert.ThrowsAsync(() => asyncQuery.DoAsync()); } /// diff --git a/GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs b/GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs index b23cc8f9..acf0cc84 100644 --- a/GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs +++ b/GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs @@ -44,7 +44,7 @@ public class AsyncQueryExecutorTests [Test] public void SendAsync_WithNullQuery_Should_ThrowArgumentNullException() { - Assert.ThrowsAsync(async () => await _asyncQueryExecutor.SendAsync(null!)); + Assert.ThrowsAsync(() => _asyncQueryExecutor.SendAsync(null!)); } /// @@ -100,7 +100,7 @@ public class AsyncQueryExecutorTests var input = new TestAsyncQueryInput { Value = 0 }; var query = new TestAsyncQueryWithException(input); - Assert.ThrowsAsync(async () => await _asyncQueryExecutor.SendAsync(query)); + Assert.ThrowsAsync(() => _asyncQueryExecutor.SendAsync(query)); } /// diff --git a/GFramework.Core.Tests/Resource/ResourceManagerTests.cs b/GFramework.Core.Tests/Resource/ResourceManagerTests.cs index 7e2e788c..dba15f62 100644 --- a/GFramework.Core.Tests/Resource/ResourceManagerTests.cs +++ b/GFramework.Core.Tests/Resource/ResourceManagerTests.cs @@ -33,7 +33,7 @@ public class TestResourceLoader : IResourceLoader public async Task LoadAsync(string path) { - await Task.Delay(10); // 模拟异步加载 + await Task.Delay(10).ConfigureAwait(false); // 模拟异步加载 return Load(path); } @@ -99,7 +99,7 @@ public class ResourceManagerTests [Test] public async Task LoadAsync_Should_Load_Resource() { - var resource = await _resourceManager.LoadAsync("test/resource1.txt"); + var resource = await _resourceManager.LoadAsync("test/resource1.txt").ConfigureAwait(false); Assert.That(resource, Is.Not.Null); Assert.That(resource!.Content, Is.EqualTo("Content 1")); @@ -404,4 +404,4 @@ public class ResourceManagerTests handle2!.Dispose(); Assert.That(_resourceManager.IsLoaded("test/resource2.txt"), Is.False); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Rule/ContextAwareEnvironmentExtensionsTests.cs b/GFramework.Core.Tests/Rule/ContextAwareEnvironmentExtensionsTests.cs index 31b8b718..4fb94ed1 100644 --- a/GFramework.Core.Tests/Rule/ContextAwareEnvironmentExtensionsTests.cs +++ b/GFramework.Core.Tests/Rule/ContextAwareEnvironmentExtensionsTests.cs @@ -93,7 +93,7 @@ public class ContextAwareEnvironmentExtensionsTests return false; } - public T GetRequired(string key) where T : class => throw new NotImplementedException(); + public T GetRequired(string key) where T : class => throw new NotSupportedException(); public void Register(string key, object value) { @@ -115,7 +115,7 @@ public class ContextAwareEnvironmentExtensionsTests return false; } - public T GetRequired(string key) where T : class => throw new NotImplementedException(); + public T GetRequired(string key) where T : class => throw new NotSupportedException(); public void Register(string key, object value) { @@ -129,4 +129,4 @@ public class ContextAwareEnvironmentExtensionsTests private class TestContextAware : ContextAwareBase { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Rule/ContextAwareServiceExtensionsTests.cs b/GFramework.Core.Tests/Rule/ContextAwareServiceExtensionsTests.cs index b0f066fb..5035903b 100644 --- a/GFramework.Core.Tests/Rule/ContextAwareServiceExtensionsTests.cs +++ b/GFramework.Core.Tests/Rule/ContextAwareServiceExtensionsTests.cs @@ -181,8 +181,8 @@ public class ContextAwareServiceExtensionsTests // Assert Assert.That(results, Has.Count.GreaterThanOrEqualTo(2)); - Assert.That(results.Any(s => s is TestSystem ts && ts.Name == "System1"), Is.True); - Assert.That(results.Any(s => s is TestSystem ts && ts.Name == "System2"), Is.True); + Assert.That(results.Any(s => s is TestSystem ts && string.Equals(ts.Name, "System1", System.StringComparison.Ordinal)), Is.True); + Assert.That(results.Any(s => s is TestSystem ts && string.Equals(ts.Name, "System2", System.StringComparison.Ordinal)), Is.True); } [Test] @@ -200,8 +200,8 @@ public class ContextAwareServiceExtensionsTests // Assert Assert.That(results, Has.Count.EqualTo(2)); - Assert.That(results.Any(m => m.Name == "Model1"), Is.True); - Assert.That(results.Any(m => m.Name == "Model2"), Is.True); + Assert.That(results.Any(m => string.Equals(m.Name, "Model1", System.StringComparison.Ordinal)), Is.True); + Assert.That(results.Any(m => string.Equals(m.Name, "Model2", System.StringComparison.Ordinal)), Is.True); } [Test] @@ -219,8 +219,8 @@ public class ContextAwareServiceExtensionsTests // Assert Assert.That(results, Has.Count.EqualTo(2)); - Assert.That(results.Any(u => u.Name == "Utility1"), Is.True); - Assert.That(results.Any(u => u.Name == "Utility2"), Is.True); + Assert.That(results.Any(u => string.Equals(u.Name, "Utility1", System.StringComparison.Ordinal)), Is.True); + Assert.That(results.Any(u => string.Equals(u.Name, "Utility2", System.StringComparison.Ordinal)), Is.True); } [Test] @@ -304,4 +304,4 @@ public class ContextAwareServiceExtensionsTests private class TestContextAware : ContextAwareBase { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/State/StateMachineSystemTests.cs b/GFramework.Core.Tests/State/StateMachineSystemTests.cs index 9ee084ff..63cb5b22 100644 --- a/GFramework.Core.Tests/State/StateMachineSystemTests.cs +++ b/GFramework.Core.Tests/State/StateMachineSystemTests.cs @@ -136,9 +136,9 @@ public class StateMachineSystemTests /// 测试DestroyAsync方法不抛出异常 /// [Test] - public async Task DestroyAsync_Should_Not_Throw_Exception() + public void DestroyAsync_Should_Not_Throw_Exception() { - Assert.That(async () => await _stateMachine!.DestroyAsync(), Throws.Nothing); + Assert.That(() => _stateMachine!.DestroyAsync(), Throws.Nothing); } /// diff --git a/GFramework.Core.Tests/State/StateMachineTests.cs b/GFramework.Core.Tests/State/StateMachineTests.cs index 3f854ade..9edfb036 100644 --- a/GFramework.Core.Tests/State/StateMachineTests.cs +++ b/GFramework.Core.Tests/State/StateMachineTests.cs @@ -216,7 +216,7 @@ public class StateMachineTests [Test] public void ChangeToAsync_ToUnregisteredState_Should_ThrowInvalidOperationException() { - Assert.ThrowsAsync(async () => await _stateMachine.ChangeToAsync()); + Assert.ThrowsAsync(() => _stateMachine.ChangeToAsync()); } /// @@ -525,7 +525,7 @@ public sealed class TestAsyncState : IState, IAsyncState /// 从哪个状态进入 public async Task OnEnterAsync(IState? from) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); EnterCalled = true; EnterCallCount++; EnterFrom = from; @@ -537,7 +537,7 @@ public sealed class TestAsyncState : IState, IAsyncState /// 离开到哪个状态 public async Task OnExitAsync(IState? to) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); ExitCalled = true; ExitCallCount++; ExitTo = to; @@ -550,7 +550,7 @@ public sealed class TestAsyncState : IState, IAsyncState /// 是否允许转换 public async Task CanTransitionToAsync(IState target) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); CanTransitionToCallCount++; return AllowTransition; } @@ -601,4 +601,4 @@ public static class StateMachineExtensions .GetValue(stateMachine) is Dictionary states && states.ContainsKey(typeof(T)); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/State/StateTests.cs b/GFramework.Core.Tests/State/StateTests.cs index 6e2d25f8..d8ac2044 100644 --- a/GFramework.Core.Tests/State/StateTests.cs +++ b/GFramework.Core.Tests/State/StateTests.cs @@ -674,7 +674,7 @@ public sealed class ConcreteAsyncStateV2 : IState, IAsyncState /// 从哪个状态进入 public async Task OnEnterAsync(IState? from) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); EnterCalled = true; EnterCallCount++; EnterFrom = from; @@ -686,7 +686,7 @@ public sealed class ConcreteAsyncStateV2 : IState, IAsyncState /// 退出到哪个状态 public async Task OnExitAsync(IState? to) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); ExitCalled = true; ExitCallCount++; ExitTo = to; @@ -699,7 +699,7 @@ public sealed class ConcreteAsyncStateV2 : IState, IAsyncState /// 如果可以转换则返回true,否则返回false public async Task CanTransitionToAsync(IState target) { - await Task.Delay(1); + await Task.Delay(1).ConfigureAwait(false); CanTransitionToAsyncAction?.Invoke(target); return AllowTransitions; } @@ -731,4 +731,4 @@ public sealed class ConcreteAsyncStateV2 : IState, IAsyncState { throw new InvalidOperationException("Sync CanTransitionTo should not be called for async state"); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Systems/AsyncTestSystem.cs b/GFramework.Core.Tests/Systems/AsyncTestSystem.cs index 2b715bc3..3e2880ad 100644 --- a/GFramework.Core.Tests/Systems/AsyncTestSystem.cs +++ b/GFramework.Core.Tests/Systems/AsyncTestSystem.cs @@ -16,7 +16,7 @@ public sealed class AsyncTestSystem : ISystem, IAsyncInitializable public async Task InitializeAsync() { - await Task.Delay(10); + await Task.Delay(10).ConfigureAwait(false); Initialized = true; } @@ -44,4 +44,4 @@ public sealed class AsyncTestSystem : ISystem, IAsyncInitializable public void OnArchitecturePhase(ArchitecturePhase phase) { } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs b/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs index a758b405..b95a70e2 100644 --- a/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs +++ b/GFramework.Core.Tests/Tests/AsyncArchitectureTests.cs @@ -89,11 +89,11 @@ public class AsyncArchitectureTests : ArchitectureTestsBase /// 异步任务 [Test] - public async Task Architecture_Should_Stop_Initialization_When_Model_Init_Fails() + public void Architecture_Should_Stop_Initialization_When_Model_Init_Fails() { Architecture!.AddPostRegistrationHook(a => { a.RegisterModel(new FailingModel()); }); - Assert.ThrowsAsync(async () => await Architecture.InitializeAsync()); + Assert.ThrowsAsync(() => Architecture.InitializeAsync()); Assert.That( Architecture.CurrentPhase, @@ -134,14 +134,14 @@ public class AsyncArchitectureTests : ArchitectureTestsBase /// 异步任务 [Test] - public async Task InitializeAsync_Should_Handle_Exception_Correctly() + public void InitializeAsync_Should_Handle_Exception_Correctly() { Architecture!.AddPostRegistrationHook(a => a.RegisterModel(new FailingModel()) ); - Assert.ThrowsAsync(async () => await Architecture.InitializeAsync()); + Assert.ThrowsAsync(() => Architecture.InitializeAsync()); AssertInitializationFailed(); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/CollectionExtensions.cs b/GFramework.Core/Extensions/CollectionExtensions.cs index 8a57ce46..baa2c4d4 100644 --- a/GFramework.Core/Extensions/CollectionExtensions.cs +++ b/GFramework.Core/Extensions/CollectionExtensions.cs @@ -20,8 +20,15 @@ public static class CollectionExtensions /// public static void ForEach(this IEnumerable source, Action action) { - ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(action); + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } foreach (var item in source) action(item); } @@ -58,7 +65,10 @@ public static class CollectionExtensions /// public static IEnumerable WhereNotNull(this IEnumerable source) where T : class { - ArgumentNullException.ThrowIfNull(source); + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } return source.Where(item => item is not null)!; } @@ -88,9 +98,20 @@ public static class CollectionExtensions Func valueSelector) where TKey : notnull #pragma warning restore MA0016 { - ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(keySelector); - ArgumentNullException.ThrowIfNull(valueSelector); + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector is null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (valueSelector is null) + { + throw new ArgumentNullException(nameof(valueSelector)); + } var dictionary = new Dictionary(); diff --git a/GFramework.Core/Extensions/ContextAwareEnvironmentExtensions.cs b/GFramework.Core/Extensions/ContextAwareEnvironmentExtensions.cs index 48abcfeb..5fd7766a 100644 --- a/GFramework.Core/Extensions/ContextAwareEnvironmentExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareEnvironmentExtensions.cs @@ -16,7 +16,11 @@ public static class ContextAwareEnvironmentExtensions /// 指定类型的环境对象,如果无法转换则返回null public static T? GetEnvironment(this IContextAware contextAware) where T : class { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetEnvironment() as T; } @@ -28,8 +32,12 @@ public static class ContextAwareEnvironmentExtensions /// 环境对象 public static IEnvironment GetEnvironment(this IContextAware contextAware) { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetEnvironment(); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs index 34512eeb..491c5230 100644 --- a/GFramework.Core/Extensions/ContextAwareEventExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareEventExtensions.cs @@ -17,6 +17,7 @@ public static class ContextAwareEventExtensions public static void SendEvent(this IContextAware contextAware) where TEvent : new() { ArgumentNullException.ThrowIfNull(contextAware); + var context = contextAware.GetContext(); context.SendEvent(); } @@ -67,4 +68,4 @@ public static class ContextAwareEventExtensions var context = contextAware.GetContext(); context.UnRegisterEvent(onEvent); } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/ContextAwareServiceExtensions.cs b/GFramework.Core/Extensions/ContextAwareServiceExtensions.cs index 2b0b8ecf..ae88a4af 100644 --- a/GFramework.Core/Extensions/ContextAwareServiceExtensions.cs +++ b/GFramework.Core/Extensions/ContextAwareServiceExtensions.cs @@ -24,7 +24,11 @@ public static class ContextAwareServiceExtensions /// 当指定服务未注册时抛出 public static TService GetService(this IContextAware contextAware) where TService : class { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return GetRequiredComponent(context, static architectureContext => architectureContext.GetService(), "Service"); @@ -40,7 +44,11 @@ public static class ContextAwareServiceExtensions /// 当指定系统未注册时抛出 public static TSystem GetSystem(this IContextAware contextAware) where TSystem : class, ISystem { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return GetRequiredComponent(context, static architectureContext => architectureContext.GetSystem(), "System"); @@ -56,7 +64,11 @@ public static class ContextAwareServiceExtensions /// 当指定模型未注册时抛出 public static TModel GetModel(this IContextAware contextAware) where TModel : class, IModel { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return GetRequiredComponent(context, static architectureContext => architectureContext.GetModel(), "Model"); @@ -72,7 +84,11 @@ public static class ContextAwareServiceExtensions /// 当指定工具未注册时抛出 public static TUtility GetUtility(this IContextAware contextAware) where TUtility : class, IUtility { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return GetRequiredComponent(context, static architectureContext => architectureContext.GetUtility(), "Utility"); @@ -92,7 +108,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetServices(this IContextAware contextAware) where TService : class { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetServices(); } @@ -107,7 +127,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetSystems(this IContextAware contextAware) where TSystem : class, ISystem { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetSystems(); } @@ -122,7 +146,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetModels(this IContextAware contextAware) where TModel : class, IModel { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetModels(); } @@ -137,7 +165,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetUtilities(this IContextAware contextAware) where TUtility : class, IUtility { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetUtilities(); } @@ -152,7 +184,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetServicesByPriority(this IContextAware contextAware) where TService : class { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetServicesByPriority(); } @@ -167,7 +203,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetSystemsByPriority(this IContextAware contextAware) where TSystem : class, ISystem { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetSystemsByPriority(); } @@ -182,7 +222,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetModelsByPriority(this IContextAware contextAware) where TModel : class, IModel { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetModelsByPriority(); } @@ -197,7 +241,11 @@ public static class ContextAwareServiceExtensions public static IReadOnlyList GetUtilitiesByPriority(this IContextAware contextAware) where TUtility : class, IUtility { - ArgumentNullException.ThrowIfNull(contextAware); + if (contextAware is null) + { + throw new ArgumentNullException(nameof(contextAware)); + } + var context = contextAware.GetContext(); return context.GetUtilitiesByPriority(); } @@ -206,11 +254,14 @@ public static class ContextAwareServiceExtensions Func resolver, string componentKind) where TComponent : class { - ArgumentNullException.ThrowIfNull(context); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } var component = resolver(context); return component ?? throw new InvalidOperationException($"{componentKind} {typeof(TComponent)} not registered"); } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/GuardExtensions.cs b/GFramework.Core/Extensions/GuardExtensions.cs index de68dcdf..716a3127 100644 --- a/GFramework.Core/Extensions/GuardExtensions.cs +++ b/GFramework.Core/Extensions/GuardExtensions.cs @@ -27,7 +27,9 @@ public static class GuardExtensions this T? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) where T : class { - ArgumentNullException.ThrowIfNull(value, paramName); + if (value is null) + throw new ArgumentNullException(paramName); + return value; } @@ -51,7 +53,8 @@ public static class GuardExtensions this string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { - ArgumentNullException.ThrowIfNull(value, paramName); + if (value is null) + throw new ArgumentNullException(paramName); if (value.Length == 0) throw new ArgumentException("字符串不能为空", paramName); @@ -79,7 +82,8 @@ public static class GuardExtensions this string? value, [CallerArgumentExpression(nameof(value))] string? paramName = null) { - ArgumentNullException.ThrowIfNull(value, paramName); + if (value is null) + throw new ArgumentNullException(paramName); if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("字符串不能为空或仅包含空白字符", paramName); @@ -108,7 +112,8 @@ public static class GuardExtensions this IEnumerable? source, [CallerArgumentExpression(nameof(source))] string? paramName = null) { - ArgumentNullException.ThrowIfNull(source, paramName); + if (source is null) + throw new ArgumentNullException(paramName); if (!source.Any()) throw new ArgumentException("集合不能为空", paramName); diff --git a/GFramework.Core/Extensions/NumericExtensions.cs b/GFramework.Core/Extensions/NumericExtensions.cs index 31e0b471..c0637310 100644 --- a/GFramework.Core/Extensions/NumericExtensions.cs +++ b/GFramework.Core/Extensions/NumericExtensions.cs @@ -30,10 +30,14 @@ public static class NumericExtensions ArgumentNullException.ThrowIfNull(max); if (min.CompareTo(max) > 0) + { throw new ArgumentException($"最小值 ({min}) 不能大于最大值 ({max})", nameof(min)); + } if (inclusive) + { return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0; + } return value.CompareTo(min) > 0 && value.CompareTo(max) < 0; } @@ -71,7 +75,9 @@ public static class NumericExtensions public static float InverseLerp(this float value, float from, float to) { if (Math.Abs(to - from) < float.Epsilon) + { throw new DivideByZeroException("起始值和目标值不能相等"); + } return (value - from) / (to - from); } diff --git a/GFramework.Core/Extensions/StoreEventBusExtensions.cs b/GFramework.Core/Extensions/StoreEventBusExtensions.cs index df3ad0ea..5d09ae24 100644 --- a/GFramework.Core/Extensions/StoreEventBusExtensions.cs +++ b/GFramework.Core/Extensions/StoreEventBusExtensions.cs @@ -27,8 +27,15 @@ public static class StoreEventBusExtensions bool publishDispatches = true, bool publishStateChanges = true) { - ArgumentNullException.ThrowIfNull(store); - ArgumentNullException.ThrowIfNull(eventBus); + if (store is null) + { + throw new ArgumentNullException(nameof(store)); + } + + if (eventBus is null) + { + throw new ArgumentNullException(nameof(eventBus)); + } IUnRegister? dispatchBridge = null; IUnRegister? stateBridge = null; @@ -60,8 +67,15 @@ public static class StoreEventBusExtensions /// 用于移除 dispatch 桥接中间件的句柄。 public static IUnRegister BridgeDispatchesToEventBus(this Store store, IEventBus eventBus) { - ArgumentNullException.ThrowIfNull(store); - ArgumentNullException.ThrowIfNull(eventBus); + if (store is null) + { + throw new ArgumentNullException(nameof(store)); + } + + if (eventBus is null) + { + throw new ArgumentNullException(nameof(eventBus)); + } return store.RegisterMiddleware(new DispatchEventBusMiddleware(eventBus)); } @@ -77,8 +91,15 @@ public static class StoreEventBusExtensions public static IUnRegister BridgeStateChangesToEventBus(this IReadonlyStore store, IEventBus eventBus) { - ArgumentNullException.ThrowIfNull(store); - ArgumentNullException.ThrowIfNull(eventBus); + if (store is null) + { + throw new ArgumentNullException(nameof(store)); + } + + if (eventBus is null) + { + throw new ArgumentNullException(nameof(eventBus)); + } return store.Subscribe(state => eventBus.Send(new StoreStateChangedEvent(state, DateTimeOffset.UtcNow))); @@ -115,4 +136,4 @@ public static class StoreEventBusExtensions _eventBus.Send(new StoreDispatchedEvent(dispatchRecord)); } } -} \ No newline at end of file +} diff --git a/GFramework.Core/Extensions/StringExtensions.cs b/GFramework.Core/Extensions/StringExtensions.cs index 63ecc4e9..914dc855 100644 --- a/GFramework.Core/Extensions/StringExtensions.cs +++ b/GFramework.Core/Extensions/StringExtensions.cs @@ -42,11 +42,15 @@ public static class StringExtensions ArgumentNullException.ThrowIfNull(suffix); if (maxLength < suffix.Length) + { throw new ArgumentOutOfRangeException(nameof(maxLength), $"最大长度必须至少为后缀长度 ({suffix.Length})"); + } if (str.Length <= maxLength) + { return str; + } return string.Concat(str.AsSpan(0, maxLength - suffix.Length), suffix); } @@ -71,4 +75,4 @@ public static class StringExtensions return string.Join(separator, values); } -} \ No newline at end of file +} diff --git a/GFramework.Core/StateManagement/StoreBuilder.cs b/GFramework.Core/StateManagement/StoreBuilder.cs index 48802aaa..50043a11 100644 --- a/GFramework.Core/StateManagement/StoreBuilder.cs +++ b/GFramework.Core/StateManagement/StoreBuilder.cs @@ -40,6 +40,7 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder UseMiddleware(IStoreMiddleware middleware) { ArgumentNullException.ThrowIfNull(middleware); + _configurators.Add(store => store.UseMiddleware(middleware)); return this; } @@ -109,6 +110,7 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder AddReducer(Func reducer) { ArgumentNullException.ThrowIfNull(reducer); + _configurators.Add(store => store.RegisterReducer(reducer)); return this; } @@ -122,7 +124,8 @@ public sealed class StoreBuilder : IStoreBuilder public IStoreBuilder AddReducer(IReducer reducer) { ArgumentNullException.ThrowIfNull(reducer); + _configurators.Add(store => store.RegisterReducer(reducer)); return this; } -} \ No newline at end of file +} diff --git a/GFramework.Core/StateManagement/StoreSelection.cs b/GFramework.Core/StateManagement/StoreSelection.cs index b912da46..0f417409 100644 --- a/GFramework.Core/StateManagement/StoreSelection.cs +++ b/GFramework.Core/StateManagement/StoreSelection.cs @@ -24,10 +24,13 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// private readonly List _listeners = []; - /// - /// 保护监听器集合和底层 Store 订阅句柄的同步锁。 - /// +#if NET9_0_OR_GREATER + // net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。 + private readonly System.Threading.Lock _lock = new(); +#else + // net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。 private readonly object _lock = new(); +#endif /// /// 负责从完整状态中投影出局部状态的选择器。 @@ -82,7 +85,9 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// 用于取消订阅的句柄。 IUnRegister IEvent.Register(Action onEvent) { - ArgumentNullException.ThrowIfNull(onEvent); + if (onEvent is null) + throw new ArgumentNullException(nameof(onEvent)); + return Register(_ => onEvent()); } @@ -94,7 +99,8 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// 时抛出。 public IUnRegister Register(Action onValueChanged) { - ArgumentNullException.ThrowIfNull(onValueChanged); + if (onValueChanged is null) + throw new ArgumentNullException(nameof(onValueChanged)); var subscription = new SelectionListenerSubscription(onValueChanged); var shouldAttach = false; @@ -126,7 +132,8 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// 时抛出。 public IUnRegister RegisterWithInitValue(Action action) { - ArgumentNullException.ThrowIfNull(action); + if (action is null) + throw new ArgumentNullException(nameof(action)); var subscription = new SelectionListenerSubscription(action) { @@ -189,7 +196,8 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// 时抛出。 public void UnRegister(Action onValueChanged) { - ArgumentNullException.ThrowIfNull(onValueChanged); + if (onValueChanged is null) + throw new ArgumentNullException(nameof(onValueChanged)); SelectionListenerSubscription? subscriptionToRemove = null; @@ -391,4 +399,4 @@ public sealed class StoreSelection : IReadonlyBindablePropert /// public TSelected PendingValue { get; set; } = default!; } -} \ No newline at end of file +} diff --git a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs index fb8ae72c..ebc77ed3 100644 --- a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs @@ -128,7 +128,7 @@ public class ArchitectureConfigIntegrationTests var secondArchitecture = new ModuleOnlyArchitecture(module); var exception = - Assert.ThrowsAsync(async () => await secondArchitecture.InitializeAsync().ConfigureAwait(false)); + Assert.ThrowsAsync(() => secondArchitecture.InitializeAsync()); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs b/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs index f9875933..1670c9b2 100644 --- a/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs +++ b/GFramework.Game.Tests/Config/GameConfigBootstrapTests.cs @@ -163,11 +163,11 @@ public class GameConfigBootstrapTests Is.True, "The first initialization attempt did not reach the guarded lifecycle section."); - var secondCallerException = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync().ConfigureAwait(false)); + var secondCallerException = Assert.ThrowsAsync(() => bootstrap.InitializeAsync()); continueInitialization.Set(); - Assert.DoesNotThrowAsync(async () => await firstInitializeTask.ConfigureAwait(false)); + Assert.DoesNotThrowAsync(() => firstInitializeTask); Assert.Multiple(() => { @@ -202,7 +202,7 @@ public class GameConfigBootstrapTests }) }); - var exception = Assert.ThrowsAsync(async () => await bootstrap.InitializeAsync().ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => bootstrap.InitializeAsync()); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs index a3468a2b..1e4da8ff 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderAllOfTests.cs @@ -90,7 +90,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -165,7 +165,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -223,7 +223,7 @@ public sealed class YamlConfigLoaderAllOfTests static config => config.Id); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -258,7 +258,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -298,7 +298,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -338,7 +338,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -378,7 +378,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -418,7 +418,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -458,7 +458,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -502,7 +502,7 @@ public sealed class YamlConfigLoaderAllOfTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs index 9c5501a1..3f0ff4c6 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentRequiredTests.cs @@ -75,7 +75,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -217,7 +217,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -267,7 +267,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateCaseSensitiveRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -317,7 +317,7 @@ public sealed class YamlConfigLoaderDependentRequiredTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs index 009655db..9cbd7d74 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderDependentSchemasTests.cs @@ -74,7 +74,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -174,7 +174,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -220,7 +220,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -264,7 +264,7 @@ public sealed class YamlConfigLoaderDependentSchemasTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs index 0edbb6e2..fe6fc325 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderEnumTests.cs @@ -127,7 +127,7 @@ public class YamlConfigLoaderEnumTests var loader = CreateLoader(); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -176,7 +176,7 @@ public class YamlConfigLoaderEnumTests var loader = CreateLoader(); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs index 071bc3e6..1d5f13ce 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderIfThenElseTests.cs @@ -105,7 +105,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -170,7 +170,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -263,7 +263,7 @@ public sealed class YamlConfigLoaderIfThenElseTests static config => config.Id); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -304,7 +304,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -345,7 +345,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -393,7 +393,7 @@ public sealed class YamlConfigLoaderIfThenElseTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs index cc510b06..70628b7f 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderNegationTests.cs @@ -70,7 +70,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -172,7 +172,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterRewardLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -272,7 +272,7 @@ public sealed class YamlConfigLoaderNegationTests var loader = CreateMonsterLoader(); var registry = CreateRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs index e23217e9..ecb44a4f 100644 --- a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs @@ -143,7 +143,7 @@ public class YamlConfigLoaderTests .RegisterTable("monster", "monster", static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -185,7 +185,7 @@ public class YamlConfigLoaderTests .RegisterTable("monster", "monster", static config => config.Id) .RegisterTable("broken", "broken", static config => config.Id); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -216,7 +216,7 @@ public class YamlConfigLoaderTests .RegisterTable("monster", "monster", static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -257,7 +257,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -306,7 +306,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -352,7 +352,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -398,7 +398,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -445,7 +445,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -494,7 +494,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -543,7 +543,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -591,7 +591,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -681,7 +681,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -769,7 +769,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -817,7 +817,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -931,7 +931,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -980,7 +980,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1028,7 +1028,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1074,7 +1074,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1174,7 +1174,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1226,7 +1226,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1280,7 +1280,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1336,7 +1336,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1394,7 +1394,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1452,7 +1452,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1561,7 +1561,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1617,7 +1617,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1635,57 +1635,7 @@ public class YamlConfigLoaderTests [Test] public async Task LoadAsync_Should_Accept_Object_Array_When_Contains_Matches_Declared_Subset_Properties() { - CreateConfigFile( - "monster/slime.yaml", - """ - id: 1 - name: Slime - entries: - - - id: 1 - weight: 2 - - - id: 2 - weight: 3 - """); - CreateSchemaFile( - "schemas/monster.schema.json", - """ - { - "type": "object", - "required": ["id", "name", "entries"], - "properties": { - "id": { "type": "integer" }, - "name": { "type": "string" }, - "entries": { - "type": "array", - "minContains": 1, - "contains": { - "type": "object", - "required": ["id"], - "properties": { - "id": { - "type": "integer", - "const": 1 - } - } - }, - "items": { - "type": "object", - "required": ["id", "weight"], - "properties": { - "id": { "type": "integer" }, - "weight": { "type": "integer" } - } - } - } - } - } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); + var loader = CreateLoaderForContainsSubsetObjectArrayScenario(); var registry = new ConfigRegistry(); await loader.LoadAsync(registry); @@ -1740,7 +1690,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1797,7 +1747,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1904,7 +1854,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -1954,7 +1904,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2004,7 +1954,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2055,7 +2005,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2107,7 +2057,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2161,7 +2111,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2261,7 +2211,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2317,7 +2267,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2369,7 +2319,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2423,7 +2373,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2478,7 +2428,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2537,7 +2487,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2553,14 +2503,7 @@ public class YamlConfigLoaderTests [Test] public void LoadAsync_Should_Throw_When_Nested_Object_Array_Reference_Target_Is_Missing() { - CreateConfigFile( - "item/potion.yaml", - """ - id: potion - name: Potion - """); - CreateConfigFile( - "monster/slime.yaml", + var loader = CreateItemBackedMonsterLoader( """ id: 1 name: Slime @@ -2571,21 +2514,7 @@ public class YamlConfigLoaderTests - wave: 2 dropItemId: bomb - """); - CreateSchemaFile( - "schemas/item.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } - } - } - """); - CreateSchemaFile( - "schemas/monster.schema.json", + """, """ { "type": "object", @@ -2609,16 +2538,12 @@ public class YamlConfigLoaderTests } } } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("item", "item", "schemas/item.schema.json", - static config => config.Id) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); + """, + static config => config.Id, + ("item/potion.yaml", "potion", "Potion")); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2749,7 +2674,7 @@ public class YamlConfigLoaderTests static config => config.Id); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2766,41 +2691,14 @@ public class YamlConfigLoaderTests [Test] public void LoadAsync_Should_Throw_When_Array_Reference_Item_Is_Missing() { - CreateConfigFile( - "item/potion.yaml", - """ - id: potion - name: Potion - """); - CreateConfigFile( - "item/slime-gel.yaml", - """ - id: slime_gel - name: Slime Gel - """); - CreateConfigFile( - "monster/slime.yaml", + var loader = CreateItemBackedMonsterLoader( """ id: 1 name: Slime dropItemIds: - potion - missing_item - """); - CreateSchemaFile( - "schemas/item.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } - } - } - """); - CreateSchemaFile( - "schemas/monster.schema.json", + """, """ { "type": "object", @@ -2815,16 +2713,13 @@ public class YamlConfigLoaderTests } } } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("item", "item", "schemas/item.schema.json", - static config => config.Id) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); + """, + static config => config.Id, + ("item/potion.yaml", "potion", "Potion"), + ("item/slime-gel.yaml", "slime_gel", "Slime Gel")); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -2841,35 +2736,14 @@ public class YamlConfigLoaderTests [Test] public void LoadAsync_Should_Throw_When_Contains_Matched_Reference_Target_Is_Missing() { - CreateConfigFile( - "item/potion.yaml", - """ - id: potion - name: Potion - """); - CreateConfigFile( - "monster/slime.yaml", + var loader = CreateItemBackedMonsterLoader( """ id: 1 name: Slime dropItemIds: - potion - missing_item - """); - CreateSchemaFile( - "schemas/item.schema.json", - """ - { - "type": "object", - "required": ["id", "name"], - "properties": { - "id": { "type": "string" }, - "name": { "type": "string" } - } - } - """); - CreateSchemaFile( - "schemas/monster.schema.json", + """, """ { "type": "object", @@ -2890,16 +2764,12 @@ public class YamlConfigLoaderTests } } } - """); - - var loader = new YamlConfigLoader(_rootPath) - .RegisterTable("item", "item", "schemas/item.schema.json", - static config => config.Id) - .RegisterTable("monster", "monster", "schemas/monster.schema.json", - static config => config.Id); + """, + static config => config.Id, + ("item/potion.yaml", "potion", "Potion")); var registry = new ConfigRegistry(); - var exception = Assert.ThrowsAsync(async () => await loader.LoadAsync(registry)); + var exception = Assert.ThrowsAsync(() => loader.LoadAsync(registry)); Assert.Multiple(() => { @@ -3353,6 +3223,111 @@ public class YamlConfigLoaderTests } } + /// + /// 为对象数组 contains 子集匹配场景创建加载器,避免测试方法体被大段固定 schema 稀释。 + /// + /// 已注册目标表的加载器。 + private YamlConfigLoader CreateLoaderForContainsSubsetObjectArrayScenario() + { + CreateConfigFile( + "monster/slime.yaml", + """ + id: 1 + name: Slime + entries: + - + id: 1 + weight: 2 + - + id: 2 + weight: 3 + """); + CreateSchemaFile( + "schemas/monster.schema.json", + """ + { + "type": "object", + "required": ["id", "name", "entries"], + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "entries": { + "type": "array", + "minContains": 1, + "contains": { + "type": "object", + "required": ["id"], + "properties": { + "id": { + "type": "integer", + "const": 1 + } + } + }, + "items": { + "type": "object", + "required": ["id", "weight"], + "properties": { + "id": { "type": "integer" }, + "weight": { "type": "integer" } + } + } + } + } + } + """); + + return new YamlConfigLoader(_rootPath) + .RegisterTable("monster", "monster", "schemas/monster.schema.json", + static config => config.Id); + } + + /// + /// 为跨表引用加载测试创建标准 item 表夹具,并按既有顺序注册 itemmonster。 + /// + /// monster 表的配置类型。 + /// monster 配置文件内容。 + /// monster schema 内容。 + /// monster 表主键选择器。 + /// 要写入的 item 配置文件集合。 + /// 已完成 schema 与表注册的加载器。 + private YamlConfigLoader CreateItemBackedMonsterLoader( + string monsterConfigContent, + string monsterSchemaContent, + Func keySelector, + params (string RelativePath, string ItemId, string Name)[] items) + { + foreach (var (relativePath, itemId, name) in items) + { + CreateConfigFile( + relativePath, + $""" + id: {itemId} + name: {name} + """); + } + + CreateConfigFile("monster/slime.yaml", monsterConfigContent); + CreateSchemaFile( + "schemas/item.schema.json", + """ + { + "type": "object", + "required": ["id", "name"], + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" } + } + } + """); + CreateSchemaFile("schemas/monster.schema.json", monsterSchemaContent); + + return new YamlConfigLoader(_rootPath) + .RegisterTable("item", "item", "schemas/item.schema.json", + static config => config.Id) + .RegisterTable("monster", "monster", "schemas/monster.schema.json", keySelector); + } + /// /// 创建测试用配置文件。 /// @@ -3389,13 +3364,13 @@ public class YamlConfigLoaderTests /// 任务结果。 private static async Task WaitForTaskWithinAsync(Task task, TimeSpan timeout) { - var completedTask = await Task.WhenAny(task, Task.Delay(timeout)); + var completedTask = await Task.WhenAny(task, Task.Delay(timeout)).ConfigureAwait(false); if (!ReferenceEquals(completedTask, task)) { Assert.Fail($"Timed out after {timeout} while waiting for file watcher notification."); } - return await task; + return await task.ConfigureAwait(false); } /// diff --git a/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs b/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs index 483617c6..5e9350c6 100644 --- a/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs +++ b/GFramework.Game.Tests/Config/YamlConfigTextValidatorTests.cs @@ -110,7 +110,7 @@ public sealed class YamlConfigTextValidatorTests /// 验证异步入口与同步入口共享相同校验语义。 /// [Test] - public async Task ValidateAsync_Should_Throw_ConfigLoadException_When_Required_Field_Is_Missing() + public void ValidateAsync_Should_Throw_ConfigLoadException_When_Required_Field_Is_Missing() { var schemaPath = CreateSchemaFile( "schemas/monster.schema.json", @@ -125,14 +125,14 @@ public sealed class YamlConfigTextValidatorTests } """); - var exception = Assert.ThrowsAsync(async () => - await YamlConfigTextValidator.ValidateAsync( + var exception = Assert.ThrowsAsync(() => + YamlConfigTextValidator.ValidateAsync( "monster", schemaPath, "monster/generated.yaml", """ id: 1 - """).ConfigureAwait(false)); + """)); Assert.Multiple(() => { diff --git a/GFramework.Game.Tests/Data/PersistenceTests.cs b/GFramework.Game.Tests/Data/PersistenceTests.cs index 4daefddc..4b9f2733 100644 --- a/GFramework.Game.Tests/Data/PersistenceTests.cs +++ b/GFramework.Game.Tests/Data/PersistenceTests.cs @@ -43,7 +43,7 @@ public class PersistenceTests var loaded = await storage.ReadAsync("folder/item").ConfigureAwait(false); Assert.That(loaded.Value, Is.EqualTo(saved.Value)); - Assert.ThrowsAsync(async () => await storage.WriteAsync("../escape", new TestSimpleData()).ConfigureAwait(false)); + Assert.ThrowsAsync(() => storage.WriteAsync("../escape", new TestSimpleData())); } /// @@ -185,7 +185,7 @@ public class PersistenceTests var repository = new SaveRepository(storage, config) .RegisterMigration(new TestSaveMigrationV1ToV2()); - var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => repository.LoadAsync(1)); Assert.That(exception!.Message, Does.Contain("from version 2")); } @@ -218,7 +218,7 @@ public class PersistenceTests var repository = new SaveRepository(storage, config) .RegisterMigration(new TestSaveMigrationV1ToV2ReturningV3()); - var exception = Assert.ThrowsAsync(async () => await repository.LoadAsync(1).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => repository.LoadAsync(1)); var persisted = await storage.ReadAsync("saves/slot_1/save").ConfigureAwait(false); Assert.Multiple(() => @@ -270,7 +270,7 @@ public class PersistenceTests repository.RegisterMigration(new TestSaveMigrationV2ToV3()); continueMigration.Set(); - var exception = Assert.ThrowsAsync(async () => await loadTask.ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => loadTask); var persisted = await storage.ReadAsync("saves/slot_1/save").ConfigureAwait(false); Assert.Multiple(() => @@ -593,7 +593,7 @@ public class PersistenceTests throwingStorage.ThrowOnWrite = true; Assert.ThrowsAsync( - async () => await repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 }).ConfigureAwait(false)); + () => repository.SaveAsync(primaryLocation, new TestSimpleData { Value = 99 })); var cachedAfterFailure = await repository.LoadAsync(primaryLocation).ConfigureAwait(false); Assert.That(cachedAfterFailure.Value, Is.EqualTo(1)); @@ -656,7 +656,7 @@ public class PersistenceTests throwingStorage.ThrowOnWrite = true; Assert.ThrowsAsync( - async () => await repository.DeleteAsync(secondaryLocation).ConfigureAwait(false)); + () => repository.DeleteAsync(secondaryLocation)); Assert.That(await repository.ExistsAsync(secondaryLocation).ConfigureAwait(false), Is.True); diff --git a/GFramework.Game/Routing/RouterBase.cs b/GFramework.Game/Routing/RouterBase.cs index 1f731505..3fe43f54 100644 --- a/GFramework.Game/Routing/RouterBase.cs +++ b/GFramework.Game/Routing/RouterBase.cs @@ -140,7 +140,7 @@ public abstract class RouterBase : AbstractSystem try { Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, routeKey); - var canEnter = await guard.CanEnterAsync(routeKey, context); + var canEnter = await guard.CanEnterAsync(routeKey, context).ConfigureAwait(false); if (!canEnter) { @@ -182,7 +182,7 @@ public abstract class RouterBase : AbstractSystem try { Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, routeKey); - var canLeave = await guard.CanLeaveAsync(routeKey); + var canLeave = await guard.CanLeaveAsync(routeKey).ConfigureAwait(false); if (!canLeave) { @@ -241,4 +241,4 @@ public abstract class RouterBase : AbstractSystem } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Game/Scene/SceneRouterBase.cs b/GFramework.Game/Scene/SceneRouterBase.cs index 812644c3..9c7450ce 100644 --- a/GFramework.Game/Scene/SceneRouterBase.cs +++ b/GFramework.Game/Scene/SceneRouterBase.cs @@ -89,13 +89,9 @@ public abstract class SceneRouterBase var @event = CreateEvent(sceneKey, SceneTransitionType.Replace, param); - await _pipeline.ExecuteAroundAsync(@event, async () => - { - await BeforeChangeAsync(@event); - await ClearInternalAsync(); - await PushInternalAsync(sceneKey, param); - await AfterChangeAsync(@event); - }); + await _pipeline.ExecuteAroundAsync( + @event, + () => ExecuteReplaceCoreAsync(@event, sceneKey, param)).ConfigureAwait(true); } finally { @@ -195,12 +191,9 @@ public abstract class SceneRouterBase var @event = CreateEvent(sceneKey, SceneTransitionType.Push, param); - await _pipeline.ExecuteAroundAsync(@event, async () => - { - await BeforeChangeAsync(@event); - await PushInternalAsync(sceneKey, param); - await AfterChangeAsync(@event); - }); + await _pipeline.ExecuteAroundAsync( + @event, + () => ExecutePushCoreAsync(@event, sceneKey, param)).ConfigureAwait(true); } finally { @@ -276,12 +269,9 @@ public abstract class SceneRouterBase var @event = CreateEvent(null, SceneTransitionType.Pop); - await _pipeline.ExecuteAroundAsync(@event, async () => - { - await BeforeChangeAsync(@event); - await PopInternalAsync(); - await AfterChangeAsync(@event); - }); + await _pipeline.ExecuteAroundAsync( + @event, + () => ExecutePopCoreAsync(@event)).ConfigureAwait(true); } finally { @@ -347,12 +337,9 @@ public abstract class SceneRouterBase var @event = CreateEvent(null, SceneTransitionType.Clear); - await _pipeline.ExecuteAroundAsync(@event, async () => - { - await BeforeChangeAsync(@event); - await ClearInternalAsync(); - await AfterChangeAsync(@event); - }); + await _pipeline.ExecuteAroundAsync( + @event, + () => ExecuteClearCoreAsync(@event)).ConfigureAwait(true); } finally { @@ -370,7 +357,7 @@ public abstract class SceneRouterBase { while (Stack.Count > 0) { - await PopInternalAsync(); + await PopInternalAsync().ConfigureAwait(true); } } @@ -378,6 +365,67 @@ public abstract class SceneRouterBase #region Helper Methods + // Scene 生命周期回调和 pipeline handlers 可能依赖引擎线程,因此这些核心切换顺序统一显式保留上下文。 + + /// + /// 执行 Replace 的核心切换顺序。 + /// + /// 场景转换事件。 + /// 目标场景键名。 + /// 场景进入参数。 + /// 异步任务。 + private async Task ExecuteReplaceCoreAsync( + SceneTransitionEvent @event, + string sceneKey, + ISceneEnterParam? param) + { + await BeforeChangeAsync(@event).ConfigureAwait(true); + await ClearInternalAsync().ConfigureAwait(true); + await PushInternalAsync(sceneKey, param).ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); + } + + /// + /// 执行 Push 的核心切换顺序。 + /// + /// 场景转换事件。 + /// 目标场景键名。 + /// 场景进入参数。 + /// 异步任务。 + private async Task ExecutePushCoreAsync( + SceneTransitionEvent @event, + string sceneKey, + ISceneEnterParam? param) + { + await BeforeChangeAsync(@event).ConfigureAwait(true); + await PushInternalAsync(sceneKey, param).ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); + } + + /// + /// 执行 Pop 的核心切换顺序。 + /// + /// 场景转换事件。 + /// 异步任务。 + private async Task ExecutePopCoreAsync(SceneTransitionEvent @event) + { + await BeforeChangeAsync(@event).ConfigureAwait(true); + await PopInternalAsync().ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); + } + + /// + /// 执行 Clear 的核心切换顺序。 + /// + /// 场景转换事件。 + /// 异步任务。 + private async Task ExecuteClearCoreAsync(SceneTransitionEvent @event) + { + await BeforeChangeAsync(@event).ConfigureAwait(true); + await ClearInternalAsync().ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); + } + /// /// 创建场景转换事件对象。 /// @@ -407,7 +455,7 @@ public abstract class SceneRouterBase private async Task BeforeChangeAsync(SceneTransitionEvent @event) { Log.Debug("BeforeChange phases started: {0}", @event.TransitionType); - await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange); + await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.BeforeChange).ConfigureAwait(true); Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType); } @@ -418,9 +466,9 @@ public abstract class SceneRouterBase private async Task AfterChangeAsync(SceneTransitionEvent @event) { Log.Debug("AfterChange phases started: {0}", @event.TransitionType); - await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange); + await _pipeline.ExecuteAsync(@event, SceneTransitionPhases.AfterChange).ConfigureAwait(true); Log.Debug("AfterChange phases completed: {0}", @event.TransitionType); } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Game/Scene/SceneTransitionPipeline.cs b/GFramework.Game/Scene/SceneTransitionPipeline.cs index a8e93e82..2a2f0afd 100644 --- a/GFramework.Game/Scene/SceneTransitionPipeline.cs +++ b/GFramework.Game/Scene/SceneTransitionPipeline.cs @@ -148,7 +148,7 @@ public class SceneTransitionPipeline foreach (var handler in sortedHandlers) { var options = _options[handler]; - await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken); + await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken).ConfigureAwait(false); } Log.Debug("Pipeline execution completed for phases: {0}", phases); @@ -173,7 +173,7 @@ public class SceneTransitionPipeline if (handlers.Count == 0) { - await coreAction(); + await coreAction().ConfigureAwait(false); return; } @@ -191,11 +191,11 @@ public class SceneTransitionPipeline var options = _aroundOptions[handler]; var next = pipeline; - pipeline = async () => await ExecuteSingleAroundHandlerAsync( - handler, options, @event, next, cancellationToken); + pipeline = () => ExecuteSingleAroundHandlerAsync( + handler, options, @event, next, cancellationToken); } - await pipeline(); + await pipeline().ConfigureAwait(false); } private List FilterAndSortHandlers( @@ -283,7 +283,7 @@ public class SceneTransitionPipeline ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token) : null; - await handler.HandleAsync(@event, next, linkedCts?.Token ?? cancellationToken); + await handler.HandleAsync(@event, next, linkedCts?.Token ?? cancellationToken).ConfigureAwait(false); Log.Debug("Around handler completed: {0}", handler.GetType().Name); } @@ -296,4 +296,4 @@ public class SceneTransitionPipeline throw; } } -} \ No newline at end of file +} diff --git a/GFramework.Game/Setting/SettingsSystem.cs b/GFramework.Game/Setting/SettingsSystem.cs index 6b5b2aed..b73580a7 100644 --- a/GFramework.Game/Setting/SettingsSystem.cs +++ b/GFramework.Game/Setting/SettingsSystem.cs @@ -19,7 +19,7 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem public async Task ApplyAll() { // 遍历所有设置应用器并尝试应用 - foreach (var applicator in _model.AllApplicators()) await TryApplyAsync(applicator); + foreach (var applicator in _model.AllApplicators()) await TryApplyAsync(applicator).ConfigureAwait(false); } /// @@ -41,7 +41,7 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem /// 完成的任务 public async Task SaveAll() { - await _model.SaveAllAsync(); + await _model.SaveAllAsync().ConfigureAwait(false); } /// @@ -51,7 +51,7 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem public async Task ResetAll() { _model.ResetAll(); - await ApplyAll(); + await ApplyAll().ConfigureAwait(false); } /// @@ -62,7 +62,7 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem public async Task Reset() where T : class, ISettingsData, IResetApplyAbleSettings, new() { _model.Reset(); - await Apply(); + await Apply().ConfigureAwait(false); } @@ -87,7 +87,7 @@ public class SettingsSystem : AbstractSystem, ISettingsSystem try { - await applyAbleSettings.ApplyAsync(); + await applyAbleSettings.ApplyAsync().ConfigureAwait(false); // 发送设置应用成功事件 this.SendEvent(new SettingsAppliedEvent(section, true)); } diff --git a/GFramework.Game/Storage/FileStorage.cs b/GFramework.Game/Storage/FileStorage.cs index 383c0298..a1c3091e 100644 --- a/GFramework.Game/Storage/FileStorage.cs +++ b/GFramework.Game/Storage/FileStorage.cs @@ -143,11 +143,11 @@ public sealed class FileStorage : IFileStorage, IDisposable ObjectDisposedException.ThrowIf(_disposed, this); var path = ToPath(key); - await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false)) - { - if (File.Exists(path)) - File.Delete(path); - } + var pathLock = await _lockManager.AcquireLockAsync(path).ConfigureAwait(false); + await using var configuredPathLock = pathLock.ConfigureAwait(false); + + if (File.Exists(path)) + File.Delete(path); } #endregion @@ -178,10 +178,10 @@ public sealed class FileStorage : IFileStorage, IDisposable ObjectDisposedException.ThrowIf(_disposed, this); var path = ToPath(key); - await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false)) - { - return File.Exists(path); - } + var pathLock = await _lockManager.AcquireLockAsync(path).ConfigureAwait(false); + await using var configuredPathLock = pathLock.ConfigureAwait(false); + + return File.Exists(path); } #endregion @@ -236,23 +236,24 @@ public sealed class FileStorage : IFileStorage, IDisposable ObjectDisposedException.ThrowIf(_disposed, this); var path = ToPath(key); - await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false)) - { - if (!File.Exists(path)) - throw new FileNotFoundException($"Storage key not found: {key}", path); + var pathLock = await _lockManager.AcquireLockAsync(path).ConfigureAwait(false); + await using var configuredPathLock = pathLock.ConfigureAwait(false); - await using var fs = new FileStream( - path, - FileMode.Open, - FileAccess.Read, - FileShare.Read, - _bufferSize, - useAsync: true); + if (!File.Exists(path)) + throw new FileNotFoundException($"Storage key not found: {key}", path); - using var sr = new StreamReader(fs, Encoding.UTF8); - var content = await sr.ReadToEndAsync().ConfigureAwait(false); - return _serializer.Deserialize(content); - } + var fs = new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.Read, + _bufferSize, + useAsync: true); + await using var configuredFileStream = fs.ConfigureAwait(false); + + using var sr = new StreamReader(fs, Encoding.UTF8, true, -1, leaveOpen: true); + var content = await sr.ReadToEndAsync().ConfigureAwait(false); + return _serializer.Deserialize(content); } #endregion @@ -354,38 +355,42 @@ public sealed class FileStorage : IFileStorage, IDisposable var path = ToPath(key); var tempPath = path + ".tmp"; - await using (await _lockManager.AcquireLockAsync(path).ConfigureAwait(false)) + var pathLock = await _lockManager.AcquireLockAsync(path).ConfigureAwait(false); + await using var configuredPathLock = pathLock.ConfigureAwait(false); + + try { - try - { - var content = _serializer.Serialize(value); + var content = _serializer.Serialize(value); - // 先写入临时文件 - await using (var fs = new FileStream( - tempPath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - _bufferSize, - useAsync: true)) - { - await using var sw = new StreamWriter(fs, Encoding.UTF8); - await sw.WriteAsync(content).ConfigureAwait(false); - await sw.FlushAsync().ConfigureAwait(false); - } - - // 原子性替换目标文件 - File.Move(tempPath, path, overwrite: true); - } - catch + // 先写入临时文件 { - // 清理临时文件 - if (File.Exists(tempPath)) - File.Delete(tempPath); - throw; + var fs = new FileStream( + tempPath, + FileMode.Create, + FileAccess.Write, + FileShare.None, + _bufferSize, + useAsync: true); + await using var configuredFileStream = fs.ConfigureAwait(false); + + var sw = new StreamWriter(fs, Encoding.UTF8, leaveOpen: true); + await using var configuredStreamWriter = sw.ConfigureAwait(false); + + await sw.WriteAsync(content).ConfigureAwait(false); + await sw.FlushAsync().ConfigureAwait(false); } + + // 原子性替换目标文件 + File.Move(tempPath, path, overwrite: true); + } + catch + { + // 清理临时文件 + if (File.Exists(tempPath)) + File.Delete(tempPath); + throw; } } #endregion -} \ No newline at end of file +} diff --git a/GFramework.Game/Storage/ScopedStorage.cs b/GFramework.Game/Storage/ScopedStorage.cs index b0714783..c3850a79 100644 --- a/GFramework.Game/Storage/ScopedStorage.cs +++ b/GFramework.Game/Storage/ScopedStorage.cs @@ -100,9 +100,9 @@ public sealed class ScopedStorage(IStorage inner, string prefix) : IScopedStorag /// /// 要删除的键 /// 异步操作任务 - public async Task DeleteAsync(string key) + public Task DeleteAsync(string key) { - await inner.DeleteAsync(Key(key)); + return inner.DeleteAsync(Key(key)); } /// @@ -166,4 +166,4 @@ public sealed class ScopedStorage(IStorage inner, string prefix) : IScopedStorag { return new ScopedStorage(inner, Key(scope)); } -} \ No newline at end of file +} diff --git a/GFramework.Game/UI/UiRouterBase.cs b/GFramework.Game/UI/UiRouterBase.cs index ede1b51b..1da37e6c 100644 --- a/GFramework.Game/UI/UiRouterBase.cs +++ b/GFramework.Game/UI/UiRouterBase.cs @@ -101,10 +101,10 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); - await DoPushPageInternalAsync(uiKey, param, policy); - await AfterChangeAsync(@event); - }); + await BeforeChangeAsync(@event).ConfigureAwait(true); + await DoPushPageInternalAsync(uiKey, param, policy).ConfigureAwait(true); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -129,10 +129,10 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); DoPushPageInternal(page, param, policy); - await AfterChangeAsync(@event); - }); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -149,7 +149,7 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); DoPopInternal(policy); - await AfterChangeAsync(@event); - }); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -182,15 +182,15 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); DoClearInternal(popPolicy); var page = _factory.Create(uiKey); Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); - await AfterChangeAsync(@event); - }); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -211,12 +211,12 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); DoClearInternal(popPolicy); Log.Debug("Use existing UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); - await AfterChangeAsync(@event); - }); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -229,10 +229,10 @@ public abstract class UiRouterBase : RouterBase { - await BeforeChangeAsync(@event); + await BeforeChangeAsync(@event).ConfigureAwait(true); DoClearInternal(UiPopPolicy.Destroy); - await AfterChangeAsync(@event); - }); + await AfterChangeAsync(@event).ConfigureAwait(true); + }).ConfigureAwait(true); } /// @@ -650,7 +650,7 @@ public abstract class UiRouterBase : RouterBase过渡策略 private async Task DoPushPageInternalAsync(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy) { - if (!await ExecuteEnterGuardsAsync(uiKey, param)) + if (!await ExecuteEnterGuardsAsync(uiKey, param).ConfigureAwait(true)) { Log.Warn("Push blocked by guard: {0}", uiKey); return; diff --git a/GFramework.Game/UI/UiTransitionPipeline.cs b/GFramework.Game/UI/UiTransitionPipeline.cs index 4ca005cf..49101d2f 100644 --- a/GFramework.Game/UI/UiTransitionPipeline.cs +++ b/GFramework.Game/UI/UiTransitionPipeline.cs @@ -133,7 +133,7 @@ public class UiTransitionPipeline foreach (var handler in sortedHandlers) { var options = _options[handler]; - await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken); + await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken).ConfigureAwait(false); } Log.Debug("Pipeline execution completed for phases: {0}", phases); @@ -158,7 +158,7 @@ public class UiTransitionPipeline if (handlers.Count == 0) { - await coreAction(); + await coreAction().ConfigureAwait(false); return; } @@ -176,11 +176,11 @@ public class UiTransitionPipeline var options = _aroundOptions[handler]; var next = pipeline; - pipeline = async () => await ExecuteSingleAroundHandlerAsync( + pipeline = () => ExecuteSingleAroundHandlerAsync( handler, options, @event, next, cancellationToken); } - await pipeline(); + await pipeline().ConfigureAwait(false); } private List FilterAndSortHandlers( @@ -268,7 +268,7 @@ public class UiTransitionPipeline ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token) : null; - await handler.HandleAsync(@event, next, linkedCts?.Token ?? cancellationToken); + await handler.HandleAsync(@event, next, linkedCts?.Token ?? cancellationToken).ConfigureAwait(false); Log.Debug("Around handler completed: {0}", handler.GetType().Name); } @@ -281,4 +281,4 @@ public class UiTransitionPipeline throw; } } -} \ No newline at end of file +} diff --git a/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs b/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs index 83dc2458..75644abc 100644 --- a/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs +++ b/GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs @@ -19,8 +19,8 @@ public sealed class AbstractArchitectureModuleInstallationTests var architecture = new TestArchitecture(); var module = new RecordingGodotModule(); - var exception = Assert.ThrowsAsync(async () => - await architecture.InstallGodotModuleForTestAsync(module).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(() => + architecture.InstallGodotModuleForTestAsync(module)); Assert.Multiple(() => { diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index cfeb8489..7ea2c6e7 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -6,41 +6,85 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-058` -- 当前阶段:`Phase 58` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-064` +- 当前阶段:`Phase 64` - 当前焦点: - - `2026-04-24` 使用 `$gframework-pr-review` 复核当前分支 PR #286 的 latest-head review threads、MegaLinter 与测试状态 - - 已确认最新 head 上唯一未解决的实质代码线程指向 `GFramework.Godot/Scene/SceneBehaviorBase.cs` 中 `OnPauseAsync` 的缩进异常,并顺带对齐 `OnResumeAsync`、`OnUnloadAsync` - - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` 通过,结果为 `565 Warning(s)`、`0 Error(s)`;当前跟进只处理 PR review 指向的格式问题,不扩散到既有 warning 基线 - - `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` 已通过,当前文件不再残留格式差异 + - `2026-04-25` 当前 turn 先执行 `$gframework-pr-review`,复核 PR #288 的 latest-head unresolved 线程与折叠评论 + - 已收敛一批经本地复核后仍成立的 review 建议,包括 `ThrowIfNull` 回退、测试桩 XML 注释修正、`FileStorage` 资源所有权、`SceneRouterBase` 线程亲和语义与若干测试噪音 + - 已确认用户在 WSL 下直接执行的标准 `dotnet build -c Release` 路径可用;前一轮失败主要来自主线程附加的 workaround 参数而非仓库本身不可构建 + - 基线 `origin/main` 仍为 `9964962`(`2026-04-24T23:05:53+08:00`) + - 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行,已触达本轮 `75 files` 阈值 + - `RP-061` 之后已接受 2 个批次提交:`03c73a8`、`9ce1fa6` + - 当前默认恢复入口不再继续扩写集;若要继续 analyzer reduction,优先重新抓取 PR #288 的 unresolved 线程并按最新 head 再做一轮收口 ## 当前活跃事实 -- 之前记录的 plain `dotnet build` `0 Warning(s)` 属于增量构建假阴性,不能再作为 warning 检查真值 -- 仓库根目录 `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,项目级 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 也未能稳定提供 clean 基线 -- 当前整仓最近一次直接观测值仍是 `dotnet build GFramework.sln -c Release` 的 `116 warning(s)` -- `RP-056` 已验证 `GeneratedConfigConsumerIntegrationTests.cs` 不再出现在项目 build warning 输出中 -- `RP-057` 已验证 `PersistenceTests.cs` 不再出现在 `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` 的 warning 输出中 -- 本轮已验证 `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"`,结果为 `Passed: 2` -- `GFramework.Game.Tests` 当前剩余热点已经几乎完全集中到 `YamlConfigLoaderTests.cs` 这一高上下文文件 -- PR #286 当前标题为 `Fix/analyzer warning reduction batch`;最新抓取时间点的 PR 状态仍为 `OPEN` -- 最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698`,CodeRabbit 于 `2026-04-24T12:44:12Z` 给出 `CHANGES_REQUESTED` -- latest-head review threads 中只有 `1` 个未解决线程,内容是 `SceneBehaviorBase.OnPauseAsync` 的缩进不一致;本地源码已修复并扩展到同段的 `OnResumeAsync` / `OnUnloadAsync` -- MegaLinter 的 `dotnet-format` 详细问题与上述格式异常一致;本地 `dotnet format --verify-no-changes` 已通过 -- PR 上其余 nitpick 仅为可选建议或已明确留待后续批次处理,当前没有额外需要立即修复的 latest-head 代码线程 +- 当前 `origin/main` 基线提交为 `9964962`(`2026-04-24T23:05:53+08:00`)。 +- 本轮 `Core.Tests` 低风险机械型清理已落地到: + - `ArchitectureAdditionalCqrsHandlersTests.cs` + - `RegistryInitializationHookBaseTests.cs` + - `CommandCoroutineExtensionsTests.cs` + - `TaskCoroutineExtensionsTests.cs` + - `WaitForTaskTTests.cs` + - `AsyncExtensionsTests.cs` + - `LogContextTests.cs` + - `PauseStackManagerTests.cs` +- 本 turn 结合 PR #288 latest-head review 额外收敛了以下仍然成立的问题: + - `AsyncExtensionsTests.cs`:修复 `WithTimeoutAsync` 无返回值测试中错误返回 `ConfiguredTaskAwaitable` 导致的 `CS0029` / `CS1662` + - `ContextAwareCommandExtensions.cs` + - `ContextAwareQueryExtensions.cs` + - `ContextAwareEventExtensions.cs` + - `AsyncExtensions.cs` + - `AsyncKeyLockManagerTests.cs`:去掉两处不会产生额外价值的 `Assert.DoesNotThrowAsync(() => Task.WhenAll(...))` 包装,并把取消断言改为直接消费 `ValueTask.AsTask()` + - `AsyncArchitectureTests.cs` + - `ArchitectureLifecycleBehaviorTests.cs` + - `StateMachineSystemTests.cs` + - `RegistryInitializationHookBaseTests.cs` + - `NumericExtensions.cs` + - `StringExtensions.cs` + - `StoreBuilder.cs` + - `StoreSelection.cs` + - `ArchitectureServicesTests.cs` + - `GameContextTests.cs` + - `RollingFileAppenderTests.cs` + - `TaskCoroutineExtensionsTests.cs` + - `WaitForTaskTests.cs` + - `ScopedStorage.cs` + - `FileStorage.cs` + - `SceneRouterBase.cs` +- 当前 PR review 观察: + - PR:`#288` + - latest reviewed commit:`70c42b579f70c90ab5461a02e611c0fbd8d8a6f2` + - 抓取时 `coderabbitai[bot]` 有 `6` 个 open threads,`greptile-apps[bot]` 有 `2` 个 open threads + - `Actionable comments posted: 7` 与 `outside diff + nitpick = 19` 并不等于必须全收;本 turn 仅接受经本地复核后仍成立且不与仓库约束冲突的建议 +- 本轮 `Core` runtime 低风险机械型清理已落地到: + - `AsyncExtensions.cs` + - `CollectionExtensions.cs` + - `ContextAwareCommandExtensions.cs` + - `ContextAwareEnvironmentExtensions.cs` + - `ContextAwareEventExtensions.cs` + - `ContextAwareQueryExtensions.cs` + - `ContextAwareServiceExtensions.cs` + - `GuardExtensions.cs` + - `NumericExtensions.cs` + - `StoreEventBusExtensions.cs` + - `StringExtensions.cs` + - `StoreBuilder.cs` + - `StoreSelection.cs` +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` 当前结果为 `0 Warning(s)`、`0 Error(s)`,可作为本轮 runtime 变更的最终最小 Release build 验证。 +- `GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental` 在 `03c73a8` 提交前的最近一次可信主线程结果为 `198 Warning(s)`、`0 Error(s)`;该观测值覆盖了 `ArchitectureContextTests`、`ArchitectureServicesTests`、`GameContextTests`、`ResultTests`、`AsyncTestModel`、`AsyncTestSystem` 与 `ContextAwareEnvironmentExtensionsTests` 的 7 文件批次。 +- 当前累计 branch diff 相对 `origin/main` 为 `75` 个文件、`2098` 行;本轮主停止条件已经达到。 ## 当前风险 -- 如果后续继续依赖增量 `dotnet build`,容易再次把 warning 数量误判为 0 - - 缓解措施:每轮 warning 检查前先执行 `dotnet clean`,再执行目标 `dotnet build` -- 仓库根目录与 `GFramework.Game.Tests` 的 `dotnet clean` 目前都无法给出新的 clean 基线 - - 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值 -- 当前 worktree 仍存在未跟踪的 `.codex` 目录 - - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交 -- 下一轮若继续深入 `GFramework.Game.Tests`,很可能需要进入 `YamlConfigLoaderTests.cs` 这种高上下文大文件 - - 缓解措施:把它单独作为一个明确的新批次处理,不与其它 warning family 混批 -- PR 标题检查当前仍显示 `Inconclusive` - - 缓解措施:如需让该检查转绿,需要单独更新 GitHub PR 标题;这不属于本地代码修改范围 +- `dotnet clean GFramework.sln -c Release` 与 `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` 仍无法稳定提供新的 clean 基线。 + - 缓解措施:后续若继续整仓 warning reduction,需要单独定位 clean 失败原因,或明确继续沿用 direct build 观测值作为临时真值。 +- 当前 worktree 仍存在未跟踪的 `.codex` 目录。 + - 缓解措施:提交当前批次时只暂存 analyzer-warning-reduction 相关源码与 `ai-plan` 文件,避免把工作目录辅助文件混入提交。 +- 将分支继续推过 `75 files` 会明显降低本轮 reviewability。 + - 缓解措施:当前恢复点默认停止;如需继续,建议在新 turn 明确新的文件阈值或先 rebase / refresh baseline。 +- `GFramework.Core`、`GFramework.Game`、`GFramework.Core.Tests` 当前都仍存在模块级历史 warning 基线。 + - 缓解措施:本 turn 已确保本次 touched files 不再引入新的编译错误,并消化了当前 PR review 中仍成立的高信号问题;若要继续 warning reduction,应开新批次按模块系统化收敛。 ## 活跃文档 @@ -56,31 +100,35 @@ ## 验证说明 -- `dotnet clean GFramework.sln -c Release` - - 结果:失败;停在 solution `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)`,未输出更具体的 error 文本 -- `dotnet build GFramework.sln -c Release` - - 结果:成功;`116 Warning(s)`、`0 Error(s)` -- `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 结果:失败;clean 阶段在 MSBuild 清理路径结束前返回 `0 Warning(s)`、`0 Error(s)`,未输出额外错误文本 -- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - `RP-055` 收尾结果:成功;`63 Warning(s)`、`0 Error(s)` - - `RP-056` 当前结果:成功;`59 Warning(s)`、`0 Error(s)` -- `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` - - `RP-057` 热点重排前:成功;`253 Warning(s)`、`0 Error(s)` - - `RP-057` 当前结果:成功;`249 Warning(s)`、`0 Error(s)` -- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` - - 结果:成功;`Passed: 19`、`Failed: 0` -- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"` - - 结果:成功;`Passed: 4`、`Failed: 0` -- `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"` - - 结果:成功;`Passed: 2`、`Failed: 0` -- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` - - 结果:成功;`565 Warning(s)`、`0 Error(s)` -- `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` - - 首次运行:失败;restore 阶段异常退出,未进入格式验证 - - 第二次运行(同命令追加 sandbox 提权):成功;workspace 仅提示加载 warning,无格式差异 +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 历史结果:成功;`0 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag` + - 历史结果:失败;`MSB4276`,默认 SDK resolver 无法解析 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator`,属于当前 WSL / dotnet 10 环境阻塞 +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,`ResolvePackageAssets` 命中失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages` +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,原因同上 +- `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,原因同上 +- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` + - 结果:成功;定位到 PR `#288`,提取 latest-head unresolved AI review threads、MegaLinter 与 Docstring Coverage 信号 +- `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal` + - 结果:成功;已刷新 WSL 原生 restore 元数据,清除先前的 stale fallback package folder 阻塞 +- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:成功;`28 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` + - 结果:成功;`329 Warning(s)`、`0 Error(s)` +- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:成功;`137 Warning(s)`、`0 Error(s)` +- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,因此不能用 `net8.0` 旁路验证 `Core.Tests` +- `git diff --name-only origin/main...HEAD | wc -l` + - 当前结果:`75` +- `git diff --numstat origin/main...HEAD` + - 当前结果:累计 `1083` added、`1015` deleted,即 `2098` changed lines ## 下一步建议 -1. 提交 `SceneBehaviorBase.cs` 与 `RP-058` tracking/trace 更新,清掉 PR #286 当前 latest-head 上的格式类 review thread -2. 若继续 warning reduction 主线,应回到 `GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs`,把它作为独立高上下文批次处理 +1. 当前 turn 已按标准 WSL `dotnet build` 路径完成 `Core` / `Game` / `Core.Tests` Release build 验证;后续若继续 PR #288 收尾,优先重新抓取 unresolved threads,确认哪些线程已可直接 resolve。 +2. 若后续要继续 `Core` / `Core.Tests` / `Game` warning reduction,应以当前标准 build 输出为新真值,而不是继续沿用上一轮带 workaround 参数的失败命令。 +3. 若要开启下一轮批处理,优先选择新的 stop-condition(例如新的 file 阈值、warning 目标或限定到单模块)后再继续。 diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index bbed0ac9..f6394342 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,251 +1,120 @@ # Analyzer Warning Reduction 追踪 -# Analyzer Warning Reduction 追踪 +## 2026-04-25 — RP-064 -## 2026-04-24 — RP-058 - -### 阶段:PR #286 latest-head review 格式跟进 +### 阶段:按标准 WSL build 路径复核 PR #288 建议并完成本轮收口 - 触发背景: - - 用户要求执行 `$gframework-pr-review`,需要以当前分支 PR 页面而不是本地记忆为准,重新核对 CodeRabbit、MegaLinter 和测试状态 - - 抓取脚本当前解析到的 PR 是 `#286`,最新 reviewed commit 为 `2b707343577193fc9904517e6078149653e95698` - - 最新 head 上真正未解决的代码线程只剩 `GFramework.Godot/Scene/SceneBehaviorBase.cs:148` 的缩进问题;其余 nitpick 为可选建议或已留待后续批次 + - 用户指出“在 WSL 里直接执行 `dotnet build` 可以成功”,要求主线程按普通路径重新验证,而不是继续使用带 `MSBuildEnableWorkloadResolver=false`、`--no-restore`、手工 `TargetFramework` 的 workaround 命令 + - 当前任务仍属于 PR #288 review follow-up,因此本轮重点改为“区分哪些 AI 建议值得采纳”以及“用真实 WSL build 结果验证” - 主线程实施: - - 运行 `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json`,确认 PR `OPEN`、测试 `2156/2156` 通过、MegaLinter 仅剩 `dotnet-format` 警告 - - 复核 `SceneBehaviorBase.cs` 后确认 `OnPauseAsync` 的方法签名与方法体缩进异常仍存在于本地源码;同段的 `OnResumeAsync`、`OnUnloadAsync` 也有同类偏差 - - 在不改变行为的前提下统一修正三个方法的缩进,保持现有 XML 注释、`ConfigureAwait(true)` 语义与 Godot 主线程说明不变 - - 更新 active tracking / trace,记录当前 PR review follow-up 已完成,本地剩余外部信号只剩 PR 标题检查 -- 验证里程碑: - - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` - - 结果:成功;`565 Warning(s)`、`0 Error(s)` - - 结论:当前格式修复未引入编译错误;模块既有 warning 基线仍存在,但不属于本次 PR review 跟进范围 - - `dotnet format GFramework.Godot/GFramework.Godot.csproj --verify-no-changes --no-restore --include GFramework.Godot/Scene/SceneBehaviorBase.cs` - - 首次运行:失败;sandbox 环境下在 build host / pipe 建立阶段报错,未进入真实格式比较 - - 提权复验:成功;仅提示 workspace load warning,无格式差异 -- 当前结论: - - PR #286 当前 latest-head 上唯一未解决的实质代码 review thread 已在本地修复 - - MegaLinter 暴露的 `dotnet-format` 问题已被本地 `verify-no-changes` 复验覆盖 - - `Title check: Inconclusive` 仍然存在,但属于 GitHub PR 标题元数据问题,不能通过本地代码提交直接消除 - -## 2026-04-24 — RP-057 - -### 阶段:清理 `PersistenceTests.cs` 残余 `MA0004` - -- 触发背景: - - `RP-056` 提交后重新做非增量热点排序时,`GFramework.Game.Tests` 的剩余测试项目 warning 已明显收敛,只剩 `PersistenceTests.cs` 少量 `MA0004` 与 `YamlConfigLoaderTests.cs` 大量 warning - - 为避免在同一轮直接进入 `YamlConfigLoaderTests.cs` 的大文件高上下文批次,先吃掉 `PersistenceTests.cs` 这个独立小切片 -- 主线程实施: - - 在 `PersistenceTests.cs` 中为统一设置仓库失败缓存一致性相关测试补齐剩余 `.ConfigureAwait(false)` - - 覆盖保存失败与删除失败两个测试场景中的缓存读取、存在性检查、后续保存和最终验证读取 - - 更新 active tracking / trace,明确下一批若继续推进应单独进入 `YamlConfigLoaderTests.cs` -- 验证里程碑: - - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --no-incremental` - - 热点重排前:成功;`253 Warning(s)`、`0 Error(s)` - - 修复后:成功;`249 Warning(s)`、`0 Error(s)` - - 结论:`PersistenceTests.cs` 不再出现在 warning 输出中 - - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~UnifiedSettingsDataRepository_SaveAsync_When_Persist_Fails_Should_Keep_Cache_Consistent|FullyQualifiedName~UnifiedSettingsDataRepository_DeleteAsync_When_Persist_Fails_Should_Keep_Cache_Consistent"` - - 结果:成功;`Passed: 2`、`Failed: 0` -- 当前结论: - - `PersistenceTests.cs` 的残余 warning 已清零,`GFramework.Game.Tests` 剩余热点几乎全部压缩到了 `YamlConfigLoaderTests.cs` - - 当前工作树投影下,分支体积为 `27` 个文件、`991` 行,仍低于 `$gframework-batch-boot 75` - - 按 batch skill 的低风险边界,这一轮应在提交后收口;下一轮再把 `YamlConfigLoaderTests.cs` 作为单独批次处理 - -## 2026-04-24 — RP-056 - -### 阶段:修复 `GeneratedConfigConsumerIntegrationTests` 编译错误并清零该文件 warning - -- 触发背景: - - `RP-055` 继续推进时,`GeneratedConfigConsumerIntegrationTests.cs` 在 raw string `invalidYaml` 段落附近出现 `CS8999`,导致 `GFramework.Game.Tests` 暂时无法编译 - - 该文件同时仍是项目内少数残留 warning 热点之一,因此适合作为同一批次中的单文件收尾 -- 主线程实施: - - 修复 `GeneratedConfigConsumerIntegrationTests.cs` 中损坏的 `CreateMonsterFiles` raw string 与方法边界,恢复文件可编译状态 - - 保留并整理上一轮已开始的 `.ConfigureAwait(false)` 与断言 helper 抽取 - - 继续将 `AssertGeneratedBindingsLoadResults` 再拆分为 catalog / monster / item 三个辅助方法,清除该文件剩余 `MA0051` - - 更新 active tracking / trace,沿用 `merge-base(origin/main, HEAD)` 作为 `$gframework-batch-boot 75` 的唯一 stop-condition 口径 -- 验证里程碑: - - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 结果:成功;`59 Warning(s)`、`0 Error(s)` - - 结论:`GeneratedConfigConsumerIntegrationTests.cs` 不再出现在 warning 输出中 - - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~GeneratedConfigConsumerIntegrationTests"` - - 结果:成功;`Passed: 4`、`Failed: 0` -- 当前结论: - - `GFramework.Game.Tests` 已从 `RP-055` 收尾时的 `63 warning(s)` 进一步收敛到 `59 warning(s)` - - 当前工作树投影下,分支体积为 `27` 个文件、`943` 行,仍低于 `$gframework-batch-boot 75` - - 后续若继续自动推进,最自然的下一批将进入 `YamlConfigLoaderTests.cs` 这类高上下文大文件 - -## 2026-04-24 — RP-055 - -### 阶段:修正 stop-condition 口径并继续 `GFramework.Game.Tests` 小热点 - -- 触发背景: - - `RP-054` 之后复核 batch stop-condition 时,发现之前一度把工作树 diff 错当成了 skill 要求的 branch diff - - 按正确口径 `merge-base(origin/main, HEAD)` 计算,`RP-054` 提交后的真实分支体积是 `23` 个文件、`603` 行,因此仍可继续下一批 - - 当前剩余 warning 里,`ArchitectureConfigIntegrationTests`、`GameConfigBootstrapTests`、`JsonSerializerTests` 属于独立且低风险的小切片 -- 主线程实施: - - 在 `ArchitectureConfigIntegrationTests.cs` 中补齐异步架构初始化 / 销毁和异常断言的 `.ConfigureAwait(false)` - - 在 `GameConfigBootstrapTests.cs` 中补齐启动流程、并发初始化断言与 `WaitForTaskWithinAsync` 的 `.ConfigureAwait(false)` - - 在 `JsonSerializerTests.cs` 中将坐标解析改为 `CultureInfo.InvariantCulture` - - 顺手清理 `YamlConfigLoaderAllOfTests.cs` 与 `PersistenceTests.cs` 中上一批遗漏的字段态状态检查和异步等待 warning - - 纠正 active tracking:明确 stop-condition 必须使用 `origin/main...HEAD` 的 merge-base 分支 diff,而不是工作树 diff -- 验证里程碑: - - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 并行误用 build/test 时:出现 `MSB3026` / `CS2012` 文件占用噪声,不计入代码结论 - - 串行复验:成功;`63 Warning(s)`、`0 Error(s)` - - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureConfigIntegrationTests|FullyQualifiedName~GameConfigBootstrapTests|FullyQualifiedName~JsonSerializerTests"` - - 结果:成功;`Passed: 19`、`Failed: 0` -- 当前结论: - - `GFramework.Game.Tests` 已从上一批收尾时的 `71 warning(s)` 进一步降到 `63 warning(s)` - - 这次提交后的分支体积投影为 `26` 个文件、`691` 行,仍低于 `$gframework-batch-boot 75` - - 剩余热点越来越集中到 `YamlConfigLoaderTests.cs` 与 `GeneratedConfigConsumerIntegrationTests.cs`,后续继续时应把它们视为高上下文批次 - -## 2026-04-24 — RP-054 - -### 阶段:`GFramework.Game.Tests` 低风险测试 warning 批次(触发文件数停止阈值) - -- 触发背景: - - 用户要求“直接进入下一批”,继续沿 `$gframework-batch-boot 75` 自动推进 warning reduction - - 以 `origin/main` 为基线时,上一批提交后分支累计 diff 仍只有 `8` 个文件,足够再落一个独立批次 - - 重新执行 `dotnet clean GFramework.sln -c Release` 仍停在 `ValidateSolutionConfiguration`,因此继续以直接 `dotnet build GFramework.sln -c Release` 的输出挑选低风险热点 -- 主线程实施: - - 从整仓 `Release build` 的 `116 warning(s)` 入口观测值中,选择 `GFramework.Game.Tests` 的小型测试文件和 `PersistenceTestUtilities.cs` 作为当前批次,刻意避开 `YamlConfigLoaderTests.cs` 这类高上下文大文件 - - 在 `YamlConfigLoaderIfThenElseTests.cs`、`YamlConfigLoaderDependentSchemasTests.cs`、`YamlConfigLoaderDependentRequiredTests.cs`、`YamlConfigLoaderNegationTests.cs`、`YamlConfigLoaderAllOfTests.cs`、`YamlConfigLoaderEnumTests.cs`、`YamlConfigTextValidatorTests.cs`、`PersistenceTests.cs` 中补齐 `.ConfigureAwait(false)`,并把字段态 `_rootPath` 的 `ThrowIfNull` 改为显式 `InvalidOperationException` - - 将 `PersistenceTestUtilities.cs` 拆分为 `TestDataLocation.cs`、`TestSaveData.cs`、`TestVersionedSaveData.cs`、`TestSimpleData.cs`、`TestNamedData.cs`,消除 `MA0048` 并对齐仓库的一文件一主类型风格 - - 在 `YamlConfigSchemaValidatorTests.cs` 中把字段态 `_rootPath` 的校验改成显式状态异常,避免继续触发 `MA0015` -- 验证里程碑: - - `dotnet clean GFramework.sln -c Release` - - 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.sln -c Release` - - 结果:成功;`116 Warning(s)`、`0 Error(s)` - - `dotnet clean GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 结果:失败;clean 阶段提前结束,`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release` - - 第一轮批次后:成功;`80 Warning(s)`、`0 Error(s)` - - 收尾修正后:成功;`71 Warning(s)`、`0 Error(s)` - - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderIfThenElseTests|FullyQualifiedName~YamlConfigLoaderDependentSchemasTests|FullyQualifiedName~YamlConfigLoaderDependentRequiredTests|FullyQualifiedName~YamlConfigLoaderNegationTests|FullyQualifiedName~YamlConfigLoaderAllOfTests|FullyQualifiedName~YamlConfigLoaderEnumTests|FullyQualifiedName~YamlConfigTextValidatorTests|FullyQualifiedName~YamlConfigSchemaValidatorTests|FullyQualifiedName~PersistenceTests"` - - 结果:成功;`Passed: 63`、`Failed: 0` -- 当前结论: - - `GFramework.Game.Tests` 本轮入口热点已从 `116 warning(s)` 收敛到 `71 warning(s)`,且本轮 touched files 不再出现在 warning 输出中 - - 当前工作树相对 `origin/main` 的累计 diff 已达到 `76` 个文件、`986` 行,超过 `$gframework-batch-boot 75` 的主停止阈值 - - 按批处理技能规则,本轮必须在提交当前批次后停止;剩余候选应在新一轮里单独评估,尤其是 `YamlConfigLoaderTests.cs` - -## 2026-04-24 — RP-053 - -### 阶段:`GFramework.Godot` / `GFramework.Godot.Tests` 小批次 warning 清理 - -- 触发背景: - - 用户以 `$gframework-batch-boot 75` 要求继续按批次推进 analyzer warning reduction,并以 `origin/main` 作为累计分支 diff 基线 - - 当前 worktree `fix/analyzer-warning-reduction-batch` 相对 `origin/main` 的已提交分支 diff 为 `0` 个文件,具备继续落一个低风险 warning batch 的空间 - - solution-level `dotnet clean GFramework.sln -c Release` 仍在 `ValidateSolutionConfiguration` 阶段失败,因此本轮继续用直接 `dotnet build GFramework.sln -c Release` 建立热点观察值 -- 主线程实施: - - 运行 `dotnet build GFramework.sln -c Release`,确认当前整仓观测值为 `1122 warning(s)`,并从输出中挑选 `GFramework.Godot` 的小范围热点作为本轮批次 - - 在 `GodotYamlConfigEnvironment.cs` 中按“普通文件系统 / Godot 路径”拆分目录枚举 helper,消除 `MA0051` - - 在 `AbstractArchitecture.cs` 与 `SceneBehaviorBase.cs` 中将必须保留 Godot 主线程上下文的 await 显式改为 `.ConfigureAwait(true)`,清理 `MA0004` 并把线程意图写入注释 - - 在 `GFramework.Godot.Tests` 中补齐异步断言的 `.ConfigureAwait(false)`,并让 `RichTextMarkupTests` 的测试字典显式指定 `StringComparer.Ordinal` -- 验证里程碑: - - `dotnet clean GFramework.sln -c Release` - - 结果:失败;停在 `ValidateSolutionConfiguration`,`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.sln -c Release` - - 结果:成功;`1122 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release` - - 第一轮修复后:成功;`12 Warning(s)`、`0 Error(s)`,仅剩 `MA0004` - - 第二轮修复后:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~AbstractArchitectureModuleInstallationTests|FullyQualifiedName~GodotYamlConfigLoaderTests|FullyQualifiedName~RichTextMarkupTests"` - - 结果:成功;`Passed: 15`、`Failed: 0` - - `dotnet build GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release` - - 并行验证时:成功;`1 Warning(s)`、`0 Error(s)`;`MSB3026` 为与并行 `dotnet test` 竞争输出 DLL 的文件占用 - - 串行复验:成功;`0 Warning(s)`、`0 Error(s)` -- 当前结论: - - `GFramework.Godot` 与 `GFramework.Godot.Tests` 本轮直接涉及的 warning 已全部清零 - - 当前待提交代码批次相对 `origin/main` 的源码 diff 为 `6` 个文件、`107` 行,距离 `$gframework-batch-boot 75` 主停止阈值仍有充足余量 - - 继续推进的下一批候选将主要落在 `GFramework.Game` 等高 warning 基线模块,已不再属于当前同等级低风险切片,因此本轮在这里收口并进入提交 - -## 2026-04-24 — RP-052 - -### 阶段:PR review follow-up(comparer 契约 + `ConfigureAwait(false)` 收尾) - -- 触发背景: - - 当前分支 PR #283 的最新 review 中,`greptile-apps[bot]` 仍有一个未解决线程,指出 `UnifiedSettingsDataRepository.CloneFile` fallback 会静默丢失原 comparer - - CodeRabbit 另指出 `AutoRegisterExportedCollectionsGeneratorTests.cs` 中还残留 5 处 `await test.RunAsync();`,与同项目其他测试文件的 `.ConfigureAwait(false)` 风格不一致 -- 主线程实施: - - 复核 PR review JSON、`UnifiedSettingsDataRepository.cs`、`UnifiedSettingsFile.cs` 与 `AutoRegisterExportedCollectionsGeneratorTests.cs` 的当前代码,确认只有 comparer 契约线程仍属最新 head 上的实质问题 - - 将 `UnifiedSettingsFile.Sections` 的 XML 注释补充为显式 comparer 契约,并把默认字典初始化改为 `StringComparer.Ordinal` - - 将 `CloneFile` fallback 从隐式默认 comparer 改为显式 `StringComparer.Ordinal`,并同步修正文档注释,避免继续暗含“保留原语义”的错误表述 - - 把 `AutoRegisterExportedCollectionsGeneratorTests` 中剩余的 5 处 `await test.RunAsync();` 统一为 `.ConfigureAwait(false)`,同时让 `VerifyDiagnosticsAsync` 内部也消费 `ConfigureAwait(false)` + - 重新抓取 PR #288 review,确认 latest-head open threads 为 `CodeRabbit 6 + Greptile 2` + - 复核 `outside diff + nitpick` 的 19 条建议,只采纳本地仍成立的建议;拒绝把“评论总数”机械等同于“必须全改” + - 完成以下高信号修复: + - `ContextAware*` / `AsyncExtensions` / `NumericExtensions` / `StringExtensions` / `StoreBuilder`:回退为 `ArgumentNullException.ThrowIfNull(...)` + - `ArchitectureServicesTests` / `GameContextTests`:同步 XML `` 到 `NotSupportedException` + - `RegistryInitializationHookBaseTests`:修复 override 可空签名实现,避免再次引入编译错误 + - `RollingFileAppenderTests` / `TaskCoroutineExtensionsTests` / `WaitForTaskTests` / `ScopedStorage`:移除无收益噪音代码 + - `FileStorage`:通过 `leaveOpen: true` 修正 `FileStream` 的双重释放语义 + - `SceneRouterBase`:统一显式 `ConfigureAwait(true)` 并补齐引擎线程亲和说明 + - `StoreSelection`:保留 `net9.0+` 的 `System.Threading.Lock`,同时修正条件编译旁的注释写法,避免 `CS1587` - 验证里程碑: + - `dotnet restore GFramework.sln -p:RestoreFallbackFolders="" -v minimal` + - 结果:成功;证明先前 `MSB4018` 来自 stale restore 元数据,而不是当前 WSL 默认 build 路径本身不可用 + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release` + - 结果:成功;`28 Warning(s)`、`0 Error(s)` - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release` - - 结果:成功;`533 Warning(s)`、`0 Error(s)`;`GFramework.Game` 仍有既有 warning 基线,本轮 follow-up 仅处理 PR review 指向的 comparer 契约与测试异步等待一致性 - - `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release` - - 结果:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build` - - 首次并行复验:失败;`FileNotFoundException`,原因是 `--no-build` 测试在 Release DLL 落盘前启动 - - 串行复验:成功;`Passed: 48`、`Failed: 0` + - 结果:成功;`329 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release` + - 结果:成功;`137 Warning(s)`、`0 Error(s)` - 当前结论: - - PR #283 当前仍打开的 comparer review thread 已在本地代码与 XML 注释层面得到对应修复 - - `AutoRegisterExportedCollectionsGeneratorTests` 的异步等待风格已与同项目其他测试保持一致 - - 当前改动已通过直接受影响测试项目的 Release build 与串行 Release test 复验,可进入提交阶段 + - 用户关于“WSL 里直接 `dotnet build` 可行”的判断正确 + - 前一轮失败的核心原因不是仓库不可构建,而是主线程附加的 workaround 参数改变了 MSBuild 行为 + - 本轮已完成 PR #288 中一组仍成立的建议修复,并重新拿到标准 WSL 路径下的 Release build 验证 + - 剩余 review 线程需要在新 head 上重新抓取后再决定是否逐条 resolve -## 2026-04-24 — RP-051 +## 2026-04-25 — RP-063 -### 阶段:`GFramework.Godot.SourceGenerators.Tests` warning 清零 +### 阶段:先收口 PR #288 latest-head 编译错误,再暂停在环境阻塞点并准备提交 - 触发背景: - - 用户要求直接运行 `dotnet clean`,不再添加额外 shell 包装;solution-level `dotnet clean` 仍然在 `ValidateSolutionConfiguration` 阶段失败 - - 直接执行仓库根目录 `dotnet build` 成功,并输出 `1184 warning(s)`,说明当前真实热点已从 `GFramework.Godot.SourceGenerators` 转移到对应测试项目 + - 用户显式要求先执行 `$gframework-pr-review`,并指出 `AsyncExtensionsTests.cs(126,23)` 当前存在 `CS0029` / `CS1662` 构建错误 + - 当前 worktree 仍是 `fix/analyzer-warning-reduction-batch`,因此本 turn 继续沿用 `analyzer-warning-reduction` 的 active recovery 文档 - 主线程实施: - - 以 `GFramework.Godot.SourceGenerators.Tests` 为独立批次,先确认该项目本地基线为 `24 warning(s)` - - 在 `BindNodeSignalGeneratorTests.cs`、`AutoSceneGeneratorTests.cs`、`AutoUiPageGeneratorTests.cs`、`GetNodeGeneratorTests.cs`、`AutoRegisterExportedCollectionsGeneratorTests.cs`、`GodotProjectMetadataGeneratorTests.cs` 中抽取共享 source / diagnostic helper,压缩重复长方法 - - 在 `Core/GeneratorTest.cs` 中补充 `ConfigureAwait(false)`,清除项目内唯一 `MA0004` - - 把 `GFramework.Godot.SourceGenerators.Tests` 项目 warning 从 `24` 降到 `0` + - 运行 PR review 抓取脚本,确认当前分支对应 PR `#288` + - 核对 latest-head unresolved review threads 后,优先修复 `AsyncExtensionsTests.cs` 中 `ct => Task.Delay(...).ConfigureAwait(false)` 错误返回 `ConfiguredTaskAwaitable` 的问题 + - 顺手收敛多处已被 latest review 点名且本地仍成立的低风险残留: + - 测试中的 `async` 无 `await` + - `ValueTask` 断言包装 + - `RegistryInitializationHookBaseTests.cs` 的可空返回签名 + - `NumericExtensions.cs`、`StringExtensions.cs`、`StoreBuilder.cs` 的 Allman 花括号残留 + - `StoreSelection.cs` 在 `net9.0+` 下切到 `System.Threading.Lock`,同时保留 `net8.0` 兼容分支 - 验证里程碑: - - `dotnet build` - - 结果:成功;`1184 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj` - - 初始结果:成功;`24 Warning(s)`、`0 Error(s)` - - 第一批(`BindNodeSignal` + `GeneratorTest`)后:`16 Warning(s)` - - 第二批(`AutoScene` / `AutoUiPage` / `GetNode`)后:`8 Warning(s)` - - 第三批(`Registration` / `Project`)后:`1 Warning(s)` - - 收尾修复后:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release` - - 结果:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet test GFramework.Godot.SourceGenerators.Tests/GFramework.Godot.SourceGenerators.Tests.csproj -c Release --no-build` - - 结果:成功;`Passed: 48`、`Failed: 0` + - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/current-pr-review.json` + - 结果:成功;确认 PR `#288` 的 latest-head unresolved AI review threads 共 `9` 个,其中 `AsyncExtensionsTests.cs:126` 为 critical 编译错误 + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`MSB4018`,`ResolvePackageAssets` 仍读取失效 Windows fallback package folder `D:\Tool\Development Tools\Microsoft Visual Studio\Shared\NuGetPackages` + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net9.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;原因同上 + - `DOTNET_CLI_HOME=/tmp/dotnet-home MSBuildEnableWorkloadResolver=false dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore -p:TargetFramework=net10.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;原因同上 - 当前结论: - - `GFramework.Godot.SourceGenerators.Tests` 已在 `Debug` / `Release` 构建下达到 `0 warning(s)` - - 按 `origin/main` merge-base 计算并只纳入当前暂存批次时,累计分支 diff 为 `23` 个文件,低于 `$gframework-batch-boot 75` 的主停止阈值 - - 仓库根目录 `dotnet clean` 仍无法稳定产出新的 clean 基线,需要在下一轮单独排查 - - 当前 worktree 已有与本批次无关的既有改动;提交时必须只暂存 analyzer warning reduction 相关文件 + - 用户点名的 `AsyncExtensionsTests.cs` 编译错误已在源码层修复 + - 本 turn 未能拿到新的可通过 Release build,阻塞点已从先前记录的 `MSB4276` 收敛为当前 `obj/*.csproj.nuget.g.props` 中 stale Windows fallback package folder 导致的 `MSB4018` + - 用户随后要求“先不管这个了,先提交吧”,因此本 turn 在记录环境阻塞后先执行提交收口 -## 2026-04-24 — RP-050 +## 2026-04-25 — RP-062 -### 阶段:clean-build 基线修正与 `GFramework.Godot.SourceGenerators` 切片清零 +### 阶段:触达 `$gframework-batch-boot 75` 停止阈值并收口到 `75 files / 2098 lines` - 触发背景: - - 用户确认之前的 `0 Warning(s)` 来自增量构建假阴性;只有先 `dotnet clean` 再 `dotnet build`,warning 才会重新出现 - - 用户给出 clean solution build 的真实结果:`Build succeeded with 1193 warning(s)` + - `RP-061` 收尾时分支相对 `origin/main` 仍只有 `48` 个已提交文件,距离本轮 `75 files` 停止条件还有明显空间 + - 用户明确允许继续委派 subagent,因此主线程继续把低风险机械型写集拆成互不重叠的 test / runtime 小批次 + - 本轮主目标不是继续深挖单个高上下文热点,而是用新的低风险文件精确把 branch diff 推到阈值后停止 - 主线程实施: - - 纠正当前 topic 的 active todo / trace,把 clean build 作为新的 warning 检查真值 - - 在 `BindNodeSignalGenerator.cs`、`GetNodeGenerator.cs`、`GodotProjectMetadataGenerator.cs` 中完成分阶段方法抽取与字符串比较修正 - - 在 `Registration/AutoRegisterExportedCollectionsGenerator.cs` 中拆分 `TryCreateRegistration`,清除最后一个 `MA0051` - - 更新 `AGENTS.md`,明确 warning 检查必须先 `dotnet clean` 再 `dotnet build` + - 先接受并提交 7 文件 `Core.Tests` 收尾批次为 `03c73a8` `test(core-tests): 收敛测试桩与辅助类型 warning` + - 随后主线程与多个 worker 并行收口以下新增文件: + - `ArchitectureAdditionalCqrsHandlersTests.cs` + - `RegistryInitializationHookBaseTests.cs` + - `CommandCoroutineExtensionsTests.cs` + - `TaskCoroutineExtensionsTests.cs` + - `WaitForTaskTTests.cs` + - `AsyncExtensionsTests.cs` + - `LogContextTests.cs` + - `PauseStackManagerTests.cs` + - `AsyncExtensions.cs` + - `CollectionExtensions.cs` + - `ContextAwareCommandExtensions.cs` + - `ContextAwareEnvironmentExtensions.cs` + - `ContextAwareEventExtensions.cs` + - `ContextAwareQueryExtensions.cs` + - `ContextAwareServiceExtensions.cs` + - `GuardExtensions.cs` + - `NumericExtensions.cs` + - `StoreEventBusExtensions.cs` + - `StringExtensions.cs` + - `StoreBuilder.cs` + - `StoreSelection.cs` + - 将上述 22 文件批次收口为 `9ce1fa6` `refactor(core): 收敛 Core 扩展与测试的机械 warning` - 验证里程碑: - - `dotnet clean GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj -c Release` + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -v minimal` - 结果:成功;`0 Warning(s)`、`0 Error(s)` - - `dotnet build GFramework.Godot.SourceGenerators/GFramework.Godot.SourceGenerators.csproj -c Release` - - 首次验证:成功;`1 Warning(s)`,剩余 `Registration/AutoRegisterExportedCollectionsGenerator.cs(182,25)` `MA0051` - - 修复后复验:成功;`0 Warning(s)`、`0 Error(s)` + - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-incremental --no-restore -p:RestoreFallbackFolders= -v:diag` + - 结果:失败;`MSB4276`,默认 SDK resolver 缺少 `Microsoft.NET.SDK.WorkloadAutoImportPropsLocator` + - `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:TestTargetFrameworks=net8.0 -p:RestoreFallbackFolders="" -v minimal` + - 结果:失败;`NU1201`,`GFramework.Tests.Common` 仅支持 `net10.0`,不能作为 `Core.Tests` 的 net8 旁路验证 + - `git diff --name-only origin/main...HEAD | wc -l` + - 结果:`75` + - `git diff --numstat origin/main...HEAD` + - 结果:累计 `1083` added、`1015` deleted,即 `2098` changed lines - 当前结论: - - `GFramework.Godot.SourceGenerators` 已在 clean `Release` build 下从 9 个 warning 降到 0 个 warning - - 整仓库 warning 基线仍以用户确认的 clean solution build `1193 warning(s)` 为准 - - 下一轮应继续从 clean solution build 输出中选择新的低风险热点 + - 本轮 `$gframework-batch-boot 75` 已精确达到主停止条件,默认恢复点应停止在 `9ce1fa6` + - `Core` runtime 的本轮机械型改动已有可通过的最小 Release build 验证 + - `Core.Tests` 的继续推进当前首先受 `MSB4276` 环境阻塞影响;下一轮若要继续,应先修复构建环境,再重新建立 warning 基线 -## Archive Context +## 历史归档指针 -- 当前轮次归档: - - [analyzer-warning-reduction-history-rp042-rp048.md](../archive/todos/analyzer-warning-reduction-history-rp042-rp048.md) - - [analyzer-warning-reduction-history-rp042-rp048.md](../archive/traces/analyzer-warning-reduction-history-rp042-rp048.md) -- 历史跟踪归档: - - [analyzer-warning-reduction-history-rp001.md](../archive/todos/analyzer-warning-reduction-history-rp001.md) - - [analyzer-warning-reduction-history-rp002-rp041.md](../archive/todos/analyzer-warning-reduction-history-rp002-rp041.md) -- 历史 trace 归档: +- 早期 trace 归档: - [analyzer-warning-reduction-history-rp001.md](../archive/traces/analyzer-warning-reduction-history-rp001.md) - [analyzer-warning-reduction-history-rp002-rp041.md](../archive/traces/analyzer-warning-reduction-history-rp002-rp041.md) + - [analyzer-warning-reduction-history-rp042-rp048.md](../archive/traces/analyzer-warning-reduction-history-rp042-rp048.md)