// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Testing.Platform.Configurations; using Microsoft.Testing.Platform.Extensions.Messages; using Microsoft.Testing.Platform.Extensions.OutputDevice; using Microsoft.Testing.Platform.Extensions.TestHost; using Microsoft.Testing.Platform.Extensions.TestHostControllers; using Microsoft.Testing.Platform.Helpers; using Microsoft.Testing.Platform.Hosts; using Microsoft.Testing.Platform.Logging; using Microsoft.Testing.Platform.Messages; using Microsoft.Testing.Platform.OutputDevice; using Microsoft.Testing.Platform.Services; using Microsoft.Testing.Platform.Telemetry; using Microsoft.Testing.Platform.TestHost; using Microsoft.Testing.Platform.TestHostControllers; using Moq; namespace Microsoft.Testing.Platform.UnitTests; [TestClass] [UnsupportedOSPlatform("browser")] public sealed class TestApplicationBuilderTests { private const string ContosoPackageSid = "S-1-15-2-1990679259-4123976751-842158434-3026549936-2944832882-252165955-409282942"; private readonly ServiceProvider _serviceProvider = new(); public TestApplicationBuilderTests() { CurrentTestApplicationModuleInfo testApplicationModuleInfo = new(new SystemEnvironment(), new SystemProcessHandler()); AggregatedConfiguration configuration = new([], testApplicationModuleInfo, new SystemFileSystem(), new SystemEnvironment(), new(null, [], [])); configuration.SetCurrentWorkingDirectory(string.Empty); configuration.SetCurrentWorkingDirectory(string.Empty); _serviceProvider.AddService(configuration); } [TestMethod] public async Task TestApplicationLifecycleCallbacks_DuplicatedId_ShouldFail() { TestHostManager testHostManager = new(); testHostManager.AddTestHostApplicationLifetime(_ => new ApplicationLifecycleCallbacks("duplicatedId")); testHostManager.AddTestHostApplicationLifetime(_ => new ApplicationLifecycleCallbacks("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostManager.BuildTestApplicationLifecycleCallbackAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(ApplicationLifecycleCallbacks).ToString())); } [TestMethod] public async Task DataConsumer_DuplicatedId_ShouldFail() { TestHostManager testHostManager = new(); testHostManager.AddDataConsumer(_ => new Consumer("duplicatedId")); testHostManager.AddDataConsumer(_ => new Consumer("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostManager.BuildDataConsumersAsync(_serviceProvider, [])); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(Consumer).ToString())); } [TestMethod] public async Task DataConsumer_DuplicatedIdWithCompositeFactory_ShouldFail() { TestHostManager testHostManager = new(); CompositeExtensionFactory compositeExtensionFactory = new(() => new Consumer("duplicatedId")); testHostManager.AddDataConsumer(_ => new Consumer("duplicatedId")); testHostManager.AddDataConsumer(compositeExtensionFactory); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostManager.BuildDataConsumersAsync(_serviceProvider, [])); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(Consumer).ToString())); } [TestMethod] public async Task TestSessionLifetimeHandle_DuplicatedId_ShouldFail() { TestHostManager testHostManager = new(); testHostManager.AddTestSessionLifetimeHandler(_ => new TestSessionLifetimeHandler("duplicatedId")); testHostManager.AddTestSessionLifetimeHandler(_ => new TestSessionLifetimeHandler("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostManager.BuildTestSessionLifetimeHandleAsync(_serviceProvider, [])); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestSessionLifetimeHandler).ToString())); } [TestMethod] public async Task TestSessionLifetimeHandle_DuplicatedIdWithCompositeFactory_ShouldFail() { TestHostManager testHostManager = new(); CompositeExtensionFactory compositeExtensionFactory = new(() => new TestSessionLifetimeHandler("duplicatedId")); testHostManager.AddTestSessionLifetimeHandler(_ => new TestSessionLifetimeHandler("duplicatedId")); testHostManager.AddTestSessionLifetimeHandler(compositeExtensionFactory); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostManager.BuildTestSessionLifetimeHandleAsync(_serviceProvider, [])); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestSessionLifetimeHandler).ToString())); } [DataRow(true)] [DataRow(false)] [TestMethod] public async Task TestHost_ComposeFactory_ShouldSucceed(bool withParameter) { TestHostManager testHostManager = new(); CompositeExtensionFactory compositeExtensionFactory = withParameter ? new(sp => new TestSessionLifetimeHandlerPlusConsumer(sp)) : new(() => new TestSessionLifetimeHandlerPlusConsumer()); testHostManager.AddTestSessionLifetimeHandler(compositeExtensionFactory); testHostManager.AddDataConsumer(compositeExtensionFactory); List compositeExtensions = []; IDataConsumer[] consumers = [.. (await testHostManager.BuildDataConsumersAsync(_serviceProvider, compositeExtensions)).Select(x => (IDataConsumer)x.Consumer)]; ITestSessionLifetimeHandler[] sessionLifetimeHandle = [.. (await testHostManager.BuildTestSessionLifetimeHandleAsync(_serviceProvider, compositeExtensions)).Select(x => (ITestSessionLifetimeHandler)x.TestSessionLifetimeHandler)]; Assert.HasCount(1, consumers); Assert.HasCount(1, sessionLifetimeHandle); Assert.AreEqual(compositeExtensions[0].GetInstance(), consumers[0]); Assert.AreEqual(compositeExtensions[0].GetInstance(), sessionLifetimeHandle[0]); } [TestMethod] public async Task TestHostControllerEnvironmentVariableProvider_DuplicatedId_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); testHostControllerManager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("duplicatedId")); testHostControllerManager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestHostEnvironmentVariableProvider).ToString())); } [TestMethod] public async Task TestHostControllerEnvironmentVariableProvider_DuplicatedIdWithCompositeFactory_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); CompositeExtensionFactory compositeExtensionFactory = new(() => new TestHostEnvironmentVariableProvider("duplicatedId")); testHostControllerManager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("duplicatedId")); testHostControllerManager.AddEnvironmentVariableProvider(compositeExtensionFactory); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestHostEnvironmentVariableProvider).ToString())); } [TestMethod] public async Task TestHostControllerEnvironmentVariableProvider_InsertFirst_ShouldPreserveOrdering() { TestHostControllersManager testHostControllerManager = new(); testHostControllerManager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("second")); testHostControllerManager.AddEnvironmentVariableProviderFirst(_ => new TestHostEnvironmentVariableProvider("first")); TestHostControllerConfiguration configuration = await testHostControllerManager.BuildAsync(_serviceProvider); Assert.AreEqual("first", configuration.EnvironmentVariableProviders[0].Uid); Assert.AreEqual("second", configuration.EnvironmentVariableProviders[1].Uid); } [TestMethod] public async Task TestHostControllerProcessLifetimeHandler_DuplicatedId_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); testHostControllerManager.AddProcessLifetimeHandler(_ => new TestHostProcessLifetimeHandler("duplicatedId")); testHostControllerManager.AddProcessLifetimeHandler(_ => new TestHostProcessLifetimeHandler("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestHostProcessLifetimeHandler).ToString())); } [TestMethod] public async Task TestHostControllerProcessLifetimeHandler_DuplicatedIdWithCompositeFactory_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); CompositeExtensionFactory compositeExtensionFactory = new(() => new TestHostProcessLifetimeHandler("duplicatedId")); testHostControllerManager.AddProcessLifetimeHandler(_ => new TestHostProcessLifetimeHandler("duplicatedId")); testHostControllerManager.AddProcessLifetimeHandler(compositeExtensionFactory); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestHostProcessLifetimeHandler).ToString())); } [TestMethod] public async Task TestHostControllerProcessLifetimeHandler_FinalizationTokenStartsUncanceled() { using CancellationTokenSource cancellationTokenSource = new(); bool? wasCanceled = null; bool finalized = await TryRunControllerExtensionAsync( token => { wasCanceled = token.IsCancellationRequested; return Task.CompletedTask; }, cancellationTokenSource.Token); Assert.IsTrue(finalized); Assert.IsFalse(wasCanceled); } [TestMethod] public async Task TestHostControllerProcessLifetimeHandler_FinalizationIsBounded() { using CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromSeconds(2)); using ManualResetEventSlim releaseFinalization = new(initialState: false); try { bool finalized = await TryRunControllerExtensionAsync( _ => { releaseFinalization.Wait(CancellationToken.None); return Task.CompletedTask; }, cancellationTokenSource.Token); Assert.IsFalse(finalized); } finally { releaseFinalization.Set(); } } [TestMethod] public async Task TestHostControllerProcessTermination_WaitIsBounded() { Mock process = new(); TaskCompletionSource neverExits = new(TaskCreationOptions.RunContinuationsAsynchronously); process.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(neverExits.Task); var stopwatch = Stopwatch.StartNew(); bool exited = await WaitForExitAfterTerminationAsync(process.Object, TimeSpan.FromMilliseconds(100), new NopLogger()); Assert.IsFalse(exited); Assert.IsLessThan(5, stopwatch.Elapsed.TotalSeconds); } [TestMethod] public async Task TestHostControllerConnectionTimeout_IsHandledWithoutThrowing() { bool timeoutHandled = false; bool connected = await TestHostControllersTestHost.WaitForTestHostControllerConnectionAsync( token => Task.Delay(Timeout.InfiniteTimeSpan, token), timeoutSeconds: 0.01, CancellationToken.None, () => { timeoutHandled = true; return Task.CompletedTask; }); Assert.IsFalse(connected); Assert.IsTrue(timeoutHandled); } [TestMethod] public async Task TestHostControllerConnectionTimeout_PreservesApplicationCancellation() { using CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.Cancel(); await Assert.ThrowsAsync( () => TestHostControllersTestHost.WaitForTestHostControllerConnectionAsync( token => Task.Delay(Timeout.InfiniteTimeSpan, token), timeoutSeconds: 1, cancellationTokenSource.Token, () => throw new AssertFailedException("The connection timeout handler should not run for application cancellation."))); } [TestMethod] [DataRow(false, false)] [DataRow(false, true)] [DataRow(true, false)] [DataRow(true, true)] public async Task ControllerProcessEarlyExitPreservesActualDiagnosticAndDoesNotStartWaitOrTermination(bool pidUnavailable, bool telemetry) { var process = new Mock(); process.SetupGet(value => value.HasExited).Returns(true); process.SetupGet(value => value.ExitCode).Returns(17); if (pidUnavailable) { process.SetupGet(value => value.Id).Throws(new InvalidOperationException("No process associated.")); } else { process.SetupGet(value => value.Id).Returns(37); } var launcher = new Mock(); launcher.Setup(value => value.Start(It.IsAny())).Returns(process.Object); ServiceProvider services = new(); using var cancellation = new CTRLPlusCCancellationTokenSource(environment: Mock.Of()); services.AddService(cancellation); services.AddService(new SystemTask()); var loggerFactory = new Mock(); loggerFactory.Setup(value => value.CreateLogger(It.IsAny())).Returns(new NopLogger()); var clock = new Mock(); clock.SetupGet(value => value.UtcNow).Returns(new DateTimeOffset(2026, 1, 1, 3, 4, 5, TimeSpan.Zero).AddMilliseconds(123)); using var host = new TestHostControllersTestHost( new([], [], [], testHostLauncher: null, requireProcessRestart: true), services, passiveNode: null, Mock.Of(), loggerFactory.Object, clock.Object); var output = new Mock(); var messages = new List(); var tokens = new List(); output.Setup(value => value.DisplayAsync(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((_, message, token) => { messages.Add(message); tokens.Add(token); }).Returns(Task.CompletedTask); var telemetryInfo = new Mock(); telemetryInfo.SetupGet(value => value.IsEnabled).Returns(telemetry); ProcessStartInfo startInfo = new("not-launched"); (int exit, TestHostProcessInformation info, string? extensions) = await RunTestHostProcessAsync( host, startInfo, [], 42, launcher.Object, Mock.Of(), testHostControllerIpc: null!, testHostControllerCancellationServer: null!, new ProxyOutputDevice(output.Object, serverModeOutputDevice: null), telemetryInfo.Object, CancellationToken.None); Assert.AreEqual(1, exit); Assert.AreEqual(pidUnavailable ? 0 : 37, info.PID); Assert.AreEqual(1, info.ExitCode); Assert.IsFalse(info.HasExitedGracefully); Assert.AreEqual(telemetry ? "[]" : null, extensions); Assert.AreEqual( "The test host process did not connect to the controller's named pipe after '0' seconds. The test host process exited with code '17'.", Assert.IsInstanceOfType(Assert.ContainsSingle(messages)).Message); Assert.AreEqual(CancellationToken.None, Assert.ContainsSingle(tokens)); Assert.AreEqual("03:04:05.123", startInfo.EnvironmentVariables[EnvironmentVariableConstants.TESTINGPLATFORM_TESTHOSTCONTROLLER_TESTHOSTPROCESSSTARTTIME + "_42"]); process.Verify(value => value.Kill(), Times.Never); process.Verify(value => value.WaitForExitAsync(It.IsAny()), Times.Never); process.Verify(value => value.Dispose(), Times.Once); launcher.Verify(value => value.Start(startInfo), Times.Once); } [TestMethod] public void TestHostControllerProcessExit_DisposedCancellationTokenSourceIsIgnored() { var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Dispose(); TestHostControllersTestHost.TryCancelTestHostExitCancellationTokenSource(cancellationTokenSource, new NopLogger()); } [TestMethod] public async Task TestHostControllerConnectionTimeout_TerminatesCustomHandleDefersDisposalAndReturnsFailure() { TaskCompletionSource exited = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource disposed = new(TaskCreationOptions.RunContinuationsAsynchronously); Mock handle = new(); handle.SetupGet(x => x.Identifier).Returns("custom-host"); handle.SetupGet(x => x.HasExited).Returns(() => exited.Task.IsCompleted); handle.SetupSequence(x => x.WaitForExitAsync(It.IsAny())) .Returns(exited.Task) .ThrowsAsync(new IOException("remote wait failed")) .Returns(exited.Task); handle.Setup(x => x.Dispose()).Callback(() => disposed.TrySetResult(true)); Mock launcher = new(); launcher.SetupGet(x => x.Uid).Returns("custom-launcher"); launcher.SetupGet(x => x.DisplayName).Returns("Custom launcher"); launcher.Setup(x => x.LaunchTestHostAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(handle.Object); ServiceProvider serviceProvider = new(); using var cancellationTokenSource = new CTRLPlusCCancellationTokenSource(); serviceProvider.AddService(cancellationTokenSource); var task = new SystemTask(); serviceProvider.AddService(task); Mock loggerFactory = new(); loggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(new NopLogger()); Mock clock = new(); clock.SetupGet(x => x.UtcNow).Returns(DateTimeOffset.UtcNow); using var host = new TestHostControllersTestHost( new([], [], [], launcher.Object, requireProcessRestart: true), serviceProvider, passiveNode: null, new SystemEnvironment(), loggerFactory.Object, clock.Object); Mock configuration = new(); configuration .SetupGet(x => x[PlatformConfigurationConstants.PlatformTestHostControllersManagerSingleConnectionNamedPipeServerWaitConnectionTimeoutSeconds]) .Returns("0.01"); var outputDevice = new ProxyOutputDevice(new NopPlatformOutputDevice(), serverModeOutputDevice: null); var telemetryInformation = new TelemetryInformation(isEnabled: false, version: "test"); using IDisposable testHostControllerIpc = CreateTestHostControllerIpc(host, CancellationToken.None); using var cancellationServer = new TestHostControllerCancellationServer( authorizedSecurityIdentities: null, new SystemEnvironment(), loggerFactory.Object, task); (int exitCode, TestHostProcessInformation processInformation, _) = await RunTestHostProcessAsync( host, new ProcessStartInfo("custom-test-host"), [], currentPid: 42, Mock.Of(), configuration.Object, testHostControllerIpc, cancellationServer, outputDevice, telemetryInformation, CancellationToken.None); Assert.AreEqual((int)ExitCode.GenericFailure, exitCode); Assert.AreEqual((int)ExitCode.GenericFailure, processInformation.ExitCode); Assert.IsFalse(processInformation.HasExitedGracefully); handle.Verify(x => x.Terminate(), Times.Once); handle.Verify(x => x.Dispose(), Times.Never); exited.SetResult(true); await disposed.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); handle.Verify(x => x.Dispose(), Times.Once); } #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. [TestMethod] public void ControllerRegistration_NullOrdinaryFactoriesAreRejectedWithExactParameterNames() { TestHostControllersManager manager = new(); Assert.AreEqual("environmentVariableProviderFactory", Assert.ThrowsExactly( () => manager.AddEnvironmentVariableProvider(null!)).ParamName); Assert.AreEqual("environmentVariableProviderFactory", Assert.ThrowsExactly( () => manager.AddEnvironmentVariableProviderFirst(null!)).ParamName); Assert.AreEqual("lifetimeHandler", Assert.ThrowsExactly( () => manager.AddProcessLifetimeHandler(null!)).ParamName); Assert.AreEqual("testHostLauncherFactory", Assert.ThrowsExactly( () => manager.AddTestHostLauncher(null!)).ParamName); } [TestMethod] public void TestHostControllerProcessExit_NonAuthoritativeAbortedExitCodePreservesSuccessfulIpcResult() { Mock handle = new(); handle.SetupGet(x => x.ExitCode).Returns((int)ExitCode.TestSessionAborted); handle.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(Task.CompletedTask); handle.As().SetupGet(x => x.IsExitCodeAuthoritative).Returns(false); using var adapter = new TestHostHandleToProcessAdapter(handle.Object); (bool testExecutionCanceled, int reportedTestHostExitCode) = TestHostControllersTestHost.ResolveTestHostExitState( applicationCancellationRequested: false, testHostUnfilteredExitCodeReceived: (int)ExitCode.Success, testHostExitCodeReceived: (int)ExitCode.Success, adapter.ExitCode, adapter.IsExitCodeAuthoritative); Assert.IsFalse(testExecutionCanceled); Assert.AreEqual((int)ExitCode.Success, reportedTestHostExitCode); } #pragma warning restore TPEXP [TestMethod] public void TestHostControllerConnectionFailureMessage_ReportsProcessState() { Mock process = new(); process.SetupGet(x => x.HasExited).Returns(true); process.SetupGet(x => x.ExitCode).Returns(134); string message = TestHostControllersTestHost.CreateTestHostControllerConnectionFailureMessage( TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), process.Object); Assert.Contains("after '3' seconds", message); Assert.Contains("timeout was '5' seconds", message); Assert.Contains("exited with code '134'", message); } [TestMethod] public void TestHostControllerConnectionFailureMessage_ReportsRunningProcess() { Mock process = new(); process.SetupGet(x => x.HasExited).Returns(false); string message = TestHostControllersTestHost.CreateTestHostControllerConnectionFailureMessage( TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(5), process.Object); Assert.Contains("after '3' seconds", message); Assert.Contains("timeout was '5' seconds", message); Assert.Contains("still running", message); } [TestMethod] public async Task TestHostControllerProcessTermination_FaultedWaitAdvancesToTermination() { Mock process = new(); process.Setup(x => x.WaitForExitAsync(It.IsAny())) .ThrowsAsync(new IOException("remote wait failed")); bool cancellationRequested = false; await TestHostControllersTestHost.HandleCanceledTestHostAsync( process.Object, () => cancellationRequested = true, new NopLogger(), TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(50)); Assert.IsTrue(cancellationRequested); process.Verify(x => x.Kill(), Times.Once); } [TestMethod] public async Task TestHostControllerProcessTermination_UnresponsiveCustomHandleIsTerminatedAndDeferred() { TaskCompletionSource exited = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource disposed = new(TaskCreationOptions.RunContinuationsAsynchronously); Mock handle = new(); handle.SetupGet(x => x.HasExited).Returns(() => exited.Task.IsCompleted); handle.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(exited.Task); handle.Setup(x => x.Dispose()).Callback(() => disposed.TrySetResult(true)); var adapter = new TestHostHandleToProcessAdapter(handle.Object); bool cancellationRequested = false; await TestHostControllersTestHost.HandleCanceledTestHostAsync( adapter, () => cancellationRequested = true, new NopLogger(), TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(50)); Assert.IsTrue(cancellationRequested); handle.Verify(x => x.Terminate(), Times.Once); adapter.Dispose(); handle.Verify(x => x.Dispose(), Times.Never); exited.SetResult(true); await disposed.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); handle.Verify(x => x.Dispose(), Times.Once); } [TestMethod] public async Task TestHostControllerConnectionFailure_UnresponsiveCustomHandleIsTerminatedAndDeferred() { TaskCompletionSource exited = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource disposed = new(TaskCreationOptions.RunContinuationsAsynchronously); Mock handle = new(); handle.SetupGet(x => x.HasExited).Returns(() => exited.Task.IsCompleted); handle.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(exited.Task); handle.Setup(x => x.Dispose()).Callback(() => disposed.TrySetResult(true)); var adapter = new TestHostHandleToProcessAdapter(handle.Object); await TestHostControllersTestHost.TerminateTestHostAfterConnectionFailureAsync( adapter, new NopLogger(), TimeSpan.FromMilliseconds(50)); handle.Verify(x => x.Terminate(), Times.Once); adapter.Dispose(); handle.Verify(x => x.Dispose(), Times.Never); exited.SetResult(true); await disposed.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); handle.Verify(x => x.Dispose(), Times.Once); } [TestMethod] public async Task TestHostControllerProcessTermination_CancellationDuringStartupUsesTeardownPath() { using CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.Cancel(); Mock process = new(); TaskCompletionSource neverExits = new(TaskCreationOptions.RunContinuationsAsynchronously); process.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(neverExits.Task); bool cancellationRequested = false; await TestHostControllersTestHost.RunWithCancellationTeardownAsync( () => Task.FromCanceled(cancellationTokenSource.Token), cancellationTokenSource.Token, process.Object, () => cancellationRequested = true, new NopLogger(), TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(50)); Assert.IsTrue(cancellationRequested); process.Verify(x => x.Kill(), Times.Once); } [TestMethod] public async Task TestHostControlledHost_LateCooperativeCancellationPreservesCompletionToken() { using CancellationTokenSource applicationCancellationTokenSource = new(); using CancellationTokenSource completionCancellationTokenSource = new(); TaskCompletionSource cancellationObserved = new(TaskCreationOptions.RunContinuationsAsynchronously); using CancellationTokenRegistration completionRegistration = completionCancellationTokenSource.Token.Register(() => cancellationObserved.TrySetResult(true)); bool cooperativeCancellation = false; using CancellationTokenRegistration registration = TestHostControlledHost.RegisterCompletionCancellationTransition( applicationCancellationTokenSource.Token, () => cooperativeCancellation, completionCancellationTokenSource, TimeSpan.FromMilliseconds(20)); cooperativeCancellation = true; applicationCancellationTokenSource.Cancel(); Assert.IsFalse(completionCancellationTokenSource.IsCancellationRequested); await cancellationObserved.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); Assert.IsTrue(completionCancellationTokenSource.IsCancellationRequested); } [TestMethod] public void TestHostControlledHost_LateNonCooperativeCancellationCancelsCompletionToken() { using CancellationTokenSource applicationCancellationTokenSource = new(); using CancellationTokenSource completionCancellationTokenSource = new(); using CancellationTokenRegistration registration = TestHostControlledHost.RegisterCompletionCancellationTransition( applicationCancellationTokenSource.Token, () => false, completionCancellationTokenSource, TimeoutHelper.DefaultHangTimeSpanTimeout); applicationCancellationTokenSource.Cancel(); Assert.IsTrue(completionCancellationTokenSource.IsCancellationRequested); } [TestMethod] public void TestHostControllerProcessTermination_CooperativeBudgetUsesConfiguredCanceledConsumerBudget() { Mock environment = new(); environment .Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.TESTINGPLATFORM_MESSAGEBUS_CANCELED_SHUTDOWN_TIMEOUT_SECONDS)) .Returns("60"); Mock loggerFactory = new(); loggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(new NopLogger()); using var host = new TestHostControllersTestHost( new([], [], [], testHostLauncher: null, requireProcessRestart: false), new(), passiveNode: null, environment.Object, loggerFactory.Object, Mock.Of()); Assert.AreEqual(TimeSpan.FromSeconds(75), host.TestHostCooperativeShutdownTimeout); } [TestMethod] public async Task TestHostControllerProcessTermination_CustomHandleDisposalWaitsForExit() { TaskCompletionSource exited = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource disposed = new(TaskCreationOptions.RunContinuationsAsynchronously); Mock handle = new(); handle.SetupGet(x => x.HasExited).Returns(() => exited.Task.IsCompleted); handle.Setup(x => x.WaitForExitAsync(It.IsAny())).Returns(exited.Task); handle.Setup(x => x.Dispose()).Callback(() => disposed.TrySetResult(true)); var adapter = new TestHostHandleToProcessAdapter(handle.Object); adapter.DeferDisposalUntilExit(); adapter.Dispose(); handle.Verify(x => x.Dispose(), Times.Never); exited.SetResult(true); await disposed.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); handle.Verify(x => x.Dispose(), Times.Once); } [TestMethod] public void TestHostControllerProcessTermination_CustomHandleDisposalIsImmediateByDefault() { Mock handle = new(); handle.SetupGet(x => x.HasExited).Returns(false); handle.Setup(x => x.WaitForExitAsync(It.IsAny())) .Returns((CancellationToken token) => Task.Delay(Timeout.InfiniteTimeSpan, token)); var adapter = new TestHostHandleToProcessAdapter(handle.Object); adapter.Dispose(); handle.Verify(x => x.Dispose(), Times.Once); } [TestMethod] public void TestHostControllerOutputFinalization_AbandonmentTracksProxyAndOriginalDevice() { Mock originalOutputDevice = new(); ProxyOutputDevice proxyOutputDevice = new(originalOutputDevice.Object, serverModeOutputDevice: null); List servicesStillRunning = []; MarkOutputDeviceStillRunning(servicesStillRunning, proxyOutputDevice); Assert.HasCount(2, servicesStillRunning); Assert.Contains(proxyOutputDevice, servicesStillRunning); Assert.Contains(originalOutputDevice.Object, servicesStillRunning); } [TestMethod] public void TestHostControllerFinalization_LateCancellationArmsSharedBoundedTokenSource() { Mock environment = new(); environment .Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.TESTINGPLATFORM_TESTHOSTCONTROLLER_FINALIZATION_TIMEOUT_SECONDS)) .Returns("0.1"); Mock loggerFactory = new(); loggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(Mock.Of()); using var host = new TestHostControllersTestHost( new([], [], [], testHostLauncher: null, requireProcessRestart: false), new(), passiveNode: null, environment.Object, loggerFactory.Object, Mock.Of()); CancellationTokenSource first = EnsureControllerFinalizationCancellationTokenSource(host); CancellationTokenSource second = EnsureControllerFinalizationCancellationTokenSource(host); Assert.IsFalse(first.Token.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500))); ArmControllerFinalizationTimeout(host); Assert.AreSame(first, second); Assert.IsTrue(first.Token.WaitHandle.WaitOne(TimeoutHelper.DefaultHangTimeSpanTimeout)); } [TestMethod] public async Task TestHostControllerFinalization_CancellationDuringCleanupArmsStableToken() { Mock environment = new(); environment .Setup(x => x.GetEnvironmentVariable(EnvironmentVariableConstants.TESTINGPLATFORM_TESTHOSTCONTROLLER_FINALIZATION_TIMEOUT_SECONDS)) .Returns("0.1"); Mock loggerFactory = new(); loggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(Mock.Of()); using var host = new TestHostControllersTestHost( new([], [], [], testHostLauncher: null, requireProcessRestart: false), new(), passiveNode: null, environment.Object, loggerFactory.Object, Mock.Of()); using CancellationTokenSource applicationCancellationTokenSource = new(); TaskCompletionSource cleanupStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); TaskCompletionSource releaseCleanup = new(TaskCreationOptions.RunContinuationsAsynchronously); EnsureControllerFinalizationCancellationTokenSource(host); RegisterControllerFinalizationTransition(host, applicationCancellationTokenSource.Token); try { Task cleanup = TryRunControllerCleanupAsync( host, () => { cleanupStarted.TrySetResult(true); return releaseCleanup.Task; }); await cleanupStarted.Task.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); #pragma warning disable VSTHRD103 // CancelAsync is unavailable on net462, which this project also targets. applicationCancellationTokenSource.Cancel(); #pragma warning restore VSTHRD103 await cleanup.TimeoutAfterAsync(TimeoutHelper.DefaultHangTimeSpanTimeout); Assert.IsFalse(await cleanup); } finally { releaseCleanup.TrySetResult(true); } } [DataRow(true)] [DataRow(false)] [TestMethod] public async Task TestHostController_ComposeFactory_ShouldSucceed(bool withParameter) { TestHostControllersManager testHostControllerManager = new(); CompositeExtensionFactory compositeExtensionFactory = withParameter ? new(sp => new TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider(sp)) : new(() => new TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider()); testHostControllerManager.AddEnvironmentVariableProvider(compositeExtensionFactory); testHostControllerManager.AddProcessLifetimeHandler(compositeExtensionFactory); TestHostControllerConfiguration configuration = await testHostControllerManager.BuildAsync(_serviceProvider); Assert.IsTrue(configuration.RequireProcessRestart); Assert.HasCount(1, configuration.LifetimeHandlers); Assert.HasCount(1, configuration.EnvironmentVariableProviders); Assert.AreEqual((object)configuration.LifetimeHandlers[0], configuration.EnvironmentVariableProviders[0]); Assert.AreEqual(((ICompositeExtensionFactory)compositeExtensionFactory).GetInstance(), configuration.LifetimeHandlers[0]); Assert.AreEqual(((ICompositeExtensionFactory)compositeExtensionFactory).GetInstance(), configuration.EnvironmentVariableProviders[0]); } #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. [TestMethod] [DataRow("environment")] [DataRow("lifetime")] [DataRow("launcher")] [DataRow("consumer")] public void ControllerRegistration_NullCompositeFactoryIsRejectedBeforeBuild(string kind) { TestHostControllersManager manager = new(); Action register = kind switch { "environment" => () => manager.AddEnvironmentVariableProvider((CompositeExtensionFactory)null!), "lifetime" => () => manager.AddProcessLifetimeHandler((CompositeExtensionFactory)null!), "launcher" => () => manager.AddTestHostLauncher((CompositeExtensionFactory)null!), _ => () => manager.AddDataConsumer((CompositeExtensionFactory)null!), }; ArgumentNullException error = Assert.ThrowsExactly(register); Assert.AreEqual("compositeServiceFactory", error.ParamName); } [TestMethod] [DataRow("environment")] [DataRow("lifetime")] [DataRow("launcher")] [DataRow("consumer")] public void ControllerRegistration_SameCompositeFactoryCannotBeRegisteredTwiceInOneCategory(string kind) { TestHostControllersManager manager = new(); Action register; if (kind == "environment") { CompositeExtensionFactory factory = new(() => new TestHostEnvironmentVariableProvider("environment")); register = () => manager.AddEnvironmentVariableProvider(factory); } else if (kind == "lifetime") { CompositeExtensionFactory factory = new(() => new TestHostProcessLifetimeHandler("lifetime")); register = () => manager.AddProcessLifetimeHandler(factory); } else if (kind == "launcher") { CompositeExtensionFactory factory = new(() => new TestHostLauncher("launcher")); register = () => manager.AddTestHostLauncher(factory); } else { CompositeExtensionFactory factory = new(() => new Consumer("consumer")); register = () => manager.AddDataConsumer(factory); } register(); ArgumentException error = Assert.ThrowsExactly(register); Assert.Contains("already", error.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("registered", error.Message, StringComparison.OrdinalIgnoreCase); } [TestMethod] [DataRow(false)] [DataRow(true)] public async Task ControllerRegistration_CompositeLauncherIsInitializedAndStoredOnlyWhenEnabled(bool enabled) { TestHostControllersManager manager = new(); var launcher = new Mock(); launcher.SetupGet(value => value.Uid).Returns("composite.launcher"); launcher.Setup(value => value.IsEnabledAsync()).ReturnsAsync(enabled); Mock initialization = launcher.As(); initialization.Setup(value => value.InitializeAsync()).Returns(Task.CompletedTask); int constructions = 0; CompositeExtensionFactory factory = new(serviceProvider => { Assert.AreSame(_serviceProvider, serviceProvider); constructions++; return launcher.Object; }); manager.AddTestHostLauncher(factory); TestHostControllerConfiguration first = await manager.BuildAsync(_serviceProvider); TestHostControllerConfiguration second = await manager.BuildAsync(_serviceProvider); Assert.AreEqual(enabled, first.RequireProcessRestart); Assert.AreEqual(enabled, second.RequireProcessRestart); Assert.AreSame(enabled ? launcher.Object : null, first.TestHostLauncher); Assert.AreSame(first.TestHostLauncher, second.TestHostLauncher); Assert.AreEqual(1, constructions); initialization.Verify(value => value.InitializeAsync(), enabled ? Times.Once : Times.Never); Assert.HasCount(enabled ? 1 : 0, _serviceProvider.Services.Where(value => ReferenceEquals(value, launcher.Object))); launcher.Verify(value => value.LaunchTestHostAsync(It.IsAny(), It.IsAny()), Times.Never); } [TestMethod] [DataRow(false)] [DataRow(true)] public async Task ControllerRegistration_CompositeLaunchersRejectDuplicateIdentityAndMultipleEnabledInstances(bool sameIdentity) { TestHostControllersManager manager = new(); CompositeExtensionFactory first = new(() => new TestHostLauncher("first")); CompositeExtensionFactory second = new(() => new TestHostLauncher(sameIdentity ? "first" : "second")); manager.AddTestHostLauncher(first); manager.AddTestHostLauncher(second); InvalidOperationException error = await Assert.ThrowsExactlyAsync(() => manager.BuildAsync(_serviceProvider)); if (sameIdentity) { Assert.Contains("first", error.Message); Assert.Contains(typeof(TestHostLauncher).ToString(), error.Message); } else { Assert.Contains("one", error.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("launcher", error.Message, StringComparison.OrdinalIgnoreCase); } } [TestMethod] public async Task ControllerRegistration_CompositeAndOrdinaryFactoriesPreserveRelativeOrderAndFirstProviderPriority() { TestHostControllersManager manager = new(); manager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("ordinary.first")); CompositeExtensionFactory composite = new(() => new TestHostEnvironmentVariableProvider("composite")); manager.AddEnvironmentVariableProvider(composite); manager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("ordinary.last")); manager.AddEnvironmentVariableProviderFirst(_ => new TestHostEnvironmentVariableProvider("priority")); manager.AddProcessLifetimeHandler(_ => new TestHostProcessLifetimeHandler("ordinary.handler")); CompositeExtensionFactory handler = new(() => new TestHostProcessLifetimeHandler("composite.handler")); manager.AddProcessLifetimeHandler(handler); Consumer? createdConsumer = null; CompositeExtensionFactory consumer = new(() => createdConsumer = new Consumer("controller.consumer")); manager.AddDataConsumer(consumer); TestHostControllerConfiguration configuration = await manager.BuildAsync(_serviceProvider); Assert.AreSequenceEqual( ["priority", "ordinary.first", "composite", "ordinary.last"], configuration.EnvironmentVariableProviders.Select(value => value.Uid)); Assert.AreSequenceEqual(["ordinary.handler", "composite.handler"], configuration.LifetimeHandlers.Select(value => value.Uid)); Assert.AreEqual("controller.consumer", Assert.ContainsSingle(configuration.DataConsumer).Uid); Assert.AreSame(createdConsumer, configuration.DataConsumer[0], "Data consumer registration clones its composite factory but must register the actual produced instance."); Assert.IsTrue(configuration.RequireProcessRestart); Assert.IsNull(configuration.TestHostLauncher); } [TestMethod] [DataRow(false, true)] [DataRow(true, false)] [DataRow(true, true)] public async Task ControllerRegistration_CompletionObserversDoNotIntroduceControllerAndHonorEnabledState(bool controller, bool enabled) { TestHostControllersManager manager = new(); var observer = new Mock(); observer.SetupGet(value => value.Uid).Returns("completion.observer"); observer.Setup(value => value.IsEnabledAsync()).ReturnsAsync(enabled); int constructions = 0; manager.AddRunCompletionHandler(_ => { constructions++; return observer.Object; }); if (controller) { manager.AddEnvironmentVariableProvider(_ => new TestHostEnvironmentVariableProvider("existing.controller")); } TestHostControllerConfiguration configuration = await manager.BuildAsync(_serviceProvider); Assert.AreEqual(controller, configuration.RequireProcessRestart); Assert.AreEqual(controller ? 1 : 0, constructions); Assert.HasCount(controller && enabled ? 1 : 0, configuration.RunCompletionHandlers); if (controller && enabled) { Assert.AreSame(observer.Object, configuration.RunCompletionHandlers[0]); } } [TestMethod] public async Task TestHostLauncher_WhenRegistered_ForcesProcessRestartAndIsStored() { TestHostControllersManager testHostControllerManager = new(); TestHostLauncher launcher = new("launcher"); testHostControllerManager.AddTestHostLauncher(_ => launcher); TestHostControllerConfiguration configuration = await testHostControllerManager.BuildAsync(_serviceProvider); Assert.IsTrue(configuration.RequireProcessRestart); Assert.AreEqual((object)launcher, configuration.TestHostLauncher); } [TestMethod] [OSCondition(ConditionMode.Include, OperatingSystems.Windows, IgnoreMessage = "AppContainer pipe authorization is Windows-only.")] public async Task TestHostLauncher_AuthorizationConsumerResolvesAndStoresIdentity() { TestHostControllersManager testHostControllerManager = new(); TestHostLauncher launcher = new("launcher"); testHostControllerManager.AddTestHostLauncher(_ => launcher); ITestHostLauncher? builtLauncher = await testHostControllerManager.BuildTestHostLauncherAsync(_serviceProvider); IReadOnlyList? authorizedSecurityIdentities = await _serviceProvider.ResolveTestHostControllerAuthorizedSecurityIdentitiesAsync( builtLauncher, "testhost.exe", new NopLogger(), CancellationToken.None); Assert.AreSequenceEqual([ContosoPackageSid], authorizedSecurityIdentities); Assert.AreSame(authorizedSecurityIdentities, _serviceProvider.TestHostControllerAuthorizedSecurityIdentities); } [TestMethod] public async Task TestHostLauncher_MultipleRegistered_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); testHostControllerManager.AddTestHostLauncher(_ => new TestHostLauncher("launcher1")); testHostControllerManager.AddTestHostLauncher(_ => new TestHostLauncher("launcher2")); await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); } [TestMethod] public async Task TestHostLauncher_DuplicatedId_ShouldFail() { TestHostControllersManager testHostControllerManager = new(); testHostControllerManager.AddTestHostLauncher(_ => new TestHostLauncher("duplicatedId")); testHostControllerManager.AddTestHostLauncher(_ => new TestHostLauncher("duplicatedId")); InvalidOperationException invalidOperationException = await Assert.ThrowsExactlyAsync(() => testHostControllerManager.BuildAsync(_serviceProvider)); Assert.IsTrue(invalidOperationException.Message.Contains("duplicatedId") && invalidOperationException.Message.Contains(typeof(TestHostLauncher).ToString())); } #pragma warning restore TPEXP [DataRow(true)] [DataRow(false)] [TestMethod] public void ComposeFactory_InvalidComposition_ShouldFail(bool withParameter) { CompositeExtensionFactory compositeExtensionFactory = withParameter ? new CompositeExtensionFactory(sp => new InvalidComposition(sp)) : new CompositeExtensionFactory(() => new InvalidComposition()); InvalidOperationException invalidOperationException = Assert.ThrowsExactly(() => ((ICompositeExtensionFactory)compositeExtensionFactory).GetInstance()); Assert.AreEqual(CompositeExtensionFactory.ValidateCompositionErrorMessage, invalidOperationException.Message); } private static Task TryRunControllerExtensionAsync( Func finalization, CancellationToken cancellationToken) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "TryRunControllerExtensionAsync", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.TryRunControllerExtensionAsync."); return (Task?)method.Invoke(null, [finalization, cancellationToken]) ?? throw new InvalidOperationException("TestHostControllersTestHost.TryRunControllerExtensionAsync returned null."); } private static Task WaitForExitAfterTerminationAsync(IProcess process, TimeSpan timeout, ILogger logger) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "WaitForExitAfterTerminationAsync", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.WaitForExitAfterTerminationAsync."); return (Task?)method.Invoke(null, [process, timeout, logger]) ?? throw new InvalidOperationException("TestHostControllersTestHost.WaitForExitAfterTerminationAsync returned null."); } private static Task<(int ExitCode, TestHostProcessInformation ProcessInformation, string? ExtensionInformation)> RunTestHostProcessAsync( TestHostControllersTestHost host, ProcessStartInfo processStartInfo, IReadOnlyList partialCommandLine, int currentPid, IProcessHandler process, IConfiguration configuration, object testHostControllerIpc, TestHostControllerCancellationServer testHostControllerCancellationServer, ProxyOutputDevice outputDevice, ITelemetryInformation telemetryInformation, CancellationToken applicationCancellationToken) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "RunTestHostProcessAsync", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.RunTestHostProcessAsync."); return (Task<(int ExitCode, TestHostProcessInformation ProcessInformation, string? ExtensionInformation)>?)method.Invoke( host, [ processStartInfo, partialCommandLine, currentPid, process, configuration, testHostControllerIpc, testHostControllerCancellationServer, outputDevice, telemetryInformation, Stopwatch.StartNew(), applicationCancellationToken, ]) ?? throw new InvalidOperationException("TestHostControllersTestHost.RunTestHostProcessAsync returned null."); } private static IDisposable CreateTestHostControllerIpc( TestHostControllersTestHost host, CancellationToken cancellationToken) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "CreateTestHostControllerIpc", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.CreateTestHostControllerIpc."); return (IDisposable?)method.Invoke(host, [null, cancellationToken]) ?? throw new InvalidOperationException("TestHostControllersTestHost.CreateTestHostControllerIpc returned null."); } private static void MarkOutputDeviceStillRunning(List servicesStillRunning, ProxyOutputDevice outputDevice) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "MarkOutputDeviceStillRunning", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.MarkOutputDeviceStillRunning."); method.Invoke(null, [servicesStillRunning, outputDevice]); } private static CancellationTokenSource EnsureControllerFinalizationCancellationTokenSource(TestHostControllersTestHost host) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "EnsureControllerFinalizationCancellationTokenSource", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.EnsureControllerFinalizationCancellationTokenSource."); return (CancellationTokenSource?)method.Invoke(host, null) ?? throw new InvalidOperationException("TestHostControllersTestHost.EnsureControllerFinalizationCancellationTokenSource returned null."); } private static void ArmControllerFinalizationTimeout(TestHostControllersTestHost host) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "ArmControllerFinalizationTimeout", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.ArmControllerFinalizationTimeout."); method.Invoke(host, null); } private static void RegisterControllerFinalizationTransition(TestHostControllersTestHost host, CancellationToken applicationCancellationToken) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "RegisterControllerFinalizationTransition", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.RegisterControllerFinalizationTransition."); method.Invoke(host, [applicationCancellationToken]); } private static Task TryRunControllerCleanupAsync(TestHostControllersTestHost host, Func cleanup) { MethodInfo method = typeof(TestHostControllersTestHost).GetMethod( "TryRunControllerCleanupAsync", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not find TestHostControllersTestHost.TryRunControllerCleanupAsync."); return (Task?)method.Invoke(host, [cleanup]) ?? throw new InvalidOperationException("TestHostControllersTestHost.TryRunControllerCleanupAsync returned null."); } [SuppressMessage("Design", "TA0001:Extension should not implement cross-functional areas", Justification = "Done on purpose for testing error")] private sealed class InvalidComposition : ITestHostProcessLifetimeHandler, ITestSessionLifetimeHandler { public InvalidComposition(IServiceProvider serviceProvider) { } public InvalidComposition() { } public string Uid => nameof(InvalidComposition); public string Version => PlatformVersion.Version; public string DisplayName => nameof(InvalidComposition); public string Description => nameof(InvalidComposition); public Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); public Task IsEnabledAsync() => throw new NotImplementedException(); public Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task OnTestHostProcessStartedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task OnTestSessionStartingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); public Task OnTestSessionFinishingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); } private sealed class TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider : ITestHostProcessLifetimeHandler, ITestHostEnvironmentVariableProvider { public TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider() { } public TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider(IServiceProvider serviceProvider) { } public string Uid => nameof(TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider); public string Version => PlatformVersion.Version; public string DisplayName => nameof(TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider); public string Description => nameof(TestHostProcessLifetimeHandlerPlusTestHostEnvironmentVariableProvider); public Task ValidateTestHostEnvironmentVariablesAsync(IReadOnlyEnvironmentVariables environmentVariables) => throw new NotImplementedException(); public Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); public Task IsEnabledAsync() => Task.FromResult(true); public Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task OnTestHostProcessStartedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task UpdateAsync(IEnvironmentVariables environmentVariables) => throw new NotImplementedException(); } private sealed class TestHostProcessLifetimeHandler : ITestHostProcessLifetimeHandler { public TestHostProcessLifetimeHandler(string id) => Uid = id; public string Uid { get; } public string Version => nameof(Consumer); public string DisplayName => nameof(Consumer); public string Description => nameof(Consumer); public Task BeforeTestHostProcessStartAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); public Task IsEnabledAsync() => Task.FromResult(true); public Task OnTestHostProcessExitedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task OnTestHostProcessStartedAsync(ITestHostProcessInformation testHostProcessInformation, CancellationToken cancellationToken) => throw new NotImplementedException(); } #pragma warning disable TPEXP // Type is for evaluation purposes only and is subject to change or removal in future updates. private sealed class TestHostLauncher : ITestHostLauncher, ITestHostControllerConnectionAuthorizer { public TestHostLauncher(string id) => Uid = id; public string Uid { get; } public string Version => nameof(TestHostLauncher); public string DisplayName => nameof(TestHostLauncher); public string Description => nameof(TestHostLauncher); public Task IsEnabledAsync() => Task.FromResult(true); public Task LaunchTestHostAsync(TestHostLaunchContext context, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task> GetAuthorizedSecurityIdentitiesAsync(string testHostFileName, CancellationToken cancellationToken) => Task.FromResult>([ContosoPackageSid]); } #pragma warning restore TPEXP private sealed class TestHostEnvironmentVariableProvider : ITestHostEnvironmentVariableProvider { public TestHostEnvironmentVariableProvider(string id) => Uid = id; public string Uid { get; } public string Version => nameof(Consumer); public string DisplayName => nameof(Consumer); public string Description => nameof(Consumer); public Task IsEnabledAsync() => Task.FromResult(true); public Task UpdateAsync(IEnvironmentVariables environmentVariables) => throw new NotImplementedException(); public Task ValidateTestHostEnvironmentVariablesAsync(IReadOnlyEnvironmentVariables environmentVariables) => throw new NotImplementedException(); } public sealed class TestSessionLifetimeHandlerPlusConsumer : ITestSessionLifetimeHandler, IDataConsumer { public TestSessionLifetimeHandlerPlusConsumer() { } public TestSessionLifetimeHandlerPlusConsumer(IServiceProvider serviceProvider) { } public string Uid => nameof(TestSessionLifetimeHandlerPlusConsumer); public string Version => PlatformVersion.Version; public string DisplayName => nameof(TestSessionLifetimeHandlerPlusConsumer); public string Description => nameof(TestSessionLifetimeHandlerPlusConsumer); public Type[] DataTypesConsumed => []; public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) => throw new NotImplementedException(); public Task IsEnabledAsync() => Task.FromResult(true); public Task OnTestSessionFinishingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); public Task OnTestSessionStartingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); } private sealed class TestSessionLifetimeHandler : ITestSessionLifetimeHandler { public TestSessionLifetimeHandler(string id) => Uid = id; public string Uid { get; } public string Version => nameof(Consumer); public string DisplayName => nameof(Consumer); public string Description => nameof(Consumer); public Task IsEnabledAsync() => Task.FromResult(true); public Task OnTestSessionStartingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); public Task OnTestSessionFinishingAsync(ITestSessionContext testSessionContext) => throw new NotImplementedException(); } private sealed class Consumer : IDataConsumer { public Consumer(string id) => Uid = id; public string Uid { get; } public string Version => nameof(Consumer); public string DisplayName => nameof(Consumer); public string Description => nameof(Consumer); public Type[] DataTypesConsumed => throw new NotImplementedException(); public Task IsEnabledAsync() => Task.FromResult(true); public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationToken cancellationToken) => throw new NotImplementedException(); } private sealed class ApplicationLifecycleCallbacks : ITestHostApplicationLifetime { public ApplicationLifecycleCallbacks(string id) => Uid = id; public string Uid { get; } public string Version => nameof(ApplicationLifecycleCallbacks); public string DisplayName => nameof(ApplicationLifecycleCallbacks); public string Description => nameof(ApplicationLifecycleCallbacks); public Task AfterRunAsync(int exitCode, CancellationToken cancellationToken) => Task.CompletedTask; public Task BeforeRunAsync(CancellationToken cancellationToken) => Task.CompletedTask; public Task IsEnabledAsync() => Task.FromResult(true); } }