using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; using UnitTestSharp; using Action = System.Action; namespace Amphora.UnitTests { public class ExceptionSandboxTests : TestFixture { public static readonly Assembly AmphoraAssembly = typeof(ExceptionSandbox).Assembly; public static readonly ExceptionSite SiteA = new ExceptionSite(new object(), phase: 0); public static readonly ExceptionSite SiteB = new ExceptionSite(new object(), phase: 0); /// /// One throw site for whatever exception it's handed. /// public static readonly Action Rethrow = exception => throw exception; /// /// A comparer that throws when asked to compare, standing in for client code that Amphora calls. /// public sealed class ThrowingComparer : IEqualityComparer { public bool Equals(int left, int right) { throw new InvalidOperationException("Thrown by client code."); } public int GetHashCode(int value) { return value; } } /// /// Returns a dictionary whose lookups of its one key run a comparer that throws. /// public static OrderedDictionary DictionaryWhoseComparerThrows() { var returnMe = new OrderedDictionary(new ThrowingComparer()); returnMe.Add(1, 1); return returnMe; } /// /// A target built in client code. /// public sealed class ClientTarget { } [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowFromClientHelper() { throw new InvalidOperationException("Thrown by a client helper."); } /// /// Does nothing. Calling it after another call keeps the caller's frame, which an x64 tail call would drop. /// [MethodImpl(MethodImplOptions.NoInlining)] public static void KeepsTheCallerOnTheStack() { } public static int ClientFactory() { return 1; } public static ExceptionSandbox NewSandbox() { return new ExceptionSandbox(AmphoraAssembly); } public static ExceptionSandbox NewDroppingSandbox() { return new ExceptionSandbox(stackCanDropFrames: true, anchors: new[] { AmphoraAssembly }); } public static ExceptionSandbox NewKeepingSandbox() { return new ExceptionSandbox(stackCanDropFrames: false, anchors: new[] { AmphoraAssembly }); } public static ExceptionSite SiteAt(object target) { return new ExceptionSite(target, phase: 0); } public static Dictionary Notes(string label, string value) { return new Dictionary { { label, value } }; } /// /// Runs behind a boundary filtered by , and returns /// whether the sandbox claimed what it threw. /// public static bool Run(ExceptionSandbox sandbox, ExceptionSite site, Action action, IReadOnlyDictionary notes = null) { try { action(); } catch (Exception exception) when (sandbox.Claims(exception, site, notes)) { return true; } return false; } public static bool Run(ExceptionSandbox sandbox, ExceptionSite site, Action action, T argument, IReadOnlyDictionary notes = null) { try { action(argument); } catch (Exception exception) when (sandbox.Claims(exception, site, notes)) { return true; } return false; } public static bool Run(ExceptionSandbox sandbox, ExceptionSite site, Action action, T1 first, T2 second) { try { action(first, second); } catch (Exception exception) when (sandbox.Claims(exception, site, notes: null)) { return true; } return false; } public static bool RunFunc(ExceptionSandbox sandbox, ExceptionSite site, Func function, T argument) { try { function(argument); } catch (Exception exception) when (sandbox.Claims(exception, site, notes: null)) { return true; } return false; } /// /// Has client code throw a new at , and returns the /// entry it landed in. /// public static ExceptionEntry Hit(ExceptionSandbox sandbox, ExceptionSite site) where TException : Exception, new() { Run(sandbox, site, Rethrow, new TException()); return sandbox.Entries.Single(entry => entry.Exception.GetType() == typeof(TException)); } public class CtorTests : TestFixture { public void StartsAtFrameZero() { CheckEqual(0L, NewSandbox().Frame); } public void StartsAtTimeZero() { CheckEqual(Duration.Zero, NewSandbox().Now); } public void StartsWithNoEntries() { CheckEqual(0, NewSandbox().Entries.Count); } public void Default_LetsTheStackDropFramesOnlyOnX64() { CheckEqual(IntPtr.Size == 8, NewSandbox().StackCanDropFrames); } public void StackCanDropFrames_IsKept() { CheckTrue(NewDroppingSandbox().StackCanDropFrames); CheckFalse(NewKeepingSandbox().StackCanDropFrames); } public void Anchors_AreKept() { Assembly other = typeof(UnitTestSharp.TestFixture).Assembly; var sandbox = new ExceptionSandbox(AmphoraAssembly, other); CheckEqual(new[] { AmphoraAssembly, other }, sandbox.Anchors.ToArray()); } public void Anchors_AreCopied() { var anchors = new[] { AmphoraAssembly }; var sandbox = new ExceptionSandbox(stackCanDropFrames: false, anchors: anchors); anchors[0] = typeof(UnitTestSharp.TestFixture).Assembly; CheckEqual(new[] { AmphoraAssembly }, sandbox.Anchors.ToArray()); } public void NoAnchors_Throws() { CheckThrow(typeof(ArgumentException)); new ExceptionSandbox(); } public void NullAnchorList_Throws() { CheckThrow(typeof(ArgumentNullException)); new ExceptionSandbox((Assembly[])null); } public void NullAnchor_Throws() { CheckThrow(typeof(ArgumentException)); new ExceptionSandbox(AmphoraAssembly, null); } } public class AdvanceTests : TestFixture { private ExceptionSandbox sandbox; private List announced; public override void TestSetup() { sandbox = NewSandbox(); announced = new List(); sandbox.EntryAdded += entry => announced.Add(entry); } public void Always_CountsOneFrame() { sandbox.Advance(TimeSpan.FromMilliseconds(16)); sandbox.Advance(TimeSpan.FromMilliseconds(16)); CheckEqual(2L, sandbox.Frame); } public void Always_AccumulatesTheElapsedTime() { sandbox.Advance(TimeSpan.FromMilliseconds(16)); sandbox.Advance(TimeSpan.FromMilliseconds(4)); CheckEqual(Duration.FromMilliseconds(20), sandbox.Now); } public void InfiniteElapsed_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); sandbox.Advance(Duration.Forever); } public void NegativeInfiniteElapsed_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); sandbox.Advance(-Duration.Forever); } public void InfiniteElapsed_LeavesTheClockAlone() { try { sandbox.Advance(Duration.Forever); } catch (ArgumentOutOfRangeException) { } CheckEqual(0L, sandbox.Frame); CheckEqual(Duration.Zero, sandbox.Now); } public void ElapsedTimePastTheLargestFiniteDuration_ClampsTheClock() { sandbox.Advance(Duration.MaxValue); sandbox.Advance(Duration.FromMilliseconds(16)); CheckEqual(Duration.MaxValue, sandbox.Now); } public void ZeroElapsed_StillCountsAFrame() { sandbox.Advance(TimeSpan.Zero); CheckEqual(1L, sandbox.Frame); CheckEqual(Duration.Zero, sandbox.Now); } public void NewEntry_IsAnnouncedOnlyOnceAdvanced() { ExceptionEntry entry = Hit(sandbox, SiteA); CheckEqual(0, announced.Count); sandbox.Advance(TimeSpan.Zero); CheckEqual(1, announced.Count); CheckEqual(entry, announced[0]); } public void EntryHitAgain_IsNotAnnouncedAgain() { Hit(sandbox, SiteA); sandbox.Advance(TimeSpan.Zero); Hit(sandbox, SiteA); sandbox.Advance(TimeSpan.Zero); CheckEqual(1, announced.Count); } public void EntryAddedWithNoSubscriber_IsNotAnnouncedToALaterOne() { ExceptionSandbox unsubscribed = NewSandbox(); 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(sandbox, SiteA); ExceptionEntry second = Hit(sandbox, SiteA); sandbox.Advance(TimeSpan.Zero); CheckEqual(2, announced.Count); CheckEqual(first, announced[0]); CheckEqual(second, announced[1]); } } public class SnoozeTests : TestFixture { public void Always_SnoozesUntilNowPlusTheDuration() { ExceptionSandbox sandbox = NewSandbox(); sandbox.Advance(TimeSpan.FromMilliseconds(16)); var entry = new ExceptionEntry(new InvalidOperationException(), sandbox.Frame, sandbox.Now); sandbox.Snooze(entry, TimeSpan.FromMilliseconds(10)); CheckEqual(Duration.FromMilliseconds(26), entry.SnoozedUntil); } public void Forever_SnoozesUntilForever() { ExceptionSandbox sandbox = NewSandbox(); sandbox.Advance(TimeSpan.FromMilliseconds(16)); var entry = new ExceptionEntry(new InvalidOperationException(), sandbox.Frame, sandbox.Now); sandbox.Snooze(entry, Duration.Forever); CheckEqual(Duration.Forever, entry.SnoozedUntil); } public void ADurationPastTheLargestFiniteTime_ClampsInsteadOfWrapping() { ExceptionSandbox sandbox = NewSandbox(); sandbox.Advance(TimeSpan.FromMilliseconds(16)); var entry = new ExceptionEntry(new InvalidOperationException(), sandbox.Frame, sandbox.Now); sandbox.Snooze(entry, Duration.MaxValue); CheckEqual(Duration.MaxValue, entry.SnoozedUntil); } } public class ClaimsTests : TestFixture { private ExceptionSandbox sandbox; public override void TestSetup() { sandbox = NewSandbox(); } public void ExceptionFromClientCode_IsClaimed() { CheckTrue(Run(sandbox, SiteA, () => throw new InvalidOperationException())); CheckEqual(1, sandbox.Entries.Count); } public void ExceptionFromAnAnchorAlone_Propagates() { CheckThrow(typeof(ArgumentOutOfRangeException)); Run(sandbox, SiteA, new DynamicArray().RemoveAt, 5); } public void ExceptionFromALibraryBeneathTheAnchorsAlone_Propagates() { var list = new List(); IEnumerator enumerator = list.GetEnumerator(); list.Add(1); CheckThrow(typeof(InvalidOperationException)); Run(sandbox, SiteA, enumerator.Reset); } public void AnchorExceptionReachedThroughClientCode_IsClaimed() { CheckTrue(Run(sandbox, SiteA, () => { new DynamicArray().RemoveAt(5); KeepsTheCallerOnTheStack(); })); } public void ClientExceptionReachedThroughAnAnchor_IsClaimed() { OrderedDictionary dictionary = DictionaryWhoseComparerThrows(); CheckTrue(RunFunc(sandbox, SiteA, dictionary.ContainsKey, 1)); } public void OutOfMemory_Propagates() { CheckThrow(typeof(OutOfMemoryException)); Run(sandbox, SiteA, () => throw new OutOfMemoryException()); } public void ClientExceptionReachedThroughALibraryBeneathTheAnchors_IsClaimed() { CheckTrue(Run>(sandbox, SiteA, new List { 1 }.ForEach, _ => throw new InvalidOperationException())); } public void DynamicCodeCalledFromClientCode_IsClaimed() { Action compiled = Expression.Lambda( Expression.Throw(Expression.New(typeof(InvalidOperationException)))).Compile(); CheckTrue(Run(sandbox, SiteA, () => { compiled(); KeepsTheCallerOnTheStack(); })); } public void SameDynamicThrowTwice_MakesOneEntry() { Action compiled = Expression.Lambda( Expression.Throw(Expression.New(typeof(InvalidOperationException)))).Compile(); Action callCompiled = () => { compiled(); KeepsTheCallerOnTheStack(); }; Run(sandbox, SiteA, callCompiled); Run(sandbox, SiteA, callCompiled); CheckEqual(1, sandbox.Entries.Count); } public void DifferentThrowSites_MakeSeparateEntries() { Run(sandbox, SiteA, () => throw new InvalidOperationException()); Run(sandbox, SiteA, () => throw new InvalidOperationException()); CheckEqual(2, sandbox.Entries.Count); } public void SameThrowSiteWithDifferentMessages_MakesOneEntry() { Run(sandbox, SiteA, Rethrow, new InvalidOperationException("one")); Run(sandbox, SiteA, Rethrow, new InvalidOperationException("two")); CheckEqual(1, sandbox.Entries.Count); CheckEqual(2, sandbox.Entries[0].Count); } public void SameThrowSiteWithDifferentExceptionTypes_MakesSeparateEntries() { Run(sandbox, SiteA, Rethrow, new InvalidOperationException()); Run(sandbox, SiteA, Rethrow, new ArgumentException()); CheckEqual(2, sandbox.Entries.Count); } public void SameClientThrowReachedThroughDifferentAnchorCallers_MakesOneEntry() { OrderedDictionary dictionary = DictionaryWhoseComparerThrows(); RunFunc(sandbox, SiteA, dictionary.ContainsKey, 1); RunFunc(sandbox, SiteB, dictionary.Remove, 1); CheckEqual(1, sandbox.Entries.Count); CheckEqual(2, sandbox.Entries[0].Count); } public void SameThrowSiteReachedFromDifferentClientCode_MakesSeparateEntries() { Run(sandbox, SiteA, () => { ThrowFromClientHelper(); KeepsTheCallerOnTheStack(); }); Run(sandbox, SiteA, () => { ThrowFromClientHelper(); KeepsTheCallerOnTheStack(); }); CheckEqual(2, sandbox.Entries.Count); } public void RepeatHit_KeepsTheFirstException() { var first = new InvalidOperationException("first"); Run(sandbox, SiteA, Rethrow, first); Run(sandbox, SiteA, Rethrow, new InvalidOperationException("second")); CheckEqual(first, sandbox.Entries[0].Exception); } public void SecondSiteHittingTheSameBug_JoinsItsEntry() { Hit(sandbox, SiteA); Hit(sandbox, SiteB); CheckEqual(1, sandbox.Entries.Count); CheckEqualCommutative(new[] { SiteA, SiteB }, sandbox.Entries[0].Sites); } public void Always_RecordsTheNotesAtTheSite() { Run(sandbox, SiteA, Rethrow, new InvalidOperationException(), Notes("path", "screen > okButton")); CheckEqual("screen > okButton", sandbox.Entries[0].NotesOf(SiteA).Single()["path"]); } public void NoNotes_RecordsOneEmptySet() { Run(sandbox, SiteA, Rethrow, new InvalidOperationException()); CheckEqual(0, sandbox.Entries[0].NotesOf(SiteA).Single().Count); } public void MultipleAnchors_AClientOfAnyOneIsClient() { Assembly unrelated = Assembly.Load( "System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); var sandbox = new ExceptionSandbox(AmphoraAssembly, unrelated); CheckTrue(Run(sandbox, SiteA, () => throw new InvalidOperationException())); } public void AnchorStackAtATargetWithAClientSubscriber_IsClaimedWhenFramesCanDrop() { var target = new Lazy(ClientFactory); CheckTrue(Run(NewDroppingSandbox(), SiteAt(target), new DynamicArray().RemoveAt, 5)); } public void AnchorStackAtATargetWithAClientSubscriber_PropagatesWhenFramesCannotDrop() { var target = new Lazy(ClientFactory); CheckThrow(typeof(ArgumentOutOfRangeException)); Run(NewKeepingSandbox(), SiteAt(target), new DynamicArray().RemoveAt, 5); } public void AnchorStackAtAClientSubclass_IsClaimedWhenFramesCanDrop() { CheckTrue(Run(NewDroppingSandbox(), SiteAt(new ClientTarget()), new DynamicArray().RemoveAt, 5)); } public void AnchorStackAtAClientSubscriberToABaseClassDelegate_IsClaimedWhenFramesCanDrop() { var target = new Task(ClientFactory); CheckTrue(Run(NewDroppingSandbox(), SiteAt(target), new DynamicArray().RemoveAt, 5)); } public void AnchorStackAtATargetWithLibraryAndClientSubscribers_IsClaimedWhenFramesCanDrop() { var combined = (Func)Delegate.Combine(new Func("text".GetHashCode), new Func(ClientFactory)); var target = new Lazy(combined); CheckTrue(Run(NewDroppingSandbox(), SiteAt(target), new DynamicArray().RemoveAt, 5)); } public void AnchorStackAtATargetWithOnlyLibrarySubscribers_Propagates() { var target = new Lazy("text".GetHashCode); CheckThrow(typeof(ArgumentOutOfRangeException)); Run(NewDroppingSandbox(), SiteAt(target), new DynamicArray().RemoveAt, 5); } public void AnchorStackAtATargetWithNoSubscribers_Propagates() { CheckThrow(typeof(ArgumentOutOfRangeException)); Run(NewDroppingSandbox(), SiteAt(new object()), new DynamicArray().RemoveAt, 5); } public void AnchorStackWithNoTarget_Propagates() { CheckThrow(typeof(ArgumentOutOfRangeException)); Run(NewDroppingSandbox(), SiteAt(null), new DynamicArray().RemoveAt, 5); } public void SameAnchorStackAtTwoTargetsWithClientCode_MakesOneEntry() { ExceptionSandbox dropping = NewDroppingSandbox(); Run(dropping, SiteAt(new ClientTarget()), new DynamicArray().RemoveAt, 5); Run(dropping, SiteAt(new ClientTarget()), new DynamicArray().RemoveAt, 5); CheckEqual(1, dropping.Entries.Count); CheckEqual(2, dropping.Entries[0].Count); } public void DifferentAnchorCallersOfOneThrowAtATargetWithClientCode_MakeSeparateEntries() { ExceptionSandbox dropping = NewDroppingSandbox(); var target = new ClientTarget(); Run(dropping, SiteAt(target), new DynamicArray().RemoveAt, 5); Run(dropping, SiteAt(target), new DynamicArray().Insert, 5, 0); CheckEqual(2, dropping.Entries.Count); } } public class QuarantineTests : TestFixture { private ExceptionSandbox sandbox; private List<(ExceptionSite site, ExceptionEntry entry)> announced; public override void TestSetup() { sandbox = NewSandbox(); announced = new List<(ExceptionSite site, ExceptionEntry entry)>(); sandbox.SiteQuarantined += (site, entry) => announced.Add((site, entry)); } [IgnoreTest] private bool Fault(ExceptionSite site, bool quarantine = true) { try { Rethrow(new InvalidOperationException()); } catch (Exception exception) when (sandbox.Claims(exception, site, notes: null, quarantine)) { return true; } return false; } public void NothingFaulted_HasNoQuarantinedSites() { CheckEqual(0, sandbox.QuarantinedSites.Count); CheckFalse(sandbox.IsQuarantined(SiteA)); } public void ClaimWithQuarantine_QuarantinesAndSkipsTheSite() { Fault(SiteA); CheckTrue(sandbox.IsQuarantined(SiteA)); CheckTrue(sandbox.IsSkipped(SiteA)); CheckEqual(new[] { SiteA }, sandbox.QuarantinedSites.ToArray()); } public void ClaimWithoutQuarantine_LeavesTheSiteRunning() { Fault(SiteA, quarantine: false); CheckFalse(sandbox.IsQuarantined(SiteA)); CheckFalse(sandbox.IsSkipped(SiteA)); } public void ClaimWithQuarantine_LeavesOtherSitesRunning() { Fault(SiteA); CheckFalse(sandbox.IsSkipped(SiteB)); CheckFalse(sandbox.IsSkipped(new ExceptionSite(SiteA.Target, phase: 1))); } public void ExceptionThatPropagates_QuarantinesNothing() { bool propagated = false; try { try { new DynamicArray().RemoveAt(5); } catch (ArgumentOutOfRangeException exception) when ( sandbox.Claims(exception, SiteA, notes: null, quarantine: true)) { } } catch (ArgumentOutOfRangeException) { propagated = true; } CheckTrue(propagated); CheckFalse(sandbox.IsQuarantined(SiteA)); } public void Release_LetsTheSiteRunAndReportsItWasQuarantined() { Fault(SiteA); CheckTrue(sandbox.Release(SiteA)); CheckFalse(sandbox.IsQuarantined(SiteA)); CheckFalse(sandbox.IsSkipped(SiteA)); } public void ReleaseOfASiteThatIsNotQuarantined_ReportsNothingToDo() { CheckFalse(sandbox.Release(SiteA)); } public void ReleasedSiteThatFaultsAgain_IsQuarantinedAgain() { Fault(SiteA); sandbox.Release(SiteA); Fault(SiteA); CheckTrue(sandbox.IsSkipped(SiteA)); } public void IgnoringOrSnoozingTheEntry_DoesNotReleaseTheSite() { Fault(SiteA); ExceptionEntry entry = sandbox.Entries[0]; entry.Ignored = true; entry.Ignored = false; sandbox.Snooze(entry, TimeSpan.FromMilliseconds(10)); sandbox.Advance(TimeSpan.FromMilliseconds(20)); CheckTrue(sandbox.IsQuarantined(SiteA)); CheckTrue(sandbox.IsSkipped(SiteA)); } public void SiteWithAnActiveEntry_IsStillSkippedWhileQuarantined() { Hit(sandbox, SiteA); Fault(SiteA); CheckTrue(sandbox.IsSkipped(SiteA)); } public void Announcement_WaitsForTheNextAdvance() { Fault(SiteA); CheckEqual(0, announced.Count); sandbox.Advance(TimeSpan.Zero); CheckEqual(1, announced.Count); CheckEqual(SiteA, announced[0].site); CheckEqual(sandbox.Entries[0], announced[0].entry); } public void Announcement_IsOnceNoMatterHowOftenTheSiteFaults() { Fault(SiteA); Fault(SiteA); sandbox.Advance(TimeSpan.Zero); sandbox.Advance(TimeSpan.Zero); CheckEqual(1, announced.Count); } public void SeveralSites_AreAnnouncedInTheOrderTheyFaulted() { Fault(SiteB); Fault(SiteA); sandbox.Advance(TimeSpan.Zero); CheckEqual(new[] { SiteB, SiteA }, announced.Select(pair => pair.site).ToArray()); } public void ReleasedSiteThatFaultsAgain_IsAnnouncedAgain() { Fault(SiteA); sandbox.Advance(TimeSpan.Zero); sandbox.Release(SiteA); Fault(SiteA); sandbox.Advance(TimeSpan.Zero); CheckEqual(2, announced.Count); } public void ClaimWithoutQuarantine_IsNotAnnounced() { Fault(SiteA, quarantine: false); sandbox.Advance(TimeSpan.Zero); CheckEqual(0, announced.Count); } public void NoSubscriber_AdvanceStillClearsTheAnnouncement() { ExceptionSandbox quiet = NewSandbox(); try { Rethrow(new InvalidOperationException()); } catch (Exception exception) when (quiet.Claims(exception, SiteA, notes: null, quarantine: true)) { } quiet.Advance(TimeSpan.Zero); quiet.SiteQuarantined += (site, entry) => announced.Add((site, entry)); quiet.Advance(TimeSpan.Zero); CheckEqual(0, announced.Count); } } public class IsSkippedTests : TestFixture { private ExceptionSandbox sandbox; public override void TestSetup() { sandbox = NewSandbox(); } public void NothingEverThrown_IsNotSkipped() { CheckFalse(sandbox.IsSkipped(SiteA)); } public void SiteOfAnActiveEntry_IsNotSkipped() { Hit(sandbox, SiteA); CheckFalse(sandbox.IsSkipped(SiteA)); } public void SiteOfAnIgnoredEntry_IsSkipped() { Hit(sandbox, SiteA).Ignored = true; CheckTrue(sandbox.IsSkipped(SiteA)); } public void SiteOfASnoozedEntry_IsSkipped() { sandbox.Snooze(Hit(sandbox, SiteA), TimeSpan.FromMilliseconds(10)); sandbox.Advance(TimeSpan.FromMilliseconds(9)); CheckTrue(sandbox.IsSkipped(SiteA)); } public void SiteOfAnEntrySnoozedForever_StaysSkipped() { sandbox.Snooze(Hit(sandbox, SiteA), Duration.Forever); sandbox.Advance(Duration.MaxValue); CheckTrue(sandbox.IsSkipped(SiteA)); } public void SiteOfAnExpiredSnooze_IsNotSkipped() { sandbox.Snooze(Hit(sandbox, SiteA), TimeSpan.FromMilliseconds(10)); sandbox.Advance(TimeSpan.FromMilliseconds(10)); CheckFalse(sandbox.IsSkipped(SiteA)); } public void SameTargetInAnotherPhase_IsNotSkipped() { Hit(sandbox, SiteA).Ignored = true; CheckFalse(sandbox.IsSkipped(new ExceptionSite(SiteA.Target, phase: 1))); } public void SiteThatNeverThrew_IsNotSkippedWhileAnotherIs() { Hit(sandbox, SiteA).Ignored = true; CheckFalse(sandbox.IsSkipped(SiteB)); } public void SiteOfAnActiveEntry_IsNotSkippedWhileAnotherEntryIs() { Hit(sandbox, SiteA).Ignored = true; Hit(sandbox, SiteB); CheckFalse(sandbox.IsSkipped(SiteB)); } public void SiteWithAnIgnoredAndAnActiveEntry_IsNotSkipped() { Hit(sandbox, SiteA).Ignored = true; Hit(sandbox, SiteA); CheckFalse(sandbox.IsSkipped(SiteA)); } public void SiteWithEveryEntryIgnoredOrSnoozed_IsSkipped() { Hit(sandbox, SiteA).Ignored = true; sandbox.Snooze(Hit(sandbox, SiteA), TimeSpan.FromSeconds(1)); CheckTrue(sandbox.IsSkipped(SiteA)); } } } }