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
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;

using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.Extensions.DependencyModel;
Expand Down Expand Up @@ -325,14 +326,52 @@ public void MissingFrameworkInRuntimeConfig_Fails(bool useAppHost)
}

command.EnableTracingAndCaptureOutputs()
.MultilevelLookup(false)
.Execute()
.Should().Fail()
.And.HaveStdErrContaining($"The library '{Binaries.HostPolicy.FileName}' required to execute the application was not found")
.And.HaveStdErrContaining("Failed to run as a self-contained app")
.And.HaveStdErrContaining($"'{app.RuntimeConfigJson}' did not specify a framework");
}

[Fact]
public void MissingFrameworkName()
{
TestApp app = sharedTestState.MockApp.Copy();

// Create a runtimeconfig.json with a framework that has no name property
var framework = new RuntimeConfig.Framework(null, TestContext.MicrosoftNETCoreAppVersion);
new RuntimeConfig(app.RuntimeConfigJson)
.WithFramework(framework)
.Save();

Command.Create(app.AppExe)
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Fail()
.And.HaveStdErrContaining($"No framework name specified: {framework.ToJson().ToJsonString(new JsonSerializerOptions { WriteIndented = false })}")
.And.HaveStdErrContaining($"Invalid runtimeconfig.json [{app.RuntimeConfigJson}]");
}

[Fact]
public void MissingFrameworkVersion()
{
TestApp app = sharedTestState.MockApp.Copy();

// Create a runtimeconfig.json with a framework that has no version property
new RuntimeConfig(app.RuntimeConfigJson)
.WithFramework(Constants.MicrosoftNETCoreApp, null)
.Save();

Command.Create(app.AppExe)
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Fail()
.And.HaveStdErrContaining($"Framework '{Constants.MicrosoftNETCoreApp}' is missing a version")
.And.HaveStdErrContaining($"Invalid runtimeconfig.json [{app.RuntimeConfigJson}]");
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.DotNet.Cli.Build;
using Microsoft.DotNet.Cli.Build.Framework;
using Xunit;
Expand Down Expand Up @@ -45,14 +46,28 @@ public void SelfContainedCanHaveIncludedFrameworks()
}

[Fact]
public void IncludedFrameworkMustSpecifyName()
public void MissingName()
{
var framework = new RuntimeConfig.Framework(null, TestContext.MicrosoftNETCoreAppVersion);
RunSelfContainedTest(
new TestSettings()
.WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig
.WithIncludedFramework(null, "5.1.2")))
.WithIncludedFramework(framework)))
.Should().Fail()
.And.HaveStdErrContaining("No framework name specified.");
.And.HaveStdErrContaining($"No framework name specified: {framework.ToJson().ToJsonString(new JsonSerializerOptions { WriteIndented = false })}")
.And.HaveStdErrContaining($"Invalid runtimeconfig.json [{SharedState.SelfContainedApp.RuntimeConfigJson}]");
}

[Fact]
public void MissingVersion()
{
RunSelfContainedTest(
new TestSettings()
.WithRuntimeConfigCustomizer(runtimeConfig => runtimeConfig
.WithIncludedFramework(Constants.MicrosoftNETCoreApp, null)))
.Should().Fail()
.And.HaveStdErrContaining($"Framework '{Constants.MicrosoftNETCoreApp}' is missing a version")
.And.HaveStdErrContaining($"Invalid runtimeconfig.json [{SharedState.SelfContainedApp.RuntimeConfigJson}]");
}

[Fact]
Expand Down
32 changes: 32 additions & 0 deletions src/installer/tests/HostActivation.Tests/NativeHostApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,38 @@ public void Hostfxr_resolve_frameworks_for_runtime_config_InvalidConfig()
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void Hostfxr_resolve_frameworks_for_runtime_config_MissingVersion(bool selfContained)
{
string api = ApiNames.hostfxr_resolve_frameworks_for_runtime_config;
using (TestArtifact artifact = TestArtifact.Create(api))
{
// Create a runtimeconfig.json with a framework reference that has no version property
string configPath = Path.Combine(artifact.Location, "test.runtimeconfig.json");
RuntimeConfig config = RuntimeConfig.FromFile(configPath);
if (selfContained)
{
config.WithIncludedFramework(Constants.MicrosoftNETCoreApp, null);
}
else
{
config.WithFramework(Constants.MicrosoftNETCoreApp, null);
}

config.Save();

TestContext.BuiltDotNet.Exec(sharedTestState.HostApiInvokerApp.AppDll, api, configPath, TestContext.BuiltDotNet.BinPath)
.CaptureStdOut()
.CaptureStdErr()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining($"Framework '{Constants.MicrosoftNETCoreApp}' is missing a version")
.And.ReturnStatusCode(api, Constants.ErrorCode.InvalidConfigFile);
}
}

