Skip to content

Commit 6aec360

Browse files
author
LoneWandererProductions
committed
add more tests
1 parent 7647c7a commit 6aec360

4 files changed

Lines changed: 349 additions & 11 deletions

File tree

MemoryManager.Tests/ArenaListTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace MemoryManager.Tests
1313
{
14+
/// <summary>
15+
/// Tests for the <see cref="ArenaList{T}" /> collection type, ensuring correct behavior of basic operations, growth mechanics, and span access.
16+
/// </summary>
1417
[TestClass]
1518
public class ArenaListTests
1619
{
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: MemoryManager.Tests
4+
* FILE: MemoryArenaTests.cs
5+
* PURPOSE: Exhaustive integration and stress verification for the MemoryArena wrapper.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using MemoryManager.Core;
10+
11+
namespace MemoryManager.Tests
12+
{
13+
[TestClass]
14+
public class MemoryArenaCoreIntegrationTests
15+
{
16+
private MemoryManagerConfig _config;
17+
18+
[TestInitialize]
19+
public void Setup()
20+
{
21+
_config = new MemoryManagerConfig
22+
{
23+
FastLaneSize = 256 * 1024, // 256 KB
24+
SlowLaneSize = 1024 * 1024, // 1 MB
25+
Threshold = 4 * 1024, // 4 KB Lane Boundary
26+
FastLaneUsageThreshold = 0.85f,
27+
SlowLaneUsageThreshold = 0.80f,
28+
CompactionThreshold = 0.85f,
29+
SlowLaneSafetyMargin = 0.10f,
30+
EnableAutoCompaction = false, // Controlled manually within integration steps
31+
PolicyCheckInterval = TimeSpan.Zero
32+
};
33+
}
34+
35+
#region --- SECTION 1: LATEST ROUTING & WORKSPACE BOUNDARY RULES ---
36+
37+
[TestMethod]
38+
public void Allocate_SizeBelowThreshold_RoutesToFastLane()
39+
{
40+
var arena = new MemoryArena(_config);
41+
int size = 2 * 1024; // 2 KB (< 4 KB Threshold)
42+
43+
var handle = arena.Allocate(size);
44+
45+
Assert.IsTrue(handle.Id > 0, "Allocations under the size threshold must receive a positive FastLane ID.");
46+
Assert.IsFalse(handle.IsInvalid, "Returned handle must declare proof of life.");
47+
}
48+
49+
[TestMethod]
50+
public void Allocate_SizeAboveThreshold_RoutesToSlowLane()
51+
{
52+
var arena = new MemoryArena(_config);
53+
int size = 8 * 1024; // 8 KB (> 4 KB Threshold)
54+
55+
var handle = arena.Allocate(size);
56+
57+
Assert.IsTrue(handle.Id < 0, "Allocations over the size threshold must receive a negative SlowLane ID.");
58+
}
59+
60+
[TestMethod]
61+
public void Allocate_SizeSpansEntireFastLane_FallbackRoutesToSlowLane()
62+
{
63+
var arena = new MemoryArena(_config);
64+
// Request size safely underneath the threshold boundary routing rule,
65+
// but configure it to request more space than the entire FastLane block contains.
66+
var tightConfig = new MemoryManagerConfig
67+
{
68+
FastLaneSize = 1024,
69+
SlowLaneSize = 64 * 1024,
70+
Threshold = 2048 // Rules technically point to FastLane
71+
};
72+
var smallArena = new MemoryArena(tightConfig);
73+
74+
var handle = smallArena.Allocate(1500);
75+
76+
Assert.IsTrue(handle.Id < 0, "If the FastLane cannot fit the requested block size, the orchestrator must fallback to the SlowLane.");
77+
}
78+
79+
[TestMethod]
80+
public void Allocate_BothLanesExhausted_ThrowsOutOfMemoryException()
81+
{
82+
var arena = new MemoryArena(_config);
83+
int impossibleSize = 2 * 1024 * 1024; // 2 MB (Exceeds total system capacity allocation bounds)
84+
85+
Assert.ThrowsException<OutOfMemoryException>(() =>
86+
{
87+
arena.Allocate(impossibleSize);
88+
}, "Requesting more memory than total workspace partitions allow must throw an explicit OutOfMemoryException.");
89+
}
90+
91+
#endregion
92+
93+
#region --- SECTION 2: SYNTACTIC SUGAR & EXTENSION STRUCT VALIDATION ---
94+
95+
[TestMethod]
96+
public void Extensions_StoreAndGetPrimitive_MaintainsValueFlawlessly()
97+
{
98+
var arena = new MemoryArena(_config);
99+
double diagnosticValue = 12345.67890;
100+
101+
var handle = arena.Store(diagnosticValue);
102+
double retrievedValue = arena.Get<double>(handle);
103+
104+
Assert.AreEqual(diagnosticValue, retrievedValue, "Primitive type serialization extensions must guarantee bitwise alignment reading back data values.");
105+
}
106+
107+
[TestMethod]
108+
public void Extensions_AllocateArrayAndBulkSetSpan_ValidatesDataBoundaries()
109+
{
110+
var arena = new MemoryArena(_config);
111+
int[] sourceData = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
112+
113+
var handle = arena.AllocateArray<int>(sourceData.Length);
114+
arena.BulkSet(handle, sourceData.AsSpan());
115+
116+
var outputSpan = arena.GetSpan<int>(handle, sourceData.Length);
117+
118+
CollectionAssert.AreEqual(sourceData, outputSpan.ToArray(), "Bulk span transfers onto flat allocated structural block memory coordinates failed data parity checks.");
119+
}
120+
121+
[TestMethod]
122+
public void Extensions_BulkSetOutOfBoundsPayload_ThrowsArgumentException()
123+
{
124+
var arena = new MemoryArena(_config);
125+
var handle = arena.AllocateArray<short>(5); // Space reserved for 10 bytes
126+
short[] overflowingPayload = { 1, 2, 3, 4, 5, 6, 7, 8 }; // Payload size requirements = 16 bytes
127+
128+
Assert.ThrowsException<ArgumentException>(() =>
129+
{
130+
arena.BulkSet<short>(handle, overflowingPayload);
131+
}, "Writing an array payload that exceeds the structural entry allocation size boundaries must throw an explicit ArgumentException.");
132+
}
133+
134+
[TestMethod]
135+
public void Extensions_StoreStringUTF8_DecodesAccurately()
136+
{
137+
var arena = new MemoryArena(_config);
138+
string initialText = "Wayfarer Memory Manager Test String Token #2026!";
139+
140+
var handle = arena.StoreString(initialText);
141+
var entry = arena.GetEntry(handle);
142+
143+
var payloadSpan = arena.GetSpan<byte>(handle, entry.Size);
144+
string reconstitutedText = System.Text.Encoding.UTF8.GetString(payloadSpan);
145+
146+
Assert.AreEqual(initialText, reconstitutedText, "String serialization extensions must accurately marshal strings to UTF8 workspace tracks.");
147+
}
148+
149+
#endregion
150+
151+
#region --- SECTION 3: ADVANCED MIGRATION & REVERSAL STRESS ---
152+
153+
[TestMethod]
154+
public void MoveSlowToFast_ValidMigration_TransfersDataAndFreesOldSlot()
155+
{
156+
var arena = new MemoryArena(_config);
157+
var baselineStruct = new SampleMetric { IdField = 99, Velocity = 451.2f };
158+
159+
var slowHandle = arena.Allocate(8 * 1024, AllocationPriority.Critical, AllocationHints.None);
160+
arena.Get<SampleMetric>(slowHandle) = baselineStruct;
161+
162+
Assert.IsTrue(slowHandle.Id < 0, "Pre-condition failure: test value must start inside SlowLane territory.");
163+
164+
// 2. Migrate the layout structural record upward into the FastLane
165+
var fastHandle = arena.MoveSlowToFast(slowHandle);
166+
167+
// 3. Assert structural position flipping properties
168+
Assert.IsTrue(fastHandle.Id > 0, "The newly assigned structural reference handle must point to the FastLane namespace.");
169+
170+
var migratedStruct = arena.Get<SampleMetric>(fastHandle);
171+
Assert.AreEqual(baselineStruct.IdField, migratedStruct.IdField, "Data properties dropped or tracking corruption noted during lane promotion.");
172+
Assert.AreEqual(baselineStruct.Velocity, migratedStruct.Velocity, "Floating point alignment errors inside structure allocation layout shifts.");
173+
174+
// 4. Assert the old SlowLane handles are cleanly destroyed
175+
Assert.ThrowsException<InvalidOperationException>(() =>
176+
{
177+
arena.Resolve(slowHandle);
178+
}, "Resolving the old slow lane index handle after execution transfers must trigger invalid token failures.");
179+
}
180+
181+
[TestMethod]
182+
public void MoveSlowToFast_PassingPositiveIdHandle_ThrowsArgumentException()
183+
{
184+
var arena = new MemoryArena(_config);
185+
var fastHandle = arena.Store(42);
186+
187+
Assert.ThrowsException<ArgumentException>(() =>
188+
{
189+
arena.MoveSlowToFast(fastHandle);
190+
}, "Invoking a slow-to-fast migration using a fast handle identifier must crash validation steps immediately.");
191+
}
192+
193+
#endregion
194+
195+
#region --- SECTION 4: CONCURRENCY LOCKOUT & MULTITHREAD STRESS ---
196+
197+
[TestMethod]
198+
public void Concurrency_ParallelAllocationsAndBackgroundCompaction_MaintainsStateSanity()
199+
{
200+
var threadedConfig = new MemoryManagerConfig
201+
{
202+
FastLaneSize = 512 * 1024,
203+
SlowLaneSize = 2 * 1024 * 1024,
204+
Threshold = 512,
205+
EnableAutoCompaction = false
206+
};
207+
var arena = new MemoryArena(threadedConfig);
208+
209+
int taskCount = 8;
210+
int allocationsPerTask = 100;
211+
var barrier = new Barrier(taskCount + 1); // Blocks synched tracks + 1 compaction thread
212+
213+
var allocatedHandles = new System.Collections.Concurrent.ConcurrentBag<MemoryHandle>();
214+
215+
// Thread Loop A: Concurrent allocations executing against shared arena tracks
216+
var allocationTasks = Enumerable.Range(0, taskCount).Select(t => Task.Run(() =>
217+
{
218+
barrier.SignalAndWait(); // Synchronize all workers for high contention
219+
for (int i = 0; i < allocationsPerTask; i++)
220+
{
221+
var handle = arena.AllocateAndStore(i + (t * 1000));
222+
allocatedHandles.Add(handle);
223+
224+
// Simulate processing cycles by resolving and updating values immediately
225+
ref int currentVal = ref arena.Get<int>(handle);
226+
currentVal += 5;
227+
}
228+
})).ToList();
229+
230+
// Thread Loop B: Trigger active maintenance cycles concurrently during operations
231+
var compactionTask = Task.Run(() =>
232+
{
233+
barrier.SignalAndWait();
234+
for (int i = 0; i < 5; i++)
235+
{
236+
arena.RunMaintenanceCycle();
237+
arena.CompactAll();
238+
Thread.Sleep(5); // Force brief scheduling gaps to hit race conditions
239+
}
240+
});
241+
242+
// Wait for full work execution completion
243+
Task.WaitAll(allocationTasks.Concat(new[] { compactionTask }).ToArray());
244+
245+
// Post-execution data verification block
246+
Assert.AreEqual(taskCount * allocationsPerTask, allocatedHandles.Count, "Total completed allocation counts do not match configured expectations.");
247+
248+
foreach (var handle in allocatedHandles)
249+
{
250+
// Every resolved int value should remain accessible and valid
251+
int storedVal = arena.Get<int>(handle);
252+
Assert.IsTrue(storedVal >= 5, "Unsynchronized background data sweeps corrupted active memory data footprints.");
253+
}
254+
}
255+
256+
#endregion
257+
258+
#region --- SECTION 5: SAFETY BOUNDARIES & EXPIRED TOKENS ---
259+
260+
[TestMethod]
261+
public void Security_InvalidHandleIdentifiers_ThrowException()
262+
{
263+
var arena = new MemoryArena(_config);
264+
var invalidHandle = new MemoryHandle(0, 1, arena.FastLane); // 0 token boundary rule
265+
266+
Assert.ThrowsException<InvalidOperationException>(() => arena.Resolve(invalidHandle));
267+
Assert.ThrowsException<InvalidOperationException>(() => arena.Free(invalidHandle));
268+
Assert.ThrowsException<InvalidOperationException>(() => arena.GetEntry(invalidHandle));
269+
}
270+
271+
[TestMethod]
272+
public void Security_TamperedVersionHandle_ThrowsAccessViolationException()
273+
{
274+
var arena = new MemoryArena(_config);
275+
var trueHandle = arena.Store(999);
276+
277+
// Construct a fake handle targeting the exact same ID slot tracking address,
278+
// but inject a spoofed generational token sequence signature.
279+
var forgedHandle = new MemoryHandle(trueHandle.Id, (uint)(trueHandle.Version + 5), arena.FastLane);
280+
281+
Assert.ThrowsException<AccessViolationException>(() =>
282+
{
283+
arena.Get<int>(forgedHandle);
284+
}, "Submitting expired, stale, or tampered handles must trigger access validation crashes.");
285+
}
286+
287+
#endregion
288+
289+
#region --- HELPER STRUCTURES ---
290+
291+
private struct SampleMetric
292+
{
293+
public long IdField;
294+
public float Velocity;
295+
}
296+
297+
#endregion
298+
}
299+
}

MemoryManager/MemoryArena.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,41 @@ public unsafe MemoryHandle AllocateAndStore<T>(
229229
}
230230
}
231231

232+
/// <summary>
233+
/// Copies a source span of data atomically into the unmanaged memory referenced by the handle.
234+
/// Guarantees thread-safety against background compaction sweeps during bulk memory transfers.
235+
/// </summary>
236+
/// <typeparam name="T">The unmanaged type of elements to copy.</typeparam>
237+
/// <param name="handle">Handle pointing to the destination unmanaged memory block.</param>
238+
/// <param name="source">The read-only data span to copy from.</param>
239+
/// <exception cref="InvalidOperationException">Thrown if the handle is invalid.</exception>
240+
/// <exception cref="ArgumentException">Thrown if the destination allocation partition is too small.</exception>
241+
public unsafe void BulkSet<T>(MemoryHandle handle, ReadOnlySpan<T> source) where T : unmanaged
242+
{
243+
if (handle.IsInvalid)
244+
throw new InvalidOperationException("Invalid handle");
245+
246+
lock (_lock) // ATOMIC BOUNDARY: No background compaction can sneak in while this block executes
247+
{
248+
// 1. Fetch metadata safely
249+
var entry = handle.Id > 0 ? FastLane.GetEntry(handle) : SlowLane.GetEntry(handle);
250+
var requiredSize = Unsafe.SizeOf<T>() * source.Length;
251+
252+
if (entry.Size < requiredSize)
253+
throw new ArgumentException(
254+
$"Destination allocation ({entry.Size} bytes) is too small for source data ({requiredSize} bytes).");
255+
256+
// 2. Resolve pointer within the same synchronized frame
257+
var dest = (handle.Id > 0 ? FastLane.Resolve(handle) : SlowLane.Resolve(handle)).ToPointer();
258+
259+
// 3. Execute bitwise copy while holding the system lockout lock
260+
fixed (T* srcPtr = source)
261+
{
262+
System.Buffer.MemoryCopy(srcPtr, dest, entry.Size, requiredSize);
263+
}
264+
}
265+
}
266+
232267
/// <summary>
233268
/// Allocates a contiguous block of memory from the appropriate lane based on size threshold policies.
234269
/// </summary>

0 commit comments

Comments
 (0)