public void TestAppendTwoSourcesWrongOrderSeparateChangesPartialInvalidation() { var source1 = new InMemoryLogSource(); source1.AddEntry("A", LevelFlags.Other, new DateTime(2019, 5, 28, 00, 34, 0)); source1.AddEntry("C", LevelFlags.Other, new DateTime(2019, 5, 28, 00, 36, 0)); var source2 = new InMemoryLogSource(); source2.AddEntry("B", LevelFlags.Other, new DateTime(2019, 5, 28, 00, 35, 0)); source2.AddEntry("D", LevelFlags.Other, new DateTime(2019, 5, 28, 00, 37, 0)); var index = new MergedLogSourceIndex(source1, source2); var changes = index.Process(new MergedLogSourcePendingModification(source1, LogSourceModification.Appended(0, 2))); changes.Should().Equal(new object[] { LogSourceModification.Appended(0, 2) }); changes = index.Process(new MergedLogSourcePendingModification(source2, LogSourceModification.Appended(0, 2))); changes.Should().Equal(new object[] { LogSourceModification.Removed(1, 1), LogSourceModification.Appended(1, 3) }); }
public void TestDeadlockWhenRemovingAnActiveDataSource() { var logFile = new Mock <ILogSource>(); var dataSource = new Mock <IDataSource>(); dataSource.Setup(x => x.UnfilteredLogSource).Returns(logFile.Object); var collection = new BookmarkCollection(_bookmarks.Object, TimeSpan.Zero); logFile.Setup(x => x.RemoveListener(It.IsAny <ILogSourceListener>())) .Callback((ILogSourceListener unused) => { // In order to produce the deadlock, we have to simulate what's happening in reality. // Any ILogFile implementation will hold a lock both while invoking listeners and while // Removing them. Waiting on the OnLogFileModified() call simulates exactly that... var task = new Task(() => { collection.OnLogFileModified(logFile.Object, LogSourceModification.Removed(0, 10)); }, TaskCreationOptions.LongRunning); task.Start(); task.Wait(); }); collection.AddDataSource(dataSource.Object); var removeTask = Task.Factory.StartNew(() => collection.RemoveDataSource(dataSource.Object)); removeTask.Wait(TimeSpan.FromSeconds(1)).Should().BeTrue("because RemoveDataSource should not block at all"); logFile.Verify(x => x.RemoveListener(collection), Times.Once); }
public void TestTail_WriteTwoLines_Changes2() { var encoding = Encoding.UTF8; var fileName = GetUniqueNonExistingFileName(); var line1 = "The sky crawlers"; var line2 = "is awesome!"; using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new StreamWriter(stream, encoding)) using (var logFile = Create(fileName, encoding)) { var changes = AddListener(logFile, 1000); changes.Should().Equal(new object[] { LogSourceModification.Reset() }); writer.Write(line1); writer.Flush(); _taskScheduler.RunOnce(); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1) }, "because the file consists of one line"); writer.Write("\n" + line2); writer.Flush(); _taskScheduler.RunOnce(); //changes.Should().Equal(new object[] {LogSourceModification.Reset(), new LogFileSection(0, 1), new LogFileSection(1, 1)}); // The current behavior won't cause wrong behavior in upstream listeners, but it will cause them unnecessary work. changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Removed(0, 1), LogSourceModification.Appended(0, 2) }); } }
public void TestReadOneLine4() { _streamWriter.Write("A"); _streamWriter.Flush(); _taskScheduler.RunOnce(); _streamWriter.Write("B"); _streamWriter.Flush(); _taskScheduler.RunOnce(); _streamWriter.Write("C"); _streamWriter.Flush(); _taskScheduler.RunOnce(); _modifications.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Removed(0, 1), LogSourceModification.Appended(0, 1), LogSourceModification.Removed(0, 1), LogSourceModification.Appended(0, 1) }, "because the log file should've sent invalidations for the 2nd and 3rd read (because the same line was modified)"); _source.GetProperty(Properties.LogEntryCount).Should().Be(1); var entry = _source.GetEntry(0); entry.Index.Should().Be(0); entry.LogEntryIndex.Should().Be(0); entry.RawContent.Should().Be("ABC"); }
public void TestEquality() { var appendModification = LogSourceModification.Appended(10, 41); var equalAppendModification = LogSourceModification.Appended(10, 41); var anotherAppendModification = LogSourceModification.Appended(9, 41); appendModification.Should().Be(equalAppendModification); equalAppendModification.Should().Be(appendModification); appendModification.Should().NotBe(anotherAppendModification); anotherAppendModification.Should().NotBe(equalAppendModification); var removedModification = LogSourceModification.Removed(10, 41); appendModification.Should().NotBe(removedModification); equalAppendModification.Should().NotBe(removedModification); anotherAppendModification.Should().NotBe(removedModification); var anotherRemovedModification = LogSourceModification.Removed(9, 41); removedModification.Should().NotBe(anotherRemovedModification); anotherRemovedModification.Should().NotBe(anotherAppendModification); appendModification.Should().NotBe(LogSourceModification.Reset()); appendModification.Should().NotBe(LogSourceModification.PropertiesChanged()); removedModification.Should().NotBe(LogSourceModification.Reset()); removedModification.Should().NotBe(LogSourceModification.PropertiesChanged()); }
public void TestSplitInvalidateGreaterThanThreshold([Values(2, 99, 100)] int count) { int maxCount = count - 1; var section = LogSourceModification.Removed(new LogLineIndex(42), count); section.Split(maxCount).Should().Equal(new[] { section }, "because invalidations are NEVER split up"); }
public void TestInvalidateThenAppendWithGap() { var changes = new MergedLogSourceChanges(10); changes.RemoveFrom(5); changes.Append(7, 10); changes.Sections.Should().Equal(new object[] { LogSourceModification.Removed(5, 5), LogSourceModification.Appended(5, 12) }); }
public void TestInvalidate() { var changes = new MergedLogSourceChanges(42); changes.RemoveFrom(10); changes.Sections.Should().Equal(new object[] { LogSourceModification.Removed(10, 32) }); changes.TryGetFirstRemovedIndex(out var index).Should().BeTrue(); index.Should().Be(new LogLineIndex(10)); }
public void TestInvalidate1() { var notifier = new LogSourceListenerNotifier(_logFile.Object, _listener.Object, TimeSpan.Zero, 1); notifier.OnRead(1); notifier.Remove(0, 1); _modifications.Should().Equal(new[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Removed(0, 1) }); notifier.LastNumberOfLines.Should().Be(0); }
public void Remove(int firstIndex, int count) { int lastIndex = Math.Min(firstIndex + count, _lastNumberOfLines); int invalidateCount = lastIndex - firstIndex; // When the start index of the invalidation is greater than the last reported index // then this means that our listeners haven't even gotten the change yet and thus // they don't need to be notified of the invalidation either. if (invalidateCount > 0) { var section = new LogSourceSection(firstIndex, invalidateCount); _listener.OnLogFileModified(_logSource, LogSourceModification.Removed(section)); _lastNumberOfLines = firstIndex; } }
public void TestRemove() { var modification = LogSourceModification.Removed(9, 22); modification.IsRemoved(out var removedSection).Should().BeTrue(); removedSection.Should().Equal(new LogSourceSection(9, 22)); modification.ToString().Should().Be("Removed [#9, #22]"); modification.IsAppended(out var appendedSection).Should().BeFalse(); appendedSection.Should().Equal(new LogSourceSection()); modification.IsReset().Should().BeFalse(); modification.IsPropertiesChanged().Should().BeFalse(); }
public void TestInvalidateTwiceSuperfluous() { var changes = new MergedLogSourceChanges(13); changes.RemoveFrom(5); changes.RemoveFrom(7); changes.Sections.Should().Equal(new object[] { LogSourceModification.Removed(5, 8) }); changes.TryGetFirstRemovedIndex(out var index).Should().BeTrue(); index.Should().Be(new LogLineIndex(5)); }
public void TestInvalidateThenAppend() { var changes = new MergedLogSourceChanges(10); changes.RemoveFrom(5); changes.Append(5, 6); changes.Sections.Should().Equal(new object[] { LogSourceModification.Removed(5, 5), LogSourceModification.Appended(5, 6) }); changes.TryGetFirstRemovedIndex(out var index).Should().BeTrue(); index.Should().Be(new LogLineIndex(5)); }
public void TestInvalidate2() { var notifier = new LogSourceListenerNotifier(_logFile.Object, _listener.Object, TimeSpan.FromSeconds(1), 10); notifier.OnRead(10); notifier.OnRead(12); notifier.Remove(0, 12); _modifications.Should().Equal(new[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 10), LogSourceModification.Removed(0, 10) }, "Because the notifier should've reported only the first 10 changes and therefore Invalidate() only had to invalidate those 10 changes" ); notifier.LastNumberOfLines.Should().Be(0); }
public void RemoveFrom(LogLineIndex firstInvalidIndex) { if (firstInvalidIndex >= _count) { Log.WarnFormat("Ignoring invalidation from index '{0}' onwards because nothing has been appended there!", firstInvalidIndex); return; } if (firstInvalidIndex >= _initialCount) { // We do not need to add an invalidation, rather we can simply clamp an existing previous append if (firstInvalidIndex > 0) { var previous = _modifications[_modifications.Count - 1]; if (previous.IsAppended(out var appendedSection)) { var gap = appendedSection.Index + appendedSection.Count - firstInvalidIndex; if (gap > 0) { appendedSection.Count -= gap; _modifications[_modifications.Count - 1] = LogSourceModification.Appended(appendedSection); } } } } else { var invalidationCount = _count - firstInvalidIndex; if (_removeIndex != -1) { var invalidation = _modifications[_removeIndex]; invalidation.IsRemoved(out var removedSection); if (removedSection.Index <= firstInvalidIndex) { return; //< Nothing to do } _modifications[_removeIndex] = LogSourceModification.Removed(firstInvalidIndex, invalidationCount); } else { _removeIndex = _modifications.Count; _modifications.Add(LogSourceModification.Removed(firstInvalidIndex, invalidationCount)); } } }
public void TestTwoSourcesAppendBothInvalidateOne() { var source1 = new InMemoryLogSource(); var source2 = new InMemoryLogSource(); source2.AddEntry("a", LevelFlags.Warning, new DateTime(2021, 2, 28, 23, 00, 0)); source2.AddEntry("b", LevelFlags.Warning, new DateTime(2021, 2, 28, 23, 01, 0)); source1.AddEntry("c", LevelFlags.Info, new DateTime(2021, 2, 28, 23, 02, 0)); source2.AddEntry("d", LevelFlags.Error, new DateTime(2021, 2, 28, 23, 03, 0)); var index = new MergedLogSourceIndex(source1, source2); var changes = index.Process( new MergedLogSourcePendingModification(source1, LogSourceModification.Appended(0, 1)), new MergedLogSourcePendingModification(source2, LogSourceModification.Appended(0, 3)) ); changes.Should().Equal(new object[] { LogSourceModification.Appended(0, 4) }); index.Count.Should().Be(4); index[0].SourceId.Should().Be(1); index[0].MergedLogEntryIndex.Should().Be(0); index[1].SourceId.Should().Be(1); index[1].MergedLogEntryIndex.Should().Be(1); index[2].SourceId.Should().Be(0); index[2].MergedLogEntryIndex.Should().Be(2); index[3].SourceId.Should().Be(1); index[3].MergedLogEntryIndex.Should().Be(3); changes = index.Process(new MergedLogSourcePendingModification(source2, LogSourceModification.Removed(1, 2))); changes.Should().Equal(new object[] { LogSourceModification.Removed(1, 3), LogSourceModification.Appended(1, 1) }); index.Count.Should().Be(2); index[0].SourceId.Should().Be(1); index[1].SourceId.Should().Be(0); }
public void TestListen3() { using (var proxy = new LogSourceProxy(_taskScheduler, TimeSpan.Zero, _logFile.Object)) { proxy.AddListener(_listener.Object, TimeSpan.Zero, 1000); _listeners.OnRead(500); _listeners.Remove(400, 100); _listeners.OnRead(550); _taskScheduler.RunOnce(); _modifications.Should().Equal(new[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 500), LogSourceModification.Removed(400, 100), LogSourceModification.Appended(400, 150) }); } }
public void TestAppendTwoSourcesWrongOrderSeparateChangesFullInvalidation() { var source1 = new InMemoryLogSource(); source1.AddEntry("B", LevelFlags.Other, new DateTime(2019, 5, 27, 23, 10, 0)); var source2 = new InMemoryLogSource(); source2.AddEntry("A", LevelFlags.Other, new DateTime(2019, 5, 27, 23, 09, 0)); var index = new MergedLogSourceIndex(source1, source2); var changes = index.Process(new MergedLogSourcePendingModification(source1, LogSourceModification.Appended(0, 1))); changes.Should().Equal(new object[] { LogSourceModification.Appended(0, 1) }); changes = index.Process(new MergedLogSourcePendingModification(source2, LogSourceModification.Appended(0, 1))); changes.Should().Equal(new object[] { LogSourceModification.Removed(0, 1), LogSourceModification.Appended(0, 2) }); var indices = index.Get(new LogSourceSection(0, 2)); indices.Count.Should().Be(2); indices[0].SourceId.Should().Be(1); indices[0].SourceLineIndex.Should().Be(0); indices[0].OriginalLogEntryIndex.Should().Be(0); indices[0].MergedLogEntryIndex.Should().Be(0); indices[0].Timestamp.Should().Be(new DateTime(2019, 5, 27, 23, 9, 0)); indices[1].SourceId.Should().Be(0); indices[1].SourceLineIndex.Should().Be(0); indices[1].OriginalLogEntryIndex.Should().Be(0); indices[1].MergedLogEntryIndex.Should().Be(1); indices[1].Timestamp.Should().Be(new DateTime(2019, 5, 27, 23, 10, 0)); }
public void TestTail_WriteTwoLines_Changes1() { var encoding = Encoding.UTF8; var fileName = GetUniqueNonExistingFileName(); var line1 = "The sky crawlers"; var line2 = "is awesome!"; using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new StreamWriter(stream, encoding)) using (var logFile = Create(fileName, encoding)) { var changes = AddListener(logFile, 1000); changes.Should().Equal(new object[] { LogSourceModification.Reset() }); writer.Write(line1 + "\r\n"); writer.Flush(); _taskScheduler.RunOnce(); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Appended(1, 1) }, "because the file consists of two lines, one being totally empty"); writer.Write(line2 + "\r\n"); writer.Flush(); _taskScheduler.RunOnce(); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Appended(1, 1), LogSourceModification.Removed(1, 1), LogSourceModification.Appended(1, 2) }); } }
public void TestTail_WriteOneLineTwoFlushes_Changes3() { var encoding = Encoding.UTF8; var fileName = GetUniqueNonExistingFileName(); var linePart1 = "The sky"; var linePart2 = " Crawlers"; using (var stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var writer = new StreamWriter(stream, encoding)) using (var logFile = Create(fileName, encoding)) { var changes = AddListener(logFile, 1000); changes.Should().Equal(new object[] { LogSourceModification.Reset() }); writer.Write(linePart1); writer.Flush(); _taskScheduler.RunOnce(); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1) }, "because the file consists of one line"); writer.Write(linePart2); writer.Flush(); _taskScheduler.RunOnce(); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 1), LogSourceModification.Removed(0, 1), LogSourceModification.Appended(0, 1) }); } }
public void TestMergeResetOneSource() { var source1 = new InMemoryLogSource(); var source2 = new InMemoryLogSource(); var merged = new MergedLogSource(_taskScheduler, TimeSpan.Zero, source1, source2); var entries = Listen(merged); var changes = ListenToChanges(merged, 100); source2.AddEntry("a", LevelFlags.Warning, new DateTime(2021, 2, 28, 22, 15, 0)); source1.AddEntry("b", LevelFlags.Info, new DateTime(2021, 2, 28, 22, 16, 0)); source2.AddEntry("c", LevelFlags.Error, new DateTime(2021, 2, 28, 22, 17, 0)); _taskScheduler.RunOnce(); merged.GetProperty(Core.Properties.PercentageProcessed).Should().Be(Percentage.HundredPercent); entries.Count.Should().Be(3); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 3) }); source1.Clear(); _taskScheduler.RunOnce(); entries.Count.Should().Be(2, "because the one entry from source should have been removed from the merged source"); changes.Should().Equal(new object[] { LogSourceModification.Reset(), LogSourceModification.Appended(0, 3), LogSourceModification.Removed(1, 2), LogSourceModification.Appended(1, 1) }); }