Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 1 addition & 24 deletions src/installer/tests/Assets/Projects/HostApiInvokerApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,7 @@ public static void MainCore(string[] args)
foreach (string arg in args)
Console.WriteLine($" {arg}");

// If requested, test multilevel lookup using fake Global SDK directories:
// 1. using a fake ProgramFiles location
// 2. using a fake SDK Self-Registered location
// Note that this has to be set here and not in the calling test process because
// %ProgramFiles% gets reset on process creation.
string testMultilevelLookupProgramFiles = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES");
string testMultilevelLookupSelfRegistered = Environment.GetEnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED");

string hostfxrPath;
if (testMultilevelLookupProgramFiles != null && testMultilevelLookupSelfRegistered != null)
{
Environment.SetEnvironmentVariable("_DOTNET_TEST_GLOBALLY_REGISTERED_PATH", testMultilevelLookupSelfRegistered);
Environment.SetEnvironmentVariable("ProgramFiles", testMultilevelLookupProgramFiles);
Environment.SetEnvironmentVariable("ProgramFiles(x86)", testMultilevelLookupProgramFiles);
Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "1");
hostfxrPath = AppContext.GetData("HOSTFXR_PATH_TEST_BEHAVIOR") as string;
}
else
{
// never rely on machine state in test if we're not faking the multi-level lookup
Environment.SetEnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0");
hostfxrPath = AppContext.GetData("HOSTFXR_PATH") as string;
}

string hostfxrPath = AppContext.GetData("HOSTFXR_PATH") as string;
if (hostfxrPath is not null)
{
Console.WriteLine($"Registering DLLImportResolver for {nameof(HostFXR.hostfxr)} -> {hostfxrPath}");
Expand Down
168 changes: 26 additions & 142 deletions src/installer/tests/HostActivation.Tests/NativeHostApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal sealed class SdkAndFrameworkFixture : IDisposable
// Versions are assumed to be in ascending order. We use these properties to check against the
// expected values passed to hostfxr API callbacks in the expected order.
public string ExeDir => Path.Combine(_artifact.Location, "ed");
public string LocalSdkDir => Path.Combine(ExeDir, "sdk");
public string LocalSdkDir { get; }
public string LocalFrameworksDir => Path.Combine(ExeDir, "shared");
public string[] LocalSdks = new[] { "0.1.200", "1.2.300", "5.6.701-preview" };
public IEnumerable<string> LocalSdkPaths => LocalSdks.Select(sdk => Path.Combine(LocalSdkDir, sdk));
Expand All @@ -50,46 +50,15 @@ internal sealed class SdkAndFrameworkFixture : IDisposable
("HostFxr.Test.C", new[] { "3.0.0" })
};

public string ProgramFiles => Path.Combine(_artifact.Location, "pf");
public string ProgramFilesGlobalSdkDir => Path.Combine(ProgramFiles, "dotnet", "sdk");
public string ProgramFilesGlobalFrameworksDir => Path.Combine(ProgramFiles, "dotnet", "shared");
public string[] ProgramFilesGlobalSdks = new[] { "1.2.300", "2.3.400-preview", "4.5.600" };
public List<(string fwName, string[] fwVersions)> ProgramFilesGlobalFrameworks =
new List<(string fwName, string[] fwVersions)>()
{
("HostFxr.Test.A", new[] { "1.2.3", "3.0.0" }),
("HostFxr.Test.B", new[] { "5.6.7-A" })
};

public string SelfRegistered => Path.Combine(_artifact.Location, "sr");
public string SelfRegisteredGlobalSdkDir => Path.Combine(SelfRegistered, "sdk");
public string[] SelfRegisteredGlobalSdks = new[] { "3.0.100", "5.6.700", "15.1.400-preview" };

