public void Exceptions_in_OnQueueEmpty_are_ignored() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var exceptionSimulated = false; var lockObject = new Object(); var queueName = "myqueue"; var mockQueue = GetMockQueue(queueName); var mockQueueClient = GetMockQueueClient(mockQueue); var mockBlobContainer = GetMockBlobContainer(); var mockBlobClient = GetMockBlobClient(mockBlobContainer); var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient); mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync(Enumerable.Empty <CloudQueueMessage>()); var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); // Simulate an exception (only the first time) lock (lockObject) { if (!exceptionSimulated) { exceptionSimulated = true; throw new Exception("This dummy exception should be ignored"); } } // Stop the message pump messagePump.Stop(); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); }; // Act messagePump.Start(); // Assert onMessageInvokeCount.ShouldBe(0); onQueueEmptyInvokeCount.ShouldBeGreaterThan(0); onErrorInvokeCount.ShouldBe(0); mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1)); }
public void Start_without_OnMessage_throws() { // Arrange var mockStorageUri = new Uri("http://bogus/myaccount"); var mockQueue = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri); var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3); // Act messagePump.Start(); }
public void Exceptions_in_OnQueueEmpty_are_ignored() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var exceptionSimulated = false; var lockObject = new Object(); var mockStorageUri = new Uri("http://bogus/myaccount"); var mockQueue = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri); mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns <CloudQueueMessage>(null); var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); // Simulate an exception (only the first time) lock (lockObject) { if (!exceptionSimulated) { exceptionSimulated = true; throw new Exception("This dummy exception should be ignored"); } } // Run the 'OnStop' on a different thread so we don't block it Task.Run(() => { messagePump.Stop(); }).ConfigureAwait(false); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); }; // Act messagePump.Start(); // Assert Assert.AreEqual(0, onMessageInvokeCount); Assert.IsTrue(onQueueEmptyInvokeCount > 0); Assert.AreEqual(0, onErrorInvokeCount); mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1)); }
public void No_message_processed_when_queue_is_empty() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var mockStorageUri = new Uri("http://bogus/myaccount"); var mockQueue = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri); mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns <CloudQueueMessage>(null); var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), 3); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); // Run the 'OnStop' on a different thread so we don't block it Task.Run(() => { messagePump.Stop(); }).ConfigureAwait(false); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); }; // Act messagePump.Start(); // Assert Assert.AreEqual(0, onMessageInvokeCount); Assert.AreEqual(1, onQueueEmptyInvokeCount); Assert.AreEqual(0, onErrorInvokeCount); // You would expect the 'GetMessageAsync' method to be invoked only once, but unfortunately we can't be sure. // It will be invoked a small number of times (probably once or twice, maybe three times but not more than that). // However we can't be more precise because we stop the message pump on another thread and 'GetMessageAsync' may be invoked // a few times while we wait for the message pump to stop. // // What this means is that there is no way to precisely assert the number of times the method has been invoked. The only // thing we know for sure, is that 'GetMessageAsync' has been invoked at least once mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1)); }
public void No_message_processed_when_queue_is_empty() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var queueName = "myqueue"; var mockQueue = GetMockQueue(queueName); var mockQueueClient = GetMockQueueClient(mockQueue); var mockBlobContainer = GetMockBlobContainer(); var mockBlobClient = GetMockBlobClient(mockBlobContainer); var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient); mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync(Enumerable.Empty <CloudQueueMessage>()); var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); messagePump.Stop(); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); }; // Act messagePump.Start(); // Assert onMessageInvokeCount.ShouldBe(0); onQueueEmptyInvokeCount.ShouldBe(1); onErrorInvokeCount.ShouldBe(0); // You would expect the 'GetMessagesAsync' method to be invoked only once, but unfortunately we can't be sure. // It will be invoked a small number of times (probably once or twice, maybe three times but not more than that). // However we can't be more precise because we stop the message pump on another thread and 'GetMessagesAsync' may // be invoked a few times while we wait for the message pump to stop. // // What this means is that there is no way to precisely assert the number of times the method has been invoked. // The only thing we know for sure, is that 'GetMessagesAsync' has been invoked at least once mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(1)); }
public void Start_without_OnMessage_throws() { // Arrange var queueName = "myqueue"; var mockQueue = GetMockQueue(queueName); var mockQueueClient = GetMockQueueClient(mockQueue); var mockBlobContainer = GetMockBlobContainer(); var mockBlobClient = GetMockBlobClient(mockBlobContainer); var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient); var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), 3); // Act Should.Throw <ArgumentNullException>(() => messagePump.Start()); }
public static void ProcessSimpleMessages(string queueName, CloudStorageAccount storageAccount, ILogProvider logProvider, IMetrics metrics) { var logger = logProvider.GetLogger("ProcessSimpleMessages"); Stopwatch sw = null; // Configure the message pump var messagePump = new AsyncMessagePump(queueName, storageAccount, 10, TimeSpan.FromMinutes(1), 3, metrics) { OnMessage = (message, cancellationToken) => { logger(Logging.LogLevel.Debug, () => message.Content.ToString()); } }; // Stop the message pump when the queue is empty. messagePump.OnQueueEmpty = cancellationToken => { // Stop the timer if (sw.IsRunning) { sw.Stop(); } // Stop the message pump logger(Logging.LogLevel.Debug, () => "Asking the 'simple' message pump to stop"); messagePump.Stop(); logger(Logging.LogLevel.Debug, () => "The 'simple' message pump has been stopped"); }; // Start the message pump sw = Stopwatch.StartNew(); logger(Logging.LogLevel.Debug, () => "The 'simple' message pump is starting"); messagePump.Start(); // Display summary logger(Logging.LogLevel.Info, () => $"\tDone in {sw.Elapsed.ToDurationString()}"); }
public void Poison_message_is_rejected() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var isRejected = false; var retries = 3; var lockObject = new Object(); var cloudMessage = new CloudQueueMessage("Message"); var queueName = "myqueue"; var mockQueue = GetMockQueue(queueName); var mockQueueClient = GetMockQueueClient(mockQueue); var mockBlobContainer = GetMockBlobContainer(); var mockBlobClient = GetMockBlobClient(mockBlobContainer); var mockStorageAccount = GetMockStorageAccount(mockBlobClient, mockQueueClient); mockQueue.Setup(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).ReturnsAsync((int messageCount, TimeSpan? visibilityTimeout, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) => { if (cloudMessage != null) { lock (lockObject) { if (cloudMessage != null) { // DequeueCount is a private property. Therefore we must use reflection to change its value var dequeueCountProperty = cloudMessage.GetType().GetProperty("DequeueCount"); dequeueCountProperty.SetValue(cloudMessage, retries + 1); // intentionally set 'DequeueCount' to a value exceeding maxRetries to simulate a poison message return(new[] { cloudMessage }); } } } return(Enumerable.Empty <CloudQueueMessage>()); }); mockQueue.Setup(q => q.DeleteMessageAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns((string messageId, string popReceipt, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) => { lock (lockObject) { cloudMessage = null; } return(Task.FromResult(true)); }); var messagePump = new AsyncMessagePump("myqueue", mockStorageAccount.Object, 1, TimeSpan.FromMinutes(1), retries); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); throw new Exception("An error occured when attempting to process the message"); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); messagePump.Stop(); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); if (isPoison) { lock (lockObject) { isRejected = true; cloudMessage = null; } } }; // Act messagePump.Start(); // Assert onMessageInvokeCount.ShouldBe(1); onQueueEmptyInvokeCount.ShouldBeGreaterThan(0); onErrorInvokeCount.ShouldBe(1); isRejected.ShouldBeTrue(); mockQueue.Verify(q => q.GetMessagesAsync(It.IsAny <int>(), It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(2)); mockQueue.Verify(q => q.DeleteMessageAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.Exactly(1)); }
public void Poison_message_is_rejected() { // Arrange var onMessageInvokeCount = 0; var onQueueEmptyInvokeCount = 0; var onErrorInvokeCount = 0; var isRejected = false; var retries = 3; var lockObject = new Object(); var cloudMessage = new CloudQueueMessage("Message"); var mockStorageUri = new Uri("http://bogus/myaccount"); var mockQueue = new Mock <CloudQueue>(MockBehavior.Strict, mockStorageUri); mockQueue.Setup(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>())).Returns((TimeSpan? visibilityTimeout, QueueRequestOptions options, OperationContext operationContext, CancellationToken cancellationToken) => { if (cloudMessage == null) { return(null); } lock (lockObject) { if (cloudMessage != null) { // DequeueCount is a private property. Therefore we must use reflection to change its value var t = cloudMessage.GetType(); t.InvokeMember("DequeueCount", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, null, cloudMessage, new object[] { cloudMessage.DequeueCount + 1 }); } return(Task.FromResult(cloudMessage)); } }); mockQueue.Setup(q => q.DeleteMessageAsync(It.IsAny <CloudQueueMessage>())).Returns((CloudQueueMessage message) => { lock (lockObject) { cloudMessage = null; } return(Task.FromResult(true)); }); var messagePump = new AsyncMessagePump(mockQueue.Object, 1, 1, TimeSpan.FromMinutes(1), retries); messagePump.OnMessage = (message, cancellationToken) => { Interlocked.Increment(ref onMessageInvokeCount); throw new Exception("An error occured when attempting to process the message"); }; messagePump.OnQueueEmpty = cancellationToken => { Interlocked.Increment(ref onQueueEmptyInvokeCount); // Run the 'OnStop' on a different thread so we don't block it Task.Run(() => { messagePump.Stop(); }).ConfigureAwait(false); }; messagePump.OnError = (message, exception, isPoison) => { Interlocked.Increment(ref onErrorInvokeCount); if (isPoison) { lock (lockObject) { isRejected = true; cloudMessage = null; } } }; // Act messagePump.Start(); // Assert Assert.AreEqual(retries + 1, onMessageInvokeCount); Assert.IsTrue(onQueueEmptyInvokeCount > 0); Assert.AreEqual(retries + 1, onErrorInvokeCount); Assert.IsTrue(isRejected); mockQueue.Verify(q => q.GetMessageAsync(It.IsAny <TimeSpan?>(), It.IsAny <QueueRequestOptions>(), It.IsAny <OperationContext>(), It.IsAny <CancellationToken>()), Times.AtLeast(retries)); }
#pragma warning disable RECS0154 // Parameter is never used static void Main(string[] args) #pragma warning restore RECS0154 // Parameter is never used { // Ensure the storage emulator is running AzureStorageEmulatorManager.StartStorageEmulator(); // If you want to see tracing from the Picton libary, change the LogLevel to 'Trace' var minLogLevel = Logging.LogLevel.Debug; // Configure logging to the console var logProvider = new ColoredConsoleLogProvider(minLogLevel); var logger = logProvider.GetLogger("IntegrationTests"); LogProvider.SetCurrentLogProvider(logProvider); // Ensure the Console is tall enough Console.WindowHeight = 60; // Setup the message queue in Azure storage emulator var storageAccount = CloudStorageAccount.DevelopmentStorageAccount; var cloudQueueClient = storageAccount.CreateCloudQueueClient(); cloudQueueClient.DefaultRequestOptions.RetryPolicy = new NoRetry(); var cloudQueue = cloudQueueClient.GetQueueReference("myqueue"); cloudQueue.CreateIfNotExists(); var lockObject = new Object(); var stopping = false; Stopwatch sw = null; // Add messages to our testing queue for (var i = 0; i < 50; i++) { cloudQueue.AddMessage(new CloudQueueMessage($"Hello world {i}")); } // Configure the message pump var messagePump = new AsyncMessagePump(cloudQueue, 1, 25, TimeSpan.FromMinutes(1), 3); messagePump.OnMessage = (message, cancellationToken) => { logger(Logging.LogLevel.Debug, () => message.AsString); }; messagePump.OnQueueEmpty = cancellationToken => { // Stop the message pump when the queue is empty. // However, ensure that we try to stop it only once (otherwise each concurrent task would try to stop it) if (!stopping) { lock (lockObject) { if (sw.IsRunning) { sw.Stop(); } if (!stopping) { // Indicate that the message pump is stopping stopping = true; // Log to console logger(Logging.LogLevel.Debug, () => "Asking the message pump to stop"); // Run the 'OnStop' on a different thread so we don't block it Task.Run(() => { messagePump.Stop(); logger(Logging.LogLevel.Debug, () => "Message pump has been stopped"); }).ConfigureAwait(false); } } } }; // Start the message pump sw = Stopwatch.StartNew(); logger(Logging.LogLevel.Debug, () => "Message pump is starting"); messagePump.Start(); // Display summary logger(Logging.LogLevel.Info, () => "Elapsed Milliseconds: " + sw.Elapsed.ToDurationString()); logger(Logging.LogLevel.Info, () => "Press any key to exit..."); // Flush the console key buffer while (Console.KeyAvailable) { Console.ReadKey(true); } // Wait for user to press a key Console.ReadKey(); }