/
ProxyExecutionManager.cs
291 lines (248 loc) · 14.3 KB
/
ProxyExecutionManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// 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.VisualStudio.TestPlatform.CrossPlatEngine.Client
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.Common;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using Microsoft.VisualStudio.TestPlatform.Utilities;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers;
using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces;
/// <summary>
/// Orchestrates test execution operations for the engine communicating with the client.
/// </summary>
internal class ProxyExecutionManager : ProxyOperationManager, IProxyExecutionManager, ITestRunEventsHandler
{
private readonly ITestRuntimeProvider testHostManager;
private IDataSerializer dataSerializer;
private bool isCommunicationEstablished;
private IRequestData requestData;
private ITestRunEventsHandler baseTestRunEventsHandler;
private bool skipDefaultAdapters;
private readonly IFileHelper fileHelper;
// Only send coverlet inproc datacollector dll to be initialized via testhost,
// ideally this should get initialized via InProcessDC Node in runsettings, but
// somehow it is failing, hence putting this ugly HACK, to fix issues like
// https://developercommunity.visualstudio.com/content/problem/738856/could-not-load-file-or-assembly-microsoftintellitr.html
private const string CoverletDataCollector = "coverlet.collector.dll";
/// <inheritdoc/>
public bool IsInitialized { get; private set; } = false;
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="ProxyExecutionManager"/> class.
/// </summary>
/// <param name="requestData">The Request Data for providing services and data for Run.</param>
/// <param name="requestSender">Test request sender instance.</param>
/// <param name="testHostManager">Test host manager for this proxy.</param>
public ProxyExecutionManager(IRequestData requestData, ITestRequestSender requestSender, ITestRuntimeProvider testHostManager) :
this(requestData, requestSender, testHostManager, JsonDataSerializer.Instance, new FileHelper())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ProxyExecutionManager"/> class.
/// Constructor with Dependency injection. Used for unit testing.
/// </summary>
/// <param name="requestData">The Request Data for Common services and data for Run.</param>
/// <param name="requestSender">Request Sender instance</param>
/// <param name="testHostManager">Test host manager instance</param>
/// <param name="dataSerializer"></param>
internal ProxyExecutionManager(IRequestData requestData, ITestRequestSender requestSender,
ITestRuntimeProvider testHostManager, IDataSerializer dataSerializer, IFileHelper fileHelper)
: base(requestData, requestSender, testHostManager)
{
this.testHostManager = testHostManager;
this.dataSerializer = dataSerializer;
this.isCommunicationEstablished = false;
this.requestData = requestData;
this.fileHelper = fileHelper;
}
#endregion
#region IProxyExecutionManager implementation.
/// <summary>
/// Ensure that the Execution component of engine is ready for execution usually by loading extensions.
/// <param name="skipDefaultAdapters">Skip default adapters flag.</param>
/// </summary>
public virtual void Initialize(bool skipDefaultAdapters)
{
this.skipDefaultAdapters = skipDefaultAdapters;
this.IsInitialized = true;
}
/// <summary>
/// Starts the test run
/// </summary>
/// <param name="testRunCriteria"> The settings/options for the test run. </param>
/// <param name="eventHandler"> EventHandler for handling execution events from Engine. </param>
/// <returns> The process id of the runner executing tests. </returns>
public virtual int StartTestRun(TestRunCriteria testRunCriteria, ITestRunEventsHandler eventHandler)
{
this.baseTestRunEventsHandler = eventHandler;
try
{
if (EqtTrace.IsVerboseEnabled)
{
EqtTrace.Verbose("ProxyExecutionManager: Test host is always Lazy initialize.");
}
var testSources = new List<string>(testRunCriteria.HasSpecificSources ? testRunCriteria.Sources :
// If the test execution is with a test filter, group them by sources
testRunCriteria.Tests.GroupBy(tc => tc.Source).Select(g => g.Key));
this.isCommunicationEstablished = this.SetupChannel(testSources, testRunCriteria.TestRunSettings);
if (this.isCommunicationEstablished)
{
this.CancellationTokenSource.Token.ThrowTestPlatformExceptionIfCancellationRequested();
this.InitializeExtensions(testSources);
// This code should be in sync with InProcessProxyExecutionManager.StartTestRun executionContext
var executionContext = new TestExecutionContext(
testRunCriteria.FrequencyOfRunStatsChangeEvent,
testRunCriteria.RunStatsChangeEventTimeout,
inIsolation: false,
keepAlive: testRunCriteria.KeepAlive,
isDataCollectionEnabled: false,
areTestCaseLevelEventsRequired: false,
hasTestRun: true,
isDebug: (testRunCriteria.TestHostLauncher != null && testRunCriteria.TestHostLauncher.IsDebug),
testCaseFilter: testRunCriteria.TestCaseFilter,
filterOptions: testRunCriteria.FilterOptions);
// This is workaround for the bug https://github.com/Microsoft/vstest/issues/970
var runsettings = this.RemoveNodesFromRunsettingsIfRequired(testRunCriteria.TestRunSettings, (testMessageLevel, message) => { this.LogMessage(testMessageLevel, message); });
if (testRunCriteria.HasSpecificSources)
{
var runRequest = testRunCriteria.CreateTestRunCriteriaForSources(testHostManager, runsettings, executionContext, testSources);
this.RequestSender.StartTestRun(runRequest, this);
}
else
{
var runRequest = testRunCriteria.CreateTestRunCriteriaForTests(testHostManager, runsettings, executionContext, testSources);
this.RequestSender.StartTestRun(runRequest, this);
}
}
}
catch (Exception exception)
{
EqtTrace.Error("ProxyExecutionManager.StartTestRun: Failed to start test run: {0}", exception);
// Log error message to design mode and CLI.
// TestPlatformException is expected exception, log only the message
// For other exceptions, log the stacktrace as well
var errorMessage = exception is TestPlatformException ? exception.Message : exception.ToString();
var testMessagePayload = new TestMessagePayload { MessageLevel = TestMessageLevel.Error, Message = errorMessage };
this.HandleRawMessage(this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload));
this.LogMessage(TestMessageLevel.Error, errorMessage);
// Send a run complete to caller. Similar logic is also used in ParallelProxyExecutionManager.StartTestRunOnConcurrentManager
// Aborted is `true`: in case of parallel run (or non shared host), an aborted message ensures another execution manager
// created to replace the current one. This will help if the current execution manager is aborted due to irreparable error
// and the test host is lost as well.
var completeArgs = new TestRunCompleteEventArgs(null, false, true, null, new Collection<AttachmentSet>(), TimeSpan.Zero);
var testRunCompletePayload = new TestRunCompletePayload { TestRunCompleteArgs = completeArgs };
this.HandleRawMessage(this.dataSerializer.SerializePayload(MessageType.ExecutionComplete, testRunCompletePayload));
this.HandleTestRunComplete(completeArgs, null, null, null);
}
return 0;
}
/// <summary>
/// Cancels the test run.
/// </summary>
/// <param name="eventHandler"> EventHandler for handling execution events from Engine. </param>
public virtual void Cancel(ITestRunEventsHandler eventHandler)
{
// Just in case ExecuteAsync isn't called yet, set the eventhandler
if(this.baseTestRunEventsHandler == null)
{
this.baseTestRunEventsHandler = eventHandler;
}
// Cancel fast, try to stop testhost deployment/launch
this.CancellationTokenSource.Cancel();
if (this.isCommunicationEstablished)
{
this.RequestSender.SendTestRunCancel();
}
}
/// <inheritdoc/>
public virtual int LaunchProcessWithDebuggerAttached(TestProcessStartInfo testProcessStartInfo)
{
return this.baseTestRunEventsHandler.LaunchProcessWithDebuggerAttached(testProcessStartInfo);
}
/// <summary>
/// Aborts the test run.
/// </summary>
/// <param name="eventHandler"> EventHandler for handling execution events from Engine. </param>
public void Abort(ITestRunEventsHandler eventHandler)
{
// Just in case ExecuteAsync isn't called yet, set the eventhandler
if(this.baseTestRunEventsHandler == null)
{
this.baseTestRunEventsHandler = eventHandler;
}
// Cancel fast, try to stop testhost deployment/launch
this.CancellationTokenSource.Cancel();
if (this.isCommunicationEstablished)
{
this.RequestSender.SendTestRunAbort();
}
}
/// <inheritdoc/>
public void HandleTestRunComplete(TestRunCompleteEventArgs testRunCompleteArgs, TestRunChangedEventArgs lastChunkArgs, ICollection<AttachmentSet> runContextAttachments, ICollection<string> executorUris)
{
this.baseTestRunEventsHandler.HandleTestRunComplete(testRunCompleteArgs, lastChunkArgs, runContextAttachments, executorUris);
}
/// <inheritdoc/>
public void HandleTestRunStatsChange(TestRunChangedEventArgs testRunChangedArgs)
{
this.baseTestRunEventsHandler.HandleTestRunStatsChange(testRunChangedArgs);
}
/// <inheritdoc/>
public void HandleRawMessage(string rawMessage)
{
var message = this.dataSerializer.DeserializeMessage(rawMessage);
if(string.Equals(message.MessageType, MessageType.ExecutionComplete))
{
this.Close();
}
this.baseTestRunEventsHandler.HandleRawMessage(rawMessage);
}
public void HandleLogMessage(TestMessageLevel level, string message)
{
this.baseTestRunEventsHandler.HandleLogMessage(level, message);
}
#endregion
private void LogMessage(TestMessageLevel testMessageLevel, string message)
{
// Log to vs ide test output
var testMessagePayload = new TestMessagePayload { MessageLevel = testMessageLevel, Message = message };
var rawMessage = this.dataSerializer.SerializePayload(MessageType.TestMessage, testMessagePayload);
this.HandleRawMessage(rawMessage);
// Log to vstest.console
this.HandleLogMessage(testMessageLevel, message);
}
private void InitializeExtensions(IEnumerable<string> sources)
{
var extensions = TestPluginCache.Instance.GetExtensionPaths(TestPlatformConstants.TestAdapterEndsWithPattern, this.skipDefaultAdapters);
// remove this line once we figure out why coverlet inproc DC is not initialized via runsetting inproc node.
extensions = extensions.Concat(TestPluginCache.Instance.GetExtensionPaths(ProxyExecutionManager.CoverletDataCollector, true)).ToList();
// Filter out non existing extensions
var nonExistingExtensions = extensions.Where(extension => !this.fileHelper.Exists(extension));
if (nonExistingExtensions.Any())
{
this.LogMessage(TestMessageLevel.Warning, string.Format(Resources.Resources.NonExistingExtensions, string.Join(",", nonExistingExtensions)));
}
var sourceList = sources.ToList();
var platformExtensions = this.testHostManager.GetTestPlatformExtensions(sourceList, extensions.Except(nonExistingExtensions));
// Only send this if needed.
if (platformExtensions.Any())
{
this.RequestSender.InitializeExecution(platformExtensions);
}
}
}
}