in src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs [376:575]
private bool RunTestInternalWithExecutors(IEnumerable<Tuple<Uri, string>> executorUriExtensionMap, long totalTests)
{
// Collecting Total Number of Adapters Discovered in Machine.
_requestData.MetricsCollection.Add(TelemetryDataConstants.NumberOfAdapterDiscoveredDuringExecution, executorUriExtensionMap.Count());
var attachedToTestHost = false;
var executorCache = new Dictionary<string, LazyExtension<ITestExecutor, ITestExecutorCapabilities>>();
foreach (var executorUriExtensionTuple in executorUriExtensionMap)
{
// Avoid processing the same executor twice.
if (executorCache.ContainsKey(executorUriExtensionTuple.Item1.AbsoluteUri))
{
continue;
}
// Get the extension manager.
var extensionManager = GetExecutorExtensionManager(executorUriExtensionTuple.Item2);
// Look up the executor.
var executor = extensionManager.TryGetTestExtension(executorUriExtensionTuple.Item1);
if (executor == null)
{
// Commenting this out because of a compatibility issue with Microsoft.Dotnet.ProjectModel released on nuGet.org.
// this.activeExecutor = null;
// var runtimeVersion = string.Concat(PlatformServices.Default.Runtime.RuntimeType, " ",
// PlatformServices.Default.Runtime.RuntimeVersion);
var runtimeVersion = " ";
TestRunEventsHandler?.HandleLogMessage(
TestMessageLevel.Warning,
string.Format(
CultureInfo.CurrentUICulture,
CrossPlatEngineResources.NoMatchingExecutor,
executorUriExtensionTuple.Item1.AbsoluteUri,
runtimeVersion));
continue;
}
// Cache the executor.
executorCache.Add(executorUriExtensionTuple.Item1.AbsoluteUri, executor);
// Check if we actually have to attach to the default test host.
if (!RunContext.IsBeingDebugged || attachedToTestHost)
{
// We already know we should attach to the default test host, simply continue.
continue;
}
// If there's at least one adapter in the filtered adapters list that doesn't
// implement the new test executor interface, we should attach to the default test
// host by default.
// Same goes if all adapters implement the new test executor interface but at
// least one of them needs the test platform to attach to the default test host.
if (!(executor.Value is ITestExecutor2)
|| ShouldAttachDebuggerToTestHost(executor, executorUriExtensionTuple, RunContext))
{
EqtTrace.Verbose("Attaching to default test host.");
attachedToTestHost = true;
var pid = Process.GetCurrentProcess().Id;
if (!FrameworkHandle.AttachDebuggerToProcess(pid))
{
EqtTrace.Warning(
string.Format(
CultureInfo.CurrentUICulture,
CrossPlatEngineResources.AttachDebuggerToDefaultTestHostFailure,
pid));
}
}
}
// Call the executor for each group of tests.
var exceptionsHitDuringRunTests = false;
var executorsFromDeprecatedLocations = false;
double totalTimeTakenByAdapters = 0;
foreach (var executorUriExtensionTuple in executorUriExtensionMap)
{
var executorUri = executorUriExtensionTuple.Item1.AbsoluteUri;
// Get the executor from the cache.
if (!executorCache.TryGetValue(executorUri, out var executor))
{
continue;
}
try
{
if (EqtTrace.IsVerboseEnabled)
{
EqtTrace.Verbose(
"BaseRunTests.RunTestInternalWithExecutors: Running tests for {0}",
executor.Metadata.ExtensionUri);
}
// set the active executor
_activeExecutor = executor.Value;
// If test run cancellation is requested, skip the next executor
if (_isCancellationRequested)
{
break;
}
var timeStartNow = DateTime.UtcNow;
var currentTotalTests = TestRunCache.TotalExecutedTests;
_testPlatformEventSource.AdapterExecutionStart(executorUri);
// Run the tests.
if (NotRequiredStaThread() || !TryToRunInStaThread(() => InvokeExecutor(executor, executorUriExtensionTuple, RunContext, FrameworkHandle), true))
{
InvokeExecutor(executor, executorUriExtensionTuple, RunContext, FrameworkHandle);
}
_testPlatformEventSource.AdapterExecutionStop(TestRunCache.TotalExecutedTests - currentTotalTests);
var totalTimeTaken = DateTime.UtcNow - timeStartNow;
// Identify whether the executor did run any tests at all
if (TestRunCache.TotalExecutedTests > totalTests)
{
ExecutorUrisThatRanTests.Add(executorUri);
// Collecting Total Tests Ran by each Adapter
var totalTestRun = TestRunCache.TotalExecutedTests - totalTests;
_requestData.MetricsCollection.Add(string.Format("{0}.{1}", TelemetryDataConstants.TotalTestsRanByAdapter, executorUri), totalTestRun);
// Only enable this for MSTestV1 telemetry for now, this might become more generic later.
if (MsTestV1TelemetryHelper.IsMsTestV1Adapter(executorUri))
{
foreach (var adapterMetrics in TestRunCache.AdapterTelemetry.Keys.Where(k => k.StartsWith(executorUri)))
{
var value = TestRunCache.AdapterTelemetry[adapterMetrics];
_requestData.MetricsCollection.Add(string.Format("{0}.{1}", TelemetryDataConstants.TotalTestsRunByMSTestv1, adapterMetrics), value);
}
}
if (!CrossPlatEngine.Constants.DefaultAdapters.Contains(executor.Metadata.ExtensionUri, StringComparer.OrdinalIgnoreCase))
{
var executorLocation = executor.Value.GetType().GetTypeInfo().Assembly.GetAssemblyLocation();
executorsFromDeprecatedLocations |= Path.GetDirectoryName(executorLocation).Equals(CrossPlatEngine.Constants.DefaultAdapterLocation);
}
totalTests = TestRunCache.TotalExecutedTests;
}
if (EqtTrace.IsVerboseEnabled)
{
EqtTrace.Verbose(
"BaseRunTests.RunTestInternalWithExecutors: Completed running tests for {0}",
executor.Metadata.ExtensionUri);
}
// Collecting Time Taken by each executor Uri
_requestData.MetricsCollection.Add(string.Format("{0}.{1}", TelemetryDataConstants.TimeTakenToRunTestsByAnAdapter, executorUri), totalTimeTaken.TotalSeconds);
totalTimeTakenByAdapters += totalTimeTaken.TotalSeconds;
}
catch (Exception e)
{
string exceptionMessage = (e is UnauthorizedAccessException)
? string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.AccessDenied, e.Message)
: ExceptionUtilities.GetExceptionMessage(e);
exceptionsHitDuringRunTests = true;
if (EqtTrace.IsErrorEnabled)
{
EqtTrace.Error(
"BaseRunTests.RunTestInternalWithExecutors: An exception occurred while invoking executor {0}. {1}.",
executorUriExtensionTuple.Item1,
e);
}
TestRunEventsHandler?.HandleLogMessage(
TestMessageLevel.Error,
string.Format(
CultureInfo.CurrentCulture,
CrossPlatEngineResources.ExceptionFromRunTests,
executorUriExtensionTuple.Item1,
exceptionMessage));
}
finally
{
_activeExecutor = null;
}
}
// Collecting Total Time Taken by Adapters
_requestData.MetricsCollection.Add(TelemetryDataConstants.TimeTakenByAllAdaptersInSec, totalTimeTakenByAdapters);
if (executorsFromDeprecatedLocations)
{
TestRunEventsHandler?.HandleLogMessage(TestMessageLevel.Warning, string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.DeprecatedAdapterPath));
}
return exceptionsHitDuringRunTests;
}