Skip to content

Commit 2d315a7

Browse files
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
1 parent 7c7df92 commit 2d315a7

9 files changed

+383
-36
lines changed

src/log4net.Tests/Appender/RollingFileAppenderWithDirTest.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
using System;
2+
using System.IO;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using log4net;
6+
using log4net.Config;
7+
using NUnit.Framework;
8+
9+
namespace log4net.Tests.Integration
10+
{
11+
/// <summary>
12+
/// Integration tests for log4net logging scenarios, including file and directory creation, log rolling, and configuration behaviors.
13+
/// <para>
14+
/// Expectations for test log files and directories:
15+
/// <list type="bullet">
16+
/// <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>
17+
/// <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>
18+
/// <item>Tests assert the existence, count, and content of log files and directories as part of their validation.</item>
19+
/// </list>
20+
/// </para>
21+
/// </summary>
22+
[TestFixture]
23+
public class Log4NetIntegrationTests
24+
{
25+
/// <summary>
26+
/// Runs before each test to remove log files and directories from previous test runs.
27+
/// Ensures a clean environment for integration tests.
28+
/// </summary>
29+
[SetUp]
30+
public void SetUp()
31+
{
32+
RemoveFilesFromPreviousRuns();
33+
RemoveDirsFromPreviousRuns();
34+
}
35+
36+
/// <summary>
37+
/// Tests basic log4net functionality - writing log entries to a file.
38+
/// This is the most fundamental integration test verifying that:
39+
/// 1. Log4net can be configured from XML config
40+
/// 2. Log entries are written to the specified file
41+
/// 3. Content is preserved exactly as logged
42+
/// Catch: Validates end-to-end logging pipeline works correctly.
43+
/// </summary>
44+
[Test]
45+
public void Log4Net_WritesLogFile_AndContentIsCorrect()
46+
{
47+
var (log, repo) = ArrangeLogger("log4net.integration.basic.config");
48+
log.Info("Hello integration test");
49+
log.Error("This is an error");
50+
repo.Shutdown();
51+
52+
// Assert: log file exists and contains expected content
53+
string[] logLines = File.ReadAllLines("integrationTestLogFile_integration.log");
54+
Assert.That(logLines.Length, Is.EqualTo(2));
55+
Assert.That(logLines[0], Does.Contain("Hello integration test"));
56+
Assert.That(logLines[1], Does.Contain("This is an error"));
57+
}
58+
59+
/// <summary>
60+
/// Tests log4net's append behavior across multiple logger instances with repository shutdown/restart cycles.
61+
/// Verifies that:
62+
/// 1. Log entries accumulate correctly when repeatedly creating and shutting down logger repositories
63+
/// 2. File append mode works properly across repository restarts
64+
/// 3. No log entries are lost during the restart process
65+
/// Catch: This tests persistence and append behavior - critical for applications that restart logging frequently.
66+
/// </summary>
67+
[Test]
68+
public void Log4Net_WritesLogFile_AndContentIsCorrectAfterRestart()
69+
{
70+
for (int i = 0; i < 10; i++)
71+
{
72+
var (log, repo) = ArrangeLogger("log4net.integration.basic.config");
73+
log.Info("Hello integration test");
74+
log.Error("This is an error");
75+
repo.Shutdown();
76+
}
77+
// Assert: log file exists and contains expected content
78+
string[] logLines = File.ReadAllLines("integrationTestLogFile_integration.log");
79+
Assert.That(logLines.Length, Is.EqualTo(20));
80+
for (int i = 0; i < 10; i++)
81+
{
82+
Assert.That(logLines[i * 2], Does.Contain("Hello integration test"));
83+
Assert.That(logLines[i * 2 + 1], Does.Contain("This is an error"));
84+
}
85+
}
86+
87+
/// <summary>
88+
/// Tests log4net's file rolling behavior with no append mode enabled.
89+
/// This test verifies that:
90+
/// 1. Multiple logger instances generate multiple rolled log files
91+
/// 2. Rolling configuration works correctly across repository restarts
92+
/// 3. Files are rolled according to the configuration policy
93+
/// Catch: Tests rolling without append - ensuring each restart creates new files rather than appending to existing ones.
94+
/// The expected file count (13) includes the current log file plus rolled files from previous iterations.
95+
/// </summary>
96+
[Test]
97+
public void Log4Net_WritesLogFile_WithRollAndNoAppend_AndContentIsCorrectAfterRestart()
98+
{
99+
for (int i = 0; i < 20; i++)
100+
{
101+
var (log, repo) = ArrangeLogger("log4net.roll.config");
102+
for (int j = 0; j < 10; ++j)
103+
{
104+
log.Info($"Hello, log4net! {i} {j}");
105+
}
106+
repo.Shutdown();
107+
}
108+
// Assert: log file exists and contains expected content
109+
string[] logFiles = Directory.GetFiles("integrationTestLogDir_roll");
110+
Assert.That(logFiles.Length, Is.EqualTo(12 + 1));
111+
}
112+
113+
/// <summary>
114+
/// Tests log4net's file rolling behavior based on maximum file size.
115+
/// This test verifies that:
116+
/// 1. Log files are rolled when they exceed the configured maximum size
117+
/// 2. The correct number of backup files are maintained
118+
/// 3. Each file remains within the size limit (10KB + small buffer for overhead)
119+
/// Catch: Tests size-based rolling policy - critical for preventing log files from growing indefinitely.
120+
/// Expected: 1 current file + 3 backup files = 4 total files.
121+
/// </summary>
122+
[Test]
123+
public void Log4Net_WritesLogFile_WithMaxSizeRoll_Config_Works()
124+
{
125+
string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "integrationTestLogDir_maxsizeroll");
126+
if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
127+
Directory.CreateDirectory(logDir);
128+
var (log, repo) = ArrangeLogger("log4net.maxsizeroll.config");
129+
for (int i = 0; i < 1000; ++i)
130+
{
131+
log.Info($"Log entry {i}");
132+
}
133+
repo.Shutdown();
134+
// Assert: rolled files exist
135+
string[] logFiles = Directory.GetFiles(logDir, "*.log");
136+
Assert.That(logFiles.Length, Is.EqualTo(4)); // 1 current + 3 backups
137+
// Optionally, check that each file is <= 10KB
138+
foreach (var file in logFiles)
139+
Assert.That(new FileInfo(file).Length, Is.LessThanOrEqualTo(10 * 1024 + 100));
140+
}
141+
142+
/// <summary>
143+
/// Tests log4net's composite rolling behavior based on both date and file size.
144+
/// This test verifies that:
145+
/// 1. Log files are rolled when they exceed the configured maximum size
146+
/// 2. Log files are also rolled based on date/time patterns
147+
/// 3. The combination of date and size rolling creates multiple files grouped by date
148+
/// 4. Each date group maintains its own rolling sequence
149+
/// Catch: Tests composite rolling policy - ensures both date and size triggers work together correctly.
150+
/// Expected: Multiple files grouped by date, with size-based rolling within each date group.
151+
/// </summary>
152+
[Test]
153+
public void Log4Net_WritesLogFile_WithDateAndSizeRoll_Config_Works()
154+
{
155+
string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "integrationTestLogDir_maxsizerolldate");
156+
if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
157+
Directory.CreateDirectory(logDir);
158+
var (log, repo) = ArrangeLogger("log4net.maxsizeroll_date.config");
159+
// Write enough lines to trigger rolling by size and date
160+
for (int i = 1; i < 10000; ++i)
161+
{
162+
log.Debug($"DateRoll entry {i}");
163+
if (i % 5000 == 0) System.Threading.Thread.Sleep(TimeSpan.FromMinutes(1)); // allow time for date to change if needed
164+
}
165+
repo.Shutdown();
166+
// Assert: rolled files exist (date+size pattern)
167+
string[] logFiles = Directory.GetFiles(logDir, "*.log");
168+
Assert.That(logFiles.Length, Is.EqualTo(8));
169+
// Group files by date part in the filename (yyyy-MM-dd-mm)
170+
var dateGroups = logFiles
171+
.Select(f => Path.GetFileNameWithoutExtension(f))
172+
.Select(name => name.Split('.').First())
173+
.GroupBy(date => date)
174+
.ToDictionary(g => g.Key, g => g.Count());
175+
// Assert that at least one group exists and print group counts
176+
Assert.That(dateGroups.Count, Is.EqualTo(2));
177+
foreach (var group in dateGroups)
178+
{
179+
TestContext.Out.WriteLine($"Date group: {group.Key}, file count: {group.Value}");
180+
}
181+
}
182+
183+
/// <summary>
184+
/// Tests log4net's automatic file naming behavior when no explicit filename is configured.
185+
/// This test verifies that:
186+
/// 1. Log4net can automatically generate file names based on date patterns
187+
/// 2. Only one file is created when no explicit file name is provided
188+
/// 3. The generated file name follows the expected date format (yyyy-MM-dd.log)
189+
/// Catch: Tests the framework's ability to handle missing file name configuration gracefully.
190+
/// Expected: A single log file with name matching today's date pattern.
191+
/// </summary>
192+
[Test]
193+
public void Log4Net_ConfigWithoutFileName_CreatesOneFile()
194+
{
195+
string logDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "integrationTestLogDir_no_file_name");
196+
if (Directory.Exists(logDir)) Directory.Delete(logDir, true);
197+
Directory.CreateDirectory(logDir);
198+
var (log, repo) = ArrangeLogger("log4net.no_file_name.config");
199+
log.Info("Test entry with no file name");
200+
repo.Shutdown();
201+
// Assert: exactly one log file was created in the directory
202+
var files = Directory.GetFiles(logDir, "*", SearchOption.AllDirectories);
203+
Assert.That(files.Length, Is.EqualTo(1), "Should create exactly one log file");
204+
var fileName = Path.GetFileName(files[0]);
205+
TestContext.Out.WriteLine($"Created file: {fileName}");
206+
// Assert the file name matches the date pattern yyyy-MM-dd.log
207+
string todayPattern = DateTime.Now.ToString("yyyy-MM-dd") + ".log";
208+
Assert.That(fileName, Is.EqualTo(todayPattern), $"File name should match pattern: {todayPattern}");
209+
}
210+
211+
private const string TestLogFilePrefix = "integrationTestLogFile";
212+
private const string TestLogDirectoryPrefix = "integrationTestLogDir";
213+
214+
private static void RemoveDirsFromPreviousRuns()
215+
{
216+
List<string> directoriesFromPreviousRuns =
217+
Directory.EnumerateDirectories(TestContext.CurrentContext.TestDirectory)
218+
.Where(dirPath => Path.GetFileName(dirPath).StartsWith(TestLogDirectoryPrefix, StringComparison.InvariantCultureIgnoreCase))
219+
.ToList();
220+
directoriesFromPreviousRuns.ForEach(d => Directory.Delete(d, true));
221+
}
222+
223+
private static void RemoveFilesFromPreviousRuns()
224+
{
225+
List<string> filesFromPreviousRuns =
226+
Directory.EnumerateFiles(TestContext.CurrentContext.TestDirectory)
227+
.Where(filePath => Path.GetFileName(filePath).StartsWith(TestLogFilePrefix, StringComparison.InvariantCultureIgnoreCase))
228+
.ToList();
229+
filesFromPreviousRuns.ForEach(File.Delete);
230+
}
231+
232+
private static string GetConfigPath(string configFile)
233+
=> Path.Combine(TestContext.CurrentContext.TestDirectory, "Integration", configFile);
234+
235+
private static (ILog log, log4net.Repository.ILoggerRepository repo) ArrangeLogger(string configFile)
236+
{
237+
string configPath = GetConfigPath(configFile);
238+
var repo = LogManager.CreateRepository(Guid.NewGuid().ToString());
239+
XmlConfigurator.Configure(repo, new FileInfo(configPath));
240+
var log = LogManager.GetLogger(repo.Name, "IntegrationTestLogger");
241+
return (log, repo);
242+
}
243+
}
244+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<log4net>
3+
<appender name="FileAppender" type="log4net.Appender.FileAppender,log4net">
4+
<file value="integrationTestLogFile_integration.log" />
5+
<appendToFile value="true" />
6+
<layout type="log4net.Layout.PatternLayout">
7+
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
8+
</layout>
9+
</appender>
10+
<root>
11+
<level value="DEBUG" />
12+
<appender-ref ref="FileAppender" />
13+
</root>
14+
</log4net>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<log4net>
4+
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
5+
<param name="File" value="integrationTestLogDir_maxsizeroll/.log" />
6+
<param name="AppendToFile" value="true" />
7+
<param name="RollingStyle" value="Composite" />
8+
<param name="DatePattern" value="yyyy-MM-dd" />
9+
<param name="MaximumFileSize" value="10KB" />
10+
<param name="MaxSizeRollBackups" value="3" />
11+
<param name="StaticLogFileName" value="false" />
12+
<param name="CountDirection" value="1" />
13+
<param name="PreserveLogFileNameExtension" value="true"/>
14+
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
15+
<layout type="log4net.Layout.PatternLayout">
16+
<param name="ConversionPattern" value="%d{yyyy/MM/dd HH:mm:ss.fff} [%-5p] %m (%M)%n"/>
17+
</layout>
18+
</appender>
19+
20+
<logger name="IntegrationTestLogger">
21+
<level value="debug" />
22+
<appender-ref ref="LogFileAppender" />
23+
</logger>
24+
</log4net>
25+
</configuration>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<log4net>
4+
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
5+
<param name="File" value="integrationTestLogDir_maxsizerolldate/.log" />
6+
<param name="AppendToFile" value="true" />
7+
<param name="RollingStyle" value="Composite" />
8+
<param name="DatePattern" value="yyyy-MM-dd-mm" />
9+
<param name="MaximumFileSize" value="10KB" />
10+
<param name="MaxSizeRollBackups" value="3" />
11+
<param name="StaticLogFileName" value="false" />
12+
<param name="CountDirection" value="1" />
13+
<param name="PreserveLogFileNameExtension" value="true"/>
14+
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
15+
<layout type="log4net.Layout.PatternLayout">
16+
<param name="ConversionPattern" value="%d{yyyy/MM/dd HH:mm:ss.fff} [%-5p] %m (%M)%n"/>
17+
</layout>
18+
</appender>
19+
20+
<logger name="IntegrationTestLogger">
21+
<level value="debug" />
22+
<appender-ref ref="LogFileAppender" />
23+
</logger>
24+
</log4net>
25+
</configuration>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<configuration>
2+
<log4net>
3+
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
4+
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
5+
<file value="integrationTestLogDir_no_file_name/"/>
6+
<encoding value="utf-8" />
7+
<datePattern value="yyyy-MM-dd'.log'"/>
8+
<staticLogFileName value="false"/>
9+
<appendToFile value="true"/>
10+
<rollingStyle value="Composite"/>
11+
<maxSizeRollBackups value="10"/>
12+
<maximumFileSize value="5MB"/>
13+
<layout type="log4net.Layout.PatternLayout">
14+
<conversionPattern value="%date [%property{id}] %-5level %logger - %message%newline"/>
15+
</layout>
16+
</appender>
17+
18+
<logger name="IntegrationTestLogger">
19+
<level value="debug" />
20+
<appender-ref ref="LogFileAppender" />
21+
</logger>
22+
</log4net>
23+
</configuration>

0 commit comments

Comments
 (0)