using System.Threading; using Thread = System.Threading.Thread; using WhiteMagic; using WhiteMagic.Native; using WhiteMagic.Thread; using Xunit; namespace WhiteMagicTest.Thread; /// /// Tests for open/suspend/resume and context round-trip. /// public sealed class RemoteThreadTests { [Fact] public void Open_by_id_succeeds_for_current_thread() { using var magic = Magic.OpenInProcess(); int currentId = (int)NativeMethods.GetCurrentThreadId(); using var thread = new RemoteThread(magic.Memory, currentId); Assert.Equal(currentId, thread.Id); } [Fact] public void Suspend_returns_prior_count_and_stops_worker() { using var magic = Magic.OpenInProcess(); using var cts = new CancellationTokenSource(); var started = new ManualResetEventSlim(false); int osThreadId = 0; var worker = new System.Threading.Thread(() => { osThreadId = (int)NativeMethods.GetCurrentThreadId(); started.Set(); while (!cts.IsCancellationRequested) System.Threading.Thread.Sleep(10); }); worker.Start(); started.Wait(); try { using var thread = new RemoteThread(magic.Memory, osThreadId); uint prior = thread.Suspend(); Assert.True(prior < 0xFFFFFFFF); cts.Cancel(); // Worker cannot observe cancellation while suspended. Assert.False(worker.Join(100)); thread.Resume(); Assert.True(worker.Join(1000)); } finally { if (worker.IsAlive) { cts.Cancel(); using var thread = new RemoteThread(magic.Memory, osThreadId); thread.Resume(); worker.Join(1000); } } } [Fact] public void Resume_restarts_a_suspended_worker() { using var magic = Magic.OpenInProcess(); using var cts = new CancellationTokenSource(); var started = new ManualResetEventSlim(false); var resumed = new ManualResetEventSlim(false); int osThreadId = 0; var worker = new System.Threading.Thread(() => { osThreadId = (int)NativeMethods.GetCurrentThreadId(); started.Set(); while (!cts.IsCancellationRequested) { resumed.Set(); System.Threading.Thread.Sleep(10); } }); worker.Start(); started.Wait(); try { using var thread = new RemoteThread(magic.Memory, osThreadId); thread.Suspend(); resumed.Reset(); uint prior = thread.Resume(); Assert.True(prior < 0xFFFFFFFF); // Worker must reach the resumed flag again. Assert.True(resumed.Wait(1000)); cts.Cancel(); Assert.True(worker.Join(1000)); } finally { if (worker.IsAlive) { cts.Cancel(); using var thread = new RemoteThread(magic.Memory, osThreadId); thread.Resume(); worker.Join(1000); } } } [Fact] public void GetTeb_returns_managed_teb_for_thread() { using var magic = Magic.OpenInProcess(); int currentId = (int)NativeMethods.GetCurrentThreadId(); using var thread = new RemoteThread(magic.Memory, currentId); using var teb = thread.GetTeb(); Assert.NotEqual(IntPtr.Zero, teb.ReadTebAddress()); } }