diff --git a/TIASshot.Tests/BitmapUtilTests.cs b/TIASshot.Tests/BitmapUtilTests.cs index 0198304..0e29218 100644 --- a/TIASshot.Tests/BitmapUtilTests.cs +++ b/TIASshot.Tests/BitmapUtilTests.cs @@ -262,6 +262,44 @@ } // --------------------------------------------------------------- + // 仕様8: LockBits が例外を投げた場合も複製先 Bitmap を解放する + // --------------------------------------------------------------- + + /// + /// FastClone_入力が既にロック中でLockBitsが失敗_複製先Bitmapのネイティブメモリを残さない: + /// 入力を先に LockBits しておくと FastClone 内の src.LockBits が例外を投げる. + /// このとき確保済みの複製先 Bitmap(大きい画素バッファ)を Dispose せずに例外を伝播すると, + /// ネイティブメモリが GC のファイナライザまで残る.GC を挟まずにプロセスの + /// プライベートメモリ増分を測り,複製先バッファ相当(約 64MB)が残っていないことを確認する. + /// + [Test] + public void FastClone_入力が既にロック中でLockBitsが失敗_複製先Bitmapのネイティブメモリを残さない() { + const int size = 4096; // 32bppArgb で 4096*4096*4 = 64MB + using (var src = new Bitmap(size, size, PixelFormat.Format32bppArgb)) { + var rect = new Rectangle(0, 0, size, size); + BitmapData held = src.LockBits(rect, ImageLockMode.ReadOnly, src.PixelFormat); + try { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + long before = System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64; + + Assert.Throws((Action)(() => BitmapUtil.FastClone(src)), + "入力がロック中なら LockBits の例外が呼び出し元へ伝播すること"); + + long after = System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64; + long leaked = after - before; + Assert.That(leaked, Is.LessThan(32L * 1024 * 1024), + $"例外時に複製先 Bitmap が解放されていない(プライベートメモリ増分 {leaked / (1024 * 1024)}MB)"); + } finally { + src.UnlockBits(held); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + } + + // --------------------------------------------------------------- // ヘルパー // ---------------------------------------------------------------