public SdkAndFrameworkFixture()
{
_artifact = TestArtifact.Create(nameof(SdkAndFrameworkFixture));

foreach (string sdk in ProgramFilesGlobalSdks)
{
AddSdkDirectory(ProgramFilesGlobalSdkDir, sdk);
}
foreach (string sdk in SelfRegisteredGlobalSdks)
{
AddSdkDirectory(SelfRegisteredGlobalSdkDir, sdk);
}
foreach (string sdk in LocalSdks)
{
AddSdkDirectory(LocalSdkDir, sdk);
}
LocalSdkDir = CreateSdkDirectories(ExeDir, LocalSdks);

// Empty SDK directory - this should not be recognized as a valid SDK directory
Directory.CreateDirectory(Path.Combine(LocalSdkDir, "9.9.9"));

foreach ((string fwName, string[] fwVersions) in ProgramFilesGlobalFrameworks)
{
foreach (string fwVersion in fwVersions)
AddFrameworkDirectory(ProgramFilesGlobalFrameworksDir, fwName, fwVersion);
}
foreach ((string fwName, string[] fwVersions) in LocalFrameworks)
{
foreach (string fwVersion in fwVersions)
Expand All @@ -99,18 +68,29 @@ public SdkAndFrameworkFixture()
Directory.CreateDirectory(Path.Combine(LocalFrameworksDir, fwName, "9.9.9"));
}

static void AddSdkDirectory(string sdkDir, string version)
static void AddFrameworkDirectory(string frameworkDir, string name, string version)
{
string versionDir = Path.Combine(sdkDir, version);
string versionDir = Path.Combine(frameworkDir, name, version);
Directory.CreateDirectory(versionDir);
File.WriteAllText(Path.Combine(versionDir, "dotnet.dll"), string.Empty);
File.WriteAllText(Path.Combine(versionDir, $"{name}.deps.json"), string.Empty);
}
}

static void AddFrameworkDirectory(string frameworkDir, string name, string version)
public static string CreateSdkDirectories(string root, string[] versions)
{
string sdkDirectory = Path.Combine(root, "sdk");
foreach (string sdk in versions)
{
string versionDir = Path.Combine(frameworkDir, name, version);
AddSdkDirectory(sdkDirectory, sdk);
}

return sdkDirectory;

static void AddSdkDirectory(string sdkDir, string version)
{
string versionDir = Path.Combine(sdkDir, version);
Directory.CreateDirectory(versionDir);
File.WriteAllText(Path.Combine(versionDir, $"{name}.deps.json"), string.Empty);
File.WriteAllText(Path.Combine(versionDir, "dotnet.dll"), string.Empty);
}
}

Expand All @@ -120,26 +100,6 @@ public void Dispose()
}
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway)
public void Hostfxr_get_available_sdks_with_multilevel_lookup()
{
// Starting with .NET 7, multi-level lookup is completely disabled for hostfxr API calls.
// This test is still valuable to validate that it is in fact disabled
var f = sharedTestState.SdkAndFrameworkFixture;
string expectedList = string.Join(';', f.LocalSdkPaths);

string api = ApiNames.hostfxr_get_available_sdks;
sharedTestState.TestBehaviorEnabledDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, f.ExeDir)
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles)
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED", f.SelfRegistered)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.ReturnStatusCode(api, Constants.ErrorCode.Success)
.And.HaveStdOutContaining($"{api} sdks:[{expectedList}]");
}

