using System; using System.Collections.Generic; using System.Linq; using UnitTestSharp; namespace Amphora.UnitTests { public class ExceptionEntryTests : TestFixture { public static readonly ExceptionSite SiteA = ExceptionSandboxTests.SiteA; public static readonly ExceptionSite SiteB = ExceptionSandboxTests.SiteB; public static Dictionary Notes(string label, string value) { return new Dictionary { { label, value } }; } public class CtorTests : TestFixture { public void StampsTheFrameAndTimeAsBothFirstAndLast() { var entry = new ExceptionEntry(new InvalidOperationException(), frame: 2, time: TimeSpan.FromMilliseconds(20)); CheckEqual(2L, entry.FirstFrame); CheckEqual(2L, entry.LastFrame); CheckEqual(Duration.FromMilliseconds(20), entry.FirstTime); CheckEqual(Duration.FromMilliseconds(20), entry.LastTime); } public void KeepsTheException() { var thrown = new InvalidOperationException(); CheckEqual(thrown, new ExceptionEntry(thrown, frame: 0, time: TimeSpan.Zero).Exception); } public void StartsWithNoHits() { var entry = new ExceptionEntry(new InvalidOperationException(), frame: 0, time: TimeSpan.Zero); CheckEqual(0, entry.Count); CheckEqual(0, entry.Sites.Count); CheckEqual(0, entry.NotesOmitted); } public void StartsNeitherIgnoredNorSnoozed() { var entry = new ExceptionEntry(new InvalidOperationException(), frame: 0, time: TimeSpan.Zero); CheckFalse(entry.Ignored); CheckNull(entry.SnoozedUntil); } } public class RecordHitTests : TestFixture { private ExceptionEntry entry; public override void TestSetup() { entry = new ExceptionEntry(new InvalidOperationException(), frame: 0, time: TimeSpan.Zero); } public void Always_CountsTheHit() { entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero); CheckEqual(2, entry.Count); } public void Always_StampsTheFrameAndTimeAsLastOnly() { entry.RecordHit(SiteA, null, frame: 1, time: TimeSpan.FromMilliseconds(16)); CheckEqual(0L, entry.FirstFrame); CheckEqual(Duration.Zero, entry.FirstTime); CheckEqual(1L, entry.LastFrame); CheckEqual(Duration.FromMilliseconds(16), entry.LastTime); } public void NewSite_IsAddedWithItsNotesAndReportedAsNew() { CheckTrue(entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero)); CheckEqual(new[] { SiteA }, entry.Sites.ToArray()); CheckEqual(1, entry.NotesOf(SiteA).Count); CheckEqual("window", entry.NotesOf(SiteA).Single()["path"]); } public void RepeatedSiteAndNotes_AreNeitherAddedAgainNorReportedAsNew() { entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero); CheckFalse(entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero)); CheckEqual(new[] { SiteA }, entry.Sites.ToArray()); CheckEqual(1, entry.NotesOf(SiteA).Count); } public void RepeatedSiteUnderOtherNotes_KeepsBothButIsNotReportedAsNew() { entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero); CheckFalse(entry.RecordHit(SiteA, Notes("path", "dialog"), frame: 0, time: TimeSpan.Zero)); CheckEqualCommutative(new[] { "window", "dialog" }, entry.NotesOf(SiteA).Select(notes => notes["path"])); } public void SeveralSites_AreEachKeptOnce() { entry.RecordHit(SiteB, null, frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, null, frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteB, null, frame: 0, time: TimeSpan.Zero); CheckEqualCommutative(new[] { SiteA, SiteB }, entry.Sites); } public void NullNotes_AreKeptAsOneEmptySet() { entry.RecordHit(SiteA, null, frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, new Dictionary(), frame: 0, time: TimeSpan.Zero); CheckEqual(1, entry.NotesOf(SiteA).Count); CheckEqual(0, entry.NotesOf(SiteA).Single().Count); } public void NotesListedInAnotherOrder_AreTheSameNotes() { entry.RecordHit(SiteA, new Dictionary { { "area", "north" }, { "npc", "guard" } }, frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, new Dictionary { { "npc", "guard" }, { "area", "north" } }, frame: 0, time: TimeSpan.Zero); CheckEqual(1, entry.NotesOf(SiteA).Count); } public void NotesThatOnlyDifferInTheirValues_AreDifferentNotes() { entry.RecordHit(SiteA, Notes("npc", "guard"), frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, Notes("npc", "cook"), frame: 0, time: TimeSpan.Zero); CheckEqual(2, entry.NotesOf(SiteA).Count); } public void NotesThatRunTheirTextTogether_AreDifferentNotes() { entry.RecordHit(SiteA, new Dictionary { { "a", "bc" } }, frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, new Dictionary { { "ab", "c" } }, frame: 0, time: TimeSpan.Zero); CheckEqual(2, entry.NotesOf(SiteA).Count); } public void NullValue_ReadsAsEmptyText() { entry.RecordHit(SiteA, Notes("npc", null), frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, Notes("npc", ""), frame: 0, time: TimeSpan.Zero); CheckEqual(1, entry.NotesOf(SiteA).Count); } public void NotesTheCallerChangesLater_AreKeptAsTheyWere() { Dictionary notes = Notes("npc", "guard"); entry.RecordHit(SiteA, notes, frame: 0, time: TimeSpan.Zero); notes["npc"] = "cook"; CheckEqual("guard", entry.NotesOf(SiteA).Single()["npc"]); } public void MoreDistinctNotesThanTheMaximum_AreDroppedAndCounted() { for (int index = 0; index < ExceptionEntry.MaxNotesPerSite + 3; ++index) { entry.RecordHit(SiteA, Notes("npc", index.ToString()), frame: 0, time: TimeSpan.Zero); } CheckEqual(ExceptionEntry.MaxNotesPerSite, entry.NotesOf(SiteA).Count); CheckEqual(3, entry.NotesOmitted); CheckEqual(ExceptionEntry.MaxNotesPerSite + 3, entry.Count); } public void NotesAlreadyKeptWhenTheMaximumIsReached_AreNotCountedAsDropped() { for (int index = 0; index < ExceptionEntry.MaxNotesPerSite; ++index) { entry.RecordHit(SiteA, Notes("npc", index.ToString()), frame: 0, time: TimeSpan.Zero); } entry.RecordHit(SiteA, Notes("npc", "0"), frame: 0, time: TimeSpan.Zero); CheckEqual(0, entry.NotesOmitted); } public void TheMaximum_IsPerSite() { for (int index = 0; index < ExceptionEntry.MaxNotesPerSite; ++index) { entry.RecordHit(SiteA, Notes("npc", index.ToString()), frame: 0, time: TimeSpan.Zero); } entry.RecordHit(SiteB, Notes("npc", "0"), frame: 0, time: TimeSpan.Zero); CheckEqual(1, entry.NotesOf(SiteB).Count); } public void NewSiteWhoseNotesAreDropped_IsStillReportedAsNew() { for (int index = 0; index < ExceptionEntry.MaxNotesPerSite; ++index) { entry.RecordHit(SiteA, Notes("npc", index.ToString()), frame: 0, time: TimeSpan.Zero); } CheckFalse(entry.RecordHit(SiteA, Notes("npc", "extra"), frame: 0, time: TimeSpan.Zero)); CheckTrue(entry.RecordHit(SiteB, Notes("npc", "extra"), frame: 0, time: TimeSpan.Zero)); } } public class NotesOfTests : TestFixture { public void SiteThatNeverHit_HasNone() { var entry = new ExceptionEntry(new InvalidOperationException(), frame: 0, time: TimeSpan.Zero); entry.RecordHit(SiteA, Notes("path", "window"), frame: 0, time: TimeSpan.Zero); CheckEqual(0, entry.NotesOf(SiteB).Count); } } public class IsSkippingAtTests : TestFixture { private static readonly Duration Now = Duration.FromMilliseconds(16); private ExceptionEntry entry; public override void TestSetup() { entry = new ExceptionEntry(new InvalidOperationException(), frame: 0, time: TimeSpan.Zero); } public void NeitherIgnoredNorSnoozed_IsNotSkipping() { CheckFalse(entry.IsSkippingAt(Now)); } public void Ignored_IsSkipping() { entry.Ignored = true; CheckTrue(entry.IsSkippingAt(Now)); } public void SnoozedUntilLater_IsSkipping() { entry.SnoozedUntil = Now + Duration.FromTicks(1L); CheckTrue(entry.IsSkippingAt(Now)); } public void SnoozedUntilNow_IsNotSkipping() { entry.SnoozedUntil = Now; CheckFalse(entry.IsSkippingAt(Now)); } public void IgnoredWithAnExpiredSnooze_IsSkipping() { entry.Ignored = true; entry.SnoozedUntil = Duration.Zero; CheckTrue(entry.IsSkippingAt(Now)); } } } }