Skip to content

Commit 90fe6f6

Browse files
author
LoneWandererProductions
committed
Some smaller fixes
1 parent da68d09 commit 90fe6f6

4 files changed

Lines changed: 270 additions & 35 deletions

File tree

CommonExtendedObjectsTests/IntListTests.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -314,8 +314,8 @@ public void BenchmarkAddPerformance()
314314

315315
swUnmanaged.Stop();
316316

317-
Console.WriteLine($"List<int>.Add: {swList.ElapsedMilliseconds} ms");
318-
Console.WriteLine($"UnmanagedIntList.Add: {swUnmanaged.ElapsedMilliseconds} ms");
317+
Trace.WriteLine($"List<int>.Add: {swList.ElapsedMilliseconds} ms");
318+
Trace.WriteLine($"UnmanagedIntList.Add: {swUnmanaged.ElapsedMilliseconds} ms");
319319

320320
unmanaged.Dispose();
321321

@@ -324,7 +324,6 @@ public void BenchmarkAddPerformance()
324324
"UnmanagedIntList.Add is too slow.");
325325
}
326326

327-
328327
/// <summary>
329328
/// Benchmarks the remove performance.
330329
/// </summary>
@@ -334,6 +333,7 @@ public void BenchmarkRemovePerformance()
334333
var list = new List<int>();
335334
var unmanaged = new UnmanagedIntList();
336335

336+
// Setup Phase
337337
for (var i = 0; i < ItemCount; i++)
338338
{
339339
list.Add(i);
@@ -343,30 +343,40 @@ public void BenchmarkRemovePerformance()
343343
const int removeCount = 1000;
344344
var removeIndex = list.Count / 2; // fixed middle index
345345

346+
// WARMUP: Force JIT to compile the methods and Stopwatch before measuring
347+
list.RemoveAt(list.Count - 1);
348+
unmanaged.RemoveAt(unmanaged.Length - 1);
349+
350+
// CLEAN SLATE: Force GC to clean up the arrays abandoned during the 'Add' phase
351+
GC.Collect();
352+
GC.WaitForPendingFinalizers();
353+
354+
// 1. Measure List<int>
346355
var swList = Stopwatch.StartNew();
347356
for (var i = 0; i < removeCount; i++)
348357
{
349358
list.RemoveAt(removeIndex);
350359
}
351-
352360
swList.Stop();
353361

362+
// CLEAN SLATE AGAIN
363+
GC.Collect();
364+
GC.WaitForPendingFinalizers();
365+
366+
// 2. Measure UnmanagedIntList
354367
var swUnmanaged = Stopwatch.StartNew();
355368
for (var i = 0; i < removeCount; i++)
356369
{
357370
unmanaged.RemoveAt(removeIndex);
358371
}
359-
360372
swUnmanaged.Stop();
361373

362-
Console.WriteLine($"List<int>.RemoveAt: {swList.ElapsedMilliseconds} ms");
363-
Console.WriteLine($"UnmanagedIntList.RemoveAt: {swUnmanaged.ElapsedMilliseconds} ms");
364-
365-
unmanaged.Dispose();
374+
// Use TotalMilliseconds for high-precision decimal values!
375+
Trace.WriteLine($"List<int>.RemoveAt: {swList.Elapsed.TotalMilliseconds:F4} ms");
376+
Trace.WriteLine($"UnmanagedIntList.RemoveAt: {swUnmanaged.Elapsed.TotalMilliseconds:F4} ms");
366377

367-
//TODO fix this!
368378
#if DEBUG
369-
//Assert.AreEqual(list.Count, unmanaged.Length);
379+
Assert.AreEqual(list.Count, unmanaged.Length, "The lists should have the exact same number of elements.");
370380
#endif
371381
}
372382

