#region Copyright & License // // Copyright 2001-2005 The Apache Software Foundation // // Licensed 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; using System.Collections; using System.IO; using System.Text.RegularExpressions; using log4net.Layout; using log4net.Core; using NUnit.Framework; using log4net.Repository.Hierarchy; using log4net.Appender; namespace log4net.Tests.Appender { /// /// Used for internal unit testing the class. /// [TestFixture] public class RollingFileAppenderTest { const string _fileName = "test_41d3d834_4320f4da.log"; const string _testMessage = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567"; const int _iMaximumFileSize = 450; // in bytes int _iMessagesLoggedThisFile = 0; int _iMessagesLogged = 0; int _iCountDirection = 0; int _MaxSizeRollBackups = 3; CountingAppender _caRoot; Logger _root; private class SilentErrorHandler : IErrorHandler { System.Text.StringBuilder m_buffer=new System.Text.StringBuilder(); public string Message { get {return m_buffer.ToString();} } public void Error(string message) {m_buffer.Append(message+"\n");} public void Error(string message, Exception e) {m_buffer.Append(message+"\n"+e.Message+"\n");} public void Error(string message, Exception e, ErrorCode errorCode) {m_buffer.Append(message+"\n"+e.Message+"\n");} } /// /// Sets up variables used for the tests /// private void InitializeVariables() { _iMessagesLoggedThisFile = 0; _iMessagesLogged = 0; _iCountDirection = +1; // Up _MaxSizeRollBackups = 3; } /// /// Shuts down any loggers in the hierarchy, along /// with all appenders, and deletes any test files used /// for logging. /// private void ResetAndDeleteTestFiles() { // Regular users should not use the clear method lightly! LogManager.GetRepository().ResetConfiguration(); LogManager.GetRepository().Shutdown(); ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Clear(); DeleteTestFiles(); } /// /// Any initialization that happens before each test can /// go here /// [SetUp] public void SetUp() { ResetAndDeleteTestFiles(); InitializeVariables(); } /// /// Any steps that happen after each test go here /// [TearDown] public void TearDown() { ResetAndDeleteTestFiles(); } /// /// Finds the number of files that match the base file name, /// and matches the result against an expected count /// /// private void VerifyFileCount( int iExpectedCount ) { ArrayList alFiles = GetExistingFiles(_fileName); Assert.IsNotNull(alFiles); Assert.AreEqual(iExpectedCount, alFiles.Count); } /// /// Creates a file with the given number, and the shared base file name /// /// private void CreateFile( int iFileNumber ) { FileInfo fileInfo = new FileInfo( MakeFileName(_fileName, iFileNumber) ); FileStream fileStream = null; try { fileStream = fileInfo.Create(); } finally { if (null != fileStream) { try { fileStream.Close(); } catch {} } fileStream = null; } } /// /// Verifies that the code correctly loads all filenames /// [Test] public void TestGetExistingFiles() { VerifyFileCount(0); CreateFile(0); VerifyFileCount(1); CreateFile(1); VerifyFileCount(2); } /// /// Removes all test files that exist /// private void DeleteTestFiles() { ArrayList alFiles = GetExistingFiles(_fileName); foreach(string sFile in alFiles) { try { System.Diagnostics.Debug.WriteLine("Deleting test file " + sFile); System.IO.File.Delete(sFile); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Exception while deleting test file " + ex.ToString()); } } } /// /// Generates a file name associated with the count. /// /// /// private string MakeFileName(int iFileCount) { return MakeFileName(_fileName, iFileCount); } /// /// Generates a file name associated with the count, using /// the base file name. /// /// /// /// private string MakeFileName(string sBaseFile, int iFileCount) { if (0 == iFileCount) { return sBaseFile; } return sBaseFile + "." + iFileCount; } /// /// Returns a RollingFileAppender using all the internal settings for maximum /// file size and number of backups /// /// private RollingFileAppender CreateAppender() { return CreateAppender(new FileAppender.ExclusiveLock()); } /// /// Returns a RollingFileAppender using all the internal settings for maximum /// file size and number of backups /// /// The locking model to test /// private RollingFileAppender CreateAppender(FileAppender.LockingModelBase lockModel) { // // Use a basic pattern that // includes just the message and a CR/LF. // PatternLayout layout = new PatternLayout("%m%n"); // // Create the new appender // RollingFileAppender appender = new RollingFileAppender(); appender.Layout = layout; appender.File = _fileName; appender.MaximumFileSize = _iMaximumFileSize.ToString(); appender.MaxSizeRollBackups = _MaxSizeRollBackups; appender.CountDirection = _iCountDirection; appender.RollingStyle = RollingFileAppender.RollingMode.Size; appender.LockingModel =lockModel; appender.ActivateOptions(); return appender; } /// /// Used for test purposes, a table of these objects can be used to identify /// any existing files and their expected length. /// public class RollFileEntry { /// /// Stores the name of the file /// private string m_fileName; /// /// The expected length of the file /// private long m_fileLength; /// /// Default constructor /// public RollFileEntry() {} /// /// Constructor used when the fileInfo and expected length are known /// /// /// public RollFileEntry(string fileName, long fileLength) { m_fileName = fileName; m_fileLength = fileLength; } /// /// Stores the name of the file /// public string FileName { get { return m_fileName; } } /// /// The expected length of the file /// public long FileLength { get { return m_fileLength; } } } /// /// Used for table-driven testing. This class holds information that can be used /// for testing of file rolling. /// public class RollConditions { /// /// A table of entries showing files that should exist and their expected sizes /// before logging is called /// private RollFileEntry[] m_preLogFileEntries; /// /// A table of entries showing files that should exist and their expected sizes /// after a message is logged /// private RollFileEntry[] m_postLogFileEntries; /// /// Constructor, taking all required parameters /// /// /// public RollConditions( RollFileEntry[] preLogFileEntries, RollFileEntry[] postLogFileEntries ) { m_preLogFileEntries = preLogFileEntries; m_postLogFileEntries = postLogFileEntries; } /// /// A table of entries showing files that should exist and their expected sizes /// before logging is called /// public RollFileEntry[] GetPreLogFileEntries() { return m_preLogFileEntries; } /// /// A table of entries showing files that should exist and their expected sizes /// after a message is logged /// public RollFileEntry[] GetPostLogFileEntries() { return m_postLogFileEntries; } } private void VerifyExistenceAndRemoveFromList( ArrayList alExisting, string sFileName, FileInfo file, RollFileEntry entry ) { Assert.IsTrue(alExisting.Contains( sFileName ), "filename {0} not found in test directory", sFileName); Assert.AreEqual(entry.FileLength, file.Length, "file length mismatch"); // Remove this file from the list alExisting.Remove( sFileName ); } /// /// Checks that all the expected files exist, and only the expected files. Also /// verifies the length of all files against the expected length /// /// /// private void VerifyFileConditions( string sBaseFileName, RollFileEntry[] fileEntries ) { ArrayList alExisting = GetExistingFiles( sBaseFileName ); if (null != fileEntries ) { // AssertEquals( "File count mismatch", alExisting.Count, fileEntries.Length ); foreach( RollFileEntry rollFile in fileEntries ) { string sFileName = rollFile.FileName; FileInfo file = new FileInfo(sFileName); if (rollFile.FileLength > 0 ) { Assert.IsTrue(file.Exists, "filename {0} does not exist", sFileName ); VerifyExistenceAndRemoveFromList( alExisting, sFileName, file, rollFile ); } else { // If length is 0, file may not exist yet. If file exists, make sure length // is zero. If file doesn't exist, this is OK if (file.Exists ) { VerifyExistenceAndRemoveFromList( alExisting, sFileName, file, rollFile ); } } } } else { Assert.AreEqual(0, alExisting.Count); } // This check ensures no extra files matching the wildcard pattern exist. // We only want the files we expect, and no others Assert.AreEqual(0, alExisting.Count); } /// /// Called before logging a message to check that all the expected files exist, /// and only the expected files. Also verifies the length of all files against /// the expected length /// /// /// private void VerifyPreConditions( string sBaseFileName, RollConditions entry ) { VerifyFileConditions( sBaseFileName, entry.GetPreLogFileEntries() ); } /// /// Called after logging a message to check that all the expected files exist, /// and only the expected files. Also verifies the length of all files against /// the expected length /// /// /// private void VerifyPostConditions( string sBaseFileName, RollConditions entry ) { VerifyFileConditions( sBaseFileName, entry.GetPostLogFileEntries() ); } /// /// Logs a message, verifying the expected message counts against the /// current running totals. /// /// /// private void LogMessage( RollConditions entry, string sMessageToLog ) { Assert.AreEqual(_caRoot.Counter, _iMessagesLogged++); _root.Log(Level.Debug, sMessageToLog, null); Assert.AreEqual(_caRoot.Counter, _iMessagesLogged); _iMessagesLoggedThisFile++; } private void DumpFileEntry( RollFileEntry entry ) { System.Diagnostics.Debug.WriteLine( "\tfile name: " + entry.FileName ); System.Diagnostics.Debug.WriteLine( "\tfile length: " + entry.FileLength ); } private void DumpTableEntry( RollConditions entry ) { System.Diagnostics.Debug.WriteLine( "Pre-Conditions" ); foreach( RollFileEntry file in entry.GetPreLogFileEntries() ) { DumpFileEntry( file ); } System.Diagnostics.Debug.WriteLine( "Post-Conditions" ); foreach( RollFileEntry file in entry.GetPostLogFileEntries() ) { DumpFileEntry( file ); } // System.Diagnostics.Debug.WriteLine(""); } /// /// Runs through all table entries, logging messages. Before each message is logged, /// pre-conditions are checked to ensure the expected files exist and they are the /// expected size. After logging, verifies the same. /// /// /// /// private void RollFromTableEntries( string sBaseFileName, RollConditions[] entries, string sMessageToLog ) { for( int i=0; i /// Returns the number of bytes logged per message, including /// any CR/LF characters in addition to the message length. /// /// /// private int TotalMessageLength( string sMessage ) { const int iLengthCRLF = 2; return sMessage.Length + iLengthCRLF; } /// /// Determines how many messages of a fixed length can be logged /// to a single file before the file rolls. /// /// /// private int MessagesPerFile( int iMessageLength ) { int iMessagesPerFile = _iMaximumFileSize / iMessageLength; // // RollingFileAppender checks for wrap BEFORE logging, // so we will actually get one more message per file than // we would otherwise. // if (iMessagesPerFile * iMessageLength < _iMaximumFileSize ) { iMessagesPerFile++; } return iMessagesPerFile; } /// /// Determines the name of the current file /// /// private string GetCurrentFile() { // Current file name is always the base file name when // counting. Dates will need a different approach return _fileName; } /// /// Turns a group of file names into an array of file entries that include the name /// and a size. This is useful for assigning the properties of backup files, when /// the length of the files are all the same size due to a fixed message length. /// /// /// /// private RollFileEntry[] MakeBackupFileEntriesFromBackupGroup( string sBackupGroup, int iBackupFileLength ) { string[] sFiles = sBackupGroup.Split( ' ' ); ArrayList alEntries = new ArrayList(); for( int i=0; i 0 ) { alEntries.Add( new RollFileEntry( sFiles[i], iBackupFileLength ) ); } } return (RollFileEntry[])alEntries.ToArray(typeof(RollFileEntry)); } /// /// Finds the iGroup group in the string (comma separated groups) /// /// /// /// private string GetBackupGroup( string sBackupGroups, int iGroup ) { string[] sGroups = sBackupGroups.Split( ',' ); return sGroups[iGroup]; } /// /// Builds a collection of file entries based on the file names /// specified in a groups string and the max file size from the /// stats object /// /// /// /// private RollFileEntry[] MakeBackupFileEntriesForPreCondition( string sBackupGroups, RollingStats stats ) { if (0 == stats.NumberOfFileRolls ) { return null; // first round has no previous backups } string sGroup; if (0 == stats.MessagesThisFile ) { // first file has special pattern...since rolling doesn't occur when message // is logged, rather before next message is logged. if (stats.NumberOfFileRolls <= 1 ) { return null; } // Use backup files from previous round. The minus 2 is because we have already // rolled, and the first round uses null instead of the string sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-2 ); } else { sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-1 ); } return MakeBackupFileEntriesFromBackupGroup( sGroup, stats.MaximumFileSize ); } /// /// Builds a collection of file entries based on the file names /// specified in a groups string and the max file size from the /// stats object /// /// /// /// private RollFileEntry[] MakeBackupFileEntriesForPostCondition( string sBackupGroups, RollingStats stats ) { if (0 == stats.NumberOfFileRolls) { return null; // first round has no previous backups } string sGroup = GetBackupGroup( sBackupGroups, stats.NumberOfFileRolls-1 ); return MakeBackupFileEntriesFromBackupGroup( sGroup, stats.MaximumFileSize ); } /// /// This class holds information that is used while we are generating /// test data sets /// public class RollingStats { private int iTotalMessageLength; private int iMessagesPerFile; private int iMessagesThisFile; private int iNumberOfFileRolls; /// /// Default constructor /// public RollingStats() {} /// /// Number of total bytes a log file can reach. /// public int MaximumFileSize { get { return TotalMessageLength * MessagesPerFile; } } /// /// The length of a message, including any CR/LF characters. /// This length assumes all messages are a fixed length for /// test purposes. /// public int TotalMessageLength { get { return iTotalMessageLength; } set { iTotalMessageLength = value;} } /// /// A count of the number of messages that are logged to each /// file. /// public int MessagesPerFile { get { return iMessagesPerFile; } set { iMessagesPerFile = value; } } /// /// Counts how many messages have been logged to the current file /// public int MessagesThisFile { get { return iMessagesThisFile; } set { iMessagesThisFile = value; } } /// /// Counts how many times a file roll has occurred /// public int NumberOfFileRolls { get { return iNumberOfFileRolls; } set { iNumberOfFileRolls = value; } } } /// /// The stats are used to keep track of progress while we are algorithmically /// generating a table of pre/post condition tests for file rolling. /// /// /// private RollingStats InitializeStats( string sTestMessage ) { RollingStats rollingStats = new RollingStats(); rollingStats.TotalMessageLength = TotalMessageLength( sTestMessage ); rollingStats.MessagesPerFile = MessagesPerFile( rollingStats.TotalMessageLength ); rollingStats.MessagesThisFile = 0; rollingStats.NumberOfFileRolls = 0; return rollingStats; } /// /// Takes an existing array of RollFileEntry objects, creates a new array one element /// bigger, and appends the final element to the end. If the existing entries are /// null (no entries), then a one-element array is returned with the final element /// as the only entry. /// /// /// /// private RollFileEntry[] AddFinalElement( RollFileEntry[] existing, RollFileEntry final ) { int iLength = 1; if (null != existing ) { iLength += existing.Length; } RollFileEntry[] combined = new RollFileEntry[iLength]; if (null != existing ) { System.Array.Copy( existing, 0, combined, 0, existing.Length ); } combined[iLength-1] = final; return combined; } /// /// Generates the pre and post condition arrays from an array of backup files and the /// current file / next file. /// /// /// /// /// /// /// private RollConditions BuildTableEntry( string sBackupFiles, RollConditions preCondition, RollFileEntry current, RollFileEntry currentNext, RollingStats rollingStats ) { RollFileEntry[] backupsPost = MakeBackupFileEntriesForPostCondition( sBackupFiles, rollingStats ); RollFileEntry[] post = AddFinalElement( backupsPost, currentNext ); if (null == preCondition ) { return new RollConditions( AddFinalElement(null, current), post ); } return new RollConditions( preCondition.GetPostLogFileEntries(), post ); } /// /// Returns a RollFileEntry that represents the next state of the current file, /// based on the current state. When the current state would roll, the next /// entry is the current file wrapped to 0 bytes. Otherwise, the next state /// is the post-condition passed in as the currentNext parameter /// /// /// /// private RollFileEntry MoveNextEntry( RollingStats rollingStats, RollFileEntry currentNext ) { rollingStats.MessagesThisFile = rollingStats.MessagesThisFile + 1; if (rollingStats.MessagesThisFile >= rollingStats.MessagesPerFile ) { rollingStats.MessagesThisFile = 0; rollingStats.NumberOfFileRolls = rollingStats.NumberOfFileRolls + 1; return new RollFileEntry( GetCurrentFile(), 0 ); } else { return currentNext; } } /// /// Callback point for the regular expression parser. Turns /// the number into a file name. /// /// /// private string NumberedNameMaker( Match match ) { Int32 iValue = Int32.Parse(match.Value); return MakeFileName( _fileName, iValue ); } /// /// Parses a numeric list of files, turning them into file names. /// Calls back to a method that does the actual replacement, turning /// the numeric value into a filename. /// /// /// /// string ConvertToFiles( string sBackupInfo, MatchEvaluator evaluator ) { Regex regex = new Regex(@"\d+"); return regex.Replace( sBackupInfo, evaluator ); } /// /// Makes test entries used for verifying counted file names /// /// A message to log repeatedly /// Filename groups used to indicate backup file name progression /// that results after each message is logged /// How many times the test message will be repeatedly logged /// private RollConditions[] MakeNumericTestEntries( string sTestMessage, string sBackupInfo, int iMessagesToLog ) { return MakeTestEntries( sTestMessage, sBackupInfo, iMessagesToLog, new MatchEvaluator(NumberedNameMaker ) ); } /// /// This routine takes a list of backup file names and a message that will be logged /// repeatedly, and generates a collection of objects containing pre-condition and /// post-condition information. This pre/post information shows the names and expected /// file sizes for all files just before and just after a message is logged. /// /// A message to log repeatedly /// Filename groups used to indicate backup file name progression /// that results after each message is logged /// How many times the test message will be repeatedly logged /// Function that can turn a number into a filename /// private RollConditions[] MakeTestEntries( string sTestMessage, string sBackupInfo, int iMessagesToLog, MatchEvaluator evaluator ) { string sBackupFiles = ConvertToFiles( sBackupInfo, evaluator ); RollConditions[] table = new RollConditions[iMessagesToLog]; RollingStats rollingStats = InitializeStats( sTestMessage ); RollConditions preCondition = null; rollingStats.MessagesThisFile = 0; RollFileEntry currentFile = new RollFileEntry( GetCurrentFile(), 0 ); for( int i=0; i /// Uses the externally defined rolling table to verify rolling names/sizes /// /// Pattern is: check pre-conditions. Log messages, checking size of current file. /// when size exceeds limit, check post conditions. Can determine from message the /// number of messages N that will cause a roll to occur. Challenge is to verify the /// expected files, their sizes, and the names. For a message of length L, the backups /// will be of size (N * L), and the current file will be of size (K * L), where K is /// the number of messages that have been logged to this file. /// /// File sizes can be checked algorithmically. /// /// File names are generated using a table driven algorithm, where a number is turned into /// the actual filename. /// /// The entries are comma-separated, with spaces between the names. Each comma indicates /// a 'roll', and the group between commas indicates the numbers for all backup files that /// occur as a result of the roll. It is assumed that no backup files exist before a roll /// occurs /// /// private void VerifyRolling( RollConditions[] table ) { ConfigureRootAppender(); RollFromTableEntries( _fileName, table, _testMessage ); } /// /// Validates rolling using a fixed number of backup files, with /// count direction set to up, so that newer files have higher counts. /// Newest = N, Oldest = N-K, where K is the number of backups to allow /// and N is the number of times rolling has occurred. /// [Test] public void TestRollingCountUpFixedBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = "1, 1 2, 1 2 3, 2 3 4, 3 4 5"; // // Count Up // _iCountDirection = +1; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Validates rolling using an infinite number of backup files, with /// count direction set to up, so that newer files have higher counts. /// Newest = N, Oldest = 1, where N is the number of times rolling has /// occurred. /// [Test] public void TestRollingCountUpInfiniteBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; // // Count Up // _iCountDirection = +1; // // Infinite backups // _MaxSizeRollBackups = -1; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Validates rolling with no backup files, with count direction set to up. /// Only the current file should be present, wrapping to 0 bytes once the /// previous file fills up. /// [Test] public void TestRollingCountUpZeroBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = ", , , , "; // // Count Up // _iCountDirection = +1; // // No backups // _MaxSizeRollBackups = 0; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Validates rolling using a fixed number of backup files, with /// count direction set to down, so that older files have higher counts. /// Newest = 1, Oldest = N, where N is the number of backups to allow /// [Test] public void TestRollingCountDownFixedBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3, 1 2 3"; // // Count Up // _iCountDirection = -1; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Validates rolling using an infinite number of backup files, with /// count direction set to down, so that older files have higher counts. /// Newest = 1, Oldest = N, where N is the number of times rolling has /// occurred /// [Test] public void TestRollingCountDownInfiniteBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = "1, 1 2, 1 2 3, 1 2 3 4, 1 2 3 4 5"; // // Count Down // _iCountDirection = -1; // // Infinite backups // _MaxSizeRollBackups = -1; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Validates rolling with no backup files, with count direction set to down. /// Only the current file should be present, wrapping to 0 bytes once the /// previous file fills up. /// [Test] public void TestRollingCountDownZeroBackups() { // // Oldest to newest when reading in a group left-to-right, so 1 2 3 means 1 is the // oldest, and 3 is the newest // string sBackupInfo = ", , , , "; // // Count Up // _iCountDirection = -1; // // No backups // _MaxSizeRollBackups = 0; // // Log 30 messages. This is 5 groups, 6 checks per group ( 0, 100, 200, 300, 400, 500 // bytes for current file as messages are logged. // int iMessagesToLog = 30; VerifyRolling( MakeNumericTestEntries( _testMessage, sBackupInfo, iMessagesToLog ) ); } /// /// Configures the root appender for counting and rolling /// private void ConfigureRootAppender() { _root = ((log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository()).Root; _root.Level = Level.Debug; _caRoot = new CountingAppender(); _root.AddAppender(_caRoot); Assert.AreEqual(_caRoot.Counter, 0); // // Set the root appender with a RollingFileAppender // _root.AddAppender( CreateAppender() ); _root.Repository.Configured = true; } /// /// Verifies that the current backup index is detected correctly when initializing /// /// /// /// private void VerifyInitializeRollBackupsFromBaseFile( string sBaseFile, ArrayList alFiles, int iExpectedCurSizeRollBackups ) { InitializeAndVerifyExpectedValue( alFiles, sBaseFile, CreateRollingFileAppender( "5,0,1" ), iExpectedCurSizeRollBackups ); } /// /// Tests that the current backup index is 0 when no /// existing files are seen /// [Test] public void TestInitializeRollBackups1() { string sBaseFile = "LogFile.log"; ArrayList arrFiles = new ArrayList(); arrFiles.Add( "junk1" ); arrFiles.Add( "junk1.log" ); arrFiles.Add( "junk2.log" ); arrFiles.Add( "junk.log.1" ); arrFiles.Add( "junk.log.2" ); int iExpectedCurSizeRollBackups = 0; VerifyInitializeRollBackupsFromBaseFile( sBaseFile, arrFiles, iExpectedCurSizeRollBackups ); } /// /// Verifies that files are detected when the base file is specified /// /// private void VerifyInitializeRollBackupsFromBaseFile( string sBaseFile ) { ArrayList alFiles = MakeTestDataFromString( sBaseFile, "0,1,2" ); int iExpectedCurSizeRollBackups = 2; VerifyInitializeRollBackupsFromBaseFile( sBaseFile, alFiles, iExpectedCurSizeRollBackups ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountUpFixed() { ArrayList alFiles = MakeTestDataFromString( "3,4,5" ); int iExpectedValue = 5; InitializeAndVerifyExpectedValue( alFiles, _fileName, CreateRollingFileAppender( "3,0,1" ), iExpectedValue ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountUpFixed2() { ArrayList alFiles = MakeTestDataFromString( "0,3" ); int iExpectedValue = 3; InitializeAndVerifyExpectedValue( alFiles, _fileName, CreateRollingFileAppender( "3,0,1" ), iExpectedValue ); } /// /// Verifies that count stays at 0 for the zero backups case /// when counting up /// [Test] public void TestInitializeCountUpZeroBackups() { ArrayList alFiles = MakeTestDataFromString( "0,3" ); int iExpectedValue = 0; InitializeAndVerifyExpectedValue( alFiles, _fileName, CreateRollingFileAppender( "0,0,1" ), iExpectedValue ); } /// /// Verifies that count stays at 0 for the zero backups case /// when counting down /// [Test] public void TestInitializeCountDownZeroBackups() { ArrayList alFiles = MakeTestDataFromString( "0,3" ); int iExpectedValue = 0; InitializeAndVerifyExpectedValue( alFiles, _fileName, CreateRollingFileAppender( "0,0,-1" ), iExpectedValue ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed() { ArrayList alFiles = MakeTestDataFromString( "4,5,6" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 0 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed2() { ArrayList alFiles = MakeTestDataFromString( "1,5,6" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 1 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed3() { ArrayList alFiles = MakeTestDataFromString( "2,5,6" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 2 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed4() { ArrayList alFiles = MakeTestDataFromString( "3,5,6" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 3 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed5() { ArrayList alFiles = MakeTestDataFromString( "1,2,3" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 3 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed6() { ArrayList alFiles = MakeTestDataFromString( "1,2" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 2 ); } /// /// Verifies that count goes to the highest when counting up /// [Test] public void TestInitializeCountDownFixed7() { ArrayList alFiles = MakeTestDataFromString( "2,3" ); VerifyInitializeDownFixedExpectedValue( alFiles, _fileName, 3 ); } private void InitializeAndVerifyExpectedValue( ArrayList alFiles, string sBaseFile, RollingFileAppender rfa, int iExpectedValue ) { InitializeRollBackups( rfa, sBaseFile, alFiles ); Assert.AreEqual( iExpectedValue, GetFieldCurSizeRollBackups(rfa) ); } /// /// Tests the count down case, with infinite max backups, to see that /// initialization of the rolling file appender results in the expected value /// /// /// /// private void VerifyInitializeDownInfiniteExpectedValue( ArrayList alFiles, string sBaseFile, int iExpectedValue ) { InitializeAndVerifyExpectedValue( alFiles, sBaseFile, CreateRollingFileAppender( "-1,0,-1" ), iExpectedValue ); } /// /// Creates a RollingFileAppender with the desired values, where the /// values are passed as a comma separated string, with 3 parameters, /// m_maxSizeRollBackups, m_curSizeRollBackups, CountDirection /// /// /// private RollingFileAppender CreateRollingFileAppender( string sParams ) { string[] asParams = sParams.Split(','); if (null == asParams || asParams.Length != 3 ) { throw new ArgumentOutOfRangeException(sParams, sParams, "Must have 3 comma separated params: MaxSizeRollBackups, CurSizeRollBackups, CountDirection" ); } RollingFileAppender rfa = new RollingFileAppender(); rfa.RollingStyle = RollingFileAppender.RollingMode.Size; SetFieldMaxSizeRollBackups(rfa, Int32.Parse(asParams[0].Trim())); SetFieldCurSizeRollBackups(rfa, Int32.Parse(asParams[1].Trim())); rfa.CountDirection = Int32.Parse(asParams[2].Trim()); return rfa; } /// /// Verifies that count goes to the highest when counting down /// and infinite backups are selected /// [Test] public void TestInitializeCountDownInfinite() { ArrayList alFiles = MakeTestDataFromString( "2,3" ); VerifyInitializeDownInfiniteExpectedValue( alFiles, _fileName, 3 ); } /// /// Verifies that count goes to the highest when counting down /// and infinite backups are selected /// [Test] public void TestInitializeCountDownInfinite2() { ArrayList alFiles = MakeTestDataFromString( "2,3,4,5,6,7,8,9,10" ); VerifyInitializeDownInfiniteExpectedValue( alFiles, _fileName, 10 ); } /// /// Verifies that count goes to the highest when counting down /// and infinite backups are selected /// [Test] public void TestInitializeCountDownInfinite3() { ArrayList alFiles = MakeTestDataFromString( "9,10,3,4,5,7,9,6,1,2,8" ); VerifyInitializeDownInfiniteExpectedValue( alFiles, _fileName, 10 ); } /// /// Verifies that count goes to the highest when counting up /// and infinite backups are selected /// [Test] public void TestInitializeCountUpInfinite() { ArrayList alFiles = MakeTestDataFromString( "2,3" ); VerifyInitializeUpInfiniteExpectedValue( alFiles, _fileName, 3 ); } /// /// Verifies that count goes to the highest when counting up /// and infinite backups are selected /// [Test] public void TestInitializeCountUpInfinite2() { ArrayList alFiles = MakeTestDataFromString( "2,3,4,5,6,7,8,9,10" ); VerifyInitializeUpInfiniteExpectedValue( alFiles, _fileName, 10 ); } /// /// Verifies that count goes to the highest when counting up /// and infinite backups are selected /// [Test] public void TestInitializeCountUpInfinite3() { ArrayList alFiles = MakeTestDataFromString( "9,10,3,4,5,7,9,6,1,2,8" ); VerifyInitializeUpInfiniteExpectedValue( alFiles, _fileName, 10 ); } /// /// Creates a logger hierarchy, configures a rolling file appender and returns an ILogger /// /// The filename to log to /// The locking model to use. /// The error handler to use. /// A configured ILogger private ILogger CreateLogger(string filename,FileAppender.LockingModelBase lockModel, IErrorHandler handler) { log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.CreateRepository("TestRepository"); log4net.Appender.RollingFileAppender appender = new log4net.Appender.RollingFileAppender(); appender.File = filename; appender.AppendToFile = false; appender.CountDirection=0; appender.RollingStyle=RollingFileAppender.RollingMode.Size; appender.MaxFileSize=100000; appender.Encoding=System.Text.Encoding.ASCII; appender.ErrorHandler=handler; if (lockModel!=null) {appender.LockingModel=lockModel;} log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout(); layout.ConversionPattern = "%m%n"; layout.ActivateOptions(); appender.Layout = layout; appender.ActivateOptions(); h.Root.AddAppender(appender); h.Configured = true; ILogger log=h.GetLogger("Logger"); return log; } /// /// Destroys the logger hierarchy created by /// private void DestroyLogger() { log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository("TestRepository"); h.ResetConfiguration(); //Replace the repository selector so that we can recreate the hierarchy with the same name if necessary LoggerManager.RepositorySelector=new DefaultRepositorySelector(log4net.Util.SystemInfo.GetTypeFromString("log4net.Repository.Hierarchy.Hierarchy",true,true)); } private void AssertFileEquals(string filename, string contents) { StreamReader sr=new StreamReader(filename); string logcont=sr.ReadToEnd(); sr.Close(); Assert.AreEqual(contents,logcont, "Log contents is not what is expected"); System.IO.File.Delete(filename); } /// /// Verifies that logging a message actually produces output /// [Test] public void TestLogOutput() { String filename="test.log"; SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.ExclusiveLock(), sh); log.Log(this.GetType(),Level.Info,"This is a message",null); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); AssertFileEquals(filename,"This is a message"+Environment.NewLine+"This is a message 2"+Environment.NewLine); Assert.AreEqual("",sh.Message, "Unexpected error message"); } /// /// Verifies that attempting to log to a locked file fails gracefully /// [Test] public void TestExclusiveLockFails() { String filename="test.log"; FileStream fs=new FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4); SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.ExclusiveLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); fs.Close(); AssertFileEquals(filename,"Test"); Assert.AreEqual(sh.Message.Substring(0,30), "Unable to acquire lock on file", "Expecting an error message"); } /// /// Verifies that attempting to log to a locked file recovers if the lock is released /// [Test] public void TestExclusiveLockRecovers() { String filename="test.log"; FileStream fs=new FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4); SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.ExclusiveLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); fs.Close(); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); AssertFileEquals(filename,"This is a message 2"+Environment.NewLine); Assert.AreEqual("Unable to acquire lock on file",sh.Message.Substring(0,30), "Expecting an error message"); } /// /// Verifies that attempting to log to a file with ExclusiveLock really locks the file /// [Test] public void TestExclusiveLockLocks() { String filename="test.log"; bool locked=false; SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.ExclusiveLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); try { FileStream fs=new FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4); fs.Close(); } catch (System.IO.IOException e1) { Assert.AreEqual("The process cannot access the file ",e1.Message.Substring(0,35), "Unexpected exception"); locked=true; } log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); Assert.IsTrue(locked, "File was not locked"); AssertFileEquals(filename,"This is a message"+Environment.NewLine+"This is a message 2"+Environment.NewLine); Assert.AreEqual("",sh.Message, "Unexpected error message"); } /// /// Verifies that attempting to log to a locked file fails gracefully /// [Test] public void TestMinimalLockFails() { String filename="test.log"; FileStream fs=new FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4); SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.MinimalLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); fs.Close(); AssertFileEquals(filename,"Test"); Assert.AreEqual("Unable to acquire lock on file",sh.Message.Substring(0,30), "Expecting an error message"); } /// /// Verifies that attempting to log to a locked file recovers if the lock is released /// [Test] public void TestMinimalLockRecovers() { String filename="test.log"; FileStream fs=new FileStream(filename,FileMode.Create,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"),0,4); SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.MinimalLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); fs.Close(); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); AssertFileEquals(filename,"This is a message 2"+Environment.NewLine); Assert.AreEqual("Unable to acquire lock on file",sh.Message.Substring(0,30), "Expecting an error message"); } /// /// Verifies that attempting to log to a file with ExclusiveLock really locks the file /// [Test] public void TestMinimalLockUnlocks() { String filename="test.log"; bool locked=false; SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,new FileAppender.MinimalLock(),sh); log.Log(this.GetType(),Level.Info,"This is a message",null); locked=true; FileStream fs=new FileStream(filename,FileMode.Append,FileAccess.Write,FileShare.None); fs.Write(System.Text.Encoding.ASCII.GetBytes("Test"+Environment.NewLine),0,4+Environment.NewLine.Length); fs.Close(); log.Log(this.GetType(),Level.Info,"This is a message 2",null); DestroyLogger(); Assert.IsTrue(locked, "File was not locked"); AssertFileEquals(filename,"This is a message"+Environment.NewLine+"Test"+Environment.NewLine+"This is a message 2"+Environment.NewLine); Assert.AreEqual("",sh.Message, "Unexpected error message"); } /// /// Verify that the default LockModel is ExclusiveLock, to maintain backwards compatibility with previous behaviour /// [Test] public void TestDefaultLockingModel() { String filename="test.log"; SilentErrorHandler sh=new SilentErrorHandler(); ILogger log=CreateLogger(filename,null,sh); IAppender[] appenders=log.Repository.GetAppenders(); Assert.AreEqual(1,appenders.Length, "The wrong number of appenders are configured"); RollingFileAppender rfa=(RollingFileAppender)(appenders[0]); Assert.AreEqual(log4net.Util.SystemInfo.GetTypeFromString("log4net.Appender.FileAppender+ExclusiveLock",true,true),rfa.LockingModel.GetType(), "The LockingModel is of an unexpected type"); DestroyLogger(); } /// /// Tests the count up case, with infinite max backups , to see that /// initialization of the rolling file appender results in the expected value /// /// /// /// private void VerifyInitializeUpInfiniteExpectedValue( ArrayList alFiles, string sBaseFile, int iExpectedValue ) { InitializeAndVerifyExpectedValue( alFiles, sBaseFile, CreateRollingFileAppender( "-1,0,1" ), iExpectedValue ); } /// /// Tests the count down case, with max backups limited to 3, to see that /// initialization of the rolling file appender results in the expected value /// /// /// /// private void VerifyInitializeDownFixedExpectedValue( ArrayList alFiles, string sBaseFile, int iExpectedValue ) { InitializeAndVerifyExpectedValue( alFiles, sBaseFile, CreateRollingFileAppender( "3,0,-1" ), iExpectedValue ); } /// /// Turns a string of comma separated numbers into a collection of filenames /// generated from the numbers. /// /// Defaults to filename in _fileName variable. /// /// /// Comma separated list of numbers for counted file names /// private ArrayList MakeTestDataFromString( string sFileNumbers ) { return MakeTestDataFromString( _fileName, sFileNumbers ); } /// /// Turns a string of comma separated numbers into a collection of filenames /// generated from the numbers /// /// Uses the input filename. /// /// Name of file to combine with numbers when generating counted file names /// Comma separated list of numbers for counted file names /// private ArrayList MakeTestDataFromString( string sFileName, string sFileNumbers ) { ArrayList alFiles = new ArrayList(); string[] sNumbers = sFileNumbers.Split( ',' ); foreach( string sNumber in sNumbers ) { Int32 iValue = Int32.Parse( sNumber.Trim() ); alFiles.Add( MakeFileName(sFileName, iValue) ); } return alFiles; } /// /// Tests that the current backup index is correctly detected /// for a file with no extension /// [Test] public void TestInitializeRollBackups2() { VerifyInitializeRollBackupsFromBaseFile( "LogFile" ); } /// /// Tests that the current backup index is correctly detected /// for a file with a .log extension /// [Test] public void TestInitializeRollBackups3() { VerifyInitializeRollBackupsFromBaseFile( "LogFile.log" ); } /// /// Makes sure that the initialization can detect the backup /// number correctly. /// /// /// public void VerifyInitializeRollBackups(int iBackups, int iMaxSizeRollBackups) { string sBaseFile = "LogFile.log"; ArrayList arrFiles = new ArrayList(); arrFiles.Add( "junk1" ); for( int i=0; i /// Tests that the current backup index is correctly detected, /// and gets no bigger than the max backups setting /// [Test] public void TestInitializeRollBackups4() { const int iMaxRollBackups = 5; VerifyInitializeRollBackups( 0, iMaxRollBackups ); VerifyInitializeRollBackups( 1, iMaxRollBackups ); VerifyInitializeRollBackups( 2, iMaxRollBackups ); VerifyInitializeRollBackups( 3, iMaxRollBackups ); VerifyInitializeRollBackups( 4, iMaxRollBackups ); VerifyInitializeRollBackups( 5, iMaxRollBackups ); VerifyInitializeRollBackups( 6, iMaxRollBackups ); // Final we cap out at the max value VerifyInitializeRollBackups( 7, iMaxRollBackups ); VerifyInitializeRollBackups( 8, iMaxRollBackups ); } /// /// /// [Test,Ignore("Not Implemented: Want to test counted files limited up, to see that others are ?? ignored? deleted?")] public void TestInitialization3() { } /// /// /// [Test,Ignore("Not Implemented: Want to test counted files limited down, to see that others are ?? ignored? deleted?")] public void TestInitialization4() { } /// /// /// [Test,Ignore("Not Implemented: Want to test dated files with a limit, to see that others are ?? ignored? deleted?")] public void TestInitialization5() { } /// /// /// [Test,Ignore("Not Implemented: Want to test dated files with no limit, to see that others are ?? ignored? deleted?")] public void TestInitialization6() { } /// /// /// [Test,Ignore("Not Implemented: Want to test dated files with mixed dates existing, to see that other dates do not matter")] public void TestInitialization7() { } // // Helper functions to dig into the appender // private static ArrayList GetExistingFiles(string baseFilePath) { RollingFileAppender appender = new RollingFileAppender(); appender.SecurityContext = log4net.Util.NullSecurityContext.Instance; return (ArrayList)Utils.InvokeMethod(appender, "GetExistingFiles", baseFilePath); } private void InitializeRollBackups(RollingFileAppender appender, string baseFile, ArrayList arrayFiles) { Utils.InvokeMethod(appender, "InitializeRollBackups", baseFile, arrayFiles); } private int GetFieldCurSizeRollBackups(RollingFileAppender appender) { return (int)Utils.GetField(appender, "m_curSizeRollBackups"); } private void SetFieldCurSizeRollBackups(RollingFileAppender appender, int val) { Utils.SetField(appender, "m_curSizeRollBackups", val); } private void SetFieldMaxSizeRollBackups(RollingFileAppender appender, int val) { Utils.SetField(appender, "m_maxSizeRollBackups", val); } } [TestFixture] public class RollingFileAppenderSubClassTest : RollingFileAppender { [Test] public void TestComputeCheckPeriod() { RollingFileAppender rfa = new RollingFileAppender(); Assert.AreEqual(RollPoint.TopOfMinute, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH:mm"), "TopOfMinute pattern"); Assert.AreEqual(RollPoint.TopOfHour, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd HH"), "TopOfHour pattern"); Assert.AreEqual(RollPoint.HalfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd tt"), "HalfDay pattern"); Assert.AreEqual(RollPoint.TopOfDay, InvokeComputeCheckPeriod(rfa, ".yyyy-MM-dd"), "TopOfDay pattern"); Assert.AreEqual(RollPoint.TopOfMonth, InvokeComputeCheckPeriod(rfa, ".yyyy-MM"), "TopOfMonth pattern"); // Test invalid roll point Assert.AreEqual(RollPoint.InvalidRollPoint, InvokeComputeCheckPeriod(rfa, "..."), "TopOfMonth pattern"); } private static RollPoint InvokeComputeCheckPeriod(RollingFileAppender rollingFileAppender, string datePattern) { return (RollPoint)Utils.InvokeMethod(rollingFileAppender, "ComputeCheckPeriod", datePattern); } } }