using Amphora; using System; using System.Runtime.InteropServices; using UnitTestSharp; namespace Blacklight.Core.UnitTests { // Nothing here touches the real clipboard, which belongs to whoever is running the tests. public class GlobalUnicodeTextTests : TestFixture { public const string Grin = "\U0001F600"; public static IntPtr BlockOf(string text) { return WinFormsClipboard.AllocateUnicodeText(new StringChunks(text)); } // A block of the given size in chars, holding the text with nothing but zeros after it. public static IntPtr BlockOfChars(string text, int capacity) { IntPtr block = NativeMethods.GlobalAlloc(NativeMethods.GMEM_MOVEABLE, new UIntPtr((uint)capacity * 2)); var chars = new char[capacity]; text.CopyTo(0, chars, 0, text.Length); IntPtr locked = NativeMethods.GlobalLock(block); Marshal.Copy(chars, 0, locked, capacity); NativeMethods.GlobalUnlock(block); return block; } public static string Digits(int count) { var chars = new char[count]; for (int i = 0; i < count; ++i) { chars[i] = (char)('0' + i % 10); } return new string(chars); } public class LockTests : TestFixture { public void ReadsTheTextUpToItsTerminator() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(5, text.Length); text.Dispose(); NativeMethods.GlobalFree(block); } public void ABlockLargerThanItsText_EndsAtTheFirstNul() { IntPtr block = BlockOfChars("hi", capacity: 7); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(2, text.Length); text.Dispose(); NativeMethods.GlobalFree(block); } public void ABlockWithNoTerminator_EndsWithTheBlock() { IntPtr block = BlockOfChars("abcd", capacity: 4); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(4, text.Length); text.Dispose(); NativeMethods.GlobalFree(block); } public void AnEmptyText_HasNoLength() { IntPtr block = BlockOf(""); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(0, text.Length); text.Dispose(); NativeMethods.GlobalFree(block); } public void ABlockThatCantBeLocked_GivesNull() { CheckNull(GlobalUnicodeText.Lock(IntPtr.Zero, null)); } } public class GetChunkTests : TestFixture { public void AShortText_IsOneChunk() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual("hello", text.GetChunk(0).ToString()); text.Dispose(); NativeMethods.GlobalFree(block); } public void ALongText_ComesInChunksOfTheChunkSize() { string content = Digits(GlobalUnicodeText.ChunkSize * 2 + 5); IntPtr block = BlockOf(content); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(GlobalUnicodeText.ChunkSize, text.GetChunk(0).Length); CheckEqual(GlobalUnicodeText.ChunkSize, text.GetChunk(GlobalUnicodeText.ChunkSize).Length); CheckEqual(5, text.GetChunk(GlobalUnicodeText.ChunkSize * 2).Length); text.Dispose(); NativeMethods.GlobalFree(block); } public void AllTheChunks_JoinToTheText() { string content = Digits(GlobalUnicodeText.ChunkSize * 2 + 5); IntPtr block = BlockOf(content); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual(content, text.ToText()); text.Dispose(); NativeMethods.GlobalFree(block); } public void AStartInsideTheText_ReadsFromThere() { IntPtr block = BlockOf("hello, world"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual("world", text.GetChunk(7).ToString()); text.Dispose(); NativeMethods.GlobalFree(block); } public void AChunkEdgeInsideAPair_JoinsIntact() { string content = new string('x', GlobalUnicodeText.ChunkSize - 1) + Grin + "y"; IntPtr block = BlockOf(content); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); Check(char.IsHighSurrogate(text.GetChunk(0)[GlobalUnicodeText.ChunkSize - 1])); CheckEqual(content, text.ToText()); text.Dispose(); NativeMethods.GlobalFree(block); } public void TheTextBeforeTheTerminator_IsAllThatIsRead() { IntPtr block = BlockOfChars("hi", capacity: 7); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); CheckEqual("hi", text.ToText()); text.Dispose(); NativeMethods.GlobalFree(block); } public void AStartAtTheLength_Throws() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); try { CheckThrow(typeof(ArgumentOutOfRangeException)); text.GetChunk(5); } finally { text.Dispose(); NativeMethods.GlobalFree(block); } } public void ANegativeStart_Throws() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); try { CheckThrow(typeof(ArgumentOutOfRangeException)); text.GetChunk(-1); } finally { text.Dispose(); NativeMethods.GlobalFree(block); } } public void AnEmptyText_HasNoChunk() { IntPtr block = BlockOf(""); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); try { CheckThrow(typeof(ArgumentOutOfRangeException)); text.GetChunk(0); } finally { text.Dispose(); NativeMethods.GlobalFree(block); } } public void AfterDispose_Throws() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); text.Dispose(); try { CheckThrow(typeof(ObjectDisposedException)); text.GetChunk(0); } finally { NativeMethods.GlobalFree(block); } } } public class DisposeTests : TestFixture { public void UnlocksTheBlock() { IntPtr block = BlockOf("hello"); GlobalUnicodeText text = GlobalUnicodeText.Lock(block, null); text.Dispose(); // Unlocking a block that is already unlocked fails. CheckFalse(NativeMethods.GlobalUnlock(block)); NativeMethods.GlobalFree(block); } public void CallsTheCallbackOnce_HoweverOftenItIsDisposed() { IntPtr block = BlockOf("hello"); int calls = 0; GlobalUnicodeText text = GlobalUnicodeText.Lock(block, () => ++calls); text.Dispose(); text.Dispose(); CheckEqual(1, calls); NativeMethods.GlobalFree(block); } public void DoesNotCallTheCallbackUntilDisposed() { IntPtr block = BlockOf("hello"); int calls = 0; GlobalUnicodeText text = GlobalUnicodeText.Lock(block, () => ++calls); CheckEqual(0, calls); text.Dispose(); NativeMethods.GlobalFree(block); } public void CallsTheCallbackAfterUnlocking() { IntPtr block = BlockOf("hello"); bool unlockedWhenCalled = false; GlobalUnicodeText text = GlobalUnicodeText.Lock(block, () => { unlockedWhenCalled = !NativeMethods.GlobalUnlock(block); }); text.Dispose(); CheckTrue(unlockedWhenCalled); NativeMethods.GlobalFree(block); } } } }