CommonLibraryGuiTests/BitmapComparisonTests.cs

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Drawing.Imaging;
1313
using System.IO;
1414
using System.Threading;
15+
using System.Windows;
1516
using System.Windows.Media.Imaging;
1617
using Imaging;
1718
using NUnit.Framework;
@@ -128,57 +129,105 @@ private static double MeasureNativeBitmapRendering()
128129
[Apartment(ApartmentState.STA)]
129130
public void CompareRenderingSpeedsForMultipleUpdates()
130131
{
131-
// Number of updates to simulate a slideshow
132132
const int updateCount = 100;
133+
const double toleranceMultiplier = 1.10; // Allow Native to be up to 10% slower due to random noise
133134

134-
// Measure time for Media.Image with conversion
135+
// 1. WARMUP: Run once to force JIT compilation and assembly loading
136+
MeasureMediaImageRendering(1);
137+
MeasureNativeBitmapRendering(1);
138+
139+
// 2. FORCE GC: Ensure a clean slate before measuring
140+
GC.Collect();
141+
GC.WaitForPendingFinalizers();
142+
143+
// Measure time for Media.Image
135144
var mediaImageTime = MeasureMediaImageRendering(updateCount);
136145

146+
// FORCE GC again
147+
GC.Collect();
148+
GC.WaitForPendingFinalizers();
149+
137150
// Measure time for NativeBitmapDisplay
138151
var nativeDisplayTime = MeasureNativeBitmapRendering(updateCount);
139152

140-
TestContext.WriteLine($"Media.Image Time for {updateCount} updates: {mediaImageTime}ms");
141-
TestContext.WriteLine($"NativeBitmapDisplay Time for {updateCount} updates: {nativeDisplayTime}ms");
153+
TestContext.WriteLine($"Media.Image Time: {mediaImageTime}ms");
154+
TestContext.WriteLine($"NativeBitmapDisplay Time: {nativeDisplayTime}ms");
142155

143-
Assert.IsTrue(nativeDisplayTime <= mediaImageTime,
144-
"NativeBitmapDisplay should be as fast or faster than Media.Image rendering.");
156+
// 3. TOLERANCE: Don't assert strict <=, allow a reasonable buffer
157+
Assert.IsTrue(nativeDisplayTime <= (mediaImageTime * toleranceMultiplier),
158+
$"NativeBitmapDisplay ({nativeDisplayTime}ms) should be roughly as fast or faster than Media.Image ({mediaImageTime}ms).");
145159
}
146160

147161
/// <summary>
148-
/// Measures the media image rendering.
162+
/// Measures the native bitmap rendering.
149163
/// </summary>
150164
/// <param name="updateCount">The update count.</param>
151-
/// <returns>elapsed Time</returns>
152-
private long MeasureMediaImageRendering(int updateCount)
165+
/// <returns></returns>
166+
public long MeasureNativeBitmapRendering(int updateCount)
153167
{
154-
var stopwatch = Stopwatch.StartNew();
168+
// 1. Initialize the UI Control
169+
var display = new NativeBitmapDisplay();
155170

156-
for (var i = 0; i < updateCount; i++)
171+
// 2. Initialize your Shared Memory (DirectBitmap)
172+
// Let's assume a 800x600 canvas
173+
using var directBitmap = new DirectBitmap(800, 600, Color.Black);
174+
175+
// 3. THE HANDSHAKE (Do this ONLY ONCE)
176+
// This passes the shared memory canvas to the WinForms picture box.
177+
display.Bitmap = directBitmap.UnsafeBitmap;
178+
179+
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
180+
181+
// 4. THE RENDER LOOP
182+
for (int i = 0; i < updateCount; i++)
157183
{
158-
// Convert Bitmap to BitmapSource
159-
var bitmapSource = BitmapToBitmapSource(_testBitmap);
184+
// A. Mutate the pixels directly in RAM (Zero allocations!)
185+
// In a real app, this might be copying a video frame array.
186+
// Here, we just draw a line to simulate work.
187+
directBitmap.DrawHorizontalLine(0, i % 600, 800, Color.Red);
160188

161-
// Simulate rendering in Media.Image
162-
_ = new System.Windows.Controls.Image { Source = bitmapSource };
189+
// B. Tell the UI to repaint what is in memory
190+
display.InvalidateCanvas();
191+
192+
// (Optional: In a UI test, you sometimes need to pump the WPF Dispatcher
193+
// here to force it to actually draw to the screen immediately, otherwise
194+
// it just queues up 100 invalidate requests and draws once at the end).
195+
DoEvents(display);
163196
}
164197

165198
stopwatch.Stop();
166199
return stopwatch.ElapsedMilliseconds;
167200
}
168201

