Skip to content

Commit ffef215

Browse files
committed
Fixed bug in dictionary added hashset and additional string helpers
1 parent a7ef777 commit ffef215

15 files changed

+2250
-207
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
8+
<IsPackable>false</IsPackable>
9+
<IsTestProject>true</IsTestProject>
10+
<Platforms>AnyCPU;x86</Platforms>
11+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="coverlet.collector" Version="6.0.0" />
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
17+
<PackageReference Include="NUnit" Version="3.14.0" />
18+
<PackageReference Include="NUnit.Analyzers" Version="3.9.0" />
19+
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<ProjectReference Include="..\Hexa.NET.Utilities\Hexa.NET.Utilities.csproj" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<Using Include="NUnit.Framework" />
28+
</ItemGroup>
29+
30+
</Project>
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
namespace Hexa.NET.Utilities.Tests
2+
{
3+
using System.Diagnostics;
4+
5+
[TestFixture]
6+
public class UnsafeDictionaryTests
7+
{
8+
[Test]
9+
public void TestInitialAdd()
10+
{
11+
UnsafeDictionary<uint, int> dict = default;
12+
13+
dict.Clear();
14+
dict[1] = 2;
15+
Assert.That(dict[1], Is.EqualTo(2));
16+
17+
foreach (var item in dict)
18+
{
19+
Console.WriteLine(item.Key + " " + item.Value);
20+
}
21+
22+
dict.Release();
23+
}
24+
25+
[Test]
26+
public void TestAddNewKey()
27+
{
28+
UnsafeDictionary<uint, int> dict = default;
29+
dict[1] = 2;
30+
dict[2] = 3;
31+
32+
Assert.That(dict[1], Is.EqualTo(2));
33+
Assert.That(dict[2], Is.EqualTo(3));
34+
35+
foreach (var item in dict)
36+
{
37+
Console.WriteLine(item.Key + " " + item.Value);
38+
}
39+
dict.Release();
40+
}
41+
42+
[Test]
43+
public void TestUpdateExistingKey()
44+
{
45+
UnsafeDictionary<uint, int> dict = default;
46+
dict[1] = 2;
47+
dict[1] = 4;
48+
49+
Assert.That(dict[1], Is.EqualTo(4));
50+
51+
foreach (var item in dict)
52+
{
53+
Console.WriteLine(item.Key + " " + item.Value);
54+
}
55+
dict.Release();
56+
}
57+
58+
[Test]
59+
public void TestRemoveKey()
60+
{
61+
UnsafeDictionary<uint, int> dict = default;
62+
dict[1] = 2;
63+
dict[2] = 3;
64+
65+
dict.Remove(1);
66+
67+
Assert.IsFalse(dict.ContainsKey(1));
68+
Assert.That(dict[2], Is.EqualTo(3));
69+
70+
foreach (var item in dict)
71+
{
72+
Console.WriteLine(item.Key + " " + item.Value);
73+
}
74+
dict.Release();
75+
}
76+
77+
[Test]
78+
public void TestRemoveNonExistentKey()
79+
{
80+
UnsafeDictionary<uint, int> dict = default;
81+
dict[1] = 2;
82+
83+
Assert.DoesNotThrow(() => dict.Remove(2));
84+
85+
Assert.That(dict[1], Is.EqualTo(2));
86+
Assert.IsFalse(dict.ContainsKey(2));
87+
88+
foreach (var item in dict)
89+
{
90+
Console.WriteLine(item.Key + " " + item.Value);
91+
}
92+
dict.Release();
93+
}
94+
95+
[Test]
96+
public void TestEmptyDictionary()
97+
{
98+
UnsafeDictionary<uint, int> dict = default;
99+
Assert.IsEmpty(dict);
100+
101+
foreach (var item in dict)
102+
{
103+
Console.WriteLine(item.Key + " " + item.Value);
104+
}
105+
dict.Release();
106+
}
107+
108+
[Test]
109+
public void TestReleaseResources()
110+
{
111+
UnsafeDictionary<uint, int> dict = default;
112+
dict[1] = 2;
113+
dict.Release();
114+
115+
// Verify that capacity and size are reset to zero
116+
Assert.That(dict.Capacity, Is.EqualTo(0));
117+
Assert.That(dict.Size, Is.EqualTo(0));
118+
119+
// Access the dictionary after release, expecting it to be reallocated
120+
dict[2] = 3;
121+
122+
// Since dictionary is reallocated, previous values should not exist
123+
Assert.IsFalse(dict.ContainsKey(1));
124+
Assert.That(dict[2], Is.EqualTo(3));
125+
126+
foreach (var item in dict)
127+
{
128+
Console.WriteLine(item.Key + " " + item.Value);
129+
}
130+
131+
// Verify that the dictionary's capacity and size have been updated correctly
132+
Assert.Greater(dict.Capacity, 0);
133+
Assert.That(dict.Size, Is.EqualTo(1));
134+
135+
// Release the dictionary again
136+
dict.Release();
137+
138+
// Verify that capacity and size are reset to zero again
139+
Assert.That(dict.Capacity, Is.EqualTo(0));
140+
Assert.That(dict.Size, Is.EqualTo(0));
141+
}
142+
143+
[Test]
144+
public void StressTest()
145+
{
146+
const int iterations = 100000;
147+
UnsafeDictionary<uint, int> dict = default;
148+
Random random = new Random();
149+
Stopwatch stopwatch = new Stopwatch();
150+
151+
const int range = int.MaxValue / iterations;
152+
153+
List<(uint, int)> keyValues = new();
154+
155+
StressInsert(iterations, random, range, keyValues, ref dict); // warmup
156+
157+
keyValues.Clear();
158+
dict.Clear();
159+
160+
stopwatch.Start();
161+
StressInsert(iterations, random, range, keyValues, ref dict);
162+
stopwatch.Stop();
163+
164+
Console.WriteLine($"Insert stress test completed in {stopwatch.Elapsed.TotalMilliseconds} ms");
165+
166+
StressLookup(keyValues, ref dict); // warmup
167+
168+
// Verify that the dictionary contains the expected values
169+
stopwatch.Restart();
170+
StressLookup(keyValues, ref dict);
171+
stopwatch.Stop();
172+
173+
Console.WriteLine($"Lookup stress test completed in {stopwatch.Elapsed.TotalMilliseconds} ms");
174+
175+
var clone = dict.Clone();
176+
StressRemove(keyValues, ref clone);
177+
178+
Assert.That(clone.Size, Is.EqualTo(0));
179+
clone.Release();
180+
181+
stopwatch.Restart();
182+
StressRemove(keyValues, ref dict);
183+
stopwatch.Stop();
184+
185+
Console.WriteLine($"Delete stress test completed in {stopwatch.Elapsed.TotalMilliseconds} ms");
186+
187+
Assert.That(dict.Size, Is.EqualTo(0));
188+
189+
dict.Release();
190+
191+
Assert.Pass();
192+
}
193+
194+
private void StressRemove(List<(uint, int)> keyValues, ref UnsafeDictionary<uint, int> dict)
195+
{
196+
for (int i = 0; i < keyValues.Count; i++)
197+
{
198+
var (key, _) = keyValues[i];
199+
dict.Remove(key);
200+
}
201+
int siu = dict.Size;
202+
}
203+
204+
private void StressLookup(List<(uint, int)> keyValues, ref UnsafeDictionary<uint, int> dict)
205+
{
206+
for (int i = 0; i < keyValues.Count; i++)
207+
{
208+
var (key, value) = keyValues[i];
209+
if (dict[key] != value)
210+
{
211+
Assert.Fail();
212+
}
213+
}
214+
}
215+
216+
private void StressInsert(int iterations, Random random, int range, List<(uint, int)> keyValues, ref UnsafeDictionary<uint, int> dict)
217+
{
218+
for (int i = 0; i < iterations; i++)
219+
{
220+
uint key = (uint)random.Next(range * i, range * (i + 1));
221+
222+
int value = random.Next();
223+
dict[key] = value;
224+
keyValues.Add((key, value));
225+
}
226+
}
227+
}
228+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
namespace Hexa.NET.Utilities.Tests
2+
{
3+
[TestFixture]
4+
public class UnsafeHashSetTests
5+
{
6+
[Test]
7+
public void TestAdd()
8+
{
9+
// Arrange
10+
UnsafeHashSet<int> set = default;
11+
12+
// Act
13+
set.Add(1);
14+
15+
// Assert
16+
Assert.That(set.Contains(1), Is.True);
17+
18+
// Clean up
19+
set.Release();
20+
}
21+
22+
[Test]
23+
public void TestRemove()
24+
{
25+
// Arrange
26+
UnsafeHashSet<int> set = default;
27+
set.Add(1);
28+
29+
// Act
30+
set.Remove(1);
31+
32+
// Assert
33+
Assert.That(set.Contains(1), Is.False);
34+
35+
// Clean up
36+
set.Release();
37+
}
38+
39+
[Test]
40+
public void TestReleaseResources()
41+
{
42+
// Arrange
43+
UnsafeHashSet<int> set = default;
44+
set.Add(1);
45+
46+
// Act
47+
set.Release();
48+
set.Add(2); // This should resurrect the set
49+
bool resurrected = set.Contains(2);
50+
51+
// Assert
52+
Assert.That(resurrected, Is.True, "The set should contain the item after resurrection.");
53+
54+
// Clean up
55+
set.Release();
56+
57+
Assert.Multiple(() =>
58+
{
59+
Assert.That(set.Capacity, Is.EqualTo(0));
60+
Assert.That(set.Size, Is.EqualTo(0));
61+
});
62+
}
63+
}
64+
}

Hexa.NET.Utilities.sln

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,44 @@ Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.10.35027.167
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexa.NET.Utilities", "Hexa.NET.Utilities\Hexa.NET.Utilities.csproj", "{055E63D3-810C-4289-97D4-E27BCDB86548}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hexa.NET.Utilities", "Hexa.NET.Utilities\Hexa.NET.Utilities.csproj", "{055E63D3-810C-4289-97D4-E27BCDB86548}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hexa.NET.Utilities.Tests", "Hexa.NET.Utilities.Tests\Hexa.NET.Utilities.Tests.csproj", "{85BB49D9-31ED-4090-BFDE-4D9405801C71}"
711
EndProject
812
Global
913
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1014
Debug|Any CPU = Debug|Any CPU
15+
Debug|x86 = Debug|x86
1116
Release|Any CPU = Release|Any CPU
17+
Release|x86 = Release|x86
1218
EndGlobalSection
1319
GlobalSection(ProjectConfigurationPlatforms) = postSolution
1420
{055E63D3-810C-4289-97D4-E27BCDB86548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1521
{055E63D3-810C-4289-97D4-E27BCDB86548}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{055E63D3-810C-4289-97D4-E27BCDB86548}.Debug|x86.ActiveCfg = Debug|x86
23+
{055E63D3-810C-4289-97D4-E27BCDB86548}.Debug|x86.Build.0 = Debug|x86
1624
{055E63D3-810C-4289-97D4-E27BCDB86548}.Release|Any CPU.ActiveCfg = Release|Any CPU
1725
{055E63D3-810C-4289-97D4-E27BCDB86548}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{055E63D3-810C-4289-97D4-E27BCDB86548}.Release|x86.ActiveCfg = Release|x86
27+
{055E63D3-810C-4289-97D4-E27BCDB86548}.Release|x86.Build.0 = Release|x86
28+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Debug|Any CPU.ActiveCfg = Debug|x86
29+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Debug|Any CPU.Build.0 = Debug|x86
30+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Debug|x86.ActiveCfg = Debug|x86
31+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Debug|x86.Build.0 = Debug|x86
32+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Release|x86.ActiveCfg = Release|x86
35+
{81CCE8DE-6E5F-427B-A09F-C7E0A0EE2638}.Release|x86.Build.0 = Release|x86
36+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Debug|Any CPU.Build.0 = Debug|Any CPU
38+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Debug|x86.ActiveCfg = Debug|x86
39+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Debug|x86.Build.0 = Debug|x86
40+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Release|Any CPU.Build.0 = Release|Any CPU
42+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Release|x86.ActiveCfg = Release|x86
43+
{85BB49D9-31ED-4090-BFDE-4D9405801C71}.Release|x86.Build.0 = Release|x86
1844
EndGlobalSection
1945
GlobalSection(SolutionProperties) = preSolution
2046
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)