From 9d97ff38f8ecc1b4cbd95c806badc48fafe247ef Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Tue, 12 Jun 2018 14:04:21 -0700 Subject: [PATCH] Load ANCM out of process via global versioning (#895) --- build/testsite.props | 4 +- nuget/AspNetCoreV2.nuspec | 4 +- ...icrosoft.AspNetCore.AspNetCoreModule.props | 4 +- .../AspNetCore/Inc/aspnetcore_shim_config.h | 10 ++ .../AspNetCore/src/applicationinfo.cpp | 69 +++----- .../AspNetCore/src/aspnetcore_shim_config.cpp | 3 + .../CommonLib/CommonLib.vcxproj | 3 + .../CommonLib/GlobalVersionUtility.cpp | 131 ++++++++++++++ .../CommonLib/GlobalVersionUtility.h | 31 ++++ .../CommonLib/config_utility.h | 106 ++++++++++++ src/AspNetCoreModuleV2/CommonLib/stdafx.h | 6 +- src/AspNetCoreModuleV2/CommonLib/utility.cxx | 2 +- src/AspNetCoreModuleV2/CommonLib/utility.h | 3 + test/CommonLibTests/CommonLibTests.vcxproj | 12 +- test/CommonLibTests/ConfigUtilityTests.cpp | 106 ++++++++++++ .../CommonLibTests/FileOutputManagerTests.cpp | 2 - test/CommonLibTests/GlobalVersionTests.cpp | 153 +++++++++++++++++ test/CommonLibTests/fakeclasses.h | 57 ++++++- test/CommonLibTests/stdafx.h | 5 +- .../OutOfProcess/GlobalVersionTests.cs | 160 ++++++++++++++++++ .../Utilities/Helpers.cs | 30 +++- test/WebSites/OutOfProcessWebSite/Startup.cs | 19 +++ test/gtest/gtest.vcxproj | 9 +- version.props | 1 + 24 files changed, 862 insertions(+), 68 deletions(-) create mode 100644 src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/config_utility.h create mode 100644 test/CommonLibTests/ConfigUtilityTests.cpp create mode 100644 test/CommonLibTests/GlobalVersionTests.cpp create mode 100644 test/IISIntegration.FunctionalTests/OutOfProcess/GlobalVersionTests.cs diff --git a/build/testsite.props b/build/testsite.props index be0f2c72cc..42e3cb1d8e 100644 --- a/build/testsite.props +++ b/build/testsite.props @@ -29,8 +29,8 @@ - - + + diff --git a/nuget/AspNetCoreV2.nuspec b/nuget/AspNetCoreV2.nuspec index 6de70ba91a..5d26da23c8 100644 --- a/nuget/AspNetCoreV2.nuspec +++ b/nuget/AspNetCoreV2.nuspec @@ -21,8 +21,8 @@ - - + + diff --git a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props index 38b8eafcc4..1f33b20ed2 100644 --- a/nuget/Microsoft.AspNetCore.AspNetCoreModule.props +++ b/nuget/Microsoft.AspNetCore.AspNetCoreModule.props @@ -5,8 +5,8 @@ $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcore.dll $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorev2_inprocess.dll $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorev2_inprocess.dll - $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\aspnetcorev2_outofprocess.dll - $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\aspnetcorev2_outofprocess.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x64\$(AspNetCoreModuleOutOfProcessVersion)\aspnetcorev2_outofprocess.dll + $(MSBuildThisFileDirectory)..\contentFiles\any\any\x86\$(AspNetCoreModuleOutOfProcessVersion)\aspnetcorev2_outofprocess.dll diff --git a/src/AspNetCoreModuleV2/AspNetCore/Inc/aspnetcore_shim_config.h b/src/AspNetCoreModuleV2/AspNetCore/Inc/aspnetcore_shim_config.h index 2d1e8e7944..36f2a18c51 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/Inc/aspnetcore_shim_config.h +++ b/src/AspNetCoreModuleV2/AspNetCore/Inc/aspnetcore_shim_config.h @@ -4,6 +4,7 @@ #pragma once #include "precomp.hxx" +#include #define CS_ASPNETCORE_SECTION L"system.webServer/aspNetCore" #define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" @@ -105,6 +106,14 @@ public: return m_hostingModel; } + STRU* + QueryHandlerVersion( + VOID + ) + { + return &m_struHandlerVersion; + } + private: ASPNETCORE_SHIM_CONFIG() : m_cRefs(1), @@ -120,5 +129,6 @@ private: STRU m_struConfigPath; APP_HOSTING_MODEL m_hostingModel; STRU m_struHostFxrLocation; + STRU m_struHandlerVersion; }; diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp index 3f18c63507..38e4d8cb80 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/applicationinfo.cpp @@ -358,60 +358,47 @@ Finished: HRESULT APPLICATION_INFO::FindNativeAssemblyFromGlobalLocation( - PCWSTR libraryName, - STRU* struFilename) + PCWSTR pstrHandlerDllName, + STRU* struFilename +) { HRESULT hr = S_OK; - DWORD dwSize = MAX_PATH; - BOOL fDone = FALSE; - DWORD dwPosition = 0; - // Though we could call LoadLibrary(L"aspnetcorerh.dll") relying the OS to solve - // the path (the targeted dll is the same folder of w3wp.exe/iisexpress) - // let's still load with full path to avoid security issue - if (FAILED(hr = struFilename->Resize(dwSize + 20))) + try { - goto Finished; - } + std::wstring modulePath = GlobalVersionUtility::GetModuleName(g_hModule); - while (!fDone) - { - DWORD dwReturnedSize = GetModuleFileNameW(g_hModule, struFilename->QueryStr(), dwSize); - if (dwReturnedSize == 0) + modulePath = GlobalVersionUtility::RemoveFileNameFromFolderPath(modulePath); + + std::wstring retval = GlobalVersionUtility::GetGlobalRequestHandlerPath(modulePath.c_str(), + m_pConfiguration->QueryHandlerVersion()->QueryStr(), + pstrHandlerDllName + ); + + if (FAILED(hr = struFilename->Copy(retval.c_str()))) { - hr = HRESULT_FROM_WIN32(GetLastError()); - fDone = TRUE; - goto Finished; - } - else if ((dwReturnedSize == dwSize) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) - { - dwSize *= 2; // smaller buffer. increase the buffer and retry - if (FAILED(hr = struFilename->Resize(dwSize + 40))) // + 40 for aspnetcorerh.dll - { - goto Finished; - } - } - else - { - fDone = TRUE; + return hr; } } - - if (FAILED(hr = struFilename->SyncWithBuffer())) + catch (std::exception& e) { - goto Finished; + STRU struEvent; + if (SUCCEEDED(struEvent.Copy(ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG)) + && SUCCEEDED(struEvent.AppendA(e.what()))) + { + UTILITY::LogEvent(g_hEventLog, + EVENTLOG_INFORMATION_TYPE, + ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING, + struEvent.QueryStr()); + } + + hr = E_FAIL; } - dwPosition = struFilename->LastIndexOf(L'\\', 0); - struFilename->QueryStr()[dwPosition] = L'\0'; - - if (FAILED(hr = struFilename->SyncWithBuffer()) || - FAILED(hr = struFilename->Append(L"\\")) || - FAILED(hr = struFilename->Append(libraryName))) + catch (...) { - goto Finished; + hr = E_FAIL; } -Finished: return hr; } diff --git a/src/AspNetCoreModuleV2/AspNetCore/src/aspnetcore_shim_config.cpp b/src/AspNetCoreModuleV2/AspNetCore/src/aspnetcore_shim_config.cpp index 1a59586160..08fd9821f3 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/src/aspnetcore_shim_config.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/src/aspnetcore_shim_config.cpp @@ -3,6 +3,7 @@ #include "aspnetcore_shim_config.h" +#include "config_utility.h" #include "hostfxr_utility.h" #include "debugutil.h" #include "ahutil.h" @@ -192,6 +193,8 @@ ASPNETCORE_SHIM_CONFIG::Populate( goto Finished; } + hr = ConfigUtility::FindHandlerVersion(pAspNetCoreElement, &m_struHandlerVersion); + Finished: if (pAspNetCoreElement != NULL) diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 42079abe33..353c0b8b9f 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -181,6 +181,8 @@ + + @@ -205,6 +207,7 @@ + diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp new file mode 100644 index 0000000000..09a927104a --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.cpp @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "stdafx.h" +#include + +namespace fs = std::experimental::filesystem; + +// throws runtime error if no request handler versions are installed. +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::GetGlobalRequestHandlerPath(PCWSTR pwzAspNetCoreFolderPath, PCWSTR pwzHandlerVersion, PCWSTR pwzHandlerName) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw new std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + if (pwzHandlerVersion == NULL) + { + throw new std::invalid_argument("pwzHandlerVersion is NULL"); + } + + if (pwzHandlerName == NULL) + { + throw new std::invalid_argument("pwzHandlerName is NULL"); + } + + std::wstring folderVersion(pwzHandlerVersion); + fs::path aspNetCoreFolderPath(pwzAspNetCoreFolderPath); + + if (folderVersion.empty()) + { + folderVersion = FindHighestGlobalVersion(pwzAspNetCoreFolderPath); + } + + aspNetCoreFolderPath = aspNetCoreFolderPath + .append(folderVersion) + .append(pwzHandlerName); + return aspNetCoreFolderPath; +} + +// Throw filesystem_error if directory_iterator can't iterate over the directory +// Throw invalid_argument if any argument is null +std::vector +GlobalVersionUtility::GetRequestHandlerVersions(PCWSTR pwzAspNetCoreFolderPath) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw new std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + std::vector versionsInDirectory; + for (auto& p : fs::directory_iterator(pwzAspNetCoreFolderPath)) + { + if (!fs::is_directory(p)) + { + continue; + } + + fx_ver_t requested_ver(-1, -1, -1); + if (fx_ver_t::parse(p.path().filename(), &requested_ver, false)) + { + versionsInDirectory.push_back(requested_ver); + } + } + return versionsInDirectory; +} + +// throws runtime error if no request handler versions are installed. +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::FindHighestGlobalVersion(PCWSTR pwzAspNetCoreFolderPath) +{ + if (pwzAspNetCoreFolderPath == NULL) + { + throw new std::invalid_argument("pwzAspNetCoreFolderPath is NULL"); + } + + std::vector versionsInDirectory = GetRequestHandlerVersions(pwzAspNetCoreFolderPath); + if (versionsInDirectory.empty()) + { + throw new std::runtime_error("Cannot find request handler next to aspnetcorev2.dll. Verify a version of the request handler is installed in a version folder."); + } + std::sort(versionsInDirectory.begin(), versionsInDirectory.end()); + + return versionsInDirectory.back().as_str(); +} + +// Throws std::out_of_range if there is an index out of range +// Throw invalid_argument if any argument is null +std::wstring +GlobalVersionUtility::RemoveFileNameFromFolderPath(std::wstring fileName) +{ + fs::path path(fileName); + return path.parent_path(); +} + +std::wstring +GlobalVersionUtility::GetModuleName(HMODULE hModuleName) +{ + DWORD dwSize = MAX_PATH; + BOOL fDone = FALSE; + + // Instead of creating a temporary buffer, use the std::wstring directly as the receive buffer. + std::wstring retVal; + retVal.resize(dwSize); + + while (!fDone) + { + DWORD dwReturnedSize = GetModuleFileNameW(hModuleName, &retVal[0], dwSize); + if (dwReturnedSize == 0) + { + throw new std::runtime_error("GetModuleFileNameW returned 0."); + } + else if ((dwReturnedSize == dwSize) && (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + { + dwSize *= 2; + retVal.resize(dwSize); // smaller buffer. increase the buffer and retry + } + else + { + // GetModuleFilename will not account for the null terminator + // std::wstring auto appends one, so we don't need to subtract 1 when resizing. + retVal.resize(dwReturnedSize); + fDone = TRUE; + } + } + + return retVal; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h new file mode 100644 index 0000000000..fd1a5ee252 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/GlobalVersionUtility.h @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once +#include + +class GlobalVersionUtility +{ +public: + + static + std::wstring + GetGlobalRequestHandlerPath(PCWSTR pwzAspNetCoreFolderPath, PCWSTR pwzHandlerVersion, PCWSTR pwzHandlerName); + + static + std::wstring + FindHighestGlobalVersion(PCWSTR pwzAspNetCoreFolderPath); + + static + std::wstring + RemoveFileNameFromFolderPath(std::wstring fileName); + + static + std::vector + GetRequestHandlerVersions(PCWSTR pwzAspNetCoreFolderPath); + + static + std::wstring + GetModuleName(HMODULE hModuleName); +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/config_utility.h b/src/AspNetCoreModuleV2/CommonLib/config_utility.h new file mode 100644 index 0000000000..0a53ff63e3 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/config_utility.h @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#pragma once + +#include "stdafx.h" + +class ConfigUtility +{ + #define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" + #define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" + #define CS_ASPNETCORE_HANDLER_SETTINGS_NAME L"name" + #define CS_ASPNETCORE_HANDLER_SETTINGS_VALUE L"value" + +public: + static + HRESULT + FindHandlerVersion(IAppHostElement* pElement, STRU* strHandlerVersionValue) + { + HRESULT hr = S_OK; + IAppHostElement *pHandlerSettings = NULL; + IAppHostElementCollection *pHandlerSettingsCollection = NULL; + ENUM_INDEX index; + IAppHostElement *pHandlerVar = NULL; + STRU strHandlerName; + STRU strHandlerValue; + + hr = GetElementChildByName(pElement, + CS_ASPNETCORE_HANDLER_SETTINGS, + &pHandlerSettings); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pHandlerSettings->get_Collection(&pHandlerSettingsCollection); + if (FAILED(hr)) + { + goto Finished; + } + + for (hr = FindFirstElement(pHandlerSettingsCollection, &index, &pHandlerVar); + SUCCEEDED(hr); + hr = FindNextElement(pHandlerSettingsCollection, &index, &pHandlerVar)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + hr = GetElementStringProperty(pHandlerVar, + CS_ASPNETCORE_HANDLER_SETTINGS_NAME, + &strHandlerName); + + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pHandlerVar, + CS_ASPNETCORE_HANDLER_SETTINGS_VALUE, + &strHandlerValue); + + if (FAILED(hr)) + { + goto Finished; + + } + + if (strHandlerName.Equals(CS_ASPNETCORE_HANDLER_VERSION)) + { + hr = strHandlerVersionValue->Copy(strHandlerValue); + goto Finished; + } + + strHandlerName.Reset(); + strHandlerValue.Reset(); + + pHandlerVar->Release(); + pHandlerVar = NULL; + } + Finished: + + if (pHandlerVar != NULL) + { + pHandlerVar->Release(); + pHandlerVar = NULL; + } + + if (pHandlerSettingsCollection != NULL) + { + pHandlerSettingsCollection->Release(); + pHandlerSettingsCollection = NULL; + } + + if (pHandlerSettings != NULL) + { + pHandlerSettings->Release(); + pHandlerSettings = NULL; + } + + return hr; + } +}; + diff --git a/src/AspNetCoreModuleV2/CommonLib/stdafx.h b/src/AspNetCoreModuleV2/CommonLib/stdafx.h index 969590ea6e..b75217ae4d 100644 --- a/src/AspNetCoreModuleV2/CommonLib/stdafx.h +++ b/src/AspNetCoreModuleV2/CommonLib/stdafx.h @@ -14,6 +14,8 @@ #include #include #include +#include + #include "Shlwapi.h" #include #include "hashtable.h" @@ -31,10 +33,11 @@ #include "application.h" #include "SRWLockWrapper.h" #include "environmentvariablehash.h" +#include "fx_ver.h" #include "utility.h" +#include "GlobalVersionUtility.h" #include "resources.h" #include "aspnetcore_msg.h" -#include "fx_ver.h" #include "hostfxr_utility.h" #include "hostfxroptions.h" #include "IOutputManager.h" @@ -42,3 +45,4 @@ #include "PipeOutputManager.h" #include "NullOutputManager.h" #include "LoggingHelpers.h" + diff --git a/src/AspNetCoreModuleV2/CommonLib/utility.cxx b/src/AspNetCoreModuleV2/CommonLib/utility.cxx index a7bb7b1d63..965b13eb58 100644 --- a/src/AspNetCoreModuleV2/CommonLib/utility.cxx +++ b/src/AspNetCoreModuleV2/CommonLib/utility.cxx @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -#include"stdafx.h" +#include "stdafx.h" // static HRESULT diff --git a/src/AspNetCoreModuleV2/CommonLib/utility.h b/src/AspNetCoreModuleV2/CommonLib/utility.h index 58a5824044..f1f2c38afd 100644 --- a/src/AspNetCoreModuleV2/CommonLib/utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/utility.h @@ -3,6 +3,8 @@ #pragma once +#include "stdafx.h" + class UTILITY { public: @@ -121,3 +123,4 @@ private: UTILITY() {} ~UTILITY() {} }; + diff --git a/test/CommonLibTests/CommonLibTests.vcxproj b/test/CommonLibTests/CommonLibTests.vcxproj index 33756b7bd0..2a21ef619e 100644 --- a/test/CommonLibTests/CommonLibTests.vcxproj +++ b/test/CommonLibTests/CommonLibTests.vcxproj @@ -50,7 +50,9 @@ + + @@ -93,7 +95,7 @@ EnableFastChecks MultiThreadedDebug Level3 - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" stdcpp17 @@ -118,7 +120,7 @@ EnableFastChecks MultiThreadedDebug Level3 - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" stdcpp17 @@ -141,7 +143,7 @@ MultiThreaded Level3 ProgramDatabase - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" stdcpp17 @@ -166,9 +168,9 @@ MultiThreaded Level3 ProgramDatabase - $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; + $(MSBuildThisFileDirectory)include;%(AdditionalIncludeDirectories);..\..\src\AspNetCoreModuleV2\IISLib;..\..\src\AspNetCoreModuleV2\CommonLib;..\gtest\googletest\googletest\include;..\gtest\googletest\googlemock\include;...\..\src\AspNetCoreModuleV2\AspNetCore\Inc;..\..\src\AspNetCoreModuleV2\InProcessRequestHandler\; /D "_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" - stdcpp14 + stdcpp17 true diff --git a/test/CommonLibTests/ConfigUtilityTests.cpp b/test/CommonLibTests/ConfigUtilityTests.cpp new file mode 100644 index 0000000000..fbed259117 --- /dev/null +++ b/test/CommonLibTests/ConfigUtilityTests.cpp @@ -0,0 +1,106 @@ +// 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. + +#include "stdafx.h" +#include "gmock/gmock.h" +using ::testing::_; +using ::testing::NiceMock; + +namespace ConfigUtilityTests +{ + TEST(ConfigUtilityTest, HandlerVersionSet) + { + IAppHostElement* retElement = NULL; + + STRU handlerVersion; + + // NiceMock removes warnings about "uninteresting calls", + auto element = std::make_unique>(); + auto innerElement = std::make_unique>(); + auto collection = std::make_unique>(); + auto nameElement = std::make_unique>(); + auto mockProperty = std::make_unique>(); + + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(innerElement.get()), testing::Return(S_OK))); + ON_CALL(*innerElement, get_Collection(_)) + .WillByDefault(testing::DoAll(testing::SetArgPointee<0>(collection.get()), testing::Return(S_OK))); + ON_CALL(*collection, get_Count(_)) + .WillByDefault(DoAll(testing::SetArgPointee<0>(1), testing::Return(S_OK))); + ON_CALL(*collection, get_Item(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nameElement.get()), testing::Return(S_OK))); + ON_CALL(*nameElement, GetPropertyByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(mockProperty.get()), testing::Return(S_OK))); + EXPECT_CALL(*mockProperty, get_StringValue(_)) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"handlerVersion")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value")), testing::Return(S_OK))); + + HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion); + + EXPECT_STREQ(handlerVersion.QueryStr(), L"value"); + } + + TEST(ConfigUtilityTest, NoHandlerVersion) + { + IAppHostElement* retElement = NULL; + + STRU handlerVersion; + + // NiceMock removes warnings about "uninteresting calls", + auto element = std::make_unique>(); + auto innerElement = std::make_unique>(); + auto collection = std::make_unique>(); + auto nameElement = std::make_unique>(); + auto mockProperty = std::make_unique>(); + + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(innerElement.get()), testing::Return(S_OK))); + ON_CALL(*innerElement, get_Collection(_)) + .WillByDefault(testing::DoAll(testing::SetArgPointee<0>(collection.get()), testing::Return(S_OK))); + ON_CALL(*collection, get_Count(_)) + .WillByDefault(DoAll(testing::SetArgPointee<0>(1), testing::Return(S_OK))); + ON_CALL(*collection, get_Item(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nameElement.get()), testing::Return(S_OK))); + ON_CALL(*nameElement, GetPropertyByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(mockProperty.get()), testing::Return(S_OK))); + EXPECT_CALL(*mockProperty, get_StringValue(_)) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"randomvalue")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value")), testing::Return(S_OK))); + + HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion); + + EXPECT_STREQ(handlerVersion.QueryStr(), L""); + } + + TEST(ConfigUtilityTest, MultipleElements) + { + IAppHostElement* retElement = NULL; + STRU handlerVersion; + + auto element = std::make_unique>(); + auto innerElement = std::make_unique>(); + auto collection = std::make_unique>(); + auto nameElement = std::make_unique>(); + auto mockProperty = std::make_unique>(); + + ON_CALL(*element, GetElementByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(innerElement.get()), testing::Return(S_OK))); + ON_CALL(*innerElement, get_Collection(_)) + .WillByDefault(testing::DoAll(testing::SetArgPointee<0>(collection.get()), testing::Return(S_OK))); + ON_CALL(*collection, get_Count(_)) + .WillByDefault(DoAll(testing::SetArgPointee<0>(2), testing::Return(S_OK))); + ON_CALL(*collection, get_Item(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(nameElement.get()), testing::Return(S_OK))); + ON_CALL(*nameElement, GetPropertyByName(_, _)) + .WillByDefault(DoAll(testing::SetArgPointee<1>(mockProperty.get()), testing::Return(S_OK))); + EXPECT_CALL(*mockProperty, get_StringValue(_)) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"key")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"handlerVersion")), testing::Return(S_OK))) + .WillOnce(DoAll(testing::SetArgPointee<0>(SysAllocString(L"value2")), testing::Return(S_OK))); + + HRESULT hr = ConfigUtility::FindHandlerVersion(element.get(), &handlerVersion); + + EXPECT_STREQ(handlerVersion.QueryStr(), L"value2"); + } +} diff --git a/test/CommonLibTests/FileOutputManagerTests.cpp b/test/CommonLibTests/FileOutputManagerTests.cpp index e3d261097a..ebfb790ed5 100644 --- a/test/CommonLibTests/FileOutputManagerTests.cpp +++ b/test/CommonLibTests/FileOutputManagerTests.cpp @@ -40,8 +40,6 @@ namespace FileOutManagerStartupTests wprintf(expected, out); } - // std::filesystem is available on c++17, however gtest fails to build when using it - // c++14 has filesystem as experimental. for (auto & p : std::experimental::filesystem::directory_iterator(tempDirectory)) { std::wstring filename(p.path().filename()); diff --git a/test/CommonLibTests/GlobalVersionTests.cpp b/test/CommonLibTests/GlobalVersionTests.cpp new file mode 100644 index 0000000000..00ec23f479 --- /dev/null +++ b/test/CommonLibTests/GlobalVersionTests.cpp @@ -0,0 +1,153 @@ +// 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. + +#include "stdafx.h" +#include "gtest/internal/gtest-port.h" + +namespace GlobalVersionTests +{ + using ::testing::Test; + namespace fs = std::experimental::filesystem; + + class GlobalVersionTest : public Test + { + protected: + void + RemoveFileNamePath(PCWSTR dllPath, PCWSTR expected) + { + std::wstring res = GlobalVersionUtility::RemoveFileNameFromFolderPath(dllPath); + EXPECT_STREQ(res.c_str(), expected); + } + }; + + TEST_F(GlobalVersionTest, RemovesPathCorrectly) + { + RemoveFileNamePath(L"test\\log.txt", L"test"); + RemoveFileNamePath(L"test\\log", L"test"); + RemoveFileNamePath(L"C:\\Program Files\\IIS\\aspnetcorev2.dll", L"C:\\Program Files\\IIS"); + RemoveFileNamePath(L"test\\log.txt", L"test"); + } + + TEST(GetRequestHandlerVersions, GetFolders) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.c_str()); + EXPECT_EQ(res.size(), 1); + EXPECT_EQ(res.at(0), fx_ver_t(2, 0, 0, std::wstring())); + } + + TEST(GetRequestHandlerVersions, GetFolderPreview) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0-preview")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.c_str()); + EXPECT_EQ(res.size(), 1); + EXPECT_EQ(res.at(0), fx_ver_t(2, 0, 0, std::wstring(L"-preview"))); + } + + TEST(GetRequestHandlerVersions, GetFolderManyVersions) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\1.9.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0")); + + auto res = GlobalVersionUtility::GetRequestHandlerVersions(tempPath.c_str()); + EXPECT_EQ(res.size(), 3); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(1, 9, 0, std::wstring())) != std::end(res)); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(2, 0, 0, std::wstring())) != std::end(res)); + EXPECT_TRUE(std::find(res.begin(), res.end(), fx_ver_t(2, 1, 0, std::wstring())) != std::end(res)); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithSingleFolder) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.c_str()); + + EXPECT_STREQ(res.c_str(), L"2.0.0"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersions) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.c_str()); + + EXPECT_STREQ(res.c_str(), L"2.1.0"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersionsPreview) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.2.0-preview")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.c_str()); + + EXPECT_STREQ(res.c_str(), L"2.2.0-preview"); + } + + TEST(FindHighestGlobalVersion, HighestVersionWithMultipleVersionNoPreview) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0-preview")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0")); + + auto res = GlobalVersionUtility::FindHighestGlobalVersion(tempPath.c_str()); + + EXPECT_STREQ(res.c_str(), L"2.1.0"); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionNoHandlerName) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.c_str(), L"", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath + L"2.0.0\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionPreviewWins) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0-preview")); + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.c_str(), L"", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath + L"2.1.0-preview\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionSpecificVersion) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0-preview")); + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.c_str(), L"2.0.0", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath + L"2.0.0\\aspnetcorev2_outofprocess.dll").c_str()); + } + + TEST(GetGlobalRequestHandlerPath, FindHighestVersionSpecificPreview) + { + std::wstring tempPath = Helpers::CreateRandomTempDirectory(); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.0.0")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.1.0-preview")); + EXPECT_TRUE(fs::create_directories(tempPath + L"\\2.2.0")); + + + auto result = GlobalVersionUtility::GetGlobalRequestHandlerPath(tempPath.c_str(), L"2.1.0-preview", L"aspnetcorev2_outofprocess.dll"); + + EXPECT_STREQ(result.c_str(), (tempPath + L"2.1.0-preview\\aspnetcorev2_outofprocess.dll").c_str()); + } +} diff --git a/test/CommonLibTests/fakeclasses.h b/test/CommonLibTests/fakeclasses.h index 5f79138879..14c6f8dfee 100644 --- a/test/CommonLibTests/fakeclasses.h +++ b/test/CommonLibTests/fakeclasses.h @@ -1,7 +1,62 @@ // 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. -#include "stdafx.h" +#pragma once + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +class MockProperty : public IAppHostProperty +{ +public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Name, HRESULT(BSTR* pbstrValue)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Value, HRESULT(VARIANT * pVariant)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, put_Value, HRESULT(VARIANT value)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_StringValue, HRESULT(BSTR* pbstrValue)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Exception, HRESULT(IAppHostPropertyException ** ppException)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT * pValue)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT value)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostPropertySchema ** ppSchema)); +}; + +class MockCollection : public IAppHostElementCollection +{ +public: + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostCollectionSchema** pSchema)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Count, HRESULT(DWORD * dwordElem)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, get_Item, HRESULT(VARIANT cIndex, IAppHostElement ** ppElement)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, AddElement, HRESULT(IAppHostElement * pElement, INT cPosition)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, DeleteElement, HRESULT(VARIANT cIndex)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, CreateNewElement, HRESULT(BSTR bstrElementName, IAppHostElement** ppElement)); +}; + +class MockElement : public IAppHostElement +{ +public: + + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, HRESULT(REFIID riid, void ** ppvObject)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG()); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Name, HRESULT(BSTR * pbstrName)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Collection, HRESULT(IAppHostElementCollection ** ppCollection)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Properties, HRESULT(IAppHostPropertyCollection ** ppProperties)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_ChildElements, HRESULT(IAppHostChildElementCollection ** ppElements)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT * pValue)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, SetMetadata, HRESULT(BSTR bstrMetadataType, VARIANT value)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Schema, HRESULT(IAppHostElementSchema** pSchema)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetElementByName, HRESULT(BSTR bstrSubName, IAppHostElement ** ppElement)); + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, GetPropertyByName, HRESULT(BSTR bstrSubName, IAppHostProperty ** ppProperty)); + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Clear, HRESULT()); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, get_Methods, HRESULT(IAppHostMethodCollection ** ppMethods)); +}; class MockHttpServer : public IHttpServer { diff --git a/test/CommonLibTests/stdafx.h b/test/CommonLibTests/stdafx.h index ed238e1b3d..47095a9fee 100644 --- a/test/CommonLibTests/stdafx.h +++ b/test/CommonLibTests/stdafx.h @@ -43,6 +43,7 @@ #include "requesthandler_config.h" #include "hostfxr_utility.h" +#include "config_utility.h" #include "environmentvariablehash.h" #include "iapplication.h" #include "utility.h" @@ -51,9 +52,11 @@ #include "resources.h" #include "aspnetcore_msg.h" #include "Helpers.h" +#include "GlobalVersionUtility.h" #undef assert // Macro redefinition in IISLib. -#include "gtest\gtest.h" +#include "gtest/gtest.h" +#include "fakeclasses.h" // Externals defined in inprocess BOOL g_fProcessDetach; diff --git a/test/IISIntegration.FunctionalTests/OutOfProcess/GlobalVersionTests.cs b/test/IISIntegration.FunctionalTests/OutOfProcess/GlobalVersionTests.cs new file mode 100644 index 0000000000..6e50228564 --- /dev/null +++ b/test/IISIntegration.FunctionalTests/OutOfProcess/GlobalVersionTests.cs @@ -0,0 +1,160 @@ +// 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 IISIntegration.FunctionalTests.Utilities; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class GlobalVersionTests : IISFunctionalTestBase + { + private const string _aspNetCoreDll = "aspnetcorev2_outofprocess.dll"; + private const string _handlerVersion20 = "2.0.0"; + private const string _helloWorldRequest = "HelloWorld"; + private const string _helloWorldResponse = "Hello World"; + private const string _outOfProcessVersionVariable = "/p:AspNetCoreModuleOutOfProcessVersion="; + + [Fact] + public async Task GlobalVersion_DefaultWorks() + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + deploymentParameters.PublishApplicationBeforeDeployment = false; + + deploymentParameters.ServerConfigTemplateContent = GetServerConfig( + element => + { + var handlerVersionElement = new XElement("handlerSetting"); + handlerVersionElement.SetAttributeValue("name", "handlerVersion"); + handlerVersionElement.SetAttributeValue("value", _handlerVersion20); + + element.Descendants("aspNetCore").Single() + .Add(new XElement("handlerSettings", handlerVersionElement)); + }); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var response = await deploymentResult.RetryingHttpClient.GetAsync(_helloWorldRequest); + var responseText = await response.Content.ReadAsStringAsync(); + Assert.Equal(_helloWorldResponse, responseText); + } + + [Theory] // Tests need to publish to change folder locations + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_NewVersionNumber_Fails(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + Helpers.ModifyHandlerSectionInWebConfig(deploymentResult, version); + + var response = await deploymentResult.RetryingHttpClient.GetAsync(_helloWorldRequest); + Assert.False(response.IsSuccessStatusCode); + } + + [Theory] // Tests need to publish to change folder locations + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_NewVersionNumber(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + deploymentParameters.AdditionalPublishParameters = $"{_outOfProcessVersionVariable}{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); + } + + [Theory] // Tests need to publish to change folder locations + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_MultipleRequestHandlers_PicksHighestOne(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + var newANCMPath = GetANCMRequestHandlerPath(deploymentResult, version); + + var di = Directory.CreateDirectory(Path.GetDirectoryName(newANCMPath)); + + File.Copy(originalANCMPath, newANCMPath, true); + + deploymentResult.RetryingHttpClient.DefaultRequestHeaders.Add("ANCMRHPath", newANCMPath); + var response = await deploymentResult.RetryingHttpClient.GetAsync("CheckRequestHandlerVersion"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + } + + [Theory] + [InlineData("2.1.0")] + [InlineData("2.1.0-preview")] + public async Task GlobalVersion_MultipleRequestHandlers_UpgradeWorks(string version) + { + var deploymentParameters = GetGlobalVersionBaseDeploymentParameters(); + var deploymentResult = await DeployAsync(deploymentParameters); + + var originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + deploymentResult.RetryingHttpClient.DefaultRequestHeaders.Add("ANCMRHPath", originalANCMPath); + var response = await deploymentResult.RetryingHttpClient.GetAsync("CheckRequestHandlerVersion"); + var responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + + Dispose(); + + deploymentResult = await DeployAsync(deploymentParameters); + + originalANCMPath = GetANCMRequestHandlerPath(deploymentResult, _handlerVersion20); + + var newANCMPath = GetANCMRequestHandlerPath(deploymentResult, version); + + var di = Directory.CreateDirectory(Path.GetDirectoryName(newANCMPath)); + + File.Copy(originalANCMPath, newANCMPath, true); + + deploymentResult.RetryingHttpClient.DefaultRequestHeaders.Add("ANCMRHPath", newANCMPath); + response = await deploymentResult.RetryingHttpClient.GetAsync("CheckRequestHandlerVersion"); + responseText = await response.Content.ReadAsStringAsync(); + + Assert.Equal(_helloWorldResponse, responseText); + } + + private DeploymentParameters GetGlobalVersionBaseDeploymentParameters() + { + return new DeploymentParameters(Helpers.GetOutOfProcessTestSitesPath(), ServerType.IISExpress, RuntimeFlavor.CoreClr, RuntimeArchitecture.x64) + { + TargetFramework = Tfm.NetCoreApp22, + ApplicationType = ApplicationType.Portable, + AncmVersion = AncmVersion.AspNetCoreModuleV2, + HostingModel = HostingModel.OutOfProcess, + PublishApplicationBeforeDeployment = true, + AdditionalPublishParameters = $"{_outOfProcessVersionVariable}{_handlerVersion20}" + }; + } + + private string GetANCMRequestHandlerPath(IISDeploymentResult deploymentResult, string version) + { + return Path.Combine(deploymentResult.DeploymentResult.ContentRoot, + deploymentResult.DeploymentResult.DeploymentParameters.RuntimeArchitecture.ToString(), + version, + _aspNetCoreDll); + } + } +} diff --git a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs index 4d3e0e65dc..d66fecf3cb 100644 --- a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs @@ -21,16 +21,31 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public static string GetOutOfProcessTestSitesPath() => GetTestWebSitePath("OutOfProcessWebSite"); public static void ModifyAspNetCoreSectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value) - => ModifySectionInWebConfig(deploymentResult, key, value, section: "aspNetCore", 0); + => ModifyAttributeInWebConfig(deploymentResult, key, value, section: "aspNetCore"); - public static void ModifySectionInWebConfig(IISDeploymentResult deploymentResult, string key, string value, string section, int index) + public static void ModifyAttributeInWebConfig(IISDeploymentResult deploymentResult, string key, string value, string section) { - // modify the web.config after publish - var root = deploymentResult.DeploymentResult.ContentRoot; - var webConfigFile = $"{root}/web.config"; + var webConfigFile = GetWebConfigFile(deploymentResult); var config = XDocument.Load(webConfigFile); - var element = config.Descendants(section).ToList()[index]; + + var element = config.Descendants(section).Single(); element.SetAttributeValue(key, value); + + 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); } @@ -46,5 +61,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests PublishApplicationBeforeDeployment = site == "InProcessWebSite", }; } + + private static string GetWebConfigFile(IISDeploymentResult deploymentResult) + => Path.Combine(deploymentResult.DeploymentResult.ContentRoot, "web.config"); } } diff --git a/test/WebSites/OutOfProcessWebSite/Startup.cs b/test/WebSites/OutOfProcessWebSite/Startup.cs index 4b26fa7ba2..fc7021c992 100644 --- a/test/WebSites/OutOfProcessWebSite/Startup.cs +++ b/test/WebSites/OutOfProcessWebSite/Startup.cs @@ -2,7 +2,9 @@ // 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.Runtime.InteropServices; using System.Security.Principal; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; @@ -84,5 +86,22 @@ namespace TestSites public Task UpgradeFeatureDetection(HttpContext context) => context.Response.WriteAsync(context.Features.Get() != null? "Enabled": "Disabled"); + + public Task CheckRequestHandlerVersion(HttpContext context) + { + // We need to check if the aspnetcorev2_outofprocess dll is loaded by iisexpress.exe + // As they aren't in the same process, we will try to delete the file and expect a file + // in use error + try + { + File.Delete(context.Request.Headers["ANCMRHPath"]); + } + catch(UnauthorizedAccessException) + { + return context.Response.WriteAsync("Hello World"); + } + + return context.Response.WriteAsync(context.Request.Headers["ANCMRHPath"]); + } } } diff --git a/test/gtest/gtest.vcxproj b/test/gtest/gtest.vcxproj index 660be83f21..9f745f3a84 100644 --- a/test/gtest/gtest.vcxproj +++ b/test/gtest/gtest.vcxproj @@ -19,6 +19,7 @@ + @@ -105,7 +106,7 @@ true WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true - googletest\googletest\include;googletest\googletest;%(AdditionalIncludeDirectories) + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreadedDebug @@ -121,7 +122,7 @@ true _DEBUG;_LIB;%(PreprocessorDefinitions) true - googletest\googletest\include;googletest\googletest;%(AdditionalIncludeDirectories) + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreadedDebug @@ -139,7 +140,7 @@ true WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) true - googletest\googletest\include;googletest\googletest;%(AdditionalIncludeDirectories) + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreaded @@ -159,7 +160,7 @@ true NDEBUG;_LIB;%(PreprocessorDefinitions) true - googletest\googletest\include;googletest\googletest;%(AdditionalIncludeDirectories) + googletest\googletest\include;googletest\googletest;googletest\googlemock;googletest\googlemock\include;%(AdditionalIncludeDirectories) MultiThreaded diff --git a/version.props b/version.props index c836c8d68c..ddf502f2b3 100644 --- a/version.props +++ b/version.props @@ -11,5 +11,6 @@ 8 1 0 + 2.0.0