Skip to content

Commit

Permalink
Merge pull request #1379 from dotnet/dev/bartde/fix_flaky_ui_tests
Browse files Browse the repository at this point in the history
Address flaky UI framework tests
  • Loading branch information
bartdesmet authored Oct 7, 2020
2 parents 16c9993 + e4ccbdd commit ffc86bb
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 343 deletions.
44 changes: 36 additions & 8 deletions Rx.NET/Source/tests/Tests.System.Reactive/DispatcherHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.

using System.Reactive.Disposables;

#if NETCOREAPP2_1 || NET472 || NETCOREAPP3_1 || CSWINRT
using System.Threading;
#endif
Expand All @@ -15,8 +17,12 @@ namespace ReactiveTests
#if HAS_DISPATCHER
static class DispatcherHelpers
{
public static DispatcherWrapper EnsureDispatcher()
private static readonly Semaphore s_oneDispatcher = new Semaphore(1, 1);

public static IDisposable RunTest(out DispatcherWrapper wrapper)
{
s_oneDispatcher.WaitOne();

#if DESKTOPCLR
var dispatcher = new Thread(Dispatcher.Run);
dispatcher.IsBackground = true;
Expand All @@ -29,11 +35,38 @@ public static DispatcherWrapper EnsureDispatcher()

while (d.BeginInvoke(new Action(() => { })).Status == DispatcherOperationStatus.Aborted) ;

return new DispatcherWrapper(d);
wrapper = new DispatcherWrapper(d);

return new DispatcherTest(dispatcher);
#else
return new DispatcherWrapper(Dispatcher.CurrentDispatcher);
wrapper = new DispatcherWrapper(Dispatcher.CurrentDispatcher);

return Disposable.Empty; // REVIEW: Anything to shut down?
#endif
}

#if DESKTOPCLR
private sealed class DispatcherTest : IDisposable
{
private readonly Thread _t;

public DispatcherTest(Thread t)
{
_t = t;
}

public void Dispose()
{
var d = Dispatcher.FromThread(_t);

d.BeginInvoke(new Action(() => d.InvokeShutdown()));

_t.Join();

s_oneDispatcher.Release();
}
}
#endif
}

class DispatcherWrapper
Expand All @@ -47,11 +80,6 @@ public DispatcherWrapper(Dispatcher dispatcher)

public Dispatcher Dispatcher { get { return _dispatcher; } }

public void InvokeShutdown()
{
_dispatcher.InvokeShutdown();
}

public static implicit operator Dispatcher(DispatcherWrapper wrapper)
{
return wrapper._dispatcher;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ public void Ctor_ArgumentChecking()
[Fact]
public void Current()
{
var d = DispatcherHelpers.EnsureDispatcher();
var e = new ManualResetEvent(false);

d.BeginInvoke(() =>
using (DispatcherHelpers.RunTest(out var d))
{
var c = DispatcherScheduler.Current;
c.Schedule(() => { e.Set(); });
});
var e = new ManualResetEvent(false);

d.BeginInvoke(() =>
{
var c = DispatcherScheduler.Current;
c.Schedule(() => { e.Set(); });
});

e.WaitOne();
e.WaitOne();
}
}

[Fact]
Expand Down Expand Up @@ -64,69 +66,76 @@ public void Current_None()
[Fact]
public void Dispatcher()
{
var disp = DispatcherHelpers.EnsureDispatcher();
Assert.Same(disp.Dispatcher, new DispatcherScheduler(disp).Dispatcher);
using (DispatcherHelpers.RunTest(out var disp))
{
Assert.Same(disp.Dispatcher, new DispatcherScheduler(disp).Dispatcher);
}
}

[Fact]
public void Now()
{
var disp = DispatcherHelpers.EnsureDispatcher();
var res = new DispatcherScheduler(disp).Now - DateTime.Now;
Assert.True(res.Seconds < 1);
using (DispatcherHelpers.RunTest(out var disp))
{
var res = new DispatcherScheduler(disp).Now - DateTime.Now;
Assert.True(res.Seconds < 1);
}
}

[Fact]
public void Schedule_ArgumentChecking()
{
var disp = DispatcherHelpers.EnsureDispatcher();
var s = new DispatcherScheduler(disp);
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, default(Func<IScheduler, int, IDisposable>)));
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, TimeSpan.FromSeconds(1), default(Func<IScheduler, int, IDisposable>)));
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, DateTimeOffset.Now, default(Func<IScheduler, int, IDisposable>)));
using (DispatcherHelpers.RunTest(out var disp))
{
var s = new DispatcherScheduler(disp);
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, default(Func<IScheduler, int, IDisposable>)));
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, TimeSpan.FromSeconds(1), default(Func<IScheduler, int, IDisposable>)));
ReactiveAssert.Throws<ArgumentNullException>(() => s.Schedule(42, DateTimeOffset.Now, default(Func<IScheduler, int, IDisposable>)));
}
}

[Fact]
[Asynchronous]
public void Schedule()
{
var disp = DispatcherHelpers.EnsureDispatcher();

RunAsync(evt =>
using (DispatcherHelpers.RunTest(out var disp))
{
var id = Thread.CurrentThread.ManagedThreadId;
var sch = new DispatcherScheduler(disp);
sch.Schedule(() =>
RunAsync(evt =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
disp.InvokeShutdown();
evt.Set();
var id = Thread.CurrentThread.ManagedThreadId;
var sch = new DispatcherScheduler(disp);
sch.Schedule(() =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
});
});
});
}
}

