// 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.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using Microsoft.Build.BackEnd;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
using Microsoft.Build.UnitTests.BackEnd;
using Shouldly;
using Xunit;

#nullable disable

namespace Microsoft.Build.UnitTests.Logging
{
    /// <summary>
    /// Test the logging service component
    /// </summary>
    public class LoggingService_Tests
    {
        #region Data
        /// <summary>
        /// An already instantiated and initialized service.
        /// This is used so the host object does not need to be
        /// used in every test method.
        /// </summary>
        private LoggingService _initializedService;

        #endregion

        #region Setup

        /// <summary>
        /// This method is run before each test case is run.
        /// We instantiate and initialize a new logging service each time
        /// </summary>
        public LoggingService_Tests()
        {
            InitializeLoggingService();
        }

        #endregion

        #region Test BuildComponent Methods

        /// <summary>
        /// Verify the CreateLogger method create a LoggingService in both Synchronous mode
        /// and Asynchronous mode.
        /// </summary>
        [Fact]
        public void CreateLogger()
        {
            // Generic host which has some default properties set inside of it
            IBuildComponent logServiceComponent = (IBuildComponent)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);

            // Create a synchronous logging service and do some quick checks
            Assert.NotNull(logServiceComponent);
            LoggingService logService = (LoggingService)logServiceComponent;
            Assert.Equal(LoggerMode.Synchronous, logService.LoggingMode);
            Assert.Equal(LoggingServiceState.Instantiated, logService.ServiceState);

            // Create an asynchronous logging service
            logServiceComponent = (IBuildComponent)LoggingService.CreateLoggingService(LoggerMode.Asynchronous, 1);
            Assert.NotNull(logServiceComponent);
            logService = (LoggingService)logServiceComponent;
            Assert.Equal(LoggerMode.Asynchronous, logService.LoggingMode);
            Assert.Equal(LoggingServiceState.Instantiated, logService.ServiceState);

            // Shutdown logging thread
            logServiceComponent.InitializeComponent(new MockHost());
            logServiceComponent.ShutdownComponent();
            Assert.Equal(LoggingServiceState.Shutdown, logService.ServiceState);
        }

        /// <summary>
        /// Test the IBuildComponent method InitializeComponent, make sure the component gets the parameters it expects
        /// </summary>
        [Fact]
        public void InitializeComponent()
        {
            IBuildComponent logServiceComponent = (IBuildComponent)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);

            BuildParameters parameters = new BuildParameters();
            parameters.MaxNodeCount = 4;
            parameters.OnlyLogCriticalEvents = true;

            IBuildComponentHost loggingHost = new MockHost(parameters);

            // Make sure we are in the Instantiated state before initializing
            Assert.Equal(LoggingServiceState.Instantiated, ((LoggingService)logServiceComponent).ServiceState);

            logServiceComponent.InitializeComponent(loggingHost);

