// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#if !WINDOWS_UWP && !WIN_UI
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;
using Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Deployment;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices;
///
/// The test deployment.
///
internal sealed class TestDeployment : ITestDeployment
{
#region Service Utility Variables
private readonly DeploymentItemUtility _deploymentItemUtility;
private readonly DeploymentUtility _deploymentUtility;
private readonly FileUtility _fileUtility;
private MSTestAdapterSettings? _adapterSettings;
#endregion
///
/// Initializes a new instance of the class.
///
public TestDeployment()
: this(new DeploymentItemUtility(PlatformServiceProvider.Instance.ReflectionOperations), new DeploymentUtility(), new FileUtility())
{
}
///
/// Initializes a new instance of the class. Used for unit tests.
///
/// The deployment item utility.
/// The deployment utility.
/// The file utility.
internal TestDeployment(DeploymentItemUtility deploymentItemUtility, DeploymentUtility deploymentUtility, FileUtility fileUtility)
{
_deploymentItemUtility = deploymentItemUtility;
_deploymentUtility = deploymentUtility;
_fileUtility = fileUtility;
_adapterSettings = null;
RunDirectories = null;
}
///
/// Gets the current run directories for this session.
///
///
/// This is initialized at the beginning of a run session when Deploy is called.
/// Leaving this as a static variable since the testContext needs to be filled in with this information.
///
internal static TestRunDirectories? RunDirectories { get; private set; }
///
/// The get deployment items.
///
/// The method.
/// The type.
/// The warnings.
/// A string of deployment items.
public KeyValuePair[]? GetDeploymentItems(MethodInfo method, Type type, ICollection warnings) =>
_deploymentItemUtility.GetDeploymentItems(method, _deploymentItemUtility.GetClassLevelDeploymentItems(type, warnings), warnings);
///
/// Cleanup deployment item directories.
///
public void Cleanup()
{
// Delete the deployment directory
if (RunDirectories != null && _adapterSettings?.DeleteDeploymentDirectoryAfterTestRunIsComplete == true)
{
if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled)
{
PlatformServiceProvider.Instance.AdapterTraceLogger.Info("Deleting deployment directory {0}", RunDirectories.RootDeploymentDirectory);
}
_fileUtility.DeleteDirectories(RunDirectories.RootDeploymentDirectory);
if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled)
{
PlatformServiceProvider.Instance.AdapterTraceLogger.Info("Deleted deployment directory {0}", RunDirectories.RootDeploymentDirectory);
}
}
}
///
/// Gets the deployment output directory where the source file along with all its dependencies is dropped.
///
/// The deployment output directory.
public string? GetDeploymentDirectory() =>
RunDirectories?.OutDirectory;
///
/// Deploy files related to the list of tests specified.
///
/// The tests.
/// The host deployment inputs.
/// The logger used to surface deployment warnings.
/// Return true if deployment is done.
public bool Deploy(IEnumerable testElements, DeploymentContext deploymentContext, IAdapterMessageLogger messageLogger)
{
DebugEx.Assert(testElements != null, "tests");
// Reset runDirectories before doing deployment, so that older values of runDirectories is not picked
// even if test host is kept alive.
RunDirectories = null;
_adapterSettings = MSTestSettingsProvider.Settings;
bool canDeploy = CanDeploy();
bool hasDeploymentItems = testElements.Any(DeploymentItemUtility.HasDeploymentItems);
// deployment directories should not be created in this case,simply return
if (!canDeploy && hasDeploymentItems)
{
return false;
}
#if NETFRAMEWORK
string? firstTestSource = testElements.FirstOrDefault()?.TestMethod.AssemblyName;
RunDirectories = _deploymentUtility.CreateDeploymentDirectories(deploymentContext, firstTestSource);
// Deployment directories are created but deployment will not happen.
// This is added just to keep consistency with MSTest v1 behavior.
if (!hasDeploymentItems)
{
return false;
}
#else
// On .NET Core, avoid creating empty deployment folders when no deployment items are used.
if (!hasDeploymentItems)
{
return false;
}
string? firstTestSource = testElements.FirstOrDefault()?.TestMethod.AssemblyName;
RunDirectories = _deploymentUtility.CreateDeploymentDirectories(deploymentContext, firstTestSource);
#endif
// Object model currently does not have support for SuspendCodeCoverage. We can remove this once support is added
#if NETFRAMEWORK
using (new SuspendCodeCoverage())
#endif
{
// Group the tests by source
var testsBySource = from test in testElements
group test by test.TestMethod.AssemblyName into testGroup
select new { Source = testGroup.Key, Tests = testGroup };
TestRunDirectories runDirectories = RunDirectories;
foreach (var group in testsBySource)
{
// do the deployment
_deploymentUtility.Deploy(@group.Tests, @group.Source, deploymentContext, messageLogger, RunDirectories);
}
// Update the runDirectories
RunDirectories = runDirectories;
}
return true;
}
internal static IDictionary GetDeploymentInformation(string? source)
{
var properties = new Dictionary(capacity: 8);
string applicationBaseDirectory = string.Empty;
// Run directories can be null in win8.
if (RunDirectories == null && !StringEx.IsNullOrEmpty(source))
{
// applicationBaseDirectory is set at source level
applicationBaseDirectory = Path.GetDirectoryName(source)!;
}
properties[TestContext.TestRunDirectoryLabel] = RunDirectories?.RootDeploymentDirectory ?? applicationBaseDirectory;
properties[TestContext.DeploymentDirectoryLabel] = RunDirectories?.OutDirectory ?? applicationBaseDirectory;
properties[TestContext.ResultsDirectoryLabel] = RunDirectories?.InDirectory ?? applicationBaseDirectory;
properties[TestContext.TestRunResultsDirectoryLabel] = RunDirectories?.InMachineNameDirectory ?? applicationBaseDirectory;
properties[TestContext.TestResultsDirectoryLabel] = RunDirectories?.InDirectory ?? applicationBaseDirectory;
return properties;
}
///
/// Reset the static variable to default values. Used only for testing purposes.
///
internal static void Reset() => RunDirectories = null;
///
/// Returns whether deployment can happen or not.
///
/// True if deployment can be done.
private bool CanDeploy()
{
DebugEx.Assert(_adapterSettings is not null, "Adapter settings should not be null.");
if (!_adapterSettings.DeploymentEnabled)
{
if (PlatformServiceProvider.Instance.AdapterTraceLogger.IsInfoEnabled)
{
PlatformServiceProvider.Instance.AdapterTraceLogger.Info("MSTestExecutor: CanDeploy is false.");
}
return false;
}
return true;
}
}
#endif