// 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