using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using UnitTestSharp;
namespace Glass.UnitTests
{
public class ExceptionLogTests : TestFixture
{
public static readonly ExceptionSite SiteA = new ExceptionSite(new Control("a"), ExceptionPhase.Update);
public static readonly ExceptionSite SiteB = new ExceptionSite(new Control("b"), ExceptionPhase.Update);
///
/// One throw site for whatever exception it's handed.
///
public static readonly Action Rethrow = exception => throw exception;
public class ThrowingControl : Control
{
public override void CancelMouseInput()
{
throw new InvalidOperationException("Thrown by client code.");
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowFromClientHelper()
{
throw new InvalidOperationException("Thrown by a client helper.");
}
public const string Pedigree = "window > toolbar > okButton";
///
/// Runs behind a boundary filtered by , and returns whether
/// the log claimed what it threw.
///
public static bool Run(ExceptionLog log, ExceptionSite site, System.Action action)
{
try
{
action();
}
catch (Exception exception) when (log.Claims(exception, site, Pedigree))
{
return true;
}
return false;
}
public static bool Run(ExceptionLog log, ExceptionSite site, Action action, T argument,
string pedigree = Pedigree)
{
try
{
action(argument);
}
catch (Exception exception) when (log.Claims(exception, site, pedigree))
{
return true;
}
return false;
}
///
/// Has client code throw a new at , and returns the
/// entry it landed in.
///
public static ExceptionEntry Hit(ExceptionLog log, ExceptionSite site)
where TException : Exception, new()
{
Run(log, site, Rethrow, new TException());
return log.Entries.Single(entry => entry.Exception.GetType() == typeof(TException));
}
public class CtorTests : TestFixture
{
public void StartsAtFrameZero()
{
CheckEqual(0L, new ExceptionLog().Frame);
}
public void StartsAtTimeZero()
{
CheckEqual(TimeSpan.Zero, new ExceptionLog().Now);
}
public void StartsWithNoEntries()
{
CheckEqual(0, new ExceptionLog().Entries.Count);
}
}
public class AdvanceTests : TestFixture
{
private ExceptionLog log;
private List announced;
public override void TestSetup()
{
log = new ExceptionLog();
announced = new List();
log.EntryAdded += entry => announced.Add(entry);
}
public void Always_CountsOneFrame()
{
log.Advance(TimeSpan.FromMilliseconds(16));
log.Advance(TimeSpan.FromMilliseconds(16));
CheckEqual(2L, log.Frame);
}
public void Always_AccumulatesTheElapsedTime()
{
log.Advance(TimeSpan.FromMilliseconds(16));
log.Advance(TimeSpan.FromMilliseconds(4));
CheckEqual(TimeSpan.FromMilliseconds(20), log.Now);
}
public void ZeroElapsed_StillCountsAFrame()
{
log.Advance(TimeSpan.Zero);
CheckEqual(1L, log.Frame);
CheckEqual(TimeSpan.Zero, log.Now);
}
public void NewEntry_IsAnnouncedOnlyOnceAdvanced()
{
ExceptionEntry entry = Hit(log, SiteA);
CheckEqual(0, announced.Count);
log.Advance(TimeSpan.Zero);
CheckEqual(1, announced.Count);
CheckEqual(entry, announced[0]);
}
public void EntryHitAgain_IsNotAnnouncedAgain()
{
Hit(log, SiteA);
log.Advance(TimeSpan.Zero);
Hit(log, SiteA);
log.Advance(TimeSpan.Zero);
CheckEqual(1, announced.Count);
}
public void EntryAddedWithNoSubscriber_IsNotAnnouncedToALaterOne()
{
var unsubscribed = new ExceptionLog();
Hit(unsubscribed, SiteA);
unsubscribed.Advance(TimeSpan.Zero);
unsubscribed.EntryAdded += entry => announced.Add(entry);
unsubscribed.Advance(TimeSpan.Zero);
CheckEqual(0, announced.Count);
}
public void SeveralNewEntries_AreAnnouncedInTheOrderTheyWereAdded()
{
ExceptionEntry first = Hit(log, SiteA);
ExceptionEntry second = Hit(log, SiteA);
log.Advance(TimeSpan.Zero);
CheckEqual(2, announced.Count);
CheckEqual(first, announced[0]);
CheckEqual(second, announced[1]);
}
}
public class SnoozeTests : TestFixture
{
public void Always_SnoozesUntilNowPlusTheDuration()
{
var log = new ExceptionLog();
log.Advance(TimeSpan.FromMilliseconds(16));
var entry = new ExceptionEntry(new InvalidOperationException(), log.Frame, log.Now);
log.Snooze(entry, TimeSpan.FromMilliseconds(10));
CheckEqual(TimeSpan.FromMilliseconds(26), entry.SnoozedUntil);
}
}
public class ClaimsTests : TestFixture
{
private ExceptionLog log;
public override void TestSetup()
{
log = new ExceptionLog();
}
public void ExceptionFromClientCode_IsClaimed()
{
CheckTrue(Run(log, SiteA, () => throw new InvalidOperationException()));
CheckEqual(1, log.Entries.Count);
}
public void ExceptionFromGlassAlone_Propagates()
{
CheckThrow(typeof(ArgumentException));
Run(log, SiteA, new ControlContainer().MoveToFront, new Control());
}
public void ExceptionFromALibraryBeneathGlassAlone_Propagates()
{
var list = new List();
IEnumerator enumerator = list.GetEnumerator();
list.Add(1);
CheckThrow(typeof(InvalidOperationException));
Run(log, SiteA, enumerator.Reset);
}
public void GlassExceptionReachedThroughClientCode_IsClaimed()
{
CheckTrue(Run(log, SiteA, () => new ControlContainer().MoveToFront(new Control())));
}
public void ClientExceptionReachedThroughGlass_IsClaimed()
{
var root = new Panel();
root.CanvasChildren.Add(new ThrowingControl());
CheckTrue(Run(log, SiteA, root.CancelMouseInputInSubtree));
}
public void OutOfMemory_Propagates()
{
CheckThrow(typeof(OutOfMemoryException));
Run(log, SiteA, () => throw new OutOfMemoryException());
}
public void ClientExceptionReachedThroughALibraryBeneathGlass_IsClaimed()
{
CheckTrue(Run>(log, SiteA, new List { 1 }.ForEach,
_ => throw new InvalidOperationException()));
}
public void DynamicCodeCalledFromClientCode_IsClaimed()
{
System.Action compiled = Expression.Lambda(
Expression.Throw(Expression.New(typeof(InvalidOperationException)))).Compile();
CheckTrue(Run(log, SiteA, () => compiled()));
}
public void SameDynamicThrowTwice_MakesOneEntry()
{
System.Action compiled = Expression.Lambda(
Expression.Throw(Expression.New(typeof(InvalidOperationException)))).Compile();
System.Action callCompiled = () => compiled();
Run(log, SiteA, callCompiled);
Run(log, SiteA, callCompiled);
CheckEqual(1, log.Entries.Count);
}
public void DifferentThrowSites_MakeSeparateEntries()
{
Run(log, SiteA, () => throw new InvalidOperationException());
Run(log, SiteA, () => throw new InvalidOperationException());
CheckEqual(2, log.Entries.Count);
}
public void SameThrowSiteWithDifferentMessages_MakesOneEntry()
{
Run(log, SiteA, Rethrow, new InvalidOperationException("one"));
Run(log, SiteA, Rethrow, new InvalidOperationException("two"));
CheckEqual(1, log.Entries.Count);
CheckEqual(2, log.Entries[0].Count);
}
public void SameThrowSiteWithDifferentExceptionTypes_MakesSeparateEntries()
{
Run(log, SiteA, Rethrow, new InvalidOperationException());
Run(log, SiteA, Rethrow, new ArgumentException());
CheckEqual(2, log.Entries.Count);
}
public void SameClientThrowAtDifferentDepthsInGlass_MakesOneEntry()
{
var shallow = new Panel();
shallow.CanvasChildren.Add(new ThrowingControl());
var deep = new Panel();
deep.CanvasChildren.Add(new Panel()).CanvasChildren.Add(new Panel())
.CanvasChildren.Add(new ThrowingControl());
Run(log, SiteA, shallow.CancelMouseInputInSubtree);
Run(log, SiteB, deep.CancelMouseInputInSubtree);
CheckEqual(1, log.Entries.Count);
CheckEqual(2, log.Entries[0].Count);
}
public void SameThrowSiteReachedFromDifferentClientCode_MakesSeparateEntries()
{
Run(log, SiteA, () => ThrowFromClientHelper());
Run(log, SiteA, () => ThrowFromClientHelper());
CheckEqual(2, log.Entries.Count);
}
public void RepeatHit_KeepsTheFirstException()
{
var first = new InvalidOperationException("first");
Run(log, SiteA, Rethrow, first);
Run(log, SiteA, Rethrow, new InvalidOperationException("second"));
CheckEqual(first, log.Entries[0].Exception);
}
public void SecondSiteHittingTheSameBug_JoinsItsEntry()
{
Hit(log, SiteA);
Hit(log, SiteB);
CheckEqual(1, log.Entries.Count);
CheckEqualCommutative(new[] { SiteA, SiteB }, log.Entries[0].Sites);
}
public void Always_RecordsThePedigreeAtTheSite()
{
Run(log, SiteA, Rethrow, new InvalidOperationException(), pedigree: "screen > okButton");
CheckEqual(new[] { "screen > okButton" }, log.Entries[0].PedigreesOf(SiteA).ToArray());
}
}
public class IsSkippedTests : TestFixture
{
private ExceptionLog log;
public override void TestSetup()
{
log = new ExceptionLog();
}
public void NothingEverThrown_IsNotSkipped()
{
CheckFalse(log.IsSkipped(SiteA));
}
public void SiteOfAnActiveEntry_IsNotSkipped()
{
Hit(log, SiteA);
CheckFalse(log.IsSkipped(SiteA));
}
public void SiteOfAnIgnoredEntry_IsSkipped()
{
Hit(log, SiteA).Ignored = true;
CheckTrue(log.IsSkipped(SiteA));
}
public void SiteOfASnoozedEntry_IsSkipped()
{
log.Snooze(Hit(log, SiteA), TimeSpan.FromMilliseconds(10));
log.Advance(TimeSpan.FromMilliseconds(9));
CheckTrue(log.IsSkipped(SiteA));
}
public void SiteOfAnExpiredSnooze_IsNotSkipped()
{
log.Snooze(Hit(log, SiteA), TimeSpan.FromMilliseconds(10));
log.Advance(TimeSpan.FromMilliseconds(10));
CheckFalse(log.IsSkipped(SiteA));
}
public void SameTargetInAnotherPhase_IsNotSkipped()
{
Hit(log, SiteA).Ignored = true;
CheckFalse(log.IsSkipped(new ExceptionSite(SiteA.Target, ExceptionPhase.Render)));
}
public void SiteThatNeverThrew_IsNotSkippedWhileAnotherIs()
{
Hit(log, SiteA).Ignored = true;
CheckFalse(log.IsSkipped(SiteB));
}
public void SiteOfAnActiveEntry_IsNotSkippedWhileAnotherEntryIs()
{
Hit(log, SiteA).Ignored = true;
Hit(log, SiteB);
CheckFalse(log.IsSkipped(SiteB));
}
public void SiteWithAnIgnoredAndAnActiveEntry_IsNotSkipped()
{
Hit(log, SiteA).Ignored = true;
Hit(log, SiteA);
CheckFalse(log.IsSkipped(SiteA));
}
public void SiteWithEveryEntryIgnoredOrSnoozed_IsSkipped()
{
Hit(log, SiteA).Ignored = true;
log.Snooze(Hit(log, SiteA), TimeSpan.FromSeconds(1));
CheckTrue(log.IsSkipped(SiteA));
}
}
}
}