public void AsyncValidation_SeveralAsyncRules_AllExecutedBeforeValidationCompleted() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); vm.Foo = null; var validation = new ValidationHelper(); bool rule1Executed = false; validation.AddAsyncRule(() => { return(Task.Run(() => { rule1Executed = true; return RuleResult.Valid(); })); }); bool rule2Executed = false; validation.AddAsyncRule(() => { return(Task.Run(() => { rule2Executed = true; return RuleResult.Valid(); })); }); bool rule3Executed = false; validation.AddAsyncRule(() => { return(Task.Run(() => { rule3Executed = true; return RuleResult.Valid(); })); }); validation.ValidateAllAsync().ContinueWith(r => { Assert.True(rule1Executed); Assert.True(rule2Executed); Assert.True(rule3Executed); Assert.True(r.Result.IsValid); completedAction(); }); }); }
public void ValidatedAsync_AsyncRuleDoesnotCallCallback_ThrowsAnExceptionAfterTimeout() { TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); validation.AsyncRuleExecutionTimeout = TimeSpan.FromSeconds(0.1); var dummy = new DummyViewModel(); validation.AddAsyncRule(() => dummy.Foo, onCompleted => { // Do nothing }); // ACT var ui = TaskScheduler.FromCurrentSynchronizationContext(); validation.ValidateAllAsync().ContinueWith(result => { Assert.True(result.IsFaulted, "Validation task must fail."); Assert.NotNull(result.Exception); completedAction(); }, ui); }); }
public void ValidateAsync_WithCallback_ValidationOccuredAndCallbackIsCalledOnUIThread() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); vm.Foo = null; bool ruleExecuted = false; var validation = new ValidationHelper(); validation.AddAsyncRule(setResult => { ruleExecuted = true; setResult(RuleResult.Invalid("Error1")); }); validation.ValidateAllAsync(result => { Assert.True(ruleExecuted, "Validation rule must be executed before validation callback is called."); Assert.Equal(dispatcher.Thread.ManagedThreadId, Thread.CurrentThread.ManagedThreadId); completedAction(); }); }); }
public void AsyncValidation_DependantProperties_IfOneInvalidSecondIsInvalidToo() { TestUtils.ExecuteWithDispatcher((dispatcher, testCompleted) => { var vm = new DummyViewModel { Foo = "abc", Bar = "abc" }; Func <bool> validCondition = () => vm.Foo != vm.Bar; var validation = new ValidationHelper(); validation.AddAsyncRule( () => vm.Foo, () => vm.Bar, setResult => { ThreadPool.QueueUserWorkItem(_ => { setResult(RuleResult.Assert(validCondition(), "Foo must be different than bar")); }); }); validation.ValidateAsync(() => vm.Bar).ContinueWith(r => { Assert.False(r.Result.IsValid, "Validation must fail"); Assert.True(r.Result.ErrorList.Count == 2, "There must be 2 errors: one for each dependant property"); testCompleted(); }); }); }
public void AsyncValidation_SeveralAsyncRules_AllExecutedBeforeValidationCompleted() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); vm.Foo = null; var validation = new ValidationHelper(); bool rule1Executed = false; validation.AddAsyncRule(setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule1Executed = true; setResultDelegate(RuleResult.Valid()); })); bool rule2Executed = false; validation.AddAsyncRule(setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule2Executed = true; setResultDelegate(RuleResult.Valid()); })); bool rule3Executed = false; validation.AddAsyncRule(setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule3Executed = true; setResultDelegate(RuleResult.Valid()); })); validation.ValidateAllAsync().ContinueWith(r => { Assert.True(rule1Executed); Assert.True(rule2Executed); Assert.True(rule3Executed); Assert.True(r.Result.IsValid); completedAction(); }); }); }
public void AsyncValidation_ExecutedInsideUIThreadTask_ValidationRunsInBackgroundThread() { // This test checks the situation described here: // http://stackoverflow.com/questions/6800705/why-is-taskscheduler-current-the-default-taskscheduler // In short: if a continuation of a task runs on the UI thread, then the tasks that you start using // Task.Factory.StartNew will run also on the UI thread. This is unexpected an may cause a deadlock, or freezing UI. TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); int uiThreadId = Thread.CurrentThread.ManagedThreadId; int validationThreadId = uiThreadId; validation.AddAsyncRule(nameof(dummy.Foo), () => { return(Task.Run(() => { validationThreadId = Thread.CurrentThread.ManagedThreadId; return RuleResult.Valid(); })); }); // ACT // Execute validation within a continuation of another task that runs on // current synchronization context, so that the TaskScheduler.Current is set to the // UI context. Task.Factory.StartNew(() => { }).ContinueWith(t => { Task <ValidationResult> task = validation.ValidateAllAsync(); task.ContinueWith(result => { // VERIFY // Schedule the varification code with the dispatcher in order to take it // out of the ContinueWith continuation. Otherwise the exceptions will be stored inside the task and // unit testing framework will not know about them. uiThreadDispatcher.BeginInvoke(new Action(() => { Assert.False(uiThreadId == validationThreadId, "Validation must be executed in a background thread, so that the UI thread is not blocked."); completedAction(); })); }); }, TaskScheduler.FromCurrentSynchronizationContext()); }); }
public void AsyncValidation_GeneralSmokeTest() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel { Foo = null }; var validation = new ValidationHelper(); bool ruleExecuted = false; // OK, this is really strange, but if Action<bool> is not mentioned anywhere in the project, then ReSharter would fail to build and run the test... // So including the following line to fix it. Action <RuleResult> dummy = null; Assert.Null(dummy); // Getting rid of the "unused variable" warning. validation.AddAsyncRule(async() => { return(await Task.Run(() => { ruleExecuted = true; return RuleResult.Invalid("Foo cannot be empty string."); })); }); validation.ResultChanged += (o, e) => { Assert.True(ruleExecuted, "Validation rule must be executed before ValidationCompleted event is fired."); var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "ValidationResultChanged must be executed on UI thread"); }; var ui = TaskScheduler.FromCurrentSynchronizationContext(); validation.ValidateAllAsync().ContinueWith(r => { var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "Validation callback must be executed on UI thread"); Assert.False(r.Result.IsValid, "Validation must fail according to the validaton rule"); Assert.False(validation.GetResult().IsValid, "Validation must fail according to the validaton rule"); Assert.True(ruleExecuted, "Rule must be executed before validation completed callback is executed."); completedAction(); }, ui); }); }
public async Task ValidateAsync_AsyncRuleRegisteredWithNewSyntax_RuleIsExecuted() { // ARRANGE var validator = new ValidationHelper(); validator.AddAsyncRule(async() => await Task.Factory.StartNew(() => RuleResult.Invalid("error"))); // ACT var result = await validator.ValidateAllAsync(); // VERIFY Assert.False(result.IsValid); }
public void Validate_ThereAreAsyncRules_ThrowsException() { // ARRANGE var validation = new ValidationHelper(); // Add a simple sync rule validation.AddRule(RuleResult.Valid); // Add an async rule validation.AddAsyncRule(() => Task.Run(() => RuleResult.Invalid("Error"))); // ACT Assert.Throws <InvalidOperationException>(() => { validation.ValidateAll(); }); }
public void AsyncValidation_GeneralSmokeTest() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel {Foo = null}; var validation = new ValidationHelper(); bool ruleExecuted = false; // OK, this is really strange, but if Action<bool> is not mentioned anywhere in the project, then ReSharter would fail to build and run the test... // So including the following line to fix it. Action<RuleResult> dummy = null; Assert.Null(dummy); // Getting rid of the "unused variable" warning. validation.AddAsyncRule(setResult => ThreadPool.QueueUserWorkItem(_ => { ruleExecuted = true; setResult(RuleResult.Invalid("Foo cannot be empty string.")); })); validation.ResultChanged += (o, e) => { Assert.True(ruleExecuted, "Validation rule must be executed before ValidationCompleted event is fired."); var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "ValidationResultChanged must be executed on UI thread"); }; var ui = TaskScheduler.FromCurrentSynchronizationContext(); validation.ValidateAllAsync().ContinueWith(r => { var isUiThread = dispatcher.Thread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId; Assert.True(isUiThread, "Validation callback must be executed on UI thread"); Assert.False(r.Result.IsValid, "Validation must fail according to the validaton rule"); Assert.False(validation.GetResult().IsValid, "Validation must fail according to the validaton rule"); Assert.True(ruleExecuted, "Rule must be executed before validation completed callback is executed."); completedAction(); }, ui); }); }
public async void AsyncValidation_RuleThrowsException_ExceptionIsPropogated() { // ARRANGE var validator = new ValidationHelper(); validator.AddAsyncRule(async() => { await Task.Factory.StartNew(() => { throw new InvalidOperationException("Test"); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); return(RuleResult.Valid()); }); // ACT & VERIFY await Assert.ThrowsAsync <ValidationException>(() => validator.ValidateAllAsync()); }
public void AsyncValidation_MultipleRulesForSameTarget_DoesNotExecuteRulesIfPerviousFailed() { TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; validation.AddRule(nameof(dummy.Foo), () => { firstRuleExecuted = true; return(RuleResult.Invalid("Error1")); }); validation.AddAsyncRule(nameof(dummy.Foo), () => { return(Task.Run(() => { secondRuleExecuted = true; return RuleResult.Invalid("Error2"); })); }); // ACT validation.ValidateAllAsync().ContinueWith(result => { // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.False(secondRuleExecuted, "Second rule should not have been executed because first rule failed."); completedAction(); }); }); }
public async void MixedValidation_SyncRuleThrowsExceptionAfterSuccesfullAsyncRule_ExceptionIsPropogated() { // ARRANGE var validator = new ValidationHelper(); validator.AddAsyncRule(async() => { return(await Task.Run(() => RuleResult.Valid())); }); validator.AddRule(() => { throw new InvalidOperationException("Test"); }); // ACT & VERIFY var task = Assert.ThrowsAsync <ValidationException>(() => validator.ValidateAllAsync()); if (task != await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(5)))) { Assert.True(false, "Looks like the validation is stuck (didn't complete in a timeout), which means it didn't handle the exception properly."); } }
public void AsyncValidation_RuleThrowsException_ExceptionIsPropogated() { // ARRANGE var validator = new ValidationHelper(); validator.AddAsyncRule(async () => { await Task.Factory.StartNew(() => { throw new InvalidOperationException("Test"); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default); return RuleResult.Valid(); }); // ACT & VERIFY Assert.Throws<ValidationException>(validator.ValidateAllAsync()); }
public void ValidateAllAsync_SimilteniousCalls_DoesNotFail() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { const int numThreadsPerIternation = 4; const int iterationCount = 10; const int numThreads = numThreadsPerIternation * iterationCount; var resetEvent = new ManualResetEvent(false); int toProcess = numThreads; var validation = new ValidationHelper(); for (int i = 0; i < iterationCount; i++) { var target1 = new object(); var target2 = new object(); validation.AddAsyncRule(setResult => { setResult(RuleResult.Invalid("Error1")); }); validation.AddAsyncRule(target1, setResult => { setResult(RuleResult.Valid()); }); validation.AddRule(target2, () => { return(RuleResult.Invalid("Error2")); }); validation.AddRule(target2, RuleResult.Valid); Action <Action> testThreadBody = exercise => { try { exercise(); if (Interlocked.Decrement(ref toProcess) == 0) { resetEvent.Set(); } } catch (Exception ex) { dispatcher.BeginInvoke(new Action(() => { throw new AggregateException(ex); })); } }; var thread1 = new Thread(() => { testThreadBody(() => { validation.ValidateAllAsync().Wait(); }); }); var thread2 = new Thread(() => { testThreadBody(() => { validation.ValidateAllAsync().Wait(); }); }); var thread3 = new Thread(() => { testThreadBody(() => { validation.Validate(target2); }); }); var thread4 = new Thread(() => { testThreadBody(() => { validation.Validate(target2); }); }); thread1.Start(); thread2.Start(); thread3.Start(); thread4.Start(); } ThreadPool.QueueUserWorkItem(_ => { resetEvent.WaitOne(); completedAction(); }); }); }
public void AsyncValidation_MixedAsyncAndNotAsyncRules_AllExecutedBeforeValidationCompleted() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { var vm = new DummyViewModel(); var validation = new ValidationHelper(); bool rule1Executed = false; validation.AddAsyncRule(vm, setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule1Executed = true; setResultDelegate(RuleResult.Valid()); })); bool rule2Executed = false; validation.AddRule(vm, () => { rule2Executed = true; return RuleResult.Valid(); }); bool rule3Executed = false; validation.AddAsyncRule(vm, setResultDelegate => ThreadPool.QueueUserWorkItem(_ => { rule3Executed = true; setResultDelegate(RuleResult.Valid()); })); validation.ValidateAllAsync().ContinueWith(r => { Assert.True(rule1Executed); Assert.True(rule2Executed); Assert.True(rule3Executed); Assert.True(r.Result.IsValid); completedAction(); }); }); }
public void ValidateAsync_MultipleRulesForSameTarget_DoesNotExecuteRulesIfPerviousFailed() { TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); bool firstRuleExecuted = false; bool secondRuleExecuted = false; validation.AddRule(() => dummy.Foo, () => { firstRuleExecuted = true; return RuleResult.Invalid("Error1"); }); validation.AddAsyncRule(() => dummy.Foo, onCompleted => { secondRuleExecuted = true; onCompleted(RuleResult.Invalid("Error2")); }); // ACT validation.ValidateAllAsync().ContinueWith(result => { // VERIFY Assert.True(firstRuleExecuted, "First rule must have been executed"); Assert.False(secondRuleExecuted, "Second rule should not have been executed because first rule failed."); completedAction(); }); }); }
public void Validate_ThereAreAsyncRules_ThrowsException() { // ARRANGE var validation = new ValidationHelper(); // Add a simple sync rule validation.AddRule(RuleResult.Valid); // Add an async rule validation.AddAsyncRule(onCompleted => onCompleted(RuleResult.Invalid("Error"))); // ACT Assert.Throws<InvalidOperationException>(() => { validation.ValidateAll(); }); }
public void ValidatedAsync_ExecutedInsideUIThreadTask_ValidationRunsInBackgroundThread() { // This test checks the situation described here: // http://stackoverflow.com/questions/6800705/why-is-taskscheduler-current-the-default-taskscheduler // In short: if a continuation of a task runs on the UI thread, then the tasks that you start using // Task.Factory.StartNew will run also on the UI thread. This is unexpected an may cause a deadlock, or freezing UI. TestUtils.ExecuteWithDispatcher((uiThreadDispatcher, completedAction) => { // ARRANGE var validation = new ValidationHelper(); var dummy = new DummyViewModel(); int uiThreadId = Thread.CurrentThread.ManagedThreadId; int validationThreadId = uiThreadId; validation.AddAsyncRule(() => dummy.Foo, onCompleted => { validationThreadId = Thread.CurrentThread.ManagedThreadId; onCompleted(RuleResult.Valid()); }); // ACT // Execute validation within a continuation of another task that runs on // current synchronization context, so that the TaskScheduler.Current is set to the // UI context. Task.Factory.StartNew(() => { }).ContinueWith(t => { Task<ValidationResult> task = validation.ValidateAllAsync(); task.ContinueWith(result => { // VERIFY // Schedule the varification code with the dispatcher in order to take it // out of the ContinueWith continuation. Otherwise the exceptions will be stored inside the task and // unit testing framework will not know about them. uiThreadDispatcher.BeginInvoke(new Action(() => { Assert.False(uiThreadId == validationThreadId, "Validation must be executed in a background thread, so that the UI thread is not blocked."); completedAction(); })); }); }, TaskScheduler.FromCurrentSynchronizationContext()); }); }
public async Task ValidateAsync_AsyncRuleRegisteredWithNewSyntax_RuleIsExecuted() { // ARRANGE var validator = new ValidationHelper(); validator.AddAsyncRule(async () => await Task.Factory.StartNew(() => RuleResult.Invalid("error"))); // ACT var result = await validator.ValidateAllAsync(); // VERIFY Assert.False(result.IsValid); }
public void ValidateAllAsync_SimilteniousCalls_DoesNotFail() { TestUtils.ExecuteWithDispatcher((dispatcher, completedAction) => { const int numThreadsPerIternation = 4; const int iterationCount = 10; const int numThreads = numThreadsPerIternation * iterationCount; var resetEvent = new ManualResetEvent(false); int toProcess = numThreads; var validation = new ValidationHelper(); for (int i = 0; i < iterationCount; i++) { var target1 = new object(); var target2 = new object(); validation.AddAsyncRule(setResult => { setResult(RuleResult.Invalid("Error1")); }); validation.AddAsyncRule(target1, setResult => { setResult(RuleResult.Valid()); }); validation.AddRule(target2, () => { return RuleResult.Invalid("Error2"); }); validation.AddRule(target2, RuleResult.Valid); Action<Action> testThreadBody = exercise => { try { exercise(); if (Interlocked.Decrement(ref toProcess) == 0) resetEvent.Set(); } catch (Exception ex) { dispatcher.BeginInvoke(new Action(() => { throw new AggregateException(ex); })); } }; var thread1 = new Thread(() => { testThreadBody(() => { validation.ValidateAllAsync().Wait(); }); }); var thread2 = new Thread(() => { testThreadBody(() => { validation.ValidateAllAsync().Wait(); }); }); var thread3 = new Thread(() => { testThreadBody(() => { validation.Validate(target2); }); }); var thread4 = new Thread(() => { testThreadBody(() => { validation.Validate(target2); }); }); thread1.Start(); thread2.Start(); thread3.Start(); thread4.Start(); } ThreadPool.QueueUserWorkItem(_ => { resetEvent.WaitOne(); completedAction(); }); }); }
public void AsyncValidation_DependantProperties_IfOneInvalidSecondIsInvalidToo() { TestUtils.ExecuteWithDispatcher((dispatcher, testCompleted) => { var vm = new DummyViewModel { Foo = "abc", Bar = "abc" }; Func<bool> validCondition = () => vm.Foo != vm.Bar; var validation = new ValidationHelper(); validation.AddAsyncRule( () => vm.Foo, () => vm.Bar, setResult => { ThreadPool.QueueUserWorkItem(_ => { setResult(RuleResult.Assert(validCondition(), "Foo must be different than bar")); }); }); validation.ValidateAsync(() => vm.Bar).ContinueWith(r => { Assert.False(r.Result.IsValid, "Validation must fail"); Assert.True(r.Result.ErrorList.Count == 2, "There must be 2 errors: one for each dependant property"); testCompleted(); }); }); }