File: WslcDotnetPublishingTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Dotnet.Tests\Aspire.Hosting.Dotnet.Tests.csproj (Aspire.Hosting.Dotnet.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable ASPIREPIPELINES003
#pragma warning disable ASPIRECONTAINERRUNTIME001
#pragma warning disable ASPIREPROJECTS001
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Dcp;
using Aspire.Hosting.Dcp.Process;
using Aspire.Hosting.Publishing;
using Aspire.Hosting.Tests.Utils;
using Aspire.Hosting.Utils;
using Aspire.Shared;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Dotnet.Tests;
 
[SkipOnPlatform(TestPlatforms.Linux | TestPlatforms.OSX | TestPlatforms.FreeBSD, "WSLc is Windows-only.")]
public class WslcDotnetPublishingTests(ITestOutputHelper output)
{
    [Theory]
    [InlineData(ContainerTargetPlatform.AllLinux)]
    [InlineData(ContainerTargetPlatform.LinuxAmd64 | ContainerTargetPlatform.WindowsAmd64)]
    [InlineData(ContainerTargetPlatform.WindowsAmd64 | ContainerTargetPlatform.WindowsArm64)]
    public async Task DotnetPublishing_RejectsMultiPlatformLocalStoreBeforeStartingProcess(ContainerTargetPlatform platform)
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        var runner = new TestProcessRunner();
        builder.Services.AddSingleton<IProcessRunner>(runner);
        builder.Services.Configure<DcpOptions>(options => options.ContainerRuntime = KnownContainerRuntimes.Wslc);
        var resource = builder.AddDotnetProject("program", "program.csproj", options => options.ExcludeLaunchProfile = true)
            .WithContainerBuildOptions(options => options.TargetPlatform = platform);
        using var app = builder.Build();
        var manager = app.Services.GetRequiredService<IResourceContainerImageManager>();
 
        var exception = await Assert.ThrowsAsync<DistributedApplicationException>(
            () => manager.BuildImageAsync(resource.Resource, TestContext.Current.CancellationToken));
 
        Assert.Contains("WSLc does not support loading multi-platform .NET SDK images into its local image store", exception.Message);
        Assert.Empty(runner.ProcessSpecs);
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public async Task DotnetPublishing_RejectsPlatformSelectedContainerFileLayering(bool archive)
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        using var workspace = TemporaryWorkspace.Create(output);
        var runner = new TestProcessRunner();
        builder.Services.AddSingleton<IProcessRunner>(runner);
        builder.Services.Configure<DcpOptions>(options => options.ContainerRuntime = KnownContainerRuntimes.Wslc);
        var source = builder.AddContainer("assets", "assets-image");
        var resource = builder.AddDotnetProject("program", "program.csproj", options => options.ExcludeLaunchProfile = true)
            .WithAnnotation(new ContainerFilesDestinationAnnotation { Source = source.Resource, DestinationPath = "wwwroot" })
            .WithContainerBuildOptions(options =>
            {
                options.TargetPlatform = null;
                if (archive)
                {
                    options.Destination = ContainerImageDestination.Archive;
                    options.OutputPath = Path.Combine(workspace.WorkspaceRoot.FullName, "program.tar");
                }
            });
        using var app = builder.Build();
        var manager = app.Services.GetRequiredService<IResourceContainerImageManager>();
 
        var exception = await Assert.ThrowsAsync<DistributedApplicationException>(
            () => manager.BuildImageAsync(resource.Resource, TestContext.Current.CancellationToken));
 
        Assert.Contains("WSLc does not support the platform-selected Dockerfile build required by PublishWithContainerFiles", exception.Message);
        Assert.Empty(runner.ProcessSpecs);
    }
 
    [Theory]
    [InlineData(null)]
    [InlineData(ContainerTargetPlatform.LinuxAmd64)]
    [InlineData(ContainerTargetPlatform.LinuxArm64)]
    public async Task DotnetPublishing_SinglePlatformUsesSdkWslcLocalRegistry(ContainerTargetPlatform? platform)
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        var runner = new TestProcessRunner();
        builder.Services.AddSingleton<IProcessRunner>(runner);
        builder.Services.Configure<DcpOptions>(options => options.ContainerRuntime = KnownContainerRuntimes.Wslc);
        var resource = builder.AddDotnetProject("program", "program.csproj", options => options.ExcludeLaunchProfile = true)
            .WithContainerBuildOptions(options => options.TargetPlatform = platform);
        using var app = builder.Build();
        var manager = app.Services.GetRequiredService<IResourceContainerImageManager>();
 
        await manager.BuildImageAsync(resource.Resource, TestContext.Current.CancellationToken);
 
        Assert.Collection(runner.ProcessSpecs,
            spec =>
            {
                Assert.Equal(KnownContainerRuntimes.Wslc, spec.ExecutablePath);
                Assert.Equal("container ls -n 1", spec.Arguments);
            },
            spec =>
            {
                Assert.Equal("dotnet", spec.ExecutablePath);
                var expectedArguments = new List<string>
                {
                    "publish", resource.Resource.GetProjectMetadata().ProjectPath,
                    "--configuration", "Release", "/t:PublishContainer",
                    "--property:ContainerRepository=program", "--property:ContainerImageTag=latest",
                    "--property:LocalRegistry=Wslc"
                };
                if (platform is not null)
                {
                    var rid = platform == ContainerTargetPlatform.LinuxArm64 ? "linux-arm64" : "linux-x64";
                    expectedArguments.AddRange([$"--property:RuntimeIdentifier={rid}", $"--property:ContainerRuntimeIdentifier={rid}"]);
                }
                Assert.Equal(expectedArguments, spec.ArgumentList);
            });
    }
 
    [Theory]
    [InlineData(null, ContainerTargetPlatform.LinuxAmd64)]
    [InlineData(ContainerImageFormat.Docker, ContainerTargetPlatform.LinuxAmd64)]
    [InlineData(ContainerImageFormat.Oci, ContainerTargetPlatform.LinuxAmd64)]
    [InlineData(ContainerImageFormat.Oci, ContainerTargetPlatform.AllLinux)]
    public async Task DotnetPublishing_ArchiveDoesNotInvokeWslc(ContainerImageFormat? format, ContainerTargetPlatform platform)
    {
        using var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish);
        using var workspace = TemporaryWorkspace.Create(output);
        var runner = new TestProcessRunner();
        builder.Services.AddSingleton<IProcessRunner>(runner);
        builder.Services.Configure<DcpOptions>(options => options.ContainerRuntime = KnownContainerRuntimes.Wslc);
        var resource = builder.AddDotnetProject("program", "program.csproj", options => options.ExcludeLaunchProfile = true)
            .WithContainerBuildOptions(options =>
            {
                options.Destination = ContainerImageDestination.Archive;
                options.OutputPath = Path.Combine(workspace.WorkspaceRoot.FullName, "program.tar");
                options.ImageFormat = format;
                options.TargetPlatform = platform;
            });
        using var app = builder.Build();
        var manager = app.Services.GetRequiredService<IResourceContainerImageManager>();
 
        await manager.BuildImagesAsync([resource.Resource], TestContext.Current.CancellationToken);
 
        var spec = Assert.Single(runner.ProcessSpecs);
        Assert.Equal("dotnet", spec.ExecutablePath);
        var expectedArguments = new List<string>
        {
            "publish",
            resource.Resource.GetProjectMetadata().ProjectPath,
            "--configuration",
            "Release",
            "/t:PublishContainer",
            "--property:ContainerRepository=program",
            "--property:ContainerImageTag=latest",
            MsBuildResponseFileFactory.CreatePropertyArgument("ContainerArchiveOutputPath", Path.Combine(workspace.WorkspaceRoot.FullName, "program.tar"))
        };
        if (format is not null)
        {
            expectedArguments.Add($"--property:ContainerImageFormat={(format == ContainerImageFormat.Oci ? "OCI" : "Docker")}");
        }
        if (platform == ContainerTargetPlatform.AllLinux)
        {
            expectedArguments.Add(MsBuildResponseFileFactory.CreatePropertyArgument("RuntimeIdentifiers", "linux-x64;linux-arm64"));
            expectedArguments.Add(MsBuildResponseFileFactory.CreatePropertyArgument("ContainerRuntimeIdentifiers", "linux-x64;linux-arm64"));
        }
        else
        {
            expectedArguments.AddRange(["--property:RuntimeIdentifier=linux-x64", "--property:ContainerRuntimeIdentifier=linux-x64"]);
        }
        Assert.Equal(expectedArguments, spec.ArgumentList);
    }
}