From fc0b74faccdf55ec6e0d03a4bed58823ff0adc03 Mon Sep 17 00:00:00 2001 From: Pr0metheus Date: Mon, 17 Aug 2026 18:48:48 +0200 Subject: [PATCH 1/2] IsMultiFile now follows the multiFile argument only - Exact production fix: IsMultiFile now follows the multiFile argument only. - Three constructor cases tested: - single file + false stays single-file - single file + true expands rollover siblings - array constructor remains multi-file - Monitoring truncation test confirms a single-file reader reports NewFile, not rollover. - Existing BufferShiftTest was left unchanged and run. - git diff --check reports no whitespace errors. --- .../Classes/Log/LogfileReader.cs | 4 +- .../LogfileReaderSingleFileMonitoringTests.cs | 99 ++++++++++++++++ .../LogfileReaderMultiFileFlagTests.cs | 111 ++++++++++++++++++ 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs create mode 100644 src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs diff --git a/src/LogExpert.Core/Classes/Log/LogfileReader.cs b/src/LogExpert.Core/Classes/Log/LogfileReader.cs index 9e2495d9..e520102a 100644 --- a/src/LogExpert.Core/Classes/Log/LogfileReader.cs +++ b/src/LogExpert.Core/Classes/Log/LogfileReader.cs @@ -150,7 +150,7 @@ private LogfileReader ( ILogFileInfo fileInfo = null; - IsMultiFile = multiFile || fileNames.Length == 1; + IsMultiFile = multiFile; _fileName = fileNames[0]; IEnumerable names = IsMultiFile @@ -1964,4 +1964,4 @@ PinHandle IBufferPinning.PinRange (int startLine, int endLine) } #endregion -} \ No newline at end of file +} diff --git a/src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs b/src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs new file mode 100644 index 00000000..259d0416 --- /dev/null +++ b/src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs @@ -0,0 +1,99 @@ +using System.Text; + +using LogExpert.Core.Classes.Log; +using LogExpert.Core.Entities; +using LogExpert.Core.Enums; +using LogExpert.Core.Interfaces; + +using NUnit.Framework; + +namespace LogExpert.Tests.Buffers; + +[TestFixture] +internal sealed class LogfileReaderSingleFileMonitoringTests +{ + private string _testDirectory = null!; + private string _logFile = null!; + + [SetUp] + public void SetUp () + { + _testDirectory = Path.Combine(Path.GetTempPath(), "LogExpertTests", Guid.NewGuid().ToString()); + _ = Directory.CreateDirectory(_testDirectory); + _logFile = Path.Combine(_testDirectory, "app.log"); + File.WriteAllLines(_logFile, Enumerable.Range(1, 100).Select(index => $"Line {index}"), Encoding.UTF8); + + _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); + } + + [TearDown] + public void TearDown () + { + if (Directory.Exists(_testDirectory)) + { + Directory.Delete(_testDirectory, recursive: true); + } + } + + [Test] + public void SingleFileReader_OnTruncation_ReportsNewFileNotRollover () + { + using var reporter = new RecordingProgressReporter(); + using var reader = new LogfileReader( + _logFile, + new EncodingOptions { Encoding = Encoding.UTF8 }, + multiFile: false, + bufferCount: 40, + linesPerBuffer: 50, + new MultiFileOptions(), + ReaderType.System, + PluginRegistry.PluginRegistry.Instance, + maximumLineLength: 500, + progressReporter: reporter); + + var rolloverReported = false; + reader.FileSizeChanged += (_, args) => rolloverReported |= args.IsRollover; + + reader.StartMonitoring(); + Assert.That(reporter.LoadingFinished.Wait(TimeSpan.FromSeconds(5)), Is.True, "Initial load did not finish"); + + File.WriteAllText(_logFile, "replacement\n", Encoding.UTF8); + + Assert.That(reporter.NewFileReported.Wait(TimeSpan.FromSeconds(5)), Is.True, "Truncation did not report a new file"); + Assert.That(rolloverReported, Is.False); + } + + private sealed class RecordingProgressReporter : ILoadProgressReporter + { + public ManualResetEventSlim LoadingFinished { get; } = new(false); + public ManualResetEventSlim NewFileReported { get; } = new(false); + + public void ReportProgress (string fileName, long position, long fileLength) + { + } + + public void ReportComplete (string fileName, long position, long fileLength) + { + } + + public void ReportNewFile (string fileName, long position, long fileLength) + { + NewFileReported.Set(); + } + + public void ReportLoadingStarted (string fileName) + { + } + + public void ReportLoadingFinished () + { + LoadingFinished.Set(); + } + + public void Dispose () + { + LoadingFinished.Dispose(); + NewFileReported.Dispose(); + } + } +} diff --git a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs new file mode 100644 index 00000000..06595f44 --- /dev/null +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -0,0 +1,111 @@ +using System.Text; + +using LogExpert.Core.Classes.Log; +using LogExpert.Core.Classes.Log.ProgressReporters; +using LogExpert.Core.Entities; +using LogExpert.Core.Enums; + +using NUnit.Framework; + +namespace LogExpert.Tests.StreamReaderTests; + +[TestFixture] +internal sealed class LogfileReaderMultiFileFlagTests +{ + private string _testDirectory = null!; + private string _logFile = null!; + + [SetUp] + public void SetUp () + { + _testDirectory = Path.Combine(Path.GetTempPath(), "LogExpertTests", Guid.NewGuid().ToString()); + _ = Directory.CreateDirectory(_testDirectory); + _logFile = Path.Combine(_testDirectory, "app.log"); + + WriteLines(_logFile, 10); + WriteLines(_logFile + ".1", 10); + + _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); + } + + [TearDown] + public void TearDown () + { + if (Directory.Exists(_testDirectory)) + { + Directory.Delete(_testDirectory, recursive: true); + } + } + + [Test] + public void SingleFileCtor_MultiFileFalse_DoesNotExpandRollover () + { + using var reader = CreateSingleFileReader(multiFile: false); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.False); + Assert.That(reader.GetLogFileInfoList(), Has.Count.EqualTo(1)); + Assert.That(reader.LineCount, Is.EqualTo(10)); + }); + } + + [Test] + public void SingleFileCtor_MultiFileTrue_ExpandsRollover () + { + using var reader = CreateSingleFileReader(multiFile: true); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.True); + Assert.That(reader.GetLogFileInfoList(), Has.Count.EqualTo(2)); + }); + } + + [Test] + public void MultiFileCtor_AlwaysMultiFile () + { + using var reader = new LogfileReader( + [_logFile], + new EncodingOptions { Encoding = Encoding.UTF8 }, + bufferCount: 40, + linesPerBuffer: 50, + new MultiFileOptions(), + ReaderType.System, + PluginRegistry.PluginRegistry.Instance, + maximumLineLength: 500, + progressReporter: NullProgressReporter.Instance); + + reader.ReadFiles(); + + Assert.Multiple(() => + { + Assert.That(reader.IsMultiFile, Is.True); + Assert.That(reader.GetLogFileInfoList(), Has.Count.EqualTo(2)); + }); + } + + private LogfileReader CreateSingleFileReader (bool multiFile) + { + return new LogfileReader( + _logFile, + new EncodingOptions { Encoding = Encoding.UTF8 }, + multiFile, + bufferCount: 40, + linesPerBuffer: 50, + new MultiFileOptions(), + ReaderType.System, + PluginRegistry.PluginRegistry.Instance, + maximumLineLength: 500, + progressReporter: NullProgressReporter.Instance); + } + + private static void WriteLines (string fileName, int lineCount) + { + File.WriteAllLines(fileName, Enumerable.Range(1, lineCount).Select(index => $"Line {index}"), Encoding.UTF8); + } +} From 0b31d411511363b96ffb9f9f1c91a2ef5c2d8751 Mon Sep 17 00:00:00 2001 From: Pr0metheus2 Date: Tue, 25 Aug 2026 15:20:17 +0200 Subject: [PATCH 2/2] Additional tests + changes --- src/LogExpert.Tests/LogExpert.Tests.csproj | 14 +++-- .../LogfileReaderMultiFileFlagTests.cs | 12 ++-- .../LogfileReaderSingleFileMonitoringTests.cs | 55 ++++++------------- src/LogExpert.Tests/TestData/app.log | 1 + src/LogExpert.Tests/TestData/app.log.1 | 1 + 5 files changed, 33 insertions(+), 50 deletions(-) rename src/LogExpert.Tests/{Buffers => StreamReaderTests}/LogfileReaderSingleFileMonitoringTests.cs (57%) create mode 100644 src/LogExpert.Tests/TestData/app.log create mode 100644 src/LogExpert.Tests/TestData/app.log.1 diff --git a/src/LogExpert.Tests/LogExpert.Tests.csproj b/src/LogExpert.Tests/LogExpert.Tests.csproj index d8bfb785..6747becb 100644 --- a/src/LogExpert.Tests/LogExpert.Tests.csproj +++ b/src/LogExpert.Tests/LogExpert.Tests.csproj @@ -101,10 +101,16 @@ PreserveNewest - - PreserveNewest - - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest diff --git a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs index 06595f44..5522313d 100644 --- a/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderMultiFileFlagTests.cs @@ -12,6 +12,8 @@ namespace LogExpert.Tests.StreamReaderTests; [TestFixture] internal sealed class LogfileReaderMultiFileFlagTests { + private static readonly string _testDataDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestData"); + private string _testDirectory = null!; private string _logFile = null!; @@ -22,8 +24,8 @@ public void SetUp () _ = Directory.CreateDirectory(_testDirectory); _logFile = Path.Combine(_testDirectory, "app.log"); - WriteLines(_logFile, 10); - WriteLines(_logFile + ".1", 10); + File.Copy(Path.Combine(_testDataDirectory, "app.log"), _logFile); + File.Copy(Path.Combine(_testDataDirectory, "app.log.1"), _logFile + ".1"); _ = PluginRegistry.PluginRegistry.Create(_testDirectory, 500); } @@ -48,7 +50,7 @@ public void SingleFileCtor_MultiFileFalse_DoesNotExpandRollover () { Assert.That(reader.IsMultiFile, Is.False); Assert.That(reader.GetLogFileInfoList(), Has.Count.EqualTo(1)); - Assert.That(reader.LineCount, Is.EqualTo(10)); + Assert.That(reader.LineCount, Is.EqualTo(1)); }); } @@ -104,8 +106,4 @@ private LogfileReader CreateSingleFileReader (bool multiFile) progressReporter: NullProgressReporter.Instance); } - private static void WriteLines (string fileName, int lineCount) - { - File.WriteAllLines(fileName, Enumerable.Range(1, lineCount).Select(index => $"Line {index}"), Encoding.UTF8); - } } diff --git a/src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderSingleFileMonitoringTests.cs similarity index 57% rename from src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs rename to src/LogExpert.Tests/StreamReaderTests/LogfileReaderSingleFileMonitoringTests.cs index 259d0416..cad47c6c 100644 --- a/src/LogExpert.Tests/Buffers/LogfileReaderSingleFileMonitoringTests.cs +++ b/src/LogExpert.Tests/StreamReaderTests/LogfileReaderSingleFileMonitoringTests.cs @@ -5,9 +5,11 @@ using LogExpert.Core.Enums; using LogExpert.Core.Interfaces; +using Moq; + using NUnit.Framework; -namespace LogExpert.Tests.Buffers; +namespace LogExpert.Tests.StreamReaderTests; [TestFixture] internal sealed class LogfileReaderSingleFileMonitoringTests @@ -38,7 +40,16 @@ public void TearDown () [Test] public void SingleFileReader_OnTruncation_ReportsNewFileNotRollover () { - using var reporter = new RecordingProgressReporter(); + using var loadingFinished = new ManualResetEventSlim(false); + using var newFileReported = new ManualResetEventSlim(false); + var progressReporterMock = new Mock(); + progressReporterMock + .Setup(reporter => reporter.ReportLoadingFinished()) + .Callback(() => loadingFinished.Set()); + progressReporterMock + .Setup(reporter => reporter.ReportNewFile(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => newFileReported.Set()); + using var reader = new LogfileReader( _logFile, new EncodingOptions { Encoding = Encoding.UTF8 }, @@ -49,51 +60,17 @@ public void SingleFileReader_OnTruncation_ReportsNewFileNotRollover () ReaderType.System, PluginRegistry.PluginRegistry.Instance, maximumLineLength: 500, - progressReporter: reporter); + progressReporter: progressReporterMock.Object); var rolloverReported = false; reader.FileSizeChanged += (_, args) => rolloverReported |= args.IsRollover; reader.StartMonitoring(); - Assert.That(reporter.LoadingFinished.Wait(TimeSpan.FromSeconds(5)), Is.True, "Initial load did not finish"); + Assert.That(loadingFinished.Wait(TimeSpan.FromSeconds(5)), Is.True, "Initial load did not finish"); File.WriteAllText(_logFile, "replacement\n", Encoding.UTF8); - Assert.That(reporter.NewFileReported.Wait(TimeSpan.FromSeconds(5)), Is.True, "Truncation did not report a new file"); + Assert.That(newFileReported.Wait(TimeSpan.FromSeconds(5)), Is.True, "Truncation did not report a new file"); Assert.That(rolloverReported, Is.False); } - - private sealed class RecordingProgressReporter : ILoadProgressReporter - { - public ManualResetEventSlim LoadingFinished { get; } = new(false); - public ManualResetEventSlim NewFileReported { get; } = new(false); - - public void ReportProgress (string fileName, long position, long fileLength) - { - } - - public void ReportComplete (string fileName, long position, long fileLength) - { - } - - public void ReportNewFile (string fileName, long position, long fileLength) - { - NewFileReported.Set(); - } - - public void ReportLoadingStarted (string fileName) - { - } - - public void ReportLoadingFinished () - { - LoadingFinished.Set(); - } - - public void Dispose () - { - LoadingFinished.Dispose(); - NewFileReported.Dispose(); - } - } } diff --git a/src/LogExpert.Tests/TestData/app.log b/src/LogExpert.Tests/TestData/app.log new file mode 100644 index 00000000..c69b11e7 --- /dev/null +++ b/src/LogExpert.Tests/TestData/app.log @@ -0,0 +1 @@ +app.log \ No newline at end of file diff --git a/src/LogExpert.Tests/TestData/app.log.1 b/src/LogExpert.Tests/TestData/app.log.1 new file mode 100644 index 00000000..e17d75d3 --- /dev/null +++ b/src/LogExpert.Tests/TestData/app.log.1 @@ -0,0 +1 @@ +app.log.1 \ No newline at end of file