169202
/// <summary>
170-
/// Measures the native bitmap rendering.
203+
/// Helper method to force WPF to actually render during a synchronous test loop
204+
/// </summary>
205+
/// <param name="control">The control.</param>
206+
private static void DoEvents(DependencyObject control)
207+
{
208+
var frame = new System.Windows.Threading.DispatcherFrame();
209+
control.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(delegate {
210+
frame.Continue = false;
211+
}));
212+
System.Windows.Threading.Dispatcher.PushFrame(frame);
213+
}
214+
215+
/// <summary>
216+
/// Measures the media image rendering.
171217
/// </summary>
172218
/// <param name="updateCount">The update count.</param>
173219
/// <returns>elapsed Time</returns>
174-
private long MeasureNativeBitmapRendering(int updateCount)
220+
private long MeasureMediaImageRendering(int updateCount)
175221
{
176222
var stopwatch = Stopwatch.StartNew();
177223

178224
for (var i = 0; i < updateCount; i++)
179225
{
180-
// Simulate rendering in NativeBitmapDisplay
181-
_ = new NativeBitmapDisplay { Bitmap = _testBitmap };
226+
// Convert Bitmap to BitmapSource
227+
var bitmapSource = BitmapToBitmapSource(_testBitmap);
228+
229+
// Simulate rendering in Media.Image
230+
_ = new System.Windows.Controls.Image { Source = bitmapSource };
182231
}
183232

184233
stopwatch.Stop();
@@ -189,7 +238,7 @@ private long MeasureNativeBitmapRendering(int updateCount)
189238
/// Bitmaps to bitmap source.
190239
/// </summary>
191240
/// <param name="bitmap">The bitmap.</param>
192-
/// <returns></returns>
241+
/// <returns>BitmapSource</returns>
193242
private static BitmapSource BitmapToBitmapSource(Image bitmap)
194243
{
195244
using var memoryStream = new MemoryStream();
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CommonLibraryTests
4+
* FILE: FileObserverTests.cs
5+
* PURPOSE: Some tests for FileObserver. Not exhaustive, but a good start to validate the core functionality and catch regressions.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
10+
using System;
11+
using System.IO;
12+
using System.Threading;
13+
using System.Threading.Tasks;
14+
using Microsoft.VisualStudio.TestTools.UnitTesting;
15+
using FileHandler;
16+
17+
namespace CommonLibraryTests
18+
{
19+
[TestClass]
20+
public class FileObserverTests
21+
{
22+
/// <summary>
23+
/// The temporary directory
24+
/// </summary>
25+
private string _tempDirectory = string.Empty;
26+
27+
/// <summary>
28+
/// Setups this instance.
29+
/// </summary>
30+
[TestInitialize]
31+
public void Setup()
32+
{
33+
// Runs before EVERY test to create a pristine, isolated folder
34+
_tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
35+
Directory.CreateDirectory(_tempDirectory);
36+
}
37+
38+
/// <summary>
39+
/// Cleanups this instance.
40+
/// </summary>
41+
[TestCleanup]
42+
public void Cleanup()
43+
{
44+
// Runs after EVERY test to wipe the folder and leave no trace
45+
if (Directory.Exists(_tempDirectory))
46+
{
47+
Directory.Delete(_tempDirectory, true);
48+
}
49+
}
50+
51+
/// <summary>
52+
/// Constructors the when directory does not exist throws directory not found exception.
53+
/// </summary>
54+
[TestMethod]
55+
public void Constructor_WhenDirectoryDoesNotExist_ThrowsDirectoryNotFoundException()
56+
{
57+
// Arrange
58+
var badPath = Path.Combine(_tempDirectory, "NonExistentFolder");
59+
60+
// Act & Assert
61+
Assert.ThrowsException<DirectoryNotFoundException>(() => new FileObserver(badPath));
62+
}
63+
64+
/// <summary>
65+
/// Creates the event when file is created fires successfully.
66+
/// </summary>
67+
[TestMethod]
68+
public async Task CreatedEvent_WhenFileIsCreated_FiresSuccessfully()
69+
{
70+
// Arrange
71+
using var observer = new FileObserver(_tempDirectory);
72+
var eventFired = false;
73+
74+
observer.Created += args =>
75+
{
76+
eventFired = true;
77+
return Task.CompletedTask;
78+
};
79+
80+
// Act
81+
observer.Start();
82+
83+
// Create a file to trigger the OS event
84+
var testFile = Path.Combine(_tempDirectory, "test.txt");
85+
await File.WriteAllTextAsync(testFile, "Hello World");
86+
87+
// Give the OS and the observer a moment to process the event
88+
await Task.Delay(100);
89+
90+
// Assert
91+
Assert.IsTrue(eventFired, "The Created event did not fire.");
92+
}
93+
94+
/// <summary>
95+
/// Changes the event when spammed debounces and fires only once.
96+
/// </summary>
97+
[TestMethod]
98+
public async Task ChangedEvent_WhenSpammed_DebouncesAndFiresOnlyOnce()
99+
{
100+
// Arrange
101+
using var observer = new FileObserver(_tempDirectory);
102+
var testFile = Path.Combine(_tempDirectory, "spam.txt");
103+
104+
// Create the initial file before we start watching so it doesn't trigger 'Created'
105+
await File.WriteAllTextAsync(testFile, "Initial Setup");
106+
107+
var eventCount = 0;
108+
observer.Changed += args =>
109+
{
110+
Interlocked.Increment(ref eventCount); // Thread-safe counter
111+
return Task.CompletedTask;
112+
};
113+
114+
observer.Start();
115+
116+
// Act: Spam the file with 5 rapid saves
117+
for (var i = 0; i < 5; i++)
118+
{
119+
using var fs = new FileStream(testFile, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
120+
using var writer = new StreamWriter(fs);
121+
await writer.WriteAsync($"Spam {i}");
122+
}
123+
124+
// Wait just slightly longer than your 200ms debounce limit
125+
await Task.Delay(300);
126+
127+
// Assert
128+
// Without your debounce logic, this would be 5 (or more, due to OS quirks).
129+
// With your logic, it should be exactly 1.
130+
Assert.AreEqual(1, eventCount);
131+
}
132+
133+
/// <summary>
134+
/// Runs the until cancelled asynchronous when token is cancelled exits gracefully.
135+
/// </summary>
136+
[TestMethod]
137+
public async Task RunUntilCancelledAsync_WhenTokenIsCancelled_ExitsGracefully()
138+
{
139+
// Arrange
140+
using var observer = new FileObserver(_tempDirectory);
141+
using var cts = new CancellationTokenSource();
142+
143+
// Act
144+
var runTask = observer.RunUntilCancelledAsync(cts.Token);
145+
146+
// Cancel it almost immediately
147+
cts.Cancel();
148+
149+
// Await the task. If it throws an unhandled exception, the test fails.
150+
await runTask;
151+
152+
// Assert
153+
Assert.IsTrue(runTask.IsCompletedSuccessfully);
154+
}
155+
}
156+
}

0 commit comments

Comments
 (0)