|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
#nullable disable
namespace Microsoft.Build.UnitTests
{
public class TaskLoggingHelperTests
{
[Fact]
public void CheckMessageCode()
{
Task t = new MockTask();
// normal
string messageOnly;
string code = t.Log.ExtractMessageCode("AL001: This is a message.", out messageOnly);
code.ShouldBe("AL001");
messageOnly.ShouldBe("This is a message.");
// whitespace before code and after colon is ok
code = t.Log.ExtractMessageCode(" AL001: This is a message.", out messageOnly);
code.ShouldBe("AL001");
messageOnly.ShouldBe("This is a message.");
// whitespace after colon is not ok
code = t.Log.ExtractMessageCode("AL001 : This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe("AL001 : This is a message.");
// big code is ok
code = t.Log.ExtractMessageCode(" RESGEN7905001: This is a message.", out messageOnly);
code.ShouldBe("RESGEN7905001");
messageOnly.ShouldBe("This is a message.");
// small code is ok
code = t.Log.ExtractMessageCode("R7: This is a message.", out messageOnly);
code.ShouldBe("R7");
messageOnly.ShouldBe("This is a message.");
// lowercase code is ok
code = t.Log.ExtractMessageCode("alink3456: This is a message.", out messageOnly);
code.ShouldBe("alink3456");
messageOnly.ShouldBe("This is a message.");
// whitespace in code is not ok
code = t.Log.ExtractMessageCode(" RES 7905: This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe(" RES 7905: This is a message.");
// only digits in code is not ok
code = t.Log.ExtractMessageCode("7905: This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe("7905: This is a message.");
// only letters in code is not ok
code = t.Log.ExtractMessageCode("ALINK: This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe("ALINK: This is a message.");
// digits before letters in code is not ok
code = t.Log.ExtractMessageCode("6780ALINK: This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe("6780ALINK: This is a message.");
// mixing digits and letters in code is not ok
code = t.Log.ExtractMessageCode("LNK658A: This is a message.", out messageOnly);
code.ShouldBeNull();
messageOnly.ShouldBe("LNK658A: This is a message.");
}
/// <summary>
/// LogMessageFromStream parses the stream and decides if it is an error/warning/message.
/// The way it figures out if a message is an error or warning is by parsing it against
/// the canonical error/warning format. If it happens to be an error this method returns
/// true ... isError. This unit test ensures that passing a canonical error format results
/// in this method returning true and passing a non canonical message results in it returning
/// false
/// </summary>
[Fact]
public void CheckMessageFromStreamParsesErrorsAndMessagesCorrectly()
{
IBuildEngine2 mockEngine = new MockEngine3();
Task t = new MockTask();
t.BuildEngine = mockEngine;
// This should return true since I am passing a canonical error as the stream
StringReader sr = new StringReader("error MSB4040: There is no target in the project.");
t.Log.LogMessagesFromStream(sr, MessageImportance.High).ShouldBeTrue();
// This should return false since I am passing a canonical warning as the stream
sr = new StringReader("warning ABCD123MyCode: Felix is a cat.");
t.Log.LogMessagesFromStream(sr, MessageImportance.Low).ShouldBeFalse();
// This should return false since I am passing a non canonical message in the stream
sr = new StringReader("Hello World");
t.Log.LogMessagesFromStream(sr, MessageImportance.High).ShouldBeFalse();
}
[Fact]
public void LogCommandLine()
{
MockEngine3 mockEngine = new MockEngine3();
Task t = new MockTask();
t.BuildEngine = mockEngine;
t.Log.LogCommandLine("MySuperCommand");
mockEngine.Log.ShouldContain("MySuperCommand");
}
/// <summary>
/// This verifies that we don't try to run FormatString on a string
/// that isn't a resource (if we did, the unmatched curly would give an exception)
/// </summary>
[Fact]
public void LogMessageWithUnmatchedCurly()
{
MockEngine3 mockEngine = new MockEngine3();
Task t = new MockTask();
t.BuildEngine = mockEngine;
#pragma warning disable CA2241 // Format argument invalid. True! But exactly what we're testing here.
t.Log.LogMessage("echo {");
t.Log.LogMessageFromText("{1", MessageImportance.High);
t.Log.LogCommandLine("{2");
t.Log.LogWarning("{3");
t.Log.LogError("{4");
#pragma warning restore CA2241
mockEngine.AssertLogContains("echo {");
mockEngine.AssertLogContains("{1");
mockEngine.AssertLogContains("{2");
mockEngine.AssertLogContains("{3");
mockEngine.AssertLogContains("{4");
}
[Fact]
public void LogFromResources()
{
MockEngine3 mockEngine = new MockEngine3();
Task t = new MockTask();
t.BuildEngine = mockEngine;
t.Log.LogErrorFromResources("MySubcategoryResource", null,
"helpkeyword", "filename", 1, 2, 3, 4, "MyErrorResource", "foo");
t.Log.LogErrorFromResources("MyErrorResource", "foo");
t.Log.LogWarningFromResources("MySubcategoryResource", null,
"helpkeyword", "filename", 1, 2, 3, 4, "MyWarningResource", "foo");
t.Log.LogWarningFromResources("MyWarningResource", "foo");
mockEngine.Log.Contains("filename(1,2,3,4): Romulan error : Oops I wiped your harddrive foo").ShouldBeTrue();
mockEngine.Log.Contains("filename(1,2,3,4): Romulan warning : Be nice or I wipe your harddrive foo").ShouldBeTrue();
mockEngine.Log.Contains("Oops I wiped your harddrive foo").ShouldBeTrue();
mockEngine.Log.Contains("Be nice or I wipe your harddrive foo").ShouldBeTrue();
}
[Fact]
public void CheckLogMessageFromFile()
{
string file = null;
try
{
file = FileUtilities.GetTemporaryFileName();
string contents = @"a message here
error abcd12345: hey jude.
warning xy11: I wanna hold your hand.
this is not an error or warning
nor is this
error def222: norwegian wood";
// This closes the reader
File.WriteAllText(file, contents);
MockEngine3 mockEngine = new MockEngine3();
Task t = new MockTask();
t.BuildEngine = mockEngine;
t.Log.LogMessagesFromFile(file, MessageImportance.High);
mockEngine.Errors.ShouldBe(2);
mockEngine.Warnings.ShouldBe(1);
mockEngine.Messages.ShouldBe(3);
mockEngine = new MockEngine3();
t = new MockTask();
t.BuildEngine = mockEngine;
t.Log.LogMessagesFromFile(file);
mockEngine.Errors.ShouldBe(2);
mockEngine.Warnings.ShouldBe(1);
mockEngine.Messages.ShouldBe(3);
}
finally
{
if (file != null)
{
File.Delete(file);
}
}
}
[Fact]
public void CheckResourcesRegistered()
{
Should.Throw<InvalidOperationException>(() =>
{
Task t = new MockTask(false /*don't register resources*/);
try
{
t.Log.FormatResourceString("bogus");
}
catch (Exception e)
{
// so I can see the exception message in NUnit's "Standard Out" window
Console.WriteLine(e.Message);
throw;
}
});
}
/// <summary>
/// Verify the LogErrorFromException & LogWarningFromException methods
/// </summary>
[Fact]
public void TestLogFromException()
{
string message = "exception message";
string stackTrace = "TaskLoggingHelperTests.TestLogFromException";
MockEngine3 engine = new MockEngine3();
MockTask task = new MockTask();
task.BuildEngine = engine;
// need to throw and catch an exception so that its stack trace is initialized to something
try
{
Exception inner = new InvalidOperationException();
throw new Exception(message, inner);
}
catch (Exception e)
{
// log error without stack trace
task.Log.LogErrorFromException(e);
engine.AssertLogContains(message);
engine.AssertLogDoesntContain(stackTrace);
engine.AssertLogDoesntContain("InvalidOperationException");
engine.Log = string.Empty;
// log warning with stack trace
task.Log.LogWarningFromException(e);
engine.AssertLogContains(message);
engine.AssertLogDoesntContain(stackTrace);
engine.Log = string.Empty;
// log error with stack trace
task.Log.LogErrorFromException(e, true);
engine.AssertLogContains(message);
engine.AssertLogContains(stackTrace);
engine.AssertLogDoesntContain("InvalidOperationException");
engine.Log = string.Empty;
// log warning with stack trace
task.Log.LogWarningFromException(e, true);
engine.AssertLogContains(message);
engine.AssertLogContains(stackTrace);
engine.Log = string.Empty;
// log error with stack trace and inner exceptions
task.Log.LogErrorFromException(e, true, true, "foo.cs");
engine.AssertLogContains(message);
engine.AssertLogContains(stackTrace);
engine.AssertLogContains("InvalidOperationException");
}
}
/// <summary>
/// Verify that <see cref="TaskLoggingHelper.LogErrorFromException(Exception, bool, bool, string)" /> logs inner exceptions from an <see cref="AggregateException" />.
/// </summary>
[Fact]
public void TestLogFromExceptionWithAggregateException()
{
AggregateException aggregateException = new AggregateException(
new InvalidOperationException("The operation was invalid"),
new IOException("An I/O error occurred"));
MockEngine3 engine = new MockEngine3();
MockTask task = new MockTask
{
BuildEngine = engine
};
task.Log.LogErrorFromException(aggregateException);
engine.Errors.ShouldBe(2);
engine.AssertLogContains("The operation was invalid");
engine.AssertLogContains("An I/O error occurred");
}
}
}
|