Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ritalin/2880770 to your computer and use it in GitHub Desktop.
Save ritalin/2880770 to your computer and use it in GitHub Desktop.
Unit Testing, Part II, Synchronisation Issues
public class ComplexBusinessOperations
{
private int moneyEarnedSoFar;
public int MoneyEarnedSoFar
{
get { return moneyEarnedSoFar; }
}
public void EarnMoney(int investment)
{
EarnMoneySubTaks(investment);
EarnMoneySubTaks(investment);
}
private void EarnMoneySubTaks(int investment)
{
for (int i = 0; i < investment; i++)
{
var result = EarnOneDollar();
moneyEarnedSoFar += result;
}
}
private int EarnOneDollar()
{
// This operation takes a while
Thread.Sleep(50);
return 1;
}
}
public class ComplexBusinessOperations
{
private int moneyEarnedSoFar;
public int MoneyEarnedSoFar
{
get { return moneyEarnedSoFar; }
}
public async Task EarnMoneyAsync(int investment)
{
var firstMoneyEarner = EarnMoneySubTaskAsync(investment);
var secondMoneyEarner = EarnMoneySubTaskAsync(investment);
await TaskEx.WhenAll(firstMoneyEarner, secondMoneyEarner);
}
private async Task EarnMoneySubTaskAsync(int investment)
{
for (int i = 0; i < investment; i++)
{
var result = await EarnOneDollarAsync();
moneyEarnedSoFar += result;
}
}
private Task<int> EarnOneDollarAsync()
{
return TaskEx.Run(
() =>
{
// This operation takes a while
Thread.Sleep(50);
return 1;
});
}
}
[Test]
public void EarnMoney()
{
var toTest = new ComplexBusinessOperations();
toTest.EarnMoney(200);
Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
}
[Test]
public void EarnMoney()
{
var toTest = new ComplexBusinessOperations();
var task = toTest.EarnMoneyAsync(200);
task.Wait();
Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
}
[Test]
public void EarnMoney()
{
TestSyncContext.Run(
async (awaiter) =>
{
var toTest = new ComplexBusinessOperations();
await toTest.EarnMoneyAsync(200);
Assert.AreEqual(400, toTest.MoneyEarnedSoFar);
});
}
public interface ISynchronizationContext {
void RunOneRound();
}
internal class MyPrimitiveSynchronisationContext : SynchronizationContext, ISynchronizationContext, IDisposable
{
private readonly Queue<Action> messagesToProcess = new Queue<Action>();
private readonly object syncHandle = new object();
private bool isRunning = true;
public override void Send(SendOrPostCallback codeToRun, object state) {
throw new NotImplementedException();
}
public override void Post(SendOrPostCallback codeToRun, object state) {
lock (syncHandle) {
if (isRunning) {
messagesToProcess.Enqueue(() => {
codeToRun(state);
});
SignalContinue();
}
}
}
public void RunMessagePump() {
while (CanContinue()) {
RunOneRound();
}
}
public void RunOneRound() {
Action nextToRun = GrabItem();
nextToRun();
}
public void Dispose() {
lock (syncHandle) {
isRunning = false;
SignalContinue();
}
}
private Action GrabItem() {
lock (syncHandle) {
while (CanContinue() && messagesToProcess.Count == 0) {
Monitor.Wait(syncHandle);
}
return messagesToProcess.Dequeue();
}
}
private bool CanContinue() {
lock (syncHandle) {
return isRunning;
}
}
private void SignalContinue() {
Monitor.Pulse(syncHandle);
}
}
public static class TestSyncContext {
public static void Run(Func<INUnitSynchronizationContext, Task> inTestAction) {
using (var msgPump = new NUnitSynchronizationContext()) {
var syncContextBackup = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(msgPump);
try {
msgPump.Post(obj => {
var task = inTestAction(msgPump);
task.AsAsyncAction().Completed = (info, state) => {
if (state == AsyncStatus.Error) {
throw info.ErrorCode;
}
msgPump.Dispose();
};
}, null);
msgPump.RunMessagePump();
}
finally {
SynchronizationContext.SetSynchronizationContext(syncContextBackup);
}
}
}
}
public class Awaiter
{
private readonly MyPrimitiveSynchronisationContext syncContext;
internal Awaiter(MyPrimitiveSynchronisationContext syncContext)
{
this.syncContext = syncContext;
}
public void WaitFor(IAsyncResult toWaitOn)
{
while(!toWaitOn.IsCompleted)
{
syncContext.RunOneRound();
}
}
}
@ritalin
Copy link
Author

ritalin commented Jun 6, 2012

Add the async / await capable for the test runner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment