File: BackEnd\BuildRequest_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.
 
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Versioning;
 
using Microsoft.Build.BackEnd;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Shouldly;
using Xunit;
 
#nullable disable
 
namespace Microsoft.Build.UnitTests.BackEnd
{
    public class BuildRequest_Tests
    {
        private int _nodeRequestId;
 
        public BuildRequest_Tests()
        {
            _nodeRequestId = 1;
        }
 
        [Fact]
        public void TestConstructorBad()
        {
            Assert.Throws<ArgumentNullException>(() =>
            {
                CreateNewBuildRequest(0, null);
            });
        }
        [Fact]
        public void TestConstructorGood()
        {
            CreateNewBuildRequest(0, Array.Empty<string>());
        }
 
        [Fact]
        public void TestConfigurationId()
        {
            BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
            Assert.Equal(0, request.ConfigurationId);
 
            BuildRequest request2 = CreateNewBuildRequest(1, Array.Empty<string>());
            Assert.Equal(1, request2.ConfigurationId);
 
            BuildRequest request3 = CreateNewBuildRequest(-1, Array.Empty<string>());
            Assert.Equal(-1, request3.ConfigurationId);
        }
 
        [Fact]
        public void TestConfigurationResolved()
        {
            BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
            Assert.False(request.IsConfigurationResolved);
 
            BuildRequest request2 = CreateNewBuildRequest(1, Array.Empty<string>());
            Assert.True(request2.IsConfigurationResolved);
 
            BuildRequest request3 = CreateNewBuildRequest(-1, Array.Empty<string>());
            Assert.False(request3.IsConfigurationResolved);
        }
 
        [Fact]
        public void TestTargets()
        {
            BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
            Assert.NotNull(request.Targets);
            Assert.Empty(request.Targets);
 
            BuildRequest request2 = CreateNewBuildRequest(1, new string[1] { "a" });
            Assert.NotNull(request2.Targets);
            Assert.Single(request2.Targets);
            Assert.Equal("a", request2.Targets[0]);
        }
 
        [Fact]
        public void TestPacketType()
        {
            BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
            Assert.Equal(NodePacketType.BuildRequest, request.Type);
        }
 
        [Fact]
        public void TestResolveConfigurationGood()
        {
            BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
            request.ResolveConfiguration(1);
            Assert.True(request.IsConfigurationResolved);
            Assert.Equal(1, request.ConfigurationId);
        }
 
