public async Task ProcessFileAsync_Failure_RetriesAndLeavesFailureStatusFile()
        {
            string testFile = WriteTestFile("dat");

            FunctionResult result = new FunctionResult(false);
            mockExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>())).ReturnsAsync(result);

            FileSystemEventArgs eventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(testFile), Path.GetFileName(testFile));
            bool fileProcessedSuccessfully = await processor.ProcessFileAsync(eventArgs, CancellationToken.None);

            Assert.False(fileProcessedSuccessfully);
            Assert.True(File.Exists(testFile));
            string statusFilePath = processor.GetStatusFile(testFile);

            StatusFileEntry[] entries = File.ReadAllLines(statusFilePath).Select(p => JsonConvert.DeserializeObject<StatusFileEntry>(p)).ToArray();

            Assert.Equal(6, entries.Length);

            // verify each of the retries
            for (int i = 0; i < processor.MaxProcessCount; i++)
            {
                // verify the processing entry
                Assert.Equal(ProcessingState.Processing, entries[i * 2].State);
                Assert.Equal(i + 1, entries[i * 2].ProcessCount);

                // verify the failure entry
                Assert.Equal(ProcessingState.Failed, entries[(i * 2) + 1].State);
                Assert.Equal(i + 1, entries[(i * 2) + 1].ProcessCount);
            }
        }
        protected override async Task ReleaseMessageAsync(CloudQueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
        {
            //Use exponential backoff. Default implementation retries the operation way too quickly.
            visibilityTimeout = TimeSpan.FromSeconds(Math.Pow(3, message.DequeueCount));

            await base.ReleaseMessageAsync(message, result, visibilityTimeout, cancellationToken);
        }
 public void Constructor_Exception()
 {
     Exception exception = new Exception("Kaboom!");
     FunctionResult result = new FunctionResult(exception);
     Assert.False(result.Succeeded);
     Assert.Same(exception, result.Exception);
 }
 /// <summary>
 /// This method completes processing of the specified message, after the job function has been invoked.
 /// </summary>
 /// <param name="message">The message to complete processing for.</param>
 /// <param name="result">The <see cref="FunctionResult"/> from the job invocation.</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use</param>
 /// <returns></returns>
 public virtual async Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
 {
     if (!result.Succeeded)
     {
         cancellationToken.ThrowIfCancellationRequested();
         await message.AbandonAsync();
     }
 }
 public virtual Task EndMessageArrivedAsync(string message, FunctionResult result, CancellationToken cancellationToken)
 {
     if (!result.Succeeded)
     {
         cancellationToken.ThrowIfCancellationRequested();
     }
     return Task.FromResult(0);
 }
        public async Task CompleteProcessingMessageAsync_DefaultOnMessageOptions()
        {
            MessageProcessor processor = new MessageProcessor(new OnMessageOptions());

            BrokeredMessage message = new BrokeredMessage();
            FunctionResult result = new FunctionResult(true);
            await processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None);
        }
        public void Constructor_Boolean()
        {
            FunctionResult result = new FunctionResult(true);
            Assert.True(result.Succeeded);
            Assert.Null(result.Exception);

            result = new FunctionResult(false);
            Assert.False(result.Succeeded);
            Assert.Null(result.Exception);
        }
        public void Constructor_BooleanAndException()
        {
            Exception exception = new Exception("Kaboom!");
            FunctionResult result = new FunctionResult(true, exception);
            Assert.True(result.Succeeded);
            Assert.Same(exception, result.Exception);

            result = new FunctionResult(false, exception);
            Assert.False(result.Succeeded);
            Assert.Same(exception, result.Exception);
        }
