diff --git a/samples/BrowserPlayground/README.md b/samples/BrowserPlayground/README.md index 0abe67d884..ca760ec0d3 100644 --- a/samples/BrowserPlayground/README.md +++ b/samples/BrowserPlayground/README.md @@ -308,6 +308,37 @@ countdown-event wait, and the adapter's per-test task factory. Console output is routed through `BrowserOutputDevice`, which forwards to the browser's `globalThis.console.*` APIs via `[JSImport]`. +Long-running tests are surfaced as durable diagnostic lines after 60 seconds, with +exponential backoff (60 seconds, 2 minutes, 4 minutes, and so on): + +```console +[slow] still running after 1m 00s: MyLongRunningTest +``` + +Set `MTP_PROGRESS_SLOW_TEST_SECONDS` to a non-negative integer to change the first +reporting threshold; `0` disables these diagnostics. The browser launcher reads it from +the page query string: + +```text +index.html?MTP_PROGRESS_SLOW_TEST_SECONDS=5 +``` + +The Node launcher forwards the process environment variable into the managed WebAssembly +runtime: + +```powershell +$env:MTP_PROGRESS_SLOW_TEST_SECONDS = '5' +node runtests.mjs +``` + +Test starts are tracked silently, so this does not duplicate normal per-test progress output. + +The reporter is cooperative: it uses asynchronous delays and does not create a thread, +timer thread, or process. It can therefore identify an asynchronously suspended test, +because control returns to the browser event loop. It cannot report while a test +synchronously blocks the sole WebAssembly thread; no managed timer or continuation can +run until that test yields or returns. + ### Feature matrix (what is *not* available on browser-wasm) Several extensions/capabilities are intentionally unavailable in the browser sandbox and diff --git a/samples/BrowserPlayground/wwwroot/main.js b/samples/BrowserPlayground/wwwroot/main.js index a212ad1806..49d54e8f32 100644 --- a/samples/BrowserPlayground/wwwroot/main.js +++ b/samples/BrowserPlayground/wwwroot/main.js @@ -7,12 +7,20 @@ // Microsoft.Testing.Platform. import { dotnet } from './_framework/dotnet.js'; -const { runMain } = await dotnet +const slowTestThresholdEnvironmentVariable = 'MTP_PROGRESS_SLOW_TEST_SECONDS'; +const runtimeBuilder = dotnet // A browser page has no argv, so Microsoft.Testing.Platform command-line options are // taken from the page's query string, e.g.: // index.html?arg=--minimum-expected-tests&arg=1 - .withApplicationArgumentsFromQuery() - .create(); + .withApplicationArgumentsFromQuery(); + +const slowTestThreshold = new URLSearchParams(globalThis.location.search) + .get(slowTestThresholdEnvironmentVariable); +if (slowTestThreshold !== null) { + runtimeBuilder.withEnvironmentVariable(slowTestThresholdEnvironmentVariable, slowTestThreshold); +} + +const { runMain } = await runtimeBuilder.create(); // runMain() invokes Program.Main and resolves to its exit code (0 = all tests passed, // non-zero = failures / policy violation). diff --git a/samples/BrowserPlayground/wwwroot/runtests.mjs b/samples/BrowserPlayground/wwwroot/runtests.mjs index cf1ff2a7ac..da55fe7ab5 100644 --- a/samples/BrowserPlayground/wwwroot/runtests.mjs +++ b/samples/BrowserPlayground/wwwroot/runtests.mjs @@ -15,9 +15,14 @@ // e.g. node runtests.mjs --minimum-expected-tests 1 import { dotnet } from './_framework/dotnet.js'; -const { runMain } = await dotnet - .withApplicationArguments(...process.argv.slice(2)) - .create(); +const slowTestThresholdEnvironmentVariable = 'MTP_PROGRESS_SLOW_TEST_SECONDS'; +const runtimeBuilder = dotnet.withApplicationArguments(...process.argv.slice(2)); +const slowTestThreshold = process.env[slowTestThresholdEnvironmentVariable]; +if (slowTestThreshold !== undefined) { + runtimeBuilder.withEnvironmentVariable(slowTestThresholdEnvironmentVariable, slowTestThreshold); +} + +const { runMain } = await runtimeBuilder.create(); // runMain() boots the same bundle under Node and resolves to the exit code of the sample's // Program.Main (defined by the top-level statements in Program.cs). diff --git a/src/Adapter/MSTest.TestAdapter/InternalAPI/InternalAPI.Unshipped.txt b/src/Adapter/MSTest.TestAdapter/InternalAPI/InternalAPI.Unshipped.txt index f35780f05c..7062e0bd2d 100644 --- a/src/Adapter/MSTest.TestAdapter/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/Adapter/MSTest.TestAdapter/InternalAPI/InternalAPI.Unshipped.txt @@ -51,6 +51,7 @@ static Microsoft.Testing.Extensions.RunSettingsProviderHelper.CanReadFile(Micros static Microsoft.Testing.Extensions.RunSettingsProviderHelper.FindInvalidTestParameter(string![]! arguments) -> string? static Microsoft.Testing.Extensions.RunSettingsProviderHelper.HasEnvironmentVariables(System.Xml.Linq.XDocument! runSettings) -> bool static Microsoft.Testing.Extensions.RunSettingsProviderHelper.TryLoadRunSettingsAsync(Microsoft.Testing.Platform.CommandLine.ICommandLineOptions! commandLineOptions, Microsoft.Testing.Platform.Helpers.IFileSystem! fileSystem, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, string! runSettingsOptionName) -> System.Threading.Tasks.Task! +static Microsoft.VisualStudio.TestTools.UnitTesting.MSTestTestNodeConverter.ToEmptyResultTestNode(Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel.UnitTestElement! element, bool isTrxEnabled) -> Microsoft.Testing.Platform.Extensions.Messages.TestNode! Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.MSTestEngine Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.MSTestEngine.Cancel() -> void Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.MSTestEngine.DiscoverAsync(System.Collections.Generic.IEnumerable! sources, string? settingsXml, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.IAdapterMessageLogger! logger, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.IUnitTestElementSink! elementSink, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ITestElementFilterProvider? filterProvider, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.IConfiguration? configuration, Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface.ITestSourceHandler! testSourceHandler, bool isMTP) -> System.Threading.Tasks.Task! diff --git a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MSTestTestNodeConverter.cs b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MSTestTestNodeConverter.cs index 5f02a7b0a9..484e8dafb3 100644 --- a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MSTestTestNodeConverter.cs +++ b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MSTestTestNodeConverter.cs @@ -52,6 +52,16 @@ public static TestNode ToInProgressTestNode(UnitTestElement element, bool isTrxE return testNode; } + /// + /// Builds a state-neutral node update that signals execution completed without producing a result. + /// + public static TestNode ToEmptyResultTestNode(UnitTestElement element, bool isTrxEnabled) + { + TestNode testNode = CreateBaseTestNode(element, isTrxEnabled, displayNameOverride: null, out _); + testNode.Properties.Add(TestNodeExecutionCompletedProperty.CachedInstance); + return testNode; + } + /// /// Builds a completed carrying the outcome, timing, output and (optionally) TRX /// properties for a single executed test result. diff --git a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MtpTestResultRecorder.cs b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MtpTestResultRecorder.cs index 7b22aebe07..42c310104e 100644 --- a/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MtpTestResultRecorder.cs +++ b/src/Adapter/MSTest.TestAdapter/TestingPlatformAdapter/MtpTestResultRecorder.cs @@ -44,10 +44,8 @@ public Task RecordStartAsync(UnitTestElement testElement) return PublishAsync(testNode); } - // Mirrors the VSTest recorder: an empty result maps to RecordEnd(TestOutcome.None), which does not publish any - // test node update to Microsoft.Testing.Platform. So there is nothing to report here. public Task RecordEmptyResultAsync(UnitTestElement testElement) - => Task.CompletedTask; + => PublishAsync(MSTestTestNodeConverter.ToEmptyResultTestNode(testElement, _isTrxEnabled)); public async Task RecordResultAsync(UnitTestElement testElement, FrameworkTestResult unitTestResult, DateTimeOffset startTime, DateTimeOffset endTime) { diff --git a/src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs b/src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs index ed892803fd..b656274e88 100644 --- a/src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs +++ b/src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpSequenceLogger.cs @@ -164,24 +164,27 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella } TestNodeStateProperty? state = update.TestNode.Properties.SingleOrDefault(); - if (state is null) + bool executionCompleted = update.TestNode.Properties.Any(); + if (state is null && !executionCompleted) { return; } - string? line = state switch - { - InProgressTestNodeStateProperty => FormatLine(StartedEvent, _clock.UtcNow, update.TestNode.Uid, update.TestNode.DisplayName), + string? line = executionCompleted + ? FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Completed") + : state switch + { + InProgressTestNodeStateProperty => FormatLine(StartedEvent, _clock.UtcNow, update.TestNode.Uid, update.TestNode.DisplayName), #pragma warning disable CS0618, MTP0001 // Type or member is obsolete - keep parity with HangDumpActivityIndicator's terminal-state set. - PassedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Passed"), - FailedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Failed"), - ErrorTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Error"), - SkippedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Skipped"), - CancelledTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Cancelled"), - TimeoutTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Timeout"), + PassedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Passed"), + FailedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Failed"), + ErrorTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Error"), + SkippedTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Skipped"), + CancelledTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Cancelled"), + TimeoutTestNodeStateProperty => FormatLine(EndedEvent, _clock.UtcNow, update.TestNode.Uid, "Timeout"), #pragma warning restore CS0618, MTP0001 - _ => null, - }; + _ => null, + }; if (line is null) { diff --git a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs index 0299ec571c..8ed681b06b 100644 --- a/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs +++ b/src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpActivityIndicator.cs @@ -130,6 +130,7 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella } TestNodeStateProperty? state = nodeChangedMessage.TestNode.Properties.SingleOrDefault(); + bool executionCompleted = nodeChangedMessage.TestNode.Properties.Any(); if (state is InProgressTestNodeStateProperty) { if (_traceLevelEnabled) @@ -140,9 +141,10 @@ public async Task ConsumeAsync(IDataProducer dataProducer, IData value, Cancella _testsCurrentExecutionState.TryAdd(nodeChangedMessage.TestNode.Uid, (nodeChangedMessage.TestNode.DisplayName, typeof(InProgressTestNodeStateProperty), _clock.UtcNow)); } #pragma warning disable CS0618, MTP0001 // Type or member is obsolete - else if (state is PassedTestNodeStateProperty or ErrorTestNodeStateProperty or CancelledTestNodeStateProperty + else if ((executionCompleted + || state is PassedTestNodeStateProperty or ErrorTestNodeStateProperty or CancelledTestNodeStateProperty #pragma warning restore CS0618, MTP0001 // Type or member is obsolete - or FailedTestNodeStateProperty or TimeoutTestNodeStateProperty or SkippedTestNodeStateProperty + or FailedTestNodeStateProperty or TimeoutTestNodeStateProperty or SkippedTestNodeStateProperty) && _testsCurrentExecutionState.TryRemove(nodeChangedMessage.TestNode.Uid, out (string Name, Type Type, DateTimeOffset StartTime) record) && _traceLevelEnabled) { diff --git a/src/Platform/Microsoft.Testing.Extensions.VideoRecorder/VideoRecorderSessionHandler.DataConsumer.cs b/src/Platform/Microsoft.Testing.Extensions.VideoRecorder/VideoRecorderSessionHandler.DataConsumer.cs index 583f7c591c..a79a4c68f5 100644 --- a/src/Platform/Microsoft.Testing.Extensions.VideoRecorder/VideoRecorderSessionHandler.DataConsumer.cs +++ b/src/Platform/Microsoft.Testing.Extensions.VideoRecorder/VideoRecorderSessionHandler.DataConsumer.cs @@ -17,6 +17,18 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo return Task.CompletedTask; } + string testUid = update.TestNode.Uid.Value; + if (update.TestNode.Properties.Any()) + { + lock (_stateGate) + { + _inFlight.Remove(testUid); + } + + TryPruneOldSegments(); + return Task.CompletedTask; + } + // A node carries a single state property in practice; use FirstOrDefault rather than // SingleOrDefault so a malformed producer can't throw out of the consumer pump. TestNodeStateProperty? state = update.TestNode.Properties.FirstOrDefault(); @@ -25,8 +37,6 @@ public Task ConsumeAsync(IDataProducer dataProducer, IData value, CancellationTo return Task.CompletedTask; } - string testUid = update.TestNode.Uid.Value; - if (state is InProgressTestNodeStateProperty) { lock (_stateGate) diff --git a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt index 881860475c..d13b39476f 100644 --- a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt @@ -65,6 +65,8 @@ Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalProgressMessageState.Te Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalProgressMessageState.Version.get -> long Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalTestReporter.UpdateProgressMessage(string! executionId, string! instanceId, string! producerUid, string! key, string? message) -> void Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalTestReporter.ClearProgressMessages() -> void +Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalTestReporter.TestCompletedWithoutResult(string! executionId, string! testNodeUid) -> void +Microsoft.Testing.Platform.Telemetry.OpenTelemetryResultHandler.NotifyExecutionCompleted(Microsoft.Testing.Platform.Extensions.Messages.TestNode! testNode) -> void virtual Microsoft.Testing.Platform.OutputDevice.SimplifiedConsoleOutputDeviceBase.DisplayActiveTestProgress.get -> bool static Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalTestReporterCommandLineOptionsProvider.IsProgressEnabled(Microsoft.Testing.Platform.CommandLine.ICommandLineOptions! commandLineOptions) -> bool const Microsoft.Testing.Platform.OutputDevice.Terminal.TestProgressStateAwareTerminal.MaximumVisibleProgressMessages = 5 -> int @@ -271,3 +273,28 @@ Microsoft.Testing.Platform.OutputDevice.Terminal.TerminalTestReporter.TerminalTe Microsoft.Testing.Platform.OutputDevice.Terminal.TestProgressStateAwareTerminal.TestProgressStateAwareTerminal(Microsoft.Testing.Platform.OutputDevice.Terminal.ITerminal! terminal, System.Func! showProgress, Microsoft.Testing.Platform.OutputDevice.Terminal.IProgressRenderer! renderer, Microsoft.Testing.Platform.Logging.ILogger! logger) -> void Microsoft.Testing.Platform.ServerMode.ServerModeManager.MessageHandlerFactory.MessageHandlerFactory(string! host, int port, Microsoft.Testing.Platform.OutputDevice.IOutputDevice! outputDevice, Microsoft.Testing.Platform.Logging.ILogger! logger) -> void Microsoft.Testing.Platform.ServerMode.TcpMessageHandler.TcpMessageHandler(System.Net.Sockets.TcpClient! client, System.IO.Stream! clientToServerStream, System.IO.Stream! serverToClientStream, Microsoft.Testing.Platform.ServerMode.IMessageFormatter! formatter, Microsoft.Testing.Platform.Logging.ILogger! logger) -> void +const Microsoft.Testing.Platform.OutputDevice.ProgressReportingConfiguration.MTP_PROGRESS_SILENCE_SECONDS = "MTP_PROGRESS_SILENCE_SECONDS" -> string! +const Microsoft.Testing.Platform.OutputDevice.ProgressReportingConfiguration.MTP_PROGRESS_SLOW_TEST_SECONDS = "MTP_PROGRESS_SLOW_TEST_SECONDS" -> string! +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.ActiveTestTracker(System.TimeSpan slowTestThreshold, System.Func! createStopwatch) -> void +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.Clear() -> void +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.Complete(Microsoft.Testing.Platform.Extensions.Messages.TestNodeUid! uid) -> void +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.GetDueDiagnostics() -> Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic![]! +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.IsActive(Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic! diagnostic) -> bool +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.IsEnabled.get -> bool +Microsoft.Testing.Platform.OutputDevice.ActiveTestTracker.Start(Microsoft.Testing.Platform.Extensions.Messages.TestNodeUid! uid, string! displayName) -> void +Microsoft.Testing.Platform.OutputDevice.ProgressReportingConfiguration +Microsoft.Testing.Platform.OutputDevice.SimplifiedConsoleOutputDeviceBase.ReportSlowTestsOnceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.Testing.Platform.OutputDevice.SimplifiedConsoleOutputDeviceBase.SimplifiedConsoleOutputDeviceBase(Microsoft.Testing.Platform.Helpers.IConsole! console, Microsoft.Testing.Platform.Services.ITestApplicationModuleInfo! testApplicationModuleInfo, Microsoft.Testing.Platform.Helpers.IAsyncMonitor! asyncMonitor, Microsoft.Testing.Platform.Helpers.IRuntimeFeature! runtimeFeature, Microsoft.Testing.Platform.Helpers.IEnvironment! environment, Microsoft.Testing.Platform.Services.IPlatformInformation! platformInformation, Microsoft.Testing.Platform.Services.IStopPoliciesService! policiesService, System.TimeSpan slowTestThreshold, System.Func! createStopwatch, System.TimeSpan slowTestPollInterval) -> void +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic.DisplayName.get -> string! +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic.Elapsed.get -> System.TimeSpan +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic.Generation.get -> long +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic.SlowTestDiagnostic(Microsoft.Testing.Platform.Extensions.Messages.TestNodeUid! uid, string! displayName, System.TimeSpan elapsed, long generation) -> void +Microsoft.Testing.Platform.OutputDevice.SlowTestDiagnostic.Uid.get -> Microsoft.Testing.Platform.Extensions.Messages.TestNodeUid! +Microsoft.Testing.Platform.OutputDevice.SlowTestThresholdState +Microsoft.Testing.Platform.OutputDevice.SlowTestThresholdState.IsDue(System.TimeSpan elapsed) -> bool +Microsoft.Testing.Platform.OutputDevice.SlowTestThresholdState.SlowTestThresholdState(System.TimeSpan initialThreshold) -> void +static Microsoft.Testing.Platform.OutputDevice.ProgressReportingConfiguration.GetThreshold(Microsoft.Testing.Platform.Helpers.IEnvironment! environment, string! variableName, int defaultSeconds) -> System.TimeSpan +Microsoft.Testing.Platform.Extensions.Messages.TestNodeExecutionCompletedProperty +static Microsoft.Testing.Platform.Extensions.Messages.TestNodeExecutionCompletedProperty.CachedInstance.get -> Microsoft.Testing.Platform.Extensions.Messages.TestNodeExecutionCompletedProperty! diff --git a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeUpdateMessage.cs b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeUpdateMessage.cs index 5cfe5b8b05..843868189c 100644 --- a/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeUpdateMessage.cs +++ b/src/Platform/Microsoft.Testing.Platform/Messages/TestNodeUpdateMessage.cs @@ -62,3 +62,12 @@ public override string ToString() return builder.ToString(); } } + +internal sealed class TestNodeExecutionCompletedProperty : IProperty +{ + private TestNodeExecutionCompletedProperty() + { + } + + internal static TestNodeExecutionCompletedProperty CachedInstance { get; } = new(); +} diff --git a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj index f250559b9a..13af3f73d6 100644 --- a/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj +++ b/src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj @@ -62,6 +62,7 @@ This package provides the core platform and the .NET implementation of the proto +