Skip to content

Commit 00ca8b7

Browse files
committed
Add lifecycle callbacks to test host orchestrator
1 parent 97ee44f commit 00ca8b7

File tree

6 files changed

+132
-0
lines changed

6 files changed

+132
-0
lines changed

src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildExtensions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public static void AddMSBuild(this ITestApplicationBuilder builder)
2626
serviceProvider.GetCommandLineOptions(),
2727
serviceProvider.GetTestApplicationCancellationTokenSource()));
2828

29+
((TestApplicationBuilder)builder).TestHostOrchestrator.AddTestApplicationLifecycleCallbacks(
30+
serviceProvider => new MSBuildOrchestratorLifecycleCallbacks(
31+
serviceProvider.GetConfiguration(),
32+
serviceProvider.GetCommandLineOptions(),
33+
serviceProvider.GetTestApplicationCancellationTokenSource()));
34+
2935
CompositeExtensionFactory<MSBuildConsumer> compositeExtensionFactory
3036
= new(serviceProvider => new MSBuildConsumer(
3137
serviceProvider,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Microsoft.Testing.Extensions.MSBuild.Serializers;
5+
using Microsoft.Testing.Platform.CommandLine;
6+
using Microsoft.Testing.Platform.Configurations;
7+
using Microsoft.Testing.Platform.Extensions.TestHost;
8+
using Microsoft.Testing.Platform.Helpers;
9+
using Microsoft.Testing.Platform.IPC;
10+
using Microsoft.Testing.Platform.IPC.Models;
11+
using Microsoft.Testing.Platform.IPC.Serializers;
12+
using Microsoft.Testing.Platform.Services;
13+
14+
namespace Microsoft.Testing.Extensions.MSBuild;
15+
16+
internal sealed class MSBuildOrchestratorLifecycleCallbacks : ITestApplicationLifecycleCallbacks
17+
{
18+
private readonly IConfiguration _configuration;
19+
private readonly ICommandLineOptions _commandLineOptions;
20+
private readonly ITestApplicationCancellationTokenSource _testApplicationCancellationTokenSource;
21+
22+
public MSBuildOrchestratorLifecycleCallbacks(
23+
IConfiguration configuration,
24+
ICommandLineOptions commandLineOptions,
25+
ITestApplicationCancellationTokenSource testApplicationCancellationTokenSource)
26+
{
27+
_configuration = configuration;
28+
_commandLineOptions = commandLineOptions;
29+
_testApplicationCancellationTokenSource = testApplicationCancellationTokenSource;
30+
}
31+
32+
public string Uid => nameof(MSBuildOrchestratorLifecycleCallbacks);
33+
34+
public string Version => AppVersion.DefaultSemVer;
35+
36+
public string DisplayName => nameof(MSBuildOrchestratorLifecycleCallbacks);
37+
38+
public string Description => Resources.ExtensionResources.MSBuildExtensionsDescription;
39+
40+
public Task<bool> IsEnabledAsync()
41+
=> Task.FromResult(_commandLineOptions.IsOptionSet(MSBuildConstants.MSBuildNodeOptionKey));
42+
43+
public async Task BeforeRunAsync(CancellationToken cancellationToken)
44+
{
45+
if (!_commandLineOptions.TryGetOptionArgumentList(MSBuildConstants.MSBuildNodeOptionKey, out string[]? msbuildInfo))
46+
{
47+
throw new InvalidOperationException($"MSBuild pipe name not found in the command line, missing {MSBuildConstants.MSBuildNodeOptionKey}");
48+
}
49+
50+
if (msbuildInfo is null || msbuildInfo.Length != 1 || string.IsNullOrEmpty(msbuildInfo[0]))
51+
{
52+
throw new InvalidOperationException($"MSBuild pipe name not found in the command line, missing argument for {MSBuildConstants.MSBuildNodeOptionKey}");
53+
}
54+
55+
using var pipeClient = new NamedPipeClient(msbuildInfo[0]);
56+
pipeClient.RegisterSerializer(new ModuleInfoRequestSerializer(), typeof(ModuleInfoRequest));
57+
pipeClient.RegisterSerializer(new VoidResponseSerializer(), typeof(VoidResponse));
58+
using var cancellationTokenSource = new CancellationTokenSource(TimeoutHelper.DefaultHangTimeSpanTimeout);
59+
using var linkedCancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, _testApplicationCancellationTokenSource.CancellationToken);
60+
await pipeClient.ConnectAsync(linkedCancellationToken.Token);
61+
await pipeClient.RequestReplyAsync<ModuleInfoRequest, VoidResponse>(
62+
new ModuleInfoRequest(
63+
RuntimeInformation.FrameworkDescription,
64+
RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(),
65+
_configuration.GetTestResultDirectory()),
66+
_testApplicationCancellationTokenSource.CancellationToken);
67+
}
68+
69+
public Task AfterRunAsync(int exitCode, CancellationToken cancellation) => Task.CompletedTask;
70+
}

src/Platform/Microsoft.Testing.Platform/Hosts/TestHostBuilder.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,12 @@ await LogTestHostCreatedAsync(
364364
{
365365
policiesService.ProcessRole = TestProcessRole.TestHostOrchestrator;
366366
await proxyOutputDevice.HandleProcessRoleAsync(TestProcessRole.TestHostOrchestrator);
367+
368+
// Build and register the test application lifecycle callbacks.
369+
ITestApplicationLifecycleCallbacks[] orchestratorTestApplicationLifecycleCallback =
370+
await ((TestHostOrchestratorManager)TestHostOrchestratorManager).BuildTestApplicationLifecycleCallbackAsync(serviceProvider);
371+
serviceProvider.AddServices(orchestratorTestApplicationLifecycleCallback);
372+
367373
return new TestHostOrchestratorHost(testHostOrchestratorConfiguration, serviceProvider);
368374
}
369375

src/Platform/Microsoft.Testing.Platform/Hosts/TestHostOchestratorHost.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using Microsoft.Testing.Platform.Extensions.TestHost;
45
using Microsoft.Testing.Platform.Extensions.TestHostOrchestrator;
56
using Microsoft.Testing.Platform.Helpers;
67
using Microsoft.Testing.Platform.Logging;
@@ -27,7 +28,18 @@ public async Task<int> RunAsync()
2728
await logger.LogInformationAsync($"Running test orchestrator '{testHostOrchestrator.Uid}'");
2829
try
2930
{
31+
foreach (ITestApplicationLifecycleCallbacks testApplicationLifecycleCallbacks in _serviceProvider.GetServicesInternal<ITestApplicationLifecycleCallbacks>())
32+
{
33+
await testApplicationLifecycleCallbacks.BeforeRunAsync(applicationCancellationToken.CancellationToken);
34+
}
35+
3036
exitCode = await testHostOrchestrator.OrchestrateTestHostExecutionAsync();
37+
38+
foreach (ITestApplicationLifecycleCallbacks testApplicationLifecycleCallbacks in _serviceProvider.GetServicesInternal<ITestApplicationLifecycleCallbacks>())
39+
{
40+
await testApplicationLifecycleCallbacks.AfterRunAsync(exitCode, applicationCancellationToken.CancellationToken);
41+
await DisposeHelper.DisposeAsync(testApplicationLifecycleCallbacks);
42+
}
3143
}
3244
catch (OperationCanceledException) when (applicationCancellationToken.CancellationToken.IsCancellationRequested)
3345
{

src/Platform/Microsoft.Testing.Platform/TestHostOrcherstrator/ITestHostOrchestratorManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using Microsoft.Testing.Platform.Extensions.TestHost;
45
using Microsoft.Testing.Platform.Services;
56

67
namespace Microsoft.Testing.Platform.Extensions.TestHostOrchestrator;
@@ -9,5 +10,7 @@ internal interface ITestHostOrchestratorManager
910
{
1011
void AddTestHostOrchestrator(Func<IServiceProvider, ITestHostOrchestrator> factory);
1112

13+
void AddTestApplicationLifecycleCallbacks(Func<IServiceProvider, ITestApplicationLifecycleCallbacks> testApplicationLifecycleCallbacks);
14+
1215
Task<TestHostOrchestratorConfiguration> BuildAsync(ServiceProvider serviceProvider);
1316
}

src/Platform/Microsoft.Testing.Platform/TestHostOrcherstrator/TestHostOrchestratorManager.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using Microsoft.Testing.Platform.Extensions.TestHost;
45
using Microsoft.Testing.Platform.Resources;
56
using Microsoft.Testing.Platform.Services;
67

78
namespace Microsoft.Testing.Platform.Extensions.TestHostOrchestrator;
89

910
internal sealed class TestHostOrchestratorManager : ITestHostOrchestratorManager
1011
{
12+
private readonly List<Func<IServiceProvider, ITestApplicationLifecycleCallbacks>> _testApplicationLifecycleCallbacksFactories = [];
1113
private List<Func<IServiceProvider, ITestHostOrchestrator>>? _factories;
1214

1315
public void AddTestHostOrchestrator(Func<IServiceProvider, ITestHostOrchestrator> factory)
@@ -48,4 +50,37 @@ public async Task<TestHostOrchestratorConfiguration> BuildAsync(ServiceProvider
4850

4951
return new TestHostOrchestratorConfiguration([.. orchestrators]);
5052
}
53+
54+
public void AddTestApplicationLifecycleCallbacks(Func<IServiceProvider, ITestApplicationLifecycleCallbacks> testApplicationLifecycleCallbacks)
55+
{
56+
Guard.NotNull(testApplicationLifecycleCallbacks);
57+
_testApplicationLifecycleCallbacksFactories.Add(testApplicationLifecycleCallbacks);
58+
}
59+
60+
internal async Task<ITestApplicationLifecycleCallbacks[]> BuildTestApplicationLifecycleCallbackAsync(ServiceProvider serviceProvider)
61+
{
62+
List<ITestApplicationLifecycleCallbacks> testApplicationLifecycleCallbacks = [];
63+
foreach (Func<IServiceProvider, ITestApplicationLifecycleCallbacks> testApplicationLifecycleCallbacksFactory in _testApplicationLifecycleCallbacksFactories)
64+
{
65+
ITestApplicationLifecycleCallbacks service = testApplicationLifecycleCallbacksFactory(serviceProvider);
66+
67+
// Check if we have already extensions of the same type with same id registered
68+
if (testApplicationLifecycleCallbacks.Any(x => x.Uid == service.Uid))
69+
{
70+
ITestApplicationLifecycleCallbacks currentRegisteredExtension = testApplicationLifecycleCallbacks.Single(x => x.Uid == service.Uid);
71+
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, PlatformResources.ExtensionWithSameUidAlreadyRegisteredErrorMessage, service.Uid, currentRegisteredExtension.GetType()));
72+
}
73+
74+
// We initialize only if enabled
75+
if (await service.IsEnabledAsync())
76+
{
77+
await service.TryInitializeAsync();
78+
79+
// Register the extension for usage
80+
testApplicationLifecycleCallbacks.Add(service);
81+
}
82+
}
83+
84+
return [.. testApplicationLifecycleCallbacks];
85+
}
5186
}

0 commit comments

Comments
 (0)