From d1d31466bb9f2e887a277807d60378afef9cc57d Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 29 Aug 2020 21:29:28 -0500 Subject: WIXFEAT:6210 Parse and compare bundle versions kind of like SemVer. --- src/dutil/dutil.vcxproj | 2 + src/dutil/dutil.vcxproj.filters | 6 + src/dutil/inc/dutilsources.h | 1 + src/dutil/inc/verutil.h | 93 +++ src/dutil/precomp.h | 1 + src/dutil/verutil.cpp | 631 +++++++++++++++ src/test/DUtilUnitTest/DUtilUnitTest.vcxproj | 1 + .../DUtilUnitTest/DUtilUnitTest.vcxproj.filters | 3 + src/test/DUtilUnitTest/VerUtilTests.cpp | 891 +++++++++++++++++++++ src/test/DUtilUnitTest/precomp.h | 1 + 10 files changed, 1630 insertions(+) create mode 100644 src/dutil/inc/verutil.h create mode 100644 src/dutil/verutil.cpp create mode 100644 src/test/DUtilUnitTest/VerUtilTests.cpp (limited to 'src') diff --git a/src/dutil/dutil.vcxproj b/src/dutil/dutil.vcxproj index e9bbb98b..017f7a6f 100644 --- a/src/dutil/dutil.vcxproj +++ b/src/dutil/dutil.vcxproj @@ -111,6 +111,7 @@ + @@ -167,6 +168,7 @@ + diff --git a/src/dutil/dutil.vcxproj.filters b/src/dutil/dutil.vcxproj.filters index 01dd6661..b93d166b 100644 --- a/src/dutil/dutil.vcxproj.filters +++ b/src/dutil/dutil.vcxproj.filters @@ -159,6 +159,9 @@ Source Files + + Source Files + Source Files @@ -329,6 +332,9 @@ Header Files + + Header Files + Header Files diff --git a/src/dutil/inc/dutilsources.h b/src/dutil/inc/dutilsources.h index b03013ca..7d512cb3 100644 --- a/src/dutil/inc/dutilsources.h +++ b/src/dutil/inc/dutilsources.h @@ -60,6 +60,7 @@ typedef enum DUTIL_SOURCE DUTIL_SOURCE_WIUTIL, DUTIL_SOURCE_WUAUTIL, DUTIL_SOURCE_XMLUTIL, + DUTIL_SOURCE_VERUTIL, DUTIL_SOURCE_EXTERNAL = 256, } DUTIL_SOURCE; diff --git a/src/dutil/inc/verutil.h b/src/dutil/inc/verutil.h new file mode 100644 index 00000000..d3715049 --- /dev/null +++ b/src/dutil/inc/verutil.h @@ -0,0 +1,93 @@ +#pragma once +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ReleaseVerutilVersion(p) if (p) { VerFreeVersion(p); p = NULL; } + +typedef struct _VERUTIL_VERSION_RELEASE_LABEL +{ + BOOL fNumeric; + DWORD dwValue; + DWORD_PTR cchLabelOffset; + int cchLabel; +} VERUTIL_VERSION_RELEASE_LABEL; + +typedef struct _VERUTIL_VERSION +{ + LPWSTR sczVersion; + DWORD dwMajor; + DWORD dwMinor; + DWORD dwPatch; + DWORD dwRevision; + DWORD cReleaseLabels; + VERUTIL_VERSION_RELEASE_LABEL* rgReleaseLabels; + DWORD_PTR cchMetadataOffset; + BOOL fInvalid; +} VERUTIL_VERSION; + +/******************************************************************* + VerCompareParsedVersions - compares the Verutil versions. + +*******************************************************************/ +HRESULT DAPI VerCompareParsedVersions( + __in VERUTIL_VERSION* pVersion1, + __in VERUTIL_VERSION* pVersion2, + __out int* pnResult + ); + +/******************************************************************* + VerCompareStringVersions - parses the strings with VerParseVersion and then + compares the Verutil versions with VerCompareParsedVersions. + +*******************************************************************/ +HRESULT DAPI VerCompareStringVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __in BOOL fStrict, + __out int* pnResult + ); + +/******************************************************************** + VerCopyVersion - copies the given Verutil version. + +*******************************************************************/ +HRESULT DAPI VerCopyVersion( + __in VERUTIL_VERSION* pSource, + __out VERUTIL_VERSION** ppVersion + ); + +/******************************************************************** + VerFreeVersion - frees any memory associated with a Verutil version. + +*******************************************************************/ +void DAPI VerFreeVersion( + __in VERUTIL_VERSION* pVersion + ); + +/******************************************************************* + VerParseVersion - parses the string into a Verutil version. + +*******************************************************************/ +HRESULT DAPI VerParseVersion( + __in_z LPCWSTR wzVersion, + __in DWORD cchVersion, + __in BOOL fStrict, + __out VERUTIL_VERSION** ppVersion + ); + +/******************************************************************* + VerParseVersion - parses the QWORD into a Verutil version. + +*******************************************************************/ +HRESULT DAPI VerVersionFromQword( + __in DWORD64 qwVersion, + __out VERUTIL_VERSION** ppVersion + ); + +#ifdef __cplusplus +} +#endif diff --git a/src/dutil/precomp.h b/src/dutil/precomp.h index 7fdc83ae..be58860c 100644 --- a/src/dutil/precomp.h +++ b/src/dutil/precomp.h @@ -89,6 +89,7 @@ #include "uncutil.h" #include "uriutil.h" #include "userutil.h" +#include "verutil.h" #include "wiutil.h" #include "wuautil.h" #include // This header is needed for msxml2.h to compile correctly diff --git a/src/dutil/verutil.cpp b/src/dutil/verutil.cpp new file mode 100644 index 00000000..f362f413 --- /dev/null +++ b/src/dutil/verutil.cpp @@ -0,0 +1,631 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +// Exit macros +#define VerExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_VERUTIL, x, s, __VA_ARGS__) +#define VerExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__) +#define VerExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__) +#define VerExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_VERUTIL, p, x, e, s, __VA_ARGS__) +#define VerExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_VERUTIL, p, x, s, __VA_ARGS__) +#define VerExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_VERUTIL, e, x, s, __VA_ARGS__) + +// constants +const DWORD GROW_RELEASE_LABELS = 3; + +// Forward declarations. +static int CompareDword( + __in const DWORD& dw1, + __in const DWORD& dw2 + ); +static HRESULT CompareReleaseLabel( + __in const VERUTIL_VERSION_RELEASE_LABEL* p1, + __in LPCWSTR wzVersion1, + __in const VERUTIL_VERSION_RELEASE_LABEL* p2, + __in LPCWSTR wzVersion2, + __out int* pnResult + ); +static HRESULT CompareVersionSubstring( + __in LPCWSTR wzString1, + __in int cchCount1, + __in LPCWSTR wzString2, + __in int cchCount2, + __out int* pnResult + ); + + +DAPI_(HRESULT) VerCompareParsedVersions( + __in VERUTIL_VERSION* pVersion1, + __in VERUTIL_VERSION* pVersion2, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + int nResult = 0; + DWORD cMaxReleaseLabels = 0; + BOOL fCompareMetadata = FALSE; + + if (!pVersion1 || !pVersion1->sczVersion || + !pVersion2 || !pVersion2->sczVersion) + { + ExitFunction1(hr = E_INVALIDARG); + } + + if (pVersion1 == pVersion2) + { + ExitFunction1(nResult = 0); + } + + nResult = CompareDword(pVersion1->dwMajor, pVersion2->dwMajor); + if (0 != nResult) + { + ExitFunction(); + } + + nResult = CompareDword(pVersion1->dwMinor, pVersion2->dwMinor); + if (0 != nResult) + { + ExitFunction(); + } + + nResult = CompareDword(pVersion1->dwPatch, pVersion2->dwPatch); + if (0 != nResult) + { + ExitFunction(); + } + + nResult = CompareDword(pVersion1->dwRevision, pVersion2->dwRevision); + if (0 != nResult) + { + ExitFunction(); + } + + if (pVersion1->fInvalid) + { + if (!pVersion2->fInvalid) + { + ExitFunction1(nResult = -1); + } + else + { + fCompareMetadata = TRUE; + } + } + else if (pVersion2->fInvalid) + { + ExitFunction1(nResult = 1); + } + + if (pVersion1->cReleaseLabels) + { + if (pVersion2->cReleaseLabels) + { + cMaxReleaseLabels = max(pVersion1->cReleaseLabels, pVersion2->cReleaseLabels); + } + else + { + ExitFunction1(nResult = -1); + } + } + else if (pVersion2->cReleaseLabels) + { + ExitFunction1(nResult = 1); + } + + if (cMaxReleaseLabels) + { + for (DWORD i = 0; i < cMaxReleaseLabels; ++i) + { + VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel1 = pVersion1->cReleaseLabels > i ? pVersion1->rgReleaseLabels + i : NULL; + VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel2 = pVersion2->cReleaseLabels > i ? pVersion2->rgReleaseLabels + i : NULL; + + hr = CompareReleaseLabel(pReleaseLabel1, pVersion1->sczVersion, pReleaseLabel2, pVersion2->sczVersion, &nResult); + if (FAILED(hr) || 0 != nResult) + { + ExitFunction(); + } + } + } + + if (fCompareMetadata) + { + hr = CompareVersionSubstring(pVersion1->sczVersion + pVersion1->cchMetadataOffset, -1, pVersion2->sczVersion + pVersion2->cchMetadataOffset, -1, &nResult); + } + +LExit: + *pnResult = nResult; + return hr; +} + +DAPI_(HRESULT) VerCompareStringVersions( + __in_z LPCWSTR wzVersion1, + __in_z LPCWSTR wzVersion2, + __in BOOL fStrict, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + int nResult = 0; + + hr = VerParseVersion(wzVersion1, 0, fStrict, &pVersion1); + VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, fStrict, &pVersion2); + VerExitOnFailure(hr, "Failed to parse Verutil version '%ls'", wzVersion2); + + hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult); + VerExitOnFailure(hr, "Failed to compare parsed Verutil versions '%ls' and '%ls'.", wzVersion1, wzVersion2); + +LExit: + *pnResult = nResult; + + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + + return hr; +} + +DAPI_(HRESULT) VerCopyVersion( + __in VERUTIL_VERSION* pSource, + __out VERUTIL_VERSION** ppVersion + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pCopy = NULL; + + pCopy = reinterpret_cast(MemAlloc(sizeof(VERUTIL_VERSION), TRUE)); + VerExitOnNull(pCopy, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version copy."); + + hr = StrAllocString(&pCopy->sczVersion, pSource->sczVersion, 0); + VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", pSource->sczVersion); + + pCopy->dwMajor = pSource->dwMajor; + pCopy->dwMinor = pSource->dwMinor; + pCopy->dwPatch = pSource->dwPatch; + pCopy->dwRevision = pSource->dwRevision; + + hr = MemEnsureArraySize(reinterpret_cast(&pCopy->rgReleaseLabels), 0, sizeof(VERUTIL_VERSION_RELEASE_LABEL), pSource->cReleaseLabels); + VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels copies."); + + pCopy->cReleaseLabels = pSource->cReleaseLabels; + + for (DWORD i = 0; i < pCopy->cReleaseLabels; ++i) + { + VERUTIL_VERSION_RELEASE_LABEL* pSourceLabel = pSource->rgReleaseLabels + i; + VERUTIL_VERSION_RELEASE_LABEL* pCopyLabel = pCopy->rgReleaseLabels + i; + + pCopyLabel->cchLabelOffset = pSourceLabel->cchLabelOffset; + pCopyLabel->cchLabel = pSourceLabel->cchLabel; + pCopyLabel->fNumeric = pSourceLabel->fNumeric; + pCopyLabel->dwValue = pSourceLabel->dwValue; + } + + pCopy->cchMetadataOffset = pSource->cchMetadataOffset; + pCopy->fInvalid = pSource->fInvalid; + + *ppVersion = pCopy; + pCopy = NULL; + +LExit: + ReleaseVerutilVersion(pCopy); + + return hr; +} + +DAPI_(void) VerFreeVersion( + __in VERUTIL_VERSION* pVersion + ) +{ + if (pVersion) + { + ReleaseStr(pVersion->sczVersion); + ReleaseMem(pVersion->rgReleaseLabels); + ReleaseMem(pVersion); + } +} + +DAPI_(HRESULT) VerParseVersion( + __in_z LPCWSTR wzVersion, + __in DWORD cchVersion, + __in BOOL fStrict, + __out VERUTIL_VERSION** ppVersion + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + LPCWSTR wzEnd = NULL; + LPCWSTR wzPartBegin = NULL; + LPCWSTR wzPartEnd = NULL; + BOOL fInvalid = FALSE; + BOOL fLastPart = FALSE; + BOOL fTrailingDot = FALSE; + BOOL fParsedVersionNumber = FALSE; + BOOL fExpectedReleaseLabels = FALSE; + DWORD iPart = 0; + + if (!wzVersion || !ppVersion) + { + ExitFunction1(hr = E_INVALIDARG); + } + + // Get string length if not provided. + if (0 == cchVersion) + { + cchVersion = lstrlenW(wzVersion); + } + + if (L'v' == *wzVersion || L'V' == *wzVersion) + { + ++wzVersion; + --cchVersion; + } + + pVersion = reinterpret_cast(MemAlloc(sizeof(VERUTIL_VERSION), TRUE)); + VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version '%ls'.", wzVersion); + + hr = StrAllocString(&pVersion->sczVersion, wzVersion, cchVersion); + VerExitOnFailure(hr, "Failed to copy Verutil version string '%ls'.", wzVersion); + + wzVersion = wzPartBegin = wzPartEnd = pVersion->sczVersion; + + // Save end pointer. + wzEnd = wzVersion + cchVersion; + + // Parse version number + while (wzPartBegin < wzEnd) + { + fTrailingDot = FALSE; + + // Find end of part. + for (;;) + { + if (wzPartEnd >= wzEnd) + { + fLastPart = TRUE; + break; + } + + switch (*wzPartEnd) + { + case L'0': + case L'1': + case L'2': + case L'3': + case L'4': + case L'5': + case L'6': + case L'7': + case L'8': + case L'9': + ++wzPartEnd; + continue; + case L'.': + fTrailingDot = TRUE; + break; + case L'-': + case L'+': + fLastPart = TRUE; + break; + default: + fInvalid = TRUE; + break; + } + + break; + } + + if (wzPartBegin == wzPartEnd) + { + fInvalid = TRUE; + } + + if (fInvalid) + { + break; + } + + DWORD cchPart = 0; + hr = ::PtrdiffTToDWord(wzPartEnd - wzPartBegin, &cchPart); + if (FAILED(hr)) + { + fInvalid = TRUE; + break; + } + + // Parse version part. + UINT uPart = 0; + hr = StrStringToUInt32(wzPartBegin, cchPart, &uPart); + if (FAILED(hr)) + { + fInvalid = TRUE; + break; + } + + switch (iPart) + { + case 0: + pVersion->dwMajor = uPart; + break; + case 1: + pVersion->dwMinor = uPart; + break; + case 2: + pVersion->dwPatch = uPart; + break; + case 3: + pVersion->dwRevision = uPart; + break; + } + + if (fTrailingDot) + { + ++wzPartEnd; + } + wzPartBegin = wzPartEnd; + ++iPart; + + if (4 <= iPart || fLastPart) + { + fParsedVersionNumber = TRUE; + break; + } + } + + fInvalid |= !fParsedVersionNumber || fTrailingDot; + + if (!fInvalid && wzPartBegin < wzEnd && *wzPartBegin == L'-') + { + wzPartBegin = wzPartEnd = wzPartBegin + 1; + fExpectedReleaseLabels = TRUE; + fLastPart = FALSE; + } + + while (fExpectedReleaseLabels && wzPartBegin < wzEnd) + { + fTrailingDot = FALSE; + + // Find end of part. + for (;;) + { + if (wzPartEnd >= wzEnd) + { + fLastPart = TRUE; + break; + } + + if (*wzPartEnd >= L'0' && *wzPartEnd <= L'9' || + *wzPartEnd >= L'A' && *wzPartEnd <= L'Z' || + *wzPartEnd >= L'a' && *wzPartEnd <= L'z' || + *wzPartEnd == L'-') + { + ++wzPartEnd; + continue; + } + else if (*wzPartEnd == L'+') + { + fLastPart = TRUE; + } + else if (*wzPartEnd == L'.') + { + fTrailingDot = TRUE; + } + else + { + fInvalid = TRUE; + } + + break; + } + + if (wzPartBegin == wzPartEnd) + { + fInvalid = TRUE; + } + + if (fInvalid) + { + break; + } + + int cchLabel = 0; + hr = ::PtrdiffTToInt32(wzPartEnd - wzPartBegin, &cchLabel); + if (FAILED(hr) || 0 > cchLabel) + { + fInvalid = TRUE; + break; + } + + hr = MemReAllocArray(reinterpret_cast(&pVersion->rgReleaseLabels), pVersion->cReleaseLabels, sizeof(VERUTIL_VERSION_RELEASE_LABEL), GROW_RELEASE_LABELS - (pVersion->cReleaseLabels % GROW_RELEASE_LABELS)); + VerExitOnFailure(hr, "Failed to allocate memory for Verutil version release labels '%ls'", wzVersion); + + VERUTIL_VERSION_RELEASE_LABEL* pReleaseLabel = pVersion->rgReleaseLabels + pVersion->cReleaseLabels; + ++pVersion->cReleaseLabels; + + // Try to parse as number. + UINT uLabel = 0; + hr = StrStringToUInt32(wzPartBegin, cchLabel, &uLabel); + if (SUCCEEDED(hr)) + { + pReleaseLabel->fNumeric = TRUE; + pReleaseLabel->dwValue = uLabel; + } + + pReleaseLabel->cchLabelOffset = wzPartBegin - pVersion->sczVersion; + pReleaseLabel->cchLabel = cchLabel; + + if (fTrailingDot) + { + ++wzPartEnd; + } + wzPartBegin = wzPartEnd; + + if (fLastPart) + { + break; + } + } + + fInvalid |= fExpectedReleaseLabels && (!pVersion->cReleaseLabels || fTrailingDot); + + if (!fInvalid && wzPartBegin < wzEnd) + { + if (*wzPartBegin == L'+') + { + wzPartBegin = wzPartEnd = wzPartBegin + 1; + } + else + { + fInvalid = TRUE; + } + } + + if (fInvalid && fStrict) + { + ExitFunction1(hr = E_INVALIDARG); + } + + pVersion->cchMetadataOffset = min(wzPartBegin, wzEnd) - pVersion->sczVersion; + pVersion->fInvalid = fInvalid; + + *ppVersion = pVersion; + pVersion = NULL; + hr = S_OK; + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + +DAPI_(HRESULT) VerVersionFromQword( + __in DWORD64 qwVersion, + __out VERUTIL_VERSION** ppVersion + ) +{ + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion = NULL; + + pVersion = reinterpret_cast(MemAlloc(sizeof(VERUTIL_VERSION), TRUE)); + VerExitOnNull(pVersion, hr, E_OUTOFMEMORY, "Failed to allocate memory for Verutil version from QWORD."); + + pVersion->dwMajor = (WORD)(qwVersion >> 48 & 0xffff); + pVersion->dwMinor = (WORD)(qwVersion >> 32 & 0xffff); + pVersion->dwPatch = (WORD)(qwVersion >> 16 & 0xffff); + pVersion->dwRevision = (WORD)(qwVersion & 0xffff); + + hr = StrAllocFormatted(&pVersion->sczVersion, L"%lu.%lu.%lu.%lu", pVersion->dwMajor, pVersion->dwMinor, pVersion->dwPatch, pVersion->dwRevision); + ExitOnFailure(hr, "Failed to allocate and format the version string."); + + pVersion->cchMetadataOffset = lstrlenW(pVersion->sczVersion); + + *ppVersion = pVersion; + pVersion = NULL; + +LExit: + ReleaseVerutilVersion(pVersion); + + return hr; +} + + +static int CompareDword( + __in const DWORD& dw1, + __in const DWORD& dw2 + ) +{ + int nResult = 0; + + if (dw1 > dw2) + { + nResult = 1; + } + else if (dw1 < dw2) + { + nResult = -1; + } + + return nResult; +} + +static HRESULT CompareReleaseLabel( + __in const VERUTIL_VERSION_RELEASE_LABEL* p1, + __in LPCWSTR wzVersion1, + __in const VERUTIL_VERSION_RELEASE_LABEL* p2, + __in LPCWSTR wzVersion2, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + int nResult = 0; + + if (p1 == p2) + { + ExitFunction(); + } + else if (p1 && !p2) + { + ExitFunction1(nResult = 1); + } + else if (!p1 && p2) + { + ExitFunction1(nResult = -1); + } + + if (p1->fNumeric) + { + if (p2->fNumeric) + { + nResult = CompareDword(p1->dwValue, p2->dwValue); + } + else + { + nResult = -1; + } + } + else + { + if (p2->fNumeric) + { + nResult = 1; + } + else + { + hr = CompareVersionSubstring(wzVersion1 + p1->cchLabelOffset, p1->cchLabel, wzVersion2 + p2->cchLabelOffset, p2->cchLabel, &nResult); + } + } + +LExit: + *pnResult = nResult; + + return hr; +} + +static HRESULT CompareVersionSubstring( + __in LPCWSTR wzString1, + __in int cchCount1, + __in LPCWSTR wzString2, + __in int cchCount2, + __out int* pnResult + ) +{ + HRESULT hr = S_OK; + int nResult = 0; + + nResult = ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzString1, cchCount1, wzString2, cchCount2); + if (!nResult) + { + VerExitOnLastError(hr, "Failed to compare version substrings"); + } + +LExit: + *pnResult = nResult - 2; + + return hr; +} diff --git a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj index 4c660aa9..31b5a5c0 100644 --- a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj +++ b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj @@ -49,6 +49,7 @@ + diff --git a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters index 0c83e3fa..fdc6d278 100644 --- a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters +++ b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters @@ -57,6 +57,9 @@ Source Files + + Source Files + diff --git a/src/test/DUtilUnitTest/VerUtilTests.cpp b/src/test/DUtilUnitTest/VerUtilTests.cpp new file mode 100644 index 00000000..58b561e9 --- /dev/null +++ b/src/test/DUtilUnitTest/VerUtilTests.cpp @@ -0,0 +1,891 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +#include "precomp.h" + +using namespace System; +using namespace Xunit; +using namespace WixBuildTools::TestSupport; + +namespace DutilTests +{ + public ref class VerUtil + { + public: + [Fact] + void VerCompareVersionsTreatsMissingRevisionAsZero() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + LPCWSTR wzVersion1 = L"1.2.3.4"; + LPCWSTR wzVersion2 = L"1.2.3"; + LPCWSTR wzVersion3 = L"1.2.3.0"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(1, pVersion1->dwMajor); + Assert::Equal(2, pVersion1->dwMinor); + Assert::Equal(3, pVersion1->dwPatch); + Assert::Equal(4, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(7, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(1, pVersion2->dwMajor); + Assert::Equal(2, pVersion2->dwMinor); + Assert::Equal(3, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(5, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(1, pVersion3->dwMajor); + Assert::Equal(2, pVersion3->dwMinor); + Assert::Equal(3, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(0, pVersion3->cReleaseLabels); + Assert::Equal(7, pVersion3->cchMetadataOffset); + Assert::Equal(FALSE, pVersion3->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1); + TestVerutilCompareParsedVersions(pVersion3, pVersion2, 0); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + } + } + + [Fact] + void VerCompareVersionsTreatsNumericReleaseLabelsAsNumbers() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + LPCWSTR wzVersion1 = L"1.0-2.0"; + LPCWSTR wzVersion2 = L"1.0-19"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(1, pVersion1->dwMajor); + Assert::Equal(0, pVersion1->dwMinor); + Assert::Equal(0, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(2, pVersion1->cReleaseLabels); + + Assert::Equal(TRUE, pVersion1->rgReleaseLabels[0].fNumeric); + Assert::Equal(2, pVersion1->rgReleaseLabels[0].dwValue); + Assert::Equal(1, pVersion1->rgReleaseLabels[0].cchLabel); + Assert::Equal(4, pVersion1->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(TRUE, pVersion1->rgReleaseLabels[1].fNumeric); + Assert::Equal(0, pVersion1->rgReleaseLabels[1].dwValue); + Assert::Equal(1, pVersion1->rgReleaseLabels[1].cchLabel); + Assert::Equal(6, pVersion1->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(7, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(1, pVersion2->dwMajor); + Assert::Equal(0, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(1, pVersion2->cReleaseLabels); + + Assert::Equal(TRUE, pVersion2->rgReleaseLabels[0].fNumeric); + Assert::Equal(19, pVersion2->rgReleaseLabels[0].dwValue); + Assert::Equal(2, pVersion2->rgReleaseLabels[0].cchLabel); + Assert::Equal(4, pVersion2->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(6, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, -1); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + } + } + + [Fact] + void VerCompareVersionsHandlesNormallyInvalidVersions() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + VERUTIL_VERSION* pVersion4 = NULL; + VERUTIL_VERSION* pVersion5 = NULL; + VERUTIL_VERSION* pVersion6 = NULL; + LPCWSTR wzVersion1 = L"10.-4.0"; + LPCWSTR wzVersion2 = L"10.-2.0"; + LPCWSTR wzVersion3 = L"0"; + LPCWSTR wzVersion4 = L""; + LPCWSTR wzVersion5 = L"10-2"; + LPCWSTR wzVersion6 = L"10-4.@"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4); + + hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5); + + hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(10, pVersion1->dwMajor); + Assert::Equal(0, pVersion1->dwMinor); + Assert::Equal(0, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(3, pVersion1->cchMetadataOffset); + Assert::Equal(TRUE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(10, pVersion2->dwMajor); + Assert::Equal(0, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(3, pVersion2->cchMetadataOffset); + Assert::Equal(TRUE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(0, pVersion3->dwMajor); + Assert::Equal(0, pVersion3->dwMinor); + Assert::Equal(0, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(0, pVersion3->cReleaseLabels); + Assert::Equal(1, pVersion3->cchMetadataOffset); + Assert::Equal(FALSE, pVersion3->fInvalid); + + NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion); + Assert::Equal(0, pVersion4->dwMajor); + Assert::Equal(0, pVersion4->dwMinor); + Assert::Equal(0, pVersion4->dwPatch); + Assert::Equal(0, pVersion4->dwRevision); + Assert::Equal(0, pVersion4->cReleaseLabels); + Assert::Equal(0, pVersion4->cchMetadataOffset); + Assert::Equal(TRUE, pVersion4->fInvalid); + + NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion); + Assert::Equal(10, pVersion5->dwMajor); + Assert::Equal(0, pVersion5->dwMinor); + Assert::Equal(0, pVersion5->dwPatch); + Assert::Equal(0, pVersion5->dwRevision); + Assert::Equal(1, pVersion5->cReleaseLabels); + + Assert::Equal(TRUE, pVersion5->rgReleaseLabels[0].fNumeric); + Assert::Equal(2, pVersion5->rgReleaseLabels[0].dwValue); + Assert::Equal(1, pVersion5->rgReleaseLabels[0].cchLabel); + Assert::Equal(3, pVersion5->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(4, pVersion5->cchMetadataOffset); + Assert::Equal(FALSE, pVersion5->fInvalid); + + NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion); + Assert::Equal(10, pVersion6->dwMajor); + Assert::Equal(0, pVersion6->dwMinor); + Assert::Equal(0, pVersion6->dwPatch); + Assert::Equal(0, pVersion6->dwRevision); + Assert::Equal(1, pVersion6->cReleaseLabels); + + Assert::Equal(TRUE, pVersion6->rgReleaseLabels[0].fNumeric); + Assert::Equal(4, pVersion6->rgReleaseLabels[0].dwValue); + Assert::Equal(1, pVersion6->rgReleaseLabels[0].cchLabel); + Assert::Equal(3, pVersion6->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(5, pVersion6->cchMetadataOffset); + Assert::Equal(TRUE, pVersion6->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1); + TestVerutilCompareParsedVersions(pVersion3, pVersion4, 1); + TestVerutilCompareParsedVersions(pVersion5, pVersion6, 1); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + ReleaseVerutilVersion(pVersion4); + ReleaseVerutilVersion(pVersion5); + ReleaseVerutilVersion(pVersion6); + } + } + + [Fact] + void VerCompareVersionsTreatsHyphenAsVersionSeparator() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + LPCWSTR wzVersion1 = L"0.0.1-a"; + LPCWSTR wzVersion2 = L"0-2"; + LPCWSTR wzVersion3 = L"1-2"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(0, pVersion1->dwMajor); + Assert::Equal(0, pVersion1->dwMinor); + Assert::Equal(1, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(1, pVersion1->cReleaseLabels); + + Assert::Equal(FALSE, pVersion1->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion1->rgReleaseLabels[0].cchLabel); + Assert::Equal(6, pVersion1->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(7, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(0, pVersion2->dwMajor); + Assert::Equal(0, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(1, pVersion2->cReleaseLabels); + + Assert::Equal(TRUE, pVersion2->rgReleaseLabels[0].fNumeric); + Assert::Equal(2, pVersion2->rgReleaseLabels[0].dwValue); + Assert::Equal(1, pVersion2->rgReleaseLabels[0].cchLabel); + Assert::Equal(2, pVersion2->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(3, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(1, pVersion3->dwMajor); + Assert::Equal(0, pVersion3->dwMinor); + Assert::Equal(0, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(1, pVersion3->cReleaseLabels); + + Assert::Equal(TRUE, pVersion3->rgReleaseLabels[0].fNumeric); + Assert::Equal(2, pVersion3->rgReleaseLabels[0].dwValue); + Assert::Equal(1, pVersion3->rgReleaseLabels[0].cchLabel); + Assert::Equal(2, pVersion3->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(3, pVersion3->cchMetadataOffset); + Assert::Equal(FALSE, pVersion3->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1); + TestVerutilCompareParsedVersions(pVersion1, pVersion3, -1); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + } + } + + [Fact] + void VerCompareVersionsIgnoresLeadingZeroes() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + VERUTIL_VERSION* pVersion4 = NULL; + LPCWSTR wzVersion1 = L"0.01-a.1"; + LPCWSTR wzVersion2 = L"0.1.0-a.1"; + LPCWSTR wzVersion3 = L"0.1-a.b.0"; + LPCWSTR wzVersion4 = L"0.1.0-a.b.000"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(0, pVersion1->dwMajor); + Assert::Equal(1, pVersion1->dwMinor); + Assert::Equal(0, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(2, pVersion1->cReleaseLabels); + + Assert::Equal(FALSE, pVersion1->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion1->rgReleaseLabels[0].cchLabel); + Assert::Equal(5, pVersion1->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(TRUE, pVersion1->rgReleaseLabels[1].fNumeric); + Assert::Equal(1, pVersion1->rgReleaseLabels[1].dwValue); + Assert::Equal(1, pVersion1->rgReleaseLabels[1].cchLabel); + Assert::Equal(7, pVersion1->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(8, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(0, pVersion2->dwMajor); + Assert::Equal(1, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(2, pVersion2->cReleaseLabels); + + Assert::Equal(FALSE, pVersion2->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion2->rgReleaseLabels[0].cchLabel); + Assert::Equal(6, pVersion2->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(TRUE, pVersion2->rgReleaseLabels[1].fNumeric); + Assert::Equal(1, pVersion2->rgReleaseLabels[1].dwValue); + Assert::Equal(1, pVersion2->rgReleaseLabels[1].cchLabel); + Assert::Equal(8, pVersion2->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(9, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(0, pVersion3->dwMajor); + Assert::Equal(1, pVersion3->dwMinor); + Assert::Equal(0, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(3, pVersion3->cReleaseLabels); + + Assert::Equal(FALSE, pVersion3->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion3->rgReleaseLabels[0].cchLabel); + Assert::Equal(4, pVersion3->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(FALSE, pVersion3->rgReleaseLabels[1].fNumeric); + Assert::Equal(1, pVersion3->rgReleaseLabels[1].cchLabel); + Assert::Equal(6, pVersion3->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(TRUE, pVersion3->rgReleaseLabels[2].fNumeric); + Assert::Equal(0, pVersion3->rgReleaseLabels[2].dwValue); + Assert::Equal(1, pVersion3->rgReleaseLabels[2].cchLabel); + Assert::Equal(8, pVersion3->rgReleaseLabels[2].cchLabelOffset); + + Assert::Equal(9, pVersion3->cchMetadataOffset); + Assert::Equal(FALSE, pVersion3->fInvalid); + + NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion); + Assert::Equal(0, pVersion4->dwMajor); + Assert::Equal(1, pVersion4->dwMinor); + Assert::Equal(0, pVersion4->dwPatch); + Assert::Equal(0, pVersion4->dwRevision); + Assert::Equal(3, pVersion4->cReleaseLabels); + + Assert::Equal(FALSE, pVersion4->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion4->rgReleaseLabels[0].cchLabel); + Assert::Equal(6, pVersion4->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(FALSE, pVersion4->rgReleaseLabels[1].fNumeric); + Assert::Equal(1, pVersion4->rgReleaseLabels[1].cchLabel); + Assert::Equal(8, pVersion4->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(TRUE, pVersion4->rgReleaseLabels[2].fNumeric); + Assert::Equal(0, pVersion4->rgReleaseLabels[2].dwValue); + Assert::Equal(3, pVersion4->rgReleaseLabels[2].cchLabel); + Assert::Equal(10, pVersion4->rgReleaseLabels[2].cchLabelOffset); + + Assert::Equal(13, pVersion4->cchMetadataOffset); + Assert::Equal(FALSE, pVersion4->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0); + TestVerutilCompareParsedVersions(pVersion3, pVersion4, 0); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + ReleaseVerutilVersion(pVersion4); + } + } + + [Fact] + void VerCompareVersionsTreatsUnexpectedContentAsMetadata() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + LPCWSTR wzVersion1 = L"1.2.3+abcd"; + LPCWSTR wzVersion2 = L"1.2.3.abcd"; + LPCWSTR wzVersion3 = L"1.2.3.-abcd"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(1, pVersion1->dwMajor); + Assert::Equal(2, pVersion1->dwMinor); + Assert::Equal(3, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(6, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(1, pVersion2->dwMajor); + Assert::Equal(2, pVersion2->dwMinor); + Assert::Equal(3, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(6, pVersion2->cchMetadataOffset); + Assert::Equal(TRUE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(1, pVersion3->dwMajor); + Assert::Equal(2, pVersion3->dwMinor); + Assert::Equal(3, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(0, pVersion3->cReleaseLabels); + Assert::Equal(6, pVersion3->cchMetadataOffset); + Assert::Equal(TRUE, pVersion3->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1); + TestVerutilCompareParsedVersions(pVersion1, pVersion3, 1); + TestVerutilCompareParsedVersions(pVersion2, pVersion3, -1); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + } + } + + [Fact] + void VerCompareVersionsIgnoresLeadingV() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + LPCWSTR wzVersion1 = L"10.20.30.40"; + LPCWSTR wzVersion2 = L"v10.20.30.40"; + LPCWSTR wzVersion3 = L"V10.20.30.40"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(10, pVersion1->dwMajor); + Assert::Equal(20, pVersion1->dwMinor); + Assert::Equal(30, pVersion1->dwPatch); + Assert::Equal(40, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(11, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion1, pVersion2->sczVersion); + Assert::Equal(10, pVersion2->dwMajor); + Assert::Equal(20, pVersion2->dwMinor); + Assert::Equal(30, pVersion2->dwPatch); + Assert::Equal(40, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(11, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion1, pVersion3->sczVersion); + Assert::Equal(10, pVersion3->dwMajor); + Assert::Equal(20, pVersion3->dwMinor); + Assert::Equal(30, pVersion3->dwPatch); + Assert::Equal(40, pVersion3->dwRevision); + Assert::Equal(0, pVersion3->cReleaseLabels); + Assert::Equal(11, pVersion3->cchMetadataOffset); + Assert::Equal(FALSE, pVersion3->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0); + TestVerutilCompareParsedVersions(pVersion1, pVersion3, 0); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + } + } + + [Fact] + void VerCompareVersionsHandlesTooLargeNumbers() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + LPCWSTR wzVersion1 = L"4294967295.4294967295.4294967295.4294967295"; + LPCWSTR wzVersion2 = L"4294967296.4294967296.4294967296.4294967296"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(4294967295, pVersion1->dwMajor); + Assert::Equal(4294967295, pVersion1->dwMinor); + Assert::Equal(4294967295, pVersion1->dwPatch); + Assert::Equal(4294967295, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(43, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(0, pVersion2->dwMajor); + Assert::Equal(0, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(0, pVersion2->cchMetadataOffset); + Assert::Equal(TRUE, pVersion2->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + } + } + + [Fact] + void VerCompareVersionsIgnoresMetadataForValidVersions() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + LPCWSTR wzVersion1 = L"1.2.3+abc"; + LPCWSTR wzVersion2 = L"1.2.3+xyz"; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(1, pVersion1->dwMajor); + Assert::Equal(2, pVersion1->dwMinor); + Assert::Equal(3, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(6, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(1, pVersion2->dwMajor); + Assert::Equal(2, pVersion2->dwMinor); + Assert::Equal(3, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(6, pVersion2->cchMetadataOffset); + Assert::Equal(FALSE, pVersion2->fInvalid); + + TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + } + } + + [Fact] + void VerCopyVersionCopiesVersion() + { + HRESULT hr = S_OK; + LPCWSTR wzVersion = L"1.2.3.4-a.b.c.d.5.+abc123"; + VERUTIL_VERSION* pSource = NULL; + VERUTIL_VERSION* pCopy = NULL; + int nResult = 0; + + try + { + hr = VerParseVersion(wzVersion, 0, FALSE, &pSource); + NativeAssert::Succeeded(hr, "VerParseVersion failed"); + + NativeAssert::StringEqual(wzVersion, pSource->sczVersion); + Assert::Equal(1, pSource->dwMajor); + Assert::Equal(2, pSource->dwMinor); + Assert::Equal(3, pSource->dwPatch); + Assert::Equal(4, pSource->dwRevision); + Assert::Equal(5, pSource->cReleaseLabels); + + Assert::Equal(FALSE, pSource->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pSource->rgReleaseLabels[0].cchLabel); + Assert::Equal(8, pSource->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(FALSE, pSource->rgReleaseLabels[1].fNumeric); + Assert::Equal(1, pSource->rgReleaseLabels[1].cchLabel); + Assert::Equal(10, pSource->rgReleaseLabels[1].cchLabelOffset); + + Assert::Equal(FALSE, pSource->rgReleaseLabels[2].fNumeric); + Assert::Equal(1, pSource->rgReleaseLabels[2].cchLabel); + Assert::Equal(12, pSource->rgReleaseLabels[2].cchLabelOffset); + + Assert::Equal(FALSE, pSource->rgReleaseLabels[3].fNumeric); + Assert::Equal(1, pSource->rgReleaseLabels[3].cchLabel); + Assert::Equal(14, pSource->rgReleaseLabels[3].cchLabelOffset); + + Assert::Equal(TRUE, pSource->rgReleaseLabels[4].fNumeric); + Assert::Equal(5, pSource->rgReleaseLabels[4].dwValue); + Assert::Equal(1, pSource->rgReleaseLabels[4].cchLabel); + Assert::Equal(16, pSource->rgReleaseLabels[4].cchLabelOffset); + + Assert::Equal(18, pSource->cchMetadataOffset); + Assert::Equal(TRUE, pSource->fInvalid); + + hr = VerCopyVersion(pSource, &pCopy); + NativeAssert::Succeeded(hr, "VerCopyVersion failed"); + + Assert::False(pSource == pCopy); + Assert::False(pSource->sczVersion == pCopy->sczVersion); + Assert::False(pSource->rgReleaseLabels == pCopy->rgReleaseLabels); + + hr = VerCompareParsedVersions(pSource, pCopy, &nResult); + NativeAssert::Succeeded(hr, "VerCompareParsedVersions failed"); + + Assert::Equal(nResult, 0); + } + finally + { + ReleaseVerutilVersion(pCopy); + ReleaseVerutilVersion(pSource); + } + } + + [Fact] + void VerParseVersionTreatsTrailingDotsAsInvalid() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + VERUTIL_VERSION* pVersion2 = NULL; + VERUTIL_VERSION* pVersion3 = NULL; + VERUTIL_VERSION* pVersion4 = NULL; + VERUTIL_VERSION* pVersion5 = NULL; + VERUTIL_VERSION* pVersion6 = NULL; + VERUTIL_VERSION* pVersion7 = NULL; + LPCWSTR wzVersion1 = L"."; + LPCWSTR wzVersion2 = L"1."; + LPCWSTR wzVersion3 = L"2.1."; + LPCWSTR wzVersion4 = L"3.2.1."; + LPCWSTR wzVersion5 = L"4.3.2.1."; + LPCWSTR wzVersion6 = L"5-."; + LPCWSTR wzVersion7 = L"6-a."; + + try + { + hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1); + + hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2); + + hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3); + + hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4); + + hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5); + + hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6); + + hr = VerParseVersion(wzVersion7, 0, FALSE, &pVersion7); + NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion7); + + NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion); + Assert::Equal(0, pVersion1->dwMajor); + Assert::Equal(0, pVersion1->dwMinor); + Assert::Equal(0, pVersion1->dwPatch); + Assert::Equal(0, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(0, pVersion1->cchMetadataOffset); + Assert::Equal(TRUE, pVersion1->fInvalid); + + NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion); + Assert::Equal(1, pVersion2->dwMajor); + Assert::Equal(0, pVersion2->dwMinor); + Assert::Equal(0, pVersion2->dwPatch); + Assert::Equal(0, pVersion2->dwRevision); + Assert::Equal(0, pVersion2->cReleaseLabels); + Assert::Equal(2, pVersion2->cchMetadataOffset); + Assert::Equal(TRUE, pVersion2->fInvalid); + + NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion); + Assert::Equal(2, pVersion3->dwMajor); + Assert::Equal(1, pVersion3->dwMinor); + Assert::Equal(0, pVersion3->dwPatch); + Assert::Equal(0, pVersion3->dwRevision); + Assert::Equal(0, pVersion3->cReleaseLabels); + Assert::Equal(4, pVersion3->cchMetadataOffset); + Assert::Equal(TRUE, pVersion3->fInvalid); + + NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion); + Assert::Equal(3, pVersion4->dwMajor); + Assert::Equal(2, pVersion4->dwMinor); + Assert::Equal(1, pVersion4->dwPatch); + Assert::Equal(0, pVersion4->dwRevision); + Assert::Equal(0, pVersion4->cReleaseLabels); + Assert::Equal(6, pVersion4->cchMetadataOffset); + Assert::Equal(TRUE, pVersion4->fInvalid); + + NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion); + Assert::Equal(4, pVersion5->dwMajor); + Assert::Equal(3, pVersion5->dwMinor); + Assert::Equal(2, pVersion5->dwPatch); + Assert::Equal(1, pVersion5->dwRevision); + Assert::Equal(0, pVersion5->cReleaseLabels); + Assert::Equal(8, pVersion5->cchMetadataOffset); + Assert::Equal(TRUE, pVersion5->fInvalid); + + NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion); + Assert::Equal(5, pVersion6->dwMajor); + Assert::Equal(0, pVersion6->dwMinor); + Assert::Equal(0, pVersion6->dwPatch); + Assert::Equal(0, pVersion6->dwRevision); + Assert::Equal(0, pVersion6->cReleaseLabels); + Assert::Equal(2, pVersion6->cchMetadataOffset); + Assert::Equal(TRUE, pVersion6->fInvalid); + + NativeAssert::StringEqual(wzVersion7, pVersion7->sczVersion); + Assert::Equal(6, pVersion7->dwMajor); + Assert::Equal(0, pVersion7->dwMinor); + Assert::Equal(0, pVersion7->dwPatch); + Assert::Equal(0, pVersion7->dwRevision); + Assert::Equal(1, pVersion7->cReleaseLabels); + + Assert::Equal(FALSE, pVersion7->rgReleaseLabels[0].fNumeric); + Assert::Equal(1, pVersion7->rgReleaseLabels[0].cchLabel); + Assert::Equal(2, pVersion7->rgReleaseLabels[0].cchLabelOffset); + + Assert::Equal(4, pVersion7->cchMetadataOffset); + Assert::Equal(TRUE, pVersion7->fInvalid); + } + finally + { + ReleaseVerutilVersion(pVersion1); + ReleaseVerutilVersion(pVersion2); + ReleaseVerutilVersion(pVersion3); + ReleaseVerutilVersion(pVersion4); + ReleaseVerutilVersion(pVersion5); + ReleaseVerutilVersion(pVersion6); + ReleaseVerutilVersion(pVersion7); + } + } + + [Fact] + void VerVersionFromQwordCreatesVersion() + { + HRESULT hr = S_OK; + VERUTIL_VERSION* pVersion1 = NULL; + + try + { + hr = VerVersionFromQword(MAKEQWORDVERSION(1, 2, 3, 4), &pVersion1); + NativeAssert::Succeeded(hr, "VerVersionFromQword failed"); + + NativeAssert::StringEqual(L"1.2.3.4", pVersion1->sczVersion); + Assert::Equal(1, pVersion1->dwMajor); + Assert::Equal(2, pVersion1->dwMinor); + Assert::Equal(3, pVersion1->dwPatch); + Assert::Equal(4, pVersion1->dwRevision); + Assert::Equal(0, pVersion1->cReleaseLabels); + Assert::Equal(7, pVersion1->cchMetadataOffset); + Assert::Equal(FALSE, pVersion1->fInvalid); + } + finally + { + ReleaseVerutilVersion(pVersion1); + } + } + + private: + void TestVerutilCompareParsedVersions(VERUTIL_VERSION* pVersion1, VERUTIL_VERSION* pVersion2, int nExpectedResult) + { + HRESULT hr = S_OK; + int nResult = 0; + + hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult); + NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion); + + Assert::Equal(nExpectedResult, nResult); + + hr = VerCompareParsedVersions(pVersion2, pVersion1, &nResult); + NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion); + + Assert::Equal(nExpectedResult, -nResult); + } + }; +} diff --git a/src/test/DUtilUnitTest/precomp.h b/src/test/DUtilUnitTest/precomp.h index b3a1a9cb..f665fed1 100644 --- a/src/test/DUtilUnitTest/precomp.h +++ b/src/test/DUtilUnitTest/precomp.h @@ -22,6 +22,7 @@ #include #include #include +#include #pragma managed #include -- cgit v1.2.3-55-g6feb