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);
}
}
}
}