diff --git a/CHANGELOG.md b/CHANGELOG.md index a094f1f2..9e45f22b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- **OperationLockServiceTests** — replaced flaky `Barrier` + `Thread.Sleep` + thread-safety test with deterministic `CountdownEvent` + `ManualResetEventSlim` + synchronization; asserts exactly 1 acquisition instead of `>= 1`. + +### Changed +- **README.md** — added missing tech stack entries: Microsoft.Extensions.DependencyInjection, + H.NotifyIcon.Wpf, NSubstitute. + ## [0.48.34] - 2026-05-19 ### Fixed diff --git a/README.md b/README.md index 34453abd..89304308 100644 --- a/README.md +++ b/README.md @@ -452,10 +452,12 @@ and elevation state in a format ready to paste into a bug report. - .NET 9 (WPF, C# 13) - CommunityToolkit.Mvvm for MVVM plumbing +- Microsoft.Extensions.DependencyInjection for IoC - ModernWpfUI for the modern title bar - LiveCharts2 for the real-time latency chart +- H.NotifyIcon.Wpf for system tray integration - Serilog for structured logging -- xUnit and FlaUI for unit, integration, and UI-automation tests +- xUnit, NSubstitute, and FlaUI for unit, integration, and UI-automation tests ## Privacy diff --git a/SysManager/SysManager.Tests/OperationLockServiceTests.cs b/SysManager/SysManager.Tests/OperationLockServiceTests.cs index 86f0d872..e11f4db8 100644 --- a/SysManager/SysManager.Tests/OperationLockServiceTests.cs +++ b/SysManager/SysManager.Tests/OperationLockServiceTests.cs @@ -93,20 +93,28 @@ public void GetActiveOperationName_ReturnsNull_WhenNotLocked() public async Task TryAcquire_IsThreadSafe() { int successCount = 0; - var barrier = new Barrier(10); + var ready = new CountdownEvent(10); + var go = new ManualResetEventSlim(false); + var allTried = new CountdownEvent(10); var tasks = Enumerable.Range(0, 10).Select(_ => Task.Run(() => { - barrier.SignalAndWait(); - using var handle = Sut.TryAcquire(OperationCategory.Network, "Race"); + ready.Signal(); + go.Wait(); + var handle = Sut.TryAcquire(OperationCategory.SystemModification, "Race"); if (handle != null) Interlocked.Increment(ref successCount); - Thread.Sleep(50); + // Wait until all threads have attempted acquisition before releasing + allTried.Signal(); + allTried.Wait(); + handle?.Dispose(); })).ToArray(); + ready.Wait(); + go.Set(); await Task.WhenAll(tasks); - // Only one thread should have acquired the lock at a time - Assert.True(successCount >= 1); + // Exactly one thread should have acquired the lock + Assert.Equal(1, successCount); } }