Skip to content

Commit fa54ce8

Browse files
authored
Permissive handling for runtimeconfig.json for ijwhost (#92037)
* Permissive handling for runtimeconfig.json * take 2: avoid changing hostfxr.dll * Fix tests * Make permissive handling optional. Opt ComHost out and IjwHost in. * Update code to use the newly introduced param * Updated tests * Fix test * Roll back change that is not required * Delete runtimeconfig.json This ensures consistent start point for each test * Refactor test according to review comments * Change tests according to review comments
1 parent a2d0a12 commit fa54ce8

File tree

5 files changed

+124
-44
lines changed

5 files changed

+124
-44
lines changed

src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,66 @@ public Ijwhost(SharedTestState sharedTestState)
2222
sharedState = sharedTestState;
2323
}
2424

25-
[Fact]
26-
public void LoadLibrary()
25+
[Theory]
26+
[InlineData(true)]
27+
[InlineData(false)]
28+
public void LoadLibrary(bool no_runtimeconfig)
2729
{
28-
string [] args = {
29-
"ijwhost",
30-
sharedState.IjwLibraryPath,
31-
"NativeEntryPoint"
32-
};
33-
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
34-
.Execute();
30+
// make a copy of a portion of the shared state because we will modify it
31+
using (var app = sharedState.IjwApp.Copy())
32+
{
33+
string [] args = {
34+
"ijwhost",
35+
app.AppDll,
36+
"NativeEntryPoint"
37+
};
38+
if (no_runtimeconfig)
39+
{
40+
File.Delete(app.RuntimeConfigJson);
41+
}
42+
43+
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
44+
.Execute();
45+
46+
if (no_runtimeconfig)
47+
{
48+
result.Should().Fail()
49+
.And.HaveStdErrContaining($"Expected active runtime context because runtimeconfig.json [{app.RuntimeConfigJson}] does not exist.");
50+
}
51+
else
52+
{
53+
result.Should().Pass()
54+
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
55+
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
56+
}
57+
}
58+
}
3559

36-
result.Should().Pass()
37-
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
38-
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
60+
[Fact]
61+
public void LoadLibraryWithoutRuntimeConfigButActiveRuntime()
62+
{
63+
// make a copy of a portion of the shared state because we will modify it
64+
using (var app = sharedState.IjwApp.Copy())
65+
{
66+
// construct runtimeconfig.json
67+
var startupConfigPath = Path.Combine(Path.GetDirectoryName(app.RuntimeConfigJson),"host.runtimeconfig.json");
68+
string [] args = {
69+
"ijwhost",
70+
app.AppDll,
71+
"NativeEntryPoint",
72+
sharedState.HostFxrPath, // optional 4th and 5th arguments that tell nativehost to start the runtime before loading the C++/CLI library
73+
startupConfigPath
74+
};
75+
76+
File.Move(app.RuntimeConfigJson, startupConfigPath);
77+
78+
CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.RepoDirectories.BuiltDotnet)
79+
.Execute();
80+
81+
result.Should().Pass()
82+
.And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class")
83+
.And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext");
84+
}
3985
}
4086

4187
[Theory]
@@ -45,7 +91,7 @@ public void ManagedHost(bool selfContained)
4591
{
4692
string [] args = {
4793
"ijwhost",
48-
sharedState.IjwLibraryPath,
94+
sharedState.IjwApp.AppDll,
4995
"NativeEntryPoint"
5096
};
5197
TestProjectFixture fixture = selfContained ? sharedState.ManagedHostFixture_SelfContained : sharedState.ManagedHostFixture_FrameworkDependent;
@@ -63,24 +109,24 @@ public void ManagedHost(bool selfContained)
63109

64110
public class SharedTestState : SharedTestStateBase
65111
{
66-
public string IjwLibraryPath { get; }
67-
112+
public string HostFxrPath { get; }
68113
public TestProjectFixture ManagedHostFixture_FrameworkDependent { get; }
69114
public TestProjectFixture ManagedHostFixture_SelfContained { get; }
115+
public TestApp IjwApp {get;}
70116

71117
public SharedTestState()
72118
{
119+
var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(RepoDirectories.BuiltDotnet);
120+
HostFxrPath = dotNet.GreatestVersionHostFxrFilePath;
73121
string folder = Path.Combine(BaseDirectory, "ijw");
74-
Directory.CreateDirectory(folder);
75-
122+
IjwApp = new TestApp(folder, "ijw");
76123
// Copy over ijwhost
77124
string ijwhostName = "ijwhost.dll";
78125
File.Copy(Path.Combine(RepoDirectories.HostArtifacts, ijwhostName), Path.Combine(folder, ijwhostName));
79126

80127
// Copy over the C++/CLI test library
81128
string ijwLibraryName = "ijw.dll";
82-
IjwLibraryPath = Path.Combine(folder, ijwLibraryName);
83-
File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), IjwLibraryPath);
129+
File.Copy(Path.Combine(RepoDirectories.HostTestArtifacts, ijwLibraryName), Path.Combine(folder, ijwLibraryName));
84130

85131
// Create a runtimeconfig.json for the C++/CLI test library
86132
new RuntimeConfig(Path.Combine(folder, "ijw.runtimeconfig.json"))

src/native/corehost/comhost/comhost.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ namespace
9494
delegates.delegate_no_load_cxt = nullptr;
9595

9696
get_function_pointer_fn get_function_pointer;
97-
int status = load_fxr_and_get_delegate(
97+
int status = load_fxr_and_get_delegate(
9898
hostfxr_delegate_type::hdt_get_function_pointer,
9999
[app_path](const pal::string_t& host_path, pal::string_t* config_path_out)
100100
{
@@ -123,7 +123,8 @@ namespace
123123
*load_context = nullptr; // Default context
124124
}
125125
},
126-
reinterpret_cast<void**>(&get_function_pointer)
126+
reinterpret_cast<void**>(&get_function_pointer),
127+
false // do not ignore missing config file if there's an active context
127128
);
128129
if (status != StatusCode::Success)
129130
return status;

src/native/corehost/fxr_resolver.h

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace fxr_resolver
1818
}
1919

2020
template<typename THostPathToConfigCallback, typename TBeforeRunCallback>
21-
int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate)
21+
int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallback host_path_to_config_path, TBeforeRunCallback on_before_run, void** delegate, bool try_ignore_missing_config)
2222
{
2323
pal::dll_t fxr;
2424

@@ -67,34 +67,45 @@ int load_fxr_and_get_delegate(hostfxr_delegate_type type, THostPathToConfigCallb
6767
if (status != StatusCode::Success)
6868
return status;
6969

70-
hostfxr_initialize_parameters parameters {
71-
sizeof(hostfxr_initialize_parameters),
72-
host_path.c_str(),
73-
dotnet_root.c_str()
74-
};
75-
7670
hostfxr_set_error_writer_fn set_error_writer_fn = reinterpret_cast<hostfxr_set_error_writer_fn>(pal::get_symbol(fxr, "hostfxr_set_error_writer"));
77-
7871
{
7972
propagate_error_writer_t propagate_error_writer_to_hostfxr(set_error_writer_fn);
73+
if (!try_ignore_missing_config || pal::file_exists(config_path))
74+
{
75+
hostfxr_initialize_parameters parameters {
76+
sizeof(hostfxr_initialize_parameters),
77+
host_path.c_str(),
78+
dotnet_root.c_str()
79+
};
8080

81-
hostfxr_handle context;
82-
int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &context);
83-
if (!STATUS_CODE_SUCCEEDED(rc))
84-
return rc;
81+
hostfxr_handle context;
82+
int rc = hostfxr_initialize_for_runtime_config(config_path.c_str(), &parameters, &context);
83+
if (!STATUS_CODE_SUCCEEDED(rc))
84+
return rc;
85+
86+
on_before_run(fxr, context);
8587

86-
on_before_run(fxr, context);
88+
rc = hostfxr_get_runtime_delegate(context, type, delegate);
8789

88-
rc = hostfxr_get_runtime_delegate(context, type, delegate);
90+
int rcClose = hostfxr_close(context);
91+
if (rcClose != StatusCode::Success)
92+
{
93+
assert(false && "Failed to close host context");
94+
trace::verbose(_X("Failed to close host context: 0x%x"), rcClose);
95+
}
8996

90-
int rcClose = hostfxr_close(context);
91-
if (rcClose != StatusCode::Success)
97+
return rc;
98+
}
99+
else
92100
{
93-
assert(false && "Failed to close host context");
94-
trace::verbose(_X("Failed to close host context: 0x%x"), rcClose);
101+
// null context means use the current one, if none exists it will fail
102+
int rc = hostfxr_get_runtime_delegate(nullptr, type, delegate);
103+
if (rc == StatusCode::HostInvalidState)
104+
{
105+
trace::error(_X("Expected active runtime context because runtimeconfig.json [%s] does not exist."), config_path.c_str());
106+
}
107+
return rc;
95108
}
96-
97-
return rc;
98109
}
99110
}
100111

src/native/corehost/ijwhost/ijwhost.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m
4141
return StatusCode::Success;
4242
},
4343
[](pal::dll_t fxr, hostfxr_handle context){ },
44-
reinterpret_cast<void**>(&get_function_pointer)
44+
reinterpret_cast<void**>(&get_function_pointer),
45+
true // ignore missing config file if there's an active context
4546
);
4647
if (status != StatusCode::Success)
4748
return status;

src/native/corehost/test/nativehost/nativehost.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,19 @@ int main(const int argc, const pal::char_t *argv[])
525525
return -1;
526526
}
527527

528+
// 2 optional arguments indicating whether we should start the runtime
529+
if (argc > 5)
530+
{
531+
const pal::string_t hostfxr_path = argv[4];
532+
const pal::char_t* config_path = argv[5];
533+
pal::stringstream_t test_output;
534+
if (!host_context_test::config(host_context_test::check_properties::none, hostfxr_path, config_path, 0, nullptr, test_output))
535+
{
536+
std::cout << "Failed to start runtime from path: " << tostr(hostfxr_path).data() << std::endl;
537+
return EXIT_FAILURE;
538+
}
539+
}
540+
528541
const pal::string_t ijw_library_path = argv[2];
529542
std::vector<char> entry_point_name = tostr(argv[3]);
530543

@@ -543,8 +556,16 @@ int main(const int argc, const pal::char_t *argv[])
543556
std::cout << "Failed to find entry point: " << entry_point_name.data() << std::endl;
544557
return EXIT_FAILURE;
545558
}
546-
547-
entry_point();
559+
try
560+
{
561+
entry_point();
562+
}
563+
catch (...)
564+
{
565+
// entry_point will throw in some tests, this is expected.
566+
// We must catch this exception to ensure that the CRT does not pop a modal dialog
567+
return EXIT_FAILURE;
568+
}
548569
return EXIT_SUCCESS;
549570
}
550571
#endif

0 commit comments

Comments
 (0)