Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions QuickFiler.Test/Helper Classes/EmailMoveMonitorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ public class EmailMoveMonitorTests
// (avoiding a compile-time WindowsBase dependency on System.Windows.Threading.Dispatcher)
// and asserts it is unchanged after each test. The class is not parallelized because other
// QuickFiler tests intentionally replace UiThread.Dispatcher with dedicated WPF dispatchers.
//
// The snapshot reads the private _dispatcher backing field rather than the public
// Dispatcher property (issue #584): the property getter now throws
// InvalidOperationException when the field is null, and PropertyInfo.GetValue would
// surface that as a TargetInvocationException from this class's setup and teardown.
// Reading the field observes the same state without invoking the guard.
private object _capturedDispatcher;
private static readonly System.Reflection.PropertyInfo DispatcherProperty =
typeof(UiThread).GetProperty(
"Dispatcher",
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static
private static readonly System.Reflection.FieldInfo DispatcherField =
typeof(UiThread).GetField(
"_dispatcher",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
);

/// <summary>
Expand All @@ -46,7 +52,7 @@ public void Setup()
{
// Snapshot the static UiThread.Dispatcher (reflectively, to avoid WindowsBase) so
// teardown can confirm no test mutated this set-once static state.
_capturedDispatcher = DispatcherProperty?.GetValue(null);
_capturedDispatcher = DispatcherField?.GetValue(null);
_marshalInvocationCount = 0;
}

Expand All @@ -55,7 +61,7 @@ public void Cleanup()
{
// Assert the static dispatcher snapshot is unchanged so any accidental static mutation
// is caught and tests remain order-independent.
object current = DispatcherProperty?.GetValue(null);
object current = DispatcherField?.GetValue(null);
current.Should().BeSameAs(_capturedDispatcher);
}

Expand Down
1 change: 1 addition & 0 deletions UtilitiesCS.Test/Threading/IdleAsyncQueue_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ namespace UtilitiesCS.Test.Threading
/// live ApplicationIdleTimer firing timing.
/// </summary>
[TestClass]
[DoNotParallelize]
public class IdleAsyncQueue_Tests
{
#region Helpers
Expand Down
1 change: 1 addition & 0 deletions UtilitiesCS.Test/Threading/ProgressTrackerAsync_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
namespace UtilitiesCS.Test.Threading
{
[TestClass]
[DoNotParallelize]
public class ProgressTrackerAsync_Tests
{
[TestMethod]
Expand Down
2 changes: 1 addition & 1 deletion UtilitiesCS.Test/Threading/ProgressTracker_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace UtilitiesCS.Test
{
[TestClass]
[TestClass, DoNotParallelize]
public class ProgressTracker_Tests
{
[TestMethod]
Expand Down
75 changes: 75 additions & 0 deletions UtilitiesCS.Test/Threading/UiThread_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Reflection;
using System.Threading;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -101,4 +102,78 @@ public override void Post(SendOrPostCallback d, object state)
}
}
}

/// <summary>
/// Regression coverage for issue #584: the accessor contract of
/// <c>UiThread.Dispatcher</c>.
///
/// Purpose:
/// Reflection is used to write the private static <c>UiThread._dispatcher</c> backing
/// field directly. The property has a private setter whose only production writer is
/// <c>UiThread.Initialize()</c>, which shows a real hidden WinForms window, so the
/// backing field is the one seam that lets a unit test place the accessor in each of
/// its two states. Driving the contract through that seam makes both tests
/// deterministic without any timing construct.
///
/// Both tests capture the prior field value and put it back in a finally block, so the
/// process-global state is left exactly as it was found.
/// </summary>
[TestClass]
[DoNotParallelize]
public class UiThread_Dispatcher_Tests
{
private static FieldInfo DispatcherField()
{
return typeof(UiThread).GetField(
"_dispatcher",
BindingFlags.NonPublic | BindingFlags.Static
);
}

[TestMethod]
public void Dispatcher_WhenBackingFieldIsNull_ThrowsInvalidOperationExceptionNamingInitialize()
{
// Arrange
var field = DispatcherField();
field.Should().NotBeNull();
var prior = field.GetValue(null);
field.SetValue(null, null);
try
{
// Act
Action act = () =>
{
_ = UiThread.Dispatcher;
};

// Assert
act.Should()
.Throw<InvalidOperationException>()
.WithMessage("*UiThread.Initialize()*");
}
finally
{
field.SetValue(null, prior);
}
}

[TestMethod]
public void Dispatcher_WhenBackingFieldIsPopulated_ReturnsThatSameInstance()
{
// Arrange
var field = DispatcherField();
var prior = field.GetValue(null);
var expected = System.Windows.Threading.Dispatcher.CurrentDispatcher;
field.SetValue(null, expected);
try
{
// Act / Assert
UiThread.Dispatcher.Should().BeSameAs(expected);
}
finally
{
field.SetValue(null, prior);
}
}
}
}
13 changes: 11 additions & 2 deletions UtilitiesCS/Threading/UiThread.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,19 @@ public static int UiThreadId

public static Dispatcher Dispatcher
{
get => _dispatcher;
get
{
if (_dispatcher is null)
{
throw new InvalidOperationException(
"The UI dispatcher has not been captured. Call UiThread.Init() so that UiThread.Initialize() runs before reading UiThread.Dispatcher."
);
}
return _dispatcher;
}
private set => _dispatcher = value;
}
private static Dispatcher _dispatcher = null!; // set in Initialize() before any access
private static Dispatcher? _dispatcher;
#endregion UI Thread Synchronization

#region Other UI Methods and Properties
Expand Down
Loading
Loading