// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Tools.CreateLayout;
using Xunit;
namespace Infrastructure.Tests.CreateLayout;
public class CreateLayoutTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public void GetComponentTargetFramework_MissingMetadataIdentifiesProject()
{
var exception = Assert.Throws<InvalidOperationException>(() =>
LayoutBuilder.GetComponentTargetFramework("Missing.Project"));
Assert.Equal("Missing target framework metadata for Missing.Project.", exception.Message);
}
[Theory]
[InlineData("Aspire.Managed", "managed", "aspire-managed")]
[InlineData("Aspire.Dashboard", "dashboard", "Aspire.Dashboard")]
[InlineData("Aspire.TerminalHost", "terminalhost", "Aspire.TerminalHost")]
[InlineData("Aspire.Hosting.Native.Server", "apphost-server", "aspire-apphost-server")]
public void CopyComponent_ExplicitDirectoryNeverFallsBack(string project, string destination, string executable)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var stale = Path.Combine(workspace.Path, "bin", project, "Debug", "net11.0", "linux-x64", "publish");
CreateComponentPublishOutput(stale, project, "stale");
var discovered = Path.Combine(workspace.Path, "bin", project, "Debug",
LayoutBuilder.GetComponentTargetFramework(project), "linux-x64", "publish");
CreateComponentPublishOutput(discovered, project, "discovered");
var selected = Path.Combine(workspace.Path, "selected payload");
CreateComponentPublishOutput(selected, project, "selected");
var output = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(output, workspace.Path, "linux-x64", "Debug", "1.0", false,
trayDirectory: null,
dashboardPath: project == "Aspire.Dashboard" ? selected : null,
managedPath: project == "Aspire.Managed" ? selected : null,
terminalHostPath: project == "Aspire.TerminalHost" ? selected : null,
appHostServerPath: project == "Aspire.Hosting.Native.Server" ? selected : null);
Action copy = project switch
{
"Aspire.Managed" => builder.CopyManaged,
"Aspire.Dashboard" => builder.CopyDashboard,
"Aspire.TerminalHost" => builder.CopyTerminalHost,
_ => builder.CopyAppHostServer
};
copy();
Assert.Equal("selected", File.ReadAllText(Path.Combine(output, destination, executable)));
var sourceExecutable = project == "Aspire.Managed" ? "aspire-managed" : project;
File.Delete(Path.Combine(selected, sourceExecutable));
var missingPayload = Assert.Throws<InvalidOperationException>(copy);
Assert.Contains(Path.Combine(selected, sourceExecutable), missingPayload.Message, StringComparison.Ordinal);
Directory.Delete(selected, recursive: true);
var missingDirectory = Assert.Throws<InvalidOperationException>(copy);
Assert.Contains($"publish directory not found at {selected}", missingDirectory.Message, StringComparison.Ordinal);
Assert.True(File.Exists(Path.Combine(discovered, sourceExecutable)));
Assert.True(File.Exists(Path.Combine(stale, sourceExecutable)));
}
[Theory]
[InlineData("Aspire.Managed", "--managed", "")]
[InlineData("Aspire.Managed", "--managed", " ")]
[InlineData("Aspire.TerminalHost", "--terminal-host", "")]
[InlineData("Aspire.TerminalHost", "--terminal-host", " ")]
[InlineData("Aspire.Hosting.Native.Server", "--apphost-server", "")]
[InlineData("Aspire.Hosting.Native.Server", "--apphost-server", " ")]
public void CopyComponent_EmptyExplicitDirectoryIsRejected(string project, string option, string selected)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var discovered = Path.Combine(workspace.Path, "bin", project, "Debug",
LayoutBuilder.GetComponentTargetFramework(project), "linux-x64", "publish");
CreateComponentPublishOutput(discovered, project, "discovered");
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, "linux-x64", "Debug", "1.0", false,
trayDirectory: null, dashboardPath: null,
managedPath: project == "Aspire.Managed" ? selected : null,
terminalHostPath: project == "Aspire.TerminalHost" ? selected : null,
appHostServerPath: project == "Aspire.Hosting.Native.Server" ? selected : null);
Action copy = project switch
{
"Aspire.Managed" => builder.CopyManaged,
"Aspire.TerminalHost" => builder.CopyTerminalHost,
_ => builder.CopyAppHostServer
};
var exception = Assert.Throws<InvalidOperationException>(copy);
Assert.Equal($"{option} requires a nonempty publish directory.", exception.Message);
}
[Theory]
[InlineData("publish")]
[InlineData("linux-x64/publish")]
public void CopyManaged_DiscoveryUsesDefaultTargetFramework(string directory)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var source = Path.Combine(workspace.Path, "bin", "Aspire.Managed", "Debug",
LayoutBuilder.GetComponentTargetFramework("Aspire.Managed"), directory);
CreateComponentPublishOutput(source, "Aspire.Managed", "managed");
var output = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(output, workspace.Path, "linux-x64", "Debug", "1.0", false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
builder.CopyManaged();
Assert.Equal("managed", File.ReadAllText(Path.Combine(output, "managed", "aspire-managed")));
}
[Theory]
[InlineData("win-x64", ".exe")]
[InlineData("linux-x64", "")]
[InlineData("osx-arm64", "")]
public void CopyAppHostServer_PreservesPayloadAndProductNameWithoutSymbols(string rid, string extension)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var source = Path.Combine(workspace.Path, "bin", "Aspire.Hosting.Native.Server", "Debug",
LayoutBuilder.GetComponentTargetFramework("Aspire.Hosting.Native.Server"), rid, "publish");
var symbolDirectory = Path.Combine(source, "Aspire.Hosting.Native.Server.dSYM");
Directory.CreateDirectory(symbolDirectory);
File.WriteAllText(Path.Combine(source, "Aspire.Hosting.Native.Server" + extension), "server");
File.WriteAllText(Path.Combine(source, "ats-contract.json"), "contract");
File.WriteAllText(Path.Combine(source, "sidecar.json"), "sidecar");
File.WriteAllText(Path.Combine(source, "Aspire.Hosting.Native.Server.pdb"), "symbols");
File.WriteAllText(Path.Combine(symbolDirectory, "symbols"), "symbols");
var output = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(output, workspace.Path, rid, "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
builder.CopyAppHostServer();
var destination = Path.Combine(output, "apphost-server");
Assert.Equal(["aspire-apphost-server" + extension, "ats-contract.json", "sidecar.json"],
Directory.GetFiles(destination, "*", SearchOption.AllDirectories).Select(Path.GetFileName).Order(StringComparer.Ordinal));
Assert.Equal("server", File.ReadAllText(Path.Combine(destination, "aspire-apphost-server" + extension)));
Assert.Equal("contract", File.ReadAllText(Path.Combine(destination, "ats-contract.json")));
Assert.Equal("sidecar", File.ReadAllText(Path.Combine(destination, "sidecar.json")));
}
[Theory]
[InlineData("publish", null)]
[InlineData("publish", "")]
[InlineData("native", null)]
[InlineData("native", "")]
public void CopyAppHostServer_RejectsMissingOrEmptyContract(string directory, string? contract)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var source = Path.Combine(workspace.Path, "bin", "Aspire.Hosting.Native.Server", "Debug",
LayoutBuilder.GetComponentTargetFramework("Aspire.Hosting.Native.Server"), "linux-x64", directory);
Directory.CreateDirectory(source);
File.WriteAllText(Path.Combine(source, "Aspire.Hosting.Native.Server"), "server");
if (contract is not null)
{
File.WriteAllText(Path.Combine(source, "ats-contract.json"), contract);
}
var output = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(output, workspace.Path, "linux-x64", "Debug", "1.0.0", false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var failure = Assert.Throws<InvalidOperationException>(builder.CopyAppHostServer);
Assert.Contains("ats-contract.json", failure.Message, StringComparison.Ordinal);
Assert.False(Directory.Exists(Path.Combine(output, "apphost-server")));
}
[Theory]
[InlineData("win-x64")]
[InlineData("win-arm64")]
[InlineData("linux-x64")]
[InlineData("linux-arm64")]
[InlineData("linux-musl-x64")]
[InlineData("osx-x64")]
[InlineData("osx-arm64")]
public void CopyTerminalHostRequiresNativeDependenciesAndExcludesSymbols(string rid)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publish = Path.Combine(workspace.Path, "bin", "Aspire.TerminalHost", "Debug",
LayoutBuilder.GetComponentTargetFramework("Aspire.TerminalHost"), rid, "publish");
var files = Aspire.Shared.TerminalHostPayload.GetRequiredFiles(rid);
foreach (var file in files)
{
var path = Path.Combine(publish, file);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, file);
}
File.WriteAllText(Path.Combine(publish, "Aspire.TerminalHost.pdb"), "symbols");
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, rid, "Debug", "1.0", false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
builder.CopyTerminalHost();
var target = Path.Combine(workspace.Path, "layout", "terminalhost");
Assert.Equal(files.Order(StringComparer.Ordinal), Directory.GetFiles(target, "*", SearchOption.AllDirectories)
.Select(path => Path.GetRelativePath(target, path).Replace('\\', '/')).Order(StringComparer.Ordinal));
File.Delete(Path.Combine(publish, files[^1]));
Assert.Throws<InvalidOperationException>(builder.CopyTerminalHost);
}
[Fact]
public void TerminalHostPayloadValidationUsesCurrentRidAndRequiresNonEmptyFiles()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var rid = System.Runtime.InteropServices.RuntimeInformation.RuntimeIdentifier;
var files = Aspire.Shared.TerminalHostPayload.GetRequiredFiles(rid);
Assert.False(Aspire.Shared.TerminalHostPayload.IsValid(workspace.Path));
foreach (var file in files)
{
var path = Path.Combine(workspace.Path, file);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, file);
}
Assert.True(Aspire.Shared.TerminalHostPayload.IsValid(workspace.Path));
Assert.True(Aspire.Shared.TerminalHostPayload.IsValid(workspace.Path, rid));
File.WriteAllText(Path.Combine(workspace.Path, files[^1]), string.Empty);
Assert.False(Aspire.Shared.TerminalHostPayload.IsValid(workspace.Path));
Assert.False(Aspire.Shared.TerminalHostPayload.IsValid(workspace.Path, rid));
}
[Theory]
[InlineData("Debug", "Release")]
[InlineData("Release", "Debug")]
[InlineData("Custom", "Release")]
public void FindPublishPath_UsesOnlyRequestedConfiguration(string configuration, string otherConfiguration)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, "win-x64", configuration, "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var expectedPaths = GetPublishPaths(workspace.Path, configuration);
foreach (var path in expectedPaths.Concat(GetPublishPaths(workspace.Path, otherConfiguration)))
{
Directory.CreateDirectory(path);
}
foreach (var expectedPath in expectedPaths)
{
Assert.Equal(expectedPath, builder.FindPublishPath("Aspire.Dashboard", LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard")));
Directory.Delete(expectedPath);
}
Assert.Null(builder.FindPublishPath("Aspire.Dashboard", LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard")));
}
[Fact]
public void FindPublishPath_RequireRidSpecific_IgnoresNonRidPublish()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var nonRidPath = GetPublishPaths(workspace.Path, "Debug")[^1];
Directory.CreateDirectory(nonRidPath);
Assert.Null(builder.FindPublishPath("Aspire.Dashboard", LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), requireRidSpecific: true));
Assert.Equal(nonRidPath, builder.FindPublishPath("Aspire.Dashboard", LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard")));
}
[Fact]
public async Task Main_RequiresConfiguration()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var outputPath = Path.Combine(workspace.Path, "layout");
var exitCode = await Aspire.Tools.CreateLayout.Program.Main(
[
"--output", outputPath,
"--artifacts", workspace.Path,
"--rid", "win-x64"
]);
Assert.Equal(1, exitCode);
Assert.False(Directory.Exists(outputPath));
}
[Fact]
public void CopyDashboard_MissingExplicitDirectoryDoesNotFallBackToDiscoverablePayload()
{
const string rid = "linux-x64";
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var discoveredPath = CreateDashboardPublishOutput(workspace.Path, rid, "libe_sqlite3.so");
var explicitPath = Path.Combine(workspace.Path, "missing prepared Dashboard");
var outputPath = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(outputPath, workspace.Path, rid, "Debug", "1.0.0",
verbose: false, trayDirectory: null, dashboardPath: explicitPath, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Equal($"Dashboard publish directory not found at {explicitPath}", exception.Message);
Assert.Equal(discoveredPath, builder.FindPublishPath("Aspire.Dashboard", LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), requireRidSpecific: true));
Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
}
[Theory]
[InlineData("win-x64", "e_sqlite3.dll")]
[InlineData("win-arm64", "e_sqlite3.dll")]
[InlineData("linux-x64", "libe_sqlite3.so")]
[InlineData("linux-arm64", "libe_sqlite3.so")]
[InlineData("linux-musl-x64", "libe_sqlite3.so")]
[InlineData("osx-x64", "libe_sqlite3.dylib")]
[InlineData("osx-arm64", "libe_sqlite3.dylib")]
public void CopyDashboard_CopiesCompletePublishPayloadWithoutSymbols(string rid, string sqliteLibraryName)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publishPath = CreateDashboardPublishOutput(workspace.Path, rid, sqliteLibraryName);
var executableName = rid.StartsWith("win-", StringComparison.Ordinal) ? "Aspire.Dashboard.exe" : "Aspire.Dashboard";
string[] expectedFiles =
[
executableName,
sqliteLibraryName,
"appsettings.json",
"other-native-library.dll",
"locales/en/messages.json",
"wwwroot/_framework/blazor.web.js"
];
string[] symbolFiles =
[
"Aspire.Dashboard.pdb",
"Aspire.Dashboard.dbg",
"Aspire.Dashboard.dSYM/Contents/Resources/DWARF/Aspire.Dashboard",
"locales/nested.pdb",
"locales/nested.dbg",
"locales/nested.dSYM/Contents/Resources/DWARF/nested"
];
foreach (var relativePath in expectedFiles.Concat(symbolFiles))
{
var filePath = Path.Combine(publishPath, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
File.WriteAllText(filePath, relativePath);
}
var outputPath = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(outputPath, workspace.Path, rid, "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
builder.CopyDashboard();
var dashboardPath = Path.Combine(outputPath, "dashboard");
var actualFiles = Directory.GetFiles(dashboardPath, "*", SearchOption.AllDirectories)
.Select(path => Path.GetRelativePath(dashboardPath, path).Replace(Path.DirectorySeparatorChar, '/'));
Assert.Equal(expectedFiles.Order(StringComparer.Ordinal), actualFiles.Order(StringComparer.Ordinal));
foreach (var relativePath in expectedFiles)
{
Assert.Equal(File.ReadAllBytes(Path.Combine(publishPath, relativePath)), File.ReadAllBytes(Path.Combine(dashboardPath, relativePath)));
}
}
[Fact]
public void CopyDashboard_MissingStaticAssets_Throws()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publishPath = CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
var wwwrootPath = Path.Combine(publishPath, "wwwroot");
Directory.Delete(wwwrootPath, recursive: true);
var outputPath = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(outputPath, workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Equal($"Native AOT Dashboard static assets not found at {wwwrootPath}", exception.Message);
Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void CopyDashboard_MissingOrEmptyBlazorScript_Throws(bool empty)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publishPath = CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
var blazorScriptPath = Path.Combine(publishPath, "wwwroot", "_framework", "blazor.web.js");
if (empty)
{
File.WriteAllBytes(blazorScriptPath, []);
}
else
{
File.Delete(blazorScriptPath);
}
var outputPath = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(outputPath, workspace.Path, "win-x64", "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Equal($"Native AOT Dashboard Blazor script missing or empty at {blazorScriptPath}", exception.Message);
Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
}
[Theory]
[InlineData("win-x64", "e_sqlite3.dll", false)]
[InlineData("win-x64", "e_sqlite3.dll", true)]
[InlineData("linux-x64", "libe_sqlite3.so", false)]
[InlineData("linux-x64", "libe_sqlite3.so", true)]
[InlineData("osx-arm64", "libe_sqlite3.dylib", false)]
[InlineData("osx-arm64", "libe_sqlite3.dylib", true)]
public void CopyDashboard_MissingOrEmptySqliteLibrary_Throws(string rid, string sqliteLibraryName, bool empty)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publishPath = CreateDashboardPublishOutput(workspace.Path, rid, sqliteLibraryName);
var sqliteLibraryPath = Path.Combine(publishPath, sqliteLibraryName);
if (empty)
{
File.WriteAllBytes(sqliteLibraryPath, []);
}
else
{
File.Delete(sqliteLibraryPath);
}
var outputPath = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(outputPath, workspace.Path, rid, "Debug", "1.0.0", verbose: false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Equal($"Native AOT Dashboard SQLite library missing or empty at {sqliteLibraryPath}", exception.Message);
Assert.False(Directory.Exists(Path.Combine(outputPath, "dashboard")));
}
[Fact]
public void CopyDashboard_ExplicitDirectoryPreservesPayloadFiltering()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
var publish = CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
var explicitPath = Path.Combine(workspace.Path, "prepared dashboard outside artifacts");
Directory.Move(publish, explicitPath);
File.WriteAllText(Path.Combine(explicitPath, "Aspire.Dashboard.exe"), "explicit executable");
File.WriteAllText(Path.Combine(explicitPath, "Aspire.Dashboard.pdb"), "symbols");
Directory.CreateDirectory(Path.Combine(explicitPath, "nested.dSYM"));
File.WriteAllText(Path.Combine(explicitPath, "nested.dSYM", "symbols"), "symbols");
File.WriteAllText(Path.Combine(explicitPath, "Aspire.Dashboard.dbg"), "symbols");
var output = Path.Combine(workspace.Path, "layout");
using var builder = new LayoutBuilder(output, workspace.Path, "win-x64", "Debug", "1.0", false,
trayDirectory: null, dashboardPath: explicitPath, managedPath: null, terminalHostPath: null, appHostServerPath: null);
builder.CopyDashboard();
var dashboard = Path.Combine(output, "dashboard");
Assert.Equal(["Aspire.Dashboard.exe", "e_sqlite3.dll", "wwwroot/_framework/blazor.web.js"],
Directory.GetFiles(dashboard, "*", SearchOption.AllDirectories)
.Select(path => Path.GetRelativePath(dashboard, path).Replace('\\', '/')).Order(StringComparer.Ordinal));
Assert.Equal("explicit executable", File.ReadAllText(Path.Combine(dashboard, "Aspire.Dashboard.exe")));
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData("\t")]
public void CopyDashboard_EmptyExplicitDirectoryIsRejected(string path)
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
CreateDashboardPublishOutput(workspace.Path, "win-x64", "e_sqlite3.dll");
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path,
"win-x64", "Debug", "1.0", false,
trayDirectory: null, dashboardPath: path, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Equal("--dashboard requires a nonempty publish directory.", exception.Message);
}
[Fact]
public void CopyDashboard_DiscoveryFailureIdentifiesSearchLocations()
{
using var workspace = TemporaryWorkspace.Create(testOutputHelper);
using var builder = new LayoutBuilder(Path.Combine(workspace.Path, "layout"), workspace.Path,
"win-x64", "Debug", "1.0", false,
trayDirectory: null, dashboardPath: null, managedPath: null, terminalHostPath: null, appHostServerPath: null);
var exception = Assert.Throws<InvalidOperationException>(builder.CopyDashboard);
Assert.Contains("win-x64", exception.Message);
Assert.Contains("Debug", exception.Message);
foreach (var path in GetPublishPaths(workspace.Path, "Debug")[..^1])
{
Assert.Contains(path, exception.Message);
}
}
private static void CreateComponentPublishOutput(string path, string project, string content)
{
string[] files = project switch
{
"Aspire.Managed" => ["aspire-managed"],
"Aspire.Dashboard" => ["Aspire.Dashboard", "libe_sqlite3.so", "wwwroot/_framework/blazor.web.js"],
"Aspire.TerminalHost" => Aspire.Shared.TerminalHostPayload.GetRequiredFiles("linux-x64"),
"Aspire.Hosting.Native.Server" => ["Aspire.Hosting.Native.Server", "ats-contract.json"],
_ => throw new ArgumentOutOfRangeException(nameof(project))
};
foreach (var file in files)
{
var filePath = Path.Combine(path, file);
Directory.CreateDirectory(Path.GetDirectoryName(filePath)!);
File.WriteAllText(filePath, content);
}
}
private static string CreateDashboardPublishOutput(string artifactsPath, string rid, string sqliteLibraryName)
{
var publishPath = Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", rid, "Debug",
LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), rid, "publish");
var frameworkPath = Path.Combine(publishPath, "wwwroot", "_framework");
Directory.CreateDirectory(frameworkPath);
var executableName = rid.StartsWith("win-", StringComparison.Ordinal) ? "Aspire.Dashboard.exe" : "Aspire.Dashboard";
File.WriteAllText(Path.Combine(publishPath, executableName), "executable");
File.WriteAllText(Path.Combine(publishPath, sqliteLibraryName), "sqlite");
File.WriteAllText(Path.Combine(frameworkPath, "blazor.web.js"), "static asset");
return publishPath;
}
private static string[] GetPublishPaths(string artifactsPath, string configuration) =>
[
Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", "win-x64", configuration, LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), "win-x64", "publish"),
Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), "win-x64", "publish"),
Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), "win-x64", "native"),
Path.Combine(artifactsPath, "bin", "Aspire.Dashboard", configuration, LayoutBuilder.GetComponentTargetFramework("Aspire.Dashboard"), "publish")
];
}