// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. namespace System.Data.Entity.Infrastructure { using System.Diagnostics.Eventing.Reader; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Transactions; using Moq; using Xunit; public class ExecutionStrategyTests { [Fact] public void Constructor_throws_on_null_parameters() { Assert.Equal( "retryDelayStrategy", Assert.Throws(() => new ExecutionStrategy(null, new Mock().Object)).ParamName); Assert.Equal( "retriableExceptionDetector", Assert.Throws(() => new ExecutionStrategy(new Mock().Object, null)).ParamName); } [Fact] public void SupportsExistingTransactions_returns_false() { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Assert.False(mockExecutionStrategy.SupportsExistingTransactions); } public class Execute { [Fact] public void Execute_Action_throws_for_an_existing_transaction() { Execute_throws_for_an_existing_transaction(e => e.Execute(() => { })); } [Fact] public void Execute_Func_throws_for_an_existing_transaction() { Execute_throws_for_an_existing_transaction(e => e.Execute(() => 1)); } private void Execute_throws_for_an_existing_transaction(Action executeAsync) { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; using (new TransactionScope()) { Assert.Throws( () => executeAsync(mockExecutionStrategy)) .ValidateMessage("ExecutionStrategy_ExistingTransaction"); } } [Fact] public void Execute_Action_throws_when_invoked_twice() { Execute_throws_when_invoked_twice(e => e.Execute(() => { })); } [Fact] public void Execute_Func_throws_when_invoked_twice() { Execute_throws_when_invoked_twice(e => e.Execute(() => 1)); } private void Execute_throws_when_invoked_twice(Action Execute) { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Execute(mockExecutionStrategy); Assert.Throws( () => Execute(mockExecutionStrategy)) .ValidateMessage("ExecutionStrategy_AlreadyExecuted"); } [Fact] public void Execute_Action_throws_on_null_parameters() { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Assert.Equal( "action", Assert.Throws(() => mockExecutionStrategy.Execute(null)).ParamName); } [Fact] public void Execute_Func_throws_on_null_parameters() { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Assert.Equal( "func", Assert.Throws(() => mockExecutionStrategy.Execute((Func)null)).ParamName); } [Fact] public void Execute_Action_throws_on_invalid_delay() { Execute_throws_on_invalid_delay((e, f) => e.Execute(() => { f(); })); } [Fact] public void Execute_Func_throws_on_invalid_delay() { Execute_throws_on_invalid_delay((e, f) => e.Execute(f)); } private void Execute_throws_on_invalid_delay(Action> execute) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(-1)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; Assert.Throws( () => execute( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { Assert.True(false); return 0; } })).ValidateMessage("ExecutionStrategy_NegativeDelay"); Assert.Equal(1, executionCount); } [Fact] public void Execute_Action_doesnt_retry_if_succesful() { Execute_doesnt_retry_if_succesful((e, f) => e.Execute(() => { f(); })); } [Fact] public void Execute_Func_doesnt_retry_if_succesful() { Execute_doesnt_retry_if_succesful((e, f) => e.Execute(f)); } private void Execute_doesnt_retry_if_succesful(Action> execute) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => { Assert.True(false); return null; }); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => { Assert.True(false); return false; }); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; execute(mockExecutionStrategy, () => executionCount++); Assert.Equal(1, executionCount); } [Fact] public void Execute_Action_retries_until_succesful() { Execute_retries_until_succesful((e, f) => e.Execute(() => { f(); })); } [Fact] public void Execute_Func_retries_until_succesful() { Execute_retries_until_succesful((e, f) => e.Execute(f)); } private void Execute_retries_until_succesful(Action> execute) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(0)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; execute( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } return executionCount; }); Assert.Equal(4, executionCount); } [Fact] public void Execute_Action_retries_until_not_retrieable_exception_is_thrown() { Execute_retries_until_not_retrieable_exception_is_thrown((e, f) => e.Execute(() => { f(); })); } [Fact] public void Execute_Func_retries_until_not_retrieable_exception_is_thrown() { Execute_retries_until_not_retrieable_exception_is_thrown((e, f) => e.Execute(f)); } private void Execute_retries_until_not_retrieable_exception_is_thrown(Action> execute) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(0)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; Assert.Throws( () => execute( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { throw new EventLogException(); } })); Assert.Equal(4, executionCount); } [Fact] public void Execute_Action_retries_until_limit_is_reached() { Execute_retries_until_limit_is_reached((e, f) => e.Execute(() => { f(); })); } [Fact] public void Execute_Func_retries_until_limit_is_reached() { Execute_retries_until_limit_is_reached((e, f) => e.Execute(f)); } private void Execute_retries_until_limit_is_reached(Action> execute) { var executionCount = 0; var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => executionCount < 3 ? (TimeSpan?)TimeSpan.FromTicks(0) : null); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; Assert.IsType( Assert.Throws( () => execute( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { Assert.True(false); return 0; } })).InnerException); Assert.Equal(3, executionCount); } } #if !NET40 public class ExecuteAsync { [Fact] public void ExecuteAsync_Action_throws_for_an_existing_transaction() { ExecuteAsync_throws_for_an_existing_transaction(e => e.ExecuteAsync(() => (Task)Task.FromResult(1))); } [Fact] public void ExecuteAsync_Func_throws_for_an_existing_transaction() { ExecuteAsync_throws_for_an_existing_transaction(e => e.ExecuteAsync(() => Task.FromResult(1))); } private void ExecuteAsync_throws_for_an_existing_transaction(Func executeAsync) { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; using (new TransactionScope()) { Assert.Throws( () => executeAsync(mockExecutionStrategy)) .ValidateMessage("ExecutionStrategy_ExistingTransaction"); } } [Fact] public void ExecuteAsync_Action_throws_when_invoked_twice() { ExecuteAsync_throws_when_invoked_twice(e => e.ExecuteAsync(() => (Task)Task.FromResult(1))); } [Fact] public void ExecuteAsync_Func_throws_when_invoked_twice() { ExecuteAsync_throws_when_invoked_twice(e => e.ExecuteAsync(() => Task.FromResult(1))); } private void ExecuteAsync_throws_when_invoked_twice(Func executeAsync) { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; executeAsync(mockExecutionStrategy).Wait(); Assert.Throws( () => ExceptionHelpers.UnwrapAggregateExceptions( () => executeAsync(mockExecutionStrategy).Wait())) .ValidateMessage("ExecutionStrategy_AlreadyExecuted"); } [Fact] public void ExecuteAsync_Action_throws_on_null_parameters() { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Assert.Equal( "taskFunc", Assert.Throws(() => mockExecutionStrategy.ExecuteAsync(null).Wait()).ParamName); } [Fact] public void ExecuteAsync_Func_throws_on_null_parameters() { var mockExecutionStrategy = new Mock(new Mock().Object, new Mock().Object) { CallBase = true }.Object; Assert.Equal( "taskFunc", Assert.Throws(() => mockExecutionStrategy.ExecuteAsync((Func>)null).Wait()).ParamName); } [Fact] public void ExecuteAsync_Action_throws_on_invalid_delay() { ExecuteAsync_throws_on_invalid_delay((e, f) => e.ExecuteAsync(() => (Task)f())); } [Fact] public void ExecuteAsync_Func_throws_on_invalid_delay() { ExecuteAsync_throws_on_invalid_delay((e, f) => e.ExecuteAsync(f)); } private void ExecuteAsync_throws_on_invalid_delay(Func>, Task> executeAsync) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(-1)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; Assert.Throws( () => ExceptionHelpers.UnwrapAggregateExceptions( () => executeAsync( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { Assert.True(false); return Task.FromResult(0); } }).Wait())).ValidateMessage("ExecutionStrategy_NegativeDelay"); Assert.Equal(1, executionCount); } [Fact] public void ExecuteAsync_Action_doesnt_retry_if_succesful() { ExecuteAsync_doesnt_retry_if_succesful((e, f) => e.ExecuteAsync(() => (Task)f())); } [Fact] public void ExecuteAsync_Func_doesnt_retry_if_succesful() { ExecuteAsync_doesnt_retry_if_succesful((e, f) => e.ExecuteAsync(f)); } private void ExecuteAsync_doesnt_retry_if_succesful(Func>, Task> executeAsync) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => { Assert.True(false); return null; }); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => { Assert.True(false); return false; }); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; executeAsync(mockExecutionStrategy, () => Task.FromResult(executionCount++)).Wait(); Assert.Equal(1, executionCount); } [Fact] public void ExecuteAsync_Action_retries_until_succesful() { ExecuteAsync_retries_until_succesful((e, f) => e.ExecuteAsync(() => (Task)f())); } [Fact] public void ExecuteAsync_Func_retries_until_succesful() { ExecuteAsync_retries_until_succesful((e, f) => e.ExecuteAsync(f)); } private void ExecuteAsync_retries_until_succesful(Func>, Task> executeAsync) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(0)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; executeAsync( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } return Task.FromResult(executionCount); }).Wait(); Assert.Equal(4, executionCount); } [Fact] public void ExecuteAsync_Action_retries_until_not_retrieable_exception_is_thrown() { ExecuteAsync_retries_until_not_retrieable_exception_is_thrown((e, f) => e.ExecuteAsync(() => (Task)f())); } [Fact] public void ExecuteAsync_Func_retries_until_not_retrieable_exception_is_thrown() { ExecuteAsync_retries_until_not_retrieable_exception_is_thrown((e, f) => e.ExecuteAsync(f)); } private void ExecuteAsync_retries_until_not_retrieable_exception_is_thrown( Func>, Task> executeAsync) { var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => TimeSpan.FromTicks(0)); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; var executionCount = 0; Assert.Throws( () => ExceptionHelpers.UnwrapAggregateExceptions( () => executeAsync( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { throw new EventLogException(); } }).Wait())); Assert.Equal(4, executionCount); } [Fact] public void ExecuteAsync_Action_retries_until_limit_is_reached() { ExecuteAsync_retries_until_limit_is_reached((e, f) => e.ExecuteAsync(() => (Task)f())); } [Fact] public void ExecuteAsync_Func_retries_until_limit_is_reached() { ExecuteAsync_retries_until_limit_is_reached((e, f) => e.ExecuteAsync(f)); } private void ExecuteAsync_retries_until_limit_is_reached(Func>, Task> executeAsync) { var executionCount = 0; var mockRetryDelayStrategy = new Mock(); mockRetryDelayStrategy.Setup(m => m.GetNextDelay(It.IsAny())).Returns( e => executionCount < 3 ? (TimeSpan?)TimeSpan.FromTicks(0) : null); var mockRetriableExceptionDetector = new Mock(); mockRetriableExceptionDetector.Setup(m => m.ShouldRetryOn(It.IsAny())).Returns( e => e is ExternalException); var mockExecutionStrategy = new Mock(mockRetryDelayStrategy.Object, mockRetriableExceptionDetector.Object) { CallBase = true }.Object; Assert.IsType( Assert.Throws( () => ExceptionHelpers.UnwrapAggregateExceptions( () => executeAsync( mockExecutionStrategy, () => { if (executionCount++ < 3) { throw new ExternalException(); } else { Assert.True(false); return Task.FromResult(0); } }).Wait())).InnerException); Assert.Equal(3, executionCount); } } #endif } }