Skip to content

Commit 7d6c1ef

Browse files
agockevitek-karas
andauthored
[release/6.0] Show error dialog in single-file app when runtime is missing (#60045)
* Show dialog when running .NET 6 GUI single-file on 5.0 hostfxr (#59929) Show dialog when running .NET 6 GUI single-file on 5.0 hostfxr (cherry picked from commit 2f76c08) * Fixes based on feedback for the original #59929 (#59967) Co-authored-by: Elinor Fung <elfung@microsoft.com> (cherry picked from commit 3d171f9) Co-authored-by: Vitek Karas <vitek.karas@microsoft.com>
1 parent c41a6cd commit 7d6c1ef

File tree

20 files changed

+200
-82
lines changed

20 files changed

+200
-82
lines changed

src/installer/tests/HostActivation.Tests/FrameworkResolution/DotNetCliExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public DotNetFramework RuntimeConfig(Action<RuntimeConfig> runtimeConfigCustomiz
7777
{
7878
string runtimeConfigPath = Path.Combine(_path, _name + ".runtimeconfig.json");
7979
_backup.Backup(runtimeConfigPath);
80-
RuntimeConfig runtimeConfig = HostActivation.RuntimeConfig.FromFile(runtimeConfigPath);
80+
RuntimeConfig runtimeConfig = Test.RuntimeConfig.FromFile(runtimeConfigPath);
8181
runtimeConfigCustomizer(runtimeConfig);
8282
runtimeConfig.Save();
8383

src/installer/tests/HostActivation.Tests/HostActivation.Tests.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<AssemblyName>HostActivation.Tests</AssemblyName>
66
<PackageId>HostActivation.Tests</PackageId>
77
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
8-
<DefineConstants Condition="'$(TargetOS)' == 'windows'">$(DefineConstants);WINDOWS</DefineConstants>
98
<!-- Reduce the length of the test output dir to make it more reliable on Windows. -->
109
<TestsOutputName>ha</TestsOutputName>
1110
<UsesTestAssets>true</UsesTestAssets>
@@ -17,7 +16,6 @@
1716
</ItemGroup>
1817

1918
<ItemGroup>
20-
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelVersion)" />
2119
<PackageReference Include="Microsoft.Win32.Registry" Version="4.3.0" />
2220
</ItemGroup>
2321

src/installer/tests/HostActivation.Tests/PortableAppActivation.cs

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ public void AppHost_GUI_FrameworkDependent_MissingRuntimeFramework_ErrorReported
526526
.MultilevelLookup(false)
527527
.Start();
528528

529-
WaitForPopupFromProcess(command.Process);
529+
WindowsUtils.WaitForPopupFromProcess(command.Process);
530530
command.Process.Kill();
531531

532532
var result = command.WaitForExit(true)
@@ -559,7 +559,7 @@ public void AppHost_GUI_MissingRuntime_ErrorReportedInDialog()
559559
.MultilevelLookup(false)
560560
.Start();
561561

562-
WaitForPopupFromProcess(command.Process);
562+
WindowsUtils.WaitForPopupFromProcess(command.Process);
563563
command.Process.Kill();
564564

565565
var expectedErrorCode = Constants.ErrorCode.CoreHostLibMissingFailure.ToString("x");
@@ -590,7 +590,7 @@ public void AppHost_GUI_NoCustomErrorWriter_FrameworkMissing_ErrorReportedInDial
590590
Directory.CreateDirectory(dotnetWithMockHostFxr);
591591
string expectedErrorCode = Constants.ErrorCode.FrameworkMissingFailure.ToString("x");
592592

593-
var dotnetBuilder = new DotNetBuilder(dotnetWithMockHostFxr, sharedTestState.RepoDirectories.BuiltDotnet, "hostfxrFrameworkMissingFailure")
593+
var dotnetBuilder = new DotNetBuilder(dotnetWithMockHostFxr, sharedTestState.RepoDirectories.BuiltDotnet, "mockhostfxrFrameworkMissingFailure")
594594
.RemoveHostFxr()
595595
.AddMockHostFxr(new Version(2, 2, 0));
596596
var dotnet = dotnetBuilder.Build();
@@ -601,7 +601,7 @@ public void AppHost_GUI_NoCustomErrorWriter_FrameworkMissing_ErrorReportedInDial
601601
.MultilevelLookup(false)
602602
.Start();
603603

604-
WaitForPopupFromProcess(command.Process);
604+
WindowsUtils.WaitForPopupFromProcess(command.Process);
605605
command.Process.Kill();
606606

607607
command.WaitForExit(true)
@@ -689,50 +689,6 @@ private string CreateAStore(TestProjectFixture testProjectFixture)
689689
return storeoutputDirectory;
690690
}
691691

692-
#if WINDOWS
693-
private delegate bool EnumThreadWindowsDelegate(IntPtr hWnd, IntPtr lParam);
694-
695-
[DllImport("user32.dll")]
696-
private static extern bool EnumThreadWindows(int dwThreadId, EnumThreadWindowsDelegate plfn, IntPtr lParam);
697-
698-
private IntPtr WaitForPopupFromProcess(Process process, int timeout = 60000)
699-
{
700-
IntPtr windowHandle = IntPtr.Zero;
701-
int timeRemaining = timeout;
702-
while (timeRemaining > 0)
703-
{
704-
foreach (ProcessThread thread in process.Threads)
705-
{
706-
// We take the last window we find. There really should only be one at most anyways.
707-
EnumThreadWindows(thread.Id,
708-
(hWnd, lParam) => {
709-
windowHandle = hWnd;
710-
return true;
711-
},
712-
IntPtr.Zero);
713-
}
714-
715-
if (windowHandle != IntPtr.Zero)
716-
{
717-
break;
718-
}
719-
720-
System.Threading.Thread.Sleep(100);
721-
timeRemaining -= 100;
722-
}
723-
724-
// Do not fail if the window could be detected, sometimes the check is fragile and doesn't work.
725-
// Not worth the trouble trying to figure out why (only happens rarely in the CI system).
726-
// We will rely on product tracing in the failure case.
727-
return windowHandle;
728-
}
729-
#else
730-
private IntPtr WaitForPopupFromProcess(Process process, int timeout = 60000)
731-
{
732-
throw new PlatformNotSupportedException();
733-
}
734-
#endif
735-
736692
public class SharedTestState : IDisposable
737693
{
738694
public TestProjectFixture PortableAppFixture_Built { get; }

src/installer/tests/Microsoft.NET.HostModel.Tests/AppHost.Bundle.Tests/BundledAppWithSubDirs.cs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.IO;
56
using BundleTests.Helpers;
67
using Microsoft.DotNet.Cli.Build.Framework;
78
using Microsoft.DotNet.CoreSetup.Test;
@@ -22,9 +23,7 @@ public BundledAppWithSubDirs(SharedTestState fixture)
2223
private void RunTheApp(string path, TestProjectFixture fixture)
2324
{
2425
Command.Create(path)
25-
.EnvironmentVariable("COREHOST_TRACE", "1")
26-
.CaptureStdErr()
27-
.CaptureStdOut()
26+
.EnableTracingAndCaptureOutputs()
2827
.EnvironmentVariable("DOTNET_ROOT", fixture.BuiltDotnet.BinPath)
2928
.EnvironmentVariable("DOTNET_ROOT(x86)", fixture.BuiltDotnet.BinPath)
3029
.EnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "0")
@@ -52,6 +51,47 @@ public void Bundled_Framework_dependent_App_Run_Succeeds(BundleOptions options)
5251
RunTheApp(singleFile, fixture);
5352
}
5453

54+
[InlineData(BundleOptions.None)]
55+
[InlineData(BundleOptions.BundleNativeBinaries)]
56+
[InlineData(BundleOptions.BundleAllContent)]
57+
[Theory]
58+
[PlatformSpecific(TestPlatforms.Windows)] // GUI app host is only supported on Windows.
59+
public void Bundled_Framework_dependent_App_GUI_DownlevelHostFxr_ErrorDialog(BundleOptions options)
60+
{
61+
var fixture = sharedTestState.TestFrameworkDependentFixture.Copy();
62+
UseFrameworkDependentHost(fixture);
63+
var singleFile = BundleHelper.BundleApp(fixture, options);
64+
AppHostExtensions.SetWindowsGraphicalUserInterfaceBit(singleFile);
65+
66+
string dotnetWithMockHostFxr = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "bundleErrors"));
67+
using (new TestArtifact(dotnetWithMockHostFxr))
68+
{
69+
Directory.CreateDirectory(dotnetWithMockHostFxr);
70+
string expectedErrorCode = Constants.ErrorCode.BundleExtractionFailure.ToString("x");
71+
72+
var dotnetBuilder = new DotNetBuilder(dotnetWithMockHostFxr, sharedTestState.RepoDirectories.BuiltDotnet, "mockhostfxrBundleVersionFailure")
73+
.RemoveHostFxr()
74+
.AddMockHostFxr(new Version(5, 0, 0));
75+
var dotnet = dotnetBuilder.Build();
76+
77+
Command command = Command.Create(singleFile)
78+
.EnableTracingAndCaptureOutputs()
79+
.DotNetRoot(dotnet.BinPath, sharedTestState.RepoDirectories.BuildArchitecture)
80+
.MultilevelLookup(false)
81+
.Start();
82+
83+
WindowsUtils.WaitForPopupFromProcess(command.Process);
84+
command.Process.Kill();
85+
86+
command
87+
.WaitForExit(true)
88+
.Should().Fail()
89+
.And.HaveStdErrContaining("Bundle header version compatibility check failed.")
90+
.And.HaveStdErrContaining($"Showing error dialog for application: '{Path.GetFileName(singleFile)}' - error code: 0x{expectedErrorCode}")
91+
.And.HaveStdErrContaining("apphost_version=");
92+
}
93+
}
94+
5595
[InlineData(BundleOptions.None)]
5696
[InlineData(BundleOptions.BundleNativeBinaries)]
5797
[InlineData(BundleOptions.BundleAllContent)]

