Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
public static class DependencyResolutionCommandResultExtensions
{
// App asset resolution extensions
public const string TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES";
public const string NATIVE_DLL_SEARCH_DIRECTORIES = "NATIVE_DLL_SEARCH_DIRECTORIES";
private const string TRUSTED_PLATFORM_ASSEMBLIES = nameof(TRUSTED_PLATFORM_ASSEMBLIES);
private const string NATIVE_DLL_SEARCH_DIRECTORIES = nameof(NATIVE_DLL_SEARCH_DIRECTORIES);
private const string PLATFORM_RESOURCE_ROOTS = nameof(PLATFORM_RESOURCE_ROOTS);

public static AndConstraint<CommandResultAssertions> HaveRuntimePropertyContaining(this CommandResultAssertions assertion, string propertyName, params string[] values)
{
Expand Down Expand Up @@ -67,6 +68,16 @@ public static AndConstraint<CommandResultAssertions> NotHaveResolvedNativeLibrar
return assertion.NotHaveRuntimePropertyContaining(NATIVE_DLL_SEARCH_DIRECTORIES, RelativePathsToAbsoluteAppPaths(path, app));
}

public static AndConstraint<CommandResultAssertions> HaveResolvedResourceRootPath(this CommandResultAssertions assertion, string path, TestApp app = null)
{
return assertion.HaveRuntimePropertyContaining(PLATFORM_RESOURCE_ROOTS, RelativePathsToAbsoluteAppPaths(path, app));
}

public static AndConstraint<CommandResultAssertions> NotHaveResolvedResourceRootPath(this CommandResultAssertions assertion, string path, TestApp app = null)
{
return assertion.NotHaveRuntimePropertyContaining(PLATFORM_RESOURCE_ROOTS, RelativePathsToAbsoluteAppPaths(path, app));
}

// Component asset resolution extensions
private const string assemblies = "assemblies";
private const string native_search_paths = "native_search_paths";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
// 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.IO;
using Microsoft.DotNet.Cli.Build;
using Xunit;
using static Microsoft.DotNet.CoreSetup.Test.NetCoreAppBuilder;

namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.DependencyResolution
{
public class LocalPath : IClassFixture<LocalPath.SharedTestState>
{
private readonly SharedTestState sharedState;

public LocalPath(SharedTestState sharedState)
{
this.sharedState = sharedState;
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void RuntimeAssemblies_FrameworkDependent(bool useLocalPath) => RuntimeAssemblies(isSelfContained: false, useLocalPath);

[Theory]
[InlineData(true)]
[InlineData(false)]
public void RuntimeAssemblies_SelfContained(bool useLocalPath) => RuntimeAssemblies(isSelfContained: true, useLocalPath);

[Theory]
[InlineData(true)]
[InlineData(false)]
public void NativeLibraries_FrameworkDependent(bool useLocalPath) => NativeLibraries(isSelfContained: false, useLocalPath);

[Theory]
[InlineData(true)]
[InlineData(false)]
public void NativeLibraries_SelfContained(bool useLocalPath) => NativeLibraries(isSelfContained: true, useLocalPath);

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ResourceAssemblies_FrameworkDependent(bool useLocalPath) => ResourceAssemblies(isSelfContained: false, useLocalPath);

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ResourceAssemblies_SelfContained(bool useLocalPath) => ResourceAssemblies(isSelfContained: true, useLocalPath);

private void RuntimeAssemblies(bool isSelfContained, bool useLocalPath)
{
RuntimeLibraryType[] libraryTypes = [ RuntimeLibraryType.project, RuntimeLibraryType.package, RuntimeLibraryType.runtimepack ];

Action<NetCoreAppBuilder> customizer = b =>
{
foreach (var libraryType in libraryTypes)
{
string library = $"Test{libraryType}";
(string path, string localPath) = GetPaths(libraryType, false);
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
.WithAssemblyGroup(null, g => g
.WithAsset(path, useLocalPath ? f => f.WithLocalPath(localPath) : null)));

if (!isSelfContained)
{
// Add RID-specific assembly
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
b.WithRuntimeLibrary(libraryType, $"{library}-{TestContext.BuildRID}", "1.0.0", p => p
.WithAssemblyGroup(TestContext.BuildRID, g => g
.WithAsset(ridPath, useLocalPath ? f => f.WithLocalPath(localRidPath) : null)));
}
}

b.WithLocalPathsInDepsJson(useLocalPath);
};

using TestApp app = CreateApp(isSelfContained, customizer);
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute();
result.Should().Pass();

// Check all library types
foreach (var libraryType in libraryTypes)
{
// Check RID-agnostic assembly
(string path, string localPath) = GetPaths(libraryType, false);

// Without localPath, RID-agnostic non-runtimepack runtime assemblies are assumed to be in <app_directory>
string relativePath = useLocalPath
? localPath
: libraryType == RuntimeLibraryType.runtimepack ? path : Path.GetFileName(path);
string expectedPath = Path.Join(app.Location, relativePath);
result.Should().HaveResolvedAssembly(expectedPath);
if (useLocalPath)
{
result.Should().NotHaveResolvedAssembly(Path.Join(app.Location, path));
}

// Check RID-specific assembly
if (!isSelfContained)
{
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
string expectedRidPath = Path.Join(app.Location, useLocalPath ? localRidPath : ridPath);
result.Should().HaveResolvedAssembly(expectedRidPath);
if (useLocalPath)
{
result.Should().NotHaveResolvedAssembly(Path.Join(app.Location, ridPath));
}
}
}

static (string Path, string LocalPath) GetPaths(RuntimeLibraryType libraryType, bool useRid)
{
string library = $"Test{libraryType}";
string path = useRid ? $"lib/{TestContext.BuildRID}/{library}-{TestContext.BuildRID}.dll" : $"lib/{library}.dll";
return (path, $"{libraryType}/{path}");
}
}

private void NativeLibraries(bool isSelfContained, bool useLocalPath)
{
NetCoreAppBuilder.RuntimeLibraryType[] libraryTypes = [NetCoreAppBuilder.RuntimeLibraryType.project, NetCoreAppBuilder.RuntimeLibraryType.package, NetCoreAppBuilder.RuntimeLibraryType.runtimepack];

Action<NetCoreAppBuilder> customizer = b =>
{
foreach (var libraryType in libraryTypes)
{
string library = $"Test{libraryType}";
(string path, string localPath) = GetPaths(libraryType, false);
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
.WithNativeLibraryGroup(null, g => g
.WithAsset($"{path}/{library}.native", useLocalPath ? f => f.WithLocalPath($"{localPath}/{library}.native") : null)));

if (!isSelfContained)
{
// Add RID-specific native library
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
b.WithRuntimeLibrary(libraryType, $"{library}-{TestContext.BuildRID}", "1.0.0", p => p
.WithNativeLibraryGroup(TestContext.BuildRID, g => g
.WithAsset($"{ridPath}/{library}-{TestContext.BuildRID}.native", useLocalPath ? f => f.WithLocalPath($"{localRidPath}/{library}-{TestContext.BuildRID}.native") : null)));
}
}

b.WithLocalPathsInDepsJson(useLocalPath);
};

using TestApp app = CreateApp(isSelfContained, customizer);
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute();
result.Should().Pass();

// Check all library types
foreach (NetCoreAppBuilder.RuntimeLibraryType libraryType in libraryTypes)
{
// Check RID-agnostic native library path
(string path, string localPath) = GetPaths(libraryType, false);

// Without localPath, RID-agnostic non-runtimepack native libraries are assumed to be in <app_directory>
string relativePath = useLocalPath
? localPath
: libraryType == RuntimeLibraryType.runtimepack ? path : string.Empty;
string expectedPath = Path.Join(app.Location, relativePath);
result.Should().HaveResolvedNativeLibraryPath(expectedPath);
if (useLocalPath)
{
result.Should().NotHaveResolvedNativeLibraryPath(Path.Join(app.Location, path));
}

// Check RID-specific native library path
if (!isSelfContained)
{
(string ridPath, string localRidPath) = GetPaths(libraryType, true);
string expectedRidPath = Path.Join(app.Location, useLocalPath ? localRidPath : ridPath);
result.Should().HaveResolvedNativeLibraryPath(expectedRidPath);
if (useLocalPath)
{
result.Should().NotHaveResolvedNativeLibraryPath(Path.Join(app.Location, ridPath));
}
}
}

static (string Path, string LocalPath) GetPaths(NetCoreAppBuilder.RuntimeLibraryType libraryType, bool useRid)
{
string path = useRid ? $"native/{TestContext.BuildRID}" : "native";
return (path, $"{libraryType}/{path}");
}
}

private void ResourceAssemblies(bool isSelfContained, bool useLocalPath)
{
NetCoreAppBuilder.RuntimeLibraryType[] libraryTypes = [NetCoreAppBuilder.RuntimeLibraryType.project, NetCoreAppBuilder.RuntimeLibraryType.package, NetCoreAppBuilder.RuntimeLibraryType.runtimepack];

Action<NetCoreAppBuilder> customizer = b =>
{
foreach (var libraryType in libraryTypes)
{
string library = $"Test{libraryType}";
(string path, string localPath) = GetPaths(libraryType);
b.WithRuntimeLibrary(libraryType, library, "1.0.0", p => p
.WithResourceAssembly($"{path}/fr/{library}.resources.dll", useLocalPath ? f => f.WithLocalPath($"{localPath}/fr/{library}.resources.dll") : null));
}

b.WithLocalPathsInDepsJson(useLocalPath);
};

using TestApp app = CreateApp(isSelfContained, customizer);
var result = sharedState.DotNetWithNetCoreApp.Exec(app.AppDll)
.EnableTracingAndCaptureOutputs()
.Execute();
result.Should().Pass();

// Check all library types
foreach (var libraryType in libraryTypes)
{
(string path, string localPath) = GetPaths(libraryType);

// Without localPath, non-runtimepack resource assemblies are assumed to be in <app_directory>/<locale>/
string relativePath = useLocalPath
? localPath
: libraryType == RuntimeLibraryType.runtimepack ? path : string.Empty;
string expectedPath = Path.Join(app.Location, relativePath);
result.Should().HaveResolvedResourceRootPath(expectedPath);
if (useLocalPath)
{
result.Should().NotHaveResolvedResourceRootPath(Path.Join(app.Location, path));
}
}

static (string Path, string LocalPath) GetPaths(NetCoreAppBuilder.RuntimeLibraryType libraryType)
{
string path = $"resources";
return (path, $"{libraryType}/{path}");
}
}

private static TestApp CreateApp(bool isSelfContained, Action<NetCoreAppBuilder> customizer)
{
TestApp app = TestApp.CreateEmpty("App");
if (isSelfContained)
{
app.PopulateSelfContained(TestApp.MockedComponent.CoreClr, customizer);
}
else
{
app.PopulateFrameworkDependent(Constants.MicrosoftNETCoreApp, TestContext.MicrosoftNETCoreAppVersion, customizer);
}
return app;
}

public class SharedTestState : SharedTestStateBase
{
public DotNetCli DotNetWithNetCoreApp { get; }

public SharedTestState()
{
DotNetWithNetCoreApp = DotNet("WithNetCoreApp")
.AddMicrosoftNETCoreAppFrameworkMockCoreClr(TestContext.MicrosoftNETCoreAppVersion)
.Build();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ public TestApp CreateComponentWithDependencies(Action<NetCoreAppBuilder> customi
.WithPackage(AdditionalDependencyName, "2.0.1", p => p.WithAssemblyGroup(null, g => g
.WithAsset($"lib/netstandard1.0/{AdditionalDependencyName}.dll", f => f
.WithVersion("2.0.0.0", "2.0.1.23344")
.WithFileOnDiskPath($"{AdditionalDependencyName}.dll"))))
.WithLocalPath($"{AdditionalDependencyName}.dll"))))
.WithPackage("Libuv", "1.9.1", p => p
.WithNativeLibraryGroup("debian-x64", g => g.WithAsset("runtimes/debian-x64/native/libuv.so"))
.WithNativeLibraryGroup("fedora-x64", g => g.WithAsset("runtimes/fedora-x64/native/libuv.so"))
Expand Down
48 changes: 48 additions & 0 deletions src/installer/tests/HostActivation.Tests/SelfContainedAppLaunch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using FluentAssertions;
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.DotNet.CoreSetup.Test;
using Microsoft.NET.HostModel.AppHost;
using Xunit;
using static Microsoft.DotNet.CoreSetup.Test.NetCoreAppBuilder;
using static Microsoft.NET.HostModel.AppHost.HostWriter;

namespace HostActivation.Tests
{
Expand Down Expand Up @@ -180,6 +183,51 @@ public void DevicePath()
.And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion);
}

[Fact]
public void CustomRuntimeLocation()
{
string subdirectory = "runtime-directory";
Action<NetCoreAppBuilder> customizer = b =>
{
// Find the NETCoreApp runtime pack
RuntimeLibraryBuilder netCoreApp = b.RuntimeLibraries.First(
r => r.Type == RuntimeLibraryType.runtimepack.ToString() && r.Name == $"runtimepack.{Constants.MicrosoftNETCoreApp}.Runtime.{TestContext.BuildRID}");

// Update all NETCoreApp asset paths to point to the subdirectory
RuntimeAssetGroupBuilder[] groups = [.. netCoreApp.AssemblyGroups, .. netCoreApp.NativeLibraryGroups];
foreach (RuntimeAssetGroupBuilder group in groups)
{
foreach (RuntimeFileBuilder asset in group.Assets)
{
asset.Path = Path.Join(subdirectory, asset.Path);
}
}

foreach (ResourceAssemblyBuilder resource in netCoreApp.ResourceAssemblies)
{
resource.Path = Path.Join(subdirectory, resource.Path);
}
};

using TestApp app = TestApp.CreateFromBuiltAssets("HelloWorld");
app.PopulateSelfContained(TestApp.MockedComponent.None, customizer);
app.CreateAppHost(dotNetRootOptions: new()
{
Location = DotNetSearchOptions.SearchLocation.AppRelative,
AppRelativeDotNet = subdirectory
});

// Apphost should be able to find hostfxr based on the .NET search options
// Runtime files should be resolved based on relative path in .deps.json
Command.Create(app.AppExe)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.HaveStdOutContaining(TestContext.MicrosoftNETCoreAppVersion)
.And.HaveStdErrContaining($"CoreCLR path = '{Path.Join(app.Location, subdirectory, Binaries.CoreClr.FileName)}'");
}

public class SharedTestState : IDisposable
{
public TestApp App { get; }
Expand Down
4 changes: 2 additions & 2 deletions src/installer/tests/TestUtils/DotNetBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ public DotNetBuilder AddMicrosoftNETCoreAppFrameworkMockCoreClr(string version,
// ./shared/Microsoft.NETCore.App/<version>/coreclr.dll - this is a mock, will not actually run CoreClr
.WithAsset((new NetCoreAppBuilder.RuntimeFileBuilder($"runtimes/{currentRid}/native/{Binaries.CoreClr.FileName}"))
.CopyFromFile(Binaries.CoreClr.MockPath)
.WithFileOnDiskPath(Binaries.CoreClr.FileName))))
.WithLocalPath(Binaries.CoreClr.FileName))))
.WithPackage($"runtime.{currentRid}.Microsoft.NETCore.DotNetHostPolicy", version, p => p
.WithNativeLibraryGroup(null, g => g
// ./shared/Microsoft.NETCore.App/<version>/hostpolicy.dll - this is the real component and will load CoreClr library
.WithAsset((new NetCoreAppBuilder.RuntimeFileBuilder($"runtimes/{currentRid}/native/{Binaries.HostPolicy.FileName}"))
.CopyFromFile(Binaries.HostPolicy.FilePath)
.WithFileOnDiskPath(Binaries.HostPolicy.FileName))))
.WithLocalPath(Binaries.HostPolicy.FileName))))
.WithCustomizer(customizer)
.Build(new TestApp(netCoreAppPath, "Microsoft.NETCore.App"));

Expand Down
Loading
Loading