예제 #9
0
        public async Task <FunctionResult> TryExecuteAsync(TriggeredFunctionData input, CancellationToken cancellationToken)
        {
            IFunctionInstance instance  = _instanceFactory.Create((TTriggerValue)input.TriggerValue, input.ParentId);
            IDelayedException exception = await _executor.TryExecuteAsync(instance, cancellationToken);

            FunctionResult result = exception != null ?
                                    new FunctionResult(exception.Exception)
                : new FunctionResult(true);

            return(result);
        }
        public async Task ProcessFileAsync_Failure_LeavesInProgressStatusFile()
        {
            string testFile = WriteTestFile("dat");

            FunctionResult result = new FunctionResult(false);
            mockExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>())).ReturnsAsync(result);

            FileSystemEventArgs eventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(testFile), Path.GetFileName(testFile));
            bool fileProcessedSuccessfully = await processor.ProcessFileAsync(eventArgs, CancellationToken.None);

            Assert.False(fileProcessedSuccessfully);
            Assert.True(File.Exists(testFile));
            string statusFilePath = processor.GetStatusFile(testFile);
            StatusFileEntry entry = processor.GetLastStatus(statusFilePath);
            Assert.Equal(ProcessingState.Processing, entry.State);
        }
        public async Task ProcessMessageAsync_Success()
        {
            BrokeredMessage message = new BrokeredMessage();
            CancellationToken cancellationToken = new CancellationToken();
            _mockMessageProcessor.Setup(p => p.BeginProcessingMessageAsync(message, cancellationToken)).ReturnsAsync(true);

            FunctionResult result = new FunctionResult(true);
            _mockExecutor.Setup(p => p.TryExecuteAsync(It.Is<TriggeredFunctionData>(q => q.TriggerValue == message), cancellationToken)).ReturnsAsync(result);

            _mockMessageProcessor.Setup(p => p.CompleteProcessingMessageAsync(message, result, cancellationToken)).Returns(Task.FromResult(0));

            await _listener.ProcessMessageAsync(message, CancellationToken.None);

            _mockMessageProcessor.VerifyAll();
            _mockExecutor.VerifyAll();
            _mockMessageProcessor.VerifyAll();
        }
