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();
+ }
+ }
+ }
+
+ // ---------------------------------------------------------------
// ヘルパー
// ---------------------------------------------------------------