File: CreateVisualStudioWorkloadTests.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Workloads.Tests\Microsoft.DotNet.Build.Tasks.Workloads.Tests.csproj (Microsoft.DotNet.Build.Tasks.Workloads.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Arcade.Test.Common;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.DotNet.Build.Tasks.Workloads.Msi;
using Xunit;
 
namespace Microsoft.DotNet.Build.Tasks.Workloads.Tests
{
    [Collection("Workload Creation")]
    public class CreateVisualStudioWorkloadTests : TestBase
    {
        [WindowsOnlyFact]
        public static void ItCanCreateWorkloads()
        {
            // Create intermediate outputs under %temp% to avoid path issues and make sure it's clean so we don't pick up
            // conflicting sources from previous runs.
            string baseIntermediateOutputPath = Path.Combine(Path.GetTempPath(), "WL");
 
            if (Directory.Exists(baseIntermediateOutputPath))
            {
                Directory.Delete(baseIntermediateOutputPath, recursive: true);
            }
 
            ITaskItem[] manifestsPackages = new[]
            {
                new TaskItem(Path.Combine(TestBase.TestAssetsPath, "microsoft.net.workload.emscripten.manifest-6.0.200.6.0.4.nupkg"))
                .WithMetadata(Metadata.MsiVersion, "6.33.28")
            };
 
            ITaskItem[] componentResources = new[]
            {
                new TaskItem("microsoft-net-sdk-emscripten")
                .WithMetadata(Metadata.Title, ".NET WebAssembly Build Tools (Emscripten)")
                .WithMetadata(Metadata.Description, "Build tools for WebAssembly ahead-of-time (AoT) compilation and native linking.")
                .WithMetadata(Metadata.Version, "5.6.7.8")
            };
 
            ITaskItem[] shortNames = new[]
            {
                new TaskItem("Microsoft.NET.Workload.Emscripten").WithMetadata("Replacement", "Emscripten"),
                new TaskItem("microsoft.netcore.app.runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("Microsoft.NETCore.App.Runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("microsoft.net.runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("Microsoft.NET.Runtime").WithMetadata("Replacement", "Microsoft")
            };
 
            IBuildEngine buildEngine = new MockBuildEngine();
 
            CreateVisualStudioWorkload createWorkloadTask = new CreateVisualStudioWorkload()
            {
                AllowMissingPacks = true,
                BaseOutputPath = TestBase.BaseOutputPath,
                BaseIntermediateOutputPath = baseIntermediateOutputPath,
                BuildEngine = buildEngine,
                ComponentResources = componentResources,
                ManifestMsiVersion = null,
                PackageSource = TestBase.TestAssetsPath,
                ShortNames = shortNames,
                WixToolsetPath = TestBase.WixToolsetPath,
                WorkloadManifestPackageFiles = manifestsPackages,
                IsOutOfSupportInVisualStudio = true
            };
 
            bool result = createWorkloadTask.Execute();
 
            Assert.True(result);
            ITaskItem manifestMsiItem = createWorkloadTask.Msis.Where(m => m.ItemSpec.ToLowerInvariant().Contains("d96ba8044ad35589f97716ecbf2732fb-x64.msi")).FirstOrDefault();
            Assert.NotNull(manifestMsiItem);
 
            // Spot check one of the manifest MSIs. We have additional tests that cover MSI generation.
            // The UpgradeCode is predictable/stable for manifest MSIs since they are upgradable withing an SDK feature band,
            Assert.Equal("{C4F269D9-6B65-36C5-9556-75B78EFE9EDA}", MsiUtils.GetProperty(manifestMsiItem.ItemSpec, MsiProperty.UpgradeCode));
            // The version should match the value passed to the build task. For actual builds like dotnet/runtiem, this value would
            // be generated.
            Assert.Equal("6.33.28", MsiUtils.GetProperty(manifestMsiItem.ItemSpec, MsiProperty.ProductVersion));
            Assert.Equal("Microsoft.NET.Workload.Emscripten,6.0.200,x64", MsiUtils.GetProviderKeyName(manifestMsiItem.ItemSpec));
 
            // Process the template in the summary information stream. This is the only way to verify the intended platform
            // of the MSI itself.
            using SummaryInfo si = new(manifestMsiItem.ItemSpec, enableWrite: false);
            Assert.Equal("x64;1033", si.Template);
 
            // Verify the SWIX authoring for the component representing the workload in VS. The first should be a standard
            // component. There should also be a second preview component.
            string componentSwr = File.ReadAllText(
                Path.Combine(Path.GetDirectoryName(
                    createWorkloadTask.SwixProjects.FirstOrDefault(
                        i => i.ItemSpec.Contains("microsoft.net.sdk.emscripten.5.6.swixproj")).ItemSpec), "component.swr"));
            Assert.Contains("package name=microsoft.net.sdk.emscripten", componentSwr);
            string previewComponentSwr = File.ReadAllText(
                Path.Combine(Path.GetDirectoryName(
                    createWorkloadTask.SwixProjects.FirstOrDefault(
                        i => i.ItemSpec.Contains("microsoft.net.sdk.emscripten.pre.5.6.swixproj")).ItemSpec), "component.swr"));
            Assert.Contains("package name=microsoft.net.sdk.emscripten.pre", previewComponentSwr);
 
            // Emscripten is an abstract workload so it should be a component group.
            Assert.Contains("vs.package.type=component", componentSwr);
            Assert.Contains("vs.package.outOfSupport=yes", componentSwr);
            Assert.Contains("isUiGroup=yes", componentSwr);
            Assert.Contains("version=5.6.7.8", componentSwr);
 
            Assert.Contains("vs.package.type=component", previewComponentSwr);
            Assert.Contains("isUiGroup=yes", previewComponentSwr);
            Assert.Contains("version=5.6.7.8", previewComponentSwr);
 
            // Verify pack dependencies. These should map to MSI packages. The VS package IDs should be the non-aliased
            // pack IDs and version from the workload manifest. The actual VS packages will point to the MSIs generated from the
            // aliased workload pack packages. 
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Node.6.0.4", componentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Python.6.0.4", componentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Sdk.6.0.4", componentSwr);
 
            // Pack dependencies for preview components should be identical to the non-preview component.
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Node.6.0.4", previewComponentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Python.6.0.4", previewComponentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Sdk.6.0.4", previewComponentSwr);
 
            // Verify the SWIX authoring for the VS package wrapping the manifest MSI
            string manifestMsiSwr = File.ReadAllText(Path.Combine(baseIntermediateOutputPath, "src", "swix", "6.0.200", "Emscripten.Manifest-6.0.200", "x64", "msi.swr"));
            Assert.Contains("package name=Emscripten.Manifest-6.0.200", manifestMsiSwr);
            Assert.Contains("vs.package.type=msi", manifestMsiSwr);
            Assert.Contains("vs.package.chip=x64", manifestMsiSwr);
            Assert.DoesNotContain("vs.package.machineArch", manifestMsiSwr);
            Assert.DoesNotContain("vs.package.outOfSupport", manifestMsiSwr);
 
            // Verify that no arm64 MSI authoring for VS. EMSDK doesn't define RIDs for arm64, but manifests always generate
            // arm64 MSIs for the CLI based installs so we should not see that.
            string swixRootDirectory = Path.Combine(baseIntermediateOutputPath, "src", "swix", "6.0.200");
            IEnumerable<string> arm64Directories = Directory.EnumerateDirectories(swixRootDirectory, "arm64", SearchOption.AllDirectories);
            Assert.DoesNotContain(arm64Directories, s => s.Contains("arm64"));
 
            // Verify the SWIX authoring for one of the workload pack MSIs. Packs get assigned random sub-folders so we
            // need to filter out the SWIX project output items the task produced.
            ITaskItem pythonPackSwixItem = createWorkloadTask.SwixProjects.Where(s => s.ItemSpec.Contains(@"Microsoft.Emscripten.Python.6.0.4\x64")).FirstOrDefault();
            string packMsiSwr = File.ReadAllText(Path.Combine(Path.GetDirectoryName(pythonPackSwixItem.ItemSpec), "msi.swr"));
            Assert.Contains("package name=Microsoft.Emscripten.Python.6.0.4", packMsiSwr);
            Assert.Contains("vs.package.chip=x64", packMsiSwr);
            Assert.Contains("vs.package.outOfSupport=yes", packMsiSwr);
            Assert.DoesNotContain("vs.package.machineArch", packMsiSwr);
 
            // Verify the swix project items for components. The project files names always contain the major.minor suffix, so we'll end up
            // with microsoft.net.sdk.emscripten.5.6.swixproj and microsoft.net.sdk.emscripten.pre.5.6.swixproj
            IEnumerable<ITaskItem> swixComponentProjects = createWorkloadTask.SwixProjects.Where(s => s.GetMetadata(Metadata.PackageType).Equals(DefaultValues.PackageTypeComponent));
            Assert.All(swixComponentProjects, c => Assert.True(c.ItemSpec.Contains(".pre.") && c.GetMetadata(Metadata.IsPreview) == "true" ||
                !c.ItemSpec.Contains(".pre.") && c.GetMetadata(Metadata.IsPreview) == "false"));
        }
 
        [WindowsOnlyFact]
        public static void ItCanCreateWorkloadsThatSupportArm64InVisualStudio()
        {
            // Create intermediate outputs under %temp% to avoid path issues and make sure it's clean so we don't pick up
            // conflicting sources from previous runs.
            string baseIntermediateOutputPath = Path.Combine(Path.GetTempPath(), "WLa64");
 
            if (Directory.Exists(baseIntermediateOutputPath))
            {
                Directory.Delete(baseIntermediateOutputPath, recursive: true);
            }
 
            ITaskItem[] manifestsPackages = new[]
            {
                new TaskItem(Path.Combine(TestBase.TestAssetsPath, "microsoft.net.workload.emscripten.manifest-6.0.200.6.0.4.nupkg"))
                .WithMetadata(Metadata.MsiVersion, "6.33.28")
                .WithMetadata(Metadata.SupportsMachineArch, "true")
            };
 
            ITaskItem[] componentResources = new[]
            {
                new TaskItem("microsoft-net-sdk-emscripten")
                .WithMetadata(Metadata.Title, ".NET WebAssembly Build Tools (Emscripten)")
                .WithMetadata(Metadata.Description, "Build tools for WebAssembly ahead-of-time (AoT) compilation and native linking.")
                .WithMetadata(Metadata.Version, "5.6.7.8")
            };
 
            ITaskItem[] shortNames = new[]
            {
                new TaskItem("Microsoft.NET.Workload.Emscripten").WithMetadata("Replacement", "Emscripten"),
                new TaskItem("microsoft.netcore.app.runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("Microsoft.NETCore.App.Runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("microsoft.net.runtime").WithMetadata("Replacement", "Microsoft"),
                new TaskItem("Microsoft.NET.Runtime").WithMetadata("Replacement", "Microsoft")
            };
 
            IBuildEngine buildEngine = new MockBuildEngine();
 
            CreateVisualStudioWorkload createWorkloadTask = new CreateVisualStudioWorkload()
            {
                AllowMissingPacks = true,
                BaseOutputPath = TestBase.BaseOutputPath,
                BaseIntermediateOutputPath = baseIntermediateOutputPath,
                BuildEngine = buildEngine,
                ComponentResources = componentResources,
                ManifestMsiVersion = null,
                PackageSource = TestBase.TestAssetsPath,
                ShortNames = shortNames,
                WixToolsetPath = TestBase.WixToolsetPath,
                WorkloadManifestPackageFiles = manifestsPackages,
            };
 
            bool result = createWorkloadTask.Execute();
 
            Assert.True(result);
            ITaskItem manifestMsiItem = createWorkloadTask.Msis.Where(m => m.ItemSpec.ToLowerInvariant().Contains("d96ba8044ad35589f97716ecbf2732fb-arm64.msi")).FirstOrDefault();
            Assert.NotNull(manifestMsiItem);
 
            // Spot check one of the manifest MSIs. We have additional tests that cover MSI generation.
            // The UpgradeCode is predictable/stable for manifest MSIs since they are upgradable withing an SDK feature band,
            Assert.Equal("{CBA7CF4A-F3C9-3B75-8F1F-0D08AF7CD7BE}", MsiUtils.GetProperty(manifestMsiItem.ItemSpec, MsiProperty.UpgradeCode));
            // The version should match the value passed to the build task. For actual builds like dotnet/runtiem, this value would
            // be generated.
            Assert.Equal("6.33.28", MsiUtils.GetProperty(manifestMsiItem.ItemSpec, MsiProperty.ProductVersion));
            Assert.Equal("Microsoft.NET.Workload.Emscripten,6.0.200,arm64", MsiUtils.GetProviderKeyName(manifestMsiItem.ItemSpec));
 
            // Process the template in the summary information stream. This is the only way to verify the intended platform
            // of the MSI itself.
            using SummaryInfo si = new(manifestMsiItem.ItemSpec, enableWrite: false);
            Assert.Equal("Arm64;1033", si.Template);
 
            // Verify the SWIX authoring for the component representing the workload in VS.
            string componentSwr = File.ReadAllText(
                Path.Combine(Path.GetDirectoryName(
                    createWorkloadTask.SwixProjects.FirstOrDefault(
                        i => i.ItemSpec.Contains("microsoft.net.sdk.emscripten.5.6.swixproj")).ItemSpec), "component.swr"));
            Assert.Contains("package name=microsoft.net.sdk.emscripten", componentSwr);
 
            // Emscripten is an abstract workload so it should be a component group.
            Assert.Contains("vs.package.type=component", componentSwr);
            Assert.Contains("isUiGroup=yes", componentSwr);
            Assert.Contains("version=5.6.7.8", componentSwr);
            // Default setting should be off
            Assert.Contains("vs.package.outOfSupport=no", componentSwr);
 
            // Verify pack dependencies. These should map to MSI packages. The VS package IDs should be the non-aliased
            // pack IDs and version from the workload manifest. The actual VS packages will point to the MSIs generated from the
            // aliased workload pack packages. 
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Node.6.0.4", componentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Python.6.0.4", componentSwr);
            Assert.Contains("vs.dependency id=Microsoft.Emscripten.Sdk.6.0.4", componentSwr);
 
            // Verify the SWIX authoring for the VS package wrapping the manifest MSI
            string manifestMsiSwr = File.ReadAllText(Path.Combine(baseIntermediateOutputPath, "src", "swix", "6.0.200", "Emscripten.Manifest-6.0.200", "arm64", "msi.swr"));
            Assert.Contains("package name=Emscripten.Manifest-6.0.200", manifestMsiSwr);
            Assert.Contains("vs.package.type=msi", manifestMsiSwr);
            Assert.DoesNotContain("vs.package.chip", manifestMsiSwr);
            Assert.Contains("vs.package.machineArch=arm64", manifestMsiSwr);
        }
    }
}