From 9c4b5559ccb55491fe68b0096d1be0496fd6fcc7 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Sat, 11 Jul 2020 21:11:30 +1000 Subject: Import DUtilUnitTest from old wix4 repo. --- src/test/DUtilUnitTest/AssemblyInfo.cpp | 12 + src/test/DUtilUnitTest/CondUtilTest.cpp | 190 ++++++++ src/test/DUtilUnitTest/DUtilUnitTest.vcxproj | 73 +++ .../DUtilUnitTest/DUtilUnitTest.vcxproj.filters | 77 +++ src/test/DUtilUnitTest/DictUtilTest.cpp | 187 ++++++++ src/test/DUtilUnitTest/DirUtilTests.cpp | 70 +++ src/test/DUtilUnitTest/FileUtilTest.cpp | 118 +++++ src/test/DUtilUnitTest/GuidUtilTest.cpp | 60 +++ src/test/DUtilUnitTest/IniUtilTest.cpp | 342 +++++++++++++ src/test/DUtilUnitTest/MemUtilTest.cpp | 491 +++++++++++++++++++ src/test/DUtilUnitTest/MonUtilTest.cpp | 487 +++++++++++++++++++ src/test/DUtilUnitTest/NativeAssert.h | 83 ++++ src/test/DUtilUnitTest/PathUtilTest.cpp | 80 ++++ src/test/DUtilUnitTest/SceUtilTest.cpp | 488 +++++++++++++++++++ src/test/DUtilUnitTest/StrUtilTest.cpp | 184 +++++++ src/test/DUtilUnitTest/UnitTest.rc | 7 + src/test/DUtilUnitTest/UriUtilTest.cpp | 96 ++++ src/test/DUtilUnitTest/VarHelpers.cpp | 147 ++++++ src/test/DUtilUnitTest/VarHelpers.h | 20 + src/test/DUtilUnitTest/VarUtilTest.cpp | 532 +++++++++++++++++++++ src/test/DUtilUnitTest/error.h | 8 + src/test/DUtilUnitTest/packages.config | 7 + src/test/DUtilUnitTest/precomp.cpp | 3 + src/test/DUtilUnitTest/precomp.h | 30 ++ src/test/DUtilUnitTest/resource.h | 2 + 25 files changed, 3794 insertions(+) create mode 100644 src/test/DUtilUnitTest/AssemblyInfo.cpp create mode 100644 src/test/DUtilUnitTest/CondUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/DUtilUnitTest.vcxproj create mode 100644 src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters create mode 100644 src/test/DUtilUnitTest/DictUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/DirUtilTests.cpp create mode 100644 src/test/DUtilUnitTest/FileUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/GuidUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/IniUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/MemUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/MonUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/NativeAssert.h create mode 100644 src/test/DUtilUnitTest/PathUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/SceUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/StrUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/UnitTest.rc create mode 100644 src/test/DUtilUnitTest/UriUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/VarHelpers.cpp create mode 100644 src/test/DUtilUnitTest/VarHelpers.h create mode 100644 src/test/DUtilUnitTest/VarUtilTest.cpp create mode 100644 src/test/DUtilUnitTest/error.h create mode 100644 src/test/DUtilUnitTest/packages.config create mode 100644 src/test/DUtilUnitTest/precomp.cpp create mode 100644 src/test/DUtilUnitTest/precomp.h create mode 100644 src/test/DUtilUnitTest/resource.h (limited to 'src') diff --git a/src/test/DUtilUnitTest/AssemblyInfo.cpp b/src/test/DUtilUnitTest/AssemblyInfo.cpp new file mode 100644 index 00000000..2d527910 --- /dev/null +++ b/src/test/DUtilUnitTest/AssemblyInfo.cpp @@ -0,0 +1,12 @@ +// 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::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; + +[assembly: AssemblyTitleAttribute("Windows Installer XML Dutil unit tests")]; +[assembly: AssemblyDescriptionAttribute("Dutil unit tests")]; +[assembly: AssemblyCultureAttribute("")]; +[assembly: ComVisible(false)]; diff --git a/src/test/DUtilUnitTest/CondUtilTest.cpp b/src/test/DUtilUnitTest/CondUtilTest.cpp new file mode 100644 index 00000000..c808363d --- /dev/null +++ b/src/test/DUtilUnitTest/CondUtilTest.cpp @@ -0,0 +1,190 @@ +// 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" + +namespace DutilTests +{ + using namespace System; + using namespace Xunit; + using namespace WixTest; + + public ref class CondUtil + { + public: + [NamedFact(Skip = "condutil Not Implemented Yet.")] + void CondEvaluateTest() + { + HRESULT hr = S_OK; + VARIABLES_HANDLE pVariables = NULL; + + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + // set variables + VarSetStringHelper(pVariables, L"PROP1", L"VAL1"); + VarSetStringHelper(pVariables, L"PROP2", L"VAL2"); + VarSetStringHelper(pVariables, L"PROP3", L"VAL3"); + VarSetStringHelper(pVariables, L"PROP4", L"BEGIN MID END"); + VarSetNumericHelper(pVariables, L"PROP5", 5); + VarSetNumericHelper(pVariables, L"PROP6", 6); + VarSetStringHelper(pVariables, L"PROP7", L""); + VarSetNumericHelper(pVariables, L"PROP8", 0); + VarSetStringHelper(pVariables, L"_PROP9", L"VAL9"); + VarSetNumericHelper(pVariables, L"PROP10", -10); + VarSetNumericHelper(pVariables, L"PROP11", 9223372036854775807ll); + VarSetNumericHelper(pVariables, L"PROP12", -9223372036854775808ll); + VarSetNumericHelper(pVariables, L"PROP13", 0x00010000); + VarSetNumericHelper(pVariables, L"PROP14", 0x00000001); + VarSetNumericHelper(pVariables, L"PROP15", 0x00010001); + VarSetVersionHelper(pVariables, L"PROP16", MAKEQWORDVERSION(0, 0, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP17", MAKEQWORDVERSION(1, 0, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP18", MAKEQWORDVERSION(1, 1, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP19", MAKEQWORDVERSION(1, 1, 1, 0)); + VarSetVersionHelper(pVariables, L"PROP20", MAKEQWORDVERSION(1, 1, 1, 1)); + VarSetNumericHelper(pVariables, L"vPROP21", 1); + VarSetVersionHelper(pVariables, L"PROP22", MAKEQWORDVERSION(65535, 65535, 65535, 65535)); + VarSetStringHelper(pVariables, L"PROP23", L"1.1.1"); + + // test conditions + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP7")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP8")); + Assert::True(EvaluateConditionHelper(pVariables, L"_PROP9")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP16")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"NONE = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 <> \"VAL1\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"NONE <> \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 ~= \"val1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"val1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 ~<> \"val1\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 <> \"val1\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 = 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 = 0")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 <> 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <> 0")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP10 = -10")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP10 <> -10")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 = v1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 = v0")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 <> v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 <> v0")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP16 = v0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 = v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 = v1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 = v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP20 = v1.1.1.1")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP20 = v1.1.1.1.0")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP20 = v1.1.1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"vPROP21 = 1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP23 = v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"v1.1.1 = PROP23")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 <> v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"v1.1.1 <> PROP1")); + + Assert::False(EvaluateConditionHelper(pVariables, L"PROP11 = 9223372036854775806")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP11 = 9223372036854775807")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP11 = 9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP11 = 92233720368547758070000")); + + Assert::False(EvaluateConditionHelper(pVariables, L"PROP12 = -9223372036854775807")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP12 = -9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP12 = -9223372036854775809")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP12 = -92233720368547758080000")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP22 = v65535.65535.65535.65535")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP22 = v65536.65535.65535.65535")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP22 = v65535.655350000.65535.65535")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 < 6")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 < 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 > 4")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 > 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <= 6")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <= 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 <= 4")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 >= 4")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 >= 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 >= 6")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 << \"BEGIN\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 << \"END\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 >> \"END\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 >> \"BEGIN\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 >< \"MID\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 >< \"NONE\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP16 < v1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP16 < v0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 > v0.12")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 > v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 >= v1.0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 >= v1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP18 >= v2.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.1234.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.0.123")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP6 = \"6\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"\"6\" = PROP6")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP6 = \"ABC\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"\"ABC\" = PROP6")); + Assert::False(EvaluateConditionHelper(pVariables, L"\"ABC\" = PROP6")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP13 << 1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP13 << 0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP14 >> 1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP14 >> 0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP15 >< 65537")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP15 >< 0")); + + Assert::False(EvaluateConditionHelper(pVariables, L"NOT PROP1")); + Assert::True(EvaluateConditionHelper(pVariables, L"NOT (PROP1 <> \"VAL1\")")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" AND PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" OR PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" OR PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" OR PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" OR PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"VAL3\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND (PROP2 = \"NOT\" OR PROP3 = \"VAL3\")")); + Assert::True(EvaluateConditionHelper(pVariables, L"(PROP1 = \"VAL1\" AND PROP2 = \"VAL2\") OR PROP3 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"VAL3\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"(PROP3 = \"NOT\" OR PROP1 = \"VAL1\") AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR (PROP1 = \"VAL1\" AND PROP2 = \"VAL2\")")); + + Assert::True(EvaluateFailureConditionHelper(pVariables, L"=")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"(PROP1")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"(PROP1 = \"")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"1A")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"*")); + + Assert::True(EvaluateFailureConditionHelper(pVariables, L"1 == 1")); + } + finally + { + ReleaseVariables(pVariables); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj new file mode 100644 index 00000000..292cf28a --- /dev/null +++ b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj @@ -0,0 +1,73 @@ + + + + + + + + Debug + Win32 + + + Release + Win32 + + + + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} + {AB7EE608-E5FB-42A5-831F-0DEEEA141223} + DUtilUnitTests + ManagedCProj + DynamicLibrary + Unicode + true + + + + $(WixRoot)src\libs\dutil\inc + rpcrt4.lib;dutil.lib;Mpr.lib;Ws2_32.lib;urlmon.lib;wininet.lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(XunitPath)\xunit.dll + + + + + + {95BABD97-FBDB-453A-AF8A-FA031A07B599} + WixCppCliTestTools + + + {55CB1042-647B-4347-9876-3EA607AF8DCE} + WixTestTools + + + + diff --git a/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters new file mode 100644 index 00000000..a83db35d --- /dev/null +++ b/src/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/src/test/DUtilUnitTest/DictUtilTest.cpp b/src/test/DUtilUnitTest/DictUtilTest.cpp new file mode 100644 index 00000000..fd8a5953 --- /dev/null +++ b/src/test/DUtilUnitTest/DictUtilTest.cpp @@ -0,0 +1,187 @@ +// 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 WixTest; + +const DWORD numIterations = 100000; + +namespace DutilTests +{ + struct Value + { + DWORD dwNum; + LPWSTR sczKey; + }; + + public ref class DictUtil + { + public: + [Fact] + void DictUtilTest() + { + EmbeddedKeyTestHelper(DICT_FLAG_NONE, numIterations); + + EmbeddedKeyTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations); + + StringListTestHelper(DICT_FLAG_NONE, numIterations); + + StringListTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations); + } + + private: + void EmbeddedKeyTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations) + { + HRESULT hr = S_OK; + Value *rgValues = NULL; + Value *valueFound = NULL; + DWORD cValues = 0; + LPWSTR sczExpectedKey = NULL; + STRINGDICT_HANDLE sdValues = NULL; + + try + { + hr = DictCreateWithEmbeddedKey(&sdValues, 0, (void **)&rgValues, offsetof(Value, sczKey), dfFlags); + NativeAssert::Succeeded(hr, "Failed to create dictionary of values"); + + for (DWORD i = 0; i < dwNumIterations; ++i) + { + cValues++; + + hr = MemEnsureArraySize((void **)&rgValues, cValues, sizeof(Value), 5); + NativeAssert::Succeeded(hr, "Failed to grow value array"); + + hr = StrAllocFormatted(&rgValues[i].sczKey, L"%u_a_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i); + + hr = DictAddValue(sdValues, rgValues + i); + NativeAssert::Succeeded(hr, "Failed to add item {0} to dict", i); + } + + for (DWORD i = 0; i < dwNumIterations; ++i) + { + hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i); + + hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound); + NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey); + + NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey); + + hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i); + + hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound); + + if (dfFlags & DICT_FLAG_CASEINSENSITIVE) + { + NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey); + + NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey, TRUE); + } + else + { + if (E_NOTFOUND != hr) + { + hr = E_FAIL; + ExitOnFailure(hr, "This embedded key is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey); + } + } + + hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i); + + hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound); + if (E_NOTFOUND != hr) + { + hr = E_FAIL; + ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey); + } + } + } + finally + { + for (DWORD i = 0; i < cValues; ++i) + { + ReleaseStr(rgValues[i].sczKey); + } + ReleaseMem(rgValues); + ReleaseStr(sczExpectedKey); + ReleaseDict(sdValues); + } + + LExit: + return; + } + + void StringListTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations) + { + HRESULT hr = S_OK; + LPWSTR sczKey = NULL; + LPWSTR sczExpectedKey = NULL; + STRINGDICT_HANDLE sdValues = NULL; + + try + { + hr = DictCreateStringList(&sdValues, 0, dfFlags); + NativeAssert::Succeeded(hr, "Failed to create dictionary of keys"); + + for (DWORD i = 0; i < dwNumIterations; ++i) + { + hr = StrAllocFormatted(&sczKey, L"%u_a_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i); + + hr = DictAddKey(sdValues, sczKey); + NativeAssert::Succeeded(hr, "Failed to add key {0} to dict", i); + } + + for (DWORD i = 0; i < dwNumIterations; ++i) + { + hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i); + + hr = DictKeyExists(sdValues, sczExpectedKey); + NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey); + + hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i); + + hr = DictKeyExists(sdValues, sczExpectedKey); + if (dfFlags & DICT_FLAG_CASEINSENSITIVE) + { + NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey); + } + else + { + if (E_NOTFOUND != hr) + { + hr = E_FAIL; + ExitOnFailure(hr, "This stringlist dict is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey); + } + } + + hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i); + NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i); + + hr = DictKeyExists(sdValues, sczExpectedKey); + if (E_NOTFOUND != hr) + { + hr = E_FAIL; + ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey); + } + } + } + finally + { + ReleaseStr(sczKey); + ReleaseStr(sczExpectedKey); + ReleaseDict(sdValues); + } + + LExit: + return; + } + }; +} diff --git a/src/test/DUtilUnitTest/DirUtilTests.cpp b/src/test/DUtilUnitTest/DirUtilTests.cpp new file mode 100644 index 00000000..a965c3d5 --- /dev/null +++ b/src/test/DUtilUnitTest/DirUtilTests.cpp @@ -0,0 +1,70 @@ +// 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 WixTest; + +namespace DutilTests +{ + public ref class DirUtil + { + public: + [Fact] + void DirUtilTest() + { + HRESULT hr = S_OK; + LPWSTR sczCurrentDir = NULL; + LPWSTR sczGuid = NULL; + LPWSTR sczFolder = NULL; + LPWSTR sczSubFolder = NULL; + + try + { + hr = GuidCreate(&sczGuid); + NativeAssert::Succeeded(hr, "Failed to create guid."); + + hr = DirGetCurrent(&sczCurrentDir); + NativeAssert::Succeeded(hr, "Failed to get current directory."); + + hr = PathConcat(sczCurrentDir, sczGuid, &sczFolder); + NativeAssert::Succeeded(hr, "Failed to combine current directory: '{0}' with Guid: '{1}'", sczCurrentDir, sczGuid); + + BOOL fExists = DirExists(sczFolder, NULL); + Assert::False(fExists); + + hr = PathConcat(sczFolder, L"foo", &sczSubFolder); + NativeAssert::Succeeded(hr, "Failed to combine folder: '%ls' with subfolder: 'foo'", sczFolder); + + hr = DirEnsureExists(sczSubFolder, NULL); + NativeAssert::Succeeded(hr, "Failed to create multiple directories: %ls", sczSubFolder); + + // Test failure to delete non-empty folder. + hr = DirEnsureDelete(sczFolder, FALSE, FALSE); + Assert::Equal(HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY), hr); + + hr = DirEnsureDelete(sczSubFolder, FALSE, FALSE); + NativeAssert::Succeeded(hr, "Failed to delete single directory: %ls", sczSubFolder); + + // Put the directory back and we'll test deleting tree. + hr = DirEnsureExists(sczSubFolder, NULL); + NativeAssert::Succeeded(hr, "Failed to create single directory: %ls", sczSubFolder); + + hr = DirEnsureDelete(sczFolder, FALSE, TRUE); + NativeAssert::Succeeded(hr, "Failed to delete directory tree: %ls", sczFolder); + + // Finally, try to create "C:\" which would normally fail, but we want success + hr = DirEnsureExists(L"C:\\", NULL); + NativeAssert::Succeeded(hr, "Failed to create C:\\"); + } + finally + { + ReleaseStr(sczSubFolder); + ReleaseStr(sczFolder); + ReleaseStr(sczGuid); + ReleaseStr(sczCurrentDir); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/FileUtilTest.cpp b/src/test/DUtilUnitTest/FileUtilTest.cpp new file mode 100644 index 00000000..41638bdb --- /dev/null +++ b/src/test/DUtilUnitTest/FileUtilTest.cpp @@ -0,0 +1,118 @@ +// 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 WixTest; + +namespace DutilTests +{ + public ref class FileUtil + { + public: + [Fact(Skip="Skipped until we have a good way to reference ANSI.txt.")] + void FileUtilTest() + { + HRESULT hr = S_OK; + LPWSTR sczTempDir = NULL; + LPWSTR sczFileDir = NULL; + + try + { + hr = PathExpand(&sczTempDir, L"%TEMP%\\FileUtilTest\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::Succeeded(hr, "Failed to get temp dir"); + + hr = PathExpand(&sczFileDir, L"%WIX_ROOT%\\examples\\data\\TextEncodings\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::Succeeded(hr, "Failed to get path to encodings file dir"); + + hr = DirEnsureExists(sczTempDir, NULL); + NativeAssert::Succeeded(hr, "Failed to ensure directory exists: {0}", sczTempDir); + + TestFile(sczFileDir, sczTempDir, L"ANSI.txt", 32, FILE_ENCODING_UTF8); + // Big endian not supported today! + //TestFile(sczFileDir, L"UnicodeBENoBOM.txt", 34); + //TestFile(sczFileDir, L"UnicodeBEWithBOM.txt", 34); + TestFile(sczFileDir, sczTempDir, L"UnicodeLENoBOM.txt", 34, FILE_ENCODING_UTF16); + TestFile(sczFileDir, sczTempDir, L"UnicodeLEWithBOM.txt", 34, FILE_ENCODING_UTF16_WITH_BOM); + TestFile(sczFileDir, sczTempDir, L"UTF8WithSignature.txt", 34, FILE_ENCODING_UTF8_WITH_BOM); + + hr = DirEnsureDelete(sczTempDir, TRUE, TRUE); + } + finally + { + ReleaseStr(sczTempDir); + ReleaseStr(sczFileDir); + } + } + + private: + void TestFile(LPWSTR wzDir, LPCWSTR wzTempDir, LPWSTR wzFileName, DWORD dwExpectedStringLength, FILE_ENCODING feExpectedEncoding) + { + HRESULT hr = S_OK; + LPWSTR sczFullPath = NULL; + LPWSTR sczContents = NULL; + LPWSTR sczOutputPath = NULL; + FILE_ENCODING feEncodingFound = FILE_ENCODING_UNSPECIFIED; + BYTE *pbFile1 = NULL; + DWORD cbFile1 = 0; + BYTE *pbFile2 = NULL; + DWORD cbFile2 = 0; + + try + { + hr = PathConcat(wzDir, wzFileName, &sczFullPath); + NativeAssert::Succeeded(hr, "Failed to create path to test file: {0}", sczFullPath); + + hr = FileToString(sczFullPath, &sczContents, &feEncodingFound); + hr = E_FAIL; + NativeAssert::Succeeded(hr, "Failed to read text from file: {0}", sczFullPath); + + if (!sczContents) + { + hr = E_FAIL; + NativeAssert::Succeeded(hr, "FileToString() returned NULL for file: {0}", sczFullPath); + } + + if ((DWORD)lstrlenW(sczContents) != dwExpectedStringLength) + { + hr = E_FAIL; + ExitOnFailure(hr, "FileToString() returned wrong size for file: %ls (expected size %u, found size %u)", sczFullPath, dwExpectedStringLength, lstrlenW(sczContents)); + } + + if (feEncodingFound != feExpectedEncoding) + { + hr = E_FAIL; + ExitOnFailure(hr, "FileToString() returned unexpected encoding type for file: %ls (expected type %u, found type %u)", sczFullPath, feExpectedEncoding, feEncodingFound); + } + + hr = PathConcat(wzTempDir, wzFileName, &sczOutputPath); + NativeAssert::Succeeded(hr, "Failed to get output path"); + + hr = FileFromString(sczOutputPath, 0, sczContents, feExpectedEncoding); + NativeAssert::Succeeded(hr, "Failed to write contents of file back out to disk"); + + hr = FileRead(&pbFile1, &cbFile1, sczFullPath); + NativeAssert::Succeeded(hr, "Failed to read input file as binary"); + + hr = FileRead(&pbFile2, &cbFile2, sczOutputPath); + NativeAssert::Succeeded(hr, "Failed to read output file as binary"); + + if (cbFile1 != cbFile2 || 0 != memcmp(pbFile1, pbFile2, cbFile1)) + { + hr = E_FAIL; + ExitOnFailure(hr, "Outputted file doesn't match input file: \"%ls\" and \"%ls\"", sczFullPath, sczOutputPath); + } + } + finally + { + ReleaseStr(sczOutputPath); + ReleaseStr(sczFullPath); + ReleaseStr(sczContents); + } + + LExit: + return; + } + }; +} diff --git a/src/test/DUtilUnitTest/GuidUtilTest.cpp b/src/test/DUtilUnitTest/GuidUtilTest.cpp new file mode 100644 index 00000000..d0ea9a89 --- /dev/null +++ b/src/test/DUtilUnitTest/GuidUtilTest.cpp @@ -0,0 +1,60 @@ +// 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 WixTest; + +namespace DutilTests +{ + public ref class GuidUtil + { + public: + [Fact] + void GuidCreateTest() + { + HRESULT hr = S_OK; + WCHAR wzGuid1[GUID_STRING_LENGTH]; + WCHAR wzGuid2[GUID_STRING_LENGTH]; + + hr = GuidFixedCreate(wzGuid1); + NativeAssert::Succeeded(hr, "Failed to create first guid."); + Guid firstGuid = Guid::Parse(gcnew String(wzGuid1)); + + hr = GuidFixedCreate(wzGuid2); + NativeAssert::Succeeded(hr, "Failed to create second guid."); + Guid secondGuid = Guid::Parse(gcnew String(wzGuid2)); + + NativeAssert::NotStringEqual(wzGuid1, wzGuid2); + NativeAssert::NotEqual(firstGuid, secondGuid); + } + + [Fact] + void GuidCreateSczTest() + { + HRESULT hr = S_OK; + LPWSTR sczGuid1 = NULL; + LPWSTR sczGuid2 = NULL; + + try + { + hr = GuidCreate(&sczGuid1); + NativeAssert::Succeeded(hr, "Failed to create first guid."); + Guid firstGuid = Guid::Parse(gcnew String(sczGuid1)); + + hr = GuidCreate(&sczGuid2); + NativeAssert::Succeeded(hr, "Failed to create second guid."); + Guid secondGuid = Guid::Parse(gcnew String(sczGuid2)); + + NativeAssert::NotStringEqual(sczGuid1, sczGuid2); + NativeAssert::NotEqual(firstGuid, secondGuid); + } + finally + { + ReleaseStr(sczGuid1); + ReleaseStr(sczGuid2); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/IniUtilTest.cpp b/src/test/DUtilUnitTest/IniUtilTest.cpp new file mode 100644 index 00000000..e28f357e --- /dev/null +++ b/src/test/DUtilUnitTest/IniUtilTest.cpp @@ -0,0 +1,342 @@ +// 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 WixTest; + +typedef HRESULT (__clrcall *IniFormatParameters)( + INI_HANDLE + ); + +namespace DutilTests +{ + public ref class IniUtil + { + public: + [Fact] + void IniUtilTest() + { + HRESULT hr = S_OK; + LPWSTR sczTempIniFilePath = NULL; + LPWSTR sczTempIniFileDir = NULL; + LPWSTR wzIniContents = L" PlainValue = \t Blah \r\n;CommentHere\r\n[Section1]\r\n ;Another Comment With = Equal Sign\r\nSection1ValueA=Foo\r\n\r\nSection1ValueB=Bar\r\n[Section2]\r\nSection2ValueA=Cha\r\nArray[0]=Arr\r\n"; + LPWSTR wzScriptContents = L"setf ~PlainValue Blah\r\n;CommentHere\r\n\r\nsetf ~Section1\\Section1ValueA Foo\r\n\r\nsetf ~Section1\\Section1ValueB Bar\r\nsetf ~Section2\\Section2ValueA Cha\r\nsetf ~Section2\\Array[0] Arr\r\n"; + + try + { + hr = PathExpand(&sczTempIniFilePath, L"%TEMP%\\IniUtilTest\\Test.ini", PATH_EXPAND_ENVIRONMENT); + NativeAssert::Succeeded(hr, "Failed to get path to temp INI file"); + + hr = PathGetDirectory(sczTempIniFilePath, &sczTempIniFileDir); + NativeAssert::Succeeded(hr, "Failed to get directory to temp INI file"); + + hr = DirEnsureDelete(sczTempIniFileDir, TRUE, TRUE); + if (E_PATHNOTFOUND == hr) + { + hr = S_OK; + } + NativeAssert::Succeeded(hr, "Failed to delete IniUtilTest directory: {0}", sczTempIniFileDir); + + hr = DirEnsureExists(sczTempIniFileDir, NULL); + NativeAssert::Succeeded(hr, "Failed to ensure temp directory exists: {0}", sczTempIniFileDir); + + // Tests parsing, then modifying a regular INI file + TestReadThenWrite(sczTempIniFilePath, StandardIniFormat, wzIniContents); + + // Tests programmatically creating from scratch, then parsing an INI file + TestWriteThenRead(sczTempIniFilePath, StandardIniFormat); + + // Tests parsing, then modifying a regular INI file + TestReadThenWrite(sczTempIniFilePath, ScriptFormat, wzScriptContents); + + // Tests programmatically creating from scratch, then parsing an INI file + TestWriteThenRead(sczTempIniFilePath, ScriptFormat); + } + finally + { + ReleaseStr(sczTempIniFilePath); + ReleaseStr(sczTempIniFileDir); + } + } + + private: + void AssertValue(INI_HANDLE iniHandle, LPCWSTR wzValueName, LPCWSTR wzValue) + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + try + { + hr = IniGetValue(iniHandle, wzValueName, &sczValue); + NativeAssert::Succeeded(hr, "Failed to get ini value: {0}", wzValueName); + + if (0 != wcscmp(sczValue, wzValue)) + { + hr = E_FAIL; + ExitOnFailure(hr, "Expected to find value in INI: '%ls'='%ls' - but found value '%ls' instead", wzValueName, wzValue, sczValue); + } + } + finally + { + ReleaseStr(sczValue); + } + + LExit: + return; + } + + void AssertNoValue(INI_HANDLE iniHandle, LPCWSTR wzValueName) + { + HRESULT hr = S_OK; + LPWSTR sczValue = NULL; + + try + { + hr = IniGetValue(iniHandle, wzValueName, &sczValue); + if (E_NOTFOUND != hr) + { + if (SUCCEEDED(hr)) + { + hr = E_FAIL; + } + ExitOnFailure(hr, "INI value shouldn't have been found: %ls", wzValueName); + } + } + finally + { + ReleaseStr(sczValue); + } + + LExit: + return; + } + + static HRESULT StandardIniFormat(__inout INI_HANDLE iniHandle) + { + HRESULT hr = S_OK; + + hr = IniSetOpenTag(iniHandle, L"[", L"]"); + NativeAssert::Succeeded(hr, "Failed to set open tag settings on ini handle"); + + hr = IniSetValueStyle(iniHandle, NULL, L"="); + NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle"); + + hr = IniSetCommentStyle(iniHandle, L";"); + NativeAssert::Succeeded(hr, "Failed to set comment style setting on ini handle"); + + return hr; + } + + static HRESULT ScriptFormat(__inout INI_HANDLE iniHandle) + { + HRESULT hr = S_OK; + + hr = IniSetValueStyle(iniHandle, L"setf ~", L" "); + NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle"); + + return hr; + } + + void TestReadThenWrite(LPWSTR wzIniFilePath, IniFormatParameters SetFormat, LPCWSTR wzContents) + { + HRESULT hr = S_OK; + INI_HANDLE iniHandle = NULL; + INI_HANDLE iniHandle2 = NULL; + INI_VALUE *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = FileWrite(wzIniFilePath, 0, reinterpret_cast(wzContents), lstrlenW(wzContents) * sizeof(WCHAR), NULL); + NativeAssert::Succeeded(hr, "Failed to write out INI file"); + + hr = IniInitialize(&iniHandle); + NativeAssert::Succeeded(hr, "Failed to initialize INI object"); + + hr = SetFormat(iniHandle); + NativeAssert::Succeeded(hr, "Failed to set parameters for INI file"); + + hr = IniParse(iniHandle, wzIniFilePath, NULL); + NativeAssert::Succeeded(hr, "Failed to parse INI file"); + + hr = IniGetValueList(iniHandle, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(5, cValues); + + AssertValue(iniHandle, L"PlainValue", L"Blah"); + AssertNoValue(iniHandle, L"PlainValue2"); + AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo"); + AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar"); + AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha"); + AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist"); + AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr"); + + hr = IniSetValue(iniHandle, L"PlainValue2", L"Blah2"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section1\\CreatedValue", L"Woo"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arrmod"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniGetValueList(iniHandle, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(7, cValues); + + AssertValue(iniHandle, L"PlainValue", L"Blah"); + AssertValue(iniHandle, L"PlainValue2", L"Blah2"); + AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo"); + AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar"); + AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha"); + AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist"); + AssertValue(iniHandle, L"Section1\\CreatedValue", L"Woo"); + AssertValue(iniHandle, L"Section2\\Array[0]", L"Arrmod"); + + // Try deleting a value as well + hr = IniSetValue(iniHandle, L"Section1\\Section1ValueB", NULL); + NativeAssert::Succeeded(hr, "Failed to kill value in INI"); + + hr = IniWriteFile(iniHandle, NULL, FILE_ENCODING_UNSPECIFIED); + NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk"); + + ReleaseNullIni(iniHandle); + // Now re-parse the INI we just wrote and make sure it matches the values we expect + hr = IniInitialize(&iniHandle2); + NativeAssert::Succeeded(hr, "Failed to initialize INI object"); + + hr = SetFormat(iniHandle2); + NativeAssert::Succeeded(hr, "Failed to set parameters for INI file"); + + hr = IniParse(iniHandle2, wzIniFilePath, NULL); + NativeAssert::Succeeded(hr, "Failed to parse INI file"); + + hr = IniGetValueList(iniHandle2, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(6, cValues); + + AssertValue(iniHandle2, L"PlainValue", L"Blah"); + AssertValue(iniHandle2, L"PlainValue2", L"Blah2"); + AssertValue(iniHandle2, L"Section1\\Section1ValueA", L"Foo"); + AssertNoValue(iniHandle2, L"Section1\\Section1ValueB"); + AssertValue(iniHandle2, L"Section2\\Section2ValueA", L"Cha"); + AssertNoValue(iniHandle2, L"Section1\\ValueDoesntExist"); + AssertValue(iniHandle2, L"Section1\\CreatedValue", L"Woo"); + AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arrmod"); + } + finally + { + ReleaseIni(iniHandle); + ReleaseIni(iniHandle2); + } + } + + void TestWriteThenRead(LPWSTR wzIniFilePath, IniFormatParameters SetFormat) + { + HRESULT hr = S_OK; + INI_HANDLE iniHandle = NULL; + INI_HANDLE iniHandle2 = NULL; + INI_VALUE *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = FileEnsureDelete(wzIniFilePath); + NativeAssert::Succeeded(hr, "Failed to ensure file is deleted"); + + hr = IniInitialize(&iniHandle); + NativeAssert::Succeeded(hr, "Failed to initialize INI object"); + + hr = SetFormat(iniHandle); + NativeAssert::Succeeded(hr, "Failed to set parameters for INI file"); + + hr = IniGetValueList(iniHandle, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(0, cValues); + + hr = IniSetValue(iniHandle, L"Value1", L"BlahTypo"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Value2", L"Blah2"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section1\\Value1", L"Section1Value1"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section1\\Value2", L"Section1Value2"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section2\\Value1", L"Section2Value1"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arr"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Value3", L"Blah3"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Value4", L"Blah4"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Value4", NULL); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniSetValue(iniHandle, L"Value1", L"Blah1"); + NativeAssert::Succeeded(hr, "Failed to set value in INI"); + + hr = IniGetValueList(iniHandle, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(8, cValues); + + AssertValue(iniHandle, L"Value1", L"Blah1"); + AssertValue(iniHandle, L"Value2", L"Blah2"); + AssertValue(iniHandle, L"Value3", L"Blah3"); + AssertNoValue(iniHandle, L"Value4"); + AssertValue(iniHandle, L"Section1\\Value1", L"Section1Value1"); + AssertValue(iniHandle, L"Section1\\Value2", L"Section1Value2"); + AssertValue(iniHandle, L"Section2\\Value1", L"Section2Value1"); + AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr"); + + hr = IniWriteFile(iniHandle, wzIniFilePath, FILE_ENCODING_UNSPECIFIED); + NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk"); + + ReleaseNullIni(iniHandle); + // Now re-parse the INI we just wrote and make sure it matches the values we expect + hr = IniInitialize(&iniHandle2); + NativeAssert::Succeeded(hr, "Failed to initialize INI object"); + + hr = SetFormat(iniHandle2); + NativeAssert::Succeeded(hr, "Failed to set parameters for INI file"); + + hr = IniParse(iniHandle2, wzIniFilePath, NULL); + NativeAssert::Succeeded(hr, "Failed to parse INI file"); + + hr = IniGetValueList(iniHandle2, &rgValues, &cValues); + NativeAssert::Succeeded(hr, "Failed to get list of values in INI"); + + NativeAssert::Equal(7, cValues); + + AssertValue(iniHandle2, L"Value1", L"Blah1"); + AssertValue(iniHandle2, L"Value2", L"Blah2"); + AssertValue(iniHandle2, L"Value3", L"Blah3"); + AssertNoValue(iniHandle2, L"Value4"); + AssertValue(iniHandle2, L"Section1\\Value1", L"Section1Value1"); + AssertValue(iniHandle2, L"Section1\\Value2", L"Section1Value2"); + AssertValue(iniHandle2, L"Section2\\Value1", L"Section2Value1"); + AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arr"); + } + finally + { + ReleaseIni(iniHandle); + ReleaseIni(iniHandle2); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/MemUtilTest.cpp b/src/test/DUtilUnitTest/MemUtilTest.cpp new file mode 100644 index 00000000..6dec9682 --- /dev/null +++ b/src/test/DUtilUnitTest/MemUtilTest.cpp @@ -0,0 +1,491 @@ +// 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 WixTest; + +namespace DutilTests +{ + struct ArrayValue + { + DWORD dwNum; + void *pvNull1; + LPWSTR sczString; + void *pvNull2; + }; + + public ref class MemUtil + { + public: + [Fact] + void MemUtilAppendTest() + { + HRESULT hr = S_OK; + DWORD dwSize; + ArrayValue *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 1"); + ++cValues; + SetItem(rgValues + 0, 0); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 2"); + ++cValues; + SetItem(rgValues + 1, 1); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 3"); + ++cValues; + SetItem(rgValues + 2, 2); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 4"); + ++cValues; + SetItem(rgValues + 3, 3); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 5"); + ++cValues; + SetItem(rgValues + 4, 4); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 6"); + ++cValues; + SetItem(rgValues + 5, 5); + + // OK, we used growth size 5, so let's try ensuring we have space for 6 (5 + first item) items + // and make sure it doesn't grow since we already have enough space + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to ensure array size matches what it should already be"); + dwSize = MemSize(rgValues); + if (dwSize != 6 * sizeof(ArrayValue)) + { + hr = E_FAIL; + ExitOnFailure(hr, "MemEnsureArraySize is growing an array that is already big enough!"); + } + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 7"); + ++cValues; + SetItem(rgValues + 6, 6); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 7"); + ++cValues; + SetItem(rgValues + 7, 7); + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 7"); + ++cValues; + SetItem(rgValues + 8, 8); + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + } + finally + { + ReleaseMem(rgValues); + } + + LExit: + return; + } + + [Fact] + void MemUtilInsertTest() + { + HRESULT hr = S_OK; + ArrayValue *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of empty array"); + ++cValues; + CheckNullItem(rgValues + 0); + SetItem(rgValues + 0, 5); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert at end of array"); + ++cValues; + CheckNullItem(rgValues + 1); + SetItem(rgValues + 1, 6); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 0); + SetItem(rgValues + 0, 4); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 0); + SetItem(rgValues + 0, 3); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 0); + SetItem(rgValues + 0, 1); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 1); + SetItem(rgValues + 1, 2); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 0); + SetItem(rgValues + 0, 0); + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to grow array size to 7"); + ++cValues; + CheckNullItem(rgValues + 7); + SetItem(rgValues + 7, 7); + + hr = MemInsertIntoArray(reinterpret_cast(&rgValues), 8, 1, cValues + 1, sizeof(ArrayValue), 5); + NativeAssert::Succeeded(hr, "Failed to insert into beginning of array"); + ++cValues; + CheckNullItem(rgValues + 8); + SetItem(rgValues + 8, 8); + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + } + finally + { + ReleaseMem(rgValues); + } + } + + [Fact] + void MemUtilRemovePreserveOrderTest() + { + HRESULT hr = S_OK; + ArrayValue *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), 10, sizeof(ArrayValue), 10); + NativeAssert::Succeeded(hr, "Failed to grow array size to 10"); + + cValues = 10; + for (DWORD i = 0; i < cValues; ++i) + { + SetItem(rgValues + i, i); + } + + // Remove last item + MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), TRUE); + --cValues; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Remove last two items + MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), TRUE); + cValues -= 2; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Remove first item + MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), TRUE); + --cValues; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i + 1); + } + + + // Remove first two items + MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE); + cValues -= 2; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i + 3); + } + + // Remove middle two items + MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), TRUE); + cValues -= 2; + + CheckItem(rgValues, 3); + CheckItem(rgValues + 1, 6); + + // Remove last 2 items to ensure we don't crash + MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE); + cValues -= 2; + } + finally + { + ReleaseMem(rgValues); + } + } + + [Fact] + void MemUtilRemoveFastTest() + { + HRESULT hr = S_OK; + ArrayValue *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), 10, sizeof(ArrayValue), 10); + NativeAssert::Succeeded(hr, "Failed to grow array size to 10"); + + cValues = 10; + for (DWORD i = 0; i < cValues; ++i) + { + SetItem(rgValues + i, i); + } + + // Remove last item + MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), FALSE); + --cValues; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Remove last two items + MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), FALSE); + cValues -= 2; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Remove first item + MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), FALSE); + --cValues; + + CheckItem(rgValues, 6); + CheckItem(rgValues + 1, 1); + CheckItem(rgValues + 2, 2); + CheckItem(rgValues + 3, 3); + CheckItem(rgValues + 4, 4); + CheckItem(rgValues + 5, 5); + + // Remove first two items + MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE); + cValues -= 2; + + CheckItem(rgValues, 4); + CheckItem(rgValues + 1, 5); + CheckItem(rgValues + 2, 2); + CheckItem(rgValues + 3, 3); + + + // Remove middle two items + MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), FALSE); + cValues -= 2; + + CheckItem(rgValues, 4); + CheckItem(rgValues + 1, 3); + + // Remove last 2 items to ensure we don't crash + MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE); + cValues -= 2; + } + finally + { + ReleaseMem(rgValues); + } + } + + [Fact] + void MemUtilSwapTest() + { + HRESULT hr = S_OK; + ArrayValue *rgValues = NULL; + DWORD cValues = 0; + + try + { + hr = MemEnsureArraySize(reinterpret_cast(&rgValues), 10, sizeof(ArrayValue), 10); + NativeAssert::Succeeded(hr, "Failed to grow array size to 10"); + + cValues = 10; + for (DWORD i = 0; i < cValues; ++i) + { + SetItem(rgValues + i, i); + } + + // Swap first two + MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue)); + --cValues; + + CheckItem(rgValues, 1); + CheckItem(rgValues + 1, 0); + for (DWORD i = 2; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Swap them back + MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue)); + --cValues; + + for (DWORD i = 0; i < cValues; ++i) + { + CheckItem(rgValues + i, i); + } + + // Swap first and last items (index 0 and 9) + MemArraySwapItems(rgValues, 0, 9, sizeof(ArrayValue)); + --cValues; + + CheckItem(rgValues, 9); + CheckItem(rgValues + 9, 0); + for (DWORD i = 1; i < cValues - 1; ++i) + { + CheckItem(rgValues + i, i); + } + + // Swap index 1 and 8 + MemArraySwapItems(rgValues, 1, 8, sizeof(ArrayValue)); + --cValues; + + CheckItem(rgValues, 9); + CheckItem(rgValues + 1, 8); + CheckItem(rgValues + 8, 1); + CheckItem(rgValues + 9, 0); + for (DWORD i = 2; i < cValues - 2; ++i) + { + CheckItem(rgValues + i, i); + } + + // Swap index 2 and 7 + MemArraySwapItems(rgValues, 2, 7, sizeof(ArrayValue)); + --cValues; + + CheckItem(rgValues, 9); + CheckItem(rgValues + 1, 8); + CheckItem(rgValues + 2, 7); + CheckItem(rgValues + 7, 2); + CheckItem(rgValues + 8, 1); + CheckItem(rgValues + 9, 0); + for (DWORD i = 3; i < cValues - 3; ++i) + { + CheckItem(rgValues + i, i); + } + + // Swap index 0 and 1 + MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue)); + --cValues; + + CheckItem(rgValues, 8); + CheckItem(rgValues + 1, 9); + CheckItem(rgValues + 2, 7); + CheckItem(rgValues + 7, 2); + CheckItem(rgValues + 8, 1); + CheckItem(rgValues + 9, 0); + for (DWORD i = 3; i < cValues - 3; ++i) + { + CheckItem(rgValues + i, i); + } + } + finally + { + ReleaseMem(rgValues); + } + } + + private: + void SetItem(ArrayValue *pValue, DWORD dwValue) + { + HRESULT hr = S_OK; + pValue->dwNum = dwValue; + + hr = StrAllocFormatted(&pValue->sczString, L"%u", dwValue); + NativeAssert::Succeeded(hr, "Failed to allocate string"); + } + + void CheckItem(ArrayValue *pValue, DWORD dwValue) + { + HRESULT hr = S_OK; + LPWSTR sczTemp = NULL; + + try + { + NativeAssert::Equal(dwValue, pValue->dwNum); + + hr = StrAllocFormatted(&sczTemp, L"%u", dwValue); + NativeAssert::Succeeded(hr, "Failed to allocate temp string"); + + NativeAssert::StringEqual(sczTemp, pValue->sczString, TRUE); + + if (pValue->pvNull1 || pValue->pvNull2) + { + hr = E_FAIL; + ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!"); + } + } + finally + { + ReleaseStr(sczTemp); + } + + LExit: + return; + } + + void CheckNullItem(ArrayValue *pValue) + { + HRESULT hr = S_OK; + + NativeAssert::Equal(0, pValue->dwNum); + + if (pValue->sczString) + { + hr = E_FAIL; + ExitOnFailure(hr, "Item found isn't NULL!"); + } + + if (pValue->pvNull1 || pValue->pvNull2) + { + hr = E_FAIL; + ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!"); + } + + LExit: + return; + } + }; +} diff --git a/src/test/DUtilUnitTest/MonUtilTest.cpp b/src/test/DUtilUnitTest/MonUtilTest.cpp new file mode 100644 index 00000000..a6ed32f1 --- /dev/null +++ b/src/test/DUtilUnitTest/MonUtilTest.cpp @@ -0,0 +1,487 @@ +// 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" +#undef RemoveDirectory + +using namespace System; +using namespace System::Collections::Generic; +using namespace System::Runtime::InteropServices; +using namespace Xunit; +using namespace WixTest; + +namespace DutilTests +{ + const int PREWAIT = 20; + const int POSTWAIT = 480; + const int FULLWAIT = 500; + const int SILENCEPERIOD = 100; + + struct RegKey + { + HRESULT hr; + HKEY hkRoot; + LPCWSTR wzSubKey; + REG_KEY_BITNESS kbKeyBitness; + BOOL fRecursive; + }; + struct Directory + { + HRESULT hr; + LPCWSTR wzPath; + BOOL fRecursive; + }; + struct Results + { + RegKey *rgRegKeys; + DWORD cRegKeys; + Directory *rgDirectories; + DWORD cDirectories; + }; + + public delegate void MonGeneralDelegate(HRESULT, LPVOID); + + public delegate void MonDriveStatusDelegate(WCHAR, BOOL, LPVOID); + + public delegate void MonDirectoryDelegate(HRESULT, LPCWSTR, BOOL, LPVOID, LPVOID); + + public delegate void MonRegKeyDelegate(HRESULT, HKEY, LPCWSTR, REG_KEY_BITNESS, BOOL, LPVOID, LPVOID); + + static void MonGeneral( + __in HRESULT /*hrResult*/, + __in_opt LPVOID /*pvContext*/ + ) + { + Assert::True(false); + } + + static void MonDriveStatus( + __in WCHAR /*chDrive*/, + __in BOOL /*fArriving*/, + __in_opt LPVOID /*pvContext*/ + ) + { + } + + static void MonDirectory( + __in HRESULT hrResult, + __in_z LPCWSTR wzPath, + __in_z BOOL fRecursive, + __in_opt LPVOID pvContext, + __in_opt LPVOID pvDirectoryContext + ) + { + Assert::Equal(S_OK, hrResult); + Assert::Equal(0, reinterpret_cast(pvDirectoryContext)); + + HRESULT hr = S_OK; + Results *pResults = reinterpret_cast(pvContext); + + hr = MemEnsureArraySize(reinterpret_cast(&pResults->rgDirectories), pResults->cDirectories + 1, sizeof(Directory), 5); + NativeAssert::ValidReturnCode(hr, S_OK); + ++pResults->cDirectories; + + pResults->rgDirectories[pResults->cDirectories - 1].hr = hrResult; + pResults->rgDirectories[pResults->cDirectories - 1].wzPath = wzPath; + pResults->rgDirectories[pResults->cDirectories - 1].fRecursive = fRecursive; + } + + static void MonRegKey( + __in HRESULT hrResult, + __in HKEY hkRoot, + __in_z LPCWSTR wzSubKey, + __in REG_KEY_BITNESS kbKeyBitness, + __in_z BOOL fRecursive, + __in_opt LPVOID pvContext, + __in_opt LPVOID pvRegKeyContext + ) + { + Assert::Equal(S_OK, hrResult); + Assert::Equal(0, reinterpret_cast(pvRegKeyContext)); + + HRESULT hr = S_OK; + Results *pResults = reinterpret_cast(pvContext); + + hr = MemEnsureArraySize(reinterpret_cast(&pResults->rgRegKeys), pResults->cRegKeys + 1, sizeof(RegKey), 5); + NativeAssert::ValidReturnCode(hr, S_OK); + ++pResults->cRegKeys; + + pResults->rgRegKeys[pResults->cRegKeys - 1].hr = hrResult; + pResults->rgRegKeys[pResults->cRegKeys - 1].hkRoot = hkRoot; + pResults->rgRegKeys[pResults->cRegKeys - 1].wzSubKey = wzSubKey; + pResults->rgRegKeys[pResults->cRegKeys - 1].kbKeyBitness = kbKeyBitness; + pResults->rgRegKeys[pResults->cRegKeys - 1].fRecursive = fRecursive; + } + + public ref class MonUtil + { + public: + void ClearResults(Results *pResults) + { + ReleaseNullMem(pResults->rgDirectories); + pResults->cDirectories = 0; + ReleaseNullMem(pResults->rgRegKeys); + pResults->cRegKeys = 0; + } + + void RemoveDirectory(LPCWSTR wzPath) + { + DWORD dwRetryCount = 0; + const DWORD c_dwMaxRetryCount = 100; + const DWORD c_dwRetryInterval = 50; + + HRESULT hr = DirEnsureDelete(wzPath, TRUE, TRUE); + + // Monitoring a directory opens a handle to that directory, which means delete requests for that directory will succeed + // (and deletion will be "pending" until our monitor handle is closed) + // but deletion of the directory containing that directory cannot complete until the handle is closed. This means DirEnsureDelete() + // can sometimes encounter HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) failures, which just means it needs to retry a bit later + // (after the waiter thread wakes up, it will release the handle) + while (HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) == hr && c_dwMaxRetryCount > dwRetryCount) + { + ::Sleep(c_dwRetryInterval); + ++dwRetryCount; + hr = DirEnsureDelete(wzPath, TRUE, TRUE); + } + + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND); + } + + void TestDirectory(MON_HANDLE handle, Results *pResults) + { + HRESULT hr = S_OK; + LPWSTR sczShallowPath = NULL; + LPWSTR sczParentPath = NULL; + LPWSTR sczDeepPath = NULL; + LPWSTR sczChildPath = NULL; + LPWSTR sczChildFilePath = NULL; + + try + { + hr = PathExpand(&sczShallowPath, L"%TEMP%\\MonUtilTest\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = PathExpand(&sczParentPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = PathExpand(&sczDeepPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = PathExpand(&sczChildPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = PathExpand(&sczChildFilePath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\file.txt", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + RemoveDirectory(sczShallowPath); + + hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = DirEnsureExists(sczParentPath, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + // Make sure creating the parent directory does nothing, even after silence period + ::Sleep(FULLWAIT); + Assert::Equal(0, pResults->cDirectories); + + // Now create the target path, no notification until after the silence period + hr = DirEnsureExists(sczDeepPath, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ::Sleep(PREWAIT); + Assert::Equal(0, pResults->cDirectories); + + // Now after the full silence period, it should have triggered + ::Sleep(POSTWAIT); + Assert::Equal(1, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[0].hr, S_OK); + + // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists. + RemoveDirectory(sczShallowPath); + + ::Sleep(FULLWAIT); + Assert::Equal(2, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[1].hr, S_OK); + + // Create the parent directory again, still should be nothing even after full silence period + hr = DirEnsureExists(sczParentPath, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ::Sleep(FULLWAIT); + Assert::Equal(2, pResults->cDirectories); + + hr = DirEnsureExists(sczChildPath, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ::Sleep(PREWAIT); + Assert::Equal(2, pResults->cDirectories); + + ::Sleep(POSTWAIT); + Assert::Equal(3, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK); + + // Write a file to a deep child subfolder, and make sure it's detected + hr = FileFromString(sczChildFilePath, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM); + NativeAssert::ValidReturnCode(hr, S_OK); + ::Sleep(PREWAIT); + Assert::Equal(3, pResults->cDirectories); + + ::Sleep(POSTWAIT); + Assert::Equal(4, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK); + + RemoveDirectory(sczParentPath); + + ::Sleep(FULLWAIT); + Assert::Equal(5, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK); + + // Now remove the directory from the list of things to monitor, and confirm changes are no longer tracked + hr = MonRemoveDirectory(handle, sczDeepPath, TRUE); + NativeAssert::ValidReturnCode(hr, S_OK); + ::Sleep(PREWAIT); + + hr = DirEnsureExists(sczDeepPath, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ::Sleep(FULLWAIT); + Assert::Equal(5, pResults->cDirectories); + NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK); + + // Finally, add it back so we can test multiple things to monitor at once + hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL); + NativeAssert::ValidReturnCode(hr, S_OK); + } + finally + { + ReleaseStr(sczShallowPath); + ReleaseStr(sczDeepPath); + ReleaseStr(sczParentPath); + } + } + + void TestRegKey(MON_HANDLE handle, Results *pResults) + { + HRESULT hr = S_OK; + LPCWSTR wzShallowRegKey = L"Software\\MonUtilTest\\"; + LPCWSTR wzParentRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\"; + LPCWSTR wzDeepRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\"; + LPCWSTR wzChildRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\"; + HKEY hk = NULL; + + try + { + hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND); + + hr = MonAddRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE, SILENCEPERIOD, NULL); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk); + ReleaseRegKey(hk); + // Make sure creating the parent key does nothing, even after silence period + ::Sleep(FULLWAIT); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + Assert::Equal(0, pResults->cRegKeys); + + // Now create the target path, no notification until after the silence period + hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ReleaseRegKey(hk); + ::Sleep(PREWAIT); + Assert::Equal(0, pResults->cRegKeys); + + // Now after the full silence period, it should have triggered + ::Sleep(POSTWAIT); + Assert::Equal(1, pResults->cRegKeys); + NativeAssert::ValidReturnCode(pResults->rgRegKeys[0].hr, S_OK); + + // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists. + hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND); + ::Sleep(PREWAIT); + Assert::Equal(1, pResults->cRegKeys); + + ::Sleep(FULLWAIT); + Assert::Equal(2, pResults->cRegKeys); + NativeAssert::ValidReturnCode(pResults->rgRegKeys[1].hr, S_OK); + + // Create the parent directory again, still should be nothing even after full silence period + hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ReleaseRegKey(hk); + ::Sleep(FULLWAIT); + Assert::Equal(2, pResults->cRegKeys); + + hr = RegCreate(HKEY_CURRENT_USER, wzChildRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ::Sleep(PREWAIT); + Assert::Equal(2, pResults->cRegKeys); + + ::Sleep(FULLWAIT); + Assert::Equal(3, pResults->cRegKeys); + NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK); + + // Write a registry value to some deep child subkey, and make sure it's detected + hr = RegWriteString(hk, L"valuename", L"testvalue"); + NativeAssert::ValidReturnCode(hr, S_OK); + ReleaseRegKey(hk); + ::Sleep(PREWAIT); + Assert::Equal(3, pResults->cRegKeys); + + ::Sleep(FULLWAIT); + Assert::Equal(4, pResults->cRegKeys); + NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK); + + hr = RegDelete(HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_32BIT, TRUE); + NativeAssert::ValidReturnCode(hr, S_OK); + + ::Sleep(FULLWAIT); + Assert::Equal(5, pResults->cRegKeys); + + // Now remove the regkey from the list of things to monitor, and confirm changes are no longer tracked + hr = MonRemoveRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + ReleaseRegKey(hk); + ::Sleep(FULLWAIT); + Assert::Equal(5, pResults->cRegKeys); + } + finally + { + ReleaseRegKey(hk); + } + } + + void TestMoreThan64(MON_HANDLE handle, Results *pResults) + { + HRESULT hr = S_OK; + LPWSTR sczBaseDir = NULL; + LPWSTR sczDir = NULL; + LPWSTR sczFile = NULL; + + try + { + hr = PathExpand(&sczBaseDir, L"%TEMP%\\ScalabilityTest\\", PATH_EXPAND_ENVIRONMENT); + NativeAssert::ValidReturnCode(hr, S_OK); + + for (DWORD i = 0; i < 200; ++i) + { + hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = DirEnsureExists(sczDir, NULL); + NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE); + + hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL); + NativeAssert::ValidReturnCode(hr, S_OK); + } + + hr = PathConcat(sczDir, L"file.txt", &sczFile); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = FileFromString(sczFile, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM); + NativeAssert::ValidReturnCode(hr, S_OK); + + ::Sleep(FULLWAIT); + Assert::Equal(1, pResults->cDirectories); + + for (DWORD i = 0; i < 199; ++i) + { + hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = MonRemoveDirectory(handle, sczDir, FALSE); + NativeAssert::ValidReturnCode(hr, S_OK); + } + ::Sleep(FULLWAIT); + + hr = FileFromString(sczFile, 0, L"contents2", FILE_ENCODING_UTF16_WITH_BOM); + NativeAssert::ValidReturnCode(hr, S_OK); + + ::Sleep(FULLWAIT); + Assert::Equal(2, pResults->cDirectories); + + for (DWORD i = 0; i < 199; ++i) + { + hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL); + NativeAssert::ValidReturnCode(hr, S_OK); + } + ::Sleep(FULLWAIT); + + hr = FileFromString(sczFile, 0, L"contents3", FILE_ENCODING_UTF16_WITH_BOM); + NativeAssert::ValidReturnCode(hr, S_OK); + + ::Sleep(FULLWAIT); + Assert::Equal(3, pResults->cDirectories); + } + finally + { + ReleaseStr(sczBaseDir); + ReleaseStr(sczDir); + ReleaseStr(sczFile); + } + } + + [Fact] + void MonUtilTest() + { + HRESULT hr = S_OK; + MON_HANDLE handle = NULL; + List^ gcHandles = gcnew List(); + Results *pResults = (Results *)MemAlloc(sizeof(Results), TRUE); + Assert::True(NULL != pResults); + + try + { + // These ensure the function pointers we send point to this thread's appdomain, which helps with assembly binding when running tests within msbuild + MonGeneralDelegate^ fpMonGeneral = gcnew MonGeneralDelegate(MonGeneral); + GCHandle gchMonGeneral = GCHandle::Alloc(fpMonGeneral); + gcHandles->Add(gchMonGeneral); + IntPtr ipMonGeneral = Marshal::GetFunctionPointerForDelegate(fpMonGeneral); + + MonDriveStatusDelegate^ fpMonDriveStatus = gcnew MonDriveStatusDelegate(MonDriveStatus); + GCHandle gchMonDriveStatus = GCHandle::Alloc(fpMonDriveStatus); + gcHandles->Add(gchMonDriveStatus); + IntPtr ipMonDriveStatus = Marshal::GetFunctionPointerForDelegate(fpMonDriveStatus); + + MonDirectoryDelegate^ fpMonDirectory = gcnew MonDirectoryDelegate(MonDirectory); + GCHandle gchMonDirectory = GCHandle::Alloc(fpMonDirectory); + gcHandles->Add(gchMonDirectory); + IntPtr ipMonDirectory = Marshal::GetFunctionPointerForDelegate(fpMonDirectory); + + MonRegKeyDelegate^ fpMonRegKey = gcnew MonRegKeyDelegate(MonRegKey); + GCHandle gchMonRegKey = GCHandle::Alloc(fpMonRegKey); + gcHandles->Add(gchMonRegKey); + IntPtr ipMonRegKey = Marshal::GetFunctionPointerForDelegate(fpMonRegKey); + + // "Silence period" is 100 ms + hr = MonCreate(&handle, static_cast(ipMonGeneral.ToPointer()), static_cast(ipMonDriveStatus.ToPointer()), static_cast(ipMonDirectory.ToPointer()), static_cast(ipMonRegKey.ToPointer()), pResults); + NativeAssert::ValidReturnCode(hr, S_OK); + + hr = RegInitialize(); + NativeAssert::ValidReturnCode(hr, S_OK); + + TestDirectory(handle, pResults); + ClearResults(pResults); + TestRegKey(handle, pResults); + ClearResults(pResults); + TestMoreThan64(handle, pResults); + ClearResults(pResults); + } + finally + { + ReleaseMon(handle); + + for each (GCHandle gcHandle in gcHandles) + { + gcHandle.Free(); + } + + ReleaseMem(pResults->rgDirectories); + ReleaseMem(pResults->rgRegKeys); + ReleaseMem(pResults); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/NativeAssert.h b/src/test/DUtilUnitTest/NativeAssert.h new file mode 100644 index 00000000..b10910c0 --- /dev/null +++ b/src/test/DUtilUnitTest/NativeAssert.h @@ -0,0 +1,83 @@ +#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. + + +namespace WixTest { + + using namespace System; + using namespace System::Collections::Generic; + using namespace System::Linq; + using namespace Xunit; + + public ref class NativeAssert : WixAssert + { + public: + static void NotNull(LPCWSTR wz) + { + if (!wz) + { + Assert::NotNull(nullptr); + } + } + + // For some reason, naming these NotStringEqual methods "NotEqual" breaks Intellisense in files that call any overload of the NotEqual method. + static void NotStringEqual(LPCWSTR expected, LPCWSTR actual) + { + NativeAssert::NotStringEqual(expected, actual, FALSE); + } + + static void NotStringEqual(LPCWSTR expected, LPCWSTR actual, BOOL ignoreCase) + { + IEqualityComparer^ comparer = ignoreCase ? StringComparer::InvariantCultureIgnoreCase : StringComparer::InvariantCulture; + Assert::NotEqual(NativeAssert::LPWSTRToString(expected), NativeAssert::LPWSTRToString(actual), comparer); + } + + // For some reason, naming these StringEqual methods "Equal" breaks Intellisense in files that call any overload of the Equal method. + static void StringEqual(LPCWSTR expected, LPCWSTR actual) + { + NativeAssert::StringEqual(expected, actual, FALSE); + } + + static void StringEqual(LPCWSTR expected, LPCWSTR actual, BOOL ignoreCase) + { + IEqualityComparer^ comparer = ignoreCase ? StringComparer::InvariantCultureIgnoreCase : StringComparer::InvariantCulture; + Assert::Equal(NativeAssert::LPWSTRToString(expected), NativeAssert::LPWSTRToString(actual), comparer); + } + + static void Succeeded(HRESULT hr, LPCSTR zFormat, LPCSTR zArg, ... array^ zArgs) + { + array^ formatArgs = gcnew array(zArgs->Length + 1); + formatArgs[0] = NativeAssert::LPSTRToString(zArg); + for (int i = 0; i < zArgs->Length; ++i) + { + formatArgs[i + 1] = NativeAssert::LPSTRToString(zArgs[i]); + } + WixAssert::Succeeded(hr, gcnew String(zFormat), formatArgs); + } + + static void Succeeded(HRESULT hr, LPCSTR zFormat, ... array^ wzArgs) + { + array^ formatArgs = gcnew array(wzArgs->Length); + for (int i = 0; i < wzArgs->Length; ++i) + { + formatArgs[i] = NativeAssert::LPWSTRToString(wzArgs[i]); + } + WixAssert::Succeeded(hr, gcnew String(zFormat), formatArgs); + } + + static void ValidReturnCode(HRESULT hr, ... array^ validReturnCodes) + { + Assert::Contains(hr, (IEnumerable^)validReturnCodes); + } + + private: + static String^ LPSTRToString(LPCSTR z) + { + return z ? gcnew String(z) : nullptr; + } + static String^ LPWSTRToString(LPCWSTR wz) + { + return wz ? gcnew String(wz) : nullptr; + } + }; +} diff --git a/src/test/DUtilUnitTest/PathUtilTest.cpp b/src/test/DUtilUnitTest/PathUtilTest.cpp new file mode 100644 index 00000000..13ec3be3 --- /dev/null +++ b/src/test/DUtilUnitTest/PathUtilTest.cpp @@ -0,0 +1,80 @@ +// 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 WixTest; + +namespace DutilTests +{ + public ref class PathUtil + { + public: + [Fact] + void PathGetHierarchyArrayTest() + { + HRESULT hr = S_OK; + LPWSTR *rgsczPaths = NULL; + UINT cPaths = 0; + + try + { + hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\a.txt", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path"); + Assert::Equal(5, cPaths); + NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]); + NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]); + NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]); + NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\a.txt", rgsczPaths[4]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular directory path"); + Assert::Equal(4, cPaths); + NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]); + NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]); + NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\file.txt", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC file path"); + Assert::Equal(3, cPaths); + NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]); + NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\file.txt", rgsczPaths[2]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(2, cPaths); + NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\ValueName", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(4, cPaths); + NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]); + NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]); + NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\ValueName", rgsczPaths[3]); + ReleaseNullStrArray(rgsczPaths, cPaths); + + hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\", &rgsczPaths, &cPaths); + NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path"); + Assert::Equal(3, cPaths); + NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]); + NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]); + NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]); + ReleaseNullStrArray(rgsczPaths, cPaths); + } + finally + { + ReleaseStrArray(rgsczPaths, cPaths); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/SceUtilTest.cpp b/src/test/DUtilUnitTest/SceUtilTest.cpp new file mode 100644 index 00000000..75b9222a --- /dev/null +++ b/src/test/DUtilUnitTest/SceUtilTest.cpp @@ -0,0 +1,488 @@ +// 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" + +#include +#include + +using namespace System; +using namespace Xunit; +using namespace WixTest; + +#define ASSIGN_INDEX_STRUCT(a, b, c) {a.wzName = c; a.rgColumns = b; a.cColumns = countof(b);}; + +namespace DutilTests +{ + enum TABLES + { + TABLE_A, + TABLE_COUNT + }; + + enum TABLE_A_COLUMNS + { + TABLE_A_KEY, + TABLE_A_BINARY, + TABLE_A_DWORD, + TABLE_A_QWORD, + TABLE_A_BOOL, + TABLE_A_STRING, + TABLE_A_DWORD_NULLABLE, + TABLE_A_INITIAL_COLUMNS, + + TABLE_A_EXTRA_STRING = TABLE_A_INITIAL_COLUMNS, + TABLE_A_FINAL_COLUMNS + }; + + struct TableARowValue + { + DWORD dwAutoGenKey; + + BYTE *pbBinary; + DWORD cBinary; + + DWORD dw; + DWORD64 qw; + BOOL f; + LPWSTR scz; + + BOOL fNullablePresent; + DWORD dwNullable; + + BOOL fSchemaV2; + LPWSTR sczExtra; + }; + + public ref class SceUtil + { + public: + void ReleaseSceSchema(SCE_DATABASE_SCHEMA *pdsSchema) + { + DWORD dwTable; + + for (dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable) + { + ReleaseNullMem(pdsSchema->rgTables[dwTable].rgColumns); + ReleaseNullMem(pdsSchema->rgTables[dwTable].rgIndexes); + } + + ReleaseMem(pdsSchema->rgTables); + + return; + } + + void SetupSchema(SCE_DATABASE_SCHEMA *pSchema, BOOL fIncludeExtended) + { + pSchema->cTables = TABLE_COUNT; + pSchema->rgTables = static_cast(MemAlloc(TABLE_COUNT * sizeof(SCE_TABLE_SCHEMA), TRUE)); + NativeAssert::True(pSchema->rgTables != NULL); + + pSchema->rgTables[TABLE_A].wzName = L"TableA"; + pSchema->rgTables[TABLE_A].cColumns = fIncludeExtended ? TABLE_A_FINAL_COLUMNS : TABLE_A_INITIAL_COLUMNS; + pSchema->rgTables[TABLE_A].cIndexes = 2; + + for (DWORD i = 0; i < pSchema->cTables; ++i) + { + pSchema->rgTables[i].rgColumns = static_cast(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cColumns, TRUE)); + NativeAssert::True(pSchema->rgTables[i].rgColumns != NULL); + + pSchema->rgTables[i].rgIndexes = static_cast(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cIndexes, TRUE)); + NativeAssert::True(pSchema->rgTables[i].rgIndexes != NULL); + } + + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].wzName = L"Key"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].dbtColumnType = DBTYPE_I4; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fPrimaryKey = TRUE; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fAutoIncrement = TRUE; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].wzName = L"Binary"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].dbtColumnType = DBTYPE_BYTES; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].wzName = L"Dword"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].dbtColumnType = DBTYPE_I4; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].wzName = L"Qword"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].dbtColumnType = DBTYPE_I8; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].wzName = L"Bool"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].dbtColumnType = DBTYPE_BOOL; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].wzName = L"String"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].dbtColumnType = DBTYPE_WSTR; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].wzName = L"Nullable"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].dbtColumnType = DBTYPE_I4; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].fNullable = TRUE; + + if (fIncludeExtended) + { + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].wzName = L"ExtraString"; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].dbtColumnType = DBTYPE_WSTR; + pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].fNullable = TRUE; + } + + static DWORD rgdwTableA_Index1[] = { TABLE_A_DWORD, TABLE_A_STRING, TABLE_A_QWORD }; + static DWORD rgdwTableA_Index2[] = { TABLE_A_DWORD, TABLE_A_STRING }; + + ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[0], rgdwTableA_Index1, L"Dword_String_Qword"); + ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[1], rgdwTableA_Index2, L"Dword_String"); + } + + void SetStructValues(TableARowValue *pValue, BYTE *pbBinary, DWORD cBinary, DWORD dw, DWORD64 qw, BOOL f, LPWSTR scz, DWORD *pdw, LPWSTR sczExtra) + { + pValue->pbBinary = pbBinary; + pValue->cBinary = cBinary; + pValue->dw = dw; + pValue->qw = qw; + pValue->f = f; + pValue->scz = scz; + + if (pdw) + { + pValue->fNullablePresent = TRUE; + pValue->dwNullable = *pdw; + } + else + { + pValue->fNullablePresent = FALSE; + } + + if (sczExtra) + { + pValue->fSchemaV2 = TRUE; + pValue->sczExtra = sczExtra; + } + else + { + pValue->fSchemaV2 = FALSE; + } + } + + void AssertStructValuesSame(TableARowValue *pValueExpected, TableARowValue *pValueOther) + { + NativeAssert::Equal(pValueExpected->cBinary, pValueOther->cBinary); + NativeAssert::True(0 == memcmp(pValueExpected->pbBinary, pValueOther->pbBinary, pValueOther->cBinary)); + + NativeAssert::Equal(pValueExpected->dw, pValueOther->dw); + NativeAssert::Equal(pValueExpected->qw, pValueOther->qw); + NativeAssert::Equal(pValueExpected->f, pValueOther->f); + NativeAssert::True(0 == wcscmp(pValueExpected->scz, pValueOther->scz)); + + NativeAssert::Equal(pValueExpected->fNullablePresent, pValueOther->fNullablePresent); + if (pValueExpected->fNullablePresent) + { + NativeAssert::Equal(pValueExpected->dwNullable, pValueOther->dwNullable); + } + + NativeAssert::Equal(pValueExpected->fSchemaV2, pValueOther->fSchemaV2); + if (pValueExpected->fSchemaV2) + { + NativeAssert::True(0 == wcscmp(pValueExpected->sczExtra, pValueOther->sczExtra)); + } + } + + void InsertRow(SCE_DATABASE *pDatabase, TableARowValue *pValue, BOOL fRollback) + { + HRESULT hr = S_OK; + SCE_ROW_HANDLE sceRow = NULL; + + hr = SceBeginTransaction(pDatabase); + NativeAssert::Succeeded(hr, "Failed to begin transaction"); + + hr = ScePrepareInsert(pDatabase, TABLE_A, &sceRow); + NativeAssert::Succeeded(hr, "Failed to prepare to insert row"); + + hr = SceSetColumnBinary(sceRow, TABLE_A_BINARY, pValue->pbBinary, pValue->cBinary); + NativeAssert::Succeeded(hr, "Failed to set binary value"); + + hr = SceSetColumnDword(sceRow, TABLE_A_DWORD, pValue->dw); + NativeAssert::Succeeded(hr, "Failed to set dword value"); + + hr = SceSetColumnQword(sceRow, TABLE_A_QWORD, pValue->qw); + NativeAssert::Succeeded(hr, "Failed to set qword value"); + + hr = SceSetColumnBool(sceRow, TABLE_A_BOOL, pValue->f); + NativeAssert::Succeeded(hr, "Failed to set bool value"); + + hr = SceSetColumnString(sceRow, TABLE_A_STRING, pValue->scz); + NativeAssert::Succeeded(hr, "Failed to set string value"); + + if (pValue->fNullablePresent) + { + hr = SceSetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, pValue->dwNullable); + NativeAssert::Succeeded(hr, "Failed to set dword value"); + } + else + { + hr = SceSetColumnNull(sceRow, TABLE_A_DWORD_NULLABLE); + NativeAssert::Succeeded(hr, "Failed to set null value"); + } + + if (pValue->fSchemaV2) + { + hr = SceSetColumnString(sceRow, TABLE_A_EXTRA_STRING, pValue->sczExtra); + NativeAssert::Succeeded(hr, "Failed to set extra string value"); + } + + hr = SceFinishUpdate(sceRow); + NativeAssert::Succeeded(hr, "Failed to finish insert"); + + if (fRollback) + { + hr = SceRollbackTransaction(pDatabase); + NativeAssert::Succeeded(hr, "Failed to rollback transaction"); + } + else + { + hr = SceCommitTransaction(pDatabase); + NativeAssert::Succeeded(hr, "Failed to commit transaction"); + + hr = SceGetColumnDword(sceRow, TABLE_A_KEY, &pValue->dwAutoGenKey); + NativeAssert::Succeeded(hr, "Failed to get autogen key after insert"); + + NativeAssert::True(pValue->dwAutoGenKey != 0); + } + + ReleaseSceRow(sceRow); + } + + void VerifyRow(TableARowValue *pExpectedValue, SCE_ROW_HANDLE sceRow) + { + HRESULT hr = S_OK; + TableARowValue value = {}; + + hr = SceGetColumnBinary(sceRow, TABLE_A_BINARY, &value.pbBinary, &value.cBinary); + NativeAssert::Succeeded(hr, "Failed to get binary value from result row"); + + hr = SceGetColumnDword(sceRow, TABLE_A_DWORD, &value.dw); + NativeAssert::Succeeded(hr, "Failed to get dword value from result row"); + + hr = SceGetColumnQword(sceRow, TABLE_A_QWORD, &value.qw); + NativeAssert::Succeeded(hr, "Failed to get qword value from result row"); + + hr = SceGetColumnBool(sceRow, TABLE_A_BOOL, &value.f); + NativeAssert::Succeeded(hr, "Failed to get bool value from result row"); + + hr = SceGetColumnString(sceRow, TABLE_A_STRING, &value.scz); + NativeAssert::Succeeded(hr, "Failed to get string value from result row"); + + hr = SceGetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, &value.dwNullable); + if (hr == E_NOTFOUND) + { + value.fNullablePresent = FALSE; + hr = S_OK; + } + else + { + NativeAssert::Succeeded(hr, "Failed to get string value from result row"); + value.fNullablePresent = TRUE; + } + + if (pExpectedValue->fSchemaV2) + { + value.fSchemaV2 = TRUE; + hr = SceGetColumnString(sceRow, TABLE_A_EXTRA_STRING, &value.sczExtra); + NativeAssert::Succeeded(hr, "Failed to get extra string value from result row"); + } + + AssertStructValuesSame(pExpectedValue, &value); + + ReleaseNullMem(value.pbBinary); + ReleaseNullStr(value.scz); + } + + void VerifyQuery(TableARowValue **rgExpectedValues, DWORD cExpectedValues, SCE_QUERY_RESULTS_HANDLE queryResults) + { + HRESULT hr = S_OK; + SCE_ROW_HANDLE sceRow = NULL; + + for (DWORD i = 0; i < cExpectedValues; ++i) + { + hr = SceGetNextResultRow(queryResults, &sceRow); + NativeAssert::Succeeded(hr, "Failed to get next result row"); + + VerifyRow(rgExpectedValues[i], sceRow); + ReleaseNullSceRow(sceRow); + } + + // No more results + NativeAssert::True(NULL == queryResults || FAILED(SceGetNextResultRow(queryResults, &sceRow))); + } + + void TestIndex(SCE_DATABASE *pDatabase) + { + HRESULT hr = S_OK; + BYTE binary1[50] = { 0x80, 0x70 }; + BYTE binary2[40] = { 0x90, 0xAB }; + BYTE binary3[40] = { 0x85, 0x88 }; + DWORD dwValue1 = 0x55555555, dwValue2 = 0x88888888; + TableARowValue value1 = {}, value2 = {}, value3 = {}, value4 = {}, value5 = {}; + SCE_QUERY_HANDLE query = NULL; + SCE_QUERY_RESULTS_HANDLE results = NULL; + + SetStructValues(&value1, static_cast(binary1), sizeof(binary1), 3, 1, TRUE, L"zzz", &dwValue1, NULL); + SetStructValues(&value2, static_cast(binary2), sizeof(binary2), 3, 2, TRUE, L"yyy", &dwValue2, NULL); + SetStructValues(&value3, static_cast(binary3), sizeof(binary3), 3, 3, TRUE, L"xxx", NULL, NULL); + SetStructValues(&value4, static_cast(binary2), sizeof(binary2), 4, 4, TRUE, L"xyz", &dwValue2, NULL); + SetStructValues(&value5, static_cast(binary3), sizeof(binary3), 3, 1, TRUE, L"yyy", &dwValue2, NULL); + + // Rollback an insert to confirm the insert doesn't happen and database can still be interacted with normally afterwards + InsertRow(pDatabase, &value1, TRUE); + + InsertRow(pDatabase, &value1, FALSE); + InsertRow(pDatabase, &value2, FALSE); + InsertRow(pDatabase, &value3, FALSE); + InsertRow(pDatabase, &value4, FALSE); + InsertRow(pDatabase, &value5, FALSE); + + NativeAssert::True(value1.dwAutoGenKey != value2.dwAutoGenKey); + + // Test setting 1 column + hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query); + NativeAssert::Succeeded(hr, "Failed to begin query"); + + hr = SceSetQueryColumnDword(query, 3); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceRunQueryRange(&query, &results); + NativeAssert::Succeeded(hr, "Failed to run query"); + NativeAssert::True(query == NULL); + + TableARowValue *sortedAfterQuery1[] = { &value3, &value5, &value2, &value1 }; + VerifyQuery(sortedAfterQuery1, _countof(sortedAfterQuery1), results); + ReleaseNullSceQueryResults(results); + + // Test setting 2 columns, third column is unspecified so results are sorted by it + hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query); + NativeAssert::Succeeded(hr, "Failed to begin query"); + + hr = SceSetQueryColumnDword(query, 3); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceSetQueryColumnString(query, L"yyy"); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceRunQueryRange(&query, &results); + NativeAssert::Succeeded(hr, "Failed to run query"); + NativeAssert::True(query == NULL); + + TableARowValue *sortedAfterQuery2[] = { &value5, &value2 }; + VerifyQuery(sortedAfterQuery2, _countof(sortedAfterQuery2), results); + ReleaseNullSceQueryResults(results); + + // Test setting 2 columns, third column of index is unspecified so results are sorted by it + hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query); + NativeAssert::Succeeded(hr, "Failed to begin query"); + + hr = SceSetQueryColumnDword(query, 3); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceSetQueryColumnString(query, L"yyy"); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceRunQueryRange(&query, &results); + NativeAssert::Succeeded(hr, "Failed to run query"); + NativeAssert::True(query == NULL); + + TableARowValue *sortedAfterQuery3[] = { &value5, &value2 }; + VerifyQuery(sortedAfterQuery3, _countof(sortedAfterQuery3), results); + ReleaseNullSceQueryResults(results); + + // Test setting 2 columns in a different (2 column) index, so there is no 3rd column in index to sort by + hr = SceBeginQuery(pDatabase, TABLE_A, 1, &query); + NativeAssert::Succeeded(hr, "Failed to begin query"); + + hr = SceSetQueryColumnDword(query, 3); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceSetQueryColumnString(query, L"yyy"); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceRunQueryRange(&query, &results); + NativeAssert::Succeeded(hr, "Failed to run query"); + NativeAssert::True(query == NULL); + + TableARowValue *sortedAfterQuery4[] = { &value2, &value5 }; + VerifyQuery(sortedAfterQuery4, _countof(sortedAfterQuery4), results); + ReleaseNullSceQueryResults(results); + } + + void TestReadWriteSchemaV2(SCE_DATABASE *pDatabase) + { + HRESULT hr = S_OK; + BYTE binary1[40] = { 0x55, 0x44 }; + DWORD dwValue1 = 58; + TableARowValue value1 = {}; + SCE_QUERY_HANDLE query = NULL; + SCE_ROW_HANDLE row = NULL; + + SetStructValues(&value1, static_cast(binary1), sizeof(binary1), 5, 1, TRUE, L"zzz", &dwValue1, L"newextrastring"); + + InsertRow(pDatabase, &value1, FALSE); + + // Test setting 1 column + hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query); + NativeAssert::Succeeded(hr, "Failed to begin query"); + + hr = SceSetQueryColumnDword(query, 5); + NativeAssert::Succeeded(hr, "Failed to set query column dword"); + + hr = SceRunQueryExact(&query, &row); + NativeAssert::Succeeded(hr, "Failed to run query exact"); + + VerifyRow(&value1, row); + } + + [Fact] + void SceUtilTest() + { + HRESULT hr = S_OK; + BOOL fComInitialized = FALSE; + LPWSTR sczDbPath = NULL; + SCE_DATABASE *pDatabase = NULL; + SCE_DATABASE_SCHEMA schema1 = {}; + SCE_DATABASE_SCHEMA schema2 = {}; + + try + { + hr = ::CoInitialize(0); + NativeAssert::Succeeded(hr, "Failed to initialize COM"); + fComInitialized = TRUE; + + SetupSchema(&schema1, FALSE); + SetupSchema(&schema2, TRUE); + + hr = PathExpand(&sczDbPath, L"%TEMP%\\SceUtilTest\\UnitTest.sdf", PATH_EXPAND_ENVIRONMENT); + NativeAssert::Succeeded(hr, "Failed to get path to test database"); + + FileEnsureDelete(sczDbPath); + + hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema1, &pDatabase); + NativeAssert::Succeeded(hr, "Failed to ensure database schema"); + + TestIndex(pDatabase); + + hr = SceCloseDatabase(pDatabase); + pDatabase = NULL; + NativeAssert::Succeeded(hr, "Failed to close database"); + + // Add column to schema + hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema2, &pDatabase); + NativeAssert::Succeeded(hr, "Failed to ensure database schema"); + + TestReadWriteSchemaV2(pDatabase); + } + finally + { + ReleaseSceSchema(&schema1); + ReleaseSceSchema(&schema2); + + if (NULL != pDatabase) + { + hr = SceCloseDatabase(pDatabase); + NativeAssert::Succeeded(hr, "Failed to close database"); + } + ReleaseStr(sczDbPath); + + if (fComInitialized) + { + ::CoUninitialize(); + } + } + } + }; +} diff --git a/src/test/DUtilUnitTest/StrUtilTest.cpp b/src/test/DUtilUnitTest/StrUtilTest.cpp new file mode 100644 index 00000000..406f2f23 --- /dev/null +++ b/src/test/DUtilUnitTest/StrUtilTest.cpp @@ -0,0 +1,184 @@ +// 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 WixTest; + +namespace DutilTests +{ + public ref class StrUtil + { + public: + [Fact] + void StrUtilFormattedTest() + { + HRESULT hr = S_OK; + LPWSTR sczText = NULL; + + try + { + hr = StrAllocFormatted(&sczText, L"%hs - %ls - %u", "ansi string", L"unicode string", 1234); + NativeAssert::Succeeded(hr, "Failed to format string."); + NativeAssert::StringEqual(L"ansi string - unicode string - 1234", sczText); + + ReleaseNullStr(sczText); + + hr = StrAllocString(&sczText, L"repeat", 0); + NativeAssert::Succeeded(hr, "Failed to allocate string."); + + hr = StrAllocFormatted(&sczText, L"%ls and %ls", sczText, sczText); + NativeAssert::Succeeded(hr, "Failed to format string unto itself."); + NativeAssert::StringEqual(L"repeat and repeat", sczText); + } + finally + { + ReleaseStr(sczText); + } + } + + [Fact] + void StrUtilTrimTest() + { + TestTrim(L"", L""); + TestTrim(L"Blah", L"Blah"); + TestTrim(L"\t\t\tBlah", L"Blah"); + TestTrim(L"\t Blah ", L"Blah"); + TestTrim(L"Blah ", L"Blah"); + TestTrim(L"\t Spaces \t Between \t", L"Spaces \t Between"); + TestTrim(L" \t\t\t ", L""); + + TestTrimAnsi("", ""); + TestTrimAnsi("Blah", "Blah"); + TestTrimAnsi("\t\t\tBlah", "Blah"); + TestTrimAnsi(" Blah ", "Blah"); + TestTrimAnsi("Blah ", "Blah"); + TestTrimAnsi("\t Spaces \t Between \t", "Spaces \t Between"); + TestTrimAnsi(" \t\t\t ", ""); + } + + [Fact] + void StrUtilConvertTest() + { + char a[] = { 'a', 'b', 'C', 'd', '\0', '\0' }; + + TestStrAllocStringAnsi(a, 5, L"abCd"); + TestStrAllocStringAnsi(a, 4, L"abCd"); + TestStrAllocStringAnsi(a, 3, L"abC"); + TestStrAllocStringAnsi(a, 2, L"ab"); + TestStrAllocStringAnsi(a, 1, L"a"); + TestStrAllocStringAnsi(a, 0, L"abCd"); + + wchar_t b[] = { L'a', L'b', L'C', L'd', L'\0', L'\0' }; + + TestStrAnsiAllocString(b, 5, "abCd"); + TestStrAnsiAllocString(b, 4, "abCd"); + TestStrAnsiAllocString(b, 3, "abC"); + TestStrAnsiAllocString(b, 2, "ab"); + TestStrAnsiAllocString(b, 1, "a"); + TestStrAnsiAllocString(b, 0, "abCd"); + } + + private: + void TestTrim(LPCWSTR wzInput, LPCWSTR wzExpectedResult) + { + HRESULT hr = S_OK; + LPWSTR sczOutput = NULL; + + try + { + hr = StrTrimWhitespace(&sczOutput, wzInput); + NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: {0}", wzInput); + + if (0 != wcscmp(wzExpectedResult, sczOutput)) + { + hr = E_FAIL; + ExitOnFailure(hr, "Trimmed string \"%ls\", expected result \"%ls\", actual result \"%ls\"", wzInput, wzExpectedResult, sczOutput); + } + } + finally + { + ReleaseStr(sczOutput); + } + + LExit: + return; + } + + void TestTrimAnsi(LPCSTR szInput, LPCSTR szExpectedResult) + { + HRESULT hr = S_OK; + LPSTR sczOutput = NULL; + + try + { + hr = StrAnsiTrimWhitespace(&sczOutput, szInput); + NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: \"{0}\"", szInput); + + if (0 != strcmp(szExpectedResult, sczOutput)) + { + hr = E_FAIL; + ExitOnFailure(hr, "Trimmed string \"%hs\", expected result \"%hs\", actual result \"%hs\"", szInput, szExpectedResult, sczOutput); + } + } + finally + { + ReleaseStr(sczOutput); + } + + LExit: + return; + } + + void TestStrAllocStringAnsi(LPCSTR szSource, DWORD cchSource, LPCWSTR wzExpectedResult) + { + HRESULT hr = S_OK; + LPWSTR sczOutput = NULL; + + try + { + hr = StrAllocStringAnsi(&sczOutput, szSource, cchSource, CP_UTF8); + NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", szSource); + + if (0 != wcscmp(sczOutput, wzExpectedResult)) + { + hr = E_FAIL; + ExitOnFailure(hr, "String doesn't match, expected result \"%ls\", actual result \"%ls\"", wzExpectedResult, sczOutput); + } + } + finally + { + ReleaseStr(sczOutput); + } + + LExit: + return; + } + + void TestStrAnsiAllocString(LPWSTR wzSource, DWORD cchSource, LPCSTR szExpectedResult) + { + HRESULT hr = S_OK; + LPSTR sczOutput = NULL; + + try + { + hr = StrAnsiAllocString(&sczOutput, wzSource, cchSource, CP_UTF8); + NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", wzSource); + + if (0 != strcmp(sczOutput, szExpectedResult)) + { + hr = E_FAIL; + ExitOnFailure(hr, "String doesn't match, expected result \"%hs\", actual result \"%hs\"", szExpectedResult, sczOutput); + } + } + finally + { + ReleaseStr(sczOutput); + } + + LExit: + return; + } + }; +} diff --git a/src/test/DUtilUnitTest/UnitTest.rc b/src/test/DUtilUnitTest/UnitTest.rc new file mode 100644 index 00000000..bf68360a --- /dev/null +++ b/src/test/DUtilUnitTest/UnitTest.rc @@ -0,0 +1,7 @@ +// 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. + +#define VER_APP +#define VER_ORIGINAL_FILENAME "UnitTest.dll" +#define VER_INTERNAL_NAME "setup" +#define VER_FILE_DESCRIPTION "WiX Toolset Bootstrapper unit tests" +#include "wix.rc" diff --git a/src/test/DUtilUnitTest/UriUtilTest.cpp b/src/test/DUtilUnitTest/UriUtilTest.cpp new file mode 100644 index 00000000..220b3ff5 --- /dev/null +++ b/src/test/DUtilUnitTest/UriUtilTest.cpp @@ -0,0 +1,96 @@ +// 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 System::Text; +using namespace System::Collections::Generic; +using namespace Xunit; + +namespace CfgTests +{ + public ref class UriUtil + { + public: + [Fact] + void UriProtocolTest() + { + HRESULT hr = S_OK; + + LPCWSTR uri = L"https://localhost/"; + URI_PROTOCOL uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol); + + uri = L"HTTPS://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol); + + uri = L"HtTpS://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol); + + uri = L"HTTP://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol); + + uri = L"http://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol); + + uri = L"HtTp://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol); + + uri = L"file://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol); + + uri = L"FILE://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol); + + uri = L"FiLe://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol); + + uri = L"FTP://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol); + + uri = L"ftp://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol); + + uri = L"FtP://localhost/"; + uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN; + hr = UriProtocol(uri, &uriProtocol); + ExitOnFailure(hr, "Failed to determine UriProtocol"); + Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol); + + LExit: + ; + } + }; +} diff --git a/src/test/DUtilUnitTest/VarHelpers.cpp b/src/test/DUtilUnitTest/VarHelpers.cpp new file mode 100644 index 00000000..aba69438 --- /dev/null +++ b/src/test/DUtilUnitTest/VarHelpers.cpp @@ -0,0 +1,147 @@ +// 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" + + +namespace DutilTests +{ + using namespace System; + using namespace WixTest; + + void VarSetStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzValue) + { + HRESULT hr = S_OK; + + hr = VarSetString(pVariables, wzVariable, wzValue); + NativeAssert::Succeeded(hr, "Failed to set {0} to: {1}", wzVariable, wzValue); + } + + void VarSetNumericHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LONGLONG llValue) + { + HRESULT hr = S_OK; + + hr = VarSetNumeric(pVariables, wzVariable, llValue); + NativeAssert::Succeeded(hr, gcnew String("Failed to set {0} to: {1}"), gcnew String(wzVariable), llValue); + } + + void VarSetVersionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, DWORD64 qwValue) + { + HRESULT hr = S_OK; + + hr = VarSetVersion(pVariables, wzVariable, qwValue); + NativeAssert::Succeeded(hr, gcnew String("Failed to set {0} to: 0x{1:X8}"), gcnew String(wzVariable), qwValue); + } + + void VarGetStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzExpectedValue) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + try + { + hr = VarGetString(pVariables, wzVariable, &scz); + NativeAssert::Succeeded(hr, "Failed to get: {0}", wzVariable); + NativeAssert::StringEqual(wzExpectedValue, scz); + } + finally + { + ReleaseStr(scz); + } + } + + void VarGetNumericHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LONGLONG llExpectedValue) + { + HRESULT hr = S_OK; + LONGLONG llValue = 0; + + hr = VarGetNumeric(pVariables, wzVariable, &llValue); + NativeAssert::Succeeded(hr, "Failed to get: {0}", wzVariable); + NativeAssert::Equal(llExpectedValue, llValue); + } + + void VarGetVersionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, DWORD64 qwExpectedValue) + { + HRESULT hr = S_OK; + DWORD64 qwValue = 0; + + hr = VarGetVersion(pVariables, wzVariable, &qwValue); + NativeAssert::Succeeded(hr, "Failed to get: {0}", wzVariable); + NativeAssert::Equal(qwExpectedValue, qwValue); + } + + void VarGetFormattedHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzExpectedValue) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + try + { + hr = VarGetFormatted(pVariables, wzVariable, &scz); + NativeAssert::Succeeded(hr, "Failed to get formatted: {0}", wzVariable); + NativeAssert::StringEqual(wzExpectedValue, scz); + } + finally + { + ReleaseStr(scz); + } + } + + void VarFormatStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzIn, LPCWSTR wzExpectedValue) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + try + { + hr = VarFormatString(pVariables, wzIn, &scz, NULL); + NativeAssert::Succeeded(hr, "Failed to format string: '{0}'", wzIn); + NativeAssert::StringEqual(wzExpectedValue, scz); + } + finally + { + ReleaseStr(scz); + } + } + + void VarEscapeStringHelper(LPCWSTR wzIn, LPCWSTR wzExpectedValue) + { + HRESULT hr = S_OK; + LPWSTR scz = NULL; + + try + { + hr = VarEscapeString(wzIn, &scz); + NativeAssert::Succeeded(hr, "Failed to escape string: '{0}'", wzIn); + NativeAssert::StringEqual(wzExpectedValue, scz); + } + finally + { + ReleaseStr(scz); + } + } + + bool EvaluateConditionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzCondition) + { + HRESULT hr = S_OK; + BOOL f = FALSE; + + hr = CondEvaluate(pVariables, wzCondition, &f); + NativeAssert::Succeeded(hr, "Failed to evaluate condition: '{0}'", wzCondition); + + return f ? true : false; + } + + bool EvaluateFailureConditionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzCondition) + { + HRESULT hr = S_OK; + BOOL f = FALSE; + + hr = CondEvaluate(pVariables, wzCondition, &f); + if (E_INVALIDDATA != hr) + { + NativeAssert::Succeeded(hr, "Failed to evaluate condition: '{0}'", wzCondition); + } + + return E_INVALIDDATA == hr ? true : false; + } +} diff --git a/src/test/DUtilUnitTest/VarHelpers.h b/src/test/DUtilUnitTest/VarHelpers.h new file mode 100644 index 00000000..9b781ce6 --- /dev/null +++ b/src/test/DUtilUnitTest/VarHelpers.h @@ -0,0 +1,20 @@ +#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. + + +namespace DutilTests +{ + +void VarSetStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzValue); +void VarSetNumericHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LONGLONG llValue); +void VarSetVersionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, DWORD64 qwValue); +void VarGetStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzExpectedValue); +void VarGetNumericHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LONGLONG llExpectedValue); +void VarGetVersionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, DWORD64 qwExpectedValue); +void VarGetFormattedHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzVariable, LPCWSTR wzExpectedValue); +void VarFormatStringHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzIn, LPCWSTR wzExpectedValue); +void VarEscapeStringHelper(LPCWSTR wzIn, LPCWSTR wzExpectedValue); +bool EvaluateConditionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzCondition); +bool EvaluateFailureConditionHelper(VARIABLES_HANDLE pVariables, LPCWSTR wzCondition); + +} diff --git a/src/test/DUtilUnitTest/VarUtilTest.cpp b/src/test/DUtilUnitTest/VarUtilTest.cpp new file mode 100644 index 00000000..206310f5 --- /dev/null +++ b/src/test/DUtilUnitTest/VarUtilTest.cpp @@ -0,0 +1,532 @@ +// 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" +#undef GetTempPath +#undef GetEnvironmentVariable + +using namespace System; +using namespace Xunit; +using namespace WixTest; + +namespace DutilTests +{ + typedef struct _VarUtilContext + { + DWORD dw; + LPWSTR scz; + } VarUtilContext; + + void FreeValueContext(LPVOID pvContext) + { + if (pvContext) + { + MemFree(pvContext); + } + } + + public ref class VarUtil + { + public: + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilBasicTest() + { + HRESULT hr = S_OK; + VARIABLES_HANDLE pVariables = NULL; + + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + // set variables + VarSetStringHelper(pVariables, L"PROP1", L"VAL1"); + VarSetNumericHelper(pVariables, L"PROP2", 2); + VarSetStringHelper(pVariables, L"PROP5", L"VAL5"); + VarSetStringHelper(pVariables, L"PROP3", L"VAL3"); + VarSetStringHelper(pVariables, L"PROP4", L"VAL4"); + VarSetStringHelper(pVariables, L"PROP6", L"VAL6"); + VarSetStringHelper(pVariables, L"PROP7", L"7"); + VarSetVersionHelper(pVariables, L"PROP8", MAKEQWORDVERSION(1, 1, 0, 0)); + + // set overwritten variables + VarSetStringHelper(pVariables, L"OVERWRITTEN_STRING", L"ORIGINAL"); + VarSetNumericHelper(pVariables, L"OVERWRITTEN_STRING", 42); + + VarSetNumericHelper(pVariables, L"OVERWRITTEN_NUMBER", 5); + VarSetStringHelper(pVariables, L"OVERWRITTEN_NUMBER", L"NEW"); + + // get and verify variable values + VarGetStringHelper(pVariables, L"PROP1", L"VAL1"); + VarGetNumericHelper(pVariables, L"PROP2", 2); + VarGetStringHelper(pVariables, L"PROP2", L"2"); + VarGetStringHelper(pVariables, L"PROP3", L"VAL3"); + VarGetStringHelper(pVariables, L"PROP4", L"VAL4"); + VarGetStringHelper(pVariables, L"PROP5", L"VAL5"); + VarGetStringHelper(pVariables, L"PROP6", L"VAL6"); + VarGetNumericHelper(pVariables, L"PROP7", 7); + VarGetVersionHelper(pVariables, L"PROP8", MAKEQWORDVERSION(1, 1, 0, 0)); + VarGetStringHelper(pVariables, L"PROP8", L"1.1.0.0"); + + VarGetNumericHelper(pVariables, L"OVERWRITTEN_STRING", 42); + VarGetStringHelper(pVariables, L"OVERWRITTEN_NUMBER", L"NEW"); + } + finally + { + ReleaseVariables(pVariables); + } + } + + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilFormatTest() + { + HRESULT hr = S_OK; + VARIABLES_HANDLE pVariables = NULL; + LPWSTR scz = NULL; + DWORD cch = 0; + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + // set variables + VarSetStringHelper(pVariables, L"PROP1", L"VAL1"); + VarSetStringHelper(pVariables, L"PROP2", L"VAL2"); + VarSetNumericHelper(pVariables, L"PROP3", 3); + + // test string formatting + VarFormatStringHelper(pVariables, L"NOPROP", L"NOPROP"); + VarFormatStringHelper(pVariables, L"[PROP1]", L"VAL1"); + VarFormatStringHelper(pVariables, L" [PROP1] ", L" VAL1 "); + VarFormatStringHelper(pVariables, L"PRE [PROP1]", L"PRE VAL1"); + VarFormatStringHelper(pVariables, L"[PROP1] POST", L"VAL1 POST"); + VarFormatStringHelper(pVariables, L"PRE [PROP1] POST", L"PRE VAL1 POST"); + VarFormatStringHelper(pVariables, L"[PROP1] MID [PROP2]", L"VAL1 MID VAL2"); + VarFormatStringHelper(pVariables, L"[NONE]", L""); + VarFormatStringHelper(pVariables, L"[prop1]", L""); + VarFormatStringHelper(pVariables, L"[\\[]", L"["); + VarFormatStringHelper(pVariables, L"[\\]]", L"]"); + VarFormatStringHelper(pVariables, L"[]", L"[]"); + VarFormatStringHelper(pVariables, L"[NONE", L"[NONE"); + VarGetFormattedHelper(pVariables, L"PROP2", L"VAL2"); + VarGetFormattedHelper(pVariables, L"PROP3", L"3"); + + hr = VarFormatString(pVariables, L"PRE [PROP1] POST", &scz, &cch); + NativeAssert::Succeeded(hr, "Failed to format string."); + + Assert::Equal(lstrlenW(scz), cch); + + hr = VarFormatString(pVariables, L"PRE [PROP1] POST", NULL, &cch); + NativeAssert::Succeeded(hr, "Failed to format string."); + + Assert::Equal(lstrlenW(scz), cch); + } + finally + { + ReleaseVariables(pVariables); + ReleaseStr(scz); + } + } + + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilEscapeTest() + { + // test string escaping + VarEscapeStringHelper(L"[", L"[\\[]"); + VarEscapeStringHelper(L"]", L"[\\]]"); + VarEscapeStringHelper(L" [TEXT] ", L" [\\[]TEXT[\\]] "); + } + + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilConditionTest() + { + HRESULT hr = S_OK; + VARIABLES_HANDLE pVariables = NULL; + + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + // set variables + VarSetStringHelper(pVariables, L"PROP1", L"VAL1"); + VarSetStringHelper(pVariables, L"PROP2", L"VAL2"); + VarSetStringHelper(pVariables, L"PROP3", L"VAL3"); + VarSetStringHelper(pVariables, L"PROP4", L"BEGIN MID END"); + VarSetNumericHelper(pVariables, L"PROP5", 5); + VarSetNumericHelper(pVariables, L"PROP6", 6); + VarSetStringHelper(pVariables, L"PROP7", L""); + VarSetNumericHelper(pVariables, L"PROP8", 0); + VarSetStringHelper(pVariables, L"_PROP9", L"VAL9"); + VarSetNumericHelper(pVariables, L"PROP10", -10); + VarSetNumericHelper(pVariables, L"PROP11", 9223372036854775807ll); + VarSetNumericHelper(pVariables, L"PROP12", -9223372036854775808ll); + VarSetNumericHelper(pVariables, L"PROP13", 0x00010000); + VarSetNumericHelper(pVariables, L"PROP14", 0x00000001); + VarSetNumericHelper(pVariables, L"PROP15", 0x00010001); + VarSetVersionHelper(pVariables, L"PROP16", MAKEQWORDVERSION(0, 0, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP17", MAKEQWORDVERSION(1, 0, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP18", MAKEQWORDVERSION(1, 1, 0, 0)); + VarSetVersionHelper(pVariables, L"PROP19", MAKEQWORDVERSION(1, 1, 1, 0)); + VarSetVersionHelper(pVariables, L"PROP20", MAKEQWORDVERSION(1, 1, 1, 1)); + VarSetNumericHelper(pVariables, L"vPROP21", 1); + VarSetVersionHelper(pVariables, L"PROP22", MAKEQWORDVERSION(65535, 65535, 65535, 65535)); + VarSetStringHelper(pVariables, L"PROP23", L"1.1.1"); + + // test conditions + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP7")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP8")); + Assert::True(EvaluateConditionHelper(pVariables, L"_PROP9")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP16")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"NONE = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 <> \"VAL1\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"NONE <> \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 ~= \"val1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"val1\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 ~<> \"val1\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 <> \"val1\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 = 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 = 0")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 <> 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <> 0")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP10 = -10")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP10 <> -10")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 = v1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 = v0")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 <> v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 <> v0")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP16 = v0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 = v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 = v1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 = v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP20 = v1.1.1.1")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP20 = v1.1.1.1.0")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP20 = v1.1.1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"vPROP21 = 1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP23 = v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"v1.1.1 = PROP23")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 <> v1.1.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"v1.1.1 <> PROP1")); + + Assert::False(EvaluateConditionHelper(pVariables, L"PROP11 = 9223372036854775806")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP11 = 9223372036854775807")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP11 = 9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP11 = 92233720368547758070000")); + + Assert::False(EvaluateConditionHelper(pVariables, L"PROP12 = -9223372036854775807")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP12 = -9223372036854775808")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP12 = -9223372036854775809")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP12 = -92233720368547758080000")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP22 = v65535.65535.65535.65535")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP22 = v65536.65535.65535.65535")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"PROP22 = v65535.655350000.65535.65535")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 < 6")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 < 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 > 4")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 > 5")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <= 6")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 <= 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 <= 4")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 >= 4")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP5 >= 5")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP5 >= 6")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 << \"BEGIN\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 << \"END\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 >> \"END\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 >> \"BEGIN\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP4 >< \"MID\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP4 >< \"NONE\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP16 < v1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP16 < v0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP17 > v0.12")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP17 > v1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 >= v1.0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP18 >= v1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP18 >= v2.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.1234.1")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.1.1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP19 <= v1.0.123")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP6 = \"6\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"\"6\" = PROP6")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP6 = \"ABC\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"\"ABC\" = PROP6")); + Assert::False(EvaluateConditionHelper(pVariables, L"\"ABC\" = PROP6")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP13 << 1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP13 << 0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP14 >> 1")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP14 >> 0")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP15 >< 65537")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP15 >< 0")); + + Assert::False(EvaluateConditionHelper(pVariables, L"NOT PROP1")); + Assert::True(EvaluateConditionHelper(pVariables, L"NOT (PROP1 <> \"VAL1\")")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" AND PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" AND PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" OR PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" OR PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" OR PROP2 = \"VAL2\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"NOT\" OR PROP2 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"VAL2\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"VAL3\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND PROP2 = \"NOT\" OR PROP3 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP1 = \"VAL1\" AND (PROP2 = \"NOT\" OR PROP3 = \"VAL3\")")); + Assert::True(EvaluateConditionHelper(pVariables, L"(PROP1 = \"VAL1\" AND PROP2 = \"VAL2\") OR PROP3 = \"NOT\"")); + + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"VAL3\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::False(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR PROP1 = \"VAL1\" AND PROP2 = \"NOT\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"(PROP3 = \"NOT\" OR PROP1 = \"VAL1\") AND PROP2 = \"VAL2\"")); + Assert::True(EvaluateConditionHelper(pVariables, L"PROP3 = \"NOT\" OR (PROP1 = \"VAL1\" AND PROP2 = \"VAL2\")")); + + Assert::True(EvaluateFailureConditionHelper(pVariables, L"=")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"(PROP1")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"(PROP1 = \"")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"1A")); + Assert::True(EvaluateFailureConditionHelper(pVariables, L"*")); + + Assert::True(EvaluateFailureConditionHelper(pVariables, L"1 == 1")); + } + finally + { + ReleaseVariables(pVariables); + } + } + + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilValueTest() + { + HRESULT hr = S_OK; + VARIABLES_HANDLE pVariables = NULL; + VARIABLE_VALUE values[8]; + + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + // set variables + InitNumericValue(pVariables, values + 0, 2, FALSE, 1, L"PROP1"); + InitStringValue(pVariables, values + 1, L"VAL2", FALSE, 2, L"PROP2"); + InitVersionValue(pVariables, values + 2, MAKEQWORDVERSION(1, 1, 0, 0), FALSE, 3, L"PROP3"); + InitNoneValue(pVariables, values + 3, FALSE, 4, L"PROP4"); + InitNoneValue(pVariables, values + 4, TRUE, 5, L"PROP5"); + InitVersionValue(pVariables, values + 5, MAKEQWORDVERSION(1, 1, 1, 0), TRUE, 6, L"PROP6"); + InitStringValue(pVariables, values + 6, L"7", TRUE, 7, L"PROP7"); + InitNumericValue(pVariables, values + 7, 11, TRUE, 8, L"PROP8"); + + for (DWORD i = 0; i < 8; i++) + { + VerifyValue(pVariables, values + i); + } + } + finally + { + VarDestroy(pVariables, FreeValueContext); + } + } + + [NamedFact(Skip = "varutil Not Implemented Yet.")] + void VarUtilEnumTest() + { + HRESULT hr = S_OK; + const DWORD dwIndex = 8; + VARIABLES_HANDLE pVariables = NULL; + VARIABLE_ENUM_HANDLE pEnum = NULL; + VARIABLE_VALUE values[dwIndex]; + VARIABLE_VALUE* pValue = NULL; + + try + { + hr = VarCreate(&pVariables); + NativeAssert::Succeeded(hr, "Failed to initialize variables."); + + hr = VarStartEnum(pVariables, &pEnum, &pValue); + NativeAssert::ValidReturnCode(hr, E_NOMOREITEMS); + + // set variables + InitNumericValue(pVariables, values + 0, 2, FALSE, 0, L"PROP1"); + InitStringValue(pVariables, values + 1, L"VAL2", FALSE, 0, L"PROP2"); + InitVersionValue(pVariables, values + 2, MAKEQWORDVERSION(1, 1, 0, 0), FALSE, 0, L"PROP3"); + InitNoneValue(pVariables, values + 3, FALSE, 0, L"PROP4"); + InitNoneValue(pVariables, values + 4, TRUE, 0, L"PROP5"); + InitVersionValue(pVariables, values + 5, MAKEQWORDVERSION(1, 1, 1, 0), TRUE, 0, L"PROP6"); + InitStringValue(pVariables, values + 6, L"7", TRUE, 0, L"PROP7"); + InitNumericValue(pVariables, values + 7, 11, TRUE, 0, L"PROP8"); + + hr = VarStartEnum(pVariables, &pEnum, &pValue); + + for (DWORD i = dwIndex - 1; i; --i) + { + NativeAssert::ValidReturnCode(hr, S_OK); + + VarUtilContext* pContext = reinterpret_cast(pValue->pvContext); + pContext->dw += 1; + + hr = VarNextVariable(pEnum, &pValue); + } + + NativeAssert::ValidReturnCode(hr, E_NOMOREITEMS); + + for (DWORD j = 0; j < dwIndex; j++) + { + VarUtilContext* pContext = reinterpret_cast(values[j].pvContext); + NativeAssert::Equal(1, pContext->dw); + } + + VarFinishEnum(pEnum); + pEnum = NULL; + + hr = VarStartEnum(pVariables, &pEnum, &pValue); + + for (DWORD i = dwIndex - 1; i; --i) + { + NativeAssert::ValidReturnCode(hr, S_OK); + + VarUtilContext* pContext = reinterpret_cast(pValue->pvContext); + pContext->dw += 1; + + hr = VarNextVariable(pEnum, &pValue); + } + + NativeAssert::ValidReturnCode(hr, E_NOMOREITEMS); + + for (DWORD j = 0; j < dwIndex; j++) + { + VarUtilContext* pContext = reinterpret_cast(values[j].pvContext); + NativeAssert::Equal(2, pContext->dw); + } + } + finally + { + VarFinishEnum(pEnum); + ReleaseVariableValue(pValue); + VarDestroy(pVariables, FreeValueContext); + } + } + + private: + void InitNoneValue(VARIABLES_HANDLE pVariables, VARIABLE_VALUE* pValue, BOOL fHidden, DWORD dw, LPCWSTR wz) + { + pValue->type = VARIABLE_VALUE_TYPE_NONE; + pValue->fHidden = fHidden; + + InitValueContext(pValue, dw, wz); + + HRESULT hr = VarSetValue(pVariables, wz, pValue); + NativeAssert::Succeeded(hr, "Failed to set value for variable {0}", wz); + } + + void InitNumericValue(VARIABLES_HANDLE pVariables, VARIABLE_VALUE* pValue, LONGLONG llValue, BOOL fHidden, DWORD dw, LPCWSTR wz) + { + pValue->type = VARIABLE_VALUE_TYPE_NUMERIC; + pValue->fHidden = fHidden; + + pValue->llValue = llValue; + + InitValueContext(pValue, dw, wz); + + HRESULT hr = VarSetValue(pVariables, wz, pValue); + NativeAssert::Succeeded(hr, "Failed to set value for variable {0}", wz); + } + + void InitStringValue(VARIABLES_HANDLE pVariables, VARIABLE_VALUE* pValue, LPWSTR wzValue, BOOL fHidden, DWORD dw, LPCWSTR wz) + { + pValue->type = VARIABLE_VALUE_TYPE_STRING; + pValue->fHidden = fHidden; + + HRESULT hr = StrAllocString(&pValue->sczValue, wzValue, 0); + NativeAssert::Succeeded(hr, "Failed to alloc string: {0}", wzValue); + + InitValueContext(pValue, dw, wz); + + hr = VarSetValue(pVariables, wz, pValue); + NativeAssert::Succeeded(hr, "Failed to set value for variable {0}", wz); + } + + void InitVersionValue(VARIABLES_HANDLE pVariables, VARIABLE_VALUE* pValue, DWORD64 qwValue, BOOL fHidden, DWORD dw, LPCWSTR wz) + { + pValue->type = VARIABLE_VALUE_TYPE_VERSION; + pValue->fHidden = fHidden; + + pValue->qwValue = qwValue; + + InitValueContext(pValue, dw, wz); + + HRESULT hr = VarSetValue(pVariables, wz, pValue); + NativeAssert::Succeeded(hr, "Failed to set value for variable {0}", wz); + } + + void InitValueContext(VARIABLE_VALUE* pValue, DWORD dw, LPCWSTR wz) + { + pValue->pvContext = MemAlloc(sizeof(VarUtilContext), TRUE); + VarUtilContext* pContext = reinterpret_cast(pValue->pvContext); + if (!pContext) + { + throw gcnew OutOfMemoryException(); + } + + pContext->dw = dw; + + HRESULT hr = StrAllocString(&pContext->scz, wz, 0); + NativeAssert::Succeeded(hr, "Failed to alloc string: {0}", wz); + } + + void VerifyValue(VARIABLES_HANDLE pVariables, VARIABLE_VALUE* pExpectedValue) + { + VARIABLE_VALUE* pActualValue = NULL; + + try + { + VarUtilContext* pExpectedContext = reinterpret_cast(pExpectedValue->pvContext); + NativeAssert::True(NULL != pExpectedContext); + + HRESULT hr = VarGetValue(pVariables, pExpectedContext->scz, &pActualValue); + NativeAssert::Succeeded(hr, "Failed to get value: {0}", pExpectedContext->scz); + + NativeAssert::Equal(pExpectedValue->type, pActualValue->type); + NativeAssert::InRange(pExpectedValue->type, VARIABLE_VALUE_TYPE_NONE, VARIABLE_VALUE_TYPE_STRING); + + switch (pExpectedValue->type) + { + case VARIABLE_VALUE_TYPE_NONE: + case VARIABLE_VALUE_TYPE_VERSION: + NativeAssert::Equal(pExpectedValue->qwValue, pActualValue->qwValue); + break; + case VARIABLE_VALUE_TYPE_NUMERIC: + NativeAssert::Equal(pExpectedValue->llValue, pActualValue->llValue); + break; + case VARIABLE_VALUE_TYPE_STRING: + NativeAssert::StringEqual(pExpectedValue->sczValue, pActualValue->sczValue); + break; + } + + NativeAssert::Equal(pExpectedValue->fHidden, pActualValue->fHidden); + NativeAssert::True(pExpectedValue->pvContext == pActualValue->pvContext); + } + finally + { + ReleaseVariableValue(pActualValue); + } + } + }; +} diff --git a/src/test/DUtilUnitTest/error.h b/src/test/DUtilUnitTest/error.h new file mode 100644 index 00000000..a52db56d --- /dev/null +++ b/src/test/DUtilUnitTest/error.h @@ -0,0 +1,8 @@ +// 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. + +const int ERROR_STRING_BUFFER = 1024; + +static char szMsg[ERROR_STRING_BUFFER]; +static WCHAR wzMsg[ERROR_STRING_BUFFER]; + +#define ExitTrace(x, f, ...) { HRESULT hrTemp = x; hr = ::StringCchPrintfA(szMsg, countof(szMsg), f, __VA_ARGS__); MultiByteToWideChar(CP_ACP, 0, szMsg, -1, wzMsg, countof(wzMsg)); throw gcnew System::Exception(System::String::Format("hr = 0x{0:X8}, message = {1}", hrTemp, gcnew System::String(wzMsg))); } diff --git a/src/test/DUtilUnitTest/packages.config b/src/test/DUtilUnitTest/packages.config new file mode 100644 index 00000000..17dcb258 --- /dev/null +++ b/src/test/DUtilUnitTest/packages.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/DUtilUnitTest/precomp.cpp b/src/test/DUtilUnitTest/precomp.cpp new file mode 100644 index 00000000..37664a1c --- /dev/null +++ b/src/test/DUtilUnitTest/precomp.cpp @@ -0,0 +1,3 @@ +// 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" diff --git a/src/test/DUtilUnitTest/precomp.h b/src/test/DUtilUnitTest/precomp.h new file mode 100644 index 00000000..aa8f7de6 --- /dev/null +++ b/src/test/DUtilUnitTest/precomp.h @@ -0,0 +1,30 @@ +#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. + + +#include +#include +#include + +// Include error.h before dutil.h +#include "error.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VarHelpers.h" + +#pragma managed +#include diff --git a/src/test/DUtilUnitTest/resource.h b/src/test/DUtilUnitTest/resource.h new file mode 100644 index 00000000..bdf252f6 --- /dev/null +++ b/src/test/DUtilUnitTest/resource.h @@ -0,0 +1,2 @@ +// 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. + -- cgit v1.2.3-55-g6feb