|
// 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.Globalization;
using System.Linq;
using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities;
using Microsoft.VisualStudio.TestPlatform.Common.Filtering;
using Microsoft.VisualStudio.TestPlatform.Common.Interfaces;
using Microsoft.VisualStudio.TestPlatform.Common.Utilities;
using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Adapter;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Discovery;
using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Utilities;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ClientProtocol;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using CrossPlatEngineResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources;
namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Execution;
internal class RunTestsWithSources : BaseRunTests
{
private readonly Dictionary<string, IEnumerable<string>> _adapterSourceMap;
private Dictionary<Tuple<Uri, string>, IEnumerable<string>>? _executorUriVsSourceList;
private readonly ITestCaseEventsHandler? _testCaseEventsHandler;
public RunTestsWithSources(IRequestData requestData, Dictionary<string, IEnumerable<string>> adapterSourceMap, string? package, string? runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler? testCaseEventsHandler, IInternalTestRunEventsHandler testRunEventsHandler)
: this(requestData, adapterSourceMap, package, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, null)
{
}
/// <summary>
/// Used for unit testing only.
/// </summary>
/// <param name="requestData"></param>
/// <param name="adapterSourceMap"></param>
/// <param name="package">The user input test source(package) if it differ from actual test source otherwise null.</param>
/// <param name="runSettings"></param>
/// <param name="testExecutionContext"></param>
/// <param name="testCaseEventsHandler"></param>
/// <param name="testRunEventsHandler"></param>
/// <param name="executorUriVsSourceList"></param>
internal RunTestsWithSources(IRequestData requestData, Dictionary<string, IEnumerable<string>> adapterSourceMap, string? package, string? runSettings, TestExecutionContext testExecutionContext, ITestCaseEventsHandler? testCaseEventsHandler, IInternalTestRunEventsHandler testRunEventsHandler, Dictionary<Tuple<Uri, string>, IEnumerable<string>>? executorUriVsSourceList)
: base(requestData, package, runSettings, testExecutionContext, testCaseEventsHandler, testRunEventsHandler, TestPlatformEventSource.Instance)
{
_adapterSourceMap = adapterSourceMap;
_executorUriVsSourceList = executorUriVsSourceList;
_testCaseEventsHandler = testCaseEventsHandler;
}
protected override void BeforeRaisingTestRunComplete(bool exceptionsHitDuringRunTests)
{
// If run was with sources and no test was executed and cancellation was not requested,
// then raise a warning saying that no test was present in the sources.
// The warning is raised only if total no of tests that have been run is zero.
if (!exceptionsHitDuringRunTests && _executorUriVsSourceList?.Count > 0 && !IsCancellationRequested
&& TestRunCache?.TotalExecutedTests <= 0)
{
LogWarningOnNoTestsExecuted();
}
}
private void LogWarningOnNoTestsExecuted()
{
IEnumerable<string> sources = new List<string>();
var sourcesArray = _adapterSourceMap.Values
.Aggregate(sources, (current, enumerable) => enumerable is not null ? current.Concat(enumerable) : current).ToArray();
var sourcesString = string.Join(" ", sourcesArray);
if (TestExecutionContext.TestCaseFilter != null)
{
var testCaseFilterToShow = TestCaseFilterDeterminer.ShortenTestCaseFilterIfRequired(TestExecutionContext.TestCaseFilter);
TestRunEventsHandler?.HandleLogMessage(
TestMessageLevel.Warning,
string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.NoTestsAvailableForGivenTestCaseFilter, testCaseFilterToShow, sourcesString));
}
else
{
TestRunEventsHandler?.HandleLogMessage(
TestMessageLevel.Warning,
string.Format(
CultureInfo.CurrentCulture,
CrossPlatEngineResources.TestRunFailed_NoDiscovererFound_NoTestsAreAvailableInTheSources,
sourcesString));
}
}
protected override IEnumerable<Tuple<Uri, string>> GetExecutorUriExtensionMap(IFrameworkHandle testExecutorFrameworkHandle, RunContext runContext)
{
_executorUriVsSourceList = GetExecutorVsSourcesList(testExecutorFrameworkHandle);
var executorUris = _executorUriVsSourceList.Keys;
runContext.FilterExpressionWrapper = !TestExecutionContext.TestCaseFilter.IsNullOrEmpty()
? new FilterExpressionWrapper(TestExecutionContext.TestCaseFilter, TestExecutionContext.FilterOptions)
: null;
return executorUris;
}
protected override void InvokeExecutor(
LazyExtension<ITestExecutor, ITestExecutorCapabilities> executor,
Tuple<Uri, string> executorUriExtensionTuple,
RunContext? runContext,
IFrameworkHandle? frameworkHandle)
{
executor?.Value.RunTests(_executorUriVsSourceList?[executorUriExtensionTuple], runContext, frameworkHandle);
}
/// <inheritdoc />
protected override bool ShouldAttachDebuggerToTestHost(
LazyExtension<ITestExecutor, ITestExecutorCapabilities> executor,
Tuple<Uri, string> executorUriExtensionTuple,
RunContext runContext)
{
// If the adapter doesn't implement the new test executor interface we should attach to
// the default test host by default to preserve old behavior.
return executor?.Value is not ITestExecutor2 convertedExecutor
|| convertedExecutor.ShouldAttachToTestHost(
_executorUriVsSourceList?[executorUriExtensionTuple],
runContext);
}
/// <summary>
/// Returns executor Vs sources list
/// </summary>
private Dictionary<Tuple<Uri, string>, IEnumerable<string>> GetExecutorVsSourcesList(IMessageLogger logger)
{
var result = new Dictionary<Tuple<Uri, string>, IEnumerable<string>>();
var verifiedExtensionSourceMap = new Dictionary<string, IEnumerable<string>>();
// Validate the sources
foreach (var kvp in _adapterSourceMap)
{
var verifiedSources = DiscoveryManager.GetValidSources(kvp.Value, logger, _package);
if (verifiedSources.Any())
{
verifiedExtensionSourceMap.Add(kvp.Key, kvp.Value);
}
}
// No valid source is found => no executor vs source map
if (!verifiedExtensionSourceMap.Any())
{
return result;
}
foreach (var kvp in verifiedExtensionSourceMap)
{
var discovererToSourcesMap = DiscovererEnumerator.GetDiscovererToSourcesMap(
kvp.Key,
kvp.Value,
logger,
new AssemblyProperties());
// Warning is logged by the inner layer
if (discovererToSourcesMap == null || discovererToSourcesMap.Count == 0)
{
continue;
}
foreach (var discoverer in discovererToSourcesMap.Keys)
{
var executorUri = discoverer.Metadata.DefaultExecutorUri;
if (executorUri == null)
{
string errorMessage = string.Format(
CultureInfo.CurrentCulture,
CrossPlatEngineResources.IgnoringExecutorAsNoDefaultExecutorUriAttribute,
discoverer.Value);
logger.SendMessage(TestMessageLevel.Warning, errorMessage);
continue;
}
var executorUriExtensionTuple = new Tuple<Uri, string>(executorUri, kvp.Key);
if (!result.ContainsKey(executorUriExtensionTuple))
{
result.Add(executorUriExtensionTuple, discovererToSourcesMap[discoverer]);
}
else
{
string errorMessage = string.Format(
CultureInfo.CurrentCulture,
CrossPlatEngineResources.DuplicateAdaptersFound,
executorUri,
discoverer.Value);
logger.SendMessage(TestMessageLevel.Warning, errorMessage);
}
}
}
return result;
}
/// <summary>
/// Sends Session-End event on in-proc datacollectors
/// </summary>
protected override void SendSessionEnd()
{
_testCaseEventsHandler?.SendSessionEnd();
}
/// <summary>
/// Sends Session-Start event on in-proc datacollectors
/// </summary>
protected override void SendSessionStart()
{
// Send session start with test sources in property bag for session start event args.
if (_testCaseEventsHandler == null)
{
return;
}
var properties = new Dictionary<string, object?>
{
{ "TestSources", TestSourcesUtility.GetSources(_adapterSourceMap!)! }
};
_testCaseEventsHandler.SendSessionStart(properties);
}
}
|