[Fact]
public void Hostpolicy_corehost_set_error_writer_test()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public AndConstraint<CommandResultAssertions> HaveStdOut(string expectedOutput)

public AndConstraint<CommandResultAssertions> HaveStdOutContaining(string pattern)
{
CurrentAssertionChain.ForCondition(Result.StdOut.Contains(pattern))
CurrentAssertionChain.ForCondition(!string.IsNullOrEmpty(Result.StdOut) && Result.StdOut.Contains(pattern))
.FailWith($"The command output did not contain expected result: '{pattern}'{GetDiagnosticsInfo()}");
return new AndConstraint<CommandResultAssertions>(this);
}
Expand Down Expand Up @@ -98,7 +98,7 @@ public AndConstraint<CommandResultAssertions> HaveStdErr()

public AndConstraint<CommandResultAssertions> HaveStdErrContaining(string pattern)
{
CurrentAssertionChain.ForCondition(Result.StdErr.Contains(pattern))
CurrentAssertionChain.ForCondition(!string.IsNullOrEmpty(Result.StdErr) && Result.StdErr.Contains(pattern))
.FailWith($"The command error output did not contain expected result: '{pattern}'{GetDiagnosticsInfo()}");
return new AndConstraint<CommandResultAssertions>(this);
}
Expand Down
2 changes: 1 addition & 1 deletion src/installer/tests/TestUtils/RuntimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Framework WithApplyPatches(bool? value)
return this;
}

internal JsonObject ToJson()
public JsonObject ToJson()
{
JsonObject frameworkReference = new();

Expand Down
7 changes: 6 additions & 1 deletion src/native/corehost/fxr/hostfxr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,12 @@ SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_resolve_frameworks_for_runtime_confi
const runtime_config_t::settings_t override_settings;
app->parse_runtime_config(runtime_config, _X(""), override_settings);

const runtime_config_t app_config = app->get_runtime_config();
const runtime_config_t& app_config = app->get_runtime_config();
if (!app_config.is_valid())
{
trace::error(_X("Invalid runtimeconfig.json [%s]"), app_config.get_path().c_str());
return StatusCode::InvalidConfigFile;
}

// Resolve frameworks for framework-dependent apps.
// Self-contained apps assume the framework is next to the app, so we just treat it as success.
Expand Down
65 changes: 31 additions & 34 deletions src/native/corehost/runtime_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ bool runtime_config_t::parse_opts(const json_parser_t::value_t& opts)
m_is_framework_dependent = true;

fx_reference_t fx_out;
if (!parse_framework(framework->value, fx_out))
if (!parse_framework(framework->value, /*name_and_version_only*/ false, fx_out))
{
return false;
}
Expand All @@ -201,7 +201,7 @@ bool runtime_config_t::parse_opts(const json_parser_t::value_t& opts)
{
m_is_framework_dependent = true;

if (!read_framework_array(iter->value, m_frameworks))
if (!read_framework_array(iter->value, /*name_and_version_only*/ false, m_frameworks))
{
return false;
}
Expand All @@ -216,7 +216,7 @@ bool runtime_config_t::parse_opts(const json_parser_t::value_t& opts)
return false;
}

if (!read_framework_array(includedFrameworks->value, m_included_frameworks, /*name_and_version_only*/ true))
if (!read_framework_array(includedFrameworks->value, /*name_and_version_only*/ true, m_included_frameworks))
{
return false;
}
Expand All @@ -241,35 +241,45 @@ namespace
}
}

