// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. #if !NO_REMOTING using System; using System.Collections.Generic; using System.Reactive; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.PlatformServices; #if NUNIT using NUnit.Framework; using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; using TestMethodAttribute = NUnit.Framework.TestAttribute; using TestInitializeAttribute = NUnit.Framework.SetUpAttribute; #else using Microsoft.VisualStudio.TestTools.UnitTesting; #endif namespace ReactiveTests.Tests { static class Exts { public static T Deq(this List l) { var t = l[0]; l.RemoveAt(0); return t; } } [TestClass] public class SystemClockTest { private void Run(CrossAppDomainDelegate a) { var domain = AppDomain.CreateDomain(Guid.NewGuid().ToString(), null, new AppDomainSetup { ApplicationBase = Utils.GetTestBaseDirectory() }); domain.DoCallBack(a); AppDomain.Unload(domain); } [TestMethod] public void PastWork() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var due = now - TimeSpan.FromMinutes(1); var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == now); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void ImmediateWork() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var due = now; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void ShortTermWork() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromSeconds(1) /* rel <= SHORTTERM */; var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void ShortTermWork_Dispose() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromSeconds(1) /* rel <= SHORTTERM */; var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; var d = s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); d.Dispose(); s.SetTime(due); next.Invoke(); Assert.IsFalse(done); }); } [TestMethod] public void ShortTermWork_InaccurateClock() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromSeconds(1); var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, s._queue.Count); var nxt1 = s._queue.Deq(); Assert.IsTrue(s.Now + nxt1.DueTime == due); s.SetTime(due - TimeSpan.FromMilliseconds(500) /* > RETRYSHORT */); nxt1.Invoke(); Assert.AreEqual(1, s._queue.Count); var nxt2 = s._queue.Deq(); Assert.IsTrue(s.Now + nxt2.DueTime == due); s.SetTime(due); nxt2.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void LongTermWork1() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromMinutes(1) /* rel > SHORTTERM */; var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, cal._queue.Count); var work = cal._queue.Deq(); Assert.IsTrue(work.Interval < rel); s.SetTime(s.Now + work.Interval); work.Value._action(work.Value._state); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void LongTermWork2() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromDays(1) /* rel > SHORTTERM and rel * MAXERRORRATIO > SHORTTERM */; var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, cal._queue.Count); var wrk1 = cal._queue.Deq(); Assert.IsTrue(wrk1.Interval < rel); s.SetTime(s.Now + wrk1.Interval); wrk1.Value._action(wrk1.Value._state); // Begin of second long term scheduling Assert.AreEqual(1, cal._queue.Count); var wrk2 = cal._queue.Deq(); Assert.IsTrue(wrk2.Interval < rel); s.SetTime(s.Now + wrk2.Interval); wrk2.Value._action(wrk2.Value._state); // End of second long term scheduling Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void LongTerm_Multiple() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var s = new MyScheduler(); s.SetTime(now); var due1 = now + TimeSpan.FromMinutes(10); var due2 = now + TimeSpan.FromMinutes(30); var due3 = now + TimeSpan.FromMinutes(60); var done1 = false; var done2 = false; var done3 = false; s.Schedule(due2, () => { done2 = true; }); s.Schedule(due1, () => { done1 = true; }); s.Schedule(due3, () => { done3 = true; }); // First CHK Assert.AreEqual(1, cal._queue.Count); var wrk1 = cal._queue.Deq(); var fst = s.Now + wrk1.Interval; Assert.IsTrue(fst < due1); // First TRN s.SetTime(fst); wrk1.Value._action(wrk1.Value._state); // First SHT Assert.AreEqual(1, s._queue.Count); var sh1 = s._queue.Deq(); // Second CHK Assert.AreEqual(1, cal._queue.Count); var wrk2 = cal._queue.Deq(); var snd = s.Now + wrk2.Interval; Assert.IsTrue(snd < due2); // First RUN s.SetTime(due1); sh1.Invoke(); Assert.IsTrue(done1); // Second TRN s.SetTime(snd); wrk2.Value._action(wrk2.Value._state); // Second SHT Assert.AreEqual(1, s._queue.Count); var sh2 = s._queue.Deq(); // Third CHK Assert.AreEqual(1, cal._queue.Count); var wrk3 = cal._queue.Deq(); var trd = s.Now + wrk3.Interval; Assert.IsTrue(trd < due3); // Second RUN s.SetTime(due2); sh2.Invoke(); Assert.IsTrue(done2); // Third TRN s.SetTime(trd); wrk3.Value._action(wrk3.Value._state); // Third SHT Assert.AreEqual(1, s._queue.Count); var sh3 = s._queue.Deq(); // Third RUN s.SetTime(due3); sh3.Invoke(); Assert.IsTrue(done3); }); } [TestMethod] public void LongTerm_Multiple_Dispose() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var s = new MyScheduler(); s.SetTime(now); var due1 = now + TimeSpan.FromMinutes(10); var due2 = now + TimeSpan.FromMinutes(30); var due3 = now + TimeSpan.FromMinutes(60); var done1 = false; var done2 = false; var done3 = false; var d2 = s.Schedule(due2, () => { done2 = true; }); var d1 = s.Schedule(due1, () => { done1 = true; }); var d3 = s.Schedule(due3, () => { done3 = true; }); // First CHK Assert.AreEqual(1, cal._queue.Count); var wrk1 = cal._queue.Deq(); var fst = s.Now + wrk1.Interval; Assert.IsTrue(fst < due1); // First TRN s.SetTime(fst); wrk1.Value._action(wrk1.Value._state); // First DIS d1.Dispose(); // First SHT Assert.AreEqual(1, s._queue.Count); var sh1 = s._queue.Deq(); // Second CHK Assert.AreEqual(1, cal._queue.Count); var wrk2 = cal._queue.Deq(); var snd = s.Now + wrk2.Interval; Assert.IsTrue(snd < due2); // First RUN s.SetTime(due1); sh1.Invoke(); Assert.IsFalse(done1); // Second DIS // Third DIS d2.Dispose(); d3.Dispose(); // Second TRN s.SetTime(snd); wrk2.Value._action(wrk2.Value._state); // Second SHT Assert.AreEqual(1, s._queue.Count); var sh2 = s._queue.Deq(); // Third CHK Assert.AreEqual(1, cal._queue.Count); var wrk3 = cal._queue.Deq(); var trd = s.Now + wrk3.Interval; Assert.IsTrue(trd < due3); // Second RUN s.SetTime(due2); sh2.Invoke(); Assert.IsFalse(done2); // Third TRN s.SetTime(trd); wrk3.Value._action(wrk3.Value._state); // Third SHT Assert.AreEqual(1, s._queue.Count); var sh3 = s._queue.Deq(); // Third RUN s.SetTime(due3); sh3.Invoke(); Assert.IsFalse(done3); }); } [TestMethod] public void ClockChanged_FalsePositive() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromMinutes(1); var due = now + rel; var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, cal._queue.Count); s.SetTime(now); scm.OnSystemClockChanged(); var work = cal._queue.Deq(); Assert.IsTrue(work.Interval < rel); s.SetTime(s.Now + work.Interval); work.Value._action(work.Value._state); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(s.Now + next.DueTime == due); s.SetTime(due); next.Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void ClockChanged_Forward1() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromMinutes(1); var due = now + rel; var err = TimeSpan.FromMinutes(1); var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, cal._queue.Count); Assert.AreEqual(0, s._queue.Count); s.SetTime(due + err); scm.OnSystemClockChanged(); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(next.DueTime == TimeSpan.Zero); next.Invoke(); Assert.IsTrue(done); var tmr = cal._queue.Deq(); tmr.Value._action(tmr.Value._state); Assert.AreEqual(0, cal._queue.Count); Assert.AreEqual(0, s._queue.Count); }); } [TestMethod] public void ClockChanged_Forward2() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromSeconds(1); var due = now + rel; var err = TimeSpan.FromMinutes(1); var s = new MyScheduler(); s.SetTime(now); var n = 0; s.Schedule(due, () => { n++; }); Assert.AreEqual(1, s._queue.Count); var wrk = s._queue.Deq(); Assert.IsTrue(wrk.DueTime == rel); s.SetTime(due + err); scm.OnSystemClockChanged(); Assert.AreEqual(1, s._queue.Count); var next = s._queue.Deq(); Assert.IsTrue(next.DueTime == TimeSpan.Zero); next.Invoke(); Assert.AreEqual(1, n); wrk.Invoke(); // Bad schedulers may not grant cancellation immediately. Assert.AreEqual(1, n); // Invoke shouldn't cause double execution of the work. }); } [TestMethod] public void ClockChanged_Backward1() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromMinutes(1); var due = now + rel; var err = TimeSpan.FromMinutes(-2); var s = new MyScheduler(); s.SetTime(now); var done = false; s.Schedule(due, () => { done = true; }); Assert.AreEqual(1, cal._queue.Count); Assert.IsTrue(cal._queue[0].Interval < rel); Assert.AreEqual(0, s._queue.Count); s.SetTime(due + err); scm.OnSystemClockChanged(); Assert.AreEqual(1, cal._queue.Count); var tmr = cal._queue.Deq(); Assert.IsTrue(tmr.Interval > rel); Assert.IsTrue(tmr.Interval < -err); s.SetTime(s.Now + tmr.Interval); tmr.Value._action(tmr.Value._state); Assert.IsFalse(done); Assert.AreEqual(0, cal._queue.Count); Assert.AreEqual(1, s._queue.Count); s.SetTime(due); s._queue.Deq().Invoke(); Assert.IsTrue(done); }); } [TestMethod] public void ClockChanged_Backward2() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var rel = TimeSpan.FromSeconds(1); var due = now + rel; var err = TimeSpan.FromMinutes(-1); var s = new MyScheduler(); s.SetTime(now); var n = 0; s.Schedule(due, () => { n++; }); Assert.AreEqual(0, cal._queue.Count); Assert.AreEqual(1, s._queue.Count); var wrk = s._queue[0]; Assert.IsTrue(wrk.DueTime == rel); s.SetTime(due + err); scm.OnSystemClockChanged(); Assert.AreEqual(1, cal._queue.Count); var tmr = cal._queue.Deq(); Assert.IsTrue(tmr.Interval > rel); Assert.IsTrue(tmr.Interval < -err); s.SetTime(s.Now + tmr.Interval); tmr.Value._action(tmr.Value._state); Assert.AreEqual(0, n); Assert.AreEqual(0, cal._queue.Count); Assert.AreEqual(1, s._queue.Count); s.SetTime(due); s._queue.Deq().Invoke(); Assert.AreEqual(1, n); wrk.Invoke(); // Bad schedulers may not grant cancellation immediately. Assert.AreEqual(1, n); // Invoke shouldn't cause double execution of the work. }); } [TestMethod] public void PeriodicSystemClockChangeMonitor() { Run(() => { var provider = new FakeClockPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var clock = (FakeClock)provider.GetService(); clock._now = new DateTimeOffset(2012, 4, 26, 12, 0, 0, TimeSpan.Zero); var cal = (FakeClockCAL)provider.GetService(); var period = TimeSpan.FromSeconds(1); var ptscm = new PeriodicTimerSystemClockMonitor(period); var delta = TimeSpan.Zero; var n = 0; var h = new EventHandler((o, e) => { delta = e.NewTime - e.OldTime; n++; }); ptscm.SystemClockChanged += h; Assert.IsNotNull(cal._action); Assert.IsTrue(cal._period == period); Assert.AreEqual(0, n); clock._now += period; cal._action(); Assert.AreEqual(0, n); clock._now += period; cal._action(); Assert.AreEqual(0, n); var diff1 = TimeSpan.FromSeconds(3); clock._now += period + diff1; cal._action(); Assert.AreEqual(1, n); Assert.IsTrue(delta == diff1); clock._now += period; cal._action(); Assert.AreEqual(1, n); clock._now += period; cal._action(); Assert.AreEqual(1, n); var diff2 = TimeSpan.FromSeconds(-5); clock._now += period + diff2; cal._action(); Assert.AreEqual(2, n); Assert.IsTrue(delta == diff2); clock._now += period; cal._action(); Assert.AreEqual(2, n); ptscm.SystemClockChanged -= h; Assert.IsNull(cal._action); }); } [TestMethod] public void ClockChanged_RefCounting() { Run(() => { var provider = new MyPlatformEnlightenmentProvider(); PlatformEnlightenmentProvider.Current = provider; var scm = (ClockChanged)provider.GetService(); var cal = provider._cal; var now = new DateTimeOffset(2012, 4, 25, 12, 0, 0, TimeSpan.Zero); var s = new MyScheduler(); s.SetTime(now); var due1 = now + TimeSpan.FromSeconds(5); var due2 = now + TimeSpan.FromSeconds(8); var due3 = now + TimeSpan.FromMinutes(1); var due4 = now + TimeSpan.FromMinutes(2); var due5 = now + TimeSpan.FromMinutes(3); var due6 = now + TimeSpan.FromMinutes(3) + TimeSpan.FromSeconds(2); var done1 = false; var done2 = false; var done3 = false; var done4 = false; var done5 = false; var done6 = false; var d1 = s.Schedule(due1, () => { done1 = true; }); var d5 = s.Schedule(due5, () => { done5 = true; }); var d3 = s.Schedule(due3, () => { done3 = true; throw new Exception(); }); var d2 = s.Schedule(due2, () => { done2 = true; }); var d4 = s.Schedule(due4, () => { done4 = true; }); d2.Dispose(); d4.Dispose(); Assert.AreEqual(1, scm.n); s.SetTime(due1); var i1 = s._queue.Deq(); i1.Invoke(); Assert.IsTrue(done1); Assert.AreEqual(1, scm.n); s.SetTime(due2); var i2 = s._queue.Deq(); i2.Invoke(); Assert.IsFalse(done2); Assert.AreEqual(1, scm.n); var l1 = cal._queue.Deq(); var l1d = now + l1.Interval; s.SetTime(l1d); l1.Value._action(l1.Value._state); s.SetTime(due3); var i3 = s._queue.Deq(); try { i3.Invoke(); Assert.Fail(); } catch { } Assert.IsTrue(done3); Assert.AreEqual(1, scm.n); var l2 = cal._queue.Deq(); var l2d = l1d + l2.Interval; s.SetTime(l2d); l2.Value._action(l2.Value._state); s.SetTime(due4); var i4 = s._queue.Deq(); i4.Invoke(); Assert.IsFalse(done4); Assert.AreEqual(1, scm.n); var l3 = cal._queue.Deq(); var l3d = l2d + l3.Interval; s.SetTime(l3d); l3.Value._action(l3.Value._state); s.SetTime(due5); var i5 = s._queue.Deq(); i5.Invoke(); Assert.IsTrue(done5); Assert.AreEqual(0, scm.n); var d6 = s.Schedule(due6, () => { done6 = true; }); Assert.AreEqual(1, scm.n); s.SetTime(due6); var i6 = s._queue.Deq(); i6.Invoke(); Assert.IsTrue(done6); Assert.AreEqual(0, scm.n); }); } class MyScheduler : LocalScheduler { internal List> _queue = new List>(); private DateTimeOffset _now; public void SetTime(DateTimeOffset now) { _now = now; } public override DateTimeOffset Now { get { return _now; } } public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { var s = new ScheduledItem(this, state, action, dueTime); _queue.Add(s); return Disposable.Create(() => _queue.Remove(s)); } } class MyPlatformEnlightenmentProvider : IPlatformEnlightenmentProvider { internal MyCAL _cal; public MyPlatformEnlightenmentProvider() { _cal = new MyCAL(); } public T GetService(params object[] args) where T : class { if (typeof(T) == typeof(IConcurrencyAbstractionLayer)) { return (T)(object)_cal; } else if (typeof(T) == typeof(INotifySystemClockChanged)) { return (T)(object)ClockChanged.Instance; } return null; } } class FakeClockPlatformEnlightenmentProvider : IPlatformEnlightenmentProvider { internal FakeClockCAL _cal; internal FakeClock _clock; public FakeClockPlatformEnlightenmentProvider() { _cal = new FakeClockCAL(); _clock = new FakeClock(); } public T GetService(params object[] args) where T : class { if (typeof(T) == typeof(IConcurrencyAbstractionLayer)) { return (T)(object)_cal; } else if (typeof(T) == typeof(ISystemClock)) { return (T)(object)_clock; } return null; } } class Work { internal readonly Action _action; internal readonly object _state; public Work(Action action, object state) { _action = action; _state = state; } } class MyCAL : IConcurrencyAbstractionLayer { internal List> _queue = new List>(); public IDisposable StartTimer(Action action, object state, TimeSpan dueTime) { var t = new TimeInterval(new Work(action, state), dueTime); _queue.Add(t); return Disposable.Create(() => _queue.Remove(t)); } public IDisposable StartPeriodicTimer(Action action, TimeSpan period) { throw new NotImplementedException(); } public IDisposable QueueUserWorkItem(Action action, object state) { throw new NotImplementedException(); } public void Sleep(TimeSpan timeout) { throw new NotImplementedException(); } public IStopwatch StartStopwatch() { throw new NotImplementedException(); } public bool SupportsLongRunning { get { throw new NotImplementedException(); } } public void StartThread(Action action, object state) { throw new NotImplementedException(); } } class FakeClockCAL : IConcurrencyAbstractionLayer { internal Action _action; internal TimeSpan _period; public IDisposable StartTimer(Action action, object state, TimeSpan dueTime) { throw new NotImplementedException(); } public IDisposable StartPeriodicTimer(Action action, TimeSpan period) { _action = action; _period = period; return Disposable.Create(() => _action = null); } public IDisposable QueueUserWorkItem(Action action, object state) { throw new NotImplementedException(); } public void Sleep(TimeSpan timeout) { throw new NotImplementedException(); } public IStopwatch StartStopwatch() { throw new NotImplementedException(); } public bool SupportsLongRunning { get { throw new NotImplementedException(); } } public void StartThread(Action action, object state) { throw new NotImplementedException(); } } class FakeClock : ISystemClock { internal DateTimeOffset _now; public DateTimeOffset UtcNow { get { return _now; } } } class ClockChanged : INotifySystemClockChanged { private static ClockChanged s_instance = new ClockChanged(); private EventHandler _systemClockChanged; internal int n = 0; public event EventHandler SystemClockChanged { add { _systemClockChanged += value; n++; } remove { _systemClockChanged -= value; n--; } } public static ClockChanged Instance { get { return s_instance; } } public void OnSystemClockChanged() { var scc = _systemClockChanged; if (scc != null) scc(this, new SystemClockChangedEventArgs()); } } } } #endif