Skip to content

Commit ef0076b

Browse files
authored
Add single-file host tests for native library load (#115061)
1 parent 1d3b5d6 commit ef0076b

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using Microsoft.DotNet.Cli.Build.Framework;
8+
using Microsoft.DotNet.CoreSetup.Test;
9+
using Microsoft.NET.HostModel.Bundle;
10+
using Xunit;
11+
12+
namespace AppHost.Bundle.Tests
13+
{
14+
public class NativeLibraries : IClassFixture<NativeLibraries.SharedTestState>
15+
{
16+
private SharedTestState sharedTestState;
17+
18+
public NativeLibraries(NativeLibraries.SharedTestState fixture)
19+
{
20+
sharedTestState = fixture;
21+
}
22+
23+
[Fact]
24+
public void PInvoke_FrameworkDependent() => PInvoke(false, false);
25+
26+
[Fact]
27+
public void PInvoke_FrameworkDependent_BundleNative() => PInvoke(false, true);
28+
29+
[Fact]
30+
public void PInvoke_SelfContained() => PInvoke(true, false);
31+
32+
[Fact]
33+
public void PInvoke_SelfContained_BundleNative() => PInvoke(true, true);
34+
35+
private void PInvoke(bool selfContained, bool bundleNative)
36+
{
37+
string app = (selfContained, bundleNative) switch
38+
{
39+
(true, true) => sharedTestState.SelfContainedApp_BundleNative,
40+
(true, false) => sharedTestState.SelfContainedApp,
41+
(false, true) => sharedTestState.FrameworkDependentApp_BundleNative,
42+
(false, false) => sharedTestState.FrameworkDependentApp
43+
};
44+
45+
Command.Create(app, "load_native_library_pinvoke")
46+
.CaptureStdErr()
47+
.CaptureStdOut()
48+
.DotNetRoot(selfContained ? null : TestContext.BuiltDotNet.BinPath)
49+
.Execute()
50+
.Should().Pass()
51+
.And.CallPInvoke(null, true)
52+
.And.CallPInvoke(DllImportSearchPath.AssemblyDirectory, true)
53+
// Single-file always looks in application directory, even when only System32 is specified
54+
.And.CallPInvoke(DllImportSearchPath.System32, true);
55+
}
56+
57+
[Fact]
58+
public void TryLoad_FrameworkDependent() => TryLoad(false, false);
59+
60+
[Fact]
61+
public void TryLoad_FrameworkDependent_BundleNative() => TryLoad(false, true);
62+
63+
[Fact]
64+
public void TryLoad_SelfContained() => TryLoad(true, false);
65+
66+
[Fact]
67+
public void TryLoad_SelfContained_BundleNative() => TryLoad(true, true);
68+
69+
private void TryLoad(bool selfContained, bool bundleNative)
70+
{
71+
string app = (selfContained, bundleNative) switch
72+
{
73+
(true, true) => sharedTestState.SelfContainedApp_BundleNative,
74+
(true, false) => sharedTestState.SelfContainedApp,
75+
(false, true) => sharedTestState.FrameworkDependentApp_BundleNative,
76+
(false, false) => sharedTestState.FrameworkDependentApp
77+
};
78+
79+
Command.Create(app, "load_native_library_api")
80+
.CaptureStdErr()
81+
.CaptureStdOut()
82+
.DotNetRoot(selfContained ? null : TestContext.BuiltDotNet.BinPath)
83+
.Execute()
84+
.Should().Pass()
85+
.And.TryLoadLibrary(null, true)
86+
.And.TryLoadLibrary(DllImportSearchPath.AssemblyDirectory, true)
87+
// Single-file always looks in application directory, even when only System32 is specified
88+
.And.TryLoadLibrary(DllImportSearchPath.System32, true);
89+
}
90+
91+
internal static string GetLibraryName(DllImportSearchPath? flags)
92+
{
93+
string name = Path.GetFileNameWithoutExtension(Binaries.HostPolicy.MockName);
94+
return flags switch
95+
{
96+
DllImportSearchPath.AssemblyDirectory => $"{name}-{nameof(DllImportSearchPath.AssemblyDirectory)}",
97+
DllImportSearchPath.System32 => $"{name}-{nameof(DllImportSearchPath.System32)}",
98+
_ => name
99+
};
100+
}
101+
102+
public class SharedTestState : IDisposable
103+
{
104+
public string FrameworkDependentApp { get; }
105+
public string SelfContainedApp { get; }
106+
107+
public string FrameworkDependentApp_BundleNative { get; }
108+
public string SelfContainedApp_BundleNative { get; }
109+
110+
private SingleFileTestApp _frameworkDependentApp;
111+
private SingleFileTestApp _selfContainedApp;
112+
113+
public SharedTestState()
114+
{
115+
_frameworkDependentApp = SingleFileTestApp.CreateFrameworkDependent("HelloWorld");
116+
_selfContainedApp = SingleFileTestApp.CreateSelfContained("HelloWorld");
117+
118+
// Copy over mockhostpolicy - the app will try to load this
119+
string[] names = [GetLibraryName(null), GetLibraryName(DllImportSearchPath.AssemblyDirectory), GetLibraryName(DllImportSearchPath.System32)];
120+
foreach (string name in names)
121+
{
122+
string fileName = $"{name}{Path.GetExtension(Binaries.HostPolicy.MockName)}";
123+
File.Copy(Binaries.HostPolicy.MockPath, Path.Combine(_frameworkDependentApp.NonBundledLocation, fileName));
124+
File.Copy(Binaries.HostPolicy.MockPath, Path.Combine(_selfContainedApp.NonBundledLocation, fileName));
125+
}
126+
127+
FrameworkDependentApp = _frameworkDependentApp.Bundle();
128+
SelfContainedApp = _selfContainedApp.Bundle();
129+
130+
FrameworkDependentApp_BundleNative = _frameworkDependentApp.Bundle(BundleOptions.BundleNativeBinaries);
131+
SelfContainedApp_BundleNative = _selfContainedApp.Bundle(BundleOptions.BundleNativeBinaries);
132+
}
133+
134+
public void Dispose()
135+
{
136+
_frameworkDependentApp.Dispose();
137+
_selfContainedApp.Dispose();
138+
}
139+
}
140+
}
141+
142+
public static class NativeLibrariesResultExtensions
143+
{
144+
public static FluentAssertions.AndConstraint<CommandResultAssertions> CallPInvoke(this CommandResultAssertions assertion, DllImportSearchPath? flags, bool success)
145+
{
146+
var constraint = assertion.HaveStdOutContaining($"Loading {NativeLibraries.GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) {(success ? "succeeded" : "failed")}");
147+
if (!success)
148+
constraint = constraint.And.HaveStdOutContaining(typeof(DllNotFoundException).FullName);
149+
150+
return constraint;
151+
}
152+
153+
public static FluentAssertions.AndConstraint<CommandResultAssertions> TryLoadLibrary(this CommandResultAssertions assertion, DllImportSearchPath? flags, bool success)
154+
{
155+
return assertion.HaveStdOutContaining($"Loading {NativeLibraries.GetLibraryName(flags)} via NativeLibrary API (flags: {(flags.HasValue ? flags : "default")}) {(success ? "succeeded" : "failed")}");
156+
}
157+
}
158+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace HelloWorld
8+
{
9+
public static class LoadNativeLibrary
10+
{
11+
private const string LibraryName = "mockhostpolicy";
12+
private const string LibraryName_AssemblyDirectory = $"{LibraryName}-{nameof(DllImportSearchPath.AssemblyDirectory)}";
13+
private const string LibraryName_System32 = $"{LibraryName}-{nameof(DllImportSearchPath.System32)}";
14+
15+
public static void PInvoke(DllImportSearchPath? flags)
16+
{
17+
try
18+
{
19+
switch (flags)
20+
{
21+
case DllImportSearchPath.AssemblyDirectory:
22+
corehost_unload_assembly_directory();
23+
break;
24+
case DllImportSearchPath.System32:
25+
corehost_unload_system32();
26+
break;
27+
default:
28+
corehost_unload();
29+
break;
30+
}
31+
32+
Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) succeeded");
33+
}
34+
catch (DllNotFoundException e)
35+
{
36+
Console.WriteLine($"Loading {GetLibraryName(flags)} via P/Invoke (flags: {(flags.HasValue ? flags : "default")}) failed: {e}");
37+
}
38+
}
39+
40+
public static void UseAPI(DllImportSearchPath? flags)
41+
{
42+
string name = GetLibraryName(flags);
43+
bool success = NativeLibrary.TryLoad(name, typeof(LoadNativeLibrary).Assembly, null, out _);
44+
Console.WriteLine($"Loading {name} via NativeLibrary API (flags: {(flags.HasValue ? flags : "default")}) {(success ? "succeeded" : "failed")}");
45+
}
46+
47+
private static string GetLibraryName(DllImportSearchPath? flags)
48+
{
49+
string name = flags switch
50+
{
51+
DllImportSearchPath.AssemblyDirectory => LibraryName_AssemblyDirectory,
52+
DllImportSearchPath.System32 => LibraryName_System32,
53+
_ => LibraryName
54+
};
55+
return OperatingSystem.IsWindows() ? name : $"lib{name}";
56+
}
57+
58+
[DllImport(LibraryName)]
59+
private static extern int corehost_unload();
60+
61+
[DllImport(LibraryName_AssemblyDirectory, EntryPoint = nameof(corehost_unload))]
62+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory)]
63+
private static extern int corehost_unload_assembly_directory();
64+
65+
[DllImport(LibraryName_System32, EntryPoint = nameof(corehost_unload))]
66+
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
67+
private static extern int corehost_unload_system32();
68+
}
69+
}

src/installer/tests/Assets/Projects/HelloWorld/Program.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ public static void Main(string[] args)
2121

2222
switch (args[0])
2323
{
24+
case "load_native_library_pinvoke":
25+
LoadNativeLibrary.PInvoke(null);
26+
LoadNativeLibrary.PInvoke(DllImportSearchPath.AssemblyDirectory);
27+
LoadNativeLibrary.PInvoke(DllImportSearchPath.System32);
28+
break;
29+
case "load_native_library_api":
30+
LoadNativeLibrary.UseAPI(null);
31+
LoadNativeLibrary.UseAPI(DllImportSearchPath.AssemblyDirectory);
32+
LoadNativeLibrary.UseAPI(DllImportSearchPath.System32);
33+
break;
2434
case "load_shared_library":
2535
var asm = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("SharedLibrary"));
2636
PropertyInfo property = asm.GetType("SharedLibrary.SharedType").GetProperty("Value");

0 commit comments

Comments
 (0)