bool runtime_config_t::parse_framework(const json_parser_t::value_t& fx_obj, fx_reference_t& fx_out, bool name_and_version_only)
bool runtime_config_t::parse_framework(const json_parser_t::value_t& fx_obj, bool name_and_version_only, fx_reference_t& fx_out)
{
if (!name_and_version_only)
{
apply_settings_to_fx_reference(m_default_settings, fx_out);
}

const auto& fx_name = fx_obj.FindMember(_X("name"));
if (fx_name != fx_obj.MemberEnd())
if (fx_name == fx_obj.MemberEnd())
{
fx_out.set_fx_name(fx_name->value.GetString());
using string_buffer_t = rapidjson::GenericStringBuffer<json_parser_t::internal_encoding_type_t>;
string_buffer_t sb;
rapidjson::Writer<string_buffer_t, json_parser_t::internal_encoding_type_t,
json_parser_t::internal_encoding_type_t> writer{sb};
fx_obj.Accept(writer);

trace::error(_X("No framework name specified: %s"), sb.GetString());
return false;
}

fx_out.set_fx_name(fx_name->value.GetString());

const auto& fx_ver = fx_obj.FindMember(_X("version"));
if (fx_ver != fx_obj.MemberEnd())
if (fx_ver == fx_obj.MemberEnd())
{
fx_out.set_fx_version(fx_ver->value.GetString());

// Release version should prefer release versions, unless the rollForwardToPrerelease is set
// in which case no preference should be applied.
if (!name_and_version_only && !fx_out.get_fx_version_number().is_prerelease() && !m_roll_forward_to_prerelease)
{
fx_out.set_prefer_release(true);
}
trace::error(_X("Framework '%s' is missing a version."), fx_out.get_fx_name().c_str());
return false;
}

fx_out.set_fx_version(fx_ver->value.GetString());

if (name_and_version_only)
{
return true;

// Release version should prefer release versions, unless the rollForwardToPrerelease is set
// in which case no preference should be applied.
if (!fx_out.get_fx_version_number().is_prerelease() && !m_roll_forward_to_prerelease)
{
fx_out.set_prefer_release(true);
}

const auto& roll_forward = fx_obj.FindMember(_X("rollForward"));
Expand Down Expand Up @@ -357,25 +367,13 @@ bool runtime_config_t::ensure_dev_config_parsed()
return true;
}

bool runtime_config_t::read_framework_array(const json_parser_t::value_t& frameworks_json, fx_reference_vector_t& frameworks_out, bool name_and_version_only)
bool runtime_config_t::read_framework_array(const json_parser_t::value_t& frameworks_json, bool name_and_version_only, fx_reference_vector_t& frameworks_out)
{
bool rc = true;

for (const auto& fx_json : frameworks_json.GetArray())
{
fx_reference_t fx_out;
rc = parse_framework(fx_json, fx_out, name_and_version_only);
if (!rc)
{
break;
}

if (fx_out.get_fx_name().length() == 0)
{
trace::verbose(_X("No framework name specified."));
rc = false;
break;
}
if (!parse_framework(fx_json, name_and_version_only, fx_out))
return false;

if (std::find_if(
frameworks_out.begin(),
Expand All @@ -384,14 +382,13 @@ bool runtime_config_t::read_framework_array(const json_parser_t::value_t& framew
!= frameworks_out.end())
{
trace::verbose(_X("Framework %s already specified."), fx_out.get_fx_name().c_str());
rc = false;
break;
return false;
}

frameworks_out.push_back(fx_out);
}

return rc;
return true;
}

bool runtime_config_t::ensure_parsed()
Expand Down
4 changes: 2 additions & 2 deletions src/native/corehost/runtime_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ class runtime_config_t
// If set to true, all versions (including pre-release) are considered even if starting from a release framework reference.
bool m_roll_forward_to_prerelease;

bool parse_framework(const json_parser_t::value_t& fx_obj, fx_reference_t& fx_out, bool name_and_version_only = false);
bool read_framework_array(const json_parser_t::value_t& frameworks, fx_reference_vector_t& frameworks_out, bool name_and_version_only = false);
bool parse_framework(const json_parser_t::value_t& fx_obj, bool name_and_version_only, fx_reference_t& fx_out);
bool read_framework_array(const json_parser_t::value_t& frameworks, bool name_and_version_only, fx_reference_vector_t& frameworks_out);

bool mark_specified_setting(specified_setting setting);
};
Expand Down
Loading