PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/ReactiveUI.Tests/ReactiveCommandTest.cs

https://github.com/theperm/ReactiveUI
C# | 460 lines | 358 code | 91 blank | 11 comment | 14 complexity | 84ecf320aebd0faeab05e111a5987446 MD5 | raw file
  1. using System.Reactive;
  2. using System.Reactive.Concurrency;
  3. using System.Reactive.Linq;
  4. using System.Reactive.Subjects;
  5. using Microsoft.Reactive.Testing;
  6. using ReactiveUI;
  7. using Xunit;
  8. using System;
  9. using System.Linq;
  10. using System.Collections.Generic;
  11. using System.Threading;
  12. using Microsoft.Reactive.Testing;
  13. using ReactiveUI.Xaml;
  14. using ReactiveUI.Testing;
  15. namespace ReactiveUI.Tests
  16. {
  17. public abstract class ReactiveCommandInterfaceTest : IEnableLogger
  18. {
  19. protected abstract IReactiveCommand createCommand(IObservable<bool> canExecute, IScheduler scheduler = null);
  20. protected abstract IReactiveCommand createRelayCommand(Func<object, bool> canExecute, IScheduler scheduler = null);
  21. [Fact]
  22. public void CompletelyDefaultReactiveCommandShouldFire()
  23. {
  24. var sched = new TestScheduler();
  25. var fixture = createCommand(null, sched);
  26. Assert.True(fixture.CanExecute(null));
  27. string result = null;
  28. fixture.Subscribe(x => result = x as string);
  29. fixture.Execute("Test");
  30. sched.Start();
  31. Assert.Equal("Test", result);
  32. fixture.Execute("Test2");
  33. sched.Start();
  34. Assert.Equal("Test2", result);
  35. }
  36. [Fact]
  37. public void ObservableCanExecuteShouldShowUpInCommand()
  38. {
  39. var input = new[] {true, false, false, true, false, true};
  40. var result = (new TestScheduler()).With(sched => {
  41. var can_execute = new Subject<bool>();
  42. var fixture = createCommand(can_execute, sched);
  43. var changes_as_observable = fixture.CanExecuteObservable.CreateCollection();
  44. int change_event_count = 0;
  45. fixture.CanExecuteChanged += (o, e) => { change_event_count++; };
  46. input.Run(x => {
  47. this.Log().InfoFormat("input = {0}", x);
  48. can_execute.OnNext(x);
  49. sched.Start();
  50. Assert.Equal(x, fixture.CanExecute(null));
  51. });
  52. // N.B. We check against '5' instead of 6 because we're supposed to
  53. // suppress changes that aren't actually changes i.e. false => false
  54. sched.RunToMilliseconds(10 * 1000);
  55. return changes_as_observable;
  56. });
  57. input.DistinctUntilChanged().AssertAreEqual(result.ToList());
  58. }
  59. [Fact]
  60. public void ObservableCanExecuteFuncShouldShowUpInCommand()
  61. {
  62. int counter = 0;
  63. var fixture = createRelayCommand(_ => (counter % 2 == 0));
  64. var changes_as_observable = fixture.CanExecuteObservable.CreateCollection();
  65. int change_event_count = 0;
  66. fixture.CanExecuteChanged += (o, e) => { change_event_count++; };
  67. Enumerable.Range(0, 6).Run(x => {
  68. this.Log().InfoFormat("Counter = {0}, x = {1}", counter, x);
  69. Assert.Equal(x % 2 == 0, fixture.CanExecute(null));
  70. counter++;
  71. });
  72. Assert.Equal(6, changes_as_observable.Count);
  73. }
  74. [Fact]
  75. public void ObservableExecuteFuncShouldBeObservableAndAct()
  76. {
  77. var executed_params = new List<object>();
  78. var fixture = createCommand(null);
  79. fixture.Subscribe(x => executed_params.Add(x));
  80. var observed_params = new ReplaySubject<object>();
  81. fixture.Subscribe(observed_params.OnNext, observed_params.OnError, observed_params.OnCompleted);
  82. var range = Enumerable.Range(0, 5);
  83. range.Run(x => fixture.Execute(x));
  84. range.AssertAreEqual(executed_params.OfType<int>());
  85. range.ToObservable()
  86. .Zip(observed_params, (expected, actual) => new { expected, actual })
  87. .Do(Console.WriteLine)
  88. .Subscribe(x => Assert.Equal(x.expected, x.actual));
  89. }
  90. [Fact]
  91. public void MultipleSubscribesShouldntResultInMultipleNotifications()
  92. {
  93. var input = new[] { 1, 2, 1, 2 };
  94. var sched = new TestScheduler();
  95. var fixture = createCommand(null, sched);
  96. var odd_list = new List<int>();
  97. var even_list = new List<int>();
  98. fixture.Where(x => ((int)x) % 2 != 0).Subscribe(x => odd_list.Add((int)x));
  99. fixture.Where(x => ((int)x) % 2 == 0).Subscribe(x => even_list.Add((int)x));
  100. input.Run(x => fixture.Execute(x));
  101. sched.RunToMilliseconds(1000);
  102. new[]{1,1}.AssertAreEqual(odd_list);
  103. new[]{2,2}.AssertAreEqual(even_list);
  104. }
  105. [Fact(Skip="I'm not convinced this is actually true, if you throw in a Subscribe it *should* permabreak")]
  106. public void ActionExceptionShouldntPermabreakCommands()
  107. {
  108. var input = new[] {1,2,3,4};
  109. var fixture = createCommand(null);
  110. fixture.Subscribe(x => {
  111. if (((int)x) == 2)
  112. throw new Exception("Die!");
  113. });
  114. var exception_list = new List<Exception>();
  115. var out_list = new List<int>();
  116. fixture.Subscribe(x => out_list.Add((int)x), ex => exception_list.Add(ex));
  117. bool we_threw = false;
  118. foreach (int i in input) {
  119. try {
  120. fixture.Execute(i);
  121. } catch {
  122. we_threw = true;
  123. if (i != 2)
  124. throw;
  125. }
  126. }
  127. Assert.True(we_threw);
  128. input.AssertAreEqual(out_list);
  129. // Now, make sure that the command isn't broken
  130. fixture.Execute(5);
  131. Console.WriteLine(String.Join(",", out_list.Select(x => x.ToString()).ToArray()));
  132. Assert.Equal(5, out_list.Count);
  133. }
  134. [Fact]
  135. public void CanExecuteExceptionShouldntPermabreakCommands()
  136. {
  137. }
  138. }
  139. public class ReactiveCommandTest : ReactiveCommandInterfaceTest
  140. {
  141. protected override IReactiveCommand createCommand(IObservable<bool> canExecute, IScheduler scheduler = null) {
  142. return new ReactiveCommand(canExecute, scheduler);
  143. }
  144. protected override IReactiveCommand createRelayCommand(Func<object, bool> canExecute, IScheduler scheduler = null) {
  145. return ReactiveCommand.Create(canExecute, null, scheduler);
  146. }
  147. }
  148. public class ReactiveAsyncCommandBaseTest : ReactiveCommandInterfaceTest
  149. {
  150. protected override IReactiveCommand createCommand(IObservable<bool> canExecute, IScheduler scheduler = null) {
  151. return new ReactiveAsyncCommand(canExecute, 1, scheduler);
  152. }
  153. protected override IReactiveCommand createRelayCommand(Func<object, bool> canExecute, IScheduler scheduler = null) {
  154. return ReactiveAsyncCommand.Create(x => 1, x => { }, canExecute, 1, scheduler);
  155. }
  156. }
  157. public class ReactiveAsyncCommandTest : IEnableLogger
  158. {
  159. [Fact]
  160. public void RegisterAsyncFunctionSmokeTest()
  161. {
  162. (new TestScheduler()).With(sched => {
  163. var fixture = new ReactiveAsyncCommand(null, 1);
  164. ReactiveCollection<int> results;
  165. results = fixture.RegisterAsyncObservable(_ =>
  166. Observable.Return(5).Delay(TimeSpan.FromSeconds(5), sched)).CreateCollection();
  167. var inflightResults = fixture.ItemsInflight.CreateCollection();
  168. sched.RunToMilliseconds(10);
  169. Assert.True(fixture.CanExecute(null));
  170. fixture.Execute(null);
  171. sched.RunToMilliseconds(1005);
  172. Assert.False(fixture.CanExecute(null));
  173. sched.RunToMilliseconds(5100);
  174. Assert.True(fixture.CanExecute(null));
  175. new[] {0,1,0}.AssertAreEqual(inflightResults);
  176. new[] {5}.AssertAreEqual(results);
  177. });
  178. }
  179. [Fact]
  180. public void RegisterMemoizedFunctionSmokeTest()
  181. {
  182. var input = new[] { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
  183. var output = new[] { 5, 5, 5, 5, 5, 10, 10, 10, 10, 10 };
  184. var sched = new EventLoopScheduler();
  185. var results = new List<Timestamped<int>>();
  186. var start = sched.Now;
  187. sched.With(_ => {
  188. var fixture = new ReactiveAsyncCommand(null, 5, sched);
  189. fixture.RegisterMemoizedFunction(x => { Thread.Sleep(1000); return ((int) x) * 5; }, 50, null, sched)
  190. .Timestamp()
  191. .DebugObservable()
  192. .Subscribe(x => results.Add(x));
  193. Assert.True(fixture.CanExecute(1));
  194. foreach (var i in input) {
  195. Assert.True(fixture.CanExecute(i));
  196. fixture.Execute(i);
  197. }
  198. Thread.Sleep(2500);
  199. });
  200. Assert.Equal(10, results.Count);
  201. this.Log().Info("Timestamp Deltas");
  202. results.Select(x => x.Timestamp - start)
  203. .Run(x => this.Log().Info(x));
  204. output.AssertAreEqual(results.Select(x => x.Value));
  205. Assert.False(results.Any(x => x.Timestamp - start > new TimeSpan(0, 0, 3)));
  206. }
  207. [Fact]
  208. public void MakeSureMemoizedReleaseFuncGetsCalled()
  209. {
  210. //Assert.True(false, "When an item gets evicted from the cache before it has a chance to complete, it deadlocks. Fix it.");
  211. var input = new[] { 1, 1, 2, 2, 1, 1, 3, 3 };
  212. var sched = new EventLoopScheduler();
  213. var fixture = new ReactiveAsyncCommand();
  214. var results = new List<Timestamped<int>>();
  215. var released = new List<int>();
  216. fixture.RegisterMemoizedFunction(x => { Thread.Sleep(250); return ((int)x) * 5; }, 2, x => released.Add(x), sched)
  217. .Timestamp()
  218. .DebugObservable()
  219. .Subscribe(x => results.Add(x));
  220. Assert.True(fixture.CanExecute(1));
  221. var start = DateTimeOffset.Now;
  222. foreach(var i in input) {
  223. Assert.True(fixture.CanExecute(i));
  224. fixture.Execute(i);
  225. }
  226. Thread.Sleep(1000);
  227. this.Log().Info("Timestamp Deltas");
  228. results.Select(x => x.Timestamp - start)
  229. .Run(x => this.Log().Info(x));
  230. this.Log().Info("Release list");
  231. released.Run(x => this.Log().Info(x));
  232. Assert.True(results.Count == 8);
  233. Assert.True(released.Count == 1);
  234. Assert.True(released[0] == 2*5);
  235. }
  236. [Fact]
  237. public void MultipleSubscribersShouldntDecrementRefcountBelowZero()
  238. {
  239. (new TestScheduler()).With(sched => {
  240. var fixture = new ReactiveAsyncCommand();
  241. var results = new List<int>();
  242. bool[] subscribers = new[] { false, false, false, false, false };
  243. var output = fixture.RegisterAsyncObservable(_ =>
  244. Observable.Return(5).Delay(TimeSpan.FromMilliseconds(5000), sched));
  245. output.Subscribe(x => results.Add(x));
  246. Enumerable.Range(0, 5).Run(x => output.Subscribe(_ => subscribers[x] = true));
  247. Assert.True(fixture.CanExecute(null));
  248. fixture.Execute(null);
  249. sched.RunToMilliseconds(2000);
  250. Assert.False(fixture.CanExecute(null));
  251. sched.RunToMilliseconds(6000);
  252. Assert.True(fixture.CanExecute(null));
  253. Assert.True(results.Count == 1);
  254. Assert.True(results[0] == 5);
  255. Assert.True(subscribers.All(x => x == true));
  256. });
  257. }
  258. [Fact]
  259. public void MultipleResultsFromObservableShouldntDecrementRefcountBelowZero()
  260. {
  261. (new TestScheduler()).With(sched => {
  262. int latestInFlight = 0;
  263. var fixture = new ReactiveAsyncCommand(null, 1, sched);
  264. var results = fixture
  265. .RegisterAsyncObservable(_ => new[] {1, 2, 3}.ToObservable())
  266. .CreateCollection();
  267. fixture.ItemsInflight.Subscribe(x => latestInFlight = x);
  268. fixture.Execute(1);
  269. sched.Start();
  270. Assert.Equal(3, results.Count);
  271. Assert.Equal(0, latestInFlight);
  272. });
  273. }
  274. [Fact]
  275. public void RAFShouldActuallyRunOnTheTaskpool()
  276. {
  277. var deferred = RxApp.DeferredScheduler;
  278. var taskpool = RxApp.TaskpoolScheduler;
  279. try {
  280. var testDeferred = new CountingTestScheduler(Scheduler.Immediate);
  281. var testTaskpool = new CountingTestScheduler(Scheduler.NewThread);
  282. RxApp.DeferredScheduler = testDeferred; RxApp.TaskpoolScheduler = testTaskpool;
  283. var fixture = new ReactiveAsyncCommand();
  284. var result = fixture.RegisterAsyncFunction(x => { Thread.Sleep(1000); return (int)x * 5; });
  285. fixture.Execute(1);
  286. Assert.Equal(5, result.First());
  287. this.Log().InfoFormat("Scheduled {0} items on deferred, {1} items on Taskpool",
  288. testDeferred.ScheduledItems.Count, testTaskpool.ScheduledItems.Count);
  289. Assert.True(testDeferred.ScheduledItems.Count >= 1);
  290. Assert.True(testTaskpool.ScheduledItems.Count >= 1);
  291. } finally {
  292. RxApp.DeferredScheduler = deferred;
  293. RxApp.TaskpoolScheduler = taskpool;
  294. }
  295. }
  296. [Fact]
  297. public void RAOShouldActuallyRunOnTheTaskpool()
  298. {
  299. var deferred = RxApp.DeferredScheduler;
  300. var taskpool = RxApp.TaskpoolScheduler;
  301. try {
  302. var testDeferred = new CountingTestScheduler(Scheduler.Immediate);
  303. var testTaskpool = new CountingTestScheduler(Scheduler.NewThread);
  304. RxApp.DeferredScheduler = testDeferred; RxApp.TaskpoolScheduler = testTaskpool;
  305. var fixture = new ReactiveAsyncCommand();
  306. var result = fixture.RegisterAsyncObservable(x =>
  307. Observable.Return((int)x * 5).Delay(TimeSpan.FromSeconds(1), RxApp.TaskpoolScheduler));
  308. fixture.Execute(1);
  309. Assert.Equal(5, result.First());
  310. this.Log().InfoFormat("Scheduled {0} items on deferred, {1} items on Taskpool",
  311. testDeferred.ScheduledItems.Count, testTaskpool.ScheduledItems.Count);
  312. Assert.True(testDeferred.ScheduledItems.Count >= 1);
  313. Assert.True(testTaskpool.ScheduledItems.Count >= 1);
  314. } finally {
  315. RxApp.DeferredScheduler = deferred;
  316. RxApp.TaskpoolScheduler = taskpool;
  317. }
  318. }
  319. [Fact]
  320. public void CanExecuteShouldChangeOnInflightOp()
  321. {
  322. (new TestScheduler()).With(sched => {
  323. var canExecute = sched.CreateHotObservable(
  324. sched.OnNextAt(0, true),
  325. sched.OnNextAt(250, false),
  326. sched.OnNextAt(500, true),
  327. sched.OnNextAt(750, false),
  328. sched.OnNextAt(1000, true),
  329. sched.OnNextAt(1100, false)
  330. );
  331. var fixture = new ReactiveAsyncCommand(canExecute);
  332. int calculatedResult = -1;
  333. bool latestCanExecute = false;
  334. fixture.RegisterAsyncObservable(x =>
  335. Observable.Return((int)x * 5).Delay(TimeSpan.FromMilliseconds(900), RxApp.DeferredScheduler))
  336. .Subscribe(x => calculatedResult = x);
  337. fixture.CanExecuteObservable.Subscribe(x => latestCanExecute = x);
  338. // CanExecute should be true, both input observable is true
  339. // and we don't have anything inflight
  340. sched.RunToMilliseconds(10);
  341. Assert.True(fixture.CanExecute(1));
  342. Assert.True(latestCanExecute);
  343. // Invoke a command 10ms in
  344. fixture.Execute(1);
  345. // At 300ms, input is false
  346. sched.RunToMilliseconds(300);
  347. Assert.False(fixture.CanExecute(1));
  348. Assert.False(latestCanExecute);
  349. // At 600ms, input is true, but the command is still running
  350. sched.RunToMilliseconds(600);
  351. Assert.False(fixture.CanExecute(1));
  352. Assert.False(latestCanExecute);
  353. // After we've completed, we should still be false, since from
  354. // 750ms-1000ms the input observable is false
  355. sched.RunToMilliseconds(900);
  356. Assert.False(fixture.CanExecute(1));
  357. Assert.False(latestCanExecute);
  358. Assert.Equal(-1, calculatedResult);
  359. sched.RunToMilliseconds(1010);
  360. Assert.True(fixture.CanExecute(1));
  361. Assert.True(latestCanExecute);
  362. Assert.Equal(calculatedResult, 5);
  363. sched.RunToMilliseconds(1200);
  364. Assert.False(fixture.CanExecute(1));
  365. Assert.False(latestCanExecute);
  366. });
  367. }
  368. }
  369. }