This is an automated email from the ASF dual-hosted git repository.
freeandnil pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git
The following commit(s) were added to refs/heads/master by this push:
new 2d315a75 Add tests for regression in file rolling and fix bug (#257)
2d315a75 is described below
commit 2d315a752328a66a3396eb8d0d300694ea86225c
Author: Grzegorz Dziadkiewicz <[email protected]>
AuthorDate: Wed Jul 2 21:53:44 2025 +0200
Add tests for regression in file rolling and fix bug (#257)
* Add draft of tests for file rolling regression
* Add additional tests, fix the restart bug and roll back test-focused
changes from #232
* Add test for no name in File case
* Adjust paths to work on Linux and macOS
* Clean up and unify
* Improve style
* Add comments
---
.../Appender/RollingFileAppenderWithDirTest.cs | 33 ---
.../Integration/Log4NetIntegrationTests.cs | 244 +++++++++++++++++++++
.../Integration/log4net.integration.basic.config | 14 ++
.../Integration/log4net.maxsizeroll.config | 25 +++
.../Integration/log4net.maxsizeroll_date.config | 25 +++
.../Integration/log4net.no_file_name.config | 23 ++
src/log4net.Tests/Integration/log4net.roll.config | 31 +++
src/log4net.Tests/log4net.Tests.csproj | 20 ++
src/log4net/Appender/RollingFileAppender.cs | 4 +-
9 files changed, 383 insertions(+), 36 deletions(-)
diff --git a/src/log4net.Tests/Appender/RollingFileAppenderWithDirTest.cs
b/src/log4net.Tests/Appender/RollingFileAppenderWithDirTest.cs
deleted file mode 100644
index 04731969..00000000
--- a/src/log4net.Tests/Appender/RollingFileAppenderWithDirTest.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-#region Apache License
-
-//
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements. See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to you under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License. You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#endregion
-
-using System.IO;
-using NUnit.Framework;
-
-namespace log4net.Tests.Appender;
-
-[TestFixture]
-public class RollingFileAppenderWithDirTest : RollingFileAppenderTest {
- public RollingFileAppenderWithDirTest()
- {
- base.FileName = Path.Combine(@".\dir\", base.FileName);
- }
-}
diff --git a/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs
b/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs
new file mode 100644
index 00000000..bf29b381
--- /dev/null
+++ b/src/log4net.Tests/Integration/Log4NetIntegrationTests.cs
@@ -0,0 +1,244 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using log4net;
+using log4net.Config;
+using NUnit.Framework;
+
+namespace log4net.Tests.Integration
+{
+ /// <summary>
+ /// Integration tests for log4net logging scenarios, including file and
directory creation, log rolling, and configuration behaviors.
+ /// <para>
+ /// Expectations for test log files and directories:
+ /// <list type="bullet">
+ /// <item>Test log files may be placed directly in the test directory with
names starting with <c>TestLogFilePrefix</c>, or inside directories whose names
start with <c>TestLogDirectoryPrefix</c> and may have arbitrary file
names.</item>
+ /// <item>Each test run starts with a clean environment: any files or
directories from previous runs matching these prefixes are deleted in
<c>SetUp</c>.</item>
+ /// <item>Tests assert the existence, count, and content of log files and
directories as part of their validation.</item>
+ /// </list>
+ /// </para>
+ /// </summary>
+ [TestFixture]
+ public class Log4NetIntegrationTests
+ {
+ /// <summary>
+ /// Runs before each test to remove log files and directories from
previous test runs.
+ /// Ensures a clean environment for integration tests.
+ /// </summary>
+ [SetUp]
+ public void SetUp()
+ {
+ RemoveFilesFromPreviousRuns();
+ RemoveDirsFromPreviousRuns();
+ }
+
+ /// <summary>
+ /// Tests basic log4net functionality - writing log entries to a file.
+ /// This is the most fundamental integration test verifying that:
+ /// 1. Log4net can be configured from XML config
+ /// 2. Log entries are written to the specified file
+ /// 3. Content is preserved exactly as logged
+ /// Catch: Validates end-to-end logging pipeline works correctly.
+ /// </summary>
+ [Test]
+ public void Log4Net_WritesLogFile_AndContentIsCorrect()
+ {
+ var (log, repo) = ArrangeLogger("log4net.integration.basic.config");
+ log.Info("Hello integration test");
+ log.Error("This is an error");
+ repo.Shutdown();
+
+ // Assert: log file exists and contains expected content
+ string[] logLines =
File.ReadAllLines("integrationTestLogFile_integration.log");
+ Assert.That(logLines.Length, Is.EqualTo(2));
+ Assert.That(logLines[0], Does.Contain("Hello integration test"));
+ Assert.That(logLines[1], Does.Contain("This is an error"));
+ }
+
+ /// <summary>
+ /// Tests log4net's append behavior across multiple logger instances with
repository shutdown/restart cycles.
+ /// Verifies that:
+ /// 1. Log entries accumulate correctly when repeatedly creating and
shutting down logger repositories
+ /// 2. File append mode works properly across repository restarts
+ /// 3. No log entries are lost during the restart process
+ /// Catch: This tests persistence and append behavior - critical for
applications that restart logging frequently.
+ /// </summary>
+ [Test]
+ public void Log4Net_WritesLogFile_AndContentIsCorrectAfterRestart()
+ {
+ for (int i = 0; i < 10; i++)
+ {
+ var (log, repo) =
ArrangeLogger("log4net.integration.basic.config");
+ log.Info("Hello integration test");
+ log.Error("This is an error");
+ repo.Shutdown();
+ }
+ // Assert: log file exists and contains expected content
+ string[] logLines =
File.ReadAllLines("integrationTestLogFile_integration.log");
+ Assert.That(logLines.Length, Is.EqualTo(20));
+ for (int i = 0; i < 10; i++)
+ {
+ Assert.That(logLines[i * 2], Does.Contain("Hello integration
test"));
+ Assert.That(logLines[i * 2 + 1], Does.Contain("This is an error"));
+ }
+ }
+
+ /// <summary>
+ /// Tests log4net's file rolling behavior with no append mode enabled.
+ /// This test verifies that:
+ /// 1. Multiple logger instances generate multiple rolled log files
+ /// 2. Rolling configuration works correctly across repository restarts
+ /// 3. Files are rolled according to the configuration policy
+ /// Catch: Tests rolling without append - ensuring each restart creates
new files rather than appending to existing ones.
+ /// The expected file count (13) includes the current log file plus rolled
files from previous iterations.
+ /// </summary>
+ [Test]
+ public void
Log4Net_WritesLogFile_WithRollAndNoAppend_AndContentIsCorrectAfterRestart()
+ {
+ for (int i = 0; i < 20; i++)
+ {
+ var (log, repo) = ArrangeLogger("log4net.roll.config");
+ for (int j = 0; j < 10; ++j)
+ {
+ log.Info($"Hello, log4net! {i} {j}");
+ }
+ repo.Shutdown();
+ }
+ // Assert: log file exists and contains expected content
+ string[] logFiles = Directory.GetFiles("integrationTestLogDir_roll");
+ Assert.That(logFiles.Length, Is.EqualTo(12 + 1));
+ }
+
+ /// <summary>
+ /// Tests log4net's file rolling behavior based on maximum file size.
+ /// This test verifies that:
+ /// 1. Log files are rolled when they exceed the configured maximum size
+ /// 2. The correct number of backup files are maintained
+ /// 3. Each file remains within the size limit (10KB + small buffer for
overhead)
+ /// Catch: Tests size-based rolling policy - critical for preventing log
files from growing indefinitely.
+ /// Expected: 1 current file + 3 backup files = 4 total files.
+ /// </summary>
+ [Test]
+ public void Log4Net_WritesLogFile_WithMaxSizeRoll_Config_Works()
+ {
+ string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory,
"integrationTestLogDir_maxsizeroll");
+ if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
+ Directory.CreateDirectory(logDir);
+ var (log, repo) = ArrangeLogger("log4net.maxsizeroll.config");
+ for (int i = 0; i < 1000; ++i)
+ {
+ log.Info($"Log entry {i}");
+ }
+ repo.Shutdown();
+ // Assert: rolled files exist
+ string[] logFiles = Directory.GetFiles(logDir, "*.log");
+ Assert.That(logFiles.Length, Is.EqualTo(4)); // 1 current + 3 backups
+ // Optionally, check that each file is <= 10KB
+ foreach (var file in logFiles)
+ Assert.That(new FileInfo(file).Length, Is.LessThanOrEqualTo(10 *
1024 + 100));
+ }
+
+ /// <summary>
+ /// Tests log4net's composite rolling behavior based on both date and file
size.
+ /// This test verifies that:
+ /// 1. Log files are rolled when they exceed the configured maximum size
+ /// 2. Log files are also rolled based on date/time patterns
+ /// 3. The combination of date and size rolling creates multiple files
grouped by date
+ /// 4. Each date group maintains its own rolling sequence
+ /// Catch: Tests composite rolling policy - ensures both date and size
triggers work together correctly.
+ /// Expected: Multiple files grouped by date, with size-based rolling
within each date group.
+ /// </summary>
+ [Test]
+ public void Log4Net_WritesLogFile_WithDateAndSizeRoll_Config_Works()
+ {
+ string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory,
"integrationTestLogDir_maxsizerolldate");
+ if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
+ Directory.CreateDirectory(logDir);
+ var (log, repo) = ArrangeLogger("log4net.maxsizeroll_date.config");
+ // Write enough lines to trigger rolling by size and date
+ for (int i = 1; i < 10000; ++i)
+ {
+ log.Debug($"DateRoll entry {i}");
+ if (i % 5000 == 0)
System.Threading.Thread.Sleep(TimeSpan.FromMinutes(1)); // allow time for date
to change if needed
+ }
+ repo.Shutdown();
+ // Assert: rolled files exist (date+size pattern)
+ string[] logFiles = Directory.GetFiles(logDir, "*.log");
+ Assert.That(logFiles.Length, Is.EqualTo(8));
+ // Group files by date part in the filename (yyyy-MM-dd-mm)
+ var dateGroups = logFiles
+ .Select(f => Path.GetFileNameWithoutExtension(f))
+ .Select(name => name.Split('.').First())
+ .GroupBy(date => date)
+ .ToDictionary(g => g.Key, g => g.Count());
+ // Assert that at least one group exists and print group counts
+ Assert.That(dateGroups.Count, Is.EqualTo(2));
+ foreach (var group in dateGroups)
+ {
+ TestContext.Out.WriteLine($"Date group: {group.Key}, file count:
{group.Value}");
+ }
+ }
+
+ /// <summary>
+ /// Tests log4net's automatic file naming behavior when no explicit
filename is configured.
+ /// This test verifies that:
+ /// 1. Log4net can automatically generate file names based on date patterns
+ /// 2. Only one file is created when no explicit file name is provided
+ /// 3. The generated file name follows the expected date format
(yyyy-MM-dd.log)
+ /// Catch: Tests the framework's ability to handle missing file name
configuration gracefully.
+ /// Expected: A single log file with name matching today's date pattern.
+ /// </summary>
+ [Test]
+ public void Log4Net_ConfigWithoutFileName_CreatesOneFile()
+ {
+ string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory,
"integrationTestLogDir_no_file_name");
+ if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
+ Directory.CreateDirectory(logDir);
+ var (log, repo) = ArrangeLogger("log4net.no_file_name.config");
+ log.Info("Test entry with no file name");
+ repo.Shutdown();
+ // Assert: exactly one log file was created in the directory
+ var files = Directory.GetFiles(logDir, "*",
SearchOption.AllDirectories);
+ Assert.That(files.Length, Is.EqualTo(1), "Should create exactly one
log file");
+ var fileName = Path.GetFileName(files[0]);
+ TestContext.Out.WriteLine($"Created file: {fileName}");
+ // Assert the file name matches the date pattern yyyy-MM-dd.log
+ string todayPattern = DateTime.Now.ToString("yyyy-MM-dd") + ".log";
+ Assert.That(fileName, Is.EqualTo(todayPattern), $"File name should
match pattern: {todayPattern}");
+ }
+
+ private const string TestLogFilePrefix = "integrationTestLogFile";
+ private const string TestLogDirectoryPrefix = "integrationTestLogDir";
+
+ private static void RemoveDirsFromPreviousRuns()
+ {
+ List<string> directoriesFromPreviousRuns =
+
Directory.EnumerateDirectories(TestContext.CurrentContext.TestDirectory)
+ .Where(dirPath =>
Path.GetFileName(dirPath).StartsWith(TestLogDirectoryPrefix,
StringComparison.InvariantCultureIgnoreCase))
+ .ToList();
+ directoriesFromPreviousRuns.ForEach(d => Directory.Delete(d, true));
+ }
+
+ private static void RemoveFilesFromPreviousRuns()
+ {
+ List<string> filesFromPreviousRuns =
+
Directory.EnumerateFiles(TestContext.CurrentContext.TestDirectory)
+ .Where(filePath =>
Path.GetFileName(filePath).StartsWith(TestLogFilePrefix,
StringComparison.InvariantCultureIgnoreCase))
+ .ToList();
+ filesFromPreviousRuns.ForEach(File.Delete);
+ }
+
+ private static string GetConfigPath(string configFile)
+ => Path.Combine(TestContext.CurrentContext.TestDirectory,
"Integration", configFile);
+
+ private static (ILog log, log4net.Repository.ILoggerRepository repo)
ArrangeLogger(string configFile)
+ {
+ string configPath = GetConfigPath(configFile);
+ var repo = LogManager.CreateRepository(Guid.NewGuid().ToString());
+ XmlConfigurator.Configure(repo, new FileInfo(configPath));
+ var log = LogManager.GetLogger(repo.Name, "IntegrationTestLogger");
+ return (log, repo);
+ }
+ }
+}
diff --git a/src/log4net.Tests/Integration/log4net.integration.basic.config
b/src/log4net.Tests/Integration/log4net.integration.basic.config
new file mode 100644
index 00000000..d8264276
--- /dev/null
+++ b/src/log4net.Tests/Integration/log4net.integration.basic.config
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<log4net>
+ <appender name="FileAppender" type="log4net.Appender.FileAppender,log4net">
+ <file value="integrationTestLogFile_integration.log" />
+ <appendToFile value="true" />
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%date [%thread] %-5level %logger -
%message%newline" />
+ </layout>
+ </appender>
+ <root>
+ <level value="DEBUG" />
+ <appender-ref ref="FileAppender" />
+ </root>
+</log4net>
diff --git a/src/log4net.Tests/Integration/log4net.maxsizeroll.config
b/src/log4net.Tests/Integration/log4net.maxsizeroll.config
new file mode 100644
index 00000000..7884b80a
--- /dev/null
+++ b/src/log4net.Tests/Integration/log4net.maxsizeroll.config
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <log4net>
+ <appender name="LogFileAppender"
type="log4net.Appender.RollingFileAppender">
+ <param name="File" value="integrationTestLogDir_maxsizeroll/.log" />
+ <param name="AppendToFile" value="true" />
+ <param name="RollingStyle" value="Composite" />
+ <param name="DatePattern" value="yyyy-MM-dd" />
+ <param name="MaximumFileSize" value="10KB" />
+ <param name="MaxSizeRollBackups" value="3" />
+ <param name="StaticLogFileName" value="false" />
+ <param name="CountDirection" value="1" />
+ <param name="PreserveLogFileNameExtension" value="true"/>
+ <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+ <layout type="log4net.Layout.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy/MM/dd HH:mm:ss.fff}
[%-5p] %m (%M)%n"/>
+ </layout>
+ </appender>
+
+ <logger name="IntegrationTestLogger">
+ <level value="debug" />
+ <appender-ref ref="LogFileAppender" />
+ </logger>
+ </log4net>
+</configuration>
\ No newline at end of file
diff --git a/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config
b/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config
new file mode 100644
index 00000000..2ea6bf4c
--- /dev/null
+++ b/src/log4net.Tests/Integration/log4net.maxsizeroll_date.config
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <log4net>
+ <appender name="LogFileAppender"
type="log4net.Appender.RollingFileAppender">
+ <param name="File" value="integrationTestLogDir_maxsizerolldate/.log" />
+ <param name="AppendToFile" value="true" />
+ <param name="RollingStyle" value="Composite" />
+ <param name="DatePattern" value="yyyy-MM-dd-mm" />
+ <param name="MaximumFileSize" value="10KB" />
+ <param name="MaxSizeRollBackups" value="3" />
+ <param name="StaticLogFileName" value="false" />
+ <param name="CountDirection" value="1" />
+ <param name="PreserveLogFileNameExtension" value="true"/>
+ <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+ <layout type="log4net.Layout.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy/MM/dd HH:mm:ss.fff}
[%-5p] %m (%M)%n"/>
+ </layout>
+ </appender>
+
+ <logger name="IntegrationTestLogger">
+ <level value="debug" />
+ <appender-ref ref="LogFileAppender" />
+ </logger>
+ </log4net>
+</configuration>
\ No newline at end of file
diff --git a/src/log4net.Tests/Integration/log4net.no_file_name.config
b/src/log4net.Tests/Integration/log4net.no_file_name.config
new file mode 100644
index 00000000..d0fc0786
--- /dev/null
+++ b/src/log4net.Tests/Integration/log4net.no_file_name.config
@@ -0,0 +1,23 @@
+<configuration>
+ <log4net>
+ <appender name="LogFileAppender"
type="log4net.Appender.RollingFileAppender">
+ <lockingModel
type="log4net.Appender.FileAppender+MinimalLock"/>
+ <file value="integrationTestLogDir_no_file_name/"/>
+ <encoding value="utf-8" />
+ <datePattern value="yyyy-MM-dd'.log'"/>
+ <staticLogFileName value="false"/>
+ <appendToFile value="true"/>
+ <rollingStyle value="Composite"/>
+ <maxSizeRollBackups value="10"/>
+ <maximumFileSize value="5MB"/>
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%date [%property{id}]
%-5level %logger - %message%newline"/>
+ </layout>
+ </appender>
+
+ <logger name="IntegrationTestLogger">
+ <level value="debug" />
+ <appender-ref ref="LogFileAppender" />
+ </logger>
+ </log4net>
+</configuration>
\ No newline at end of file
diff --git a/src/log4net.Tests/Integration/log4net.roll.config
b/src/log4net.Tests/Integration/log4net.roll.config
new file mode 100644
index 00000000..15b6735d
--- /dev/null
+++ b/src/log4net.Tests/Integration/log4net.roll.config
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <log4net>
+ <appender name="Log"
+ type="log4net.Appender.RollingFileAppender">
+
+ <file type="log4net.Util.PatternString">
+ <conversionPattern value="integrationTestLogDir_roll/Logging.log"/>
+ </file>
+
+ <param name="StaticLogFileName" value="true"/>
+ <param name="AppendToFile" value="false"/>
+ <rollingStyle value="Size"/>
+ <maximumFileSize value="1MB"/>
+ <maxSizeRollBackups value="12"/>
+ <CountDirection value="1"/>
+ <preserveLogFileNameExtension value="true"/>
+ <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
+
+ <layout type="log4net.Layout.PatternLayout">
+ <param name="ConversionPattern"
+ value="%date [%3thread] %-5level %logger{1} %-20message %n"/>
+ </layout>
+ </appender>
+
+ <logger name="IntegrationTestLogger">
+ <level value="debug" />
+ <appender-ref ref="Log" />
+ </logger>
+ </log4net>
+</configuration>
\ No newline at end of file
diff --git a/src/log4net.Tests/log4net.Tests.csproj
b/src/log4net.Tests/log4net.Tests.csproj
index 8670c967..b144e43a 100644
--- a/src/log4net.Tests/log4net.Tests.csproj
+++ b/src/log4net.Tests/log4net.Tests.csproj
@@ -34,5 +34,25 @@
<ItemGroup Condition="'$(TargetFramework)'=='net8.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk"
Version="$(MicrosoftNetTestSdkPackageVersion)" />
</ItemGroup>
+ <ItemGroup>
+ <None Update="Integration\log4net.maxsizeroll.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Update="Integration\log4net.maxsizeroll_date.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Update="Integration\log4net.no_file_name.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Update="Integration\log4net.roll.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Update="Integration\log4net.roll.config.xml">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ <None Update="Integration\log4net.integration.basic.config">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
<Import Project="../MonoForFramework.targets" />
</Project>
diff --git a/src/log4net/Appender/RollingFileAppender.cs
b/src/log4net/Appender/RollingFileAppender.cs
index 229842d2..55105c9f 100644
--- a/src/log4net/Appender/RollingFileAppender.cs
+++ b/src/log4net/Appender/RollingFileAppender.cs
@@ -681,7 +681,6 @@ protected List<string> GetExistingFiles(string baseFilePath)
using (SecurityContext?.Impersonate(this))
{
string fullPath = Path.GetFullPath(baseFilePath);
- string dir = Path.GetDirectoryName(baseFilePath);
directory = Path.GetDirectoryName(fullPath);
if (Directory.Exists(directory))
@@ -691,8 +690,7 @@ protected List<string> GetExistingFiles(string baseFilePath)
string[] files = Directory.GetFiles(directory,
GetWildcardPatternForFile(baseFileName));
result.AddRange(files
.Select(Path.GetFileName)
- .Where(curFileName =>
curFileName.StartsWith(Path.GetFileNameWithoutExtension(baseFileName)))
- .Select(file => Path.Combine(dir, file)));
+ .Where(curFileName =>
curFileName.StartsWith(Path.GetFileNameWithoutExtension(baseFileName))));
}
}
LogLog.Debug(_declaringType, "Searched for existing files in [" +
directory + "]");