aboutsummaryrefslogtreecommitdiff
path: root/src/libs/dutil/test/DUtilUnitTest
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-22 06:38:23 -0700
committerRob Mensching <rob@firegiant.com>2021-04-29 16:21:09 -0700
commit7f642e51670bc38a4ef782a363936850bc2b0ba9 (patch)
tree19684b2d94979f130c0935328f0d44cf006e45ef /src/libs/dutil/test/DUtilUnitTest
parentf39e7a3e164d0736e45049e5726d0da2013da3c9 (diff)
downloadwix-7f642e51670bc38a4ef782a363936850bc2b0ba9.tar.gz
wix-7f642e51670bc38a4ef782a363936850bc2b0ba9.tar.bz2
wix-7f642e51670bc38a4ef782a363936850bc2b0ba9.zip
Move dutil into libs/dutil
Diffstat (limited to 'src/libs/dutil/test/DUtilUnitTest')
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp46
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp12
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp35
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj99
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters80
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp191
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp70
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp125
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp60
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp345
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp505
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp487
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp80
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp488
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp192
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml68
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/UnitTest.rc6
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp98
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp933
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/error.cpp26
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/error.h14
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/packages.config13
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/precomp.cpp3
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/precomp.h32
-rw-r--r--src/libs/dutil/test/DUtilUnitTest/resource.h2
25 files changed, 4010 insertions, 0 deletions
diff --git a/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp
new file mode 100644
index 00000000..30a45f5a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/ApupUtilTests.cpp
@@ -0,0 +1,46 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class ApupUtil
12 {
13 public:
14 [Fact]
15 void AllocChainFromAtomSortsDescending()
16 {
17 HRESULT hr = S_OK;
18 ATOM_FEED* pFeed = NULL;
19 APPLICATION_UPDATE_CHAIN* pChain = NULL;
20
21 DutilInitialize(&DutilTestTraceError);
22
23 try
24 {
25 XmlInitialize();
26 NativeAssert::Succeeded(hr, "Failed to initialize Xml.");
27
28 pin_ptr<const wchar_t> feedFilePath = PtrToStringChars(TestData::Get("TestData", "ApupUtilTests", "FeedBv2.0.xml"));
29 hr = AtomParseFromFile(feedFilePath, &pFeed);
30 NativeAssert::Succeeded(hr, "Failed to parse feed: {0}", feedFilePath);
31
32 hr = ApupAllocChainFromAtom(pFeed, &pChain);
33 NativeAssert::Succeeded(hr, "Failed to get chain from feed.");
34
35 Assert::Equal(3ul, pChain->cEntries);
36 NativeAssert::StringEqual(L"Bundle v2.0", pChain->rgEntries[0].wzTitle);
37 NativeAssert::StringEqual(L"Bundle v1.0", pChain->rgEntries[1].wzTitle);
38 NativeAssert::StringEqual(L"Bundle v1.0-preview", pChain->rgEntries[2].wzTitle);
39 }
40 finally
41 {
42 DutilUninitialize();
43 }
44 }
45 };
46}
diff --git a/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp b/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp
new file mode 100644
index 00000000..2d527910
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/AssemblyInfo.cpp
@@ -0,0 +1,12 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System::Reflection;
6using namespace System::Runtime::CompilerServices;
7using namespace System::Runtime::InteropServices;
8
9[assembly: AssemblyTitleAttribute("Windows Installer XML Dutil unit tests")];
10[assembly: AssemblyDescriptionAttribute("Dutil unit tests")];
11[assembly: AssemblyCultureAttribute("")];
12[assembly: ComVisible(false)];
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp
new file mode 100644
index 00000000..55e81d46
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilTests.cpp
@@ -0,0 +1,35 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class DUtil
12 {
13 public:
14 [Fact]
15 void DUtilTraceErrorSourceFiltersOnTraceLevel()
16 {
17 DutilInitialize(&DutilTestTraceError);
18
19 CallDutilTraceErrorSource();
20
21 Dutil_TraceSetLevel(REPORT_DEBUG, FALSE);
22
23 Action^ action = gcnew Action(this, &DUtil::CallDutilTraceErrorSource);
24 Assert::Throws<Exception^>(action);
25
26 DutilUninitialize();
27 }
28
29 private:
30 void CallDutilTraceErrorSource()
31 {
32 Dutil_TraceErrorSource(__FILE__, __LINE__, REPORT_DEBUG, DUTIL_SOURCE_EXTERNAL, E_FAIL, "Error message");
33 }
34 };
35}
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
new file mode 100644
index 00000000..18410e5d
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj
@@ -0,0 +1,99 @@
1<?xml version="1.0" encoding="utf-8"?>
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. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <Import Project="..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props" Condition="Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props')" />
6 <ItemGroup Label="ProjectConfigurations">
7 <ProjectConfiguration Include="Debug|Win32">
8 <Configuration>Debug</Configuration>
9 <Platform>Win32</Platform>
10 </ProjectConfiguration>
11 <ProjectConfiguration Include="Release|Win32">
12 <Configuration>Release</Configuration>
13 <Platform>Win32</Platform>
14 </ProjectConfiguration>
15 </ItemGroup>
16
17 <PropertyGroup Label="Globals">
18 <ProjectTypes>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}</ProjectTypes>
19 <ProjectGuid>{AB7EE608-E5FB-42A5-831F-0DEEEA141223}</ProjectGuid>
20 <RootNamespace>DUtilUnitTests</RootNamespace>
21 <Keyword>ManagedCProj</Keyword>
22 <ConfigurationType>DynamicLibrary</ConfigurationType>
23 <CharacterSet>Unicode</CharacterSet>
24 <CLRSupport>true</CLRSupport>
25 <SignOutput>false</SignOutput>
26 </PropertyGroup>
27
28 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
29 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
30
31 <PropertyGroup>
32 <ProjectAdditionalIncludeDirectories>..\..\dutil\inc</ProjectAdditionalIncludeDirectories>
33 <ProjectAdditionalLinkLibraries>rpcrt4.lib;Mpr.lib;Ws2_32.lib;urlmon.lib;wininet.lib</ProjectAdditionalLinkLibraries>
34 </PropertyGroup>
35
36 <ItemGroup>
37 <ClCompile Include="ApupUtilTests.cpp" />
38 <ClCompile Include="AssemblyInfo.cpp" />
39 <ClCompile Include="DictUtilTest.cpp" />
40 <ClCompile Include="DirUtilTests.cpp" />
41 <ClCompile Include="DUtilTests.cpp" />
42 <ClCompile Include="error.cpp" />
43 <ClCompile Include="FileUtilTest.cpp" />
44 <ClCompile Include="GuidUtilTest.cpp" />
45 <ClCompile Include="IniUtilTest.cpp" />
46 <ClCompile Include="MemUtilTest.cpp" />
47 <ClCompile Include="MonUtilTest.cpp" />
48 <ClCompile Include="PathUtilTest.cpp" />
49 <ClCompile Include="precomp.cpp">
50 <PrecompiledHeader>Create</PrecompiledHeader>
51 <!-- Warnings from referencing netstandard dlls -->
52 <DisableSpecificWarnings>4564;4691</DisableSpecificWarnings>
53 </ClCompile>
54 <ClCompile Include="SceUtilTest.cpp" Condition=" Exists('$(SqlCESdkIncludePath)') " />
55 <ClCompile Include="StrUtilTest.cpp" />
56 <ClCompile Include="UriUtilTest.cpp" />
57 <ClCompile Include="VerUtilTests.cpp" />
58 </ItemGroup>
59
60 <ItemGroup>
61 <ClInclude Include="precomp.h" />
62 <ClInclude Include="error.h" />
63 </ItemGroup>
64
65 <ItemGroup>
66 <None Include="packages.config" />
67 <ResourceCompile Include="UnitTest.rc" />
68 </ItemGroup>
69
70 <ItemGroup>
71 <None Include="TestData\ApupUtilTests\FeedBv2.0.xml" CopyToOutputDirectory="PreserveNewest" />
72 </ItemGroup>
73
74 <ItemGroup>
75 <Reference Include="System" />
76 <Reference Include="System.Core" />
77 <Reference Include="WixBuildTools.TestSupport">
78 <HintPath>..\..\..\packages\WixBuildTools.TestSupport.4.0.47\lib\net472\WixBuildTools.TestSupport.dll</HintPath>
79 </Reference>
80 <Reference Include="WixBuildTools.TestSupport.Native">
81 <HintPath>..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\lib\net472\WixBuildTools.TestSupport.Native.dll</HintPath>
82 </Reference>
83 </ItemGroup>
84
85 <ItemGroup>
86 <ProjectReference Include="..\..\dutil\dutil.vcxproj">
87 <Project>{1244E671-F108-4334-BA52-8A7517F26ECD}</Project>
88 </ProjectReference>
89 </ItemGroup>
90 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
91 <Import Project="..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets" Condition="Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets')" />
92 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
93 <PropertyGroup>
94 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
95 </PropertyGroup>
96 <Error Condition="!Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.props'))" />
97 <Error Condition="!Exists('..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\WixBuildTools.TestSupport.Native.4.0.47\build\WixBuildTools.TestSupport.Native.targets'))" />
98 </Target>
99</Project> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
new file mode 100644
index 00000000..4df7af89
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DUtilUnitTest.vcxproj.filters
@@ -0,0 +1,80 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="ApupUtilTests.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="AssemblyInfo.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="DictUtilTest.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="DirUtilTests.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 <ClCompile Include="DUtilTests.cpp">
31 <Filter>Source Files</Filter>
32 </ClCompile>
33 <ClCompile Include="error.cpp">
34 <Filter>Source Files</Filter>
35 </ClCompile>
36 <ClCompile Include="FileUtilTest.cpp">
37 <Filter>Source Files</Filter>
38 </ClCompile>
39 <ClCompile Include="GuidUtilTest.cpp">
40 <Filter>Source Files</Filter>
41 </ClCompile>
42 <ClCompile Include="IniUtilTest.cpp">
43 <Filter>Source Files</Filter>
44 </ClCompile>
45 <ClCompile Include="MemUtilTest.cpp">
46 <Filter>Source Files</Filter>
47 </ClCompile>
48 <ClCompile Include="MonUtilTest.cpp">
49 <Filter>Source Files</Filter>
50 </ClCompile>
51 <ClCompile Include="PathUtilTest.cpp">
52 <Filter>Source Files</Filter>
53 </ClCompile>
54 <ClCompile Include="precomp.cpp">
55 <Filter>Source Files</Filter>
56 </ClCompile>
57 <ClCompile Include="StrUtilTest.cpp">
58 <Filter>Source Files</Filter>
59 </ClCompile>
60 <ClCompile Include="UriUtilTest.cpp">
61 <Filter>Source Files</Filter>
62 </ClCompile>
63 <ClCompile Include="VerUtilTests.cpp">
64 <Filter>Source Files</Filter>
65 </ClCompile>
66 </ItemGroup>
67 <ItemGroup>
68 <ResourceCompile Include="UnitTest.rc">
69 <Filter>Resource Files</Filter>
70 </ResourceCompile>
71 </ItemGroup>
72 <ItemGroup>
73 <ClInclude Include="precomp.h">
74 <Filter>Header Files</Filter>
75 </ClInclude>
76 <ClInclude Include="error.h">
77 <Filter>Header Files</Filter>
78 </ClInclude>
79 </ItemGroup>
80</Project> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp
new file mode 100644
index 00000000..4b4777d7
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DictUtilTest.cpp
@@ -0,0 +1,191 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9const DWORD numIterations = 100000;
10
11namespace DutilTests
12{
13 struct Value
14 {
15 DWORD dwNum;
16 LPWSTR sczKey;
17 };
18
19 public ref class DictUtil
20 {
21 public:
22 [Fact]
23 void DictUtilTest()
24 {
25 DutilInitialize(&DutilTestTraceError);
26
27 EmbeddedKeyTestHelper(DICT_FLAG_NONE, numIterations);
28
29 EmbeddedKeyTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations);
30
31 StringListTestHelper(DICT_FLAG_NONE, numIterations);
32
33 StringListTestHelper(DICT_FLAG_CASEINSENSITIVE, numIterations);
34
35 DutilUninitialize();
36 }
37
38 private:
39 void EmbeddedKeyTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations)
40 {
41 HRESULT hr = S_OK;
42 Value *rgValues = NULL;
43 Value *valueFound = NULL;
44 DWORD cValues = 0;
45 LPWSTR sczExpectedKey = NULL;
46 STRINGDICT_HANDLE sdValues = NULL;
47
48 try
49 {
50 hr = DictCreateWithEmbeddedKey(&sdValues, 0, (void **)&rgValues, offsetof(Value, sczKey), dfFlags);
51 NativeAssert::Succeeded(hr, "Failed to create dictionary of values");
52
53 for (DWORD i = 0; i < dwNumIterations; ++i)
54 {
55 cValues++;
56
57 hr = MemEnsureArraySize((void **)&rgValues, cValues, sizeof(Value), 5);
58 NativeAssert::Succeeded(hr, "Failed to grow value array");
59
60 hr = StrAllocFormatted(&rgValues[i].sczKey, L"%u_a_%u", i, i);
61 NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i);
62
63 hr = DictAddValue(sdValues, rgValues + i);
64 NativeAssert::Succeeded(hr, "Failed to add item {0} to dict", i);
65 }
66
67 for (DWORD i = 0; i < dwNumIterations; ++i)
68 {
69 hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i);
70 NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i);
71
72 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
73 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
74
75 NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey);
76
77 hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i);
78 NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i);
79
80 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
81
82 if (dfFlags & DICT_FLAG_CASEINSENSITIVE)
83 {
84 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
85
86 NativeAssert::StringEqual(sczExpectedKey, valueFound->sczKey, TRUE);
87 }
88 else
89 {
90 if (E_NOTFOUND != hr)
91 {
92 hr = E_FAIL;
93 ExitOnFailure(hr, "This embedded key is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey);
94 }
95 }
96
97 hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i);
98 NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i);
99
100 hr = DictGetValue(sdValues, sczExpectedKey, (void **)&valueFound);
101 if (E_NOTFOUND != hr)
102 {
103 hr = E_FAIL;
104 ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey);
105 }
106 }
107 }
108 finally
109 {
110 for (DWORD i = 0; i < cValues; ++i)
111 {
112 ReleaseStr(rgValues[i].sczKey);
113 }
114 ReleaseMem(rgValues);
115 ReleaseStr(sczExpectedKey);
116 ReleaseDict(sdValues);
117 }
118
119 LExit:
120 return;
121 }
122
123 void StringListTestHelper(DICT_FLAG dfFlags, DWORD dwNumIterations)
124 {
125 HRESULT hr = S_OK;
126 LPWSTR sczKey = NULL;
127 LPWSTR sczExpectedKey = NULL;
128 STRINGDICT_HANDLE sdValues = NULL;
129
130 try
131 {
132 hr = DictCreateStringList(&sdValues, 0, dfFlags);
133 NativeAssert::Succeeded(hr, "Failed to create dictionary of keys");
134
135 for (DWORD i = 0; i < dwNumIterations; ++i)
136 {
137 hr = StrAllocFormatted(&sczKey, L"%u_a_%u", i, i);
138 NativeAssert::Succeeded(hr, "Failed to allocate key for value {0}", i);
139
140 hr = DictAddKey(sdValues, sczKey);
141 NativeAssert::Succeeded(hr, "Failed to add key {0} to dict", i);
142 }
143
144 for (DWORD i = 0; i < dwNumIterations; ++i)
145 {
146 hr = StrAllocFormatted(&sczExpectedKey, L"%u_a_%u", i, i);
147 NativeAssert::Succeeded(hr, "Failed to allocate expected key {0}", i);
148
149 hr = DictKeyExists(sdValues, sczExpectedKey);
150 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
151
152 hr = StrAllocFormatted(&sczExpectedKey, L"%u_A_%u", i, i);
153 NativeAssert::Succeeded(hr, "Failed to allocate uppercase expected key {0}", i);
154
155 hr = DictKeyExists(sdValues, sczExpectedKey);
156 if (dfFlags & DICT_FLAG_CASEINSENSITIVE)
157 {
158 NativeAssert::Succeeded(hr, "Failed to find value {0}", sczExpectedKey);
159 }
160 else
161 {
162 if (E_NOTFOUND != hr)
163 {
164 hr = E_FAIL;
165 ExitOnFailure(hr, "This stringlist dict is case sensitive, but it seemed to have found something case using case insensitivity!: %ls", sczExpectedKey);
166 }
167 }
168
169 hr = StrAllocFormatted(&sczExpectedKey, L"%u_b_%u", i, i);
170 NativeAssert::Succeeded(hr, "Failed to allocate unexpected key {0}", i);
171
172 hr = DictKeyExists(sdValues, sczExpectedKey);
173 if (E_NOTFOUND != hr)
174 {
175 hr = E_FAIL;
176 ExitOnFailure(hr, "Item shouldn't have been found in dictionary: %ls", sczExpectedKey);
177 }
178 }
179 }
180 finally
181 {
182 ReleaseStr(sczKey);
183 ReleaseStr(sczExpectedKey);
184 ReleaseDict(sdValues);
185 }
186
187 LExit:
188 return;
189 }
190 };
191}
diff --git a/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp
new file mode 100644
index 00000000..7643366f
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/DirUtilTests.cpp
@@ -0,0 +1,70 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class DirUtil
12 {
13 public:
14 [Fact]
15 void DirUtilTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczCurrentDir = NULL;
19 LPWSTR sczGuid = NULL;
20 LPWSTR sczFolder = NULL;
21 LPWSTR sczSubFolder = NULL;
22
23 try
24 {
25 hr = GuidCreate(&sczGuid);
26 NativeAssert::Succeeded(hr, "Failed to create guid.");
27
28 hr = DirGetCurrent(&sczCurrentDir);
29 NativeAssert::Succeeded(hr, "Failed to get current directory.");
30
31 hr = PathConcat(sczCurrentDir, sczGuid, &sczFolder);
32 NativeAssert::Succeeded(hr, "Failed to combine current directory: '{0}' with Guid: '{1}'", sczCurrentDir, sczGuid);
33
34 BOOL fExists = DirExists(sczFolder, NULL);
35 Assert::False(fExists == TRUE);
36
37 hr = PathConcat(sczFolder, L"foo", &sczSubFolder);
38 NativeAssert::Succeeded(hr, "Failed to combine folder: '%ls' with subfolder: 'foo'", sczFolder);
39
40 hr = DirEnsureExists(sczSubFolder, NULL);
41 NativeAssert::Succeeded(hr, "Failed to create multiple directories: %ls", sczSubFolder);
42
43 // Test failure to delete non-empty folder.
44 hr = DirEnsureDelete(sczFolder, FALSE, FALSE);
45 Assert::Equal(HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY), hr);
46
47 hr = DirEnsureDelete(sczSubFolder, FALSE, FALSE);
48 NativeAssert::Succeeded(hr, "Failed to delete single directory: %ls", sczSubFolder);
49
50 // Put the directory back and we'll test deleting tree.
51 hr = DirEnsureExists(sczSubFolder, NULL);
52 NativeAssert::Succeeded(hr, "Failed to create single directory: %ls", sczSubFolder);
53
54 hr = DirEnsureDelete(sczFolder, FALSE, TRUE);
55 NativeAssert::Succeeded(hr, "Failed to delete directory tree: %ls", sczFolder);
56
57 // Finally, try to create "C:\" which would normally fail, but we want success
58 hr = DirEnsureExists(L"C:\\", NULL);
59 NativeAssert::Succeeded(hr, "Failed to create C:\\");
60 }
61 finally
62 {
63 ReleaseStr(sczSubFolder);
64 ReleaseStr(sczFolder);
65 ReleaseStr(sczGuid);
66 ReleaseStr(sczCurrentDir);
67 }
68 }
69 };
70}
diff --git a/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp
new file mode 100644
index 00000000..ac071ef2
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/FileUtilTest.cpp
@@ -0,0 +1,125 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class FileUtil
12 {
13 public:
14 [Fact(Skip="Skipped until we have a good way to reference ANSI.txt.")]
15 void FileUtilTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczTempDir = NULL;
19 LPWSTR sczFileDir = NULL;
20
21 DutilInitialize(&DutilTestTraceError);
22
23 try
24 {
25 hr = PathExpand(&sczTempDir, L"%TEMP%\\FileUtilTest\\", PATH_EXPAND_ENVIRONMENT);
26 NativeAssert::Succeeded(hr, "Failed to get temp dir");
27
28 hr = PathExpand(&sczFileDir, L"%WIX_ROOT%\\examples\\data\\TextEncodings\\", PATH_EXPAND_ENVIRONMENT);
29 NativeAssert::Succeeded(hr, "Failed to get path to encodings file dir");
30
31 hr = DirEnsureExists(sczTempDir, NULL);
32 NativeAssert::Succeeded(hr, "Failed to ensure directory exists: {0}", sczTempDir);
33
34 TestFile(sczFileDir, sczTempDir, L"ANSI.txt", 32, FILE_ENCODING_UTF8);
35 // Big endian not supported today!
36 //TestFile(sczFileDir, L"UnicodeBENoBOM.txt", 34);
37 //TestFile(sczFileDir, L"UnicodeBEWithBOM.txt", 34);
38 TestFile(sczFileDir, sczTempDir, L"UnicodeLENoBOM.txt", 34, FILE_ENCODING_UTF16);
39 TestFile(sczFileDir, sczTempDir, L"UnicodeLEWithBOM.txt", 34, FILE_ENCODING_UTF16_WITH_BOM);
40 TestFile(sczFileDir, sczTempDir, L"UTF8WithSignature.txt", 34, FILE_ENCODING_UTF8_WITH_BOM);
41
42 hr = DirEnsureDelete(sczTempDir, TRUE, TRUE);
43 }
44 finally
45 {
46 ReleaseStr(sczTempDir);
47 ReleaseStr(sczFileDir);
48 DutilUninitialize();
49 }
50 }
51
52 private:
53 void TestFile(LPWSTR wzDir, LPCWSTR wzTempDir, LPWSTR wzFileName, size_t cbExpectedStringLength, FILE_ENCODING feExpectedEncoding)
54 {
55 HRESULT hr = S_OK;
56 LPWSTR sczFullPath = NULL;
57 LPWSTR sczContents = NULL;
58 LPWSTR sczOutputPath = NULL;
59 FILE_ENCODING feEncodingFound = FILE_ENCODING_UNSPECIFIED;
60 BYTE *pbFile1 = NULL;
61 DWORD cbFile1 = 0;
62 BYTE *pbFile2 = NULL;
63 DWORD cbFile2 = 0;
64 size_t cbActualStringLength = 0;
65
66 try
67 {
68 hr = PathConcat(wzDir, wzFileName, &sczFullPath);
69 NativeAssert::Succeeded(hr, "Failed to create path to test file: {0}", sczFullPath);
70
71 hr = FileToString(sczFullPath, &sczContents, &feEncodingFound);
72 hr = E_FAIL;
73 NativeAssert::Succeeded(hr, "Failed to read text from file: {0}", sczFullPath);
74
75 if (!sczContents)
76 {
77 hr = E_FAIL;
78 NativeAssert::Succeeded(hr, "FileToString() returned NULL for file: {0}", sczFullPath);
79 }
80
81 hr = ::StringCchLengthW(sczContents, STRSAFE_MAX_CCH, &cbActualStringLength);
82 NativeAssert::Succeeded(hr, "Failed to get length of text from file: {0}", sczFullPath);
83
84 if (cbActualStringLength != cbExpectedStringLength)
85 {
86 hr = E_FAIL;
87 ExitOnFailure(hr, "FileToString() returned wrong size for file: %ls (expected size %Iu, found size %Iu)", sczFullPath, cbExpectedStringLength, cbActualStringLength);
88 }
89
90 if (feEncodingFound != feExpectedEncoding)
91 {
92 hr = E_FAIL;
93 ExitOnFailure(hr, "FileToString() returned unexpected encoding type for file: %ls (expected type %u, found type %u)", sczFullPath, feExpectedEncoding, feEncodingFound);
94 }
95
96 hr = PathConcat(wzTempDir, wzFileName, &sczOutputPath);
97 NativeAssert::Succeeded(hr, "Failed to get output path");
98
99 hr = FileFromString(sczOutputPath, 0, sczContents, feExpectedEncoding);
100 NativeAssert::Succeeded(hr, "Failed to write contents of file back out to disk");
101
102 hr = FileRead(&pbFile1, &cbFile1, sczFullPath);
103 NativeAssert::Succeeded(hr, "Failed to read input file as binary");
104
105 hr = FileRead(&pbFile2, &cbFile2, sczOutputPath);
106 NativeAssert::Succeeded(hr, "Failed to read output file as binary");
107
108 if (cbFile1 != cbFile2 || 0 != memcmp(pbFile1, pbFile2, cbFile1))
109 {
110 hr = E_FAIL;
111 ExitOnFailure(hr, "Outputted file doesn't match input file: \"%ls\" and \"%ls\"", sczFullPath, sczOutputPath);
112 }
113 }
114 finally
115 {
116 ReleaseStr(sczOutputPath);
117 ReleaseStr(sczFullPath);
118 ReleaseStr(sczContents);
119 }
120
121 LExit:
122 return;
123 }
124 };
125}
diff --git a/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp
new file mode 100644
index 00000000..a6e27a09
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/GuidUtilTest.cpp
@@ -0,0 +1,60 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class GuidUtil
12 {
13 public:
14 [Fact]
15 void GuidCreateTest()
16 {
17 HRESULT hr = S_OK;
18 WCHAR wzGuid1[GUID_STRING_LENGTH];
19 WCHAR wzGuid2[GUID_STRING_LENGTH];
20
21 hr = GuidFixedCreate(wzGuid1);
22 NativeAssert::Succeeded(hr, "Failed to create first guid.");
23 Guid firstGuid = Guid::Parse(gcnew String(wzGuid1));
24
25 hr = GuidFixedCreate(wzGuid2);
26 NativeAssert::Succeeded(hr, "Failed to create second guid.");
27 Guid secondGuid = Guid::Parse(gcnew String(wzGuid2));
28
29 NativeAssert::NotStringEqual(wzGuid1, wzGuid2);
30 NativeAssert::NotEqual(firstGuid, secondGuid);
31 }
32
33 [Fact]
34 void GuidCreateSczTest()
35 {
36 HRESULT hr = S_OK;
37 LPWSTR sczGuid1 = NULL;
38 LPWSTR sczGuid2 = NULL;
39
40 try
41 {
42 hr = GuidCreate(&sczGuid1);
43 NativeAssert::Succeeded(hr, "Failed to create first guid.");
44 Guid firstGuid = Guid::Parse(gcnew String(sczGuid1));
45
46 hr = GuidCreate(&sczGuid2);
47 NativeAssert::Succeeded(hr, "Failed to create second guid.");
48 Guid secondGuid = Guid::Parse(gcnew String(sczGuid2));
49
50 NativeAssert::NotStringEqual(sczGuid1, sczGuid2);
51 NativeAssert::NotEqual(firstGuid, secondGuid);
52 }
53 finally
54 {
55 ReleaseStr(sczGuid1);
56 ReleaseStr(sczGuid2);
57 }
58 }
59 };
60}
diff --git a/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp
new file mode 100644
index 00000000..946f19c5
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/IniUtilTest.cpp
@@ -0,0 +1,345 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9typedef HRESULT (__clrcall *IniFormatParameters)(
10 INI_HANDLE
11 );
12
13namespace DutilTests
14{
15 public ref class IniUtil
16 {
17 public:
18 [Fact]
19 void IniUtilTest()
20 {
21 HRESULT hr = S_OK;
22 LPWSTR sczTempIniFilePath = NULL;
23 LPWSTR sczTempIniFileDir = NULL;
24 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";
25 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";
26
27 DutilInitialize(&DutilTestTraceError);
28
29 try
30 {
31 hr = PathExpand(&sczTempIniFilePath, L"%TEMP%\\IniUtilTest\\Test.ini", PATH_EXPAND_ENVIRONMENT);
32 NativeAssert::Succeeded(hr, "Failed to get path to temp INI file");
33
34 hr = PathGetDirectory(sczTempIniFilePath, &sczTempIniFileDir);
35 NativeAssert::Succeeded(hr, "Failed to get directory to temp INI file");
36
37 hr = DirEnsureDelete(sczTempIniFileDir, TRUE, TRUE);
38 if (E_PATHNOTFOUND == hr)
39 {
40 hr = S_OK;
41 }
42 NativeAssert::Succeeded(hr, "Failed to delete IniUtilTest directory: {0}", sczTempIniFileDir);
43
44 hr = DirEnsureExists(sczTempIniFileDir, NULL);
45 NativeAssert::Succeeded(hr, "Failed to ensure temp directory exists: {0}", sczTempIniFileDir);
46
47 // Tests parsing, then modifying a regular INI file
48 TestReadThenWrite(sczTempIniFilePath, StandardIniFormat, wzIniContents);
49
50 // Tests programmatically creating from scratch, then parsing an INI file
51 TestWriteThenRead(sczTempIniFilePath, StandardIniFormat);
52
53 // Tests parsing, then modifying a regular INI file
54 TestReadThenWrite(sczTempIniFilePath, ScriptFormat, wzScriptContents);
55
56 // Tests programmatically creating from scratch, then parsing an INI file
57 TestWriteThenRead(sczTempIniFilePath, ScriptFormat);
58 }
59 finally
60 {
61 ReleaseStr(sczTempIniFilePath);
62 ReleaseStr(sczTempIniFileDir);
63 DutilUninitialize();
64 }
65 }
66
67 private:
68 void AssertValue(INI_HANDLE iniHandle, LPCWSTR wzValueName, LPCWSTR wzValue)
69 {
70 HRESULT hr = S_OK;
71 LPWSTR sczValue = NULL;
72
73 try
74 {
75 hr = IniGetValue(iniHandle, wzValueName, &sczValue);
76 NativeAssert::Succeeded(hr, "Failed to get ini value: {0}", wzValueName);
77
78 if (0 != wcscmp(sczValue, wzValue))
79 {
80 hr = E_FAIL;
81 ExitOnFailure(hr, "Expected to find value in INI: '%ls'='%ls' - but found value '%ls' instead", wzValueName, wzValue, sczValue);
82 }
83 }
84 finally
85 {
86 ReleaseStr(sczValue);
87 }
88
89 LExit:
90 return;
91 }
92
93 void AssertNoValue(INI_HANDLE iniHandle, LPCWSTR wzValueName)
94 {
95 HRESULT hr = S_OK;
96 LPWSTR sczValue = NULL;
97
98 try
99 {
100 hr = IniGetValue(iniHandle, wzValueName, &sczValue);
101 if (E_NOTFOUND != hr)
102 {
103 if (SUCCEEDED(hr))
104 {
105 hr = E_FAIL;
106 }
107 ExitOnFailure(hr, "INI value shouldn't have been found: %ls", wzValueName);
108 }
109 }
110 finally
111 {
112 ReleaseStr(sczValue);
113 }
114
115 LExit:
116 return;
117 }
118
119 static HRESULT StandardIniFormat(__inout INI_HANDLE iniHandle)
120 {
121 HRESULT hr = S_OK;
122
123 hr = IniSetOpenTag(iniHandle, L"[", L"]");
124 NativeAssert::Succeeded(hr, "Failed to set open tag settings on ini handle");
125
126 hr = IniSetValueStyle(iniHandle, NULL, L"=");
127 NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle");
128
129 hr = IniSetCommentStyle(iniHandle, L";");
130 NativeAssert::Succeeded(hr, "Failed to set comment style setting on ini handle");
131
132 return hr;
133 }
134
135 static HRESULT ScriptFormat(__inout INI_HANDLE iniHandle)
136 {
137 HRESULT hr = S_OK;
138
139 hr = IniSetValueStyle(iniHandle, L"setf ~", L" ");
140 NativeAssert::Succeeded(hr, "Failed to set value separator setting on ini handle");
141
142 return hr;
143 }
144
145 void TestReadThenWrite(LPWSTR wzIniFilePath, IniFormatParameters SetFormat, LPCWSTR wzContents)
146 {
147 HRESULT hr = S_OK;
148 INI_HANDLE iniHandle = NULL;
149 INI_HANDLE iniHandle2 = NULL;
150 INI_VALUE *rgValues = NULL;
151 DWORD cValues = 0;
152
153 try
154 {
155 hr = FileWrite(wzIniFilePath, 0, reinterpret_cast<LPCBYTE>(wzContents), lstrlenW(wzContents) * sizeof(WCHAR), NULL);
156 NativeAssert::Succeeded(hr, "Failed to write out INI file");
157
158 hr = IniInitialize(&iniHandle);
159 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
160
161 hr = SetFormat(iniHandle);
162 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
163
164 hr = IniParse(iniHandle, wzIniFilePath, NULL);
165 NativeAssert::Succeeded(hr, "Failed to parse INI file");
166
167 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
168 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
169
170 NativeAssert::Equal<DWORD>(5, cValues);
171
172 AssertValue(iniHandle, L"PlainValue", L"Blah");
173 AssertNoValue(iniHandle, L"PlainValue2");
174 AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo");
175 AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar");
176 AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha");
177 AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist");
178 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr");
179
180 hr = IniSetValue(iniHandle, L"PlainValue2", L"Blah2");
181 NativeAssert::Succeeded(hr, "Failed to set value in INI");
182
183 hr = IniSetValue(iniHandle, L"Section1\\CreatedValue", L"Woo");
184 NativeAssert::Succeeded(hr, "Failed to set value in INI");
185
186 hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arrmod");
187 NativeAssert::Succeeded(hr, "Failed to set value in INI");
188
189 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
190 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
191
192 NativeAssert::Equal<DWORD>(7, cValues);
193
194 AssertValue(iniHandle, L"PlainValue", L"Blah");
195 AssertValue(iniHandle, L"PlainValue2", L"Blah2");
196 AssertValue(iniHandle, L"Section1\\Section1ValueA", L"Foo");
197 AssertValue(iniHandle, L"Section1\\Section1ValueB", L"Bar");
198 AssertValue(iniHandle, L"Section2\\Section2ValueA", L"Cha");
199 AssertNoValue(iniHandle, L"Section1\\ValueDoesntExist");
200 AssertValue(iniHandle, L"Section1\\CreatedValue", L"Woo");
201 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arrmod");
202
203 // Try deleting a value as well
204 hr = IniSetValue(iniHandle, L"Section1\\Section1ValueB", NULL);
205 NativeAssert::Succeeded(hr, "Failed to kill value in INI");
206
207 hr = IniWriteFile(iniHandle, NULL, FILE_ENCODING_UNSPECIFIED);
208 NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk");
209
210 ReleaseNullIni(iniHandle);
211 // Now re-parse the INI we just wrote and make sure it matches the values we expect
212 hr = IniInitialize(&iniHandle2);
213 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
214
215 hr = SetFormat(iniHandle2);
216 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
217
218 hr = IniParse(iniHandle2, wzIniFilePath, NULL);
219 NativeAssert::Succeeded(hr, "Failed to parse INI file");
220
221 hr = IniGetValueList(iniHandle2, &rgValues, &cValues);
222 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
223
224 NativeAssert::Equal<DWORD>(6, cValues);
225
226 AssertValue(iniHandle2, L"PlainValue", L"Blah");
227 AssertValue(iniHandle2, L"PlainValue2", L"Blah2");
228 AssertValue(iniHandle2, L"Section1\\Section1ValueA", L"Foo");
229 AssertNoValue(iniHandle2, L"Section1\\Section1ValueB");
230 AssertValue(iniHandle2, L"Section2\\Section2ValueA", L"Cha");
231 AssertNoValue(iniHandle2, L"Section1\\ValueDoesntExist");
232 AssertValue(iniHandle2, L"Section1\\CreatedValue", L"Woo");
233 AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arrmod");
234 }
235 finally
236 {
237 ReleaseIni(iniHandle);
238 ReleaseIni(iniHandle2);
239 }
240 }
241
242 void TestWriteThenRead(LPWSTR wzIniFilePath, IniFormatParameters SetFormat)
243 {
244 HRESULT hr = S_OK;
245 INI_HANDLE iniHandle = NULL;
246 INI_HANDLE iniHandle2 = NULL;
247 INI_VALUE *rgValues = NULL;
248 DWORD cValues = 0;
249
250 try
251 {
252 hr = FileEnsureDelete(wzIniFilePath);
253 NativeAssert::Succeeded(hr, "Failed to ensure file is deleted");
254
255 hr = IniInitialize(&iniHandle);
256 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
257
258 hr = SetFormat(iniHandle);
259 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
260
261 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
262 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
263
264 NativeAssert::Equal<DWORD>(0, cValues);
265
266 hr = IniSetValue(iniHandle, L"Value1", L"BlahTypo");
267 NativeAssert::Succeeded(hr, "Failed to set value in INI");
268
269 hr = IniSetValue(iniHandle, L"Value2", L"Blah2");
270 NativeAssert::Succeeded(hr, "Failed to set value in INI");
271
272 hr = IniSetValue(iniHandle, L"Section1\\Value1", L"Section1Value1");
273 NativeAssert::Succeeded(hr, "Failed to set value in INI");
274
275 hr = IniSetValue(iniHandle, L"Section1\\Value2", L"Section1Value2");
276 NativeAssert::Succeeded(hr, "Failed to set value in INI");
277
278 hr = IniSetValue(iniHandle, L"Section2\\Value1", L"Section2Value1");
279 NativeAssert::Succeeded(hr, "Failed to set value in INI");
280
281 hr = IniSetValue(iniHandle, L"Section2\\Array[0]", L"Arr");
282 NativeAssert::Succeeded(hr, "Failed to set value in INI");
283
284 hr = IniSetValue(iniHandle, L"Value3", L"Blah3");
285 NativeAssert::Succeeded(hr, "Failed to set value in INI");
286
287 hr = IniSetValue(iniHandle, L"Value4", L"Blah4");
288 NativeAssert::Succeeded(hr, "Failed to set value in INI");
289
290 hr = IniSetValue(iniHandle, L"Value4", NULL);
291 NativeAssert::Succeeded(hr, "Failed to set value in INI");
292
293 hr = IniSetValue(iniHandle, L"Value1", L"Blah1");
294 NativeAssert::Succeeded(hr, "Failed to set value in INI");
295
296 hr = IniGetValueList(iniHandle, &rgValues, &cValues);
297 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
298
299 NativeAssert::Equal<DWORD>(8, cValues);
300
301 AssertValue(iniHandle, L"Value1", L"Blah1");
302 AssertValue(iniHandle, L"Value2", L"Blah2");
303 AssertValue(iniHandle, L"Value3", L"Blah3");
304 AssertNoValue(iniHandle, L"Value4");
305 AssertValue(iniHandle, L"Section1\\Value1", L"Section1Value1");
306 AssertValue(iniHandle, L"Section1\\Value2", L"Section1Value2");
307 AssertValue(iniHandle, L"Section2\\Value1", L"Section2Value1");
308 AssertValue(iniHandle, L"Section2\\Array[0]", L"Arr");
309
310 hr = IniWriteFile(iniHandle, wzIniFilePath, FILE_ENCODING_UNSPECIFIED);
311 NativeAssert::Succeeded(hr, "Failed to write ini file back out to disk");
312
313 ReleaseNullIni(iniHandle);
314 // Now re-parse the INI we just wrote and make sure it matches the values we expect
315 hr = IniInitialize(&iniHandle2);
316 NativeAssert::Succeeded(hr, "Failed to initialize INI object");
317
318 hr = SetFormat(iniHandle2);
319 NativeAssert::Succeeded(hr, "Failed to set parameters for INI file");
320
321 hr = IniParse(iniHandle2, wzIniFilePath, NULL);
322 NativeAssert::Succeeded(hr, "Failed to parse INI file");
323
324 hr = IniGetValueList(iniHandle2, &rgValues, &cValues);
325 NativeAssert::Succeeded(hr, "Failed to get list of values in INI");
326
327 NativeAssert::Equal<DWORD>(7, cValues);
328
329 AssertValue(iniHandle2, L"Value1", L"Blah1");
330 AssertValue(iniHandle2, L"Value2", L"Blah2");
331 AssertValue(iniHandle2, L"Value3", L"Blah3");
332 AssertNoValue(iniHandle2, L"Value4");
333 AssertValue(iniHandle2, L"Section1\\Value1", L"Section1Value1");
334 AssertValue(iniHandle2, L"Section1\\Value2", L"Section1Value2");
335 AssertValue(iniHandle2, L"Section2\\Value1", L"Section2Value1");
336 AssertValue(iniHandle2, L"Section2\\Array[0]", L"Arr");
337 }
338 finally
339 {
340 ReleaseIni(iniHandle);
341 ReleaseIni(iniHandle2);
342 }
343 }
344 };
345}
diff --git a/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp
new file mode 100644
index 00000000..09692bfb
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/MemUtilTest.cpp
@@ -0,0 +1,505 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 struct ArrayValue
12 {
13 DWORD dwNum;
14 void *pvNull1;
15 LPWSTR sczString;
16 void *pvNull2;
17 };
18
19 public ref class MemUtil
20 {
21 public:
22 [Fact]
23 void MemUtilAppendTest()
24 {
25 HRESULT hr = S_OK;
26 DWORD dwSize;
27 ArrayValue *rgValues = NULL;
28 DWORD cValues = 0;
29
30 DutilInitialize(&DutilTestTraceError);
31
32 try
33 {
34 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
35 NativeAssert::Succeeded(hr, "Failed to grow array size to 1");
36 ++cValues;
37 SetItem(rgValues + 0, 0);
38
39 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
40 NativeAssert::Succeeded(hr, "Failed to grow array size to 2");
41 ++cValues;
42 SetItem(rgValues + 1, 1);
43
44 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
45 NativeAssert::Succeeded(hr, "Failed to grow array size to 3");
46 ++cValues;
47 SetItem(rgValues + 2, 2);
48
49 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
50 NativeAssert::Succeeded(hr, "Failed to grow array size to 4");
51 ++cValues;
52 SetItem(rgValues + 3, 3);
53
54 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
55 NativeAssert::Succeeded(hr, "Failed to grow array size to 5");
56 ++cValues;
57 SetItem(rgValues + 4, 4);
58
59 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
60 NativeAssert::Succeeded(hr, "Failed to grow array size to 6");
61 ++cValues;
62 SetItem(rgValues + 5, 5);
63
64 // OK, we used growth size 5, so let's try ensuring we have space for 6 (5 + first item) items
65 // and make sure it doesn't grow since we already have enough space
66 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues, sizeof(ArrayValue), 5);
67 NativeAssert::Succeeded(hr, "Failed to ensure array size matches what it should already be");
68 dwSize = MemSize(rgValues);
69 if (dwSize != 6 * sizeof(ArrayValue))
70 {
71 hr = E_FAIL;
72 ExitOnFailure(hr, "MemEnsureArraySize is growing an array that is already big enough!");
73 }
74
75 for (DWORD i = 0; i < cValues; ++i)
76 {
77 CheckItem(rgValues + i, i);
78 }
79
80 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
81 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
82 ++cValues;
83 SetItem(rgValues + 6, 6);
84
85 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
86 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
87 ++cValues;
88 SetItem(rgValues + 7, 7);
89
90 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
91 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
92 ++cValues;
93 SetItem(rgValues + 8, 8);
94
95 for (DWORD i = 0; i < cValues; ++i)
96 {
97 CheckItem(rgValues + i, i);
98 }
99 }
100 finally
101 {
102 ReleaseMem(rgValues);
103 }
104
105 LExit:
106 DutilUninitialize();
107 }
108
109 [Fact]
110 void MemUtilInsertTest()
111 {
112 HRESULT hr = S_OK;
113 ArrayValue *rgValues = NULL;
114 DWORD cValues = 0;
115
116 DutilInitialize(&DutilTestTraceError);
117
118 try
119 {
120 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
121 NativeAssert::Succeeded(hr, "Failed to insert into beginning of empty array");
122 ++cValues;
123 CheckNullItem(rgValues + 0);
124 SetItem(rgValues + 0, 5);
125
126 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5);
127 NativeAssert::Succeeded(hr, "Failed to insert at end of array");
128 ++cValues;
129 CheckNullItem(rgValues + 1);
130 SetItem(rgValues + 1, 6);
131
132 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
133 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
134 ++cValues;
135 CheckNullItem(rgValues + 0);
136 SetItem(rgValues + 0, 4);
137
138 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
139 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
140 ++cValues;
141 CheckNullItem(rgValues + 0);
142 SetItem(rgValues + 0, 3);
143
144 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
145 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
146 ++cValues;
147 CheckNullItem(rgValues + 0);
148 SetItem(rgValues + 0, 1);
149
150 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 1, 1, cValues + 1, sizeof(ArrayValue), 5);
151 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
152 ++cValues;
153 CheckNullItem(rgValues + 1);
154 SetItem(rgValues + 1, 2);
155
156 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 0, 1, cValues + 1, sizeof(ArrayValue), 5);
157 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
158 ++cValues;
159 CheckNullItem(rgValues + 0);
160 SetItem(rgValues + 0, 0);
161
162 for (DWORD i = 0; i < cValues; ++i)
163 {
164 CheckItem(rgValues + i, i);
165 }
166
167 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), cValues + 1, sizeof(ArrayValue), 5);
168 NativeAssert::Succeeded(hr, "Failed to grow array size to 7");
169 ++cValues;
170 CheckNullItem(rgValues + 7);
171 SetItem(rgValues + 7, 7);
172
173 hr = MemInsertIntoArray(reinterpret_cast<LPVOID*>(&rgValues), 8, 1, cValues + 1, sizeof(ArrayValue), 5);
174 NativeAssert::Succeeded(hr, "Failed to insert into beginning of array");
175 ++cValues;
176 CheckNullItem(rgValues + 8);
177 SetItem(rgValues + 8, 8);
178
179 for (DWORD i = 0; i < cValues; ++i)
180 {
181 CheckItem(rgValues + i, i);
182 }
183 }
184 finally
185 {
186 ReleaseMem(rgValues);
187 DutilUninitialize();
188 }
189 }
190
191 [Fact]
192 void MemUtilRemovePreserveOrderTest()
193 {
194 HRESULT hr = S_OK;
195 ArrayValue *rgValues = NULL;
196 DWORD cValues = 0;
197
198 DutilInitialize(&DutilTestTraceError);
199
200 try
201 {
202 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
203 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
204
205 cValues = 10;
206 for (DWORD i = 0; i < cValues; ++i)
207 {
208 SetItem(rgValues + i, i);
209 }
210
211 // Remove last item
212 MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), TRUE);
213 --cValues;
214
215 for (DWORD i = 0; i < cValues; ++i)
216 {
217 CheckItem(rgValues + i, i);
218 }
219
220 // Remove last two items
221 MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), TRUE);
222 cValues -= 2;
223
224 for (DWORD i = 0; i < cValues; ++i)
225 {
226 CheckItem(rgValues + i, i);
227 }
228
229 // Remove first item
230 MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), TRUE);
231 --cValues;
232
233 for (DWORD i = 0; i < cValues; ++i)
234 {
235 CheckItem(rgValues + i, i + 1);
236 }
237
238
239 // Remove first two items
240 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE);
241 cValues -= 2;
242
243 for (DWORD i = 0; i < cValues; ++i)
244 {
245 CheckItem(rgValues + i, i + 3);
246 }
247
248 // Remove middle two items
249 MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), TRUE);
250 cValues -= 2;
251
252 CheckItem(rgValues, 3);
253 CheckItem(rgValues + 1, 6);
254
255 // Remove last 2 items to ensure we don't crash
256 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), TRUE);
257 cValues -= 2;
258 }
259 finally
260 {
261 ReleaseMem(rgValues);
262 DutilUninitialize();
263 }
264 }
265
266 [Fact]
267 void MemUtilRemoveFastTest()
268 {
269 HRESULT hr = S_OK;
270 ArrayValue *rgValues = NULL;
271 DWORD cValues = 0;
272
273 DutilInitialize(&DutilTestTraceError);
274
275 try
276 {
277 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
278 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
279
280 cValues = 10;
281 for (DWORD i = 0; i < cValues; ++i)
282 {
283 SetItem(rgValues + i, i);
284 }
285
286 // Remove last item
287 MemRemoveFromArray(rgValues, 9, 1, cValues, sizeof(ArrayValue), FALSE);
288 --cValues;
289
290 for (DWORD i = 0; i < cValues; ++i)
291 {
292 CheckItem(rgValues + i, i);
293 }
294
295 // Remove last two items
296 MemRemoveFromArray(rgValues, 7, 2, cValues, sizeof(ArrayValue), FALSE);
297 cValues -= 2;
298
299 for (DWORD i = 0; i < cValues; ++i)
300 {
301 CheckItem(rgValues + i, i);
302 }
303
304 // Remove first item
305 MemRemoveFromArray(rgValues, 0, 1, cValues, sizeof(ArrayValue), FALSE);
306 --cValues;
307
308 CheckItem(rgValues, 6);
309 CheckItem(rgValues + 1, 1);
310 CheckItem(rgValues + 2, 2);
311 CheckItem(rgValues + 3, 3);
312 CheckItem(rgValues + 4, 4);
313 CheckItem(rgValues + 5, 5);
314
315 // Remove first two items
316 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE);
317 cValues -= 2;
318
319 CheckItem(rgValues, 4);
320 CheckItem(rgValues + 1, 5);
321 CheckItem(rgValues + 2, 2);
322 CheckItem(rgValues + 3, 3);
323
324
325 // Remove middle two items
326 MemRemoveFromArray(rgValues, 1, 2, cValues, sizeof(ArrayValue), FALSE);
327 cValues -= 2;
328
329 CheckItem(rgValues, 4);
330 CheckItem(rgValues + 1, 3);
331
332 // Remove last 2 items to ensure we don't crash
333 MemRemoveFromArray(rgValues, 0, 2, cValues, sizeof(ArrayValue), FALSE);
334 cValues -= 2;
335 }
336 finally
337 {
338 ReleaseMem(rgValues);
339 DutilUninitialize();
340 }
341 }
342
343 [Fact]
344 void MemUtilSwapTest()
345 {
346 HRESULT hr = S_OK;
347 ArrayValue *rgValues = NULL;
348 DWORD cValues = 0;
349
350 DutilInitialize(&DutilTestTraceError);
351
352 try
353 {
354 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&rgValues), 10, sizeof(ArrayValue), 10);
355 NativeAssert::Succeeded(hr, "Failed to grow array size to 10");
356
357 cValues = 10;
358 for (DWORD i = 0; i < cValues; ++i)
359 {
360 SetItem(rgValues + i, i);
361 }
362
363 // Swap first two
364 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
365 --cValues;
366
367 CheckItem(rgValues, 1);
368 CheckItem(rgValues + 1, 0);
369 for (DWORD i = 2; i < cValues; ++i)
370 {
371 CheckItem(rgValues + i, i);
372 }
373
374 // Swap them back
375 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
376 --cValues;
377
378 for (DWORD i = 0; i < cValues; ++i)
379 {
380 CheckItem(rgValues + i, i);
381 }
382
383 // Swap first and last items (index 0 and 9)
384 MemArraySwapItems(rgValues, 0, 9, sizeof(ArrayValue));
385 --cValues;
386
387 CheckItem(rgValues, 9);
388 CheckItem(rgValues + 9, 0);
389 for (DWORD i = 1; i < cValues - 1; ++i)
390 {
391 CheckItem(rgValues + i, i);
392 }
393
394 // Swap index 1 and 8
395 MemArraySwapItems(rgValues, 1, 8, sizeof(ArrayValue));
396 --cValues;
397
398 CheckItem(rgValues, 9);
399 CheckItem(rgValues + 1, 8);
400 CheckItem(rgValues + 8, 1);
401 CheckItem(rgValues + 9, 0);
402 for (DWORD i = 2; i < cValues - 2; ++i)
403 {
404 CheckItem(rgValues + i, i);
405 }
406
407 // Swap index 2 and 7
408 MemArraySwapItems(rgValues, 2, 7, sizeof(ArrayValue));
409 --cValues;
410
411 CheckItem(rgValues, 9);
412 CheckItem(rgValues + 1, 8);
413 CheckItem(rgValues + 2, 7);
414 CheckItem(rgValues + 7, 2);
415 CheckItem(rgValues + 8, 1);
416 CheckItem(rgValues + 9, 0);
417 for (DWORD i = 3; i < cValues - 3; ++i)
418 {
419 CheckItem(rgValues + i, i);
420 }
421
422 // Swap index 0 and 1
423 MemArraySwapItems(rgValues, 0, 1, sizeof(ArrayValue));
424 --cValues;
425
426 CheckItem(rgValues, 8);
427 CheckItem(rgValues + 1, 9);
428 CheckItem(rgValues + 2, 7);
429 CheckItem(rgValues + 7, 2);
430 CheckItem(rgValues + 8, 1);
431 CheckItem(rgValues + 9, 0);
432 for (DWORD i = 3; i < cValues - 3; ++i)
433 {
434 CheckItem(rgValues + i, i);
435 }
436 }
437 finally
438 {
439 ReleaseMem(rgValues);
440 DutilUninitialize();
441 }
442 }
443
444 private:
445 void SetItem(ArrayValue *pValue, DWORD dwValue)
446 {
447 HRESULT hr = S_OK;
448 pValue->dwNum = dwValue;
449
450 hr = StrAllocFormatted(&pValue->sczString, L"%u", dwValue);
451 NativeAssert::Succeeded(hr, "Failed to allocate string");
452 }
453
454 void CheckItem(ArrayValue *pValue, DWORD dwValue)
455 {
456 HRESULT hr = S_OK;
457 LPWSTR sczTemp = NULL;
458
459 try
460 {
461 NativeAssert::Equal(dwValue, pValue->dwNum);
462
463 hr = StrAllocFormatted(&sczTemp, L"%u", dwValue);
464 NativeAssert::Succeeded(hr, "Failed to allocate temp string");
465
466 NativeAssert::StringEqual(sczTemp, pValue->sczString, TRUE);
467
468 if (pValue->pvNull1 || pValue->pvNull2)
469 {
470 hr = E_FAIL;
471 ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!");
472 }
473 }
474 finally
475 {
476 ReleaseStr(sczTemp);
477 }
478
479 LExit:
480 return;
481 }
482
483 void CheckNullItem(ArrayValue *pValue)
484 {
485 HRESULT hr = S_OK;
486
487 NativeAssert::Equal<DWORD>(0, pValue->dwNum);
488
489 if (pValue->sczString)
490 {
491 hr = E_FAIL;
492 ExitOnFailure(hr, "Item found isn't NULL!");
493 }
494
495 if (pValue->pvNull1 || pValue->pvNull2)
496 {
497 hr = E_FAIL;
498 ExitOnFailure(hr, "One of the expected NULL values wasn't NULL!");
499 }
500
501 LExit:
502 return;
503 }
504 };
505}
diff --git a/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp
new file mode 100644
index 00000000..273f2eb6
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/MonUtilTest.cpp
@@ -0,0 +1,487 @@
1// 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.
2
3#include "precomp.h"
4#undef RemoveDirectory
5
6using namespace System;
7using namespace System::Collections::Generic;
8using namespace System::Runtime::InteropServices;
9using namespace Xunit;
10using namespace WixBuildTools::TestSupport;
11
12namespace DutilTests
13{
14 const int PREWAIT = 20;
15 const int POSTWAIT = 480;
16 const int FULLWAIT = 500;
17 const int SILENCEPERIOD = 100;
18
19 struct RegKey
20 {
21 HRESULT hr;
22 HKEY hkRoot;
23 LPCWSTR wzSubKey;
24 REG_KEY_BITNESS kbKeyBitness;
25 BOOL fRecursive;
26 };
27 struct Directory
28 {
29 HRESULT hr;
30 LPCWSTR wzPath;
31 BOOL fRecursive;
32 };
33 struct Results
34 {
35 RegKey *rgRegKeys;
36 DWORD cRegKeys;
37 Directory *rgDirectories;
38 DWORD cDirectories;
39 };
40
41 public delegate void MonGeneralDelegate(HRESULT, LPVOID);
42
43 public delegate void MonDriveStatusDelegate(WCHAR, BOOL, LPVOID);
44
45 public delegate void MonDirectoryDelegate(HRESULT, LPCWSTR, BOOL, LPVOID, LPVOID);
46
47 public delegate void MonRegKeyDelegate(HRESULT, HKEY, LPCWSTR, REG_KEY_BITNESS, BOOL, LPVOID, LPVOID);
48
49 static void MonGeneral(
50 __in HRESULT /*hrResult*/,
51 __in_opt LPVOID /*pvContext*/
52 )
53 {
54 Assert::True(false);
55 }
56
57 static void MonDriveStatus(
58 __in WCHAR /*chDrive*/,
59 __in BOOL /*fArriving*/,
60 __in_opt LPVOID /*pvContext*/
61 )
62 {
63 }
64
65 static void MonDirectory(
66 __in HRESULT hrResult,
67 __in_z LPCWSTR wzPath,
68 __in_z BOOL fRecursive,
69 __in_opt LPVOID pvContext,
70 __in_opt LPVOID pvDirectoryContext
71 )
72 {
73 Assert::Equal(S_OK, hrResult);
74 Assert::Equal<DWORD_PTR>(0, reinterpret_cast<DWORD_PTR>(pvDirectoryContext));
75
76 HRESULT hr = S_OK;
77 Results *pResults = reinterpret_cast<Results *>(pvContext);
78
79 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pResults->rgDirectories), pResults->cDirectories + 1, sizeof(Directory), 5);
80 NativeAssert::ValidReturnCode(hr, S_OK);
81 ++pResults->cDirectories;
82
83 pResults->rgDirectories[pResults->cDirectories - 1].hr = hrResult;
84 pResults->rgDirectories[pResults->cDirectories - 1].wzPath = wzPath;
85 pResults->rgDirectories[pResults->cDirectories - 1].fRecursive = fRecursive;
86 }
87
88 static void MonRegKey(
89 __in HRESULT hrResult,
90 __in HKEY hkRoot,
91 __in_z LPCWSTR wzSubKey,
92 __in REG_KEY_BITNESS kbKeyBitness,
93 __in_z BOOL fRecursive,
94 __in_opt LPVOID pvContext,
95 __in_opt LPVOID pvRegKeyContext
96 )
97 {
98 Assert::Equal<HRESULT>(S_OK, hrResult);
99 Assert::Equal<DWORD_PTR>(0, reinterpret_cast<DWORD_PTR>(pvRegKeyContext));
100
101 HRESULT hr = S_OK;
102 Results *pResults = reinterpret_cast<Results *>(pvContext);
103
104 hr = MemEnsureArraySize(reinterpret_cast<LPVOID*>(&pResults->rgRegKeys), pResults->cRegKeys + 1, sizeof(RegKey), 5);
105 NativeAssert::ValidReturnCode(hr, S_OK);
106 ++pResults->cRegKeys;
107
108 pResults->rgRegKeys[pResults->cRegKeys - 1].hr = hrResult;
109 pResults->rgRegKeys[pResults->cRegKeys - 1].hkRoot = hkRoot;
110 pResults->rgRegKeys[pResults->cRegKeys - 1].wzSubKey = wzSubKey;
111 pResults->rgRegKeys[pResults->cRegKeys - 1].kbKeyBitness = kbKeyBitness;
112 pResults->rgRegKeys[pResults->cRegKeys - 1].fRecursive = fRecursive;
113 }
114
115 public ref class MonUtil
116 {
117 public:
118 void ClearResults(Results *pResults)
119 {
120 ReleaseNullMem(pResults->rgDirectories);
121 pResults->cDirectories = 0;
122 ReleaseNullMem(pResults->rgRegKeys);
123 pResults->cRegKeys = 0;
124 }
125
126 void RemoveDirectory(LPCWSTR wzPath)
127 {
128 DWORD dwRetryCount = 0;
129 const DWORD c_dwMaxRetryCount = 100;
130 const DWORD c_dwRetryInterval = 50;
131
132 HRESULT hr = DirEnsureDelete(wzPath, TRUE, TRUE);
133
134 // Monitoring a directory opens a handle to that directory, which means delete requests for that directory will succeed
135 // (and deletion will be "pending" until our monitor handle is closed)
136 // but deletion of the directory containing that directory cannot complete until the handle is closed. This means DirEnsureDelete()
137 // can sometimes encounter HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) failures, which just means it needs to retry a bit later
138 // (after the waiter thread wakes up, it will release the handle)
139 while (HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) == hr && c_dwMaxRetryCount > dwRetryCount)
140 {
141 ::Sleep(c_dwRetryInterval);
142 ++dwRetryCount;
143 hr = DirEnsureDelete(wzPath, TRUE, TRUE);
144 }
145
146 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
147 }
148
149 void TestDirectory(MON_HANDLE handle, Results *pResults)
150 {
151 HRESULT hr = S_OK;
152 LPWSTR sczShallowPath = NULL;
153 LPWSTR sczParentPath = NULL;
154 LPWSTR sczDeepPath = NULL;
155 LPWSTR sczChildPath = NULL;
156 LPWSTR sczChildFilePath = NULL;
157
158 try
159 {
160 hr = PathExpand(&sczShallowPath, L"%TEMP%\\MonUtilTest\\", PATH_EXPAND_ENVIRONMENT);
161 NativeAssert::ValidReturnCode(hr, S_OK);
162
163 hr = PathExpand(&sczParentPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\", PATH_EXPAND_ENVIRONMENT);
164 NativeAssert::ValidReturnCode(hr, S_OK);
165
166 hr = PathExpand(&sczDeepPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\", PATH_EXPAND_ENVIRONMENT);
167 NativeAssert::ValidReturnCode(hr, S_OK);
168
169 hr = PathExpand(&sczChildPath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\", PATH_EXPAND_ENVIRONMENT);
170 NativeAssert::ValidReturnCode(hr, S_OK);
171
172 hr = PathExpand(&sczChildFilePath, L"%TEMP%\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\file.txt", PATH_EXPAND_ENVIRONMENT);
173 NativeAssert::ValidReturnCode(hr, S_OK);
174
175 RemoveDirectory(sczShallowPath);
176
177 hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL);
178 NativeAssert::ValidReturnCode(hr, S_OK);
179
180 hr = DirEnsureExists(sczParentPath, NULL);
181 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
182 // Make sure creating the parent directory does nothing, even after silence period
183 ::Sleep(FULLWAIT);
184 Assert::Equal<DWORD>(0, pResults->cDirectories);
185
186 // Now create the target path, no notification until after the silence period
187 hr = DirEnsureExists(sczDeepPath, NULL);
188 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
189 ::Sleep(PREWAIT);
190 Assert::Equal<DWORD>(0, pResults->cDirectories);
191
192 // Now after the full silence period, it should have triggered
193 ::Sleep(POSTWAIT);
194 Assert::Equal<DWORD>(1, pResults->cDirectories);
195 NativeAssert::ValidReturnCode(pResults->rgDirectories[0].hr, S_OK);
196
197 // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists.
198 RemoveDirectory(sczShallowPath);
199
200 ::Sleep(FULLWAIT);
201 Assert::Equal<DWORD>(2, pResults->cDirectories);
202 NativeAssert::ValidReturnCode(pResults->rgDirectories[1].hr, S_OK);
203
204 // Create the parent directory again, still should be nothing even after full silence period
205 hr = DirEnsureExists(sczParentPath, NULL);
206 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
207 ::Sleep(FULLWAIT);
208 Assert::Equal<DWORD>(2, pResults->cDirectories);
209
210 hr = DirEnsureExists(sczChildPath, NULL);
211 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
212 ::Sleep(PREWAIT);
213 Assert::Equal<DWORD>(2, pResults->cDirectories);
214
215 ::Sleep(POSTWAIT);
216 Assert::Equal<DWORD>(3, pResults->cDirectories);
217 NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK);
218
219 // Write a file to a deep child subfolder, and make sure it's detected
220 hr = FileFromString(sczChildFilePath, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM);
221 NativeAssert::ValidReturnCode(hr, S_OK);
222 ::Sleep(PREWAIT);
223 Assert::Equal<DWORD>(3, pResults->cDirectories);
224
225 ::Sleep(POSTWAIT);
226 Assert::Equal<DWORD>(4, pResults->cDirectories);
227 NativeAssert::ValidReturnCode(pResults->rgDirectories[2].hr, S_OK);
228
229 RemoveDirectory(sczParentPath);
230
231 ::Sleep(FULLWAIT);
232 Assert::Equal<DWORD>(5, pResults->cDirectories);
233 NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK);
234
235 // Now remove the directory from the list of things to monitor, and confirm changes are no longer tracked
236 hr = MonRemoveDirectory(handle, sczDeepPath, TRUE);
237 NativeAssert::ValidReturnCode(hr, S_OK);
238 ::Sleep(PREWAIT);
239
240 hr = DirEnsureExists(sczDeepPath, NULL);
241 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
242 ::Sleep(FULLWAIT);
243 Assert::Equal<DWORD>(5, pResults->cDirectories);
244 NativeAssert::ValidReturnCode(pResults->rgDirectories[3].hr, S_OK);
245
246 // Finally, add it back so we can test multiple things to monitor at once
247 hr = MonAddDirectory(handle, sczDeepPath, TRUE, SILENCEPERIOD, NULL);
248 NativeAssert::ValidReturnCode(hr, S_OK);
249 }
250 finally
251 {
252 ReleaseStr(sczShallowPath);
253 ReleaseStr(sczDeepPath);
254 ReleaseStr(sczParentPath);
255 }
256 }
257
258 void TestRegKey(MON_HANDLE handle, Results *pResults)
259 {
260 HRESULT hr = S_OK;
261 LPCWSTR wzShallowRegKey = L"Software\\MonUtilTest\\";
262 LPCWSTR wzParentRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\";
263 LPCWSTR wzDeepRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\";
264 LPCWSTR wzChildRegKey = L"Software\\MonUtilTest\\sub\\folder\\that\\might\\not\\exist\\some\\sub\\folder\\";
265 HKEY hk = NULL;
266
267 try
268 {
269 hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE);
270 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
271
272 hr = MonAddRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE, SILENCEPERIOD, NULL);
273 NativeAssert::ValidReturnCode(hr, S_OK);
274
275 hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
276 ReleaseRegKey(hk);
277 // Make sure creating the parent key does nothing, even after silence period
278 ::Sleep(FULLWAIT);
279 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
280 Assert::Equal<DWORD>(0, pResults->cRegKeys);
281
282 // Now create the target path, no notification until after the silence period
283 hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
284 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
285 ReleaseRegKey(hk);
286 ::Sleep(PREWAIT);
287 Assert::Equal<DWORD>(0, pResults->cRegKeys);
288
289 // Now after the full silence period, it should have triggered
290 ::Sleep(POSTWAIT);
291 Assert::Equal<DWORD>(1, pResults->cRegKeys);
292 NativeAssert::ValidReturnCode(pResults->rgRegKeys[0].hr, S_OK);
293
294 // Now delete the directory, along with a ton of parents. This verifies MonUtil will keep watching the closest parent that still exists.
295 hr = RegDelete(HKEY_CURRENT_USER, wzShallowRegKey, REG_KEY_32BIT, TRUE);
296 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE, E_PATHNOTFOUND);
297 ::Sleep(PREWAIT);
298 Assert::Equal<DWORD>(1, pResults->cRegKeys);
299
300 ::Sleep(FULLWAIT);
301 Assert::Equal<DWORD>(2, pResults->cRegKeys);
302 NativeAssert::ValidReturnCode(pResults->rgRegKeys[1].hr, S_OK);
303
304 // Create the parent directory again, still should be nothing even after full silence period
305 hr = RegCreate(HKEY_CURRENT_USER, wzParentRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
306 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
307 ReleaseRegKey(hk);
308 ::Sleep(FULLWAIT);
309 Assert::Equal<DWORD>(2, pResults->cRegKeys);
310
311 hr = RegCreate(HKEY_CURRENT_USER, wzChildRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
312 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
313 ::Sleep(PREWAIT);
314 Assert::Equal<DWORD>(2, pResults->cRegKeys);
315
316 ::Sleep(FULLWAIT);
317 Assert::Equal<DWORD>(3, pResults->cRegKeys);
318 NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK);
319
320 // Write a registry value to some deep child subkey, and make sure it's detected
321 hr = RegWriteString(hk, L"valuename", L"testvalue");
322 NativeAssert::ValidReturnCode(hr, S_OK);
323 ReleaseRegKey(hk);
324 ::Sleep(PREWAIT);
325 Assert::Equal<DWORD>(3, pResults->cRegKeys);
326
327 ::Sleep(FULLWAIT);
328 Assert::Equal<DWORD>(4, pResults->cRegKeys);
329 NativeAssert::ValidReturnCode(pResults->rgRegKeys[2].hr, S_OK);
330
331 hr = RegDelete(HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_32BIT, TRUE);
332 NativeAssert::ValidReturnCode(hr, S_OK);
333
334 ::Sleep(FULLWAIT);
335 Assert::Equal<DWORD>(5, pResults->cRegKeys);
336
337 // Now remove the regkey from the list of things to monitor, and confirm changes are no longer tracked
338 hr = MonRemoveRegKey(handle, HKEY_CURRENT_USER, wzDeepRegKey, REG_KEY_DEFAULT, TRUE);
339 NativeAssert::ValidReturnCode(hr, S_OK);
340
341 hr = RegCreate(HKEY_CURRENT_USER, wzDeepRegKey, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hk);
342 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
343 ReleaseRegKey(hk);
344 ::Sleep(FULLWAIT);
345 Assert::Equal<DWORD>(5, pResults->cRegKeys);
346 }
347 finally
348 {
349 ReleaseRegKey(hk);
350 }
351 }
352
353 void TestMoreThan64(MON_HANDLE handle, Results *pResults)
354 {
355 HRESULT hr = S_OK;
356 LPWSTR sczBaseDir = NULL;
357 LPWSTR sczDir = NULL;
358 LPWSTR sczFile = NULL;
359
360 try
361 {
362 hr = PathExpand(&sczBaseDir, L"%TEMP%\\ScalabilityTest\\", PATH_EXPAND_ENVIRONMENT);
363 NativeAssert::ValidReturnCode(hr, S_OK);
364
365 for (DWORD i = 0; i < 200; ++i)
366 {
367 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
368 NativeAssert::ValidReturnCode(hr, S_OK);
369
370 hr = DirEnsureExists(sczDir, NULL);
371 NativeAssert::ValidReturnCode(hr, S_OK, S_FALSE);
372
373 hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL);
374 NativeAssert::ValidReturnCode(hr, S_OK);
375 }
376
377 hr = PathConcat(sczDir, L"file.txt", &sczFile);
378 NativeAssert::ValidReturnCode(hr, S_OK);
379
380 hr = FileFromString(sczFile, 0, L"contents", FILE_ENCODING_UTF16_WITH_BOM);
381 NativeAssert::ValidReturnCode(hr, S_OK);
382
383 ::Sleep(FULLWAIT);
384 Assert::Equal<DWORD>(1, pResults->cDirectories);
385
386 for (DWORD i = 0; i < 199; ++i)
387 {
388 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
389 NativeAssert::ValidReturnCode(hr, S_OK);
390
391 hr = MonRemoveDirectory(handle, sczDir, FALSE);
392 NativeAssert::ValidReturnCode(hr, S_OK);
393 }
394 ::Sleep(FULLWAIT);
395
396 hr = FileFromString(sczFile, 0, L"contents2", FILE_ENCODING_UTF16_WITH_BOM);
397 NativeAssert::ValidReturnCode(hr, S_OK);
398
399 ::Sleep(FULLWAIT);
400 Assert::Equal<DWORD>(2, pResults->cDirectories);
401
402 for (DWORD i = 0; i < 199; ++i)
403 {
404 hr = StrAllocFormatted(&sczDir, L"%ls%u\\", sczBaseDir, i);
405 NativeAssert::ValidReturnCode(hr, S_OK);
406
407 hr = MonAddDirectory(handle, sczDir, FALSE, SILENCEPERIOD, NULL);
408 NativeAssert::ValidReturnCode(hr, S_OK);
409 }
410 ::Sleep(FULLWAIT);
411
412 hr = FileFromString(sczFile, 0, L"contents3", FILE_ENCODING_UTF16_WITH_BOM);
413 NativeAssert::ValidReturnCode(hr, S_OK);
414
415 ::Sleep(FULLWAIT);
416 Assert::Equal<DWORD>(3, pResults->cDirectories);
417 }
418 finally
419 {
420 ReleaseStr(sczBaseDir);
421 ReleaseStr(sczDir);
422 ReleaseStr(sczFile);
423 }
424 }
425
426 [Fact(Skip = "Test demonstrates failure")]
427 void MonUtilTest()
428 {
429 HRESULT hr = S_OK;
430 MON_HANDLE handle = NULL;
431 List<GCHandle>^ gcHandles = gcnew List<GCHandle>();
432 Results *pResults = (Results *)MemAlloc(sizeof(Results), TRUE);
433 Assert::True(NULL != pResults);
434
435 try
436 {
437 // These ensure the function pointers we send point to this thread's appdomain, which helps with assembly binding when running tests within msbuild
438 MonGeneralDelegate^ fpMonGeneral = gcnew MonGeneralDelegate(MonGeneral);
439 GCHandle gchMonGeneral = GCHandle::Alloc(fpMonGeneral);
440 gcHandles->Add(gchMonGeneral);
441 IntPtr ipMonGeneral = Marshal::GetFunctionPointerForDelegate(fpMonGeneral);
442
443 MonDriveStatusDelegate^ fpMonDriveStatus = gcnew MonDriveStatusDelegate(MonDriveStatus);
444 GCHandle gchMonDriveStatus = GCHandle::Alloc(fpMonDriveStatus);
445 gcHandles->Add(gchMonDriveStatus);
446 IntPtr ipMonDriveStatus = Marshal::GetFunctionPointerForDelegate(fpMonDriveStatus);
447
448 MonDirectoryDelegate^ fpMonDirectory = gcnew MonDirectoryDelegate(MonDirectory);
449 GCHandle gchMonDirectory = GCHandle::Alloc(fpMonDirectory);
450 gcHandles->Add(gchMonDirectory);
451 IntPtr ipMonDirectory = Marshal::GetFunctionPointerForDelegate(fpMonDirectory);
452
453 MonRegKeyDelegate^ fpMonRegKey = gcnew MonRegKeyDelegate(MonRegKey);
454 GCHandle gchMonRegKey = GCHandle::Alloc(fpMonRegKey);
455 gcHandles->Add(gchMonRegKey);
456 IntPtr ipMonRegKey = Marshal::GetFunctionPointerForDelegate(fpMonRegKey);
457
458 // "Silence period" is 100 ms
459 hr = MonCreate(&handle, static_cast<PFN_MONGENERAL>(ipMonGeneral.ToPointer()), static_cast<PFN_MONDRIVESTATUS>(ipMonDriveStatus.ToPointer()), static_cast<PFN_MONDIRECTORY>(ipMonDirectory.ToPointer()), static_cast<PFN_MONREGKEY>(ipMonRegKey.ToPointer()), pResults);
460 NativeAssert::ValidReturnCode(hr, S_OK);
461
462 hr = RegInitialize();
463 NativeAssert::ValidReturnCode(hr, S_OK);
464
465 TestDirectory(handle, pResults);
466 ClearResults(pResults);
467 TestRegKey(handle, pResults);
468 ClearResults(pResults);
469 TestMoreThan64(handle, pResults);
470 ClearResults(pResults);
471 }
472 finally
473 {
474 ReleaseMon(handle);
475
476 for each (GCHandle gcHandle in gcHandles)
477 {
478 gcHandle.Free();
479 }
480
481 ReleaseMem(pResults->rgDirectories);
482 ReleaseMem(pResults->rgRegKeys);
483 ReleaseMem(pResults);
484 }
485 }
486 };
487}
diff --git a/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
new file mode 100644
index 00000000..5a1f06fd
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/PathUtilTest.cpp
@@ -0,0 +1,80 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class PathUtil
12 {
13 public:
14 [Fact]
15 void PathGetHierarchyArrayTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR *rgsczPaths = NULL;
19 UINT cPaths = 0;
20
21 try
22 {
23 hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\a.txt", &rgsczPaths, &cPaths);
24 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular file path");
25 Assert::Equal<DWORD>(5, cPaths);
26 NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]);
27 NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]);
28 NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]);
29 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]);
30 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\a.txt", rgsczPaths[4]);
31 ReleaseNullStrArray(rgsczPaths, cPaths);
32
33 hr = PathGetHierarchyArray(L"c:\\foo\\bar\\bas\\", &rgsczPaths, &cPaths);
34 NativeAssert::Succeeded(hr, "Failed to get parent directories array for regular directory path");
35 Assert::Equal<DWORD>(4, cPaths);
36 NativeAssert::StringEqual(L"c:\\", rgsczPaths[0]);
37 NativeAssert::StringEqual(L"c:\\foo\\", rgsczPaths[1]);
38 NativeAssert::StringEqual(L"c:\\foo\\bar\\", rgsczPaths[2]);
39 NativeAssert::StringEqual(L"c:\\foo\\bar\\bas\\", rgsczPaths[3]);
40 ReleaseNullStrArray(rgsczPaths, cPaths);
41
42 hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\file.txt", &rgsczPaths, &cPaths);
43 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC file path");
44 Assert::Equal<DWORD>(3, cPaths);
45 NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]);
46 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]);
47 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\file.txt", rgsczPaths[2]);
48 ReleaseNullStrArray(rgsczPaths, cPaths);
49
50 hr = PathGetHierarchyArray(L"\\\\server\\share\\subdir\\", &rgsczPaths, &cPaths);
51 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
52 Assert::Equal<DWORD>(2, cPaths);
53 NativeAssert::StringEqual(L"\\\\server\\share\\", rgsczPaths[0]);
54 NativeAssert::StringEqual(L"\\\\server\\share\\subdir\\", rgsczPaths[1]);
55 ReleaseNullStrArray(rgsczPaths, cPaths);
56
57 hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\ValueName", &rgsczPaths, &cPaths);
58 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
59 Assert::Equal<DWORD>(4, cPaths);
60 NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]);
61 NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]);
62 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
63 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\ValueName", rgsczPaths[3]);
64 ReleaseNullStrArray(rgsczPaths, cPaths);
65
66 hr = PathGetHierarchyArray(L"Software\\Microsoft\\Windows\\", &rgsczPaths, &cPaths);
67 NativeAssert::Succeeded(hr, "Failed to get parent directories array for UNC directory path");
68 Assert::Equal<DWORD>(3, cPaths);
69 NativeAssert::StringEqual(L"Software\\", rgsczPaths[0]);
70 NativeAssert::StringEqual(L"Software\\Microsoft\\", rgsczPaths[1]);
71 NativeAssert::StringEqual(L"Software\\Microsoft\\Windows\\", rgsczPaths[2]);
72 ReleaseNullStrArray(rgsczPaths, cPaths);
73 }
74 finally
75 {
76 ReleaseStrArray(rgsczPaths, cPaths);
77 }
78 }
79 };
80}
diff --git a/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp
new file mode 100644
index 00000000..75b9222a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/SceUtilTest.cpp
@@ -0,0 +1,488 @@
1// 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.
2
3#include "precomp.h"
4
5#include <sqlce_oledb.h>
6#include <sceutil.h>
7
8using namespace System;
9using namespace Xunit;
10using namespace WixTest;
11
12#define ASSIGN_INDEX_STRUCT(a, b, c) {a.wzName = c; a.rgColumns = b; a.cColumns = countof(b);};
13
14namespace DutilTests
15{
16 enum TABLES
17 {
18 TABLE_A,
19 TABLE_COUNT
20 };
21
22 enum TABLE_A_COLUMNS
23 {
24 TABLE_A_KEY,
25 TABLE_A_BINARY,
26 TABLE_A_DWORD,
27 TABLE_A_QWORD,
28 TABLE_A_BOOL,
29 TABLE_A_STRING,
30 TABLE_A_DWORD_NULLABLE,
31 TABLE_A_INITIAL_COLUMNS,
32
33 TABLE_A_EXTRA_STRING = TABLE_A_INITIAL_COLUMNS,
34 TABLE_A_FINAL_COLUMNS
35 };
36
37 struct TableARowValue
38 {
39 DWORD dwAutoGenKey;
40
41 BYTE *pbBinary;
42 DWORD cBinary;
43
44 DWORD dw;
45 DWORD64 qw;
46 BOOL f;
47 LPWSTR scz;
48
49 BOOL fNullablePresent;
50 DWORD dwNullable;
51
52 BOOL fSchemaV2;
53 LPWSTR sczExtra;
54 };
55
56 public ref class SceUtil
57 {
58 public:
59 void ReleaseSceSchema(SCE_DATABASE_SCHEMA *pdsSchema)
60 {
61 DWORD dwTable;
62
63 for (dwTable = 0; dwTable < pdsSchema->cTables; ++dwTable)
64 {
65 ReleaseNullMem(pdsSchema->rgTables[dwTable].rgColumns);
66 ReleaseNullMem(pdsSchema->rgTables[dwTable].rgIndexes);
67 }
68
69 ReleaseMem(pdsSchema->rgTables);
70
71 return;
72 }
73
74 void SetupSchema(SCE_DATABASE_SCHEMA *pSchema, BOOL fIncludeExtended)
75 {
76 pSchema->cTables = TABLE_COUNT;
77 pSchema->rgTables = static_cast<SCE_TABLE_SCHEMA*>(MemAlloc(TABLE_COUNT * sizeof(SCE_TABLE_SCHEMA), TRUE));
78 NativeAssert::True(pSchema->rgTables != NULL);
79
80 pSchema->rgTables[TABLE_A].wzName = L"TableA";
81 pSchema->rgTables[TABLE_A].cColumns = fIncludeExtended ? TABLE_A_FINAL_COLUMNS : TABLE_A_INITIAL_COLUMNS;
82 pSchema->rgTables[TABLE_A].cIndexes = 2;
83
84 for (DWORD i = 0; i < pSchema->cTables; ++i)
85 {
86 pSchema->rgTables[i].rgColumns = static_cast<SCE_COLUMN_SCHEMA*>(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cColumns, TRUE));
87 NativeAssert::True(pSchema->rgTables[i].rgColumns != NULL);
88
89 pSchema->rgTables[i].rgIndexes = static_cast<SCE_INDEX_SCHEMA*>(MemAlloc(sizeof(SCE_COLUMN_SCHEMA) * pSchema->rgTables[i].cIndexes, TRUE));
90 NativeAssert::True(pSchema->rgTables[i].rgIndexes != NULL);
91 }
92
93 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].wzName = L"Key";
94 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].dbtColumnType = DBTYPE_I4;
95 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fPrimaryKey = TRUE;
96 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_KEY].fAutoIncrement = TRUE;
97 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].wzName = L"Binary";
98 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BINARY].dbtColumnType = DBTYPE_BYTES;
99 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].wzName = L"Dword";
100 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD].dbtColumnType = DBTYPE_I4;
101 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].wzName = L"Qword";
102 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_QWORD].dbtColumnType = DBTYPE_I8;
103 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].wzName = L"Bool";
104 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_BOOL].dbtColumnType = DBTYPE_BOOL;
105 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].wzName = L"String";
106 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_STRING].dbtColumnType = DBTYPE_WSTR;
107 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].wzName = L"Nullable";
108 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].dbtColumnType = DBTYPE_I4;
109 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_DWORD_NULLABLE].fNullable = TRUE;
110
111 if (fIncludeExtended)
112 {
113 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].wzName = L"ExtraString";
114 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].dbtColumnType = DBTYPE_WSTR;
115 pSchema->rgTables[TABLE_A].rgColumns[TABLE_A_EXTRA_STRING].fNullable = TRUE;
116 }
117
118 static DWORD rgdwTableA_Index1[] = { TABLE_A_DWORD, TABLE_A_STRING, TABLE_A_QWORD };
119 static DWORD rgdwTableA_Index2[] = { TABLE_A_DWORD, TABLE_A_STRING };
120
121 ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[0], rgdwTableA_Index1, L"Dword_String_Qword");
122 ASSIGN_INDEX_STRUCT(pSchema->rgTables[TABLE_A].rgIndexes[1], rgdwTableA_Index2, L"Dword_String");
123 }
124
125 void SetStructValues(TableARowValue *pValue, BYTE *pbBinary, DWORD cBinary, DWORD dw, DWORD64 qw, BOOL f, LPWSTR scz, DWORD *pdw, LPWSTR sczExtra)
126 {
127 pValue->pbBinary = pbBinary;
128 pValue->cBinary = cBinary;
129 pValue->dw = dw;
130 pValue->qw = qw;
131 pValue->f = f;
132 pValue->scz = scz;
133
134 if (pdw)
135 {
136 pValue->fNullablePresent = TRUE;
137 pValue->dwNullable = *pdw;
138 }
139 else
140 {
141 pValue->fNullablePresent = FALSE;
142 }
143
144 if (sczExtra)
145 {
146 pValue->fSchemaV2 = TRUE;
147 pValue->sczExtra = sczExtra;
148 }
149 else
150 {
151 pValue->fSchemaV2 = FALSE;
152 }
153 }
154
155 void AssertStructValuesSame(TableARowValue *pValueExpected, TableARowValue *pValueOther)
156 {
157 NativeAssert::Equal(pValueExpected->cBinary, pValueOther->cBinary);
158 NativeAssert::True(0 == memcmp(pValueExpected->pbBinary, pValueOther->pbBinary, pValueOther->cBinary));
159
160 NativeAssert::Equal(pValueExpected->dw, pValueOther->dw);
161 NativeAssert::Equal(pValueExpected->qw, pValueOther->qw);
162 NativeAssert::Equal(pValueExpected->f, pValueOther->f);
163 NativeAssert::True(0 == wcscmp(pValueExpected->scz, pValueOther->scz));
164
165 NativeAssert::Equal(pValueExpected->fNullablePresent, pValueOther->fNullablePresent);
166 if (pValueExpected->fNullablePresent)
167 {
168 NativeAssert::Equal(pValueExpected->dwNullable, pValueOther->dwNullable);
169 }
170
171 NativeAssert::Equal(pValueExpected->fSchemaV2, pValueOther->fSchemaV2);
172 if (pValueExpected->fSchemaV2)
173 {
174 NativeAssert::True(0 == wcscmp(pValueExpected->sczExtra, pValueOther->sczExtra));
175 }
176 }
177
178 void InsertRow(SCE_DATABASE *pDatabase, TableARowValue *pValue, BOOL fRollback)
179 {
180 HRESULT hr = S_OK;
181 SCE_ROW_HANDLE sceRow = NULL;
182
183 hr = SceBeginTransaction(pDatabase);
184 NativeAssert::Succeeded(hr, "Failed to begin transaction");
185
186 hr = ScePrepareInsert(pDatabase, TABLE_A, &sceRow);
187 NativeAssert::Succeeded(hr, "Failed to prepare to insert row");
188
189 hr = SceSetColumnBinary(sceRow, TABLE_A_BINARY, pValue->pbBinary, pValue->cBinary);
190 NativeAssert::Succeeded(hr, "Failed to set binary value");
191
192 hr = SceSetColumnDword(sceRow, TABLE_A_DWORD, pValue->dw);
193 NativeAssert::Succeeded(hr, "Failed to set dword value");
194
195 hr = SceSetColumnQword(sceRow, TABLE_A_QWORD, pValue->qw);
196 NativeAssert::Succeeded(hr, "Failed to set qword value");
197
198 hr = SceSetColumnBool(sceRow, TABLE_A_BOOL, pValue->f);
199 NativeAssert::Succeeded(hr, "Failed to set bool value");
200
201 hr = SceSetColumnString(sceRow, TABLE_A_STRING, pValue->scz);
202 NativeAssert::Succeeded(hr, "Failed to set string value");
203
204 if (pValue->fNullablePresent)
205 {
206 hr = SceSetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, pValue->dwNullable);
207 NativeAssert::Succeeded(hr, "Failed to set dword value");
208 }
209 else
210 {
211 hr = SceSetColumnNull(sceRow, TABLE_A_DWORD_NULLABLE);
212 NativeAssert::Succeeded(hr, "Failed to set null value");
213 }
214
215 if (pValue->fSchemaV2)
216 {
217 hr = SceSetColumnString(sceRow, TABLE_A_EXTRA_STRING, pValue->sczExtra);
218 NativeAssert::Succeeded(hr, "Failed to set extra string value");
219 }
220
221 hr = SceFinishUpdate(sceRow);
222 NativeAssert::Succeeded(hr, "Failed to finish insert");
223
224 if (fRollback)
225 {
226 hr = SceRollbackTransaction(pDatabase);
227 NativeAssert::Succeeded(hr, "Failed to rollback transaction");
228 }
229 else
230 {
231 hr = SceCommitTransaction(pDatabase);
232 NativeAssert::Succeeded(hr, "Failed to commit transaction");
233
234 hr = SceGetColumnDword(sceRow, TABLE_A_KEY, &pValue->dwAutoGenKey);
235 NativeAssert::Succeeded(hr, "Failed to get autogen key after insert");
236
237 NativeAssert::True(pValue->dwAutoGenKey != 0);
238 }
239
240 ReleaseSceRow(sceRow);
241 }
242
243 void VerifyRow(TableARowValue *pExpectedValue, SCE_ROW_HANDLE sceRow)
244 {
245 HRESULT hr = S_OK;
246 TableARowValue value = {};
247
248 hr = SceGetColumnBinary(sceRow, TABLE_A_BINARY, &value.pbBinary, &value.cBinary);
249 NativeAssert::Succeeded(hr, "Failed to get binary value from result row");
250
251 hr = SceGetColumnDword(sceRow, TABLE_A_DWORD, &value.dw);
252 NativeAssert::Succeeded(hr, "Failed to get dword value from result row");
253
254 hr = SceGetColumnQword(sceRow, TABLE_A_QWORD, &value.qw);
255 NativeAssert::Succeeded(hr, "Failed to get qword value from result row");
256
257 hr = SceGetColumnBool(sceRow, TABLE_A_BOOL, &value.f);
258 NativeAssert::Succeeded(hr, "Failed to get bool value from result row");
259
260 hr = SceGetColumnString(sceRow, TABLE_A_STRING, &value.scz);
261 NativeAssert::Succeeded(hr, "Failed to get string value from result row");
262
263 hr = SceGetColumnDword(sceRow, TABLE_A_DWORD_NULLABLE, &value.dwNullable);
264 if (hr == E_NOTFOUND)
265 {
266 value.fNullablePresent = FALSE;
267 hr = S_OK;
268 }
269 else
270 {
271 NativeAssert::Succeeded(hr, "Failed to get string value from result row");
272 value.fNullablePresent = TRUE;
273 }
274
275 if (pExpectedValue->fSchemaV2)
276 {
277 value.fSchemaV2 = TRUE;
278 hr = SceGetColumnString(sceRow, TABLE_A_EXTRA_STRING, &value.sczExtra);
279 NativeAssert::Succeeded(hr, "Failed to get extra string value from result row");
280 }
281
282 AssertStructValuesSame(pExpectedValue, &value);
283
284 ReleaseNullMem(value.pbBinary);
285 ReleaseNullStr(value.scz);
286 }
287
288 void VerifyQuery(TableARowValue **rgExpectedValues, DWORD cExpectedValues, SCE_QUERY_RESULTS_HANDLE queryResults)
289 {
290 HRESULT hr = S_OK;
291 SCE_ROW_HANDLE sceRow = NULL;
292
293 for (DWORD i = 0; i < cExpectedValues; ++i)
294 {
295 hr = SceGetNextResultRow(queryResults, &sceRow);
296 NativeAssert::Succeeded(hr, "Failed to get next result row");
297
298 VerifyRow(rgExpectedValues[i], sceRow);
299 ReleaseNullSceRow(sceRow);
300 }
301
302 // No more results
303 NativeAssert::True(NULL == queryResults || FAILED(SceGetNextResultRow(queryResults, &sceRow)));
304 }
305
306 void TestIndex(SCE_DATABASE *pDatabase)
307 {
308 HRESULT hr = S_OK;
309 BYTE binary1[50] = { 0x80, 0x70 };
310 BYTE binary2[40] = { 0x90, 0xAB };
311 BYTE binary3[40] = { 0x85, 0x88 };
312 DWORD dwValue1 = 0x55555555, dwValue2 = 0x88888888;
313 TableARowValue value1 = {}, value2 = {}, value3 = {}, value4 = {}, value5 = {};
314 SCE_QUERY_HANDLE query = NULL;
315 SCE_QUERY_RESULTS_HANDLE results = NULL;
316
317 SetStructValues(&value1, static_cast<BYTE *>(binary1), sizeof(binary1), 3, 1, TRUE, L"zzz", &dwValue1, NULL);
318 SetStructValues(&value2, static_cast<BYTE *>(binary2), sizeof(binary2), 3, 2, TRUE, L"yyy", &dwValue2, NULL);
319 SetStructValues(&value3, static_cast<BYTE *>(binary3), sizeof(binary3), 3, 3, TRUE, L"xxx", NULL, NULL);
320 SetStructValues(&value4, static_cast<BYTE *>(binary2), sizeof(binary2), 4, 4, TRUE, L"xyz", &dwValue2, NULL);
321 SetStructValues(&value5, static_cast<BYTE *>(binary3), sizeof(binary3), 3, 1, TRUE, L"yyy", &dwValue2, NULL);
322
323 // Rollback an insert to confirm the insert doesn't happen and database can still be interacted with normally afterwards
324 InsertRow(pDatabase, &value1, TRUE);
325
326 InsertRow(pDatabase, &value1, FALSE);
327 InsertRow(pDatabase, &value2, FALSE);
328 InsertRow(pDatabase, &value3, FALSE);
329 InsertRow(pDatabase, &value4, FALSE);
330 InsertRow(pDatabase, &value5, FALSE);
331
332 NativeAssert::True(value1.dwAutoGenKey != value2.dwAutoGenKey);
333
334 // Test setting 1 column
335 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
336 NativeAssert::Succeeded(hr, "Failed to begin query");
337
338 hr = SceSetQueryColumnDword(query, 3);
339 NativeAssert::Succeeded(hr, "Failed to set query column dword");
340
341 hr = SceRunQueryRange(&query, &results);
342 NativeAssert::Succeeded(hr, "Failed to run query");
343 NativeAssert::True(query == NULL);
344
345 TableARowValue *sortedAfterQuery1[] = { &value3, &value5, &value2, &value1 };
346 VerifyQuery(sortedAfterQuery1, _countof(sortedAfterQuery1), results);
347 ReleaseNullSceQueryResults(results);
348
349 // Test setting 2 columns, third column is unspecified so results are sorted by it
350 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
351 NativeAssert::Succeeded(hr, "Failed to begin query");
352
353 hr = SceSetQueryColumnDword(query, 3);
354 NativeAssert::Succeeded(hr, "Failed to set query column dword");
355
356 hr = SceSetQueryColumnString(query, L"yyy");
357 NativeAssert::Succeeded(hr, "Failed to set query column dword");
358
359 hr = SceRunQueryRange(&query, &results);
360 NativeAssert::Succeeded(hr, "Failed to run query");
361 NativeAssert::True(query == NULL);
362
363 TableARowValue *sortedAfterQuery2[] = { &value5, &value2 };
364 VerifyQuery(sortedAfterQuery2, _countof(sortedAfterQuery2), results);
365 ReleaseNullSceQueryResults(results);
366
367 // Test setting 2 columns, third column of index is unspecified so results are sorted by it
368 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
369 NativeAssert::Succeeded(hr, "Failed to begin query");
370
371 hr = SceSetQueryColumnDword(query, 3);
372 NativeAssert::Succeeded(hr, "Failed to set query column dword");
373
374 hr = SceSetQueryColumnString(query, L"yyy");
375 NativeAssert::Succeeded(hr, "Failed to set query column dword");
376
377 hr = SceRunQueryRange(&query, &results);
378 NativeAssert::Succeeded(hr, "Failed to run query");
379 NativeAssert::True(query == NULL);
380
381 TableARowValue *sortedAfterQuery3[] = { &value5, &value2 };
382 VerifyQuery(sortedAfterQuery3, _countof(sortedAfterQuery3), results);
383 ReleaseNullSceQueryResults(results);
384
385 // Test setting 2 columns in a different (2 column) index, so there is no 3rd column in index to sort by
386 hr = SceBeginQuery(pDatabase, TABLE_A, 1, &query);
387 NativeAssert::Succeeded(hr, "Failed to begin query");
388
389 hr = SceSetQueryColumnDword(query, 3);
390 NativeAssert::Succeeded(hr, "Failed to set query column dword");
391
392 hr = SceSetQueryColumnString(query, L"yyy");
393 NativeAssert::Succeeded(hr, "Failed to set query column dword");
394
395 hr = SceRunQueryRange(&query, &results);
396 NativeAssert::Succeeded(hr, "Failed to run query");
397 NativeAssert::True(query == NULL);
398
399 TableARowValue *sortedAfterQuery4[] = { &value2, &value5 };
400 VerifyQuery(sortedAfterQuery4, _countof(sortedAfterQuery4), results);
401 ReleaseNullSceQueryResults(results);
402 }
403
404 void TestReadWriteSchemaV2(SCE_DATABASE *pDatabase)
405 {
406 HRESULT hr = S_OK;
407 BYTE binary1[40] = { 0x55, 0x44 };
408 DWORD dwValue1 = 58;
409 TableARowValue value1 = {};
410 SCE_QUERY_HANDLE query = NULL;
411 SCE_ROW_HANDLE row = NULL;
412
413 SetStructValues(&value1, static_cast<BYTE *>(binary1), sizeof(binary1), 5, 1, TRUE, L"zzz", &dwValue1, L"newextrastring");
414
415 InsertRow(pDatabase, &value1, FALSE);
416
417 // Test setting 1 column
418 hr = SceBeginQuery(pDatabase, TABLE_A, 0, &query);
419 NativeAssert::Succeeded(hr, "Failed to begin query");
420
421 hr = SceSetQueryColumnDword(query, 5);
422 NativeAssert::Succeeded(hr, "Failed to set query column dword");
423
424 hr = SceRunQueryExact(&query, &row);
425 NativeAssert::Succeeded(hr, "Failed to run query exact");
426
427 VerifyRow(&value1, row);
428 }
429
430 [Fact]
431 void SceUtilTest()
432 {
433 HRESULT hr = S_OK;
434 BOOL fComInitialized = FALSE;
435 LPWSTR sczDbPath = NULL;
436 SCE_DATABASE *pDatabase = NULL;
437 SCE_DATABASE_SCHEMA schema1 = {};
438 SCE_DATABASE_SCHEMA schema2 = {};
439
440 try
441 {
442 hr = ::CoInitialize(0);
443 NativeAssert::Succeeded(hr, "Failed to initialize COM");
444 fComInitialized = TRUE;
445
446 SetupSchema(&schema1, FALSE);
447 SetupSchema(&schema2, TRUE);
448
449 hr = PathExpand(&sczDbPath, L"%TEMP%\\SceUtilTest\\UnitTest.sdf", PATH_EXPAND_ENVIRONMENT);
450 NativeAssert::Succeeded(hr, "Failed to get path to test database");
451
452 FileEnsureDelete(sczDbPath);
453
454 hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema1, &pDatabase);
455 NativeAssert::Succeeded(hr, "Failed to ensure database schema");
456
457 TestIndex(pDatabase);
458
459 hr = SceCloseDatabase(pDatabase);
460 pDatabase = NULL;
461 NativeAssert::Succeeded(hr, "Failed to close database");
462
463 // Add column to schema
464 hr = SceEnsureDatabase(sczDbPath, L"sqlceoledb40.dll", L"Test", 1, &schema2, &pDatabase);
465 NativeAssert::Succeeded(hr, "Failed to ensure database schema");
466
467 TestReadWriteSchemaV2(pDatabase);
468 }
469 finally
470 {
471 ReleaseSceSchema(&schema1);
472 ReleaseSceSchema(&schema2);
473
474 if (NULL != pDatabase)
475 {
476 hr = SceCloseDatabase(pDatabase);
477 NativeAssert::Succeeded(hr, "Failed to close database");
478 }
479 ReleaseStr(sczDbPath);
480
481 if (fComInitialized)
482 {
483 ::CoUninitialize();
484 }
485 }
486 }
487 };
488}
diff --git a/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp
new file mode 100644
index 00000000..94fee280
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/StrUtilTest.cpp
@@ -0,0 +1,192 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class StrUtil
12 {
13 public:
14 [Fact]
15 void StrUtilFormattedTest()
16 {
17 HRESULT hr = S_OK;
18 LPWSTR sczText = NULL;
19
20 try
21 {
22 hr = StrAllocFormatted(&sczText, L"%hs - %ls - %u", "ansi string", L"unicode string", 1234);
23 NativeAssert::Succeeded(hr, "Failed to format string.");
24 NativeAssert::StringEqual(L"ansi string - unicode string - 1234", sczText);
25
26 ReleaseNullStr(sczText);
27
28 hr = StrAllocString(&sczText, L"repeat", 0);
29 NativeAssert::Succeeded(hr, "Failed to allocate string.");
30
31 hr = StrAllocFormatted(&sczText, L"%ls and %ls", sczText, sczText);
32 NativeAssert::Succeeded(hr, "Failed to format string unto itself.");
33 NativeAssert::StringEqual(L"repeat and repeat", sczText);
34 }
35 finally
36 {
37 ReleaseStr(sczText);
38 }
39 }
40
41 [Fact]
42 void StrUtilTrimTest()
43 {
44 TestTrim(L"", L"");
45 TestTrim(L"Blah", L"Blah");
46 TestTrim(L"\t\t\tBlah", L"Blah");
47 TestTrim(L"\t Blah ", L"Blah");
48 TestTrim(L"Blah ", L"Blah");
49 TestTrim(L"\t Spaces \t Between \t", L"Spaces \t Between");
50 TestTrim(L" \t\t\t ", L"");
51
52 TestTrimAnsi("", "");
53 TestTrimAnsi("Blah", "Blah");
54 TestTrimAnsi("\t\t\tBlah", "Blah");
55 TestTrimAnsi(" Blah ", "Blah");
56 TestTrimAnsi("Blah ", "Blah");
57 TestTrimAnsi("\t Spaces \t Between \t", "Spaces \t Between");
58 TestTrimAnsi(" \t\t\t ", "");
59 }
60
61 [Fact]
62 void StrUtilConvertTest()
63 {
64 char a[] = { 'a', 'b', 'C', 'd', '\0', '\0' };
65
66 TestStrAllocStringAnsi(a, 5, L"abCd");
67 TestStrAllocStringAnsi(a, 4, L"abCd");
68 TestStrAllocStringAnsi(a, 3, L"abC");
69 TestStrAllocStringAnsi(a, 2, L"ab");
70 TestStrAllocStringAnsi(a, 1, L"a");
71 TestStrAllocStringAnsi(a, 0, L"abCd");
72
73 wchar_t b[] = { L'a', L'b', L'C', L'd', L'\0', L'\0' };
74
75 TestStrAnsiAllocString(b, 5, "abCd");
76 TestStrAnsiAllocString(b, 4, "abCd");
77 TestStrAnsiAllocString(b, 3, "abC");
78 TestStrAnsiAllocString(b, 2, "ab");
79 TestStrAnsiAllocString(b, 1, "a");
80 TestStrAnsiAllocString(b, 0, "abCd");
81 }
82
83 private:
84 void TestTrim(LPCWSTR wzInput, LPCWSTR wzExpectedResult)
85 {
86 HRESULT hr = S_OK;
87 LPWSTR sczOutput = NULL;
88
89 DutilInitialize(&DutilTestTraceError);
90
91 try
92 {
93 hr = StrTrimWhitespace(&sczOutput, wzInput);
94 NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: {0}", wzInput);
95
96 if (0 != wcscmp(wzExpectedResult, sczOutput))
97 {
98 hr = E_FAIL;
99 ExitOnFailure(hr, "Trimmed string \"%ls\", expected result \"%ls\", actual result \"%ls\"", wzInput, wzExpectedResult, sczOutput);
100 }
101 }
102 finally
103 {
104 ReleaseStr(sczOutput);
105 }
106
107 LExit:
108 DutilUninitialize();
109 }
110
111 void TestTrimAnsi(LPCSTR szInput, LPCSTR szExpectedResult)
112 {
113 HRESULT hr = S_OK;
114 LPSTR sczOutput = NULL;
115
116 DutilInitialize(&DutilTestTraceError);
117
118 try
119 {
120 hr = StrAnsiTrimWhitespace(&sczOutput, szInput);
121 NativeAssert::Succeeded(hr, "Failed to trim whitespace from string: \"{0}\"", szInput);
122
123 if (0 != strcmp(szExpectedResult, sczOutput))
124 {
125 hr = E_FAIL;
126 ExitOnFailure(hr, "Trimmed string \"%hs\", expected result \"%hs\", actual result \"%hs\"", szInput, szExpectedResult, sczOutput);
127 }
128 }
129 finally
130 {
131 ReleaseStr(sczOutput);
132 }
133
134 LExit:
135 DutilUninitialize();
136 }
137
138 void TestStrAllocStringAnsi(LPCSTR szSource, DWORD cchSource, LPCWSTR wzExpectedResult)
139 {
140 HRESULT hr = S_OK;
141 LPWSTR sczOutput = NULL;
142
143 DutilInitialize(&DutilTestTraceError);
144
145 try
146 {
147 hr = StrAllocStringAnsi(&sczOutput, szSource, cchSource, CP_UTF8);
148 NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", szSource);
149
150 if (0 != wcscmp(sczOutput, wzExpectedResult))
151 {
152 hr = E_FAIL;
153 ExitOnFailure(hr, "String doesn't match, expected result \"%ls\", actual result \"%ls\"", wzExpectedResult, sczOutput);
154 }
155 }
156 finally
157 {
158 ReleaseStr(sczOutput);
159 }
160
161 LExit:
162 DutilUninitialize();
163 }
164
165 void TestStrAnsiAllocString(LPWSTR wzSource, DWORD cchSource, LPCSTR szExpectedResult)
166 {
167 HRESULT hr = S_OK;
168 LPSTR sczOutput = NULL;
169
170 DutilInitialize(&DutilTestTraceError);
171
172 try
173 {
174 hr = StrAnsiAllocString(&sczOutput, wzSource, cchSource, CP_UTF8);
175 NativeAssert::Succeeded(hr, "Failed to call StrAllocStringAnsi on string: \"{0}\"", wzSource);
176
177 if (0 != strcmp(sczOutput, szExpectedResult))
178 {
179 hr = E_FAIL;
180 ExitOnFailure(hr, "String doesn't match, expected result \"%hs\", actual result \"%hs\"", szExpectedResult, sczOutput);
181 }
182 }
183 finally
184 {
185 ReleaseStr(sczOutput);
186 }
187
188 LExit:
189 DutilUninitialize();
190 }
191 };
192}
diff --git a/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml b/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml
new file mode 100644
index 00000000..d9f961fe
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/TestData/ApupUtilTests/FeedBv2.0.xml
@@ -0,0 +1,68 @@
1<?xml version='1.0' ?>
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. -->
3
4
5<feed xmlns="http://www.w3.org/2005/Atom" xmlns:as="http://appsyndication.org/2006/appsyn">
6 <title type="text">BundleB v2.0</title>
7 <subtitle type="text">Bundle Subtitle.</subtitle>
8 <as:application type="application/exe">1116353B-7C6E-4C29-BFA1-D4A972CD421D</as:application>
9 <updated>2014-07-14T12:39:00.000Z</updated>
10 <id>http://localhost:9999/wix4/BundleB/feed</id>
11 <link rel="self" type="application/atom+xml" href="http://localhost:9999/wix4/BundleB/feed"/>
12 <generator version="0.1">manual build</generator>
13 <entry>
14 <title>Bundle v2.0</title>
15 <id>v2.0</id>
16 <author>
17 <name>Bundle_Author</name>
18 <uri>http://mycompany.com/software</uri>
19 <email>Bundle_Author@mycompany.com</email>
20 </author>
21 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
22 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/2.0/BundleB.exe" length="0" type="application/octet-stream"/>
23 <content type="html">
24 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
25 &lt;li&gt;Updated release.&lt;/li&gt;
26 &lt;/ul&gt;
27 </content>
28 <as:version>2.0.0.0</as:version>
29 <updated>2014-11-10T12:39:00.000Z</updated>
30 </entry>
31 <entry>
32 <title>Bundle v1.0</title>
33 <id>v1.0</id>
34 <author>
35 <name>Bundle_Author</name>
36 <uri>http://mycompany.com/software</uri>
37 <email>Bundle_Author@mycompany.com</email>
38 </author>
39 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
40 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/1.0/BundleB.exe" length="0" type="application/octet-stream"/>
41 <content type="html">
42 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
43 &lt;li&gt;Initial release.&lt;/li&gt;
44 &lt;/ul&gt;
45 </content>
46 <as:upgrade version="1.0.0.0-preview" />
47 <as:version>1.0.0.0</as:version>
48 <updated>2014-11-09T12:39:00.000Z</updated>
49 </entry>
50 <entry>
51 <title>Bundle v1.0-preview</title>
52 <id>v1.0-preview</id>
53 <author>
54 <name>Bundle_Author</name>
55 <uri>http://mycompany.com/software</uri>
56 <email>Bundle_Author@mycompany.com</email>
57 </author>
58 <link rel="alternate" href="http://www.mycompany.com/content/view/software"/>
59 <link rel="enclosure" href="http://localhost:9999/wix4/BundleB/1.0-preview/BundleB.exe" length="10000" type="application/octet-stream"/>
60 <content type="html">
61 &lt;p&gt;Change list:&lt;/p&gt;&lt;ul&gt;
62 &lt;li&gt;Initial release.&lt;/li&gt;
63 &lt;/ul&gt;
64 </content>
65 <as:version>1.0.0.0</as:version>
66 <updated>2014-11-09T12:39:00.000Z</updated>
67 </entry>
68</feed>
diff --git a/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc b/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc
new file mode 100644
index 00000000..14cebe1a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/UnitTest.rc
@@ -0,0 +1,6 @@
1// 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.
2
3#define VER_APP
4#define VER_ORIGINAL_FILENAME "UnitTest.dll"
5#define VER_INTERNAL_NAME "setup"
6#define VER_FILE_DESCRIPTION "WiX Toolset Bootstrapper unit tests"
diff --git a/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp b/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp
new file mode 100644
index 00000000..b3bf87a2
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/UriUtilTest.cpp
@@ -0,0 +1,98 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace System::Text;
7using namespace System::Collections::Generic;
8using namespace Xunit;
9
10namespace CfgTests
11{
12 public ref class UriUtil
13 {
14 public:
15 [Fact]
16 void UriProtocolTest()
17 {
18 HRESULT hr = S_OK;
19
20 DutilInitialize(&DutilTestTraceError);
21
22 LPCWSTR uri = L"https://localhost/";
23 URI_PROTOCOL uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
24 hr = UriProtocol(uri, &uriProtocol);
25 ExitOnFailure(hr, "Failed to determine UriProtocol");
26 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
27
28 uri = L"HTTPS://localhost/";
29 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
30 hr = UriProtocol(uri, &uriProtocol);
31 ExitOnFailure(hr, "Failed to determine UriProtocol");
32 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
33
34 uri = L"HtTpS://localhost/";
35 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
36 hr = UriProtocol(uri, &uriProtocol);
37 ExitOnFailure(hr, "Failed to determine UriProtocol");
38 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTPS, (int)uriProtocol);
39
40 uri = L"HTTP://localhost/";
41 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
42 hr = UriProtocol(uri, &uriProtocol);
43 ExitOnFailure(hr, "Failed to determine UriProtocol");
44 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
45
46 uri = L"http://localhost/";
47 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
48 hr = UriProtocol(uri, &uriProtocol);
49 ExitOnFailure(hr, "Failed to determine UriProtocol");
50 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
51
52 uri = L"HtTp://localhost/";
53 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
54 hr = UriProtocol(uri, &uriProtocol);
55 ExitOnFailure(hr, "Failed to determine UriProtocol");
56 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_HTTP, (int)uriProtocol);
57
58 uri = L"file://localhost/";
59 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
60 hr = UriProtocol(uri, &uriProtocol);
61 ExitOnFailure(hr, "Failed to determine UriProtocol");
62 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
63
64 uri = L"FILE://localhost/";
65 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
66 hr = UriProtocol(uri, &uriProtocol);
67 ExitOnFailure(hr, "Failed to determine UriProtocol");
68 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
69
70 uri = L"FiLe://localhost/";
71 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
72 hr = UriProtocol(uri, &uriProtocol);
73 ExitOnFailure(hr, "Failed to determine UriProtocol");
74 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FILE, (int)uriProtocol);
75
76 uri = L"FTP://localhost/";
77 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
78 hr = UriProtocol(uri, &uriProtocol);
79 ExitOnFailure(hr, "Failed to determine UriProtocol");
80 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
81
82 uri = L"ftp://localhost/";
83 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
84 hr = UriProtocol(uri, &uriProtocol);
85 ExitOnFailure(hr, "Failed to determine UriProtocol");
86 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
87
88 uri = L"FtP://localhost/";
89 uriProtocol = URI_PROTOCOL::URI_PROTOCOL_UNKNOWN;
90 hr = UriProtocol(uri, &uriProtocol);
91 ExitOnFailure(hr, "Failed to determine UriProtocol");
92 Assert::Equal((int)URI_PROTOCOL::URI_PROTOCOL_FTP, (int)uriProtocol);
93
94 LExit:
95 DutilUninitialize();
96 }
97 };
98}
diff --git a/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp b/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp
new file mode 100644
index 00000000..8f24ad1a
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/VerUtilTests.cpp
@@ -0,0 +1,933 @@
1// 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.
2
3#include "precomp.h"
4
5using namespace System;
6using namespace Xunit;
7using namespace WixBuildTools::TestSupport;
8
9namespace DutilTests
10{
11 public ref class VerUtil
12 {
13 public:
14 [Fact]
15 void VerCompareVersionsTreatsMissingRevisionAsZero()
16 {
17 HRESULT hr = S_OK;
18 VERUTIL_VERSION* pVersion1 = NULL;
19 VERUTIL_VERSION* pVersion2 = NULL;
20 VERUTIL_VERSION* pVersion3 = NULL;
21 LPCWSTR wzVersion1 = L"1.2.3.4";
22 LPCWSTR wzVersion2 = L"1.2.3";
23 LPCWSTR wzVersion3 = L"1.2.3.0";
24
25 try
26 {
27 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
28 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
29
30 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
31 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
32
33 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
34 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
35
36 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
37 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
38 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
39 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
40 Assert::Equal<DWORD>(4, pVersion1->dwRevision);
41 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
42 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
43 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
44
45 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
46 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
47 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
48 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
49 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
50 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
51 Assert::Equal<DWORD>(5, pVersion2->cchMetadataOffset);
52 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
53
54 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
55 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
56 Assert::Equal<DWORD>(2, pVersion3->dwMinor);
57 Assert::Equal<DWORD>(3, pVersion3->dwPatch);
58 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
59 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
60 Assert::Equal<DWORD>(7, pVersion3->cchMetadataOffset);
61 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
62
63 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
64 TestVerutilCompareParsedVersions(pVersion3, pVersion2, 0);
65 }
66 finally
67 {
68 ReleaseVerutilVersion(pVersion1);
69 ReleaseVerutilVersion(pVersion2);
70 ReleaseVerutilVersion(pVersion3);
71 }
72 }
73
74 [Fact]
75 void VerCompareVersionsTreatsNumericReleaseLabelsAsNumbers()
76 {
77 HRESULT hr = S_OK;
78 VERUTIL_VERSION* pVersion1 = NULL;
79 VERUTIL_VERSION* pVersion2 = NULL;
80 LPCWSTR wzVersion1 = L"1.0-2.0";
81 LPCWSTR wzVersion2 = L"1.0-19";
82
83 try
84 {
85 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
86 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
87
88 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
89 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
90
91 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
92 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
93 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
94 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
95 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
96 Assert::Equal<DWORD>(2, pVersion1->cReleaseLabels);
97
98 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[0].fNumeric);
99 Assert::Equal<DWORD>(2, pVersion1->rgReleaseLabels[0].dwValue);
100 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
101 Assert::Equal<DWORD>(4, pVersion1->rgReleaseLabels[0].cchLabelOffset);
102
103 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[1].fNumeric);
104 Assert::Equal<DWORD>(0, pVersion1->rgReleaseLabels[1].dwValue);
105 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].cchLabel);
106 Assert::Equal<DWORD>(6, pVersion1->rgReleaseLabels[1].cchLabelOffset);
107
108 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
109 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
110
111 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
112 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
113 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
114 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
115 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
116 Assert::Equal<DWORD>(1, pVersion2->cReleaseLabels);
117
118 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[0].fNumeric);
119 Assert::Equal<DWORD>(19, pVersion2->rgReleaseLabels[0].dwValue);
120 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].cchLabel);
121 Assert::Equal<DWORD>(4, pVersion2->rgReleaseLabels[0].cchLabelOffset);
122
123 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
124 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
125
126 TestVerutilCompareParsedVersions(pVersion1, pVersion2, -1);
127 }
128 finally
129 {
130 ReleaseVerutilVersion(pVersion1);
131 ReleaseVerutilVersion(pVersion2);
132 }
133 }
134
135 [Fact]
136 void VerCompareVersionsHandlesNormallyInvalidVersions()
137 {
138 HRESULT hr = S_OK;
139 VERUTIL_VERSION* pVersion1 = NULL;
140 VERUTIL_VERSION* pVersion2 = NULL;
141 VERUTIL_VERSION* pVersion3 = NULL;
142 VERUTIL_VERSION* pVersion4 = NULL;
143 VERUTIL_VERSION* pVersion5 = NULL;
144 VERUTIL_VERSION* pVersion6 = NULL;
145 LPCWSTR wzVersion1 = L"10.-4.0";
146 LPCWSTR wzVersion2 = L"10.-2.0";
147 LPCWSTR wzVersion3 = L"0";
148 LPCWSTR wzVersion4 = L"";
149 LPCWSTR wzVersion5 = L"10-2";
150 LPCWSTR wzVersion6 = L"10-4.@";
151
152 try
153 {
154 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
155 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
156
157 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
158 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
159
160 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
161 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
162
163 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
164 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
165
166 hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5);
167 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5);
168
169 hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6);
170 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6);
171
172 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
173 Assert::Equal<DWORD>(10, pVersion1->dwMajor);
174 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
175 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
176 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
177 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
178 Assert::Equal<DWORD>(3, pVersion1->cchMetadataOffset);
179 Assert::Equal<BOOL>(TRUE, pVersion1->fInvalid);
180
181 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
182 Assert::Equal<DWORD>(10, pVersion2->dwMajor);
183 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
184 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
185 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
186 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
187 Assert::Equal<DWORD>(3, pVersion2->cchMetadataOffset);
188 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
189
190 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
191 Assert::Equal<DWORD>(0, pVersion3->dwMajor);
192 Assert::Equal<DWORD>(0, pVersion3->dwMinor);
193 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
194 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
195 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
196 Assert::Equal<DWORD>(1, pVersion3->cchMetadataOffset);
197 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
198
199 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
200 Assert::Equal<DWORD>(0, pVersion4->dwMajor);
201 Assert::Equal<DWORD>(0, pVersion4->dwMinor);
202 Assert::Equal<DWORD>(0, pVersion4->dwPatch);
203 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
204 Assert::Equal<DWORD>(0, pVersion4->cReleaseLabels);
205 Assert::Equal<DWORD>(0, pVersion4->cchMetadataOffset);
206 Assert::Equal<BOOL>(TRUE, pVersion4->fInvalid);
207
208 NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion);
209 Assert::Equal<DWORD>(10, pVersion5->dwMajor);
210 Assert::Equal<DWORD>(0, pVersion5->dwMinor);
211 Assert::Equal<DWORD>(0, pVersion5->dwPatch);
212 Assert::Equal<DWORD>(0, pVersion5->dwRevision);
213 Assert::Equal<DWORD>(1, pVersion5->cReleaseLabels);
214
215 Assert::Equal<BOOL>(TRUE, pVersion5->rgReleaseLabels[0].fNumeric);
216 Assert::Equal<DWORD>(2, pVersion5->rgReleaseLabels[0].dwValue);
217 Assert::Equal<DWORD>(1, pVersion5->rgReleaseLabels[0].cchLabel);
218 Assert::Equal<DWORD>(3, pVersion5->rgReleaseLabels[0].cchLabelOffset);
219
220 Assert::Equal<DWORD>(4, pVersion5->cchMetadataOffset);
221 Assert::Equal<BOOL>(FALSE, pVersion5->fInvalid);
222
223 NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion);
224 Assert::Equal<DWORD>(10, pVersion6->dwMajor);
225 Assert::Equal<DWORD>(0, pVersion6->dwMinor);
226 Assert::Equal<DWORD>(0, pVersion6->dwPatch);
227 Assert::Equal<DWORD>(0, pVersion6->dwRevision);
228 Assert::Equal<DWORD>(1, pVersion6->cReleaseLabels);
229
230 Assert::Equal<BOOL>(TRUE, pVersion6->rgReleaseLabels[0].fNumeric);
231 Assert::Equal<DWORD>(4, pVersion6->rgReleaseLabels[0].dwValue);
232 Assert::Equal<DWORD>(1, pVersion6->rgReleaseLabels[0].cchLabel);
233 Assert::Equal<DWORD>(3, pVersion6->rgReleaseLabels[0].cchLabelOffset);
234
235 Assert::Equal<DWORD>(5, pVersion6->cchMetadataOffset);
236 Assert::Equal<BOOL>(TRUE, pVersion6->fInvalid);
237
238 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
239 TestVerutilCompareParsedVersions(pVersion3, pVersion4, 1);
240 TestVerutilCompareParsedVersions(pVersion5, pVersion6, -1);
241 }
242 finally
243 {
244 ReleaseVerutilVersion(pVersion1);
245 ReleaseVerutilVersion(pVersion2);
246 ReleaseVerutilVersion(pVersion3);
247 ReleaseVerutilVersion(pVersion4);
248 ReleaseVerutilVersion(pVersion5);
249 ReleaseVerutilVersion(pVersion6);
250 }
251 }
252
253 [Fact]
254 void VerCompareVersionsTreatsHyphenAsVersionSeparator()
255 {
256 HRESULT hr = S_OK;
257 VERUTIL_VERSION* pVersion1 = NULL;
258 VERUTIL_VERSION* pVersion2 = NULL;
259 VERUTIL_VERSION* pVersion3 = NULL;
260 LPCWSTR wzVersion1 = L"0.0.1-a";
261 LPCWSTR wzVersion2 = L"0-2";
262 LPCWSTR wzVersion3 = L"1-2";
263
264 try
265 {
266 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
267 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
268
269 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
270 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
271
272 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
273 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
274
275 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
276 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
277 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
278 Assert::Equal<DWORD>(1, pVersion1->dwPatch);
279 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
280 Assert::Equal<DWORD>(1, pVersion1->cReleaseLabels);
281
282 Assert::Equal<BOOL>(FALSE, pVersion1->rgReleaseLabels[0].fNumeric);
283 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
284 Assert::Equal<DWORD>(6, pVersion1->rgReleaseLabels[0].cchLabelOffset);
285
286 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
287 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
288
289 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
290 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
291 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
292 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
293 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
294 Assert::Equal<DWORD>(1, pVersion2->cReleaseLabels);
295
296 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[0].fNumeric);
297 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].dwValue);
298 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[0].cchLabel);
299 Assert::Equal<DWORD>(2, pVersion2->rgReleaseLabels[0].cchLabelOffset);
300
301 Assert::Equal<DWORD>(3, pVersion2->cchMetadataOffset);
302 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
303
304 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
305 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
306 Assert::Equal<DWORD>(0, pVersion3->dwMinor);
307 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
308 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
309 Assert::Equal<DWORD>(1, pVersion3->cReleaseLabels);
310
311 Assert::Equal<BOOL>(TRUE, pVersion3->rgReleaseLabels[0].fNumeric);
312 Assert::Equal<DWORD>(2, pVersion3->rgReleaseLabels[0].dwValue);
313 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[0].cchLabel);
314 Assert::Equal<DWORD>(2, pVersion3->rgReleaseLabels[0].cchLabelOffset);
315
316 Assert::Equal<DWORD>(3, pVersion3->cchMetadataOffset);
317 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
318
319 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
320 TestVerutilCompareParsedVersions(pVersion1, pVersion3, -1);
321 }
322 finally
323 {
324 ReleaseVerutilVersion(pVersion1);
325 ReleaseVerutilVersion(pVersion2);
326 ReleaseVerutilVersion(pVersion3);
327 }
328 }
329
330 [Fact]
331 void VerCompareVersionsIgnoresLeadingZeroes()
332 {
333 HRESULT hr = S_OK;
334 VERUTIL_VERSION* pVersion1 = NULL;
335 VERUTIL_VERSION* pVersion2 = NULL;
336 VERUTIL_VERSION* pVersion3 = NULL;
337 VERUTIL_VERSION* pVersion4 = NULL;
338 LPCWSTR wzVersion1 = L"0.01-a.1";
339 LPCWSTR wzVersion2 = L"0.1.0-a.1";
340 LPCWSTR wzVersion3 = L"0.1-a.b.0";
341 LPCWSTR wzVersion4 = L"0.1.0-a.b.000";
342
343 try
344 {
345 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
346 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
347
348 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
349 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
350
351 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
352 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
353
354 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
355 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
356
357 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
358 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
359 Assert::Equal<DWORD>(1, pVersion1->dwMinor);
360 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
361 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
362 Assert::Equal<DWORD>(2, pVersion1->cReleaseLabels);
363
364 Assert::Equal<BOOL>(FALSE, pVersion1->rgReleaseLabels[0].fNumeric);
365 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[0].cchLabel);
366 Assert::Equal<DWORD>(5, pVersion1->rgReleaseLabels[0].cchLabelOffset);
367
368 Assert::Equal<BOOL>(TRUE, pVersion1->rgReleaseLabels[1].fNumeric);
369 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].dwValue);
370 Assert::Equal<DWORD>(1, pVersion1->rgReleaseLabels[1].cchLabel);
371 Assert::Equal<DWORD>(7, pVersion1->rgReleaseLabels[1].cchLabelOffset);
372
373 Assert::Equal<DWORD>(8, pVersion1->cchMetadataOffset);
374 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
375
376 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
377 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
378 Assert::Equal<DWORD>(1, pVersion2->dwMinor);
379 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
380 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
381 Assert::Equal<DWORD>(2, pVersion2->cReleaseLabels);
382
383 Assert::Equal<BOOL>(FALSE, pVersion2->rgReleaseLabels[0].fNumeric);
384 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[0].cchLabel);
385 Assert::Equal<DWORD>(6, pVersion2->rgReleaseLabels[0].cchLabelOffset);
386
387 Assert::Equal<BOOL>(TRUE, pVersion2->rgReleaseLabels[1].fNumeric);
388 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[1].dwValue);
389 Assert::Equal<DWORD>(1, pVersion2->rgReleaseLabels[1].cchLabel);
390 Assert::Equal<DWORD>(8, pVersion2->rgReleaseLabels[1].cchLabelOffset);
391
392 Assert::Equal<DWORD>(9, pVersion2->cchMetadataOffset);
393 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
394
395 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
396 Assert::Equal<DWORD>(0, pVersion3->dwMajor);
397 Assert::Equal<DWORD>(1, pVersion3->dwMinor);
398 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
399 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
400 Assert::Equal<DWORD>(3, pVersion3->cReleaseLabels);
401
402 Assert::Equal<BOOL>(FALSE, pVersion3->rgReleaseLabels[0].fNumeric);
403 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[0].cchLabel);
404 Assert::Equal<DWORD>(4, pVersion3->rgReleaseLabels[0].cchLabelOffset);
405
406 Assert::Equal<BOOL>(FALSE, pVersion3->rgReleaseLabels[1].fNumeric);
407 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[1].cchLabel);
408 Assert::Equal<DWORD>(6, pVersion3->rgReleaseLabels[1].cchLabelOffset);
409
410 Assert::Equal<BOOL>(TRUE, pVersion3->rgReleaseLabels[2].fNumeric);
411 Assert::Equal<DWORD>(0, pVersion3->rgReleaseLabels[2].dwValue);
412 Assert::Equal<DWORD>(1, pVersion3->rgReleaseLabels[2].cchLabel);
413 Assert::Equal<DWORD>(8, pVersion3->rgReleaseLabels[2].cchLabelOffset);
414
415 Assert::Equal<DWORD>(9, pVersion3->cchMetadataOffset);
416 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
417
418 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
419 Assert::Equal<DWORD>(0, pVersion4->dwMajor);
420 Assert::Equal<DWORD>(1, pVersion4->dwMinor);
421 Assert::Equal<DWORD>(0, pVersion4->dwPatch);
422 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
423 Assert::Equal<DWORD>(3, pVersion4->cReleaseLabels);
424
425 Assert::Equal<BOOL>(FALSE, pVersion4->rgReleaseLabels[0].fNumeric);
426 Assert::Equal<DWORD>(1, pVersion4->rgReleaseLabels[0].cchLabel);
427 Assert::Equal<DWORD>(6, pVersion4->rgReleaseLabels[0].cchLabelOffset);
428
429 Assert::Equal<BOOL>(FALSE, pVersion4->rgReleaseLabels[1].fNumeric);
430 Assert::Equal<DWORD>(1, pVersion4->rgReleaseLabels[1].cchLabel);
431 Assert::Equal<DWORD>(8, pVersion4->rgReleaseLabels[1].cchLabelOffset);
432
433 Assert::Equal<BOOL>(TRUE, pVersion4->rgReleaseLabels[2].fNumeric);
434 Assert::Equal<DWORD>(0, pVersion4->rgReleaseLabels[2].dwValue);
435 Assert::Equal<DWORD>(3, pVersion4->rgReleaseLabels[2].cchLabel);
436 Assert::Equal<DWORD>(10, pVersion4->rgReleaseLabels[2].cchLabelOffset);
437
438 Assert::Equal<DWORD>(13, pVersion4->cchMetadataOffset);
439 Assert::Equal<BOOL>(FALSE, pVersion4->fInvalid);
440
441 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
442 TestVerutilCompareParsedVersions(pVersion3, pVersion4, 0);
443 }
444 finally
445 {
446 ReleaseVerutilVersion(pVersion1);
447 ReleaseVerutilVersion(pVersion2);
448 ReleaseVerutilVersion(pVersion3);
449 ReleaseVerutilVersion(pVersion4);
450 }
451 }
452
453 [Fact]
454 void VerCompareVersionsTreatsUnexpectedContentAsMetadata()
455 {
456 HRESULT hr = S_OK;
457 VERUTIL_VERSION* pVersion1 = NULL;
458 VERUTIL_VERSION* pVersion2 = NULL;
459 VERUTIL_VERSION* pVersion3 = NULL;
460 LPCWSTR wzVersion1 = L"1.2.3+abcd";
461 LPCWSTR wzVersion2 = L"1.2.3.abcd";
462 LPCWSTR wzVersion3 = L"1.2.3.-abcd";
463
464 try
465 {
466 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
467 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
468
469 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
470 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
471
472 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
473 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
474
475 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
476 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
477 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
478 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
479 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
480 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
481 Assert::Equal<DWORD>(6, pVersion1->cchMetadataOffset);
482 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
483
484 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
485 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
486 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
487 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
488 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
489 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
490 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
491 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
492
493 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
494 Assert::Equal<DWORD>(1, pVersion3->dwMajor);
495 Assert::Equal<DWORD>(2, pVersion3->dwMinor);
496 Assert::Equal<DWORD>(3, pVersion3->dwPatch);
497 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
498 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
499 Assert::Equal<DWORD>(6, pVersion3->cchMetadataOffset);
500 Assert::Equal<BOOL>(TRUE, pVersion3->fInvalid);
501
502 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
503 TestVerutilCompareParsedVersions(pVersion1, pVersion3, 1);
504 TestVerutilCompareParsedVersions(pVersion2, pVersion3, -1);
505 }
506 finally
507 {
508 ReleaseVerutilVersion(pVersion1);
509 ReleaseVerutilVersion(pVersion2);
510 ReleaseVerutilVersion(pVersion3);
511 }
512 }
513
514 [Fact]
515 void VerCompareVersionsIgnoresLeadingV()
516 {
517 HRESULT hr = S_OK;
518 VERUTIL_VERSION* pVersion1 = NULL;
519 VERUTIL_VERSION* pVersion2 = NULL;
520 VERUTIL_VERSION* pVersion3 = NULL;
521 LPCWSTR wzVersion1 = L"10.20.30.40";
522 LPCWSTR wzVersion2 = L"v10.20.30.40";
523 LPCWSTR wzVersion3 = L"V10.20.30.40";
524
525 try
526 {
527 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
528 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
529
530 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
531 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
532
533 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
534 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
535
536 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
537 Assert::Equal<DWORD>(10, pVersion1->dwMajor);
538 Assert::Equal<DWORD>(20, pVersion1->dwMinor);
539 Assert::Equal<DWORD>(30, pVersion1->dwPatch);
540 Assert::Equal<DWORD>(40, pVersion1->dwRevision);
541 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
542 Assert::Equal<DWORD>(11, pVersion1->cchMetadataOffset);
543 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
544
545 NativeAssert::StringEqual(wzVersion1, pVersion2->sczVersion);
546 Assert::Equal<DWORD>(10, pVersion2->dwMajor);
547 Assert::Equal<DWORD>(20, pVersion2->dwMinor);
548 Assert::Equal<DWORD>(30, pVersion2->dwPatch);
549 Assert::Equal<DWORD>(40, pVersion2->dwRevision);
550 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
551 Assert::Equal<DWORD>(11, pVersion2->cchMetadataOffset);
552 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
553
554 NativeAssert::StringEqual(wzVersion1, pVersion3->sczVersion);
555 Assert::Equal<DWORD>(10, pVersion3->dwMajor);
556 Assert::Equal<DWORD>(20, pVersion3->dwMinor);
557 Assert::Equal<DWORD>(30, pVersion3->dwPatch);
558 Assert::Equal<DWORD>(40, pVersion3->dwRevision);
559 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
560 Assert::Equal<DWORD>(11, pVersion3->cchMetadataOffset);
561 Assert::Equal<BOOL>(FALSE, pVersion3->fInvalid);
562
563 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
564 TestVerutilCompareParsedVersions(pVersion1, pVersion3, 0);
565 }
566 finally
567 {
568 ReleaseVerutilVersion(pVersion1);
569 ReleaseVerutilVersion(pVersion2);
570 ReleaseVerutilVersion(pVersion3);
571 }
572 }
573
574 [Fact]
575 void VerCompareVersionsHandlesTooLargeNumbers()
576 {
577 HRESULT hr = S_OK;
578 VERUTIL_VERSION* pVersion1 = NULL;
579 VERUTIL_VERSION* pVersion2 = NULL;
580 LPCWSTR wzVersion1 = L"4294967295.4294967295.4294967295.4294967295";
581 LPCWSTR wzVersion2 = L"4294967296.4294967296.4294967296.4294967296";
582
583 try
584 {
585 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
586 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
587
588 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
589 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
590
591 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
592 Assert::Equal<DWORD>(4294967295, pVersion1->dwMajor);
593 Assert::Equal<DWORD>(4294967295, pVersion1->dwMinor);
594 Assert::Equal<DWORD>(4294967295, pVersion1->dwPatch);
595 Assert::Equal<DWORD>(4294967295, pVersion1->dwRevision);
596 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
597 Assert::Equal<DWORD>(43, pVersion1->cchMetadataOffset);
598 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
599
600 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
601 Assert::Equal<DWORD>(0, pVersion2->dwMajor);
602 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
603 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
604 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
605 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
606 Assert::Equal<DWORD>(0, pVersion2->cchMetadataOffset);
607 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
608
609 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 1);
610 }
611 finally
612 {
613 ReleaseVerutilVersion(pVersion1);
614 ReleaseVerutilVersion(pVersion2);
615 }
616 }
617
618 [Fact]
619 void VerCompareVersionsIgnoresMetadataForValidVersions()
620 {
621 HRESULT hr = S_OK;
622 VERUTIL_VERSION* pVersion1 = NULL;
623 VERUTIL_VERSION* pVersion2 = NULL;
624 LPCWSTR wzVersion1 = L"1.2.3+abc";
625 LPCWSTR wzVersion2 = L"1.2.3+xyz";
626
627 try
628 {
629 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
630 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
631
632 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
633 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
634
635 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
636 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
637 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
638 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
639 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
640 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
641 Assert::Equal<DWORD>(6, pVersion1->cchMetadataOffset);
642 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
643
644 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
645 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
646 Assert::Equal<DWORD>(2, pVersion2->dwMinor);
647 Assert::Equal<DWORD>(3, pVersion2->dwPatch);
648 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
649 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
650 Assert::Equal<DWORD>(6, pVersion2->cchMetadataOffset);
651 Assert::Equal<BOOL>(FALSE, pVersion2->fInvalid);
652
653 TestVerutilCompareParsedVersions(pVersion1, pVersion2, 0);
654 }
655 finally
656 {
657 ReleaseVerutilVersion(pVersion1);
658 ReleaseVerutilVersion(pVersion2);
659 }
660 }
661
662 [Fact]
663 void VerCopyVersionCopiesVersion()
664 {
665 HRESULT hr = S_OK;
666 LPCWSTR wzVersion = L"1.2.3.4+abc123";
667 VERUTIL_VERSION* pSource = NULL;
668 VERUTIL_VERSION* pCopy = NULL;
669 int nResult = 0;
670
671 try
672 {
673 hr = VerParseVersion(wzVersion, 0, FALSE, &pSource);
674 NativeAssert::Succeeded(hr, "VerParseVersion failed");
675
676 NativeAssert::StringEqual(wzVersion, pSource->sczVersion);
677 Assert::Equal<DWORD>(1, pSource->dwMajor);
678 Assert::Equal<DWORD>(2, pSource->dwMinor);
679 Assert::Equal<DWORD>(3, pSource->dwPatch);
680 Assert::Equal<DWORD>(4, pSource->dwRevision);
681 Assert::Equal<DWORD>(0, pSource->cReleaseLabels);
682
683 Assert::Equal<DWORD>(8, pSource->cchMetadataOffset);
684 Assert::Equal<BOOL>(FALSE, pSource->fInvalid);
685
686 hr = VerCopyVersion(pSource, &pCopy);
687 NativeAssert::Succeeded(hr, "VerCopyVersion failed");
688
689 Assert::False(pSource == pCopy);
690 Assert::False(pSource->sczVersion == pCopy->sczVersion);
691
692 hr = VerCompareParsedVersions(pSource, pCopy, &nResult);
693 NativeAssert::Succeeded(hr, "VerCompareParsedVersions failed");
694
695 Assert::Equal<int>(nResult, 0);
696 }
697 finally
698 {
699 ReleaseVerutilVersion(pCopy);
700 ReleaseVerutilVersion(pSource);
701 }
702 }
703
704 [Fact]
705 void VerCopyVersionCopiesPrereleaseVersion()
706 {
707 HRESULT hr = S_OK;
708 LPCWSTR wzVersion = L"1.2.3.4-a.b.c.d.5.+abc123";
709 VERUTIL_VERSION* pSource = NULL;
710 VERUTIL_VERSION* pCopy = NULL;
711 int nResult = 0;
712
713 try
714 {
715 hr = VerParseVersion(wzVersion, 0, FALSE, &pSource);
716 NativeAssert::Succeeded(hr, "VerParseVersion failed");
717
718 NativeAssert::StringEqual(wzVersion, pSource->sczVersion);
719 Assert::Equal<DWORD>(1, pSource->dwMajor);
720 Assert::Equal<DWORD>(2, pSource->dwMinor);
721 Assert::Equal<DWORD>(3, pSource->dwPatch);
722 Assert::Equal<DWORD>(4, pSource->dwRevision);
723 Assert::Equal<DWORD>(5, pSource->cReleaseLabels);
724
725 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[0].fNumeric);
726 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[0].cchLabel);
727 Assert::Equal<DWORD>(8, pSource->rgReleaseLabels[0].cchLabelOffset);
728
729 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[1].fNumeric);
730 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[1].cchLabel);
731 Assert::Equal<DWORD>(10, pSource->rgReleaseLabels[1].cchLabelOffset);
732
733 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[2].fNumeric);
734 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[2].cchLabel);
735 Assert::Equal<DWORD>(12, pSource->rgReleaseLabels[2].cchLabelOffset);
736
737 Assert::Equal<BOOL>(FALSE, pSource->rgReleaseLabels[3].fNumeric);
738 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[3].cchLabel);
739 Assert::Equal<DWORD>(14, pSource->rgReleaseLabels[3].cchLabelOffset);
740
741 Assert::Equal<BOOL>(TRUE, pSource->rgReleaseLabels[4].fNumeric);
742 Assert::Equal<DWORD>(5, pSource->rgReleaseLabels[4].dwValue);
743 Assert::Equal<DWORD>(1, pSource->rgReleaseLabels[4].cchLabel);
744 Assert::Equal<DWORD>(16, pSource->rgReleaseLabels[4].cchLabelOffset);
745
746 Assert::Equal<DWORD>(18, pSource->cchMetadataOffset);
747 Assert::Equal<BOOL>(TRUE, pSource->fInvalid);
748
749 hr = VerCopyVersion(pSource, &pCopy);
750 NativeAssert::Succeeded(hr, "VerCopyVersion failed");
751
752 Assert::False(pSource == pCopy);
753 Assert::False(pSource->sczVersion == pCopy->sczVersion);
754 Assert::False(pSource->rgReleaseLabels == pCopy->rgReleaseLabels);
755
756 hr = VerCompareParsedVersions(pSource, pCopy, &nResult);
757 NativeAssert::Succeeded(hr, "VerCompareParsedVersions failed");
758
759 Assert::Equal<int>(nResult, 0);
760 }
761 finally
762 {
763 ReleaseVerutilVersion(pCopy);
764 ReleaseVerutilVersion(pSource);
765 }
766 }
767
768 [Fact]
769 void VerParseVersionTreatsTrailingDotsAsInvalid()
770 {
771 HRESULT hr = S_OK;
772 VERUTIL_VERSION* pVersion1 = NULL;
773 VERUTIL_VERSION* pVersion2 = NULL;
774 VERUTIL_VERSION* pVersion3 = NULL;
775 VERUTIL_VERSION* pVersion4 = NULL;
776 VERUTIL_VERSION* pVersion5 = NULL;
777 VERUTIL_VERSION* pVersion6 = NULL;
778 VERUTIL_VERSION* pVersion7 = NULL;
779 LPCWSTR wzVersion1 = L".";
780 LPCWSTR wzVersion2 = L"1.";
781 LPCWSTR wzVersion3 = L"2.1.";
782 LPCWSTR wzVersion4 = L"3.2.1.";
783 LPCWSTR wzVersion5 = L"4.3.2.1.";
784 LPCWSTR wzVersion6 = L"5-.";
785 LPCWSTR wzVersion7 = L"6-a.";
786
787 try
788 {
789 hr = VerParseVersion(wzVersion1, 0, FALSE, &pVersion1);
790 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion1);
791
792 hr = VerParseVersion(wzVersion2, 0, FALSE, &pVersion2);
793 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion2);
794
795 hr = VerParseVersion(wzVersion3, 0, FALSE, &pVersion3);
796 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion3);
797
798 hr = VerParseVersion(wzVersion4, 0, FALSE, &pVersion4);
799 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion4);
800
801 hr = VerParseVersion(wzVersion5, 0, FALSE, &pVersion5);
802 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion5);
803
804 hr = VerParseVersion(wzVersion6, 0, FALSE, &pVersion6);
805 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion6);
806
807 hr = VerParseVersion(wzVersion7, 0, FALSE, &pVersion7);
808 NativeAssert::Succeeded(hr, "Failed to parse version '{0}'", wzVersion7);
809
810 NativeAssert::StringEqual(wzVersion1, pVersion1->sczVersion);
811 Assert::Equal<DWORD>(0, pVersion1->dwMajor);
812 Assert::Equal<DWORD>(0, pVersion1->dwMinor);
813 Assert::Equal<DWORD>(0, pVersion1->dwPatch);
814 Assert::Equal<DWORD>(0, pVersion1->dwRevision);
815 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
816 Assert::Equal<DWORD>(0, pVersion1->cchMetadataOffset);
817 Assert::Equal<BOOL>(TRUE, pVersion1->fInvalid);
818
819 NativeAssert::StringEqual(wzVersion2, pVersion2->sczVersion);
820 Assert::Equal<DWORD>(1, pVersion2->dwMajor);
821 Assert::Equal<DWORD>(0, pVersion2->dwMinor);
822 Assert::Equal<DWORD>(0, pVersion2->dwPatch);
823 Assert::Equal<DWORD>(0, pVersion2->dwRevision);
824 Assert::Equal<DWORD>(0, pVersion2->cReleaseLabels);
825 Assert::Equal<DWORD>(2, pVersion2->cchMetadataOffset);
826 Assert::Equal<BOOL>(TRUE, pVersion2->fInvalid);
827
828 NativeAssert::StringEqual(wzVersion3, pVersion3->sczVersion);
829 Assert::Equal<DWORD>(2, pVersion3->dwMajor);
830 Assert::Equal<DWORD>(1, pVersion3->dwMinor);
831 Assert::Equal<DWORD>(0, pVersion3->dwPatch);
832 Assert::Equal<DWORD>(0, pVersion3->dwRevision);
833 Assert::Equal<DWORD>(0, pVersion3->cReleaseLabels);
834 Assert::Equal<DWORD>(4, pVersion3->cchMetadataOffset);
835 Assert::Equal<BOOL>(TRUE, pVersion3->fInvalid);
836
837 NativeAssert::StringEqual(wzVersion4, pVersion4->sczVersion);
838 Assert::Equal<DWORD>(3, pVersion4->dwMajor);
839 Assert::Equal<DWORD>(2, pVersion4->dwMinor);
840 Assert::Equal<DWORD>(1, pVersion4->dwPatch);
841 Assert::Equal<DWORD>(0, pVersion4->dwRevision);
842 Assert::Equal<DWORD>(0, pVersion4->cReleaseLabels);
843 Assert::Equal<DWORD>(6, pVersion4->cchMetadataOffset);
844 Assert::Equal<BOOL>(TRUE, pVersion4->fInvalid);
845
846 NativeAssert::StringEqual(wzVersion5, pVersion5->sczVersion);
847 Assert::Equal<DWORD>(4, pVersion5->dwMajor);
848 Assert::Equal<DWORD>(3, pVersion5->dwMinor);
849 Assert::Equal<DWORD>(2, pVersion5->dwPatch);
850 Assert::Equal<DWORD>(1, pVersion5->dwRevision);
851 Assert::Equal<DWORD>(0, pVersion5->cReleaseLabels);
852 Assert::Equal<DWORD>(8, pVersion5->cchMetadataOffset);
853 Assert::Equal<BOOL>(TRUE, pVersion5->fInvalid);
854
855 NativeAssert::StringEqual(wzVersion6, pVersion6->sczVersion);
856 Assert::Equal<DWORD>(5, pVersion6->dwMajor);
857 Assert::Equal<DWORD>(0, pVersion6->dwMinor);
858 Assert::Equal<DWORD>(0, pVersion6->dwPatch);
859 Assert::Equal<DWORD>(0, pVersion6->dwRevision);
860 Assert::Equal<DWORD>(0, pVersion6->cReleaseLabels);
861 Assert::Equal<DWORD>(2, pVersion6->cchMetadataOffset);
862 Assert::Equal<BOOL>(TRUE, pVersion6->fInvalid);
863
864 NativeAssert::StringEqual(wzVersion7, pVersion7->sczVersion);
865 Assert::Equal<DWORD>(6, pVersion7->dwMajor);
866 Assert::Equal<DWORD>(0, pVersion7->dwMinor);
867 Assert::Equal<DWORD>(0, pVersion7->dwPatch);
868 Assert::Equal<DWORD>(0, pVersion7->dwRevision);
869 Assert::Equal<DWORD>(1, pVersion7->cReleaseLabels);
870
871 Assert::Equal<BOOL>(FALSE, pVersion7->rgReleaseLabels[0].fNumeric);
872 Assert::Equal<DWORD>(1, pVersion7->rgReleaseLabels[0].cchLabel);
873 Assert::Equal<DWORD>(2, pVersion7->rgReleaseLabels[0].cchLabelOffset);
874
875 Assert::Equal<DWORD>(4, pVersion7->cchMetadataOffset);
876 Assert::Equal<BOOL>(TRUE, pVersion7->fInvalid);
877 }
878 finally
879 {
880 ReleaseVerutilVersion(pVersion1);
881 ReleaseVerutilVersion(pVersion2);
882 ReleaseVerutilVersion(pVersion3);
883 ReleaseVerutilVersion(pVersion4);
884 ReleaseVerutilVersion(pVersion5);
885 ReleaseVerutilVersion(pVersion6);
886 ReleaseVerutilVersion(pVersion7);
887 }
888 }
889
890 [Fact]
891 void VerVersionFromQwordCreatesVersion()
892 {
893 HRESULT hr = S_OK;
894 VERUTIL_VERSION* pVersion1 = NULL;
895
896 try
897 {
898 hr = VerVersionFromQword(MAKEQWORDVERSION(1, 2, 3, 4), &pVersion1);
899 NativeAssert::Succeeded(hr, "VerVersionFromQword failed");
900
901 NativeAssert::StringEqual(L"1.2.3.4", pVersion1->sczVersion);
902 Assert::Equal<DWORD>(1, pVersion1->dwMajor);
903 Assert::Equal<DWORD>(2, pVersion1->dwMinor);
904 Assert::Equal<DWORD>(3, pVersion1->dwPatch);
905 Assert::Equal<DWORD>(4, pVersion1->dwRevision);
906 Assert::Equal<DWORD>(0, pVersion1->cReleaseLabels);
907 Assert::Equal<DWORD>(7, pVersion1->cchMetadataOffset);
908 Assert::Equal<BOOL>(FALSE, pVersion1->fInvalid);
909 }
910 finally
911 {
912 ReleaseVerutilVersion(pVersion1);
913 }
914 }
915
916 private:
917 void TestVerutilCompareParsedVersions(VERUTIL_VERSION* pVersion1, VERUTIL_VERSION* pVersion2, int nExpectedResult)
918 {
919 HRESULT hr = S_OK;
920 int nResult = 0;
921
922 hr = VerCompareParsedVersions(pVersion1, pVersion2, &nResult);
923 NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion);
924
925 Assert::Equal(nExpectedResult, nResult);
926
927 hr = VerCompareParsedVersions(pVersion2, pVersion1, &nResult);
928 NativeAssert::Succeeded(hr, "Failed to compare versions '{0}' and '{1}'", pVersion1->sczVersion, pVersion2->sczVersion);
929
930 Assert::Equal(nExpectedResult, -nResult);
931 }
932 };
933}
diff --git a/src/libs/dutil/test/DUtilUnitTest/error.cpp b/src/libs/dutil/test/DUtilUnitTest/error.cpp
new file mode 100644
index 00000000..e51971c3
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/error.cpp
@@ -0,0 +1,26 @@
1// 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.
2
3#include "precomp.h"
4
5const int ERROR_STRING_BUFFER = 1024;
6
7static char szMsg[ERROR_STRING_BUFFER];
8static WCHAR wzMsg[ERROR_STRING_BUFFER];
9
10void CALLBACK DutilTestTraceError(
11 __in_z LPCSTR /*szFile*/,
12 __in int /*iLine*/,
13 __in REPORT_LEVEL /*rl*/,
14 __in UINT source,
15 __in HRESULT hrError,
16 __in_z __format_string LPCSTR szFormat,
17 __in va_list args
18 )
19{
20 if (DUTIL_SOURCE_EXTERNAL == source)
21 {
22 ::StringCchPrintfA(szMsg, countof(szMsg), szFormat, args);
23 MultiByteToWideChar(CP_ACP, 0, szMsg, -1, wzMsg, countof(wzMsg));
24 throw gcnew System::Exception(System::String::Format("hr = 0x{0:X8}, message = {1}", hrError, gcnew System::String(wzMsg)));
25 }
26}
diff --git a/src/libs/dutil/test/DUtilUnitTest/error.h b/src/libs/dutil/test/DUtilUnitTest/error.h
new file mode 100644
index 00000000..b973acaf
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/error.h
@@ -0,0 +1,14 @@
1#pragma once
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.
3
4#define DUTIL_SOURCE_DEFAULT DUTIL_SOURCE_EXTERNAL
5
6void CALLBACK DutilTestTraceError(
7 __in_z LPCSTR szFile,
8 __in int iLine,
9 __in REPORT_LEVEL rl,
10 __in UINT source,
11 __in HRESULT hrError,
12 __in_z __format_string LPCSTR szFormat,
13 __in va_list args
14 );
diff --git a/src/libs/dutil/test/DUtilUnitTest/packages.config b/src/libs/dutil/test/DUtilUnitTest/packages.config
new file mode 100644
index 00000000..a4fef2bf
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/packages.config
@@ -0,0 +1,13 @@
1<?xml version="1.0" encoding="utf-8"?>
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. -->
3<packages>
4 <package id="xunit.abstractions" version="2.0.3" />
5 <package id="xunit.assert" version="2.4.1" />
6 <package id="xunit.core" version="2.4.1" />
7 <package id="xunit.extensibility.core" version="2.4.1" />
8 <package id="xunit.extensibility.execution" version="2.4.1" />
9 <package id="xunit.runner.msbuild" version="2.4.1" />
10 <package id="xunit.runner.visualstudio" version="2.4.1" />
11 <package id="WixBuildTools.TestSupport" version="4.0.47" />
12 <package id="WixBuildTools.TestSupport.Native" version="4.0.47" />
13</packages> \ No newline at end of file
diff --git a/src/libs/dutil/test/DUtilUnitTest/precomp.cpp b/src/libs/dutil/test/DUtilUnitTest/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/precomp.cpp
@@ -0,0 +1,3 @@
1// 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.
2
3#include "precomp.h"
diff --git a/src/libs/dutil/test/DUtilUnitTest/precomp.h b/src/libs/dutil/test/DUtilUnitTest/precomp.h
new file mode 100644
index 00000000..e9f8770b
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/precomp.h
@@ -0,0 +1,32 @@
1#pragma once
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.
3
4
5#include <windows.h>
6#include <strsafe.h>
7#include <ShlObj.h>
8
9// Include error.h before dutil.h
10#include <dutilsources.h>
11#include "error.h"
12#include <dutil.h>
13
14#include <verutil.h>
15#include <atomutil.h>
16#include <dictutil.h>
17#include <dirutil.h>
18#include <fileutil.h>
19#include <guidutil.h>
20#include <iniutil.h>
21#include <memutil.h>
22#include <pathutil.h>
23#include <strutil.h>
24#include <monutil.h>
25#include <regutil.h>
26#include <rssutil.h>
27#include <apuputil.h> // NOTE: this must come after atomutil.h and rssutil.h since it uses them.
28#include <uriutil.h>
29#include <xmlutil.h>
30
31#pragma managed
32#include <vcclr.h>
diff --git a/src/libs/dutil/test/DUtilUnitTest/resource.h b/src/libs/dutil/test/DUtilUnitTest/resource.h
new file mode 100644
index 00000000..bdf252f6
--- /dev/null
+++ b/src/libs/dutil/test/DUtilUnitTest/resource.h
@@ -0,0 +1,2 @@
1// 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.
2