src/installer/tests/HostActivation.Tests/CommandExtensions.cs renamed to src/installer/tests/TestUtils/CommandExtensions.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using Microsoft.DotNet.Cli.Build.Framework;
5+
using Microsoft.DotNet.CoreSetup.Test;
56
using System;
67
using System.IO;
78

8-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
9+
namespace Microsoft.DotNet.CoreSetup.Test
910
{
1011
public static class CommandExtensions
1112
{
@@ -38,11 +39,11 @@ public static Command EnableTracingAndCaptureOutputs(this Command command)
3839
public static Command DotNetRoot(this Command command, string dotNetRoot, string architecture = null)
3940
{
4041
if (!string.IsNullOrEmpty(architecture))
41-
return command.EnvironmentVariable($"DOTNET_ROOT_{architecture.ToUpper()}", dotNetRoot);
42+
return command.EnvironmentVariable(Constants.DotnetRoot.ArchitectureEnvironmentVariablePrefix + architecture.ToUpper(), dotNetRoot);
4243

4344
return command
44-
.EnvironmentVariable("DOTNET_ROOT", dotNetRoot)
45-
.EnvironmentVariable("DOTNET_ROOT(x86)", dotNetRoot);
45+
.EnvironmentVariable(Constants.DotnetRoot.EnvironmentVariable, dotNetRoot)
46+
.EnvironmentVariable(Constants.DotnetRoot.WindowsX86EnvironmentVariable, dotNetRoot);
4647
}
4748

4849
public static Command MultilevelLookup(this Command command, bool enable)

src/installer/tests/HostActivation.Tests/Constants.cs renamed to src/installer/tests/TestUtils/Constants.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
4+
namespace Microsoft.DotNet.CoreSetup.Test
55
{
6-
internal static class Constants
6+
public static class Constants
77
{
88
public const string MicrosoftNETCoreApp = "Microsoft.NETCore.App";
99

@@ -72,6 +72,13 @@ public static class HostTracing
7272
public const string TraceFileEnvironmentVariable = "COREHOST_TRACEFILE";
7373
}
7474

75+
public static class DotnetRoot
76+
{
77+
public const string EnvironmentVariable = "DOTNET_ROOT";
78+
public const string WindowsX86EnvironmentVariable = "DOTNET_ROOT(x86)";
79+
public const string ArchitectureEnvironmentVariablePrefix = "DOTNET_ROOT_";
80+
}
81+
7582
public static class ErrorCode
7683
{
7784
public const int InvalidArgFailure = unchecked((int)0x80008081);
@@ -81,6 +88,7 @@ public static class ErrorCode
8188
public const int LibHostInvalidArgs = unchecked((int)0x80008092);
8289
public const int AppArgNotRunnable = unchecked((int)0x80008094);
8390
public const int FrameworkMissingFailure = unchecked((int)0x80008096);
91+
public const int BundleExtractionFailure = unchecked((int)0x8000809f);
8492

8593
public const int COMPlusException = unchecked((int)0xe0434352);
8694
public const int SIGABRT = 134;

src/installer/tests/HostActivation.Tests/DotNetBuilder.cs renamed to src/installer/tests/TestUtils/DotNetBuilder.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System;
66
using System.IO;
77

8-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
8+
namespace Microsoft.DotNet.CoreSetup.Test
99
{
1010
/// <summary>
1111
/// Helper class for creating a mock version of a dotnet installation
@@ -68,16 +68,19 @@ public DotNetBuilder AddMicrosoftNETCoreAppFrameworkMockHostPolicy(string versio
6868
/// Use a mock version of HostFxr.
6969
/// </summary>
7070
/// <param name="version">Version to add</param>
71-
/// <remarks>
72-
/// Currently, the only mock version of HostFxr that we have is mockhostfxr_2_2.
73-
/// </remarks>
7471
public DotNetBuilder AddMockHostFxr(Version version)
7572
{
7673
string hostfxrPath = Path.Combine(_path, "host", "fxr", version.ToString());
7774
Directory.CreateDirectory(hostfxrPath);
78-
bool hasCustomErrorWriter = version.Major >= 3;
7975

80-
string mockHostFxrFileName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform(hasCustomErrorWriter ? "mockhostfxr" : "mockhostfxr_2_2");
76+
string mockHostFxrFileNameBase = version switch
77+
{
78+
{ Major: 2, Minor: 2 } => "mockhostfxr_2_2",
79+
{ Major: 5, Minor: 0 } => "mockhostfxr_5_0",
80+
_ => throw new InvalidOperationException($"Unsupported version {version} of mockhostfxr.")
81+
};
82+
83+
string mockHostFxrFileName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform(mockHostFxrFileNameBase);
8184
File.Copy(
8285
Path.Combine(_repoDirectories.Artifacts, "corehost_test", mockHostFxrFileName),
8386
Path.Combine(hostfxrPath, RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr")),

src/installer/tests/TestUtils/FileUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.IO;
66
using System.IO.MemoryMappedFiles;
77

8-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
8+
namespace Microsoft.DotNet.CoreSetup.Test
99
{
1010
public static class FileUtils
1111
{

src/installer/tests/HostActivation.Tests/NetCoreAppBuilder.cs renamed to src/installer/tests/TestUtils/NetCoreAppBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
using System.IO;
88
using System.Linq;
99

10-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
10+
namespace Microsoft.DotNet.CoreSetup.Test
1111
{
1212
public class NetCoreAppBuilder
1313
{

src/installer/tests/HostActivation.Tests/RuntimeConfig.cs renamed to src/installer/tests/TestUtils/RuntimeConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
using System.IO;
99
using System.Linq;
1010

11-
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
11+
namespace Microsoft.DotNet.CoreSetup.Test
1212
{
1313
public class RuntimeConfig
1414
{

0 commit comments

Comments
 (0)