        [Fact]
        public void TestResolveConfigurationBad()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                BuildRequest request = CreateNewBuildRequest(1, Array.Empty<string>());
                request.ResolveConfiguration(2);
            });
        }
 
        [Fact]
        public void TestResolveConfigurationBad2()
        {
            Assert.Throws<InternalErrorException>(() =>
            {
                BuildRequest request = CreateNewBuildRequest(0, Array.Empty<string>());
                request.ResolveConfiguration(-1);
            });
        }
        [Fact]
        public void TestTranslation()
        {
            BuildRequest request = CreateNewBuildRequest(1, new string[] { "alpha", "omega" });
 
            Assert.Equal(NodePacketType.BuildRequest, request.Type);
 
            ((ITranslatable)request).Translate(TranslationHelpers.GetWriteTranslator());
            INodePacket packet = BuildRequest.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
 
            BuildRequest deserializedRequest = packet as BuildRequest;
 
            Assert.Equal(request.BuildEventContext, deserializedRequest.BuildEventContext);
            Assert.Equal(request.ConfigurationId, deserializedRequest.ConfigurationId);
            Assert.Equal(request.GlobalRequestId, deserializedRequest.GlobalRequestId);
            Assert.Equal(request.IsConfigurationResolved, deserializedRequest.IsConfigurationResolved);
            Assert.Equal(request.NodeRequestId, deserializedRequest.NodeRequestId);
            Assert.Equal(request.ParentBuildEventContext, deserializedRequest.ParentBuildEventContext);
            Assert.Equal(request.Targets.Count, deserializedRequest.Targets.Count);
            for (int i = 0; i < request.Targets.Count; i++)
            {
                Assert.Equal(request.Targets[i], deserializedRequest.Targets[i]);
            }
        }
 
        [WindowsOnlyFact]
        [SupportedOSPlatform("windows")]
        public void TestRunningObjectTableErrorLogging()
        {
            var rot = new RunningObjectTable();
            var nonExistentMoniker = "NonExistent_" + Guid.NewGuid();
 
            var exception = Should.Throw<COMException>(() => rot.GetObject(nonExistentMoniker));
 
            exception.Message.ShouldContain($"Failed to get object '{nonExistentMoniker}' from Running Object Table");
            exception.Message.ShouldContain("HRESULT:");
            exception.HResult.ShouldNotBe(0);
        }
 
        [WindowsOnlyFact]
        [SupportedOSPlatform("windows")]
        public void TestRunningObjectTableErrorDoesNotMaskOriginalError()
        {
            var rot = new RunningObjectTable();
            var testMoniker = "ErrorTest_" + Guid.NewGuid();
 
            var exception = Should.Throw<COMException>(() => rot.GetObject(testMoniker));
 
            exception.ShouldBeOfType<COMException>();
            exception.HResult.ShouldNotBe(0);
 
            exception.Message.ShouldContain(testMoniker);
        }
 
        [WindowsOnlyFact]
        [SupportedOSPlatform("windows")]
        public void TestRunningObjectTableSuccessDoesNotThrow()
        {
            var stateInHostObject = 42;
            var hostServices = new HostServices();
            var rot = new MockRunningObjectTable();
            hostServices.SetTestRunningObjectTable(rot);
 
            var moniker = nameof(TestRunningObjectTableSuccessDoesNotThrow) + Guid.NewGuid();
            var remoteHost = new MockRemoteHostObject(stateInHostObject);
 
            using (var result = rot.Register(moniker, remoteHost))
            {
                // This should succeed without throwing - validates error logging doesn't affect success path
                var retrievedObject = Should.NotThrow(() => rot.GetObject(moniker));
 
                retrievedObject.ShouldNotBeNull();
                retrievedObject.ShouldBeOfType<MockRemoteHostObject>();
                ((MockRemoteHostObject)retrievedObject).GetState().ShouldBe(stateInHostObject);
            }
        }
 
        [WindowsOnlyFact]
        [SupportedOSPlatform("windows")]
        public void TestRunningObjectTableErrorMessageIsMultiLine()
        {
            var rot = new RunningObjectTable();
            var testMoniker = "MultiLineTest_" + Guid.NewGuid();
 
            var exception = Should.Throw<COMException>(() => rot.GetObject(testMoniker));
 
            // The error message should have at least 2 lines:
            // 1. "Failed to get object..." 
            // 2. "HRESULT: ..."
            var lines = exception.Message.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
            lines.Length.ShouldBeGreaterThanOrEqualTo(2);
 
            // First line should mention the failure
            lines[0].ShouldContain("Failed to get object");
            lines[0].ShouldContain(testMoniker);
 
            // Second line should have HRESULT
            lines[1].ShouldContain("HRESULT:");
        }
 
        [WindowsOnlyFact]
        [SupportedOSPlatform("windows")]
        public void TestTranslationRemoteHostObjects()
        {
            var stateInHostObject = 3;
 
            var hostServices = new HostServices();
            var rot = new MockRunningObjectTable();
            hostServices.SetTestRunningObjectTable(rot);
            var moniker = nameof(TestTranslationRemoteHostObjects) + Guid.NewGuid();
            var remoteHost = new MockRemoteHostObject(stateInHostObject);
            using (var result = rot.Register(moniker, remoteHost))
            {
                hostServices.RegisterHostObject(
                    "WithOutOfProc.targets",
                    "DisplayMessages",
                    "ATask",
                    moniker);
 
                BuildRequest request = new BuildRequest(
                    submissionId: 1,
                    _nodeRequestId++,
                    1,
                    new string[] { "alpha", "omega" },
                    hostServices: hostServices,
                    BuildEventContext.Invalid,
                    parentRequest: null);
 
                ((ITranslatable)request).Translate(TranslationHelpers.GetWriteTranslator());
                INodePacket packet = BuildRequest.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
 
                BuildRequest deserializedRequest = packet as BuildRequest;
                deserializedRequest.HostServices.SetTestRunningObjectTable(rot);
                var hostObject = deserializedRequest.HostServices.GetHostObject(
                    "WithOutOfProc.targets",
                    "DisplayMessages",
                    "ATask") as ITestRemoteHostObject;
 
                hostObject.GetState().ShouldBe(stateInHostObject);
            }
        }
 
        [Fact]
        public void TestTranslationHostObjectsWhenEmpty()
        {
            var hostServices = new HostServices();
            BuildRequest request = new BuildRequest(
                submissionId: 1,
                _nodeRequestId++,
                1,
                new string[] { "alpha", "omega" },
                hostServices: hostServices,
                BuildEventContext.Invalid,
                parentRequest: null);
 
            ((ITranslatable)request).Translate(TranslationHelpers.GetWriteTranslator());
            BuildRequest.FactoryForDeserialization(TranslationHelpers.GetReadTranslator());
        }
 
        private BuildRequest CreateNewBuildRequest(int configurationId, string[] targets)
        {
            return new BuildRequest(1 /* submissionId */, _nodeRequestId++, configurationId, targets, null, BuildEventContext.Invalid, null);
        }
    }
}