From 665c795b8fae52d40d07d82228e7d6e26eeb2f13 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Thu, 9 Mar 2017 19:16:30 -0800 Subject: [PATCH] Support detection of legacy VS products Resolves issues #24 --- docker/Tests/legacy.tests.ps1 | 54 +++++ src/vswhere.lib/CommandArgs.cpp | 13 ++ src/vswhere.lib/CommandArgs.h | 8 + src/vswhere.lib/ILegacyProvider.h | 13 ++ src/vswhere.lib/InstanceSelector.cpp | 25 ++- src/vswhere.lib/InstanceSelector.h | 9 +- src/vswhere.lib/LegacyInstance.cpp | 130 +++++++++++ src/vswhere.lib/LegacyInstance.h | 105 +++++++++ src/vswhere.lib/LegacyProvider.cpp | 57 +++++ src/vswhere.lib/LegacyProvider.h | 40 ++++ src/vswhere.lib/resource.h | Bin 2170 -> 2260 bytes src/vswhere.lib/stdafx.h | 3 + src/vswhere.lib/vswhere.lib.rc | Bin 7048 -> 7676 bytes src/vswhere.lib/vswhere.lib.vcxproj | 5 + src/vswhere.lib/vswhere.lib.vcxproj.filters | 17 +- test/vswhere.test/CommandArgsTests.cpp | 35 +++ test/vswhere.test/InstanceSelectorTests.cpp | 201 ++++++++++++++++++ test/vswhere.test/TestHelper.h | 14 +- test/vswhere.test/TestLegacyProvider.cpp | 29 +++ test/vswhere.test/TestLegacyProvider.h | 24 +++ test/vswhere.test/stdafx.h | 1 + test/vswhere.test/vswhere.test.vcxproj | 2 + .../vswhere.test/vswhere.test.vcxproj.filters | 6 + 23 files changed, 787 insertions(+), 4 deletions(-) create mode 100644 docker/Tests/legacy.tests.ps1 create mode 100644 src/vswhere.lib/ILegacyProvider.h create mode 100644 src/vswhere.lib/LegacyInstance.cpp create mode 100644 src/vswhere.lib/LegacyInstance.h create mode 100644 src/vswhere.lib/LegacyProvider.cpp create mode 100644 src/vswhere.lib/LegacyProvider.h create mode 100644 test/vswhere.test/TestLegacyProvider.cpp create mode 100644 test/vswhere.test/TestLegacyProvider.h diff --git a/docker/Tests/legacy.tests.ps1 b/docker/Tests/legacy.tests.ps1 new file mode 100644 index 0000000..038513f --- /dev/null +++ b/docker/Tests/legacy.tests.ps1 @@ -0,0 +1,54 @@ +# Copyright (C) Microsoft Corporation. All rights reserved. +# Licensed under the MIT license. See LICENSE.txt in the project root for license information. + +Describe 'vswhere -legacy' { + BeforeEach { + # Make sure localized values are returned consistently across machines. + $enu = [System.Globalization.CultureInfo]::GetCultureInfo('en-US') + + [System.Globalization.CultureInfo]::CurrentCulture = $enu + [System.Globalization.CultureInfo]::CurrentUICulture = $enu + } + + AfterEach { + # Make sure the registry is cleaned up. + Remove-Item HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\SxS\VS7 -Force -ErrorAction 'SilentlyContinue' + } + + Context 'no legacy' { + It 'returns 2 instances' { + $instances = C:\bin\vswhere.exe -legacy -format json | ConvertFrom-Json + $instances.Count | Should Be 2 + } + } + + Context 'has legacy' { + BeforeEach { + New-Item HKLM:\Software\WOW6432Node\Microsoft\VisualStudio\SxS\VS7 -Force | ForEach-Object { + foreach ($version in '10.0', '14.0') { + $_ | New-ItemProperty -Name $version -Value "C:\VisualStudio\$version" -Force + } + } + } + + It 'returns 4 instances' { + $instances = C:\bin\vswhere.exe -legacy -format json | ConvertFrom-Json + $instances.Count | Should Be 4 + } + + It '-version "10.0" returns 4 instances' { + $instances = C:\bin\vswhere.exe -legacy -version '10.0' -format json | ConvertFrom-Json + $instances.Count | Should Be 4 + } + + It '-version "14.0" returns 3 instances' { + $instances = C:\bin\vswhere.exe -legacy -version '14.0' -format json | ConvertFrom-Json + $instances.Count | Should Be 3 + } + + It '-version "[10.0,15.0)" returns 2 instances' { + $instances = C:\bin\vswhere.exe -legacy -version '[10.0,15.0)' -format json | ConvertFrom-Json + $instances.Count | Should Be 2 + } + } +} diff --git a/src/vswhere.lib/CommandArgs.cpp b/src/vswhere.lib/CommandArgs.cpp index ca90143..ad77e4d 100644 --- a/src/vswhere.lib/CommandArgs.cpp +++ b/src/vswhere.lib/CommandArgs.cpp @@ -77,6 +77,10 @@ void CommandArgs::Parse(_In_ vector args) { m_latest = true; } + else if (ArgumentEquals(arg.Value, L"legacy")) + { + m_legacy = true; + } else if (ArgumentEquals(arg.Value, L"format")) { auto format = ParseArgument(it, args.end(), arg); @@ -121,6 +125,15 @@ void CommandArgs::Parse(_In_ vector args) m_products.clear(); } + if (m_legacy) + { + if (!m_products.empty() || !m_requires.empty()) + { + auto message = ResourceManager::GetString(IDS_E_LEGACY); + throw win32_error(ERROR_INVALID_PARAMETER, message); + } + } + if (!m_property.empty() && m_format.empty()) { m_format = L"value"; diff --git a/src/vswhere.lib/CommandArgs.h b/src/vswhere.lib/CommandArgs.h index 6c39128..2631990 100644 --- a/src/vswhere.lib/CommandArgs.h +++ b/src/vswhere.lib/CommandArgs.h @@ -14,6 +14,7 @@ class CommandArgs m_all(false), m_productsAll(false), m_latest(false), + m_legacy(false), m_nologo(false), m_help(false) { @@ -27,6 +28,7 @@ class CommandArgs m_requires(obj.m_requires), m_version(obj.m_version), m_latest(obj.m_latest), + m_legacy(obj.m_legacy), m_format(obj.m_format), m_property(obj.m_property), m_nologo(obj.m_nologo), @@ -69,6 +71,11 @@ class CommandArgs return m_latest; } + const bool get_Legacy() const noexcept + { + return m_legacy; + } + const std::wstring& get_Format() const noexcept { if (m_format.empty()) @@ -112,6 +119,7 @@ class CommandArgs std::vector m_requires; std::wstring m_version; bool m_latest; + bool m_legacy; std::wstring m_format; std::wstring m_property; bool m_nologo; diff --git a/src/vswhere.lib/ILegacyProvider.h b/src/vswhere.lib/ILegacyProvider.h new file mode 100644 index 0000000..4665311 --- /dev/null +++ b/src/vswhere.lib/ILegacyProvider.h @@ -0,0 +1,13 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#pragma once + +class ILegacyProvider +{ +public: + virtual bool HasLegacyInstances() const = 0; + virtual bool TryGetLegacyInstance(_In_ LPCWSTR wzVersion, _Out_ ISetupInstance** ppInstance) const = 0; +}; diff --git a/src/vswhere.lib/InstanceSelector.cpp b/src/vswhere.lib/InstanceSelector.cpp index c17cebd..0719cb8 100644 --- a/src/vswhere.lib/InstanceSelector.cpp +++ b/src/vswhere.lib/InstanceSelector.cpp @@ -8,8 +8,9 @@ using namespace std; using std::placeholders::_1; -InstanceSelector::InstanceSelector(_In_ const CommandArgs& args, _In_opt_ ISetupHelper* pHelper) : +InstanceSelector::InstanceSelector(_In_ const CommandArgs& args, _In_ ILegacyProvider& provider, _In_opt_ ISetupHelper* pHelper) : m_args(args), + m_provider(provider), m_ullMinimumVersion(0), m_ullMaximumVersion(0) { @@ -114,6 +115,28 @@ vector InstanceSelector::Select(_In_opt_ IEnumSetupInstances* } while (SUCCEEDED(hr) && celtFetched); } + if (m_args.get_Legacy() && m_provider.HasLegacyInstances()) + { + vector versions = + { + L"14.0", + L"12.0", + L"11.0", + L"10.0", + }; + + for (auto version : versions) + { + ISetupInstancePtr instance; + + // Currently only support version checks. + if (m_provider.TryGetLegacyInstance(version, &instance) && IsVersionMatch(instance)) + { + instances.push_back(instance); + } + } + } + if (m_args.get_Latest() && 1 < instances.size()) { sort(instances.begin(), instances.end(), [&](const ISetupInstancePtr& a, const ISetupInstancePtr& b) -> bool diff --git a/src/vswhere.lib/InstanceSelector.h b/src/vswhere.lib/InstanceSelector.h index b3d601c..3e0950a 100644 --- a/src/vswhere.lib/InstanceSelector.h +++ b/src/vswhere.lib/InstanceSelector.h @@ -8,9 +8,15 @@ class InstanceSelector { public: - InstanceSelector(_In_ const CommandArgs& args, _In_opt_ ISetupHelper* pHelper = NULL); + InstanceSelector(_In_ const CommandArgs& args, _In_opt_ ISetupHelper* pHelper = NULL) : + InstanceSelector(args, LegacyProvider::Instance, pHelper) + { + } + + InstanceSelector(_In_ const CommandArgs& args, _In_ ILegacyProvider& provider, _In_opt_ ISetupHelper* pHelper = NULL); InstanceSelector(_In_ const InstanceSelector& obj) : m_args(obj.m_args), + m_provider(obj.m_provider), m_helper(obj.m_helper), m_ullMinimumVersion(obj.m_ullMinimumVersion), m_ullMaximumVersion(obj.m_ullMaximumVersion) @@ -32,6 +38,7 @@ class InstanceSelector } const CommandArgs& m_args; + const ILegacyProvider& m_provider; ULONGLONG m_ullMinimumVersion; ULONGLONG m_ullMaximumVersion; ISetupHelperPtr m_helper; diff --git a/src/vswhere.lib/LegacyInstance.cpp b/src/vswhere.lib/LegacyInstance.cpp new file mode 100644 index 0000000..679be04 --- /dev/null +++ b/src/vswhere.lib/LegacyInstance.cpp @@ -0,0 +1,130 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#include "stdafx.h" + +using namespace std; + +template +inline HRESULT NotImplemented(_In_ T* p) +{ + if (!p) + { + return E_POINTER; + } + + *p = NULL; + return E_NOTFOUND; +} + +template<> +inline HRESULT NotImplemented(_In_ LPFILETIME pft) +{ + if (!pft) + { + return E_POINTER; + } + + ::SecureZeroMemory(pft, sizeof(FILETIME)); + return E_NOTFOUND; +} + +STDMETHODIMP LegacyInstance::GetInstanceId( + _Out_ BSTR* pbstrInstanceId +) +{ + if (!pbstrInstanceId) + { + return E_POINTER; + } + + *pbstrInstanceId = NULL; + + // Let exceptions throw since we're not a "true" COM object. + wstring instanceId(L"VisualStudio."); + instanceId += m_version; + + *pbstrInstanceId = ::SysAllocString(instanceId.c_str()); + if (!pbstrInstanceId) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +STDMETHODIMP LegacyInstance::GetInstallDate( + _Out_ LPFILETIME pInstallDate +) +{ + return NotImplemented(pInstallDate); +} + +STDMETHODIMP LegacyInstance::GetInstallationName( + _Out_ BSTR* pbstrInstallationName +) +{ + return NotImplemented(pbstrInstallationName); +} + +STDMETHODIMP LegacyInstance::GetInstallationPath( + _Out_ BSTR* pbstrInstallationPath +) +{ + if (!pbstrInstallationPath) + { + return E_POINTER; + } + + *pbstrInstallationPath = ::SysAllocString(m_path.c_str()); + if (!pbstrInstallationPath) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +STDMETHODIMP LegacyInstance::GetInstallationVersion( + _Out_ BSTR* pbstrInstallationVersion +) +{ + if (!pbstrInstallationVersion) + { + return E_POINTER; + } + + *pbstrInstallationVersion = ::SysAllocString(m_version.c_str()); + if (!pbstrInstallationVersion) + { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +STDMETHODIMP LegacyInstance::GetDisplayName( + _In_ LCID lcid, + _Out_ BSTR* pbstrDisplayName +) +{ + return NotImplemented(pbstrDisplayName); +} + +STDMETHODIMP LegacyInstance::GetDescription( + _In_ LCID lcid, + _Out_ BSTR* pbstrDescription +) +{ + return NotImplemented(pbstrDescription); +} + +STDMETHODIMP LegacyInstance::ResolvePath( + _In_opt_z_ LPCOLESTR pwszRelativePath, + _Out_ BSTR* pbstrAbsolutePath +) +{ + return NotImplemented(pbstrAbsolutePath); +} diff --git a/src/vswhere.lib/LegacyInstance.h b/src/vswhere.lib/LegacyInstance.h new file mode 100644 index 0000000..c8ff575 --- /dev/null +++ b/src/vswhere.lib/LegacyInstance.h @@ -0,0 +1,105 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#pragma once + +class LegacyInstance : + public ISetupInstance +{ +public: + LegacyInstance(_In_ LPCWSTR wzVersion, _In_ LPCWSTR wzPath) : + m_version(wzVersion), + m_path(wzPath), + m_ulRef(1) + { + } + + // IUnknown + STDMETHODIMP QueryInterface( + _In_ REFIID riid, + _Outptr_ LPVOID *ppvObject) + { + if (!ppvObject) + { + return E_POINTER; + } + + HRESULT hr = S_OK; + if (riid == __uuidof(ISetupInstance)) + { + AddRef(); + *ppvObject = static_cast(this); + } + else if (riid == __uuidof(IUnknown)) + { + AddRef(); + *ppvObject = static_cast(this); + } + else + { + hr = E_NOINTERFACE; + } + + return hr; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return ::InterlockedIncrement(&m_ulRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + auto ulRef = ::InterlockedDecrement(&m_ulRef); + if (!ulRef) + { + delete this; + } + + return ulRef; + } + +public: + // SetupInstance + STDMETHOD(GetInstanceId)( + _Out_ BSTR* pbstrInstanceId + ); + + STDMETHOD(GetInstallDate)( + _Out_ LPFILETIME pInstallDate + ); + + STDMETHOD(GetInstallationName)( + _Out_ BSTR* pbstrInstallationName + ); + + STDMETHOD(GetInstallationPath)( + _Out_ BSTR* pbstrInstallationPath + ); + + STDMETHOD(GetInstallationVersion)( + _Out_ BSTR* pbstrInstallationVersion + ); + + STDMETHOD(GetDisplayName)( + _In_ LCID lcid, + _Out_ BSTR* pbstrDisplayName + ); + + STDMETHOD(GetDescription)( + _In_ LCID lcid, + _Out_ BSTR* pbstrDescription + ); + + STDMETHOD(ResolvePath)( + _In_opt_z_ LPCOLESTR pwszRelativePath, + _Out_ BSTR* pbstrAbsolutePath + ); + +private: + const std::wstring m_version; + const std::wstring m_path; + ULONG m_ulRef; +}; diff --git a/src/vswhere.lib/LegacyProvider.cpp b/src/vswhere.lib/LegacyProvider.cpp new file mode 100644 index 0000000..2ecce02 --- /dev/null +++ b/src/vswhere.lib/LegacyProvider.cpp @@ -0,0 +1,57 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#include "stdafx.h" + +using namespace std; + +ILegacyProvider& LegacyProvider::Instance = LegacyProvider(); + +bool LegacyProvider::HasLegacyInstances() const +{ + return NULL != m_hKey; +} + +bool LegacyProvider::TryGetLegacyInstance(_In_ LPCWSTR wzVersion, _Out_ ISetupInstance** ppInstance) const +{ + _ASSERT(ppInstance); + + *ppInstance = NULL; + + if (!m_hKey) + { + return false; + } + + DWORD dwType = 0; + DWORD cbData = 0; + + auto err = ::RegQueryValueExW(m_hKey, wzVersion, NULL, &dwType, NULL, &cbData); + if (ERROR_FILE_NOT_FOUND == err || REG_SZ != dwType) + { + return false; + } + else if (ERROR_SUCCESS != err) + { + throw win32_error(err); + } + + wstring sz(cbData / sizeof(wstring::value_type), wstring::value_type()); + auto lpData = reinterpret_cast(const_cast(sz.c_str())); + + err = ::RegQueryValueExW(m_hKey, wzVersion, NULL, NULL, lpData, &cbData); + if (ERROR_SUCCESS != err) + { + throw win32_error(err); + } + + *ppInstance = new (nothrow) LegacyInstance(wzVersion, sz.c_str()); + if (!*ppInstance) + { + throw win32_error(E_OUTOFMEMORY); + } + + return true; +} diff --git a/src/vswhere.lib/LegacyProvider.h b/src/vswhere.lib/LegacyProvider.h new file mode 100644 index 0000000..6779079 --- /dev/null +++ b/src/vswhere.lib/LegacyProvider.h @@ -0,0 +1,40 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#pragma once + +class LegacyProvider : + public ILegacyProvider +{ +public: + static ILegacyProvider& Instance; + + LegacyProvider() : + m_hKey(NULL) + { + auto err = ::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &m_hKey); + if (ERROR_SUCCESS != err && ERROR_FILE_NOT_FOUND != err) + { + throw win32_error(err); + } + } + + LegacyProvider(const LegacyProvider& obj) = delete; + + ~LegacyProvider() + { + if (m_hKey) + { + ::CloseHandle(m_hKey); + m_hKey = NULL; + } + } + + bool HasLegacyInstances() const override; + bool TryGetLegacyInstance(_In_ LPCWSTR wzVersion, _Out_ ISetupInstance** ppInstance) const override; + +private: + HKEY m_hKey; +}; diff --git a/src/vswhere.lib/resource.h b/src/vswhere.lib/resource.h index 235cc80d8611cd9d2944fc937e5b93dd3feea684..9cde9fc68332551e4dba62a4c255304e476adc2c 100644 GIT binary patch delta 43 zcmew*a7A!K0yA$sgAao%gFAyGgEK?qY5PgOC!HE}7!eOB-=@a}wQdmd`K@`Ep#xmYr^gvzRyC7C#7eo-5KN84) z@fYL=B)x@i_acNSVVPxS-n^N4)2n|U_P;!N3rjLuUhvM)!Z8|X;!-~S&6f%;aDxO2 z6~0?~*JvZcvx^lT7#Z3fk(_2&88N=+)66JbId;a%gh+}GEkR7=iC)g`igDo;e4Cq0 zJSEMrQH(ChIkL%E?FQME3`-x0rq27t|E^V&^`-J)=$Xa(vvo!VxvGrUHp1dK)!EYwnn7#j2d3R$rvLx| delta 21 dcmexk-C@4r8uw%gcD~JTc#7CIpAs?R1OR1p2sZ!# diff --git a/src/vswhere.lib/vswhere.lib.vcxproj b/src/vswhere.lib/vswhere.lib.vcxproj index f381206..e1c9905 100644 --- a/src/vswhere.lib/vswhere.lib.vcxproj +++ b/src/vswhere.lib/vswhere.lib.vcxproj @@ -164,8 +164,11 @@ + + + @@ -184,6 +187,8 @@ + + Create diff --git a/src/vswhere.lib/vswhere.lib.vcxproj.filters b/src/vswhere.lib/vswhere.lib.vcxproj.filters index 6238d7c..10f67a0 100644 --- a/src/vswhere.lib/vswhere.lib.vcxproj.filters +++ b/src/vswhere.lib/vswhere.lib.vcxproj.filters @@ -66,6 +66,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -107,6 +116,12 @@ Source Files + + Source Files + + + Source Files + @@ -116,4 +131,4 @@ Resource Files - + \ No newline at end of file diff --git a/test/vswhere.test/CommandArgsTests.cpp b/test/vswhere.test/CommandArgsTests.cpp index 339c663..282e0ff 100644 --- a/test/vswhere.test/CommandArgsTests.cpp +++ b/test/vswhere.test/CommandArgsTests.cpp @@ -20,6 +20,7 @@ TEST_CLASS(CommandArgsTests) Assert::IsFalse(args.get_All()); Assert::AreEqual(0, args.get_Version().length()); Assert::IsFalse(args.get_Latest()); + Assert::IsFalse(args.get_Legacy()); Assert::AreEqual(L"text", args.get_Format().c_str()); Assert::AreEqual(0, args.get_Property().length()); Assert::IsFalse(args.get_Help()); @@ -240,4 +241,38 @@ TEST_CLASS(CommandArgsTests) args.Parse(L"vswhere.exe -products A * B"); Assert::IsTrue(args.get_Products().empty()); } + + TEST_METHOD(Parse_Legacy) + { + CommandArgs args; + Assert::IsFalse(args.get_Legacy()); + + args.Parse(L"vswhere.exe -legacy"); + Assert::IsTrue(args.get_Legacy()); + } + + TEST_METHOD(Parse_Legacy_Products_Throws) + { + CommandArgs args; + Assert::IsFalse(args.get_Legacy()); + + Assert::ExpectException([&] { args.Parse(L"vswhere.exe -legacy -products A"); }); + } + + TEST_METHOD(Parse_Legacy_Products_Asterisk) + { + CommandArgs args; + Assert::IsFalse(args.get_Legacy()); + + args.Parse(L"vswhere.exe -legacy -products *"); + Assert::IsTrue(args.get_Legacy()); + } + + TEST_METHOD(Parse_Legacy_Requires_Throws) + { + CommandArgs args; + Assert::IsFalse(args.get_Legacy()); + + Assert::ExpectException([&] { args.Parse(L"vswhere.exe -legacy -requires A"); }); + } }; diff --git a/test/vswhere.test/InstanceSelectorTests.cpp b/test/vswhere.test/InstanceSelectorTests.cpp index 1bebe6c..aa24303 100644 --- a/test/vswhere.test/InstanceSelectorTests.cpp +++ b/test/vswhere.test/InstanceSelectorTests.cpp @@ -400,4 +400,205 @@ TEST_CLASS(InstanceSelectorTests) InstanceSelector sut(args, &helper); Assert::ExpectException([&] { sut.Less(&a, &b); }); } + + TEST_METHOD(Select_No_Legacy) + { + TestPackageReference product = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestInstance::MapType properties = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance(&product, {}, properties); + TestEnumInstances instances = + { + &instance, + }; + + TestLegacyProvider legacy = + { + { L"14.0", L"C:\\VisualStudio\\14.0" }, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe"); + + InstanceSelector sut(args, legacy); + auto selected = sut.Select(&instances); + + Assert::AreEqual(1, selected.size()); + + bstr_t bstrInstanceId; + Assert::AreEqual(S_OK, selected[0]->GetInstanceId(bstrInstanceId.GetAddress())); + Assert::AreEqual(L"a1b2c3", bstrInstanceId); + } + + TEST_METHOD(Select_Missing_Legacy) + { + TestPackageReference product = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestInstance::MapType properties = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance(&product, {}, properties); + TestEnumInstances instances = + { + &instance, + }; + + TestLegacyProvider legacy = {}; + + CommandArgs args; + args.Parse(L"vswhere.exe -legacy"); + + InstanceSelector sut(args, legacy); + auto selected = sut.Select(&instances); + + Assert::AreEqual(1, selected.size()); + } + + TEST_METHOD(Select_With_Legacy) + { + TestPackageReference product = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestInstance::MapType properties = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance(&product, {}, properties); + TestEnumInstances instances = + { + &instance, + }; + + TestLegacyProvider legacy = + { + { L"14.0", L"C:\\VisualStudio\\14.0" }, + }; + + CommandArgs args; + args.Parse(L"vswhere.exe -legacy"); + + InstanceSelector sut(args, legacy); + auto selected = sut.Select(&instances); + + Assert::AreEqual(2, selected.size()); + } + + TEST_METHOD(Select_Only_Legacy) + { + TestPackageReference product = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestInstance::MapType properties = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance(&product, {}, properties); + TestEnumInstances instances = + { + &instance, + }; + + TestLegacyProvider legacy = + { + { L"14.0", L"C:\\VisualStudio\\14.0" }, + }; + + TestHelper helper(MAKEVERSION(14, 0, 0, 0), MAKEVERSION(14, USHRT_MAX, USHRT_MAX, USHRT_MAX)); + + CommandArgs args; + args.Parse(L"vswhere.exe -legacy -version [14.0,15.0)"); + + InstanceSelector sut(args, legacy, &helper); + auto selected = sut.Select(&instances); + + Assert::AreEqual(1, selected.size()); + + bstr_t bstrInstanceId; + Assert::AreEqual(S_OK, selected[0]->GetInstanceId(bstrInstanceId.GetAddress())); + Assert::AreEqual(L"VisualStudio.14.0", bstrInstanceId); + } + + TEST_METHOD(Select_No_New) + { + TestLegacyProvider legacy = + { + { L"14.0", L"C:\\VisualStudio\\14.0" }, + }; + + TestHelper helper(MAKEVERSION(14, 0, 0, 0), MAKEVERSION(14, USHRT_MAX, USHRT_MAX, USHRT_MAX)); + + CommandArgs args; + args.Parse(L"vswhere.exe -legacy"); + + InstanceSelector sut(args, legacy, &helper); + auto selected = sut.Select(NULL); + + Assert::AreEqual(1, selected.size()); + + bstr_t bstrInstanceId; + Assert::AreEqual(S_OK, selected[0]->GetInstanceId(bstrInstanceId.GetAddress())); + Assert::AreEqual(L"VisualStudio.14.0", bstrInstanceId); + } + + TEST_METHOD(Select_Legacy_Range) + { + TestPackageReference product = + { + { L"Id", L"Microsoft.VisualStudio.Product.Enterprise" }, + }; + + TestInstance::MapType properties = + { + { L"InstanceId", L"a1b2c3" }, + { L"InstallationName", L"test" }, + }; + + TestInstance instance(&product, {}, properties); + TestEnumInstances instances = + { + &instance, + }; + + TestLegacyProvider legacy = + { + { L"10.0", L"C:\\VisualStudio\\10.0" }, + { L"14.0", L"C:\\VisualStudio\\14.0" }, + }; + + TestHelper helper(MAKEVERSION(14, 0, 0, 0), MAKEVERSION(14, USHRT_MAX, USHRT_MAX, USHRT_MAX)); + + CommandArgs args; + args.Parse(L"vswhere.exe -legacy -version [14.0,15.0)"); + + InstanceSelector sut(args, legacy, &helper); + auto selected = sut.Select(&instances); + + Assert::AreEqual(1, selected.size()); + + bstr_t bstrInstanceId; + Assert::AreEqual(S_OK, selected[0]->GetInstanceId(bstrInstanceId.GetAddress())); + Assert::AreEqual(L"VisualStudio.14.0", bstrInstanceId); + } }; diff --git a/test/vswhere.test/TestHelper.h b/test/vswhere.test/TestHelper.h index 6cde8c4..642dabc 100644 --- a/test/vswhere.test/TestHelper.h +++ b/test/vswhere.test/TestHelper.h @@ -5,7 +5,7 @@ #pragma once -#define MAKEVERSION(major, minor, build, revision) ULONGLONG(major) << 24 || ULONGLONG(minor) << 16 || ULONGLONG(build) << 8 || ULONGLONG(revision) +#define MAKEVERSION(major, minor, build, revision) ULONGLONG(major) << 24 | ULONGLONG(minor) << 16 | ULONGLONG(build) << 8 | ULONGLONG(revision) class TestHelper : public ISetupHelper @@ -82,6 +82,18 @@ class TestHelper : return S_OK; } + if (equal(pwszVersion, L"10.0")) + { + *pullVersion = MAKEVERSION(10, 0, 0, 0); + return S_OK; + } + + if (equal(pwszVersion, L"14.0")) + { + *pullVersion = MAKEVERSION(14, 0, 0, 0); + return S_OK; + } + return E_NOTIMPL; } diff --git a/test/vswhere.test/TestLegacyProvider.cpp b/test/vswhere.test/TestLegacyProvider.cpp new file mode 100644 index 0000000..b7da7be --- /dev/null +++ b/test/vswhere.test/TestLegacyProvider.cpp @@ -0,0 +1,29 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#include "stdafx.h" + +using namespace std; + +bool TestLegacyProvider::HasLegacyInstances() const +{ + return !m_instances.empty(); +} + +bool TestLegacyProvider::TryGetLegacyInstance(_In_ LPCWSTR wzVersion, _Out_ ISetupInstance** ppInstance) const +{ + _ASSERT(ppInstance); + + *ppInstance = NULL; + + auto it = m_instances.find(wzVersion); + if (it != m_instances.end()) + { + *ppInstance = new LegacyInstance(it->first.c_str(), it->second.c_str()); + return true; + } + + return false; +} diff --git a/test/vswhere.test/TestLegacyProvider.h b/test/vswhere.test/TestLegacyProvider.h new file mode 100644 index 0000000..6d13dc2 --- /dev/null +++ b/test/vswhere.test/TestLegacyProvider.h @@ -0,0 +1,24 @@ +// +// Copyright (C) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt in the project root for license information. +// + +#pragma once + +class TestLegacyProvider : + public ILegacyProvider +{ +public: + typedef std::unordered_map, ci_equal> MapType; + + TestLegacyProvider(_In_ std::initializer_list list) : + m_instances(list.begin(), list.end()) + { + } + + bool HasLegacyInstances() const override; + bool TryGetLegacyInstance(_In_ LPCWSTR wzVersion, _Out_ ISetupInstance** ppInstance) const override; + +private: + MapType m_instances; +}; diff --git a/test/vswhere.test/stdafx.h b/test/vswhere.test/stdafx.h index 6a9be1c..64da3b0 100644 --- a/test/vswhere.test/stdafx.h +++ b/test/vswhere.test/stdafx.h @@ -23,4 +23,5 @@ #include "TestEnumInstances.h" #include "TestHelper.h" #include "TestInstance.h" +#include "TestLegacyProvider.h" #include "TestPackageReference.h" diff --git a/test/vswhere.test/vswhere.test.vcxproj b/test/vswhere.test/vswhere.test.vcxproj index 06f4a6b..3b6489a 100644 --- a/test/vswhere.test/vswhere.test.vcxproj +++ b/test/vswhere.test/vswhere.test.vcxproj @@ -97,6 +97,7 @@ + @@ -111,6 +112,7 @@ + diff --git a/test/vswhere.test/vswhere.test.vcxproj.filters b/test/vswhere.test/vswhere.test.vcxproj.filters index 97d8ba9..2145a85 100644 --- a/test/vswhere.test/vswhere.test.vcxproj.filters +++ b/test/vswhere.test/vswhere.test.vcxproj.filters @@ -42,6 +42,9 @@ Header Files + + Header Files + @@ -80,6 +83,9 @@ Source Files + + Source Files +