PageRenderTime 147ms CodeModel.GetById 33ms RepoModel.GetById 6ms app.codeStats 1ms

/Mono.Reactive.Testing/Mono.Reactive.Testing.cs

https://github.com/gshackles/mono-reactive
C# | 366 lines | 301 code | 61 blank | 4 comment | 23 complexity | 0eda0dd5424edd45734052c5271a77b6 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Reactive;
  6. using System.Reactive.Concurrency;
  7. using System.Reactive.Disposables;
  8. using System.Reactive.Linq;
  9. using System.Reactive.Subjects;
  10. using NUnit.Framework;
  11. namespace Mono.Reactive.Testing
  12. {
  13. public interface ITestableObservable<T> : IObservable<T>
  14. {
  15. IList<Recorded<Notification<T>>> Messages { get; }
  16. IList<Subscription> Subscriptions { get; }
  17. }
  18. public interface ITestableObserver<T> : IObserver<T>
  19. {
  20. IList<Recorded<Notification<T>>> Messages { get; }
  21. }
  22. internal class TestableObservable<T> : ITestableObservable<T>
  23. {
  24. IList<Recorded<Notification<T>>> messages;
  25. IList<Subscription> subscriptions = new List<Subscription> ();
  26. ISubject<T> subject;
  27. bool hot;
  28. TestScheduler scheduler;
  29. public TestableObservable (TestScheduler scheduler, bool hot, Recorded<Notification<T>> [] messages)
  30. {
  31. this.scheduler = scheduler;
  32. this.hot = hot;
  33. this.messages = messages;
  34. subject = hot ? (ISubject<T>) new Subject<T> () : new ReplaySubject<T> ();
  35. }
  36. public IDisposable Subscribe (IObserver<T> observer)
  37. {
  38. var subscription = ReactiveTest.Subscribe (scheduler.Clock);
  39. subscriptions.Add (subscription);
  40. // FIXME: I wonder if Subscription records the actual disposal time. If so, this should return IDisposable that involves setting disposal time on the subscription instance.
  41. return subject.Subscribe (observer);
  42. }
  43. public IList<Recorded<Notification<T>>> Messages {
  44. get { return messages; }
  45. }
  46. public IList<Subscription> Subscriptions {
  47. get { return subscriptions; }
  48. }
  49. }
  50. internal class TestableObserver<T> : ITestableObserver<T>
  51. {
  52. TestScheduler scheduler;
  53. public TestableObserver (TestScheduler scheduler)
  54. {
  55. this.scheduler = scheduler;
  56. Messages = new List<Recorded<Notification<T>>> ();
  57. }
  58. public IList<Recorded<Notification<T>>> Messages { get; private set; }
  59. public void OnNext (T value)
  60. {
  61. Messages.Add (ReactiveTest.OnNext<T> (scheduler.Clock, value));
  62. }
  63. public void OnError (Exception exception)
  64. {
  65. Messages.Add (ReactiveTest.OnError<T> (scheduler.Clock, exception));
  66. }
  67. public void OnCompleted ()
  68. {
  69. Messages.Add (ReactiveTest.OnCompleted<T> (scheduler.Clock));
  70. }
  71. }
  72. public static class ReactiveAssert
  73. {
  74. public static void AreElementsEqual<T> (IEnumerable<T> expected, IEnumerable<T> actual)
  75. {
  76. AreElementsEqual (expected, actual, "Enumerated items are not equal");
  77. }
  78. public static void AreElementsEqual<T> (IObservable<T> expected, IObservable<T> actual)
  79. {
  80. AreElementsEqual (expected, actual, "Observed items are not equal");
  81. }
  82. public static void AreElementsEqual<T> (IEnumerable<T> expected, IEnumerable<T> actual, string message)
  83. {
  84. if (expected == null) {
  85. if (actual != null)
  86. throw new ArgumentNullException ("expected");
  87. else
  88. return;
  89. }
  90. var ee = expected.GetEnumerator ();
  91. var ae = actual.GetEnumerator ();
  92. int i = 0;
  93. for (; ee.MoveNext (); i++) {
  94. if (!ae.MoveNext ())
  95. Assert.Fail (String.Format ("{0} (Insufficient items, ended at index {1})", message, i));
  96. Assert.AreEqual (ee.Current, ae.Current, String.Format ("{0} (items at index {1})", message, i));
  97. }
  98. if (ae.MoveNext ())
  99. Assert.Fail (String.Format ("{0} (Extra items, after index {1})", message, i));
  100. }
  101. struct Indexed<T>
  102. {
  103. public Indexed (int i, T value)
  104. {
  105. index = i;
  106. this.value = value;
  107. }
  108. int index;
  109. T value;
  110. public int Index { get { return index; } }
  111. public T Value { get { return value; } }
  112. }
  113. public static void AreElementsEqual<T> (IObservable<T> expected, IObservable<T> actual, string message)
  114. {
  115. if (expected == null) {
  116. if (actual != null)
  117. throw new ArgumentNullException ("expected");
  118. else
  119. return;
  120. }
  121. int ie = 0, ia = 0, endE = 0, endA = 0;
  122. var iex = expected.Select (e => new Indexed<T> (ie++, e)).Finally (() => endE = ie);
  123. var iac = actual.Select (e => new Indexed<T> (ia++, e)).Finally (() => endA = ia);
  124. var source = iex.Zip (iac, (e, a) => { Assert.AreEqual (e.Value, a.Value, String.Format ("{0} (Items differ at index {1})", message, e.Index)); return Unit.Default; });
  125. var dis = new SingleAssignmentDisposable ();
  126. dis.Disposable = source.Finally<Unit> (() => dis.Dispose ()).Subscribe (v => {}, () => Assert.AreEqual (endE, endA, String.Format ("{0} (Items counts differ: expected {1} but got {2})", endE, endA)));
  127. }
  128. public static void Throws<TException> (Action action)
  129. where TException : Exception
  130. {
  131. Throws<TException> (action, "Should raise " + typeof (TException));
  132. }
  133. public static void Throws<TException> (Action action, string message)
  134. where TException : Exception
  135. {
  136. try {
  137. action ();
  138. Assert.Fail (message);
  139. } catch (Exception ex) {
  140. // FIXME: should this be IsAssignableFrom() ?
  141. Assert.AreEqual (typeof (TException), ex.GetType (), message);
  142. }
  143. }
  144. public static void Throws<TException> (TException exception, Action action)
  145. where TException : Exception
  146. {
  147. Throws<TException> (exception, action, "Should raise " + typeof (TException));
  148. }
  149. public static void Throws<TException> (TException exception, Action action, string message)
  150. where TException : Exception
  151. {
  152. try {
  153. action ();
  154. Assert.Fail (message);
  155. } catch (Exception ex) {
  156. Assert.AreEqual (exception, ex, message);
  157. }
  158. }
  159. }
  160. public class ReactiveTest
  161. {
  162. public const long Created = 100;
  163. public const long Disposed = 1000;
  164. public const long Subscribed = 200;
  165. public static Recorded<Notification<T>> OnCompleted<T> (long ticks)
  166. {
  167. return new Recorded<Notification<T>> (ticks, Notification.CreateOnCompleted<T> ());
  168. }
  169. public static Recorded<Notification<T>> OnError<T> (long ticks, Exception exception)
  170. {
  171. return new Recorded<Notification<T>> (ticks, Notification.CreateOnError<T> (exception));
  172. }
  173. public static Recorded<Notification<T>> OnNext<T> (long ticks, T value)
  174. {
  175. return new Recorded<Notification<T>> (ticks, Notification.CreateOnNext<T> (value));
  176. }
  177. public static Subscription Subscribe (long start)
  178. {
  179. return new Subscription (start);
  180. }
  181. public static Subscription Subscribe (long start, long end)
  182. {
  183. return new Subscription (start, end);
  184. }
  185. }
  186. [SerializableAttribute]
  187. public struct Recorded<T> : IEquatable<Recorded<T>>
  188. {
  189. public Recorded (long time, T value)
  190. {
  191. this.time = time;
  192. this.value = value;
  193. }
  194. long time;
  195. T value;
  196. public long Time { get { return time; } }
  197. public T Value { get { return value; } }
  198. public override bool Equals (object obj)
  199. {
  200. return obj is Recorded<T> && Equals ((Recorded<T>) obj);
  201. }
  202. public bool Equals (Recorded<T> other)
  203. {
  204. return time == other.time && value.Equals (other.value);
  205. }
  206. public override int GetHashCode ()
  207. {
  208. return (int) time + value.GetHashCode ();
  209. }
  210. public override string ToString ()
  211. {
  212. return value + "@" + time;
  213. }
  214. public static bool operator == (Recorded<T> left, Recorded<T> right)
  215. {
  216. return left.Equals (right);
  217. }
  218. public static bool operator != (Recorded<T> left, Recorded<T> right)
  219. {
  220. return !left.Equals (right);
  221. }
  222. }
  223. [SerializableAttribute]
  224. public struct Subscription : IEquatable<Subscription>
  225. {
  226. public const long Infinite = long.MaxValue;
  227. public Subscription (long subscribe)
  228. : this (subscribe, Infinite)
  229. {
  230. }
  231. public Subscription (long subscribe, long unsubscribe)
  232. {
  233. sub = subscribe;
  234. unsub = unsubscribe;
  235. }
  236. long sub, unsub;
  237. public long Subscribe { get { return sub; } }
  238. public long Unsubscribe { get { return unsub; } }
  239. public override bool Equals (object obj)
  240. {
  241. return obj is Subscription && Equals ((Subscription) obj);
  242. }
  243. public bool Equals (Subscription other)
  244. {
  245. return sub == other.sub && unsub == other.unsub;
  246. }
  247. public override int GetHashCode ()
  248. {
  249. return (int) ((sub << 17) + unsub);
  250. }
  251. public override string ToString ()
  252. {
  253. return String.Format ("({0}, {1})", sub, unsub == Infinite ? "Infinite" : unsub.ToString (CultureInfo.InvariantCulture));
  254. }
  255. public static bool operator == (Subscription left, Subscription right)
  256. {
  257. return left.Equals (right);
  258. }
  259. public static bool operator != (Subscription left, Subscription right)
  260. {
  261. return !left.Equals (right);
  262. }
  263. }
  264. public class TestScheduler : VirtualTimeScheduler<long, long>
  265. {
  266. // VirtualTimeScheduler members.
  267. protected override long Add (long absolute, long relative)
  268. {
  269. return absolute + relative;
  270. }
  271. protected override DateTimeOffset ToDateTimeOffset (long absolute)
  272. {
  273. return new DateTimeOffset (DateTime.MinValue.AddTicks (absolute), TimeSpan.Zero);
  274. }
  275. protected override long ToRelative (TimeSpan timeSpan)
  276. {
  277. return timeSpan.Ticks;
  278. }
  279. // TestScheduler specific.
  280. public ITestableObservable<T> CreateColdObservable<T> (params Recorded<Notification<T>> [] messages)
  281. {
  282. return new TestableObservable<T> (this, false, messages);
  283. }
  284. public ITestableObservable<T> CreateHotObservable<T> (params Recorded<Notification<T>> [] messages)
  285. {
  286. return new TestableObservable<T> (this, true, messages);
  287. }
  288. public ITestableObserver<T> CreateObserver<T> ()
  289. {
  290. return new TestableObserver<T> (this);
  291. }
  292. public ITestableObserver<T> Start<T> (Func<IObservable<T>> create)
  293. {
  294. throw new NotImplementedException ();
  295. }
  296. public ITestableObserver<T> Start<T> (Func<IObservable<T>> create, long disposed)
  297. {
  298. throw new NotImplementedException ();
  299. }
  300. public ITestableObserver<T> Start<T> (Func<IObservable<T>> create, long created, long subscribed, long disposed)
  301. {
  302. throw new NotImplementedException ();
  303. }
  304. }
  305. }