예제 #12
0
 /// <summary>
 /// This method completes processing of the specified message, after the job function has been invoked.
 /// </summary>
 /// <param name="message">The message to complete processing for.</param>
 /// <param name="result">The <see cref="FunctionResult"/> from the job invocation.</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use</param>
 /// <returns>A <see cref="Task"/> that will complete the message processing.</returns>
 public virtual async Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
 {
     if (result.Succeeded)
     {
         if (!MessageOptions.AutoComplete)
         {
             // AutoComplete is true by default, but if set to false
             // we need to complete the message
             cancellationToken.ThrowIfCancellationRequested();
             await message.CompleteAsync();
         }
     }
     else
     {
         cancellationToken.ThrowIfCancellationRequested();
         await message.AbandonAsync();
     }
 }
        public async Task CompleteProcessingMessageAsync_Failure_AbandonsMessage()
        {
            OnMessageOptions options = new OnMessageOptions
            {
                AutoComplete = false
            };
            MessageProcessor processor = new MessageProcessor(options);

            BrokeredMessage message = new BrokeredMessage();
            FunctionResult result = new FunctionResult(false);
            var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
            {
                await processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None);
            });

            // this verifies that we initiated the abandon
            Assert.True(ex.ToString().Contains("Microsoft.ServiceBus.Messaging.BrokeredMessage.BeginAbandon"));
        }
        public async Task CompleteProcessingMessageAsync_Success_CompletesMessage_WhenAutoCompleteFalse()
        {
            OnMessageOptions options = new OnMessageOptions
            {
                AutoComplete = false
            };
            MessageProcessor processor = new MessageProcessor(options);

            BrokeredMessage message = new BrokeredMessage();
            FunctionResult result = new FunctionResult(true);
            var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
            {
                await processor.CompleteProcessingMessageAsync(message, result, CancellationToken.None);
            });

            // The service bus APIs aren't unit testable, so in this test suite
            // we rely on exception stacks to verify APIs are called as expected.
            // this verifies that we initiated the completion
            Assert.True(ex.ToString().Contains("Microsoft.ServiceBus.Messaging.BrokeredMessage.BeginComplete"));
        }
 public override Task CompleteProcessingMessageAsync(CloudQueueMessage message, FunctionResult result, CancellationToken cancellationToken)
 {
     return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
 }
        public async Task ProcessFileAsync_ChangeTypeCreate_Success()
        {
            FileTriggerAttribute attribute = new FileTriggerAttribute(attributeSubPath, "*.dat");
            processor = CreateTestProcessor(attribute);

            string testFile = WriteTestFile("dat");

            FunctionResult result = new FunctionResult(true);
            mockExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>())).ReturnsAsync(result);

            string testFilePath = Path.GetDirectoryName(testFile);
            string testFileName = Path.GetFileName(testFile);
            FileSystemEventArgs eventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, testFilePath, testFileName);
            bool fileProcessedSuccessfully = await processor.ProcessFileAsync(eventArgs, CancellationToken.None);

            Assert.True(fileProcessedSuccessfully);
            string expectedStatusFile = processor.GetStatusFile(testFile);
            Assert.True(File.Exists(testFile));
            Assert.True(File.Exists(expectedStatusFile));
            string[] lines = File.ReadAllLines(expectedStatusFile);
            Assert.Equal(2, lines.Length);

            StatusFileEntry entry = (StatusFileEntry)_serializer.Deserialize(new StringReader(lines[0]), typeof(StatusFileEntry));
            Assert.Equal(ProcessingState.Processing, entry.State);
            Assert.Equal(WatcherChangeTypes.Created, entry.ChangeType);
            Assert.Equal(InstanceId.Substring(0, 20), entry.InstanceId);

            entry = (StatusFileEntry)_serializer.Deserialize(new StringReader(lines[1]), typeof(StatusFileEntry));
            Assert.Equal(ProcessingState.Processed, entry.State);
            Assert.Equal(WatcherChangeTypes.Created, entry.ChangeType);
            Assert.Equal(InstanceId.Substring(0, 20), entry.InstanceId);
        }
                protected override async Task ReleaseMessageAsync(CloudQueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
                {
                    // demonstrates how visibility timeout for failed messages can be customized
                    // the logic here could implement exponential backoff, etc.
                    visibilityTimeout = TimeSpan.FromSeconds(message.DequeueCount);

                    await base.ReleaseMessageAsync(message, result, visibilityTimeout, cancellationToken);
                }
        public async Task ProcessFileAsync_JobFunctionSucceeds()
        {
            string testFile = WriteTestFile("dat");

            FunctionResult result = new FunctionResult(true);
            mockExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>())).ReturnsAsync(result);

            FileSystemEventArgs eventArgs = new FileSystemEventArgs(WatcherChangeTypes.Created, combinedTestFilePath, Path.GetFileName(testFile));
            await processor.ProcessFileAsync(eventArgs, CancellationToken.None);

            Assert.Equal(2, Directory.GetFiles(combinedTestFilePath).Length);
            string expectedStatusFile = processor.GetStatusFile(testFile);
            Assert.True(File.Exists(testFile));
            Assert.True(File.Exists(expectedStatusFile));

            string[] statusLines = File.ReadAllLines(expectedStatusFile);
            Assert.Equal(2, statusLines.Length);
            StatusFileEntry entry = (StatusFileEntry)_serializer.Deserialize(new StringReader(statusLines[0]), typeof(StatusFileEntry));
            Assert.Equal(ProcessingState.Processing, entry.State);
            Assert.Equal(WatcherChangeTypes.Created, entry.ChangeType);
            Assert.Equal(processor.InstanceId, entry.InstanceId);
            entry = (StatusFileEntry)_serializer.Deserialize(new StringReader(statusLines[1]), typeof(StatusFileEntry));
            Assert.Equal(ProcessingState.Processed, entry.State);
            Assert.Equal(WatcherChangeTypes.Created, entry.ChangeType);
            Assert.Equal(processor.InstanceId, entry.InstanceId);
        }
            protected override async Task ReleaseMessageAsync(CloudQueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
            {
                FakeStorageQueueMessage fakeMessage = new FakeStorageQueueMessage(message);

                await _queue.UpdateMessageAsync(fakeMessage, visibilityTimeout, MessageUpdateFields.Visibility, cancellationToken);
            }
예제 #20
0
 /// <summary>
 /// Release the specified failed message back to the queue.
 /// </summary>
 /// <param name="message">The message to release</param>
 /// <param name="result">The <see cref="FunctionResult"/> from the job invocation.</param>
 /// <param name="visibilityTimeout">The visibility timeout to set for the message</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use</param>
 /// <returns></returns>
 protected virtual async Task ReleaseMessageAsync(CloudQueueMessage message, FunctionResult result, TimeSpan visibilityTimeout, CancellationToken cancellationToken)
 {
     try
     {
         // We couldn't process the message. Let someone else try.
         await _queue.UpdateMessageAsync(message, visibilityTimeout, MessageUpdateFields.Visibility, cancellationToken);
     }
     catch (StorageException exception)
     {
         if (exception.IsBadRequestPopReceiptMismatch())
         {
             // Someone else already took over the message; no need to do anything.
             return;
         }
         else if (exception.IsNotFoundMessageOrQueueNotFound() ||
                  exception.IsConflictQueueBeingDeletedOrDisabled())
         {
             // The message or queue is gone, or the queue is down; no need to release the message.
             return;
         }
         else
         {
             throw;
         }
     }
 }
        public void ExecuteAsync_IfInnerExecutorFails_ReturnsFailureResult()
        {
            // Arrange
            string functionId = "FunctionId";
            string matchingETag = "ETag";
            IBlobETagReader eTagReader = CreateStubETagReader(matchingETag);
            IBlobCausalityReader causalityReader = CreateStubCausalityReader();

            FunctionResult expectedResult = new FunctionResult(false);
            Mock<ITriggeredFunctionExecutor> mock = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
            mock.Setup(e => e.TryExecuteAsync(
                It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(expectedResult)
                .Verifiable();

            BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, causalityReader);

            ITriggeredFunctionExecutor innerExecutor = mock.Object;
            BlobQueueRegistration registration = new BlobQueueRegistration
            {
                BlobClient = CreateClient(),
                Executor = innerExecutor
            };
            product.Register(functionId, registration);

            IStorageQueueMessage message = CreateMessage(functionId, matchingETag);

            // Act
            Task<FunctionResult> task = product.ExecuteAsync(message, CancellationToken.None);

            // Assert
            Assert.False(task.Result.Succeeded);
        }
예제 #22
0
 /// <summary>
 /// This method completes processing of the specified message, after the job function has been invoked.
 /// </summary>
 /// <remarks>
 /// If the message was processed successfully, the message should be deleted. If message processing failed, the
 /// message should be release back to the queue, or if the maximum dequeue count has been exceeded, the message
 /// should be moved to the poison queue (if poison queue handling is configured for the queue).
 /// </remarks>
 /// <param name="message">The message to complete processing for.</param>
 /// <param name="result">The <see cref="FunctionResult"/> from the job invocation.</param>
 /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use</param>
 /// <returns></returns>
 public virtual async Task CompleteProcessingMessageAsync(CloudQueueMessage message, FunctionResult result, CancellationToken cancellationToken)
 {
     if (result.Succeeded)
     {
         await DeleteMessageAsync(message, cancellationToken);
     }
     else if (_poisonQueue != null)
     {
         if (message.DequeueCount >= _maxDequeueCount)
         {
             await CopyMessageToPoisonQueueAsync(message, cancellationToken);
             await DeleteMessageAsync(message, cancellationToken);
         }
         else
         {
             await ReleaseMessageAsync(message, result, TimeSpan.Zero, cancellationToken);
         }
     }
     else
     {
         // For queues without a corresponding poison queue, leave the message invisible when processing
         // fails to prevent a fast infinite loop.
         // Specifically, don't call ReleaseMessage(message)
     }
 }
        public async Task ExecuteAsync_IfBlobIsUnchanged_CallsInnerExecutor()
        {
            // Arrange
            string functionId = "FunctionId";
            string matchingETag = "ETag";
            Guid expectedParentId = Guid.NewGuid();
            IStorageQueueMessage message = CreateMessage(functionId, matchingETag);
            IBlobETagReader eTagReader = CreateStubETagReader(matchingETag);
            IBlobCausalityReader causalityReader = CreateStubCausalityReader(expectedParentId);

            FunctionResult expectedResult = new FunctionResult(true);
            Mock<ITriggeredFunctionExecutor> mock = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
            mock.Setup(e => e.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()))
                .Callback<TriggeredFunctionData, CancellationToken>(
                (mockInput, mockCancellationToken) =>
                {
                    Assert.Equal(expectedParentId, mockInput.ParentId);

                    StorageBlockBlob resultBlob = (StorageBlockBlob)mockInput.TriggerValue;
                    Assert.Equal(TestBlobName, resultBlob.Name);
                })
                .ReturnsAsync(expectedResult)
                .Verifiable();

            ITriggeredFunctionExecutor innerExecutor = mock.Object;
            BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, causalityReader);

            BlobQueueRegistration registration = new BlobQueueRegistration
            {
                BlobClient = CreateClient(),
                Executor = innerExecutor
            };
            product.Register(functionId, registration);

            // Act
            FunctionResult result = await product.ExecuteAsync(message, CancellationToken.None);

            // Assert
            Assert.Same(expectedResult, result);
            mock.Verify();
        }
        public async Task ProcessMessageAsync_FunctionInvocationFails()
        {
            CancellationToken cancellationToken = new CancellationToken();
            FunctionResult result = new FunctionResult(false);
            _mockQueueProcessor.Setup(p => p.BeginProcessingMessageAsync(_storageMessage.SdkObject, cancellationToken)).ReturnsAsync(true);
            _mockTriggerExecutor.Setup(p => p.ExecuteAsync(_storageMessage, cancellationToken)).ReturnsAsync(result);
            _mockQueueProcessor.Setup(p => p.CompleteProcessingMessageAsync(_storageMessage.SdkObject, result, cancellationToken)).Returns(Task.FromResult(true));

            await _listener.ProcessMessageAsync(_storageMessage, TimeSpan.FromMinutes(10), cancellationToken);
        }
 public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
 {
     // perform any post processing after the job function has been invoked
     return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
 }
        /// <summary>
        /// Gets the next retry interval to use.
        /// </summary>
        /// <param name="result">The <see cref="FunctionResult"/> for the last failure.</param>
        /// <param name="count">The current execution count (the number of times the function has
        /// been invoked).</param>
        /// <returns>A <see cref="TimeSpan"/> representing the delay interval that should be used.</returns>
        protected virtual TimeSpan GetRetryInterval(FunctionResult result, int count)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            return TimeSpan.FromSeconds(3);
        }
 private void CreateTestListener(string expression, bool useMonitor = true)
 {
     _attribute = new TimerTriggerAttribute(expression);
     _attribute.UseMonitor = useMonitor;
     _config = new TimersConfiguration();
     _mockScheduleMonitor = new Mock<ScheduleMonitor>(MockBehavior.Strict);
     _config.ScheduleMonitor = _mockScheduleMonitor.Object;
     _mockTriggerExecutor = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
     FunctionResult result = new FunctionResult(true);
     _mockTriggerExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()))
         .Callback<TriggeredFunctionData, CancellationToken>((mockFunctionData, mockToken) =>
         {
             _triggeredFunctionData = mockFunctionData;
         })
         .Returns(Task.FromResult(result));
     _listener = new TimerListener(_attribute, _testTimerName, _config, _mockTriggerExecutor.Object, new TestTraceWriter());
 }
        public async Task ConcurrentListeners_ProcessFilesCorrectly(int concurrentListenerCount, int inputFileCount)
        {
            // mock out the executor so we can capture function invocations
            Mock<ITriggeredFunctionExecutor> mockExecutor = new Mock<ITriggeredFunctionExecutor>(MockBehavior.Strict);
            ConcurrentBag<string> processedFiles = new ConcurrentBag<string>();
            FunctionResult result = new FunctionResult(true);
            mockExecutor.Setup(p => p.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()))
                .Callback<TriggeredFunctionData, CancellationToken>(async (mockData, mockToken) =>
                    {
                        await Task.Delay(50);
                        FileSystemEventArgs fileEvent = mockData.TriggerValue as FileSystemEventArgs;
                        processedFiles.Add(fileEvent.Name);
                    })
                .ReturnsAsync(result);

            FilesConfiguration config = new FilesConfiguration()
            {
                RootPath = rootPath
            };
            FileTriggerAttribute attribute = new FileTriggerAttribute(attributeSubPath, changeTypes: WatcherChangeTypes.Created | WatcherChangeTypes.Changed, filter: "*.dat");

            // create a bunch of listeners and start them
            CancellationTokenSource tokenSource = new CancellationTokenSource();
            CancellationToken cancellationToken = tokenSource.Token;
            List<Task> listenerStartupTasks = new List<Task>();
            List<FileListener> listeners = new List<FileListener>();
            for (int i = 0; i < concurrentListenerCount; i++)
            {
                FileListener listener = new FileListener(config, attribute, mockExecutor.Object, new TestTraceWriter());
                listeners.Add(listener);
                listenerStartupTasks.Add(listener.StartAsync(cancellationToken));
            };
            await Task.WhenAll(listenerStartupTasks);

            // now start creating files
            List<string> expectedFiles = new List<string>();
            for (int i = 0; i < inputFileCount; i++)
            {
                string file = WriteTestFile();
                await Task.Delay(50);
                expectedFiles.Add(Path.GetFileName(file));
            }

            // wait for all files to be processed
            await TestHelpers.Await(() =>
            {
                return processedFiles.Count >= inputFileCount;
            });
            Assert.Equal(inputFileCount, processedFiles.Count);

            // verify that each file was only processed once
            Assert.True(expectedFiles.OrderBy(p => p).SequenceEqual(processedFiles.OrderBy(p => p)));
            Assert.Equal(expectedFiles.Count * 2, Directory.GetFiles(testFileDir).Length);

            // verify contents of each status file
            FileProcessor processor = listeners[0].Processor;
            foreach (string processedFile in processedFiles)
            {
                string statusFilePath = processor.GetStatusFile(Path.Combine(testFileDir, processedFile));

                string[] statusLines = File.ReadAllLines(statusFilePath);

                Assert.Equal(2, statusLines.Length);
                StatusFileEntry statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[0]);
                Assert.Equal(ProcessingState.Processing, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Created, statusEntry.ChangeType);

                statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[1]);
                Assert.Equal(ProcessingState.Processed, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Created, statusEntry.ChangeType);
            }

            // Now test concurrency handling for updates by updating some files
            // and verifying the updates are only processed once
            string[] filesToUpdate = processedFiles.Take(50).Select(p => Path.Combine(testFileDir, p)).ToArray();
            string item;
            while (!processedFiles.IsEmpty)
            {
                processedFiles.TryTake(out item);
            }
            await Task.Delay(1000);
            foreach (string fileToUpdate in filesToUpdate)
            {
                await Task.Delay(50);
                File.AppendAllText(fileToUpdate, "update");
            }

            // wait for all files to be processed
            await TestHelpers.Await(() =>
            {
                return processedFiles.Count >= filesToUpdate.Length;
            });
            Assert.Equal(filesToUpdate.Length, processedFiles.Count);
            Assert.Equal(expectedFiles.Count * 2, Directory.GetFiles(testFileDir).Length);

            // verify the status files are correct for each of the updated files
            foreach (string updatedFile in filesToUpdate)
            {
                string statusFilePath = processor.GetStatusFile(updatedFile);

                string[] statusLines = File.ReadAllLines(statusFilePath);

                Assert.Equal(4, statusLines.Length);
                StatusFileEntry statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[0]);
                Assert.Equal(ProcessingState.Processing, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Created, statusEntry.ChangeType);

                statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[1]);
                Assert.Equal(ProcessingState.Processed, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Created, statusEntry.ChangeType);

                statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[2]);
                Assert.Equal(ProcessingState.Processing, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Changed, statusEntry.ChangeType);

                statusEntry = JsonConvert.DeserializeObject<StatusFileEntry>(statusLines[3]);
                Assert.Equal(ProcessingState.Processed, statusEntry.State);
                Assert.Equal(WatcherChangeTypes.Changed, statusEntry.ChangeType);
            }

            // Now call purge to clean up all processed files
            processor.CleanupProcessedFiles();
            Assert.Equal(0, Directory.GetFiles(testFileDir).Length);

            foreach (FileListener listener in listeners)
            {
                listener.Dispose();
            }
        }
        public void ExecuteAsync_IfInnerExecutorSucceeds_ReturnsSuccessResult()
        {
            // Arrange
            string functionId = "FunctionId";
            string matchingETag = "ETag";
            IBlobETagReader eTagReader = CreateStubETagReader(matchingETag);
            IBlobCausalityReader causalityReader = CreateStubCausalityReader();

            FunctionResult expectedResult = new FunctionResult(true);
            Mock<ITriggeredFunctionExecutor<IStorageBlob>> mock = new Mock<ITriggeredFunctionExecutor<IStorageBlob>>(MockBehavior.Strict);
            mock.Setup(e => e.TryExecuteAsync(
                It.IsAny<TriggeredFunctionData<IStorageBlob>>(), It.IsAny<CancellationToken>()))
                .ReturnsAsync(expectedResult)
                .Verifiable();

            BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, causalityReader);

            ITriggeredFunctionExecutor<IStorageBlob> innerExecutor = mock.Object;
            product.Register(functionId, innerExecutor);

            IStorageQueueMessage message = CreateMessage(functionId, matchingETag);

            // Act
            Task<FunctionResult> task = product.ExecuteAsync(message, CancellationToken.None);

            // Assert
            Assert.Same(expectedResult, task.Result);
        }