diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64ff041d5c..eac4268e4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing ====== -Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. +Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. diff --git a/build/dependencies.props b/build/dependencies.props index 8e4a0a815d..a1e8c3f738 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -34,6 +34,7 @@ 2.0.9 2.1.2 2.2.0-preview1-26618-02 + 1.0.1 3.0.0-alpha1-10123 15.6.1 11.1.0 diff --git a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp b/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp index a857706142..4cb6b222cf 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp @@ -3,6 +3,7 @@ #include "aspnetcore_shim_config.h" +#include "EventLog.h" #include "config_utility.h" #include "hostfxr_utility.h" #include "ahutil.h" @@ -49,6 +50,7 @@ ASPNETCORE_SHIM_CONFIG::Populate( else { // block unknown hosting value + EVENTLOG(g_hEventLog, UNKNOWN_HOSTING_MODEL_ERROR, strHostingModel.QueryStr()); RETURN_IF_FAILED(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 2be58bcc5c..5ff3e0103f 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -195,6 +195,7 @@ + diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp index 396a69ff31..b2c6b69871 100644 --- a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include -#include +#include #include "GlobalVersionUtility.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp index 3b07cf8645..dff673fee3 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.cpp @@ -3,7 +3,7 @@ #include "PollingAppOfflineApplication.h" -#include +#include #include "SRWExclusiveLock.h" #include "HandleWrapper.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h index e66f5f46c8..592cdb2dc8 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h +++ b/src/AspNetCoreModuleV2/CommonLib/PollingAppOfflineApplication.h @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #pragma once -#include +#include #include "application.h" #include "requesthandler.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index c15047b235..477c1a0d0a 100644 --- a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -224,6 +224,19 @@ Language=English %1 . + +Messageid=1033 +SymbolicName=ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL +Language=English +%1 +. + +Messageid=1034 +SymbolicName=ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR +Language=English +%1 +. + ; ;#endif // _ASPNETCORE_MODULE_MSG_H_ ; diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index fd3a65abf8..ab827460bf 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -141,7 +141,9 @@ HOSTFXR_UTILITY::GetHostFxrParameters( auto fullProcessPath = GetAbsolutePathToDotnet(applicationPhysicalPath, processPath); if (!fullProcessPath.has_value()) { - return E_FAIL; + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + EVENTLOG(hEventLog, INVALID_PROCESS_PATH, processPath.c_str(), hr); + return hr; } processPath = fullProcessPath.value(); @@ -149,7 +151,8 @@ HOSTFXR_UTILITY::GetHostFxrParameters( auto hostFxrPath = GetAbsolutePathToHostFxr(processPath, hEventLog); if (!hostFxrPath.has_value()) { - return E_FAIL; + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); + return hr; } RETURN_IF_FAILED(HOSTFXR_UTILITY::ParseHostfxrArguments( @@ -368,6 +371,8 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( // Only do it if no path is specified if (requestedPath.has_parent_path()) { + WLOG_INFOF(L"Absolute path to dotnet.exe was not found at %s", requestedPath.c_str()); + return std::nullopt; } diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index dfdeb08b3a..1315865803 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include "stringu.h" diff --git a/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h index 2956cce581..ae26e9777f 100644 --- a/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -26,8 +26,11 @@ #define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." #define ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown application '%s'." #define ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG L"Application '%s' with physical root '%s' failed to load clr and managed application, ErrorCode = '0x%x." +#define ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG L"Application '%s' has shutdown." #define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool." #define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%d' other than the one of running application(s)." +#define ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR_MSG L"Unknown hosting model '%s'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file." +#define ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR_LEVEL EVENTLOG_ERROR_TYPE #define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Last 4KB characters of captured stdout and stderr logs:\r\n%s" #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x. Please check the stderr logs for more information." @@ -48,7 +51,7 @@ #define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG L"Could not find application executable in '%s'. ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information." #define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_LEVEL EVENTLOG_ERROR_TYPE -#define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = %s, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = '%s', ErrorCode = '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application." #define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress." #define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully." diff --git a/src/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/AspNetCoreModuleV2/CommonLib/stdafx.h index d5407df316..b17ae43485 100644 --- a/src/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -14,4 +14,4 @@ #include #include #include -#include +#include diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index baf73ec9c1..b52b08f833 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -126,6 +126,14 @@ Finished: // exit(hr); } + else + { + UTILITY::LogEventF(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL, + ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG, + m_pConfig->QueryConfigPath()->QueryStr()); + } } VOID diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h b/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h index fd6382710b..78a02ce72b 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/stdafx.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include "Shlwapi.h" #include diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs index aaec90971e..8fddbaede6 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/ApplicationDeployerFactory.cs @@ -33,7 +33,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting switch (deploymentParameters.ServerType) { case ServerType.IISExpress: - return new IIS.IISExpressDeployer(deploymentParameters, loggerFactory); + return new IISExpressDeployer(deploymentParameters, loggerFactory); case ServerType.IIS: return new IISDeployer(deploymentParameters, loggerFactory); default: diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs index d0c0bfb153..b83e7aa4df 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISApplication.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting /// internal class IISApplication { - private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(10); private static readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(200); private readonly ServerManager _serverManager = new ServerManager(); private readonly DeploymentParameters _deploymentParameters; @@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting "config", "applicationhost.config"); + public Process HostProcess { get; set; } + public IISApplication(DeploymentParameters deploymentParameters, ILogger logger) { _deploymentParameters = deploymentParameters; @@ -63,33 +65,19 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting } AddTemporaryAppHostConfig(); - ConfigureAppPool(contentRoot); + var apppool = ConfigureAppPool(contentRoot); ConfigureSite(contentRoot, port); ConfigureAppHostConfig(contentRoot); - if (_deploymentParameters.ApplicationType == ApplicationType.Portable) - { - ModifyAspNetCoreSectionInWebConfig("processPath", DotNetCommands.GetDotNetExecutable(_deploymentParameters.RuntimeArchitecture)); - } - _serverManager.CommitChanges(); - await WaitUntilSiteStarted(); + await WaitUntilSiteStarted(apppool); } } - private void ModifyAspNetCoreSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(_deploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - private async Task WaitUntilSiteStarted() + private async Task WaitUntilSiteStarted(ApplicationPool appPool) { var sw = Stopwatch.StartNew(); @@ -98,6 +86,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting try { var site = _serverManager.Sites.FirstOrDefault(s => s.Name.Equals(WebSiteName)); + if (site.State == ObjectState.Started) { _logger.LogInformation($"Site {WebSiteName} has started."); diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs index f1f4922e88..3f7425fce6 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployer.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.AspNetCore.Server.IntegrationTesting.Common; using Microsoft.Extensions.Logging; @@ -14,12 +12,17 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS /// /// Deployer for IIS. /// - public partial class IISDeployer : ApplicationDeployer + public partial class IISDeployer : IISDeployerBase { private IISApplication _application; private CancellationTokenSource _hostShutdownToken = new CancellationTokenSource(); public IISDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(new IISDeploymentParameters(deploymentParameters), loggerFactory) + { + } + + public IISDeployer(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) : base(deploymentParameters, loggerFactory) { } @@ -53,31 +56,41 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS DeploymentParameters.ServerConfigTemplateContent = File.ReadAllText("IIS.config"); } - _application = new IISApplication(DeploymentParameters, Logger); + _application = new IISApplication(IISDeploymentParameters, Logger); // For now, only support using published output DeploymentParameters.PublishApplicationBeforeDeployment = true; + + + if (DeploymentParameters.ApplicationType == ApplicationType.Portable) + { + DefaultWebConfigActions.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection( + "processPath", + DotNetCommands.GetDotNetExecutable(DeploymentParameters.RuntimeArchitecture))); + } + if (DeploymentParameters.PublishApplicationBeforeDeployment) { DotnetPublish(); contentRoot = DeploymentParameters.PublishedApplicationRootPath; + IISDeploymentParameters.AddDebugLogToWebConfig(Path.Combine(contentRoot, $"{_application.WebSiteName}.txt")); + RunWebConfigActions(); } - WebConfigHelpers.AddDebugLogToWebConfig(contentRoot, Path.Combine(contentRoot, $"{_application.WebSiteName}.txt")); - var uri = TestUriHelper.BuildTestUri(ServerType.IIS, DeploymentParameters.ApplicationBaseUriHint); // To prevent modifying the IIS setup concurrently. await _application.StartIIS(uri, contentRoot); // Warm up time for IIS setup. Logger.LogInformation("Successfully finished IIS application directory setup."); - - return new DeploymentResult( + return new IISDeploymentResult( LoggerFactory, - DeploymentParameters, + IISDeploymentParameters, applicationBaseUri: uri.ToString(), contentRoot: contentRoot, - hostShutdownToken: _hostShutdownToken.Token + hostShutdownToken: _hostShutdownToken.Token, + hostProcess: _application.HostProcess ); } } diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs new file mode 100644 index 0000000000..5042a192e3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeployerBase.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public abstract class IISDeployerBase : ApplicationDeployer + { + public IISDeploymentParameters IISDeploymentParameters { get; } + + protected List> DefaultWebConfigActions { get; } = new List>(); + + public IISDeployerBase(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(deploymentParameters, loggerFactory) + { + IISDeploymentParameters = deploymentParameters; + } + + public void RunWebConfigActions() + { + if (IISDeploymentParameters == null) + { + return; + } + + if (!DeploymentParameters.PublishApplicationBeforeDeployment) + { + throw new InvalidOperationException("Cannot modify web.config file if no published output."); + } + + var path = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); + var webconfig = XDocument.Load(path); + var xElement = webconfig.Descendants("system.webServer").Single(); + + foreach (var action in DefaultWebConfigActions) + { + action.Invoke(xElement); + } + + foreach (var action in IISDeploymentParameters.WebConfigActionList) + { + action.Invoke(xElement); + } + + webconfig.Save(path); + } + + + public string RunServerConfigActions(string serverConfigString) + { + if (IISDeploymentParameters == null) + { + return serverConfigString; + } + + var serverConfig = XDocument.Parse(serverConfigString); + var xElement = serverConfig.Descendants("configuration").FirstOrDefault(); + + foreach (var action in IISDeploymentParameters.ServerConfigActionList) + { + action.Invoke(xElement); + } + return xElement.ToString(); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs new file mode 100644 index 0000000000..ff2365a82a --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameterExtensions.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Xml.Linq; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public static class IISDeploymentParameterExtensions + { + public static void AddDebugLogToWebConfig(this IISDeploymentParameters parameters, string filename) + { + parameters.HandlerSettings["debugLevel"] = "4"; + parameters.HandlerSettings["debugFile"] = filename; + } + + public static void AddServerConfigAction(this IISDeploymentParameters parameters, Action action) + { + parameters.ServerConfigActionList.Add(action); + } + + public static void AddHttpsToServerConfig(this IISDeploymentParameters parameters) + { + parameters.ServerConfigActionList.Add( + element => + { + element.Descendants("binding") + .Single() + .SetAttributeValue("protocol", "https"); + + element.Descendants("access") + .Single() + .SetAttributeValue("sslFlags", "Ssl, SslNegotiateCert"); + }); + } + + public static void AddWindowsAuthToServerConfig(this IISDeploymentParameters parameters) + { + parameters.ServerConfigActionList.Add( + element => + { + element.Descendants("windowsAuthentication") + .Single() + .SetAttributeValue("enabled", "true"); + }); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs new file mode 100644 index 0000000000..a22116d557 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentParameters.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class IISDeploymentParameters : DeploymentParameters + { + public IISDeploymentParameters() : base() + { + WebConfigActionList = CreateDefaultWebConfigActionList(); + } + + public IISDeploymentParameters(TestVariant variant) + : base(variant) + { + WebConfigActionList = CreateDefaultWebConfigActionList(); + } + + public IISDeploymentParameters( + string applicationPath, + ServerType serverType, + RuntimeFlavor runtimeFlavor, + RuntimeArchitecture runtimeArchitecture) + : base(applicationPath, serverType, runtimeFlavor, runtimeArchitecture) + { + WebConfigActionList = CreateDefaultWebConfigActionList(); + } + + public IISDeploymentParameters(DeploymentParameters parameters) + : base(parameters) + { + WebConfigActionList = CreateDefaultWebConfigActionList(); + + if (parameters is IISDeploymentParameters) + { + var tempParameters = (IISDeploymentParameters)parameters; + WebConfigActionList = tempParameters.WebConfigActionList; + ServerConfigActionList = tempParameters.ServerConfigActionList; + WebConfigBasedEnvironmentVariables = tempParameters.WebConfigBasedEnvironmentVariables; + HandlerSettings = tempParameters.HandlerSettings; + GracefulShutdown = tempParameters.GracefulShutdown; + } + } + + private IList> CreateDefaultWebConfigActionList() + { + return new List>() { AddWebConfigEnvironmentVariables(), AddHandlerSettings() }; + } + + public IList> WebConfigActionList { get; } + + public IList> ServerConfigActionList { get; } = new List>(); + + public IDictionary WebConfigBasedEnvironmentVariables { get; set; } = new Dictionary(); + + public IDictionary HandlerSettings { get; set; } = new Dictionary(); + + public bool GracefulShutdown { get; set; } + + private Action AddWebConfigEnvironmentVariables() + { + return xElement => + { + if (WebConfigBasedEnvironmentVariables.Count == 0) + { + return; + } + + var element = xElement.Descendants("environmentVariables").SingleOrDefault(); + if (element == null) + { + element = new XElement("environmentVariables"); + xElement.Descendants("aspNetCore").SingleOrDefault().Add(element); + } + + foreach (var envVar in WebConfigBasedEnvironmentVariables) + { + CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable"); + } + }; + } + + private Action AddHandlerSettings() + { + return xElement => + { + if (HandlerSettings.Count == 0) + { + return; + } + + var element = xElement.Descendants("handlerSettings").SingleOrDefault(); + if (element == null) + { + element = new XElement("handlerSettings"); + xElement.Descendants("aspNetCore").SingleOrDefault().Add(element); + } + + foreach (var handlerSetting in HandlerSettings) + { + CreateOrSetElement(element, handlerSetting.Key, handlerSetting.Value, "handlerSetting"); + } + }; + } + + private static void CreateOrSetElement(XElement rootElement, string name, string value, string elementName) + { + if (rootElement.Descendants() + .Attributes() + .Where(attribute => attribute.Value == name) + .Any()) + { + return; + } + var element = new XElement(elementName); + element.SetAttributeValue("name", name); + element.SetAttributeValue("value", value); + rootElement.Add(element); + } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs new file mode 100644 index 0000000000..4a9d881900 --- /dev/null +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISDeploymentResult.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Net.Http; +using System.Threading; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS +{ + public class IISDeploymentResult : DeploymentResult + { + public ILogger Logger { get; set; } + public Process HostProcess { get; } + + public IISDeploymentResult(ILoggerFactory loggerFactory, + IISDeploymentParameters deploymentParameters, + string applicationBaseUri, + string contentRoot, + CancellationToken hostShutdownToken, + Process hostProcess) + : base(loggerFactory, + deploymentParameters, + applicationBaseUri, + contentRoot, + hostShutdownToken) + { + HostProcess = hostProcess; + Logger = loggerFactory.CreateLogger(deploymentParameters.SiteName); + // SocketsHttpHandler isn't available in netstandard2.0 + RetryingHttpClient = CreateRetryClient(new HttpClientHandler()); + HttpClient = CreateClient(new HttpClientHandler()); + } + + public HttpClient CreateRetryClient(HttpMessageHandler messageHandler) + { + var loggingHandler = new LoggingHandler(messageHandler, Logger); + var retryHandler = new RetryHandler(loggingHandler, Logger); + return new HttpClient(retryHandler) + { + BaseAddress = base.HttpClient.BaseAddress + }; + } + + public HttpClient CreateClient(HttpMessageHandler messageHandler) + { + return new HttpClient(new LoggingHandler(messageHandler, Logger)) + { + BaseAddress = base.HttpClient.BaseAddress + }; + } + + public HttpClient RetryingHttpClient { get; set; } + + public new HttpClient HttpClient { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs index 0cd67cc132..0fea5fa391 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/IISExpressDeployer.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -19,18 +20,23 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS /// /// Deployment helper for IISExpress. /// - public class IISExpressDeployer : ApplicationDeployer + public class IISExpressDeployer : IISDeployerBase { private const string IISExpressRunningMessage = "IIS Express is running."; private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings"; private const string UnableToStartIISExpressMessage = "Unable to start iisexpress."; private const int MaximumAttempts = 5; - + private readonly TimeSpan ShutdownTimeSpan = TimeSpan.FromSeconds(60); private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?[^""]+)"" for site.*$"); private Process _hostProcess; public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) + : base(new IISDeploymentParameters(deploymentParameters), loggerFactory) + { + } + + public IISExpressDeployer(IISDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory) : base(deploymentParameters, loggerFactory) { } @@ -92,12 +98,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS Logger.LogInformation("Application ready at URL: {appUrl}", actualUri); // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath. - return new DeploymentResult( + + return new IISDeploymentResult( LoggerFactory, - DeploymentParameters, + IISDeploymentParameters, applicationBaseUri: actualUri.ToString(), contentRoot: contentRoot, - hostShutdownToken: hostExitToken); + hostShutdownToken: hostExitToken, + hostProcess: _hostProcess); } } @@ -272,11 +280,16 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS if (DeploymentParameters.PublishApplicationBeforeDeployment) { // For published apps, prefer the content in the web.config, but update it. - ModifyAspNetCoreSectionInWebConfig(key: "hostingModel", - value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : ""); - ModifyHandlerSectionInWebConfig(key: "modules", value: DeploymentParameters.AncmVersion.ToString()); + DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection( + key: "hostingModel", + value: DeploymentParameters.HostingModel == HostingModel.InProcess ? "inprocess" : "")); + + DefaultWebConfigActions.Add(WebConfigHelpers.AddOrModifyHandlerSection( + key: "modules", + value: DeploymentParameters.AncmVersion.ToString())); ModifyDotNetExePathInWebConfig(); serverConfig = RemoveRedundantElements(serverConfig); + RunWebConfigActions(); } else { @@ -284,6 +297,7 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS serverConfig = ReplacePlaceholder(serverConfig, "[HostingModel]", DeploymentParameters.HostingModel.ToString()); serverConfig = ReplacePlaceholder(serverConfig, "[AspNetCoreModule]", DeploymentParameters.AncmVersion.ToString()); } + serverConfig = RunServerConfigActions(serverConfig); DeploymentParameters.ServerConfigLocation = Path.GetTempFileName(); Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation); @@ -345,7 +359,14 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { using (Logger.BeginScope("Dispose")) { - ShutDownIfAnyHostProcess(_hostProcess); + if (IISDeploymentParameters.GracefulShutdown) + { + GracefullyShutdownProcess(_hostProcess); + } + else + { + ShutDownIfAnyHostProcess(_hostProcess); + } if (!string.IsNullOrEmpty(DeploymentParameters.ServerConfigLocation) && File.Exists(DeploymentParameters.ServerConfigLocation)) @@ -381,6 +402,59 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS } } + private class WindowsNativeMethods + { + [DllImport("user32.dll")] + internal static extern IntPtr GetTopWindow(IntPtr hWnd); + [DllImport("user32.dll")] + internal static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); + [DllImport("user32.dll")] + internal static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint lpdwProcessId); + [DllImport("user32.dll")] + internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + } + + private static void SendStopMessageToProcess(int pid) + { + for (var ptr = WindowsNativeMethods.GetTopWindow(IntPtr.Zero); ptr != IntPtr.Zero; ptr = WindowsNativeMethods.GetWindow(ptr, 2)) + { + uint num; + WindowsNativeMethods.GetWindowThreadProcessId(ptr, out num); + if (pid == num) + { + var hWnd = new HandleRef(null, ptr); + WindowsNativeMethods.PostMessage(hWnd, 0x12, IntPtr.Zero, IntPtr.Zero); + return; + } + } + } + + private void GracefullyShutdownProcess(Process hostProcess) + { + if (hostProcess != null && !hostProcess.HasExited) + { + // Calling hostProcess.StandardInput.WriteLine("q") with StandardInput redirected + // for the process does not work when stopping IISExpress + // Also, hostProcess.CloseMainWindow() doesn't work either. + // Instead we have to send WM_QUIT to the iisexpress process via pInvokes. + // See: https://stackoverflow.com/questions/4772092/starting-and-stopping-iis-express-programmatically + + SendStopMessageToProcess(hostProcess.Id); + if (!hostProcess.WaitForExit((int)ShutdownTimeSpan.TotalMilliseconds)) + { + throw new InvalidOperationException($"iisexpress Process {hostProcess.Id} failed to gracefully shutdown."); + } + if (hostProcess.ExitCode != 0) + { + Logger.LogWarning($"IISExpress exit code is non-zero after graceful shutdown. Exit code: {hostProcess.ExitCode}"); + } + } + else + { + throw new InvalidOperationException($"iisexpress Process {hostProcess.Id} crashed before shutdown was triggered."); + } + } + private void ModifyDotNetExePathInWebConfig() { // We assume the x64 dotnet.exe is on the path so we need to provide an absolute path for x86 scenarios. @@ -394,29 +468,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { throw new Exception($"Unable to find '{executableName}'.'"); } - ModifyAspNetCoreSectionInWebConfig("processPath", executableName); + DefaultWebConfigActions.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", executableName)); } } - // Transforms the web.config file to set attributes like hostingModel="inprocess" element - private void ModifyAspNetCoreSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("aspNetCore").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - - private void ModifyHandlerSectionInWebConfig(string key, string value) - { - var webConfigFile = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, "web.config"); - var config = XDocument.Load(webConfigFile); - var element = config.Descendants("handlers").FirstOrDefault().Descendants("add").FirstOrDefault(); - element.SetAttributeValue(key, value); - config.Save(webConfigFile); - } - // These elements are duplicated in the web.config if you publish. Remove them from the host.config. private string RemoveRedundantElements(string serverConfig) { diff --git a/test/Common.Tests/Utilities/LoggingHandler.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs similarity index 96% rename from test/Common.Tests/Utilities/LoggingHandler.cs rename to src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs index 9559d8106c..241991f62d 100644 --- a/test/Common.Tests/Utilities/LoggingHandler.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/LoggingHandler.cs @@ -4,7 +4,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.IntegrationTesting +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { public class LoggingHandler: DelegatingHandler { diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj index 0891a315dc..363de4e34f 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/Microsoft.AspNetCore.Server.IntegrationTesting.IIS.csproj @@ -15,6 +15,7 @@ + diff --git a/test/Common.Tests/Utilities/RetryHandler.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs similarity index 96% rename from test/Common.Tests/Utilities/RetryHandler.cs rename to src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs index 00e1174c7a..4282c3afca 100644 --- a/test/Common.Tests/Utilities/RetryHandler.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/RetryHandler.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace Microsoft.AspNetCore.Server.IntegrationTesting +namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { public class RetryHandler : DelegatingHandler { diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/TestUriHelper.cs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs index e5c13225b5..8d2af5eb1d 100644 --- a/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs +++ b/src/Microsoft.AspNetCore.Server.IntegrationTesting.IIS/WebConfigHelpers.cs @@ -2,49 +2,34 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Linq; using System.Xml.Linq; -using Microsoft.AspNetCore.Server.IntegrationTesting; -using Microsoft.AspNetCore.Testing; namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS { - public class WebConfigHelpers + public static class WebConfigHelpers { - public static void AddDebugLogToWebConfig(string contentRoot, string filename) + public static Action AddOrModifyAspNetCoreSection(string key, string value) + => AddAction(key, value, section: "aspNetCore"); + + public static Action AddAction(string key, string value, string section) { - var path = Path.Combine(contentRoot, "web.config"); - var webconfig = XDocument.Load(path); - var xElement = webconfig.Descendants("aspNetCore").Single(); - - var element = xElement.Descendants("handlerSettings").SingleOrDefault(); - if (element == null) + return (element) => { - element = new XElement("handlerSettings"); - xElement.Add(element); - } - - CreateOrSetElement(element, "debugLevel", "4"); - - CreateOrSetElement(element, "debugFile", Path.Combine(contentRoot, filename)); - - webconfig.Save(path); + element.Descendants(section).SingleOrDefault().SetAttributeValue(key, value); + }; } - private static void CreateOrSetElement(XElement rootElement, string name, string value) + public static Action AddOrModifyHandlerSection(string key, string value) { - if (rootElement.Descendants() - .Attributes() - .Where(attribute => attribute.Value == name) - .Any()) + return element => { - return; - } - var element = new XElement("handlerSetting"); - element.SetAttributeValue("name", name); - element.SetAttributeValue("value", value); - rootElement.Add(element); + element.Descendants("handlers") + .FirstOrDefault() + .Descendants("add") + .FirstOrDefault() + .SetAttributeValue(key, value); + }; } } } diff --git a/test/Common.FunctionalTests/Inprocess/EventLogTests.cs b/test/Common.FunctionalTests/Inprocess/EventLogTests.cs index 176661189c..a2476cea1b 100644 --- a/test/Common.FunctionalTests/Inprocess/EventLogTests.cs +++ b/test/Common.FunctionalTests/Inprocess/EventLogTests.cs @@ -1,4 +1,6 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -20,5 +22,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests EventLogHelpers.VerifyEventLogEvent(TestSink, "Application '.+' started the coreclr in-process successfully."); } + + [ConditionalFact] + public async Task CheckShutdownEventLogMessage() + { + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); + deploymentParameters.GracefulShutdown = true; + var deploymentResult = await DeployAsync(deploymentParameters); + await Helpers.AssertStarts(deploymentResult); + + StopServer(); + + EventLogHelpers.VerifyEventLogEvent(TestSink, "Application '.+' has shutdown."); + } } } diff --git a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs index 6384fd508e..eb3294a537 100644 --- a/test/Common.FunctionalTests/Inprocess/LoggingTests.cs +++ b/test/Common.FunctionalTests/Inprocess/LoggingTests.cs @@ -21,17 +21,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [InlineData("CheckLogFile")] public async Task CheckStdoutLoggingToFile(string path) { - var deploymentParameters = Helpers.GetBaseDeploymentParameters(); - deploymentParameters.PublishApplicationBeforeDeployment = true; + var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); + + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogEnabled", "true")); + + var pathToLogs = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine(pathToLogs, "std"))); var deploymentResult = await DeployAsync(deploymentParameters); - var pathToLogs = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); try { - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogEnabled", "true"); - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", Path.Combine(pathToLogs, "std")); - await Helpers.AssertStarts(deploymentResult, path); StopServer(); @@ -63,10 +65,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); - var deploymentResult = await DeployAsync(deploymentParameters); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogEnabled", "true")); + deploymentParameters.WebConfigActionList.Add( + WebConfigHelpers.AddOrModifyAspNetCoreSection("stdoutLogFile", Path.Combine("Q:", "std"))); - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogEnabled", "true"); - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "stdoutLogFile", Path.Combine("Q:", "std")); + var deploymentResult = await DeployAsync(deploymentParameters); await Helpers.AssertStarts(deploymentResult, "HelloWorld"); } @@ -80,11 +84,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = tempFile; + deploymentParameters.AddDebugLogToWebConfig(tempFile); var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.AddDebugLogToWebConfig(deploymentResult.DeploymentResult.ContentRoot, tempFile); - var response = await deploymentResult.RetryingHttpClient.GetAsync("/"); StopServer(); @@ -123,11 +126,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task CheckStdoutLoggingToPipeWithFirstWrite(string path) { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); - - var deploymentResult = await DeployAsync(deploymentParameters); var firstWriteString = path + path; - Helpers.ModifyEnvironmentVariableCollectionInWebConfig(deploymentResult, "ASPNETCORE_INPROCESS_INITIAL_WRITE", firstWriteString); + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_INITIAL_WRITE"] = firstWriteString; + + var deploymentResult = await DeployAsync(deploymentParameters); await Helpers.AssertStarts(deploymentResult, path); @@ -152,9 +155,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); deploymentParameters.EnvironmentVariables["ASPNETCORE_MODULE_DEBUG_FILE"] = firstTempFile; + deploymentParameters.AddDebugLogToWebConfig(secondTempFile); var deploymentResult = await DeployAsync(deploymentParameters); - WebConfigHelpers.AddDebugLogToWebConfig(deploymentParameters.PublishedApplicationRootPath, secondTempFile); var response = await deploymentResult.RetryingHttpClient.GetAsync("/"); diff --git a/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs b/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs index 667c47327c..93683b8a78 100644 --- a/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs +++ b/test/Common.FunctionalTests/Inprocess/StartupExceptionTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; @@ -25,15 +24,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true); var randomNumberString = new Random(Guid.NewGuid().GetHashCode()).Next(10000000).ToString(); - - var environmentVariablesInWebConfig = new Dictionary - { - ["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path, - ["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString - }; + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_RANDOM_VALUE"] = randomNumberString; var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.AddEnvironmentVariablesToWebConfig(deploymentResult.DeploymentResult.ContentRoot, environmentVariablesInWebConfig); var response = await deploymentResult.RetryingHttpClient.GetAsync(path); @@ -52,16 +46,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task CheckStdoutWithLargeWrites(string path) { var deploymentParameters = Helpers.GetBaseDeploymentParameters("StartupExceptionWebsite", publish: true); - - var environmentVariablesInWebConfig = new Dictionary - { - ["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path - }; + deploymentParameters.WebConfigBasedEnvironmentVariables["ASPNETCORE_INPROCESS_STARTUP_VALUE"] = path; var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.AddEnvironmentVariablesToWebConfig(deploymentResult.DeploymentResult.ContentRoot, environmentVariablesInWebConfig); - var response = await deploymentResult.RetryingHttpClient.GetAsync(path); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); diff --git a/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs b/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs index 876b8e010f..8ae0c7dc89 100644 --- a/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs +++ b/test/Common.FunctionalTests/OutOfProcess/HelloWorldTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { // The default in hosting sets windows auth to true. // Set it to the IISExpress.config file - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetOutOfProcessTestSitesPath() }; diff --git a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs index 4827fcaecc..d23b7836e5 100644 --- a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs +++ b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.Extensions.Logging.Testing; using Xunit.Abstractions; @@ -16,9 +17,9 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting { } - private ApplicationDeployer _deployer; + protected ApplicationDeployer _deployer; - protected virtual async Task DeployAsync(DeploymentParameters parameters) + protected virtual async Task DeployAsync(IISDeploymentParameters parameters) { if (!parameters.EnvironmentVariables.ContainsKey(DebugEnvironmentVariable)) { @@ -30,11 +31,10 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting parameters.ServerConfigTemplateContent = parameters.ServerConfigTemplateContent ?? File.ReadAllText("IISExpress.config"); } + _deployer = IISApplicationDeployerFactory.Create(parameters, LoggerFactory); - var result = await _deployer.DeployAsync(); - - return new IISDeploymentResult(result, Logger); + return (IISDeploymentResult)await _deployer.DeployAsync(); } public override void Dispose() diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index 63f09dbac1..f4348514d1 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -2,13 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing; using Xunit; @@ -24,117 +24,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public static string GetInProcessTestSitesPath() => GetTestWebSitePath("InProcessWebSite"); public static string GetOutOfProcessTestSitesPath() => GetTestWebSitePath("OutOfProcessWebSite"); - - public static void ModifyAspNetCoreSectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) - => ModifyAttributeInWebConfig(deploymentResult, key, value, section: "aspNetCore"); - - public static void ModifyAttributeInWebConfig(IISDeploymentResult deploymentResult, string key, string value, string section) - { - var webConfigFile = GetWebConfigFile(deploymentResult); - var config = XDocument.Load(webConfigFile); - - var element = config.Descendants(section).Single(); - element.SetAttributeValue(key, value); - - config.Save(webConfigFile); - } - - public static void ModifyEnvironmentVariableCollectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) - { - var webConfigFile = GetWebConfigFile(deploymentResult); - var config = XDocument.Load(webConfigFile); - - var envVarElement = new XElement("environmentVariable"); - envVarElement.SetAttributeValue("name", key); - envVarElement.SetAttributeValue("value", value); - - config.Descendants("aspNetCore").Single() - .Descendants("environmentVariables").Single() - .Add(envVarElement); - - config.Save(webConfigFile); - } - - public static void ModifyHandlerSectionInWebConfig(IISDeploymentResult deploymentResult, string handlerVersionValue) - { - var webConfigFile = GetWebConfigFile(deploymentResult); - var config = XDocument.Load(webConfigFile); - - var handlerVersionElement = new XElement("handlerSetting"); - handlerVersionElement.SetAttributeValue("name", "handlerVersion"); - handlerVersionElement.SetAttributeValue("value", handlerVersionValue); - - config.Descendants("aspNetCore").Single() - .Add(new XElement("handlerSettings", handlerVersionElement)); - - config.Save(webConfigFile); - } - - public static void AddDebugLogToWebConfig(string contentRoot, string filename) - { - var path = Path.Combine(contentRoot, "web.config"); - var webconfig = XDocument.Load(path); - var xElement = webconfig.Descendants("aspNetCore").Single(); - - var element = xElement.Descendants("handlerSettings").SingleOrDefault(); - if (element == null) - { - element = new XElement("handlerSettings"); - xElement.Add(element); - } - - CreateOrSetElement(element, "debugLevel", "4", "handlerSetting"); - - CreateOrSetElement(element, "debugFile", Path.Combine(contentRoot, filename), "handlerSetting"); - - webconfig.Save(path); - } - - public static void AddEnvironmentVariablesToWebConfig(string contentRoot, IDictionary environmentVariables) - { - var path = Path.Combine(contentRoot, "web.config"); - var webconfig = XDocument.Load(path); - var xElement = webconfig.Descendants("aspNetCore").Single(); - - var element = xElement.Descendants("environmentVariables").SingleOrDefault(); - if (element == null) - { - element = new XElement("environmentVariables"); - xElement.Add(element); - } - - foreach (var envVar in environmentVariables) - { - CreateOrSetElement(element, envVar.Key, envVar.Value, "environmentVariable"); - } - - webconfig.Save(path); - } - - public static void CreateOrSetElement(XElement rootElement, string name, string value, string elementName) - { - if (rootElement.Descendants() - .Attributes() - .Where(attribute => attribute.Value == name) - .Any()) - { - return; - } - var element = new XElement(elementName); - element.SetAttributeValue("name", name); - element.SetAttributeValue("value", value); - rootElement.Add(element); - } - + // Defaults to inprocess specific deployment parameters - public static DeploymentParameters GetBaseDeploymentParameters(string site = null, HostingModel hostingModel = HostingModel.InProcess, bool publish = false) + public static IISDeploymentParameters GetBaseDeploymentParameters(string site = null, HostingModel hostingModel = HostingModel.InProcess, bool publish = false) { if (site == null) { site = hostingModel == HostingModel.InProcess ? "InProcessWebSite" : "OutOfProcessWebSite"; } - return new DeploymentParameters(GetTestWebSitePath(site), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + return new IISDeploymentParameters(GetTestWebSitePath(site), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { TargetFramework = Tfm.NetCoreApp22, ApplicationType = ApplicationType.Portable, @@ -144,9 +43,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests }; } - private static string GetWebConfigFile(IISDeploymentResult deploymentResult) - => Path.Combine(deploymentResult.DeploymentResult.ContentRoot, "web.config"); - public static async Task AssertStarts(IISDeploymentResult deploymentResult, string path = "/HelloWorld") { var response = await deploymentResult.RetryingHttpClient.GetAsync(path); diff --git a/test/Common.FunctionalTests/Utilities/IISDeploymentResult.cs b/test/Common.FunctionalTests/Utilities/IISDeploymentResult.cs deleted file mode 100644 index 8e39634f2b..0000000000 --- a/test/Common.FunctionalTests/Utilities/IISDeploymentResult.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net.Http; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Server.IntegrationTesting -{ - public class IISDeploymentResult - { - public DeploymentResult DeploymentResult { get; } - public ILogger Logger { get; } - - public IISDeploymentResult(DeploymentResult deploymentResult, ILogger logger) - { - DeploymentResult = deploymentResult; - Logger = logger; - - RetryingHttpClient = CreateRetryClient(new SocketsHttpHandler()); - HttpClient = CreateClient(new SocketsHttpHandler()); - } - - public HttpClient CreateRetryClient(HttpMessageHandler messageHandler) - { - return new HttpClient(new RetryHandler(new LoggingHandler(messageHandler, Logger), Logger)) - { - BaseAddress = DeploymentResult.HttpClient.BaseAddress - }; - } - - public HttpClient CreateClient(HttpMessageHandler messageHandler) - { - return new HttpClient(new LoggingHandler(messageHandler, Logger)) - { - BaseAddress = DeploymentResult.HttpClient.BaseAddress - }; - } - - public HttpClient HttpClient { get; set; } - public HttpClient RetryingHttpClient { get; set; } - } -} diff --git a/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs b/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs index d4c74c551b..a20a5e2e0e 100644 --- a/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs +++ b/test/Common.FunctionalTests/Utilities/IISFunctionalTestBase.cs @@ -1,11 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Xml.Linq; -using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.Server.IntegrationTesting; using Xunit.Abstractions; @@ -16,36 +11,5 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities public IISFunctionalTestBase(ITestOutputHelper output = null) : base(output) { } - - protected string GetServerConfig(Action transform) - { - var doc = XDocument.Load(DeployerSelector.ServerType == ServerType.IIS ? "IIS.config" : "IISExpress.config"); - transform?.Invoke(doc.Root); - return doc.ToString(); - } - - protected string GetHttpsServerConfig() - { - return GetServerConfig( - element => { - element.Descendants("binding") - .Single() - .SetAttributeValue("protocol", "https"); - - element.Descendants("access") - .Single() - .SetAttributeValue("sslFlags", "Ssl, SslNegotiateCert"); - }); - } - - protected string GetWindowsAuthConfig() - { - return GetServerConfig( - element => { - element.Descendants("windowsAuthentication") - .Single() - .SetAttributeValue("enabled", "true"); - }); - } } } diff --git a/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs index d8fd3342ed..787276a8fa 100644 --- a/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs +++ b/test/Common.FunctionalTests/Utilities/IISTestSiteFixture.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; @@ -20,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var logging = AssemblyTestLog.ForAssembly(typeof(IISTestSiteFixture).Assembly); - var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), + var deploymentParameters = new IISDeploymentParameters(Helpers.GetInProcessTestSitesPath(), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) diff --git a/test/CommonLibTests/stdafx.h b/test/CommonLibTests/stdafx.h index 33d3008f40..93dffac456 100644 --- a/test/CommonLibTests/stdafx.h +++ b/test/CommonLibTests/stdafx.h @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include diff --git a/test/IIS.Tests/Utilities/TestServer.cs b/test/IIS.Tests/Utilities/TestServer.cs index c64fe4fbe9..4adaaff776 100644 --- a/test/IIS.Tests/Utilities/TestServer.cs +++ b/test/IIS.Tests/Utilities/TestServer.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj b/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj index cbd3d05b6b..3ae8fcffe8 100644 --- a/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj +++ b/test/IISExpress.FunctionalTests/IISExpress.FunctionalTests.csproj @@ -29,7 +29,6 @@ - diff --git a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs index b7e850cdb8..265907542f 100644 --- a/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/AppOfflineTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging; using Xunit; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess { @@ -26,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess { var deploymentResult = await DeployApp(hostingModel); - AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); + AddAppOffline(deploymentResult.ContentRoot); await AssertAppOffline(deploymentResult); } @@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess var expectedResponse = "The app is offline."; var deploymentResult = await DeployApp(hostingModel); - AddAppOffline(deploymentResult.DeploymentResult.ContentRoot, expectedResponse); + AddAppOffline(deploymentResult.ContentRoot, expectedResponse); await AssertAppOffline(deploymentResult, expectedResponse); } @@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess { var deploymentResult = await AssertStarts(HostingModel.InProcess); - AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); + AddAppOffline(deploymentResult.ContentRoot); await AssertStopsProcess(deploymentResult); } @@ -62,9 +63,9 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess // Repeat dropping file and restarting multiple times for (int i = 0; i < 5; i++) { - AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); + AddAppOffline(deploymentResult.ContentRoot); await AssertAppOffline(deploymentResult); - RemoveAppOffline(deploymentResult.DeploymentResult.ContentRoot); + RemoveAppOffline(deploymentResult.ContentRoot); await AssertRunning(deploymentResult); } } @@ -76,11 +77,11 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess { var deploymentResult = await DeployApp(hostingModel); - AddAppOffline(deploymentResult.DeploymentResult.ContentRoot); + AddAppOffline(deploymentResult.ContentRoot); await AssertAppOffline(deploymentResult); - RemoveAppOffline(deploymentResult.DeploymentResult.ContentRoot); + RemoveAppOffline(deploymentResult.ContentRoot); await AssertRunning(deploymentResult); } @@ -132,7 +133,7 @@ namespace Microsoft.AspNetCore.Server.IIS.FunctionalTests.Inprocess // dropping app_offline will kill the process } - var hostShutdownToken = deploymentResult.DeploymentResult.HostShutdownToken; + var hostShutdownToken = deploymentResult.HostShutdownToken; Assert.True(hostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); Assert.True(hostShutdownToken.IsCancellationRequested); diff --git a/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs b/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs index 31179b1ecf..5105b753c8 100644 --- a/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/AuthenticationTests.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; @@ -17,7 +18,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task Authentication_InProcess() { var deploymentParameters = Helpers.GetBaseDeploymentParameters(publish: true); - deploymentParameters.ServerConfigTemplateContent = GetWindowsAuthConfig(); + deploymentParameters.AddWindowsAuthToServerConfig(); var deploymentResult = await DeployAsync(deploymentParameters); @@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); var httpClientHandler = new HttpClientHandler() { UseDefaultCredentials = true }; - var httpClient = deploymentResult.DeploymentResult.CreateHttpClient(httpClientHandler); + var httpClient = deploymentResult.CreateHttpClient(httpClientHandler); response = await httpClient.GetAsync("/AuthenticationAnonymous"); responseText = await response.Content.ReadAsStringAsync(); diff --git a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs index 179c6bd198..b1ab071c5d 100644 --- a/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/ShutdownTests.cs @@ -24,7 +24,30 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var result = await DeployAsync(parameters); var response = await result.RetryingHttpClient.GetAsync("/Shutdown"); - Assert.True(result.DeploymentResult.HostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); + Assert.True(result.HostShutdownToken.WaitHandle.WaitOne(TimeoutExtensions.DefaultTimeout)); + } + + [ConditionalFact] + public async Task GracefulShutdown_DoesNotCrashProcess() + { + var parameters = Helpers.GetBaseDeploymentParameters(publish: true); + parameters.GracefulShutdown = true; + var result = await DeployAsync(parameters); + + var response = await result.RetryingHttpClient.GetAsync("/HelloWorld"); + StopServer(); + Assert.True(result.HostProcess.ExitCode == 0); + } + + [ConditionalFact] + public async Task ForcefulShutdown_DoesrashProcess() + { + var parameters = Helpers.GetBaseDeploymentParameters(publish: true); + var result = await DeployAsync(parameters); + + var response = await result.RetryingHttpClient.GetAsync("/HelloWorld"); + StopServer(); + Assert.True(result.HostProcess.ExitCode == 1); } } } diff --git a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs index 4de6ceba3d..36812d3f2e 100644 --- a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs @@ -8,6 +8,7 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -27,8 +28,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { // Point to dotnet installed in user profile. await AssertStarts( - deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"), - deploymentParameters => deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation); + deploymentParameters => + { + deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "%DotnetPath%")); + } + ); } [ConditionalTheory] @@ -38,14 +43,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task InvalidProcessPath_ExpectServerError(string path) { var deploymentParameters = GetBaseDeploymentParameters(); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); + var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path); - var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + EventLogHelpers.VerifyEventLogEvent(TestSink, @"Invalid or unknown processPath provided in web\.config: processPath = '.+', ErrorCode = '0x80070002'\."); } [ConditionalFact] @@ -54,16 +61,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")); await AssertStarts( - deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", dotnetLocationWithoutExtension)); + deploymentParameters => + { + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + } + ); } [ConditionalFact] public async Task StartsWithDotnetLocationUppercase() { var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")).ToUpperInvariant(); - await AssertStarts( - deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", dotnetLocationWithoutExtension)); + deploymentParameters => + { + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + } + ); } [ConditionalTheory] @@ -72,14 +86,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task StartsWithDotnetOnThePath(string path) { await AssertStarts( - deploymentResult => Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", path), - deploymentParameters => deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation)); + deploymentParameters => + { + deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); + } + ); // Verify that in this scenario where.exe was invoked only once by shim and request handler uses cached value Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains("Invoking where.exe to find dotnet.exe"))); } - private async Task AssertStarts(Action postDeploy, Action preDeploy = null) + private async Task AssertStarts(Action preDeploy = null) { var deploymentParameters = GetBaseDeploymentParameters(); @@ -87,8 +105,6 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var deploymentResult = await DeployAsync(deploymentParameters); - postDeploy?.Invoke(deploymentResult); - var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); var responseText = await response.Content.ReadAsStringAsync(); @@ -105,7 +121,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [MemberData(nameof(TestVariants))] public async Task HelloWorld(TestVariant variant) { - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetInProcessTestSitesPath(), PublishApplicationBeforeDeployment = true @@ -131,10 +147,25 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests Assert.Contains(TestSink.Writes, context => context.Message.Contains("Application is running inside IIS process but is not configured to use IIS server")); } - // Defaults to inprocess specific deployment parameters - public static DeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") + [ConditionalFact] + public async Task CheckInvalidHostingModelParameter() { - return new DeploymentParameters(Helpers.GetTestWebSitePath(site), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + var deploymentParameters = GetBaseDeploymentParameters(); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("hostingModel", "bogus")); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.RetryingHttpClient.GetAsync("HelloWorld"); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + + EventLogHelpers.VerifyEventLogEvent(TestSink, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file."); + } + + // Defaults to inprocess specific deployment parameters + public static IISDeploymentParameters GetBaseDeploymentParameters(string site = "InProcessWebSite") + { + return new IISDeploymentParameters(Helpers.GetTestWebSitePath(site), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { TargetFramework = Tfm.NetCoreApp22, ApplicationType = ApplicationType.Portable, diff --git a/test/IISExpress.FunctionalTests/OutOfProcess/GlobalVersionTests.cs b/test/IISExpress.FunctionalTests/OutOfProcess/GlobalVersionTests.cs index fd53c0ffbb..8ad7a70665 100644 --- a/test/IISExpress.FunctionalTests/OutOfProcess/GlobalVersionTests.cs +++ b/test/IISExpress.FunctionalTests/OutOfProcess/GlobalVersionTests.cs @@ -1,16 +1,15 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; -using Xunit.Abstractions; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { @@ -28,7 +27,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); deploymentParameters.PublishApplicationBeforeDeployment = false; - deploymentParameters.ServerConfigTemplateContent = GetServerConfig( + deploymentParameters.AddServerConfigAction( element => { var handlerVersionElement = new XElement("handlerSetting"); @@ -52,11 +51,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task GlobalVersion_NewVersionNumber_Fails(string version) { var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + deploymentParameters.HandlerSettings["handlerVersion"] = version; var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.ModifyHandlerSectionInWebConfig(deploymentResult, version); - var response = await deploymentResult.RetryingHttpClient.GetAsync(_helloWorldRequest); Assert.False(response.IsSuccessStatusCode); } @@ -68,11 +66,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); deploymentParameters.AdditionalPublishParameters = $"{_outOfProcessVersionVariable}{version}"; + deploymentParameters.HandlerSettings["handlerVersion"] = version; var deploymentResult = await DeployAsync(deploymentParameters); - Helpers.ModifyHandlerSectionInWebConfig(deploymentResult, version); - var response = await deploymentResult.RetryingHttpClient.GetAsync(_helloWorldRequest); var responseText = await response.Content.ReadAsStringAsync(); Assert.Equal(_helloWorldResponse, responseText); @@ -140,9 +137,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests AssertLoadedVersion(version); } - private DeploymentParameters GetGlobalVersionBaseDeploymentParameters() + private IISDeploymentParameters GetGlobalVersionBaseDeploymentParameters() { - return new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + return new IISDeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { TargetFramework = Tfm.NetCoreApp22, ApplicationType = ApplicationType.Portable, @@ -155,8 +152,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private string GetANCMRequestHandlerPath(IISDeploymentResult deploymentResult, string version) { - return Path.Combine(deploymentResult.DeploymentResult.ContentRoot, - deploymentResult.DeploymentResult.DeploymentParameters.RuntimeArchitecture.ToString(), + return Path.Combine(deploymentResult.ContentRoot, + deploymentResult.DeploymentParameters.RuntimeArchitecture.ToString(), version, _aspNetCoreDll); } diff --git a/test/IISExpress.FunctionalTests/OutOfProcess/HttpsTest.cs b/test/IISExpress.FunctionalTests/OutOfProcess/HttpsTest.cs index 543481ee14..ff9996c851 100644 --- a/test/IISExpress.FunctionalTests/OutOfProcess/HttpsTest.cs +++ b/test/IISExpress.FunctionalTests/OutOfProcess/HttpsTest.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.Common; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -33,13 +34,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task HttpsHelloWorld(TestVariant variant) { var port = TestPortHelper.GetNextSSLPort(); - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(), - ApplicationBaseUriHint = $"https://localhost:{port}/", - ServerConfigTemplateContent = GetHttpsServerConfig() + ApplicationBaseUriHint = $"https://localhost:{port}/" }; + deploymentParameters.AddHttpsToServerConfig(); + var deploymentResult = await DeployAsync(deploymentParameters); var handler = new HttpClientHandler @@ -71,13 +73,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private async Task HttpsHelloWorldCerts(TestVariant variant, bool sendClientCert) { var port = TestPortHelper.GetNextSSLPort(); - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(), ApplicationBaseUriHint = $"https://localhost:{port}/", - ServerConfigTemplateContent = GetHttpsServerConfig() }; + deploymentParameters.AddHttpsToServerConfig(); + var deploymentResult = await DeployAsync(deploymentParameters); var handler = new HttpClientHandler diff --git a/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs b/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs index 9aab93a56f..437f019f52 100644 --- a/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs +++ b/test/IISExpress.FunctionalTests/OutOfProcess/NtlmAuthentationTest.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -31,13 +32,14 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [MemberData(nameof(TestVariants))] public async Task NtlmAuthentication(TestVariant variant) { - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(), - ApplicationBaseUriHint = $"http://localhost:0/", - ServerConfigTemplateContent = GetWindowsAuthConfig() + ApplicationBaseUriHint = $"http://localhost:0/" }; + deploymentParameters.AddWindowsAuthToServerConfig(); + var result = await DeployAsync(deploymentParameters); var response = await result.RetryingHttpClient.GetAsync("/HelloWorld"); diff --git a/test/IISExpress.FunctionalTests/OutOfProcess/WindowsAuthTests.cs b/test/IISExpress.FunctionalTests/OutOfProcess/WindowsAuthTests.cs index 83d6a8e20a..038f57029d 100644 --- a/test/IISExpress.FunctionalTests/OutOfProcess/WindowsAuthTests.cs +++ b/test/IISExpress.FunctionalTests/OutOfProcess/WindowsAuthTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -27,11 +28,11 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [MemberData(nameof(TestVariants))] public async Task WindowsAuthTest(TestVariant variant) { - var deploymentParameters = new DeploymentParameters(variant) + var deploymentParameters = new IISDeploymentParameters(variant) { ApplicationPath = Helpers.GetOutOfProcessTestSitesPath(), - ServerConfigTemplateContent = GetWindowsAuthConfig() }; + deploymentParameters.AddWindowsAuthToServerConfig(); // The default in hosting sets windows auth to true. var deploymentResult = await DeployAsync(deploymentParameters); diff --git a/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs b/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs index dc64262d00..316a87f9b3 100644 --- a/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs +++ b/test/IISExpress.FunctionalTests/UpgradeFeatureDetectionTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Testing.xunit; using Xunit; using Xunit.Abstractions; @@ -59,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests private async Task UpgradeFeatureDetectionDeployer(bool disableWebSocket, string sitePath, string expected, HostingModel hostingModel) { - var deploymentParameters = new DeploymentParameters(sitePath, DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + var deploymentParameters = new IISDeploymentParameters(sitePath, DeployerSelector.ServerType, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) { TargetFramework = Tfm.NetCoreApp22, ApplicationType = ApplicationType.Portable, @@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests if (disableWebSocket) { // For IIS, we need to modify the apphost.config file - deploymentParameters.ServerConfigTemplateContent = GetServerConfig( + deploymentParameters.AddServerConfigAction( element => element.Descendants("webSocket") .Single() .SetAttributeValue("enabled", "false"));