using System; using System.Diagnostics; using System.Threading; using UnitTestSharp; namespace TestsForUnitTestSharp { public class ThreadCpuClockTests : TestFixture { private const int BurnMilliseconds = 100; /// /// GetThreadTimes resolves to the scheduler tick (~15.6ms), so a thread that burned nothing still can't be /// asserted at exactly zero. Bounding both directions with the one threshold is what makes busy and idle /// provably distinguishable. /// public static readonly TimeSpan NoiseFloor = TimeSpan.FromMilliseconds(50); public static void BurnCpu() { var elapsed = Stopwatch.StartNew(); double accumulator = 0; while (elapsed.ElapsedMilliseconds < BurnMilliseconds) { accumulator += Math.Sqrt(accumulator + 1); } } /// /// A thread that captures its own clock and then parks, so a reading can be taken before its work starts. /// public class MeasuredThread { private readonly ManualResetEventSlim clockCaptured = new ManualResetEventSlim(false); private readonly ManualResetEventSlim released = new ManualResetEventSlim(false); private readonly Thread thread; public ThreadCpuClock Clock { get; private set; } public MeasuredThread(System.Action work) { thread = new Thread(() => { Clock = ThreadCpuClock.ForCurrentThread(); clockCaptured.Set(); released.Wait(); work(); }); thread.IsBackground = true; thread.Start(); clockCaptured.Wait(); } public void RunToCompletion() { released.Set(); thread.Join(); } public TimeSpan CpuSpentRunning() { TimeSpan before = Clock.TotalProcessorTime.Value; RunToCompletion(); return Clock.TotalProcessorTime.Value - before; } } public class ForCurrentThreadTests : TestFixture { public void ForCurrentThread_Always_ReturnsAClock() { CheckNotNull(ThreadCpuClock.ForCurrentThread()); } public void TotalProcessorTime_ReportsAValue() { CheckNotNull(ThreadCpuClock.ForCurrentThread().TotalProcessorTime); } public void TotalProcessorTime_NewClock_StartsAtZero() { var clock = ThreadCpuClock.ForCurrentThread(); CheckLess(clock.TotalProcessorTime, NoiseFloor); } public void TotalProcessorTime_ThreadBurnsCpu_Increases() { var clock = ThreadCpuClock.ForCurrentThread(); BurnCpu(); CheckGreater(clock.TotalProcessorTime, NoiseFloor); } public void TotalProcessorTime_ThreadSleeps_StaysAtZero() { var clock = ThreadCpuClock.ForCurrentThread(); Thread.Sleep(BurnMilliseconds); CheckLess(clock.TotalProcessorTime, NoiseFloor); } } public class CrossThreadTests : TestFixture { public void TotalProcessorTime_WorkerBurnsCpu_IncreasesWhenReadByAnotherThread() { var worker = new MeasuredThread(BurnCpu); worker.RunToCompletion(); CheckGreater(worker.Clock.TotalProcessorTime, NoiseFloor); } public void TotalProcessorTime_ReaderBurnsCpu_DoesNotChargeTheWorker() { var worker = new MeasuredThread(() => { }); BurnCpu(); worker.RunToCompletion(); CheckLess(worker.Clock.TotalProcessorTime, NoiseFloor); } } } }