[Fact]
public void Hostfxr_get_available_sdks()
{
Expand All @@ -165,7 +125,7 @@ public void Hostfxr_resolve_sdk2_NoGlobalJson()
var f = sharedTestState.SdkAndFrameworkFixture;
string expectedData = string.Join(';', new[]
{
("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, "5.6.701-preview")),
("resolved_sdk_dir", Path.Combine(f.LocalSdkDir, f.LocalSdks[^1])),
("global_json_state", "not_found"),
});

Expand Down Expand Up @@ -238,10 +198,14 @@ public void Hostfxr_resolve_sdk2_GlobalJson_Paths()
var f = sharedTestState.SdkAndFrameworkFixture;
using (TestArtifact workingDir = TestArtifact.Create(nameof(workingDir)))
{
string globalJson = GlobalJson.Write(workingDir.Location, new GlobalJson.Sdk() { Paths = [ f.SelfRegistered, f.LocalSdkDir ] });
string customSdkRoot = workingDir.GetUniqueSubdirectory("custom_paths");
string[] versions = [ "3.0.100", "5.6.700", "15.1.400-preview" ];
string customSdkDirectory = SdkAndFrameworkFixture.CreateSdkDirectories(customSdkRoot, versions);

string globalJson = GlobalJson.Write(workingDir.Location, new GlobalJson.Sdk() { Paths = [ customSdkRoot, f.LocalSdkDir ] });
string expectedData = string.Join(';', new[]
{
("resolved_sdk_dir", Path.Combine(f.SelfRegisteredGlobalSdkDir, "15.1.400-preview")),
("resolved_sdk_dir", Path.Combine(customSdkDirectory, versions[^1])),
("global_json_path", globalJson),
("global_json_state", "valid"),
});
Expand Down Expand Up @@ -387,73 +351,6 @@ public void Hostfxr_get_dotnet_environment_info_dotnet_root_only()
.And.HaveStdOutContaining($"{api} framework paths:[{expectedFrameworkPaths}]");
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway)
public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_with_dotnet_root()
{
var f = sharedTestState.SdkAndFrameworkFixture;
string expectedSdkVersions = string.Join(';', f.LocalSdks);
string expectedSdkPaths = string.Join(';', f.LocalSdkPaths);

string expectedFrameworkNames = string.Join(';', new[]
{
"HostFxr.Test.B",
"HostFxr.Test.B",
"HostFxr.Test.C"
});

string expectedFrameworkVersions = string.Join(';', new[]
{
"4.0.0",
"5.6.7-A",
"3.0.0"
});

string expectedFrameworkPaths = string.Join(';', new[]
{
Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"),
Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.B"),
Path.Combine(f.LocalFrameworksDir, "HostFxr.Test.C")
});

string api = ApiNames.hostfxr_get_dotnet_environment_info;
sharedTestState.TestBehaviorEnabledDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, new[] { api, f.ExeDir })
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles)
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED", f.SelfRegistered)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.ReturnStatusCode(api, Constants.ErrorCode.Success)
.And.HaveStdOutContaining($"{api} sdk versions:[{expectedSdkVersions}]")
.And.HaveStdOutContaining($"{api} sdk paths:[{expectedSdkPaths}]")
.And.HaveStdOutContaining($"{api} framework names:[{expectedFrameworkNames}]")
.And.HaveStdOutContaining($"{api} framework versions:[{expectedFrameworkVersions}]")
.And.HaveStdOutContaining($"{api} framework paths:[{expectedFrameworkPaths}]");
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)] // The test setup only works on Windows (and MLL was Windows-only anyway)
public void Hostfxr_get_dotnet_environment_info_with_multilevel_lookup_only()
{
var f = sharedTestState.SdkAndFrameworkFixture;

// Multi-level lookup is completely disabled on 7+
// The test runs the API with the dotnet root directory set to a location which doesn't have any SDKs or frameworks
string api = ApiNames.hostfxr_get_dotnet_environment_info;
sharedTestState.TestBehaviorEnabledDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, sharedTestState.HostApiInvokerApp.Location)
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_PROGRAM_FILES", f.ProgramFiles)
.EnvironmentVariable("TEST_MULTILEVEL_LOOKUP_SELF_REGISTERED", f.SelfRegistered)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.ReturnStatusCode(api, Constants.ErrorCode.Success)
.And.HaveStdOutContaining($"{api} sdk versions:[]")
.And.HaveStdOutContaining($"{api} sdk paths:[]")
.And.HaveStdOutContaining($"{api} framework names:[]")
.And.HaveStdOutContaining($"{api} framework versions:[]")
.And.HaveStdOutContaining($"{api} framework paths:[]");
}

[Fact]
public void Hostfxr_get_dotnet_environment_info_global_install_path()
{
Expand Down Expand Up @@ -873,28 +770,16 @@ public class SharedTestState : IDisposable
{
public TestApp HostApiInvokerApp { get; }

public DotNetCli TestBehaviorEnabledDotNet { get; }
private readonly TestArtifact copiedDotnet;

internal SdkAndFrameworkFixture SdkAndFrameworkFixture { get; }

public SharedTestState()
{
// Make a copy of the built .NET, as we will enable test-only behaviour
copiedDotnet = TestArtifact.CreateFromCopy(nameof(NativeHostApis), TestContext.BuiltDotNet.BinPath);
TestBehaviorEnabledDotNet = new DotNetCli(copiedDotnet.Location);

// Enable test-only behavior for the copied .NET. We don't bother disabling the behaviour later,
// as we just delete the entire copy after the tests run.
_ = TestOnlyProductBehavior.Enable(TestBehaviorEnabledDotNet.GreatestVersionHostFxrFilePath);

HostApiInvokerApp = TestApp.CreateFromBuiltAssets("HostApiInvokerApp");

// On non-Windows, we can't just P/Invoke to already loaded hostfxr, so provide the app with
// paths to hostfxr so that it can handle resolving the library.
RuntimeConfig.FromFile(HostApiInvokerApp.RuntimeConfigJson)
.WithProperty("HOSTFXR_PATH", TestContext.BuiltDotNet.GreatestVersionHostFxrFilePath)
.WithProperty("HOSTFXR_PATH_TEST_BEHAVIOR", TestBehaviorEnabledDotNet.GreatestVersionHostFxrFilePath)
.Save();

SdkAndFrameworkFixture = new SdkAndFrameworkFixture();
Expand All @@ -903,7 +788,6 @@ public SharedTestState()
public void Dispose()
{
HostApiInvokerApp?.Dispose();
copiedDotnet.Dispose();
SdkAndFrameworkFixture.Dispose();
}
}
Expand Down