[Fact]
public void ScheduleError()
{
var ex = new Exception();
using (DispatcherHelpers.RunTest(out var disp))
{
var ex = new Exception();

var id = Thread.CurrentThread.ManagedThreadId;
var disp = DispatcherHelpers.EnsureDispatcher();
var evt = new ManualResetEvent(false);
var id = Thread.CurrentThread.ManagedThreadId;
var evt = new ManualResetEvent(false);

Exception thrownEx = null;
disp.UnhandledException += (o, e) =>
{
thrownEx = e.Exception;
evt.Set();
e.Handled = true;
};
var sch = new DispatcherScheduler(disp);
sch.Schedule(() => { throw ex; });
evt.WaitOne();
disp.InvokeShutdown();

Assert.Same(ex, thrownEx);
Exception thrownEx = null;
disp.UnhandledException += (o, e) =>
{
thrownEx = e.Exception;
evt.Set();
e.Handled = true;
};
var sch = new DispatcherScheduler(disp);
sch.Schedule(() => { throw ex; });
evt.WaitOne();

Assert.Same(ex, thrownEx);
}
}

[Fact]
Expand All @@ -143,111 +152,116 @@ public void ScheduleRelative_Zero()

private void ScheduleRelative_(TimeSpan delay)
{
var evt = new ManualResetEvent(false);

var id = Thread.CurrentThread.ManagedThreadId;
using (DispatcherHelpers.RunTest(out var disp))
{
var evt = new ManualResetEvent(false);

var disp = DispatcherHelpers.EnsureDispatcher();
var sch = new DispatcherScheduler(disp);
var id = Thread.CurrentThread.ManagedThreadId;

sch.Schedule(delay, () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
var sch = new DispatcherScheduler(disp);

sch.Schedule(delay, () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
sch.Schedule(delay, () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
});
});
});

evt.WaitOne();
disp.InvokeShutdown();
evt.WaitOne();
}
}

[Fact]
public void ScheduleRelative_Cancel()
{
var evt = new ManualResetEvent(false);

var id = Thread.CurrentThread.ManagedThreadId;

var disp = DispatcherHelpers.EnsureDispatcher();
var sch = new DispatcherScheduler(disp);

sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
using (DispatcherHelpers.RunTest(out var disp))
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
var evt = new ManualResetEvent(false);

var id = Thread.CurrentThread.ManagedThreadId;

var d = sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
var sch = new DispatcherScheduler(disp);

sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
{
Assert.True(false);
evt.Set();
});
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
sch.Schedule(() =>
{
d.Dispose();
});
var d = sch.Schedule(TimeSpan.FromSeconds(0.1), () =>
{
Assert.True(false);
evt.Set();
});
sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
sch.Schedule(() =>
{
d.Dispose();
});
sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
});
});
});

evt.WaitOne();
disp.InvokeShutdown();
evt.WaitOne();
}
}

[Fact]
public void SchedulePeriodic_ArgumentChecking()
{
var disp = DispatcherHelpers.EnsureDispatcher();
var s = new DispatcherScheduler(disp);
using (DispatcherHelpers.RunTest(out var disp))
{
var s = new DispatcherScheduler(disp);

ReactiveAssert.Throws<ArgumentNullException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(1), default(Func<int, int>)));
ReactiveAssert.Throws<ArgumentOutOfRangeException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(-1), x => x));
ReactiveAssert.Throws<ArgumentNullException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(1), default(Func<int, int>)));
ReactiveAssert.Throws<ArgumentOutOfRangeException>(() => s.SchedulePeriodic(42, TimeSpan.FromSeconds(-1), x => x));
}
}

[Fact]
public void SchedulePeriodic()
{
var evt = new ManualResetEvent(false);

var id = Thread.CurrentThread.ManagedThreadId;
using (DispatcherHelpers.RunTest(out var disp))
{
var evt = new ManualResetEvent(false);

var disp = DispatcherHelpers.EnsureDispatcher();
var sch = new DispatcherScheduler(disp);
var id = Thread.CurrentThread.ManagedThreadId;

var d = new SingleAssignmentDisposable();
var sch = new DispatcherScheduler(disp);

d.Disposable = sch.SchedulePeriodic(1, TimeSpan.FromSeconds(0.1), n =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
var d = new SingleAssignmentDisposable();

if (n == 3)
d.Disposable = sch.SchedulePeriodic(1, TimeSpan.FromSeconds(0.1), n =>
{
d.Dispose();
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
if (n == 3)
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
});
}
d.Dispose();
if (n > 3)
{
Assert.True(false);
}
sch.Schedule(TimeSpan.FromSeconds(0.2), () =>
{
Assert.NotEqual(id, Thread.CurrentThread.ManagedThreadId);
evt.Set();
});
}
return n + 1;
});
if (n > 3)
{
Assert.True(false);
}
return n + 1;
});

evt.WaitOne();
disp.InvokeShutdown();
evt.WaitOne();
}
}
}
}
Expand Down
Loading

0 comments on commit ffc86bb

Please sign in to comment.