            // Make sure that the parameters in the host are set in the logging service
            LoggingService service = (LoggingService)logServiceComponent;
            Assert.Equal(LoggingServiceState.Initialized, service.ServiceState);
            Assert.Equal(4, service.MaxCPUCount);
            Assert.True(service.OnlyLogCriticalEvents);
        }

        /// <summary>
        /// Verify the correct exception is thrown when a null Component host is passed in
        /// </summary>
        [Fact]
        public void InitializeComponentNullHost()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                IBuildComponent logServiceComponent = (IBuildComponent)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
                logServiceComponent.InitializeComponent(null);
            });
        }
        /// <summary>
        /// Verify an exception is thrown if in initialized is called after the service has been shutdown
        /// </summary>
        [Fact]
        public void InitializeComponentAfterShutdown()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.ShutdownComponent();
                _initializedService.InitializeComponent(new MockHost());
            });
        }
        /// <summary>
        /// Verify the correct exceptions are thrown if the loggers crash
        /// when they are shutdown
        /// </summary>
        [Fact]
        public void ShutDownComponentExceptionsInForwardingLogger()
        {
            // Cause a logger exception in the shutdown of the logger
            string className = "Microsoft.Build.UnitTests.Logging.LoggingService_Tests+ShutdownLoggerExceptionFL";
            Type exceptionType = typeof(LoggerException);
            VerifyShutdownExceptions(null, className, exceptionType);
            Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState);

            // Cause a general exception which should result in an InternalLoggerException
            className = "Microsoft.Build.UnitTests.Logging.LoggingService_Tests+ShutdownGeneralExceptionFL";
            exceptionType = typeof(InternalLoggerException);
            VerifyShutdownExceptions(null, className, exceptionType);
            Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState);

            // Cause a StackOverflow exception in the shutdown of the logger
            // this kind of exception should not be caught
            className = "Microsoft.Build.UnitTests.Logging.LoggingService_Tests+ShutdownStackoverflowExceptionFL";
            exceptionType = typeof(StackOverflowException);
            VerifyShutdownExceptions(null, className, exceptionType);

            Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState);
        }

        /// <summary>
        /// Verify the correct exceptions are thrown when ILoggers
        /// throw exceptions during shutdown
        /// </summary>
        [Fact]
        public void ShutDownComponentExceptionsInLogger()
        {
            LoggerThrowException logger = new LoggerThrowException(true, false, new LoggerException("Hello"));
            VerifyShutdownExceptions(logger, null, typeof(LoggerException));

            logger = new LoggerThrowException(true, false, new Exception("boo"));
            VerifyShutdownExceptions(logger, null, typeof(InternalLoggerException));

            logger = new LoggerThrowException(true, false, new StackOverflowException());
            VerifyShutdownExceptions(logger, null, typeof(StackOverflowException));

            Assert.Equal(LoggingServiceState.Shutdown, _initializedService.ServiceState);
        }

        /// <summary>
        /// Make sure an exception is thrown if shutdown is called
        /// more than once
        /// </summary>
        [Fact]
        public void DoubleShutdown()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.ShutdownComponent();
                _initializedService.ShutdownComponent();
            });
        }
        #endregion

        #region RegisterLogger
        /// <summary>
        /// Verify we get an exception when a null logger is passed in
        /// </summary>
        [Fact]
        public void NullLogger()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.RegisterLogger(null);
            });
        }
        /// <summary>
        /// Verify we get an exception when we try and register a logger
        /// and the system has already shutdown
        /// </summary>
        [Fact]
        public void RegisterLoggerServiceShutdown()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.ShutdownComponent();
                RegularILogger regularILogger = new RegularILogger();
                _initializedService.RegisterLogger(regularILogger);
            });
        }
        /// <summary>
        /// Verify a logger exception when initializing a logger is rethrown
        /// as a logger exception
        /// </summary>
        [Fact]
        public void LoggerExceptionInInitialize()
        {
            Assert.Throws<LoggerException>(() =>
            {
                LoggerThrowException exceptionLogger = new LoggerThrowException(false, true, new LoggerException());
                _initializedService.RegisterLogger(exceptionLogger);
            });
        }
        /// <summary>
        /// Verify a general exception when initializing a logger is wrapped
        /// as a InternalLogger exception
        /// </summary>
        [Fact]
        public void GeneralExceptionInInitialize()
        {
            Assert.Throws<InternalLoggerException>(() =>
            {
                LoggerThrowException exceptionLogger = new LoggerThrowException(false, true, new Exception());
                _initializedService.RegisterLogger(exceptionLogger);
            });
        }

        /// <summary>
        /// Verify a critical exception is not wrapped
        /// </summary>
        [Fact]
        public void ILoggerExceptionInInitialize()
        {
            Assert.Throws<StackOverflowException>(() =>
            {
                LoggerThrowException exceptionLogger = new LoggerThrowException(false, true, new StackOverflowException());
                _initializedService.RegisterLogger(exceptionLogger);
            });
        }

        /// <summary>
        /// Register an good Logger and verify it was registered.
        /// </summary>
        [Fact]
        public void RegisterILoggerAndINodeLoggerGood()
        {
            ConsoleLogger consoleLogger = new ConsoleLogger();
            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterLogger(consoleLogger));
            Assert.True(_initializedService.RegisterLogger(regularILogger));
            Assert.NotNull(_initializedService.RegisteredLoggerTypeNames);

            // Should have 2 central loggers and 1 forwarding logger
            Assert.Equal(3, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.BackEnd.Logging.CentralForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.Logging.ConsoleLogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 1 event sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.RegisteredSinkNames);
        }

        /// <summary>
        /// Try and register the same logger multiple times
        /// </summary>
        [Fact]
        public void RegisterDuplicateLogger()
        {
            ConsoleLogger consoleLogger = new ConsoleLogger();
            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterLogger(consoleLogger));
            Assert.False(_initializedService.RegisterLogger(consoleLogger));
            Assert.True(_initializedService.RegisterLogger(regularILogger));
            Assert.False(_initializedService.RegisterLogger(regularILogger));
            Assert.NotNull(_initializedService.RegisteredLoggerTypeNames);

            Assert.Equal(3, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.BackEnd.Logging.CentralForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.Logging.ConsoleLogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 1 event sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.RegisteredSinkNames);
        }

        #endregion

        #region RegisterDistributedLogger
        /// <summary>
        /// Verify we get an exception when a null logger forwarding logger is passed in
        /// </summary>
        [Fact]
        public void NullForwardingLogger()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.RegisterDistributedLogger(null, null);
            });
        }
        /// <summary>
        /// Verify we get an exception when we try and register a distributed logger
        /// and the system has already shutdown
        /// </summary>
        [Fact]
        public void RegisterDistributedLoggerServiceShutdown()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.ShutdownComponent();
                string className = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
