|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable 436
using System;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.Shared;
using Xunit;
using EventSourceSink = Microsoft.Build.BackEnd.Logging.EventSourceSink;
using Project = Microsoft.Build.Evaluation.Project;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public class FileLogger_Tests
{
/// <summary>
/// Basic test of the file logger. Writes to a log file in the temp directory.
/// </summary>
[Fact]
public void Basic()
{
FileLogger fileLogger = new FileLogger();
string logFile = FileUtilities.GetTemporaryFile();
fileLogger.Parameters = "verbosity=Normal;logfile=" + logFile;
Project project = ObjectModelHelpers.CreateInMemoryProject(@"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`Build`>
<Message Text=`Hello world from the FileLogger`/>
</Target>
</Project>
");
project.Build(fileLogger);
project.ProjectCollection.UnregisterAllLoggers();
string log = File.ReadAllText(logFile);
Assert.Contains("Hello world from the FileLogger", log); // "Log should have contained message"
File.Delete(logFile);
}
/// <summary>
/// Basic case of logging a message to a file
/// Verify it logs and encoding is ANSI
/// </summary>
[Fact]
public void BasicNoExistingFile()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
VerifyFileContent(log, "message here");
byte[] content = ReadRawBytes(log);
Assert.Equal((byte)109, content[0]); // 'm'
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Invalid file should error nicely
/// </summary>
[Fact]
[Trait("Category", "netcore-osx-failing")]
[Trait("Category", "netcore-linux-failing")]
public void InvalidFile()
{
Assert.Throws<LoggerException>(() =>
{
string log = null;
try
{
SetUpFileLoggerAndLogMessage("logfile=||invalid||", new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
});
}
/// <summary>
/// Specific verbosity overrides global verbosity
/// </summary>
[Fact]
public void SpecificVerbosity()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
FileLogger fl = new FileLogger();
EventSourceSink es = new EventSourceSink();
fl.Parameters = "verbosity=diagnostic;logfile=" + log; // diagnostic specific setting
fl.Verbosity = LoggerVerbosity.Quiet; // quiet global setting
fl.Initialize(es);
fl.MessageHandler(null, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
fl.Shutdown();
// expect message to appear because diagnostic not quiet verbosity was used
VerifyFileContent(log, "message here");
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Test the short hand verbosity settings for the file logger
/// </summary>
[Fact]
public void ValidVerbosities()
{
string[] verbositySettings = new string[] { "Q", "quiet", "m", "minimal", "N", "normal", "d", "detailed", "diag", "DIAGNOSTIC" };
LoggerVerbosity[] verbosityEnumerations = new LoggerVerbosity[] {LoggerVerbosity.Quiet, LoggerVerbosity.Quiet,
LoggerVerbosity.Minimal, LoggerVerbosity.Minimal,
LoggerVerbosity.Normal, LoggerVerbosity.Normal,
LoggerVerbosity.Detailed, LoggerVerbosity.Detailed,
LoggerVerbosity.Diagnostic, LoggerVerbosity.Diagnostic};
for (int i = 0; i < verbositySettings.Length; i++)
{
FileLogger fl = new FileLogger();
fl.Parameters = "verbosity=" + verbositySettings[i] + ";";
EventSourceSink es = new EventSourceSink();
fl.Initialize(es);
fl.Shutdown();
Assert.Equal(fl.Verbosity, verbosityEnumerations[i]);
}
// Do the same using the v shorthand
for (int i = 0; i < verbositySettings.Length; i++)
{
FileLogger fl = new FileLogger();
fl.Parameters = "v=" + verbositySettings[i] + ";";
EventSourceSink es = new EventSourceSink();
fl.Initialize(es);
fl.Shutdown();
Assert.Equal(fl.Verbosity, verbosityEnumerations[i]);
}
}
/// <summary>
/// Invalid verbosity setting
/// </summary>
[Fact]
public void InvalidVerbosity()
{
Assert.Throws<LoggerException>(() =>
{
FileLogger fl = new FileLogger();
fl.Parameters = "verbosity=CookiesAndCream";
EventSourceSink es = new EventSourceSink();
fl.Initialize(es);
});
}
/// <summary>
/// Invalid encoding setting
/// </summary>
[Fact]
public void InvalidEncoding()
{
Assert.Throws<LoggerException>(() =>
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
FileLogger fl = new FileLogger();
EventSourceSink es = new EventSourceSink();
fl.Parameters = "encoding=foo;logfile=" + log;
fl.Initialize(es);
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
});
}
/// <summary>
/// Valid encoding setting
/// </summary>
[Fact]
public void ValidEncoding()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
SetUpFileLoggerAndLogMessage("encoding=utf-16;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
byte[] content = ReadRawBytes(log);
// FF FE is the BOM for UTF16
Assert.Equal((byte)255, content[0]);
Assert.Equal((byte)254, content[1]);
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Valid encoding setting
/// </summary>
[Fact]
public void ValidEncoding2()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
SetUpFileLoggerAndLogMessage("encoding=utf-8;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
byte[] content = ReadRawBytes(log);
// EF BB BF is the BOM for UTF8
Assert.Equal((byte)239, content[0]);
Assert.Equal((byte)187, content[1]);
Assert.Equal((byte)191, content[2]);
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Read the raw byte content of a file
/// </summary>
/// <param name="log"></param>
/// <returns></returns>
private byte[] ReadRawBytes(string log)
{
byte[] content;
using (FileStream stream = new FileStream(log, FileMode.Open))
{
content = new byte[stream.Length];
for (int i = 0; i < stream.Length; i++)
{
content[i] = (byte)stream.ReadByte();
}
}
return content;
}
/// <summary>
/// Logging a message to a file that already exists should overwrite it
/// </summary>
[Fact]
public void BasicExistingFileNoAppend()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
WriteContentToFile(log);
SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
VerifyFileContent(log, "message here");
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Logging to a file that already exists, with "append" set, should append
/// </summary>
[Fact]
public void BasicExistingFileAppend()
{
string log = null;
try
{
log = FileUtilities.GetTemporaryFileName();
WriteContentToFile(log);
SetUpFileLoggerAndLogMessage("append;logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
VerifyFileContent(log, "existing content\nmessage here");
}
finally
{
if (log != null)
{
File.Delete(log);
}
}
}
/// <summary>
/// Logging to a file in a directory that doesn't exists
/// </summary>
[Fact]
public void BasicNoExistingDirectory()
{
string directory = Path.Combine(ObjectModelHelpers.TempProjectDir, Guid.NewGuid().ToString("N"));
string log = Path.Combine(directory, "build.log");
Assert.False(Directory.Exists(directory));
Assert.False(File.Exists(log));
try
{
SetUpFileLoggerAndLogMessage("logfile=" + log, new BuildMessageEventArgs("message here", null, null, MessageImportance.High));
VerifyFileContent(log, "message here");
}
finally
{
ObjectModelHelpers.DeleteDirectory(directory);
}
}
[Theory]
[InlineData("warningsonly")]
[InlineData("errorsonly")]
[InlineData("errorsonly;warningsonly")]
public void EmptyErrorLogUsingWarningsErrorsOnly(string loggerOption)
{
using (var env = TestEnvironment.Create())
{
var logFile = env.CreateFile(".log").Path;
// Note: Only the ParallelConsoleLogger supports this scenario (log file empty on no error/warn). We
// need to explicitly enable it here with the 'ENABLEMPLOGGING' flag.
FileLogger fileLogger = new FileLogger { Parameters = $"{loggerOption};logfile={logFile};ENABLEMPLOGGING" };
Project project = ObjectModelHelpers.CreateInMemoryProject(@"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`Build`>
<Message Text=`Hello world from the FileLogger`/>
</Target>
</Project>");
project.Build(fileLogger);
project.ProjectCollection.UnregisterAllLoggers();
// File should exist and be 0 length (no summary information, etc.)
var result = new FileInfo(logFile);
Assert.True(result.Exists);
Assert.Equal(0, new FileInfo(logFile).Length);
}
}
/// <summary>
/// File logger is writting the verbosity level as soon the build starts.
/// </summary>
[Theory]
[InlineData(LoggerVerbosity.Quiet, false)]
[InlineData(LoggerVerbosity.Minimal, false)]
[InlineData(LoggerVerbosity.Normal, true)]
[InlineData(LoggerVerbosity.Detailed, true)]
[InlineData(LoggerVerbosity.Diagnostic, true)]
public void LogVerbosityMessage(LoggerVerbosity loggerVerbosity, bool shouldContain)
{
using (var testEnvironment = TestEnvironment.Create())
{
var fileLogger = new FileLogger
{
Verbosity = loggerVerbosity
};
var logFile = testEnvironment.CreateFile(".log");
fileLogger.Parameters = "logfile=" + logFile.Path;
Project project = ObjectModelHelpers.CreateInMemoryProject(@"
<Project ToolsVersion=`msbuilddefaulttoolsversion` xmlns=`msbuildnamespace`>
<Target Name=`Build` />
</Project>
");
project.Build(fileLogger);
project.ProjectCollection.UnregisterAllLoggers();
string log = File.ReadAllText(logFile.Path);
var message = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("LogLoggerVerbosity", loggerVerbosity);
if (shouldContain)
{
Assert.Contains(message, log);
}
else
{
Assert.DoesNotContain(message, log);
}
}
}
/// <summary>
/// Writes a string to a file.
/// </summary>
/// <param name="log"></param>
private void WriteContentToFile(string log)
{
using (StreamWriter sw = FileUtilities.OpenWrite(log, false))
{
sw.WriteLine("existing content");
}
}
/// <summary>
/// Creates a FileLogger, sets its parameters and initializes it,
/// logs a message to it, and calls shutdown
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
private void SetUpFileLoggerAndLogMessage(string parameters, BuildMessageEventArgs message)
{
FileLogger fl = new FileLogger();
EventSourceSink es = new EventSourceSink();
fl.Parameters = parameters;
fl.Initialize(es);
fl.MessageHandler(null, message);
fl.Shutdown();
return;
}
/// <summary>
/// Verifies that a file contains exactly the expected content.
/// </summary>
/// <param name="file"></param>
/// <param name="expectedContent"></param>
private void VerifyFileContent(string file, string expectedContent)
{
string actualContent;
using (StreamReader sr = FileUtilities.OpenRead(file))
{
actualContent = sr.ReadToEnd();
}
string[] actualLines = actualContent.Split(MSBuildConstants.NewlineChar, StringSplitOptions.RemoveEmptyEntries);
string[] expectedLines = expectedContent.Split(MSBuildConstants.NewlineChar, StringSplitOptions.RemoveEmptyEntries);
Assert.Equal(expectedLines.Length, actualLines.Length);
for (int i = 0; i < expectedLines.Length; i++)
{
Assert.Equal(expectedLines[i].Trim(), actualLines[i].Trim());
}
}
#region DistributedLogger
/// <summary>
/// Check the ability of the distributed logger to correctly tell its internal file logger where to log the file
/// </summary>
[Fact]
public void DistributedFileLoggerParameters()
{
DistributedFileLogger fileLogger = new DistributedFileLogger();
try
{
fileLogger.NodeId = 0;
fileLogger.Initialize(new EventSourceSink());
Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=msbuild0.log;", StringComparison.OrdinalIgnoreCase));
fileLogger.Shutdown();
fileLogger.NodeId = 3;
fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
fileLogger.Initialize(new EventSourceSink());
Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log") + ";", StringComparison.OrdinalIgnoreCase));
fileLogger.Shutdown();
fileLogger.NodeId = 4;
fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile.log");
fileLogger.Initialize(new EventSourceSink());
Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log") + ";", StringComparison.OrdinalIgnoreCase));
fileLogger.Shutdown();
Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
fileLogger.NodeId = 1;
fileLogger.Parameters = "logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile.log");
fileLogger.Initialize(new EventSourceSink());
Assert.Equal(0, string.Compare(fileLogger.InternalFilelogger.Parameters, "ForceNoAlign;ShowEventId;ShowCommandLine;logfile=" + Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log") + ";", StringComparison.OrdinalIgnoreCase));
fileLogger.Shutdown();
}
finally
{
if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "tempura")))
{
File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "tempura", "mylogfile1.log"));
FileUtilities.DeleteWithoutTrailingBackslash(Path.Combine(Directory.GetCurrentDirectory(), "tempura"));
}
File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile0.log"));
File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile3.log"));
File.Delete(Path.Combine(Directory.GetCurrentDirectory(), "mylogfile4.log"));
}
}
[Fact]
public void DistributedLoggerNullEmpty()
{
Assert.Throws<LoggerException>(() =>
{
DistributedFileLogger fileLogger = new DistributedFileLogger();
fileLogger.NodeId = 0;
fileLogger.Initialize(new EventSourceSink());
fileLogger.NodeId = 1;
fileLogger.Parameters = "logfile=";
fileLogger.Initialize(new EventSourceSink());
Assert.Fail();
});
}
#endregion
}
}
|