| File: Client\MTP\MtpProxyDiscoveryManager.cs | Web Access |
| Project: src\vstest\src\Microsoft.TestPlatform.CrossPlatEngine\Microsoft.TestPlatform.CrossPlatEngine.csproj (Microsoft.TestPlatform.CrossPlatEngine) |
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using Jsonite; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; /// <summary> /// An <see cref="IProxyDiscoveryManager"/> that discovers tests by driving a /// Microsoft.Testing.Platform (MTP) application over the MTP JSON-RPC protocol instead of the /// vstest testhost protocol. /// </summary> internal sealed class MtpProxyDiscoveryManager : IProxyDiscoveryManager, IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); public void Initialize(bool skipDefaultAdapters) { } public void InitializeDiscovery(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler, bool skipDefaultAdapters) => Initialize(skipDefaultAdapters); public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler) { var sources = discoveryCriteria.Sources?.ToList() ?? new List<string>(); long totalTests = 0; bool aborted = false; foreach (string source in sources) { if (_cancellationTokenSource.IsCancellationRequested) { aborted = true; break; } try { totalTests += DiscoverSource(source, eventHandler); } catch (OperationCanceledException) { aborted = true; break; } catch (Exception ex) { EqtTrace.Error("MtpProxyDiscoveryManager.DiscoverTests: discovery failed for '{0}': {1}", source, ex); eventHandler.HandleLogMessage(ObjectModel.Logging.TestMessageLevel.Error, $"Microsoft.Testing.Platform discovery failed for '{source}': {ex.Message}"); aborted = true; } } eventHandler.HandleDiscoveryComplete(new DiscoveryCompleteEventArgs(totalTests, aborted), null); } public void Abort() => _cancellationTokenSource.Cancel(); public void Abort(ITestDiscoveryEventsHandler2 eventHandler) => Abort(); public void Close() => _cancellationTokenSource.Cancel(); public void Dispose() { try { _cancellationTokenSource.Dispose(); } catch { // ignore } } private int DiscoverSource(string source, ITestDiscoveryEventsHandler2 eventHandler) { var discovered = new List<TestCase>(); var completed = new ManualResetEventSlim(false); using var connection = new MtpServerConnection(); connection.LogReceived += (level, message) => eventHandler.HandleLogMessage(MtpClientHelpers.MapLevel(level), message); connection.TestNodesUpdated += parameters => { if (MtpClientHelpers.IsCompletionSentinel(parameters)) { completed.Set(); return; } foreach (JsonObject node in MtpClientHelpers.EnumerateNodes(parameters)) { if (MtpTestNodeConverter.IsActionNode(node)) { lock (discovered) { discovered.Add(MtpTestNodeConverter.ToTestCase(node, source)); } } } }; connection.Start(source, environmentVariables: null, MtpClientHelpers.GetConnectionTimeout()); connection.InvokeAsync(MtpConstants.InitializeMethod, MtpClientHelpers.InitializeParameters(), _cancellationTokenSource.Token).GetAwaiter().GetResult(); var runId = Guid.NewGuid(); var discoverTask = connection.InvokeAsync( MtpConstants.DiscoverTestsMethod, new Dictionary<string, object?> { [MtpConstants.RunIdParameter] = runId.ToString() }, _cancellationTokenSource.Token); // The response indicates the server has finished discovery. Because messages arrive on a // single ordered stream that we read sequentially, every node notification sent before the // response has already been dispatched by the time the response completes. discoverTask.GetAwaiter().GetResult(); completed.Wait(TimeSpan.FromSeconds(3)); List<TestCase> chunk; lock (discovered) { chunk = discovered.ToList(); } if (chunk.Count > 0) { eventHandler.HandleDiscoveredTests(chunk); } connection.SendNotification(MtpConstants.ExitMethod, null); return chunk.Count; } }