Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ private static async Task NotifyTestSessionEndAsync(ITestSessionContext testSess
return;
}

IShutdownProgressReporter? shutdownProgressReporter = serviceProvider.GetService<IShutdownProgressReporter>();

// First, we call OnTestSessionFinishingAsync on all non-consumers.
bool hasNonDataConsumers = false;
foreach (ITestSessionLifetimeHandler testSessionLifetimeHandler in testSessionLifetimeHandlersContainer.TestSessionLifetimeHandlers)
Expand All @@ -272,6 +274,7 @@ private static async Task NotifyTestSessionEndAsync(ITestSessionContext testSess
hasNonDataConsumers = true;

using (otelService?.StartActivity(testSessionLifetimeHandler.Uid, testSessionLifetimeHandler.ToOTelTags()))
using (shutdownProgressReporter?.Track(testSessionLifetimeHandler.Uid, testSessionLifetimeHandler.DisplayName, nameof(ITestSessionLifetimeHandler.OnTestSessionFinishingAsync)))
{
await testSessionLifetimeHandler.OnTestSessionFinishingAsync(testSessionContext).ConfigureAwait(false);
}
Expand All @@ -294,6 +297,7 @@ private static async Task NotifyTestSessionEndAsync(ITestSessionContext testSess
}

using (otelService?.StartActivity(testSessionLifetimeHandler.Uid, testSessionLifetimeHandler.ToOTelTags()))
using (shutdownProgressReporter?.Track(testSessionLifetimeHandler.Uid, testSessionLifetimeHandler.DisplayName, nameof(ITestSessionLifetimeHandler.OnTestSessionFinishingAsync)))
{
await testSessionLifetimeHandler.OnTestSessionFinishingAsync(testSessionContext).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,15 @@ private async Task<BuildContext> SetupCommonServicesAsync(
serviceProvider.TryAddService(context.ProxyOutputDevice);
serviceProvider.TryAddService(context.ProxyOutputDevice.OriginalOutputDevice);

// Reports extensions/consumers that have not yet completed once the test-application
// cancellation token is signalled. Registered after the output device so it can use it.
ShutdownProgressReporter shutdownProgressReporter = new(
context.TestApplicationCancellationTokenSource,
context.ProxyOutputDevice,
context.LoggerFactory,
systemClock);
serviceProvider.AddService(shutdownProgressReporter);

context.TestFrameworkCapabilities = TestFramework!.TestFrameworkCapabilitiesFactory(serviceProvider);
if (context.TestFrameworkCapabilities is IAsyncInitializableExtension testFrameworkCapabilitiesAsyncInitializable)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ private static async Task<ITestFramework> BuildTestFrameworkAsync(TestFrameworkB
serviceProvider.GetTestApplicationCancellationTokenSource(),
serviceProvider.GetTask(),
serviceProvider.GetLoggerFactory(),
serviceProvider.GetEnvironment());
serviceProvider.GetEnvironment(),
serviceProvider.GetService<IShutdownProgressReporter>());
await concreteMessageBusService.InitAsync().ConfigureAwait(false);
testFrameworkBuilderData.MessageBusProxy.SetBuiltMessageBus(concreteMessageBusService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,8 @@ protected override async Task<int> InternalRunAsync(CancellationToken cancellati
ServiceProvider.GetTestApplicationCancellationTokenSource(),
ServiceProvider.GetTask(),
ServiceProvider.GetLoggerFactory(),
ServiceProvider.GetEnvironment());
ServiceProvider.GetEnvironment(),
ServiceProvider.GetService<IShutdownProgressReporter>());
await concreteMessageBusService.InitAsync().ConfigureAwait(false);
((MessageBusProxy)ServiceProvider.GetMessageBus()).SetBuiltMessageBus(concreteMessageBusService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ internal sealed class AsynchronousMessageBus : BaseMessageBus, IMessageBus, IDis
private readonly Dictionary<Type, List<IAsyncConsumerDataProcessor>> _dataTypeConsumers = [];
private readonly IDataConsumer[] _dataConsumers;
private readonly ITestApplicationCancellationTokenSource _testApplicationCancellationTokenSource;
private readonly IShutdownProgressReporter? _shutdownProgressReporter;
private bool _disabled;

public AsynchronousMessageBus(
Expand All @@ -32,11 +33,23 @@ public AsynchronousMessageBus(
ITask task,
ILoggerFactory loggerFactory,
IEnvironment environment)
: this(dataConsumers, testApplicationCancellationTokenSource, task, loggerFactory, environment, shutdownProgressReporter: null)
{
}

public AsynchronousMessageBus(
IDataConsumer[] dataConsumers,
ITestApplicationCancellationTokenSource testApplicationCancellationTokenSource,
ITask task,
ILoggerFactory loggerFactory,
IEnvironment environment,
IShutdownProgressReporter? shutdownProgressReporter)
{
_dataConsumers = dataConsumers;
_testApplicationCancellationTokenSource = testApplicationCancellationTokenSource;
_task = task;
_environment = environment;
_shutdownProgressReporter = shutdownProgressReporter;
_logger = loggerFactory.CreateLogger<AsynchronousMessageBus>();
_isTraceLoggingEnabled = _logger.IsEnabled(LogLevel.Trace);
}
Expand Down Expand Up @@ -157,7 +170,10 @@ public override async Task DrainDataAsync()

foreach (IAsyncConsumerDataProcessor processor in _consumerProcessor.Values)
{
await processor.DrainDataAsync().ConfigureAwait(false);
using (_shutdownProgressReporter?.Track(processor.DataConsumer.Uid, processor.DataConsumer.DisplayName, nameof(IAsyncConsumerDataProcessor.DrainDataAsync)))
{
await processor.DrainDataAsync().ConfigureAwait(false);
}
}

bool anyNewlyReceived = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ Read more about Microsoft Testing Platform telemetry: https://aka.ms/testingplat
<data name="CancellingTestSession" xml:space="preserve">
<value>Canceling the test session...</value>
</data>
<data name="ShutdownProgressStillWaitingPrefix" xml:space="preserve">
<value>Still waiting for: </value>
<comment>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</comment>
</data>
<data name="DiagnosticFileLevelWithAsyncFlush" xml:space="preserve">
<value>Diagnostic file (level '{0}' with async flush): {1}</value>
<comment>0 level such as verbose,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Může mít jenom jeden argument jako řetězec ve formátu &lt;value&gt;[h|m|s]
<target state="translated">Instance typu ITestFramework by neměly být registrovány prostřednictvím poskytovatele služeb, ale prostřednictvím metody ITestApplicationBuilder.RegisterTestFramework.</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Přeskočeno</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Nimmt ein Argument als Zeichenfolge im Format &lt;value&gt;[h|m|s], wobei "value
<target state="translated">Instanzen vom Typ "ITestFramework" sollten nicht über den Dienstanbieter, sondern über "ITestApplicationBuilder.RegisterTestFramework" registriert werden.</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Übersprungen</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Toma un argumento como cadena con el formato &lt;value&gt;[h|m|s] donde 'value'
<target state="translated">Las instancias de tipo "ITestFramework" no deben registrarse mediante el proveedor de servicios, sino mediante "ITestApplicationBuilder.RegisterTestFramework".</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Omitida</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Prend un argument sous forme de chaîne au format &lt;value&gt;[h|m|s] où « v
<target state="translated">Les instances de type « ITestFramework » ne doivent pas être inscrites par le biais du fournisseur de services, mais par le biais de « ITestApplicationBuilder.RegisterTestFramework »</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Ignoré</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Acquisisce un argomento come stringa nel formato &lt;value&gt;[h|m|s] dove 'valu
<target state="translated">Le istanze di tipo 'ITestFramework' non devono essere registrate tramite il provider di servizi, ma tramite 'ITestApplicationBuilder.RegisterTestFramework'</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Ignorato</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<target state="translated">'ITestFramework' 型のインスタンスは、サービス プロバイダーを介して登録することはできません。'ITestApplicationBuilder.RegisterTestFramework' を介して登録する必要があります</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">スキップ</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<target state="translated">'ITestFramework' 형식의 인스턴스는 서비스 공급자를 통해 등록하지 않아야 하지만 'ITestApplicationBuilder.RegisterTestFramework'를 통해 등록해야 합니다.</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">건너뜀</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Pobiera jeden argument jako ciąg w formacie &lt;value&gt;[h|m|s], gdzie element
<target state="translated">Wystąpienia typu „ITestFramework” nie powinny być rejestrowane za pośrednictwem dostawcy usług, ale za pośrednictwem elementu „ITestApplicationBuilder.RegisterTestFramework”</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Pominięto</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Recebe um argumento como cadeia de caracteres no formato &lt;valor&gt;[h|m|s] em
<target state="translated">Instâncias do tipo "ITestFramework" não devem ser registradas por meio do provedor de serviços, mas por meio de "ITestApplicationBuilder.RegisterTestFramework"</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Ignorado</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<target state="translated">Экземпляры типа "ITestFramework" следует регистрировать не через поставщика услуг, а через "ITestApplicationBuilder.RegisterTestFramework"</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Пропущен</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Bir bağımsız değişkeni, 'value' değerinin kayan olduğu &lt;value&gt;[h|m|
<target state="translated">'ITestFramework' türündeki örnekler hizmet sağlayıcısı aracılığıyla değil, 'ITestApplicationBuilder.RegisterTestFramework' aracılığıyla kaydedilmelidir</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">Atlandı</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<target state="translated">不应通过服务提供商注册类型为“ITestFramework”的实例,而应通过“ITestApplicationBuilder.RegisterTestFramework”进行注册</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">已跳过</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,11 @@ Takes one argument as string in the format &lt;value&gt;[h|m|s] where 'value' is
<target state="translated">類型 'ITestFramework' 的執行個體不應透過服務提供者註冊,而是透過 'ITestApplicationBuilder.RegisterTestFramework' 註冊</target>
<note />
</trans-unit>
<trans-unit id="ShutdownProgressStillWaitingPrefix">
<source>Still waiting for: </source>
<target state="new">Still waiting for: </target>
<note>Prefix for the message that lists extensions that have not yet completed their shutdown work. Each entry is formatted as "{DisplayName} ({Phase}, {Seconds}s)" and entries are separated by "; ".</note>
</trans-unit>
<trans-unit id="Skipped">
<source>Skipped</source>
<target state="translated">略過</target>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Microsoft.Testing.Platform.Services;

/// <summary>
/// Tracks units of work that the platform is awaiting during shutdown so that, after
/// cancellation has been requested, the user can be told which extensions are still
/// running and how long each one has been blocking.
/// </summary>
/// <remarks>
/// Implementations must be thread-safe. Callers wrap shutdown-relevant awaits with
/// <c>using (reporter.Track(uid, displayName, phase)) { await ... }</c>; on the
/// happy path (no cancellation) this is effectively a no-op beyond bookkeeping.
/// </remarks>
internal interface IShutdownProgressReporter
{
/// <summary>
/// Registers a unit of in-flight work and returns a disposable that removes it.
/// </summary>
/// <param name="uid">Stable identifier of the extension / consumer being awaited.</param>
/// <param name="displayName">Human-readable name surfaced to the user.</param>
/// <param name="phase">Short label describing what we are awaiting (e.g. <c>OnTestSessionFinishingAsync</c>).</param>
/// <returns>A disposable that removes the tracker when the awaited work completes.</returns>
IDisposable Track(string uid, string displayName, string phase);
}
Loading