File: TerminalLoggerConfiguration_Tests.cs
Web Access
Project: ..\..\..\src\Build.UnitTests\Microsoft.Build.Engine.UnitTests.csproj (Microsoft.Build.Engine.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
using System;
using System.Collections.Generic;
using Microsoft.Build.Framework.Telemetry;
using Microsoft.Build.UnitTests.Shared;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
 
namespace Microsoft.Build.UnitTests;
 
/// <summary>
/// End to end tests for the terminal logger configuration.
/// We need to execute msbuild process as tested code path is also in XMake.cs.
/// Also verifies that the telemetry is logged correctly.
/// Because we need to test the telemetry for the terminal logger, we need to use the MockLogger which outputs all telemetry properties.
/// </summary>
public class TerminalLoggerConfiguration_Tests : IDisposable
{
    private readonly TestEnvironment _env;
 
    private readonly string _cmd;
 
    public TerminalLoggerConfiguration_Tests(ITestOutputHelper output)
    {
        _env = TestEnvironment.Create(output);
 
        TransientTestFolder logFolder = _env.CreateFolder(createFolder: true);
        TransientTestFile projectFile = _env.CreateFile(logFolder, "myProj.proj", $"""
            <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Hello">
                <Target Name="Hello">
                  <Message Text="Hello, world!" />
                </Target>
            </Project>
            """);
        _cmd = $"{projectFile.Path} -target:Hello -logger:{typeof(MockLogger).FullName},{typeof(MockLogger).Assembly.Location};ReportTelemetry";
    }
 
    /// <summary>
    /// TearDown
    /// </summary>
    public void Dispose()
    {
        _env.Dispose();
    }
 
    [Theory]
    [InlineData("on")]
    [InlineData("true")]
    public void TerminalLoggerOn(string tlValue)
    {
        string output = RunnerUtilities.ExecMSBuild($"{_cmd} -tl:{tlValue}", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = true,
            TerminalLoggerDefault = bool.FalseString,
            TerminalLoggerDefaultSource = "msbuild",
            TerminalLoggerUserIntent = tlValue,
            TerminalLoggerUserIntentSource = "arg",
            ConsoleLogger = false,
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldBeTerminalLog(output);
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("auto")]
    public void TerminalLoggerWithTlAutoIsOff(string tlValue)
    {
        string output = RunnerUtilities.ExecMSBuild($"{_cmd} -tl:{tlValue}", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = false,
            TerminalLoggerDefault = bool.FalseString,
            TerminalLoggerDefaultSource = "msbuild",
            TerminalLoggerUserIntent = tlValue,
            TerminalLoggerUserIntentSource = "arg",
            ConsoleLogger = true,
            ConsoleLoggerType = "parallel",
            ConsoleLoggerVerbosity = "normal",
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldNotBeTerminalLog(output);
    }
 
    [Fact]
    public void TerminalLoggerDefaultByEnv()
    {
        _env.SetEnvironmentVariable("DOTNET_CLI_CONFIGURE_MSBUILD_TERMINAL_LOGGER", bool.TrueString);
        string output = RunnerUtilities.ExecMSBuild($"{_cmd} -tlp:default={bool.TrueString}", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = true,
            TerminalLoggerDefault = bool.TrueString,
            TerminalLoggerDefaultSource = "DOTNET_CLI_CONFIGURE_MSBUILD_TERMINAL_LOGGER",
            TerminalLoggerUserIntent = null,
            TerminalLoggerUserIntentSource = null,
            ConsoleLogger = false,
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldBeTerminalLog(output);
    }
 
    [Theory]
    [InlineData("MSBUILDLIVELOGGER")]
    [InlineData("MSBUILDTERMINALLOGGER")]
    public void TerminalLoggerOnByEnv(string envVarSource)
    {
        _env.SetEnvironmentVariable(envVarSource, bool.TrueString);
        string output = RunnerUtilities.ExecMSBuild($"{_cmd}", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = true,
            TerminalLoggerDefault = bool.FalseString,
            TerminalLoggerDefaultSource = "msbuild",
            TerminalLoggerUserIntent = bool.TrueString,
            TerminalLoggerUserIntentSource = envVarSource,
            ConsoleLogger = false,
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldBeTerminalLog(output);
    }
 
    [Theory]
    [InlineData("on")]
    [InlineData("true")]
    public void TerminalLoggerDefaultOn(string defaultValue)
    {
        string output = RunnerUtilities.ExecMSBuild($"{_cmd} -tlp:default={defaultValue}", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = true,
            TerminalLoggerDefault = defaultValue,
            TerminalLoggerDefaultSource = "sdk",
            TerminalLoggerUserIntent = null,
            TerminalLoggerUserIntentSource = null,
            ConsoleLogger = false,
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldBeTerminalLog(output);
    }
 
    [Theory]
    [InlineData("off")]
    [InlineData("false")]
    public void TerminalLoggerDefaultOff(string defaultValue)
    {
        string output = RunnerUtilities.ExecMSBuild($"{_cmd} -tlp:default={defaultValue} -v:m", out bool success);
        success.ShouldBeTrue();
 
        LoggingConfigurationTelemetry expectedTelemetry = new LoggingConfigurationTelemetry
        {
            TerminalLogger = false,
            TerminalLoggerDefault = defaultValue,
            TerminalLoggerDefaultSource = "sdk",
            TerminalLoggerUserIntent = null,
            TerminalLoggerUserIntentSource = null,
            ConsoleLogger = true,
            ConsoleLoggerVerbosity = "minimal",
            ConsoleLoggerType = "parallel",
            FileLogger = false,
        };
 
        foreach (KeyValuePair<string, string> pair in expectedTelemetry.GetProperties())
        {
            output.ShouldContain($"{expectedTelemetry.EventName}:{pair.Key}={pair.Value}");
        }
 
        // Test if there is ANSI clear screen sequence, which shall only occur when the terminal logger was enabled.
        ShouldNotBeTerminalLog(output);
    }
 
    [Theory]
    [InlineData("1")]
    [InlineData("0")]
    public void TerminalLoggerOnInvalidProjectBuild(string msbuildinprocnodeState)
    {
        _ = _env.SetEnvironmentVariable("MSBUILDNOINPROCNODE", msbuildinprocnodeState);
 
        string output = RunnerUtilities.ExecMSBuild(
            $"{_cmd} -tl:true",
            out bool success);
 
        success.ShouldBeTrue();
        ShouldBeTerminalLog(output);
        output.ShouldContain("Build succeeded.");
    }
 
    private static void ShouldBeTerminalLog(string output) => output.ShouldContain("\x1b[?25l");
 
    private static void ShouldNotBeTerminalLog(string output) => output.ShouldNotContain("\x1b[?25l");
}