提交 c17bb033 编写于 作者: E Elinor Fung 提交者: GitHub

Show error dialog for missing runtime/framework (dotnet/core-setup#8509)



Commit migrated from https://github.com/dotnet/core-setup/commit/346b717dc7be7f9b3c2554d883d26e8f33977b86
上级 4d82434a
......@@ -43,6 +43,14 @@ set(HEADERS
./bundle/dir_utils.h
)
if(WIN32)
list(APPEND SOURCES
apphost.windows.cpp)
list(APPEND HEADERS
apphost.windows.h)
endif()
include(../exe.cmake)
add_definitions(-DFEATURE_APPHOST=1)
......@@ -56,5 +64,5 @@ endif()
# Specify non-default Windows libs to be used for Arm/Arm64 builds
if (WIN32 AND (CLI_CMAKE_PLATFORM_ARCH_ARM OR CLI_CMAKE_PLATFORM_ARCH_ARM64))
target_link_libraries(apphost Advapi32.lib)
target_link_libraries(apphost Advapi32.lib shell32.lib)
endif()
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#include "apphost.windows.h"
#include "error_codes.h"
#include "pal.h"
#include "trace.h"
#include "utils.h"
namespace
{
pal::string_t g_buffered_errors;
void buffering_trace_writer(const pal::char_t* message)
{
// Add to buffer for later use.
g_buffered_errors.append(message).append(_X("\n"));
// Also write to stderr immediately
pal::err_fputs(message);
}
// Determines if the current module (apphost executable) is marked as a Windows GUI application
bool is_gui_application()
{
HMODULE module = ::GetModuleHandleW(nullptr);
assert(module != nullptr);
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
BYTE *bytes = reinterpret_cast<BYTE *>(module);
UINT32 pe_header_offset = reinterpret_cast<IMAGE_DOS_HEADER *>(bytes)->e_lfanew;
UINT16 subsystem = reinterpret_cast<IMAGE_NT_HEADERS *>(bytes + pe_header_offset)->OptionalHeader.Subsystem;
return subsystem == IMAGE_SUBSYSTEM_WINDOWS_GUI;
}
void write_errors_to_event_log(const pal::char_t *executable_path, const pal::char_t *executable_name)
{
// Report errors to the Windows Event Log.
auto eventSource = ::RegisterEventSourceW(nullptr, _X(".NET Runtime"));
const DWORD traceErrorID = 1023; // Matches CoreCLR ERT_UnmanagedFailFast
pal::string_t message;
message.append(_X("Description: A .NET Core application failed.\n"));
message.append(_X("Application: ")).append(executable_name).append(_X("\n"));
message.append(_X("Path: ")).append(executable_path).append(_X("\n"));
message.append(_X("Message: ")).append(g_buffered_errors).append(_X("\n"));
LPCWSTR messages[] = {message.c_str()};
::ReportEventW(eventSource, EVENTLOG_ERROR_TYPE, 0, traceErrorID, nullptr, 1, 0, messages, nullptr);
::DeregisterEventSource(eventSource);
}
void show_error_dialog(const pal::char_t *executable_name, int error_code)
{
// Show message dialog for UI apps with actionable errors
if (error_code != StatusCode::CoreHostLibMissingFailure // missing hostfxr
&& error_code != StatusCode::FrameworkMissingFailure) // missing framework
return;
pal::string_t gui_errors_disabled;
if (pal::getenv(_X("DOTNET_DISABLE_GUI_ERRORS"), &gui_errors_disabled) && pal::xtoi(gui_errors_disabled.c_str()) == 1)
return;
pal::string_t dialogMsg = _X("To run this application, you must install .NET Core.\n\n");
pal::string_t url;
if (error_code == StatusCode::CoreHostLibMissingFailure)
{
url = get_download_url();
}
else if (error_code == StatusCode::FrameworkMissingFailure)
{
// We don't have a great way of passing out different kinds of detailed error info across components, so
// just match the expected error string. See fx_resolver.messages.cpp.
pal::string_t line;
pal::stringstream_t ss(g_buffered_errors);
while (std::getline(ss, line, _X('\n'))){
const pal::string_t prefix = _X("The framework '");
const pal::string_t suffix = _X("' was not found.");
const pal::string_t url_prefix = _X(" - ") DOTNET_CORE_APPLAUNCH_URL _X("?");
if (starts_with(line, prefix, true) && ends_with(line, suffix, true))
{
dialogMsg.append(line);
dialogMsg.append(_X("\n\n"));
}
else if (starts_with(line, url_prefix, true))
{
size_t offset = url_prefix.length() - pal::strlen(DOTNET_CORE_APPLAUNCH_URL) - 1;
url = line.substr(offset, line.length() - offset);
break;
}
}
}
dialogMsg.append(_X("Would you like to download it now?"));
assert(url.length() > 0);
url.append(_X("&apphost_version="));
url.append(_STRINGIFY(COMMON_HOST_PKG_VER));
trace::verbose(_X("Showing error dialog for application: '%s' - error code: 0x%x - url: '%s'"), executable_name, error_code, url.c_str());
if (::MessageBoxW(nullptr, dialogMsg.c_str(), executable_name, MB_ICONERROR | MB_YESNO) == IDYES)
{
// Open the URL in default browser
::ShellExecuteW(
nullptr,
_X("open"),
url.c_str(),
nullptr,
nullptr,
SW_SHOWNORMAL);
}
}
}
void apphost::buffer_errors()
{
trace::verbose(_X("Redirecting errors to custom writer."));
trace::set_error_writer(buffering_trace_writer);
}
void apphost::write_buffered_errors(int error_code)
{
if (g_buffered_errors.empty())
return;
pal::string_t executable_path;
pal::string_t executable_name;
if (pal::get_own_executable_path(&executable_path))
{
executable_name = get_filename(executable_path);
}
write_errors_to_event_log(executable_path.c_str(), executable_name.c_str());
if (is_gui_application())
show_error_dialog(executable_name.c_str(), error_code);
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#ifndef __APPHOST_WINDOWS_H__
#define __APPHOST_WINDOWS_H__
namespace apphost
{
void buffer_errors();
void write_buffered_errors(int error_code);
}
#endif // __APPHOST_WINDOWS_H__
......@@ -129,17 +129,8 @@ void deps_json_t::reconcile_libraries_with_targets(
// Returns the RID determined (computed or fallback) for the platform the host is running on.
pal::string_t deps_json_t::get_current_rid(const rid_fallback_graph_t& rid_fallback_graph)
{
pal::string_t currentRid;
if (!pal::getenv(_X("DOTNET_RUNTIME_ID"), &currentRid))
{
currentRid = pal::get_current_os_rid_platform();
if (!currentRid.empty())
{
currentRid = currentRid + pal::string_t(_X("-")) + get_arch();
}
}
pal::string_t currentRid = get_current_runtime_id(false /*use_fallback*/);
trace::info(_X("HostRID is %s"), currentRid.empty()? _X("not available"): currentRid.c_str());
// If the current RID is not present in the RID fallback graph, then the platform
......
......@@ -113,11 +113,11 @@ void fx_resolver_t::display_missing_framework_error(
// Display the error message about missing FX.
if (fx_version.length())
{
trace::error(_X("The specified framework '%s', version '%s' was not found."), fx_name.c_str(), fx_version.c_str());
trace::error(_X("The framework '%s', version '%s' was not found."), fx_name.c_str(), fx_version.c_str());
}
else
{
trace::error(_X("The specified framework '%s' was not found."), fx_name.c_str());
trace::error(_X("The framework '%s' was not found."), fx_name.c_str());
}
if (framework_infos.size())
......@@ -133,11 +133,12 @@ void fx_resolver_t::display_missing_framework_error(
trace::error(_X(" - No frameworks were found."));
}
pal::string_t url = get_download_url(fx_name.c_str(), fx_version.c_str());
trace::error(_X(""));
trace::error(_X("You can resolve the problem by installing the specified framework and/or SDK."));
trace::error(_X(""));
trace::error(_X("The .NET Core frameworks can be found at:"));
trace::error(_X(" - %s"), DOTNET_CORE_DOWNLOAD_URL);
trace::error(_X("The specified framework can be found at:"));
trace::error(_X(" - %s"), url.c_str());
}
void fx_resolver_t::display_incompatible_loaded_framework_error(
......
......@@ -117,6 +117,9 @@ bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* o
default_install_location.c_str(),
dotnet_root_env_var_name.c_str(),
self_registered_message.c_str());
trace::error(_X(""));
trace::error(_X("The .NET Core runtime can be found at:"));
trace::error(_X(" - %s"), get_download_url().c_str());
return false;
}
......
......@@ -200,6 +200,25 @@ const pal::char_t* get_arch()
#endif
}
pal::string_t get_current_runtime_id(bool use_fallback)
{
pal::string_t rid;
if (pal::getenv(_X("DOTNET_RUNTIME_ID"), &rid))
return rid;
rid = pal::get_current_os_rid_platform();
if (rid.empty() && use_fallback)
rid = pal::get_current_os_fallback_rid();
if (!rid.empty())
{
rid.append(_X("-"));
rid.append(get_arch());
}
return rid;
}
bool get_env_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm)
{
pal::string_t path;
......@@ -383,6 +402,33 @@ pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path)
return get_directory(get_directory(fxr_root));
}
pal::string_t get_download_url(const pal::char_t *framework_name, const pal::char_t *framework_version)
{
pal::string_t url = DOTNET_CORE_APPLAUNCH_URL _X("?");
if (framework_name != nullptr && pal::strlen(framework_name) > 0)
{
url.append(_X("framework="));
url.append(framework_name);
if (framework_version != nullptr && pal::strlen(framework_version) > 0)
{
url.append(_X("&framework_version="));
url.append(framework_version);
}
}
else
{
url.append(_X("missing_runtime=true"));
}
url.append(_X("&arch="));
url.append(get_arch());
pal::string_t rid = get_current_runtime_id(true /*use_fallback*/);
url.append(_X("&rid="));
url.append(rid);
return url;
}
#define TEST_ONLY_MARKER "d38cc827-e34f-4453-9df4-1e796e9f1d07"
// Retrieves environment variable which is only used for testing.
......
......@@ -16,8 +16,8 @@
#else
#define DOTNET_CORE_INSTALL_PREREQUISITES_URL _X("https://go.microsoft.com/fwlink/?linkid=2063370")
#endif
#define DOTNET_CORE_DOWNLOAD_RUNTIME_URL _X("https://aka.ms/dotnet-download-runtime")
#define DOTNET_CORE_DOWNLOAD_URL _X("https://aka.ms/dotnet-download")
#define DOTNET_CORE_APPLAUNCH_URL _X("https://aka.ms/dotnet-core-applaunch")
#define RUNTIME_STORE_DIRECTORY_NAME _X("store")
......@@ -35,6 +35,7 @@ void remove_trailing_dir_seperator(pal::string_t* dir);
void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl);
pal::string_t get_replaced_char(const pal::string_t& path, pal::char_t match, pal::char_t repl);
const pal::char_t* get_arch();
pal::string_t get_current_runtime_id(bool use_fallback);
bool get_env_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm);
bool get_global_shared_store_dirs(std::vector<pal::string_t>* dirs, const pal::string_t& arch, const pal::string_t& tfm);
bool multilevel_lookup_enabled();
......@@ -47,6 +48,10 @@ pal::string_t get_deps_from_app_binary(const pal::string_t& app_base, const pal:
void get_runtime_config_paths(const pal::string_t& path, const pal::string_t& name, pal::string_t* cfg, pal::string_t* dev_cfg);
pal::string_t get_dotnet_root_from_fxr_path(const pal::string_t &fxr_path);
// Get a download URL for a specific framework and version
// If no framework is specified, a download URL for the runtime is returned
pal::string_t get_download_url(const pal::char_t *framework_name = nullptr, const pal::char_t *framework_version = nullptr);
// Retrieves environment variable which is only used for testing.
// This will return the value of the variable only if the product binary is stamped
// with test-only marker.
......
......@@ -14,6 +14,10 @@
#include "cli/apphost/bundle/marker.h"
#include "cli/apphost/bundle/runner.h"
#if defined(_WIN32)
#include "cli/apphost/apphost.windows.h"
#endif
#define CURHOST_TYPE _X("apphost")
#define CUREXE_PKG_VER COMMON_HOST_PKG_VER
#define CURHOST_EXE
......@@ -249,19 +253,6 @@ int exe_start(const int argc, const pal::char_t* argv[])
return rc;
}
#if defined(_WIN32) && defined(FEATURE_APPHOST)
pal::string_t g_buffered_errors;
void buffering_trace_writer(const pal::char_t* message)
{
// Add to buffer for later use.
g_buffered_errors.append(message).append(_X("\n"));
// Also write to stderr immediately
pal::err_fputs(message);
}
#endif
#if defined(_WIN32)
int __cdecl wmain(const int argc, const pal::char_t* argv[])
#else
......@@ -281,9 +272,8 @@ int main(const int argc, const pal::char_t* argv[])
}
#if defined(_WIN32) && defined(FEATURE_APPHOST)
trace::verbose(_X("Redirecting errors to custom writer."));
// Buffer errors to use them later.
trace::set_error_writer(buffering_trace_writer);
apphost::buffer_errors();
#endif
int exit_code = exe_start(argc, argv);
......@@ -293,28 +283,7 @@ int main(const int argc, const pal::char_t* argv[])
#if defined(_WIN32) && defined(FEATURE_APPHOST)
// No need to unregister the error writer since we're exiting anyway.
if (!g_buffered_errors.empty())
{
// If there are errors buffered, write them to the Windows Event Log.
pal::string_t executable_path;
pal::string_t executable_name;
if (pal::get_own_executable_path(&executable_path))
{
executable_name = get_filename(executable_path);
}
auto eventSource = ::RegisterEventSourceW(nullptr, _X(".NET Runtime"));
const DWORD traceErrorID = 1023; // Matches CoreCLR ERT_UnmanagedFailFast
pal::string_t message;
message.append(_X("Description: A .NET Core application failed.\n"));
message.append(_X("Application: ")).append(executable_name).append(_X("\n"));
message.append(_X("Path: ")).append(executable_path).append(_X("\n"));
message.append(_X("Message: ")).append(g_buffered_errors).append(_X("\n"));
LPCWSTR messages[] = {message.c_str()};
::ReportEventW(eventSource, EVENTLOG_ERROR_TYPE, 0, traceErrorID, nullptr, 1, 0, messages, nullptr);
::DeregisterEventSource(eventSource);
}
apphost::write_buffered_errors(exit_code);
#endif
return exit_code;
......
......@@ -80,6 +80,7 @@ public static class ErrorCode
public const int ResolverInitFailure = unchecked((int)0x8000808b);
public const int ResolverResolveFailure = unchecked((int)0x8000808c);
public const int LibHostInvalidArgs = unchecked((int)0x80008092);
public const int FrameworkMissingFailure = unchecked((int)0x80008096);
}
}
}
......@@ -3,11 +3,10 @@
// See the LICENSE file in the project root for more information.
using Microsoft.DotNet.Cli.Build.Framework;
using Microsoft.NET.HostModel.AppHost;
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Runtime.InteropServices;
using Xunit;
namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
......@@ -240,7 +239,7 @@ public void Muxer_Exec_activation_of_Publish_Output_Portable_DLL_with_DepsJson_R
}
[Fact]
public void Framework_Dependent_AppHost_Succeeds()
public void AppHost_FrameworkDependent_Succeeds()
{
var fixture = sharedTestState.PortableAppFixture_Published
.Copy();
......@@ -248,27 +247,9 @@ public void Framework_Dependent_AppHost_Succeeds()
// Since SDK doesn't support building framework dependent apphost yet, emulate that behavior
// by creating the executable from apphost.exe
var appExe = fixture.TestProject.AppExe;
var appDllName = Path.GetFileName(fixture.TestProject.AppDll);
string hostExeName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost");
string builtAppHost = Path.Combine(sharedTestState.RepoDirectories.HostArtifacts, hostExeName);
string appDir = Path.GetDirectoryName(appExe);
string appDirHostExe = Path.Combine(appDir, hostExeName);
// Make a copy of apphost first, replace hash and overwrite app.exe, rather than
// overwrite app.exe and edit in place, because the file is opened as "write" for
// the replacement -- the test fails with ETXTBSY (exit code: 26) in Linux when
// executing a file opened in "write" mode.
File.Copy(builtAppHost, appDirHostExe, true);
using (var sha256 = SHA256.Create())
{
// Replace the hash with the managed DLL name.
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes("foobar"));
var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower();
BinaryUtils.SearchAndReplace(appDirHostExe, Encoding.UTF8.GetBytes(hashStr), Encoding.UTF8.GetBytes(appDllName));
}
File.Copy(appDirHostExe, appExe, true);
File.Copy(sharedTestState.BuiltAppHost, appExe, overwrite: true);
AppHostExtensions.BindAppHost(appExe);
// Get the framework location that was built
string builtDotnet = fixture.BuiltDotnet.BinPath;
......@@ -300,7 +281,7 @@ public void Framework_Dependent_AppHost_Succeeds()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Framework_Dependent_AppHost_From_Global_Location_Succeeds(bool useRegisteredLocation)
public void AppHost_FrameworkDependent_GlobalLocation_Succeeds(bool useRegisteredLocation)
{
var fixture = sharedTestState.PortableAppFixture_Published
.Copy();
......@@ -308,26 +289,8 @@ public void Framework_Dependent_AppHost_From_Global_Location_Succeeds(bool useRe
// Since SDK doesn't support building framework dependent apphost yet, emulate that behavior
// by creating the executable from apphost.exe
var appExe = fixture.TestProject.AppExe;
var appDllName = Path.GetFileName(fixture.TestProject.AppDll);
string hostExeName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost");
string builtAppHost = Path.Combine(sharedTestState.RepoDirectories.HostArtifacts, hostExeName);
string appDir = Path.GetDirectoryName(appExe);
string appDirHostExe = Path.Combine(appDir, hostExeName);
// Make a copy of apphost first, replace hash and overwrite app.exe, rather than
// overwrite app.exe and edit in place, because the file is opened as "write" for
// the replacement -- the test fails with ETXTBSY (exit code: 26) in Linux when
// executing a file opened in "write" mode.
File.Copy(builtAppHost, appDirHostExe, true);
using (var sha256 = SHA256.Create())
{
// Replace the hash with the managed DLL name.
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes("foobar"));
var hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower();
BinaryUtils.SearchAndReplace(appDirHostExe, Encoding.UTF8.GetBytes(hashStr), Encoding.UTF8.GetBytes(appDllName));
}
File.Copy(appDirHostExe, appExe, true);
File.Copy(sharedTestState.BuiltAppHost, appExe, overwrite: true);
AppHostExtensions.BindAppHost(appExe);
// Get the framework location that was built
string builtDotnet = fixture.BuiltDotnet.BinPath;
......@@ -385,6 +348,94 @@ public void ComputedTPADoesntEndWithPathSeparator()
.And.HaveStdErrMatching($"Property TRUSTED_PLATFORM_ASSEMBLIES = .*[^{Path.PathSeparator}]$", System.Text.RegularExpressions.RegexOptions.Multiline);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void AppHost_GUI_FrameworkDependent_MissingRuntimeFramework_ErrorReportedInDialog(bool missingHostfxr)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// GUI app host is only supported on Windows.
return;
}
var fixture = sharedTestState.PortableAppFixture_Built
.Copy();
string appExe = fixture.TestProject.AppExe;
File.Copy(sharedTestState.BuiltAppHost, appExe, overwrite: true);
AppHostExtensions.BindAppHost(appExe);
AppHostExtensions.SetWindowsGraphicalUserInterfaceBit(appExe);
string invalidDotNet = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "guiErrors"));
using (new TestArtifact(invalidDotNet))
{
Directory.CreateDirectory(invalidDotNet);
string expectedErrorCode;
string expectedUrlQuery;
if (missingHostfxr)
{
expectedErrorCode = Constants.ErrorCode.CoreHostLibMissingFailure.ToString("x");
expectedUrlQuery = "missing_runtime=true";
}
else
{
invalidDotNet = new DotNetBuilder(invalidDotNet, sharedTestState.RepoDirectories.BuiltDotnet, "missingFramework")
.Build()
.BinPath;
expectedErrorCode = Constants.ErrorCode.FrameworkMissingFailure.ToString("x");
expectedUrlQuery = $"framework={Constants.MicrosoftNETCoreApp}&framework_version={sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}";
}
Command command = Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.DotNetRoot(invalidDotNet)
.MultilevelLookup(false)
.Start();
WaitForPopupFromProcess(command.Process);
command.Process.Kill();
command.WaitForExit(true)
.Should().Fail()
.And.HaveStdErrContaining($"Showing error dialog for application: '{Path.GetFileName(appExe)}' - error code: 0x{expectedErrorCode}")
.And.HaveStdErrContaining($"url: 'https://aka.ms/dotnet-core-applaunch?{expectedUrlQuery}")
.And.HaveStdErrContaining($"apphost_version={sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}");
}
}
[Fact]
public void AppHost_GUI_FrameworkDependent_DisabledGUIErrors_DialogNotShown()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// GUI app host is only supported on Windows.
return;
}
var fixture = sharedTestState.PortableAppFixture_Built
.Copy();
string appExe = fixture.TestProject.AppExe;
File.Copy(sharedTestState.BuiltAppHost, appExe, overwrite: true);
AppHostExtensions.BindAppHost(appExe);
AppHostExtensions.SetWindowsGraphicalUserInterfaceBit(appExe);
string invalidDotNet = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "guiErrors"));
using (new TestArtifact(invalidDotNet))
{
Directory.CreateDirectory(invalidDotNet);
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.DotNetRoot(invalidDotNet)
.MultilevelLookup(false)
.EnvironmentVariable(Constants.DisableGuiErrors.EnvironmentVariable, "1")
.Execute()
.Should().Fail()
.And.NotHaveStdErrContaining("Showing error dialog for application");
}
}
private string MoveDepsJsonToSubdirectory(TestProjectFixture testProjectFixture)
{
......@@ -437,15 +488,61 @@ private string CreateAStore(TestProjectFixture testProjectFixture)
return storeoutputDirectory;
}
#if WINDOWS
private delegate bool EnumThreadWindowsDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int dwThreadId, EnumThreadWindowsDelegate plfn, IntPtr lParam);
private IntPtr WaitForPopupFromProcess(Process process, int timeout = 60000)
{
IntPtr windowHandle = IntPtr.Zero;
int timeRemaining = timeout;
while (timeRemaining > 0)
{
foreach (ProcessThread thread in process.Threads)
{
// We take the last window we find. There really should only be one at most anyways.
EnumThreadWindows(thread.Id,
(hWnd, lParam) => {
windowHandle = hWnd;
return true;
},
IntPtr.Zero);
}
if (windowHandle != IntPtr.Zero)
{
break;
}
System.Threading.Thread.Sleep(100);
timeRemaining -= 100;
}
// Do not fail if the window could be detected, sometimes the check is fragile and doesn't work.
// Not worth the trouble trying to figure out why (only happens rarely in the CI system).
// We will rely on product tracing in the failure case.
return windowHandle;
}
#else
private IntPtr WaitForPopupFromProcess(Process process, int timeout = 60000)
{
throw new PlatformNotSupportedException();
}
#endif
public class SharedTestState : IDisposable
{
public TestProjectFixture PortableAppFixture_Built { get; }
public TestProjectFixture PortableAppFixture_Published { get; }
public RepoDirectoriesProvider RepoDirectories { get; }
public string BuiltAppHost { get; }
public SharedTestState()
{
RepoDirectories = new RepoDirectoriesProvider();
BuiltAppHost = Path.Combine(RepoDirectories.HostArtifacts, RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("apphost"));
PortableAppFixture_Built = new TestProjectFixture("PortableApp", RepoDirectories)
.EnsureRestored(RepoDirectories.CorehostPackages)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册