#if FEATURE_ASSEMBLY_LOCATION
                LoggerDescription description = CreateLoggerDescription(className, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
                LoggerDescription description = CreateLoggerDescription(className, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif
                _initializedService.RegisterDistributedLogger(null, description);
            });
        }
        /// <summary>
        /// Register both a good central logger and a good forwarding logger
        /// </summary>
        [Fact]
        public void RegisterGoodDistributedAndCentralLogger()
        {
            string configurableClassName = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
            string distributedClassName = "Microsoft.Build.Logging.DistributedFileLogger";
#if FEATURE_ASSEMBLY_LOCATION
            LoggerDescription configurableDescription = CreateLoggerDescription(configurableClassName, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
            LoggerDescription distributedDescription = CreateLoggerDescription(distributedClassName, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
            LoggerDescription configurableDescription = CreateLoggerDescription(configurableClassName, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
            LoggerDescription distributedDescription = CreateLoggerDescription(distributedClassName, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif

            DistributedFileLogger fileLogger = new DistributedFileLogger();
            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterDistributedLogger(regularILogger, configurableDescription));
            Assert.True(_initializedService.RegisterDistributedLogger(null, distributedDescription));
            Assert.NotNull(_initializedService.RegisteredLoggerTypeNames);

            // Should have 2 central loggers and 2 forwarding logger
            Assert.Equal(4, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.Logging.ConfigurableForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.Logging.DistributedFileLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.BackEnd.Logging.NullCentralLogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 2 event sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Equal(2, _initializedService.RegisteredSinkNames.Count);
            Assert.Equal(2, _initializedService.LoggerDescriptions.Count);
        }

        /// <summary>
        /// Have a one forwarding logger which forwards build started and finished and have one which does not and a regular logger. Expect the central loggers to all get
        /// one build started and one build finished event only.
        /// </summary>
        [Fact]
        public void RegisterGoodDistributedAndCentralLoggerTestBuildStartedFinished()
        {
            string configurableClassNameA = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
            string configurableClassNameB = "Microsoft.Build.Logging.ConfigurableForwardingLogger";

#if FEATURE_ASSEMBLY_LOCATION
            LoggerDescription configurableDescriptionA = CreateLoggerDescription(configurableClassNameA, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
            LoggerDescription configurableDescriptionB = CreateLoggerDescription(configurableClassNameB, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
            LoggerDescription configurableDescriptionA = CreateLoggerDescription(configurableClassNameA, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
            LoggerDescription configurableDescriptionB = CreateLoggerDescription(configurableClassNameB, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif

            RegularILogger regularILoggerA = new RegularILogger();
            RegularILogger regularILoggerB = new RegularILogger();
            RegularILogger regularILoggerC = new RegularILogger();
            Assert.True(_initializedService.RegisterDistributedLogger(regularILoggerA, configurableDescriptionA));
            Assert.True(_initializedService.RegisterDistributedLogger(regularILoggerB, configurableDescriptionB));
            Assert.True(_initializedService.RegisterLogger(regularILoggerC));
            Assert.NotNull(_initializedService.RegisteredLoggerTypeNames);

            _initializedService.LogBuildStarted();
            Assert.Equal(1, regularILoggerA.BuildStartedCount);
            Assert.Equal(1, regularILoggerB.BuildStartedCount);
            Assert.Equal(1, regularILoggerC.BuildStartedCount);

            _initializedService.LogBuildFinished(true);
            Assert.Equal(1, regularILoggerA.BuildFinishedCount);
            Assert.Equal(1, regularILoggerB.BuildFinishedCount);
            Assert.Equal(1, regularILoggerC.BuildFinishedCount);

            // Make sure if we call build started again we only get one other build started event.
            _initializedService.LogBuildStarted();
            Assert.Equal(2, regularILoggerA.BuildStartedCount);
            Assert.Equal(2, regularILoggerB.BuildStartedCount);
            Assert.Equal(2, regularILoggerC.BuildStartedCount);

            // Make sure if we call build finished again we only get one other build finished event.
            _initializedService.LogBuildFinished(true);
            Assert.Equal(2, regularILoggerA.BuildFinishedCount);
            Assert.Equal(2, regularILoggerB.BuildFinishedCount);
            Assert.Equal(2, regularILoggerC.BuildFinishedCount);
        }

        /// <summary>
        /// Try and register a duplicate central logger
        /// </summary>
        [Fact]
        public void RegisterDuplicateCentralLogger()
        {
            string className = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
#if FEATURE_ASSEMBLY_LOCATION
            LoggerDescription description = CreateLoggerDescription(className, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
            LoggerDescription description = CreateLoggerDescription(className, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif

            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterDistributedLogger(regularILogger, description));
            Assert.False(_initializedService.RegisterDistributedLogger(regularILogger, description));

            // Should have 2 central loggers and 1 forwarding logger
            Assert.Equal(2, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.Logging.ConfigurableForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 1 sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.LoggerDescriptions);
        }

        /// <summary>
        /// Try and register a duplicate Forwarding logger
        /// </summary>
        [Fact]
        public void RegisterDuplicateForwardingLoggerLogger()
        {
            string className = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
#if FEATURE_ASSEMBLY_LOCATION
            LoggerDescription description = CreateLoggerDescription(className, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
            LoggerDescription description = CreateLoggerDescription(className, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif

            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterDistributedLogger(regularILogger, description));
            Assert.True(_initializedService.RegisterDistributedLogger(null, description));
            Assert.Equal(4, _initializedService.RegisteredLoggerTypeNames.Count);

            // Verify there are two versions in the type names, one for each description
            int countForwardingLogger = 0;
            foreach (string loggerName in _initializedService.RegisteredLoggerTypeNames)
            {
                if (String.Equals("Microsoft.Build.Logging.ConfigurableForwardingLogger", loggerName, StringComparison.OrdinalIgnoreCase))
                {
                    countForwardingLogger++;
                }
            }

            Assert.Equal(2, countForwardingLogger);
            Assert.Contains("Microsoft.Build.BackEnd.Logging.NullCentralLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 2 sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Equal(2, _initializedService.RegisteredSinkNames.Count);
            Assert.Equal(2, _initializedService.LoggerDescriptions.Count);
        }

        #endregion

        #region RegisterLoggerDescriptions
        /// <summary>
        /// Verify we get an exception when a null description collection is passed in
        /// </summary>
        [Fact]
        public void NullDescriptionCollection()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.InitializeNodeLoggers(null, new EventSourceSink(), 3);
            });
        }
        /// <summary>
        /// Verify we get an exception when an empty description collection is passed in
        /// </summary>
        [Fact]
        public void EmptyDescriptionCollection()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                _initializedService.InitializeNodeLoggers(new List<LoggerDescription>(), new EventSourceSink(), 3);
            });
        }
        /// <summary>
        /// Verify we get an exception when we try and register a description and the component has already shutdown
        /// </summary>
        [Fact]
        public void NullForwardingLoggerSink()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                string className = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
#if FEATURE_ASSEMBLY_LOCATION
                LoggerDescription description = CreateLoggerDescription(className, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
                LoggerDescription description = CreateLoggerDescription(className, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif
                _initializedService.ShutdownComponent();
                List<LoggerDescription> tempList = new List<LoggerDescription>();
                tempList.Add(description);
                _initializedService.InitializeNodeLoggers(tempList, new EventSourceSink(), 2);
            });
        }
        /// <summary>
        /// Register both a good central logger and a good forwarding logger
        /// </summary>
        [Fact]
        public void RegisterGoodDiscriptions()
        {
            string configurableClassName = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
            string distributedClassName = "Microsoft.Build.BackEnd.Logging.CentralForwardingLogger";
            EventSourceSink sink = new EventSourceSink();
            EventSourceSink sink2 = new EventSourceSink();
            List<LoggerDescription> loggerDescriptions = new List<LoggerDescription>();
#if FEATURE_ASSEMBLY_LOCATION
            loggerDescriptions.Add(CreateLoggerDescription(configurableClassName, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true));
            loggerDescriptions.Add(CreateLoggerDescription(distributedClassName, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true));
#else
            loggerDescriptions.Add(CreateLoggerDescription(configurableClassName, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true));
            loggerDescriptions.Add(CreateLoggerDescription(distributedClassName, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true));
#endif

            // Register some descriptions with a sink
            _initializedService.InitializeNodeLoggers(loggerDescriptions, sink, 1);

            // Register the same descriptions with another sink (so we can see that another sink was added)
            _initializedService.InitializeNodeLoggers(loggerDescriptions, sink2, 1);

            // Register the descriptions again with the same sink so we can verify that another sink was not created
            _initializedService.InitializeNodeLoggers(loggerDescriptions, sink, 1);

            Assert.NotNull(_initializedService.RegisteredLoggerTypeNames);

            // Should have 6 forwarding logger. three of each type
            Assert.Equal(6, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.Logging.ConfigurableForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.BackEnd.Logging.CentralForwardingLogger", _initializedService.RegisteredLoggerTypeNames);

            int countForwardingLogger = 0;
            foreach (string loggerName in _initializedService.RegisteredLoggerTypeNames)
            {
                if (String.Equals("Microsoft.Build.Logging.ConfigurableForwardingLogger", loggerName, StringComparison.OrdinalIgnoreCase))
                {
                    countForwardingLogger++;
                }
            }

            // Should be 3, one for each call to RegisterLoggerDescriptions
            Assert.Equal(3, countForwardingLogger);

            countForwardingLogger = 0;
            foreach (string loggerName in _initializedService.RegisteredLoggerTypeNames)
            {
                if (String.Equals("Microsoft.Build.BackEnd.Logging.CentralForwardingLogger", loggerName, StringComparison.OrdinalIgnoreCase))
                {
                    countForwardingLogger++;
                }
            }

            // Should be 3, one for each call to RegisterLoggerDescriptions
            Assert.Equal(3, countForwardingLogger);

            // Should have 2 event sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Equal(2, _initializedService.RegisteredSinkNames.Count);

            // There should not be any (this method is to be called on a child node)
            Assert.Empty(_initializedService.LoggerDescriptions);
        }

        /// <summary>
        /// Try and register a duplicate central logger
        /// </summary>
        [Fact]
        public void RegisterDuplicateDistributedCentralLogger()
        {
            string className = "Microsoft.Build.Logging.ConfigurableForwardingLogger";
#if FEATURE_ASSEMBLY_LOCATION
            LoggerDescription description = CreateLoggerDescription(className, Assembly.GetAssembly(typeof(ProjectCollection)).FullName, true);
#else
            LoggerDescription description = CreateLoggerDescription(className, typeof(ProjectCollection).GetTypeInfo().Assembly.FullName, true);
#endif

            RegularILogger regularILogger = new RegularILogger();
            Assert.True(_initializedService.RegisterDistributedLogger(regularILogger, description));
            Assert.False(_initializedService.RegisterDistributedLogger(regularILogger, description));

            // Should have 2 central loggers and 1 forwarding logger
            Assert.Equal(2, _initializedService.RegisteredLoggerTypeNames.Count);
            Assert.Contains("Microsoft.Build.Logging.ConfigurableForwardingLogger", _initializedService.RegisteredLoggerTypeNames);
            Assert.Contains("Microsoft.Build.UnitTests.Logging.LoggingService_Tests+RegularILogger", _initializedService.RegisteredLoggerTypeNames);

            // Should have 1 sink
            Assert.NotNull(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.RegisteredSinkNames);
            Assert.Single(_initializedService.LoggerDescriptions);
        }
        #endregion

        #region Test Properties
        /// <summary>
        /// Verify the getters and setters for the properties work.
        /// </summary>
        [Fact]
        public void Properties()
        {
            // Test OnlyLogCriticalEvents
            LoggingService loggingService = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
            Assert.False(loggingService.OnlyLogCriticalEvents); // "Expected only log critical events to be false"
            loggingService.OnlyLogCriticalEvents = true;
            Assert.True(loggingService.OnlyLogCriticalEvents); // "Expected only log critical events to be true"

            // Test LoggingMode
            Assert.Equal(LoggerMode.Synchronous, loggingService.LoggingMode); // "Expected Logging mode to be Synchronous"

            // Test LoggerDescriptions
            Assert.Empty(loggingService.LoggerDescriptions); // "Expected LoggerDescriptions to be empty"

            // Test Number of InitialNodes
            Assert.Equal(1, loggingService.MaxCPUCount);
            loggingService.MaxCPUCount = 5;
            Assert.Equal(5, loggingService.MaxCPUCount);

            // Test MinimumRequiredMessageImportance
            Assert.Equal(MessageImportance.Low, loggingService.MinimumRequiredMessageImportance);
            loggingService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
            Assert.Equal(MessageImportance.Normal, loggingService.MinimumRequiredMessageImportance);
        }

        #endregion

        #region PacketHandling Tests
        /// <summary>
        /// Verify how a null packet is handled. There should be an InternalErrorException thrown
        /// </summary>
        [Fact]
        public void NullPacketReceived()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                LoggingService loggingService = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
                loggingService.PacketReceived(1, null);
            });
        }
        /// <summary>
        /// Verify when a non logging packet is received.
        /// An invalid operation should be thrown
        /// </summary>
        [Fact]
        public void NonLoggingPacketPacketReceived()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                LoggingService loggingService = (LoggingService)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
                NonLoggingPacket packet = new NonLoggingPacket();
                loggingService.PacketReceived(1, packet);
            });
        }
        /// <summary>
        /// Verify when a logging packet is received the build event is
        /// properly passed to ProcessLoggingEvent
        /// An invalid operation should be thrown
        /// </summary>
        [Fact]
        public void LoggingPacketReceived()
        {
            LoggingServicesLogMethod_Tests.ProcessBuildEventHelper loggingService = (LoggingServicesLogMethod_Tests.ProcessBuildEventHelper)LoggingServicesLogMethod_Tests.ProcessBuildEventHelper.CreateLoggingService(LoggerMode.Synchronous, 1);
            BuildMessageEventArgs messageEvent = new BuildMessageEventArgs("MyMessage", "HelpKeyword", "Sender", MessageImportance.High);
            LogMessagePacket packet = new LogMessagePacket(new KeyValuePair<int, BuildEventArgs>(1, messageEvent));
            loggingService.PacketReceived(1, packet);

            BuildMessageEventArgs messageEventFromPacket = loggingService.ProcessedBuildEvent as BuildMessageEventArgs;
            Assert.NotNull(messageEventFromPacket);
            Assert.Equal(messageEventFromPacket, messageEvent); // "Expected messages to match"
        }

        #endregion

        #region WarningsAsErrors Tests

        private static readonly BuildWarningEventArgs BuildWarningEventForTreatAsErrorOrMessageTests = new BuildWarningEventArgs("subcategory", "C94A41A90FFB4EF592BF98BA59BEE8AF", "file", 1, 2, 3, 4, "message", "helpKeyword", "senderName");

        /// <summary>
        /// Verifies that a warning is logged as an error when it's warning code specified.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsErrorWhenSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsErrors = new HashSet<string>
            {
                "123",
                BuildWarningEventForTreatAsErrorOrMessageTests.Code,
                "ABC",
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsErrors);

            BuildErrorEventArgs actualBuildEvent = logger.Errors.ShouldHaveSingleItem();

            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Code, actualBuildEvent.Code);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.File, actualBuildEvent.File);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ProjectFile, actualBuildEvent.ProjectFile);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Subcategory, actualBuildEvent.Subcategory);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.HelpKeyword, actualBuildEvent.HelpKeyword);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Message, actualBuildEvent.Message);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.SenderName, actualBuildEvent.SenderName);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ColumnNumber, actualBuildEvent.ColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndColumnNumber, actualBuildEvent.EndColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndLineNumber, actualBuildEvent.EndLineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.LineNumber, actualBuildEvent.LineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.BuildEventContext, actualBuildEvent.BuildEventContext);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Timestamp, actualBuildEvent.Timestamp);
        }

        /// <summary>
        /// Verifies that a warning is not treated as an error when other warning codes are specified.
        /// </summary>
        [Fact]
        public void NotTreatWarningsAsErrorWhenNotSpecified()
        {
            HashSet<string> warningsAsErrors = new HashSet<string>
            {
                "123",
                "ABC",
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, warningsAsErrors: warningsAsErrors);

            var actualEvent = logger.Warnings.ShouldHaveSingleItem();

            actualEvent.ShouldBe(BuildWarningEventForTreatAsErrorOrMessageTests);
        }

        /// <summary>
        /// Verifies that a warning is not treated as an error when other warning codes are specified.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsErrorWhenAllSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsErrors = new HashSet<string>();

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsErrors);

            logger.Errors.ShouldHaveSingleItem();
        }

        [Fact]
        public void VerifyWarningsPromotedToErrorsAreCounted()
        {
            ILoggingService ls = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
            ls.WarningsAsErrors = new HashSet<string>();
            ls.WarningsAsErrors.Add("FOR123");
            BuildWarningEventArgs warningArgs = new("abc", "FOR123", "", 0, 0, 0, 0, "warning message", "keyword", "sender");
            warningArgs.BuildEventContext = new BuildEventContext(1, 2, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidProjectContextId, 5, 6);
            ls.LogBuildEvent(warningArgs);
            ls.HasBuildSubmissionLoggedErrors(1).ShouldBeTrue();
        }

        /// <summary>
        /// Verifies that a warning is logged as a low importance message when it's warning code is specified.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsMessagesWhenSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsMessages = new HashSet<string>
            {
                "FOO",
                BuildWarningEventForTreatAsErrorOrMessageTests.Code,
                "BAR",
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsMessages: warningsAsMessages);

            BuildMessageEventArgs actualBuildEvent = logger.BuildMessageEvents.ShouldHaveSingleItem();

            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.BuildEventContext, actualBuildEvent.BuildEventContext);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Code, actualBuildEvent.Code);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ColumnNumber, actualBuildEvent.ColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndColumnNumber, actualBuildEvent.EndColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndLineNumber, actualBuildEvent.EndLineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.File, actualBuildEvent.File);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.HelpKeyword, actualBuildEvent.HelpKeyword);
            Assert.Equal(MessageImportance.Low, actualBuildEvent.Importance);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.LineNumber, actualBuildEvent.LineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Message, actualBuildEvent.Message);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ProjectFile, actualBuildEvent.ProjectFile);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.SenderName, actualBuildEvent.SenderName);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Subcategory, actualBuildEvent.Subcategory);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Timestamp, actualBuildEvent.Timestamp);
        }

        /// <summary>
        /// Verifies that a warning is not treated as a low importance message when other warning codes are specified.
        /// </summary>
        [Fact]
        public void NotTreatWarningsAsMessagesWhenNotSpecified()
        {
            HashSet<string> warningsAsMessages = new HashSet<string>
            {
                "FOO",
                "BAR",
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, warningsAsMessages: warningsAsMessages);

            logger.Warnings.ShouldHaveSingleItem();
        }

        /// <summary>
        /// Verifies that warnings are treated as an error for a particular project when codes are specified.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsErrorByProjectWhenSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsErrorsForProject = new HashSet<string>
            {
                "123",
                BuildWarningEventForTreatAsErrorOrMessageTests.Code,
                "ABC"
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsErrorsForProject: warningsAsErrorsForProject);

            BuildErrorEventArgs actualBuildEvent = logger.Errors.ShouldHaveSingleItem();

            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Code, actualBuildEvent.Code);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.File, actualBuildEvent.File);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ProjectFile, actualBuildEvent.ProjectFile);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Subcategory, actualBuildEvent.Subcategory);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.HelpKeyword, actualBuildEvent.HelpKeyword);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Message, actualBuildEvent.Message);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.SenderName, actualBuildEvent.SenderName);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ColumnNumber, actualBuildEvent.ColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndColumnNumber, actualBuildEvent.EndColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndLineNumber, actualBuildEvent.EndLineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.LineNumber, actualBuildEvent.LineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.BuildEventContext, actualBuildEvent.BuildEventContext);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Timestamp, actualBuildEvent.Timestamp);
        }

        /// <summary>
        /// Verifies that all warnings are treated as errors for a particular project.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsErrorByProjectWhenAllSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsErrorsForProject = new HashSet<string>();

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsErrorsForProject: warningsAsErrorsForProject);

            logger.Errors.ShouldHaveSingleItem();
        }

        /// <summary>
        /// Verifies that warnings are treated as messages for a particular project.
        /// </summary>
        [Theory]
        [InlineData(0, 1)]
        [InlineData(0, 2)]
        [InlineData(1, 1)]
        [InlineData(1, 2)]
        public void TreatWarningsAsMessagesByProjectWhenSpecified(int loggerMode, int nodeId)
        {
            HashSet<string> warningsAsMessagesForProject = new HashSet<string>
            {
                "123",
                BuildWarningEventForTreatAsErrorOrMessageTests.Code,
                "ABC"
            };

            MockLogger logger = GetLoggedEventsWithWarningsAsErrorsOrMessages(BuildWarningEventForTreatAsErrorOrMessageTests, (LoggerMode)loggerMode, nodeId, warningsAsMessagesForProject: warningsAsMessagesForProject);

            BuildMessageEventArgs actualBuildEvent = logger.BuildMessageEvents.ShouldHaveSingleItem();

            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.BuildEventContext, actualBuildEvent.BuildEventContext);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Code, actualBuildEvent.Code);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ColumnNumber, actualBuildEvent.ColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndColumnNumber, actualBuildEvent.EndColumnNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.EndLineNumber, actualBuildEvent.EndLineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.File, actualBuildEvent.File);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.HelpKeyword, actualBuildEvent.HelpKeyword);
            Assert.Equal(MessageImportance.Low, actualBuildEvent.Importance);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.LineNumber, actualBuildEvent.LineNumber);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Message, actualBuildEvent.Message);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.ProjectFile, actualBuildEvent.ProjectFile);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.SenderName, actualBuildEvent.SenderName);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Subcategory, actualBuildEvent.Subcategory);
            Assert.Equal(BuildWarningEventForTreatAsErrorOrMessageTests.Timestamp, actualBuildEvent.Timestamp);
        }

        private MockLogger GetLoggedEventsWithWarningsAsErrorsOrMessages(
            BuildEventArgs buildEvent,
            LoggerMode loggerMode = LoggerMode.Synchronous,
            int nodeId = 1,
            ISet<string> warningsAsErrors = null,
            ISet<string> warningsAsMessages = null,
            ISet<string> warningsAsErrorsForProject = null,
            ISet<string> warningsAsMessagesForProject = null)
        {
            IBuildComponentHost host = new MockHost();

            BuildEventContext buildEventContext = new BuildEventContext(
                submissionId: 0,
                nodeId: 1,
                projectInstanceId: 2,
                projectContextId: -1,
                targetId: -1,
                taskId: -1);

            BuildRequestData buildRequestData = new BuildRequestData("projectFile", new Dictionary<string, string>(), "Current", new[] { "Build" }, null);

            ConfigCache configCache = host.GetComponent(BuildComponentType.ConfigCache) as ConfigCache;

            configCache.AddConfiguration(new BuildRequestConfiguration(buildEventContext.ProjectInstanceId, buildRequestData, buildRequestData.ExplicitlySpecifiedToolsVersion));

            MockLogger logger = new MockLogger();

            ILoggingService loggingService = LoggingService.CreateLoggingService(loggerMode, nodeId);

            ((IBuildComponent)loggingService).InitializeComponent(host);

            loggingService.RegisterLogger(logger);

            BuildEventContext projectStarted = loggingService.LogProjectStarted(buildEventContext, 0, buildEventContext.ProjectInstanceId, BuildEventContext.Invalid, "projectFile", "Build", Enumerable.Empty<DictionaryEntry>(), Enumerable.Empty<DictionaryEntry>());

            if (warningsAsErrorsForProject != null)
            {
                loggingService.AddWarningsAsErrors(projectStarted, warningsAsErrorsForProject);
            }

            if (warningsAsMessagesForProject != null)
            {
                loggingService.AddWarningsAsMessages(projectStarted, warningsAsMessagesForProject);
            }

            loggingService.WarningsAsErrors = warningsAsErrors;
            loggingService.WarningsAsMessages = warningsAsMessages;

            buildEvent.BuildEventContext = projectStarted;

            loggingService.LogBuildEvent(buildEvent);

            loggingService.LogProjectFinished(projectStarted, "projectFile", true);

            while (logger.ProjectFinishedEvents.Count == 0)
            {
                Thread.Sleep(100);
            }

            ((IBuildComponent)loggingService).ShutdownComponent();

            return logger;
        }

        #endregion

        #region MinimumRequiredMessageImportance Tests

        [Fact]
        public void ImportanceReflectsConsoleLoggerVerbosity()
        {
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Quiet));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High - 1);
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Minimal));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Normal);
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Detailed));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Diagnostic));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
        }

        [Fact]
        public void ImportanceReflectsConfigurableForwardingLoggerVerbosity()
        {
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Quiet));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High - 1);
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Normal));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Normal);
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Detailed));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Diagnostic));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
        }

        [Fact]
        public void ImportanceReflectsCentralForwardingLoggerVerbosity()
        {
            MockHost mockHost = new MockHost();
            ILoggingService node1LoggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
            ((IBuildComponent)node1LoggingService).InitializeComponent(mockHost);
            ILoggingService node2LoggingService = LoggingService.CreateLoggingService(LoggerMode.Synchronous, 2);
            ((IBuildComponent)node2LoggingService).InitializeComponent(mockHost);

            // CentralForwardingLogger is always registered in in-proc nodes and it does not affect minimum importance.
            node1LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
            node1LoggingService.RegisterLogger(new CentralForwardingLogger());
            node1LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.High);

            // CentralForwardingLogger in out-of-proc nodes means that we are forwarding everything and the minimum importance
            // is Low regardless of what other loggers are registered.
            node2LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
            node2LoggingService.RegisterLogger(new CentralForwardingLogger());
            node2LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
            // Register another ConsoleLogger and verify that minimum importance hasn't changed.
            node2LoggingService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Minimal));
            node2LoggingService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
        }

        [Fact]
        public void ImportanceReflectsUnknownLoggerVerbosity()
        {
            // Minimum message importance is Low (i.e. we're logging everything) even when all registered loggers have
            // Normal verbosity if at least of one them is not on our allowlist.
            _initializedService.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Normal));
            _initializedService.RegisterLogger(new MockLogger() { Verbosity = LoggerVerbosity.Normal });
            _initializedService.RegisterLogger(CreateConfigurableForwardingLogger(LoggerVerbosity.Normal));
            _initializedService.MinimumRequiredMessageImportance.ShouldBe(MessageImportance.Low);
        }
        #endregion

        #region PrivateMethods

        /// <summary>
        /// Instantiate and Initialize a new loggingService.
        /// This is used by the test setup method to create
        /// a new logging service before each test.
        /// </summary>
        private void InitializeLoggingService()
        {
            BuildParameters parameters = new BuildParameters();
            parameters.MaxNodeCount = 2;
            MockHost mockHost = new MockHost(parameters);

            IBuildComponent logServiceComponent = (IBuildComponent)LoggingService.CreateLoggingService(LoggerMode.Synchronous, 1);
            logServiceComponent.InitializeComponent(mockHost);
            _initializedService = logServiceComponent as LoggingService;
        }

        /// <summary>
        /// Register the correct logger and then call the shutdownComponent method.
        /// This will call shutdown on the loggers, we should expect to see certain exceptions.
        /// </summary>
        /// <param name="logger">Logger to register, this will only be used if className is null</param>
        /// <param name="className">ClassName to instantiate a new distributed logger</param>
        /// <param name="expectedExceptionType">Exception type which is expected to be thrown</param>
        private void VerifyShutdownExceptions(ILogger logger, string className, Type expectedExceptionType)
        {
            InitializeLoggingService();
            if (className != null)
            {
#if FEATURE_ASSEMBLY_LOCATION
                Assembly thisAssembly = Assembly.GetAssembly(typeof(LoggingService_Tests));
#else
                Assembly thisAssembly = typeof(LoggingService_Tests).GetTypeInfo().Assembly;
#endif
                string loggerAssemblyName = thisAssembly.FullName;
                LoggerDescription centralLoggerDescrption = CreateLoggerDescription(className, loggerAssemblyName, true);
                _initializedService.RegisterDistributedLogger(null, centralLoggerDescrption);
            }
            else
            {
                _initializedService.RegisterLogger(logger);
            }

            try
            {
                _initializedService.ShutdownComponent();
                Assert.Fail("No Exceptions Generated");
            }
            catch (Exception e)
            {
                if (e.GetType() != expectedExceptionType)
                {
                    Assert.Fail("Expected a " + expectedExceptionType + " but got a " + e.GetType() + " Stack:" + e.ToString());
                }
            }
        }

        /// <summary>
        /// Create a logger description from the class name and logger assembly
        /// This is used in any test which needs to register a distributed logger.
        /// </summary>
        /// <param name="loggerClassName">Fully qualified class name (don't for get ParentClass+Nestedclass, if nested)</param>
        /// <param name="loggerAssemblyName">Assembly name which contains class</param>
        /// <returns>A logger description which can be registered</returns>
        private LoggerDescription CreateLoggerDescription(string loggerClassName, string loggerAssemblyName, bool forwardAllEvents)
        {
            string eventsToForward = "CustomEvent";

            if (forwardAllEvents)
            {
                eventsToForward = "BuildStartedEvent;BuildFinishedEvent;ProjectStartedEvent;ProjectFinishedEvent;TargetStartedEvent;TargetFinishedEvent;TaskStartedEvent;TaskFinishedEvent;ErrorEvent;WarningEvent;HighMessageEvent;NormalMessageEvent;LowMessageEvent;CustomEvent;CommandLine";
            }

            LoggerDescription centralLoggerDescrption = new LoggerDescription(
                                                                              loggerClassName,
                                                                              loggerAssemblyName,
                                                                              null /*Not needed as we are loading from current assembly*/,
                                                                              eventsToForward,
                                                                              LoggerVerbosity.Diagnostic); /*Not used, but the spirit of the logger is to forward everything so this is the most appropriate verbosity */
            return centralLoggerDescrption;
        }

        /// <summary>
        /// Creates a new <see cref="ConfigurableForwardingLogger"/> with the given verbosity.
        /// </summary>
        private ConfigurableForwardingLogger CreateConfigurableForwardingLogger(LoggerVerbosity verbosity)
        {
            return new ConfigurableForwardingLogger()
            {
                Verbosity = verbosity
            };
        }
        #endregion

        #region HelperClasses

        /// <summary>
        /// A forwarding logger which will throw an exception
        /// </summary>
        public class BaseFLThrowException : LoggerThrowException, IForwardingLogger
        {
            #region Constructor

            /// <summary>
            /// Create a forwarding logger which will throw an exception on initialize or shutdown
            /// </summary>
            /// <param name="throwOnShutdown">Throw exception on shutdown</param>
            /// <param name="throwOnInitialize">Throw exception on initialize</param>
            /// <param name="exception">Exception to throw</param>
            internal BaseFLThrowException(bool throwOnShutdown, bool throwOnInitialize, Exception exception)
                : base(throwOnShutdown, throwOnInitialize, exception)
            {
            }
            #endregion

            #region IForwardingLogger Members

            /// <summary>
            /// Not used, implemented due to interface
            /// </summary>
            /// <value>Notused</value>
            public IEventRedirector BuildEventRedirector
            {
                get;
                set;
            }

            /// <summary>
            /// Not used, implemented due to interface
            /// </summary>
            /// <value>Not used</value>
            public int NodeId
            {
                get;
                set;
            }

            #endregion
        }

        /// <summary>
        /// Forwarding logger which throws a logger exception in the shutdown method.
        /// This is to test the logging service exception handling.
        /// </summary>
        public class ShutdownLoggerExceptionFL : BaseFLThrowException
        {
            /// <summary>
            /// Create a logger which will throw a logger exception
            /// in the shutdown method
            /// </summary>
            public ShutdownLoggerExceptionFL()
                : base(true, false, new LoggerException("Hello"))
            {
            }
        }

        /// <summary>
        /// Forwarding logger which will throw a general exception in the shutdown method
        /// This is used to test the logging service shutdown handling method.
        /// </summary>
        public class ShutdownGeneralExceptionFL : BaseFLThrowException
        {
            /// <summary>
            /// Create a logger which logs a general exception in the shutdown method
            /// </summary>
            public ShutdownGeneralExceptionFL()
                : base(true, false, new Exception("Hello"))
            {
            }
        }

        /// <summary>
        /// Forwarding logger which will throw a StackOverflowException
        /// in the shutdown method. This is to test the shutdown exception handling
        /// </summary>
        public class ShutdownStackoverflowExceptionFL : BaseFLThrowException
        {
            /// <summary>
            /// Create a logger which will throw a StackOverflow exception
            /// in the shutdown method.
            /// </summary>
            public ShutdownStackoverflowExceptionFL()
                : base(true, false, new StackOverflowException())
            {
            }
        }

        /// <summary>
        /// Logger which can throw a defined exception in the initialize or shutdown methods
        /// </summary>
        public class LoggerThrowException : INodeLogger
        {
            #region Constructor

            /// <summary>
            /// Constructor to tell the logger when to throw an exception and what exception
            /// to throw
            /// </summary>
            /// <param name="throwOnShutdown">True, throw the exception when shutdown is called</param>
            /// <param name="throwOnInitialize">True, throw the exception when Initialize is called</param>
            /// <param name="exception">The exception to throw</param>
            internal LoggerThrowException(bool throwOnShutdown, bool throwOnInitialize, Exception exception)
            {
                ExceptionToThrow = exception;
                ThrowExceptionOnShutdown = throwOnShutdown;
                ThrowExceptionOnInitialize = throwOnInitialize;
            }
            #endregion

            #region Properties

            /// <summary>
            /// Not used, implemented due to ILoggerInterface
            /// </summary>
            /// <value>Not used</value>
            public LoggerVerbosity Verbosity
            {
                get;
                set;
            }

            /// <summary>
            /// Not used, implemented due to ILoggerInterface
            /// </summary>
            /// <value>Not used</value>
            public string Parameters
            {
                get;
                set;
            }

            /// <summary>
            /// Should the exception be thrown on the call to shutdown
            /// </summary>
            /// <value>Not used</value>
            protected bool ThrowExceptionOnShutdown
            {
                get;
                set;
            }

            /// <summary>
            /// Should the exception be thrown on the call to initialize
            /// </summary>
            /// <value>Not used</value>
            protected bool ThrowExceptionOnInitialize
            {
                get;
                set;
            }

            /// <summary>
            /// The exception which will be thrown in shutdown or initialize
            /// </summary>
            /// <value>Not used</value>
            protected Exception ExceptionToThrow
            {
                get;
                set;
            }
            #endregion

            #region ILogger Members

            /// <summary>
            /// Initialize the logger, throw an exception
            /// if ThrowExceptionOnInitialize is set
            /// </summary>
            /// <param name="eventSource">Not used</param>
            public void Initialize(IEventSource eventSource)
            {
                if (ThrowExceptionOnInitialize && ExceptionToThrow != null)
                {
                    throw ExceptionToThrow;
                }
            }

            /// <summary>
            /// Shutdown the logger, throw an exception if
            /// ThrowExceptionOnShutdown is set
            /// </summary>
            public void Shutdown()
            {
                if (ThrowExceptionOnShutdown && ExceptionToThrow != null)
                {
                    throw ExceptionToThrow;
                }
            }

            /// <summary>
            /// Initialize using the INodeLogger Interface
            /// </summary>
            /// <param name="eventSource">Not used</param>
            /// <param name="nodeCount">Not used</param>
            public void Initialize(IEventSource eventSource, int nodeCount)
            {
                Initialize(eventSource);
            }
            #endregion
        }

        /// <summary>
        /// Create a regular ILogger to test Registering ILoggers.
        /// </summary>
        public class RegularILogger : ILogger
        {
            #region Properties

            /// <summary>
            /// ParametersForTheLogger
            /// </summary>
            public string Parameters
            {
                get;
                set;
            }

            /// <summary>
            /// Verbosity
            /// </summary>
            public LoggerVerbosity Verbosity
            {
                get;
                set;
            }

            /// <summary>
            /// Number of times build started was logged
            /// </summary>
            internal int BuildStartedCount
            {
                get;
                set;
            }

            /// <summary>
            /// Number of times build finished was logged
            /// </summary>
            internal int BuildFinishedCount
            {
                get;
                set;
            }

            /// <summary>
            /// Initialize
            /// </summary>
            public void Initialize(IEventSource eventSource)
            {
                eventSource.AnyEventRaised +=
                         LoggerEventHandler;
            }

            /// <summary>
            /// DoNothing
            /// </summary>
            public void Shutdown()
            {
                // do nothing
            }

            /// <summary>
            /// Log the event
            /// </summary>
            internal void LoggerEventHandler(object sender, BuildEventArgs eventArgs)
            {
                if (eventArgs is BuildStartedEventArgs)
                {
                    ++BuildStartedCount;
                }

                if (eventArgs is BuildFinishedEventArgs)
                {
                    ++BuildFinishedCount;
                }
            }
        }

        /// <summary>
        /// Create a regular ILogger which keeps track of how many of each event were logged
        /// </summary>
        public class TestLogger : ILogger
        {
            /// <summary>
            /// Not Used
            /// </summary>
            /// <value>Not used</value>
            public LoggerVerbosity Verbosity
            {
                get;
                set;
            }

            /// <summary>
            /// Do Nothing
            /// </summary>
            /// <value>Not Used</value>
            public string Parameters
            {
                get;
                set;
            }

            /// <summary>
            /// Do Nothing
            /// </summary>
            /// <param name="eventSource">Not Used</param>
            public void Initialize(IEventSource eventSource)
            {
            }

            /// <summary>
            /// Do Nothing
            /// </summary>
            public void Shutdown()
            {
            }

            #endregion
        }

        /// <summary>
        ///  Create a non logging packet to test the packet handling code
        /// </summary>
        internal sealed class NonLoggingPacket : INodePacket
        {
            #region Members

            /// <summary>
            /// Inform users of the class, this class is a BuildRequest packet
            /// </summary>
            public NodePacketType Type
            {
                get
                {
                    return NodePacketType.BuildRequest;
                }
            }

            /// <summary>
            /// Serialize the packet
            /// </summary>
            public void Translate(ITranslator translator)
            {
                throw new NotImplementedException();
            }

            #endregion
        }
        #endregion
    }
}
