aspnetcore/test/Microsoft.AspnetCore.Blazor.../Circuits/CircuitSynchronizationConte...

684 lines
20 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Blazor.Server.Circuits;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.Server
{
public class CircuitSynchronizationContextTest
{
// Nothing should exceed the timeout in a successful run of the the tests, this is just here to catch
// failures.
public TimeSpan Timeout = Debugger.IsAttached ? System.Threading.Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(10);
[Fact]
public void Post_CanRunSynchronously_WhenNotBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
// Act
context.Post((_) =>
{
capturedThread = Thread.CurrentThread;
}, null);
// Assert
Assert.Same(thread, capturedThread);
}
[Fact]
public void Post_CanRunSynchronously_WhenNotBusy_Exception()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act & Assert
Assert.Throws<InvalidTimeZoneException>(() => context.Post((_) =>
{
throw new InvalidTimeZoneException();
}, null));
}
[Fact]
public async Task Post_CanRunAsynchronously_WhenBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
// Act
context.Post((_) =>
{
capturedThread = Thread.CurrentThread;
e3.Set();
}, null);
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task;
Assert.True(e3.Wait(Timeout), "timeout");
Assert.NotSame(thread, capturedThread);
}
[Fact]
public async Task Post_CanRunAsynchronously_CaptureExecutionContext()
{
// Arrange
var context = new CircuitSynchronizationContext();
// CultureInfo uses the execution context.
CultureInfo.CurrentCulture = new CultureInfo("en-GB");
CultureInfo capturedCulture = null;
SynchronizationContext capturedContext = null;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
// Act
SynchronizationContext original = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(context);
context.Post((_) =>
{
capturedCulture = CultureInfo.CurrentCulture;
capturedContext = SynchronizationContext.Current;
e3.Set();
}, null);
}
finally
{
SynchronizationContext.SetSynchronizationContext(original);
}
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task;
Assert.True(e3.Wait(Timeout), "timeout");
Assert.Same(CultureInfo.CurrentCulture, capturedCulture);
Assert.Same(context, capturedContext);
}
[Fact]
public async Task Post_CanRunAsynchronously_WhenBusy_Exception()
{
// Arrange
var context = new CircuitSynchronizationContext();
Exception exception = null;
context.UnhandledException += (sender, e) =>
{
exception = (InvalidTimeZoneException)e.ExceptionObject;
};
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var task = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
// Act
context.Post((_) =>
{
throw new InvalidTimeZoneException();
}, null);
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task;
// Use another item to 'push through' the throwing one
context.Send((_) => { }, null);
Assert.NotNull(exception);
}
[Fact]
public async Task Post_BackgroundWorkItem_CanProcessMoreItemsInline()
{
// Arrange
var context = new CircuitSynchronizationContext();
Thread capturedThread = null;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var e4 = new ManualResetEventSlim();
var e5 = new ManualResetEventSlim();
var e6 = new ManualResetEventSlim();
// Force task2 to execute in the background
var task1 = Task.Run(() => context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null));
Assert.True(e1.Wait(Timeout), "timeout");
var task2 = Task.Run(() =>
{
context.Send((_) =>
{
e3.Set();
Assert.True(e4.Wait(Timeout), "timeout");
capturedThread = Thread.CurrentThread;
}, null);
});
e2.Set();
await task1;
Assert.True(e3.Wait(Timeout), "timeout");
// Act
//
// Now task2 is 'running' in the sync context. Schedule more work items - they will be
// run immediately after the second item
context.Post((_) =>
{
e5.Set();
Assert.Same(Thread.CurrentThread, capturedThread);
}, null);
context.Post((_) =>
{
e6.Set();
Assert.Same(Thread.CurrentThread, capturedThread);
}, null);
// Assert
e4.Set();
await task2;
Assert.True(e5.Wait(Timeout), "timeout");
Assert.True(e6.Wait(Timeout), "timeout");
}
[Fact]
public void Post_CapturesContext()
{
// Arrange
var context = new CircuitSynchronizationContext();
var e1 = new ManualResetEventSlim();
// CultureInfo uses the execution context.
CultureInfo.CurrentCulture = new CultureInfo("en-GB");
CultureInfo capturedCulture = null;
SynchronizationContext capturedContext = null;
// Act
context.Post(async (_) =>
{
await Task.Yield();
capturedCulture = CultureInfo.CurrentCulture;
capturedContext = SynchronizationContext.Current;
e1.Set();
}, null);
// Assert
Assert.True(e1.Wait(Timeout), "timeout");
Assert.Same(CultureInfo.CurrentCulture, capturedCulture);
Assert.Same(context, capturedContext);
}
[Fact]
public void Send_CanRunSynchonously()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
// Act
context.Send((_) =>
{
capturedThread = Thread.CurrentThread;
}, null);
// Assert
Assert.Same(thread, capturedThread);
}
[Fact]
public void Send_CanRunSynchronously_Exception()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act & Assert
Assert.Throws<InvalidTimeZoneException>(() => context.Send((_) =>
{
throw new InvalidTimeZoneException();
}, null));
}
[Fact]
public async Task Send_BlocksWhenOtherWorkRunning()
{
// Arrange
var context = new CircuitSynchronizationContext();
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var e4 = new ManualResetEventSlim();
// Force task2 to execute in the background
var task1 = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
// Act
//
// Dispatch this on the background thread because otherwise it would block.
var task2 = Task.Run(() =>
{
e3.Set();
context.Send((_) =>
{
e4.Set();
}, null);
});
// Assert
Assert.True(e3.Wait(Timeout), "timeout");
Assert.True(e3.IsSet);
// Unblock the first item
e2.Set();
await task1;
await task2;
Assert.True(e4.IsSet);
}
[Fact]
public void Send_CapturesContext()
{
// Arrange
var context = new CircuitSynchronizationContext();
var e1 = new ManualResetEventSlim();
// CultureInfo uses the execution context.
CultureInfo.CurrentCulture = new CultureInfo("en-GB");
CultureInfo capturedCulture = null;
SynchronizationContext capturedContext = null;
// Act
context.Send(async (_) =>
{
await Task.Yield();
capturedCulture = CultureInfo.CurrentCulture;
capturedContext = SynchronizationContext.Current;
e1.Set();
}, null);
// Assert
Assert.True(e1.Wait(Timeout), "timeout");
Assert.Same(CultureInfo.CurrentCulture, capturedCulture);
Assert.Same(context, capturedContext);
}
[Fact]
public async Task Invoke_Void_CanRunSynchronously_WhenNotBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
// Act
var task = context.Invoke(() =>
{
capturedThread = Thread.CurrentThread;
});
// Assert
await task;
Assert.Same(thread, capturedThread);
}
[Fact]
public async Task Invoke_Void_CanRunAsynchronously_WhenBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task1 = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
var task2 = context.Invoke(() =>
{
capturedThread = Thread.CurrentThread;
e3.Set();
});
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task1;
Assert.True(e3.Wait(Timeout), "timeout");
await task2;
Assert.NotSame(thread, capturedThread);
}
[Fact]
public async Task Invoke_Void_CanRethrowExceptions()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act
var task = context.Invoke(() =>
{
throw new InvalidTimeZoneException();
});
// Assert
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
}
[Fact]
public async Task Invoke_T_CanRunSynchronously_WhenNotBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
// Act
var task = context.Invoke(() =>
{
return Thread.CurrentThread;
});
// Assert
Assert.Same(thread, await task);
}
[Fact]
public async Task Invoke_T_CanRunAsynchronously_WhenBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task1 = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
var task2 = context.Invoke(() =>
{
e3.Set();
return Thread.CurrentThread;
});
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task1;
Assert.True(e3.Wait(Timeout), "timeout");
Assert.NotSame(thread, await task2);
}
[Fact]
public async Task Invoke_T_CanRethrowExceptions()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act
var task = context.Invoke<string>(() =>
{
throw new InvalidTimeZoneException();
});
// Assert
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
}
[Fact]
public async Task InvokeAsync_Void_CanRunSynchronously_WhenNotBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
// Act
var task = context.Invoke(() =>
{
capturedThread = Thread.CurrentThread;
return Task.CompletedTask;
});
// Assert
await task;
Assert.Same(thread, capturedThread);
}
[Fact]
public async Task InvokeAsync_Void_CanRunAsynchronously_WhenBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
Thread capturedThread = null;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task1 = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
var task2 = context.InvokeAsync(() =>
{
capturedThread = Thread.CurrentThread;
e3.Set();
return Task.CompletedTask;
});
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task1;
Assert.True(e3.Wait(Timeout), "timeout");
await task2;
Assert.NotSame(thread, capturedThread);
}
[Fact]
public async Task InvokeAsync_Void_CanRethrowExceptions()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act
var task = context.InvokeAsync(() =>
{
throw new InvalidTimeZoneException();
});
// Assert
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
}
[Fact]
public async Task InvokeAsync_T_CanRunSynchronously_WhenNotBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
// Act
var task = context.InvokeAsync(() =>
{
return Task.FromResult(Thread.CurrentThread);
});
// Assert
Assert.Same(thread, await task);
}
[Fact]
public async Task InvokeAsync_T_CanRunAsynchronously_WhenBusy()
{
// Arrange
var context = new CircuitSynchronizationContext();
var thread = Thread.CurrentThread;
var e1 = new ManualResetEventSlim();
var e2 = new ManualResetEventSlim();
var e3 = new ManualResetEventSlim();
var task1 = Task.Run(() =>
{
context.Send((_) =>
{
e1.Set();
Assert.True(e2.Wait(Timeout), "timeout");
}, null);
});
Assert.True(e1.Wait(Timeout), "timeout");
var task2 = context.InvokeAsync(() =>
{
e3.Set();
return Task.FromResult(Thread.CurrentThread);
});
// Assert
Assert.False(e2.IsSet);
e2.Set(); // Unblock the first item
await task1;
Assert.True(e3.Wait(Timeout), "timeout");
Assert.NotSame(thread, await task2);
}
[Fact]
public async Task InvokeAsync_T_CanRethrowExceptions()
{
// Arrange
var context = new CircuitSynchronizationContext();
// Act
var task = context.InvokeAsync<string>(() =>
{
throw new InvalidTimeZoneException();
});
// Assert
await Assert.ThrowsAsync<InvalidTimeZoneException>(async () => await task);
}
}
}