public async Task TimerExpiration() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Approval) }; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(TimerExpiration))) { await host.StartAsync(); var timeout = TimeSpan.FromSeconds(10); var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], timeout, this.output); // Need to wait for the instance to start before sending events to it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-functions-durable-extension/issues/101 await client.WaitForStartupAsync(TimeSpan.FromSeconds(10), this.output); // Don't send any notification - let the internal timeout expire var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(20), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("Expired", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "TimerExpiration", orchestratorFunctionNames); } }
public async Task TerminateOrchestration() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Counter) }; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(TerminateOrchestration))) { await host.StartAsync(); // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], 0, this.output); // Need to wait for the instance to start before we can terminate it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-functions-durable-extension/issues/101 await client.WaitForStartupAsync(TimeSpan.FromSeconds(10), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "TerminateOrchestration", orchestratorFunctionNames); } }
public async Task OrchestrationConcurrency() { using (JobHost host = GetJobHost()) { await host.StartAsync(); Func <Task> orchestrationStarter = async delegate() { var timeout = TimeSpan.FromSeconds(10); var client = await host.StartFunctionAsync(nameof(Orchestrations.Approval), timeout, this.output); await client.WaitForCompletionAsync(TimeSpan.FromSeconds(20), this.output); // Don't send any notification - let the internal timeout expire }; int iterations = 100; var tasks = new Task[iterations]; for (int i = 0; i < iterations; i++) { tasks[i] = orchestrationStarter(); } // The 100 orchestrations above (which each delay for 10 seconds) should all complete in less than 30 seconds. Task parallelOrchestrations = Task.WhenAll(tasks); Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(30)); Task winner = await Task.WhenAny(parallelOrchestrations, timeoutTask); Assert.Equal(parallelOrchestrations, winner); await host.StopAsync(); } }
public async Task AggregatorOnly() { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { // enable the aggregator _hostConfig.Aggregator.IsEnabled = true; _hostConfig.Aggregator.BatchSize = 1; JobHost host = new JobHost(_hostConfig); await host.StartAsync(); await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue")); await WaitForFunctionCompleteAsync(); // ensure all logs have had a chance to flush await Task.Delay(3000); await host.StopAsync(); // Make sure the aggregator was logged to var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).Single(); var count = logger.GetLogMessages().Count; Assert.True(count == 4, $"Expected 4. Actual {count}.{Environment.NewLine}{_loggerProvider.GetLogString()}"); } }
public async void JobIsTriggeredForNewFiles() { JobHost host = CreateTestJobHost(); await host.StartAsync(); int count = ApiHubFileTestJobs.Processed.Count; // now write a file to trigger the job var fileItem = await _fixture.WriteTestFile(); await TestHelpers.Await(() => { return(_fixture.RootFolder.FileExistsAsync(fileItem.Path).GetAwaiter().GetResult()); }); await TestHelpers.Await(() => { return(ApiHubFileTestJobs.Processed.Count > count); }); Assert.True(ApiHubFileTestJobs.Processed.Contains(Path.GetFileName(fileItem.Path))); await host.StopAsync(); await fileItem.DeleteAsync(); }
public async Task SequentialOrchestration() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(SequentialOrchestration))) { await host.StartAsync(); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.Factorial), 10, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(10, status?.Input); Assert.Equal(3628800, status?.Output); await host.StopAsync(); } // Assert log entry count if (this.useTestLogger) { var logger = loggerProvider.CreatedLoggers.Single(l => l.Category == TestHelpers.LogCategory); var logMessages = logger.LogMessages.ToList(); Assert.Equal(153, logMessages.Count); } }
private async Task AsyncChainEndToEndInternal() { _resolver = new RandomNameResolver(); JobHostConfiguration hostConfiguration = new JobHostConfiguration() { NameResolver = _resolver, TypeLocator = new FakeTypeLocator(typeof(AsyncChainEndToEndTests)) }; _storageAccount = CloudStorageAccount.Parse(hostConfiguration.StorageConnectionString); JobHost host = new JobHost(hostConfiguration); await host.StartAsync(); await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue")); _functionCompletedEvent.WaitOne(); // Stop async waits for the function to complete await host.StopAsync(); await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("ReadResultBlob")); Assert.Equal("async works", _finalBlobContent); }
public async Task UnhandledOrchestrationExceptionWithRetry() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.OrchestratorThrowWithRetry) }; using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(UnhandledOrchestrationExceptionWithRetry))) { await host.StartAsync(); // Null input should result in ArgumentNullException in the orchestration code. var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], null, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(50), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("Value cannot be null")); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.UnhandledOrchesterationExceptionWithRetry_AssertLogMessageSequence(loggerProvider, "UnhandledOrchestrationExceptionWithRetry", orchestratorFunctionNames); } }
public async void JobIsTriggeredForNewFiles() { JobHost host = CreateTestJobHost(); await host.StartAsync(); Assert.Equal(0, FilesTestJobs.Processed.Count); // now write a file to trigger the job string testFilePath = WriteTestFile(); await Task.Delay(2000); Assert.Equal(1, FilesTestJobs.Processed.Count); Assert.Equal(Path.GetFileName(testFilePath), FilesTestJobs.Processed.Single()); Assert.True(File.Exists(testFilePath)); // write a non .dat file - don't expect it to trigger the job string ignoreFilePath = WriteTestFile("txt"); await Task.Delay(2000); Assert.Equal(1, FilesTestJobs.Processed.Count); Assert.True(File.Exists(ignoreFilePath)); await host.StopAsync(); }
private async Task IndexBindings(Type testType, bool includeDefaultUri = true) { // Just start the jobhost -- this should fail if function indexing fails. ExplicitTypeLocator locator = new ExplicitTypeLocator(testType); var nameResolver = new TestNameResolver(); if (includeDefaultUri) { nameResolver.Values.Add(MobileAppsConfiguration.AzureWebJobsMobileAppUriName, "https://default"); } JobHostConfiguration config = new JobHostConfiguration { NameResolver = nameResolver, TypeLocator = locator, }; config.UseMobileApps(); JobHost host = new JobHost(config); await host.StartAsync(); await host.StopAsync(); }
private Tuple <JobHost, IHost> BuildHost <T>() { JobHost jobHost = null; var config = new ConfigurationBuilder() .AddEnvironmentVariables() .AddTestSettings() .Build(); const string connectionName = "AzureWebJobsTestHubConnection"; string connection = config.GetConnectionStringOrSetting(connectionName); Assert.True(!string.IsNullOrEmpty(connection), $"Required test connection string '{connectionName}' is missing."); IHost host = new HostBuilder() .ConfigureDefaultTestHost <T>(b => { b.AddEventHubs(options => { options.EventProcessorOptions.EnableReceiverRuntimeMetric = true; options.AddSender(TestHubName, connection); options.AddReceiver(TestHubName, connection); }); }) .ConfigureLogging(b => { b.SetMinimumLevel(LogLevel.Debug); }) .Build(); jobHost = host.GetJobHost(); jobHost.StartAsync().GetAwaiter().GetResult(); return(new Tuple <JobHost, IHost>(jobHost, host)); }
public async Task AggregatorOnly() { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { // enable the aggregator _hostConfig.Aggregator.IsEnabled = true; _hostConfig.Aggregator.BatchSize = 1; JobHost host = new JobHost(_hostConfig); await host.StartAsync(); await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue")); await TestHelpers.Await(() => _functionCompletedEvent.WaitOne(200), 30000); // ensure all logs have had a chance to flush await Task.Delay(3000); await host.StopAsync(); // Make sure the aggregator was logged to var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).Single(); Assert.Equal(4, logger.LogMessages.Count); } }
public async Task HelloWorldOrchestration_Activity() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.SayHelloWithActivity) }; string activityFunctionName = nameof(TestActivities.Hello); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(HelloWorldOrchestration_Activity))) { await host.StartAsync(); var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], "World", this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal("World", status?.Input); Assert.Equal("Hello, World!", status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "HelloWorldOrchestration_Activity", orchestratorFunctionNames, activityFunctionName); } }
public async Task ExistingFilesAreBatchProcessedOnStartup() { JobHost host = CreateTestJobHost(); // create a bunch of preexisting files List <string> filesToProcess = new List <string>(); int preexistingFileCount = 10; for (int i = 0; i < preexistingFileCount; i++) { string testFilePath = WriteTestFile(); filesToProcess.Add(Path.GetFileName(testFilePath)); } // write a non .dat file - don't expect it to be processed WriteTestFile("txt"); await host.StartAsync(); await TestHelpers.Await(() => { return(FilesTestJobs.Processed.Count == preexistingFileCount); }); Assert.True(FilesTestJobs.Processed.OrderBy(p => p).SequenceEqual(filesToProcess.OrderBy(p => p))); await host.StopAsync(); }
public async Task BigReturnValue_Activity() { string taskHub = nameof(BigReturnValue_Activity); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.CallActivity); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); // The expected maximum payload size is 60 KB. // Strings in Azure Storage are encoded in UTF-32, which is 4 bytes per character. int stringLength = (61 * 1024) / 4; var input = new StartOrchestrationArgs { FunctionName = nameof(TestActivities.BigReturnValue), Input = stringLength }; var client = await host.StartOrchestratorAsync(orchestrator, input, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); // Activity function exception details are not captured in the orchestrator output: // https://github.com/Azure/azure-functions-durable-extension/issues/84 ////Assert.True(status?.Output.ToString().Contains("The UTF-32 size of the JSON-serialized payload must not exceed 60 KB")); Assert.StartsWith($"The activity function '{input.FunctionName}' failed.", (string)status?.Output); await host.StopAsync(); } }
public async Task BigReturnValue_Orchestrator() { string taskHub = nameof(BigReturnValue_Orchestrator); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.BigReturnValue); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); // The expected maximum payload size is 60 KB. // Strings in Azure Storage are encoded in UTF-32, which is 4 bytes per character. int stringLength = (61 * 1024) / 4; var client = await host.StartOrchestratorAsync(orchestrator, stringLength, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); Assert.True(status?.Output.ToString().Contains("The UTF-32 size of the JSON-serialized payload must not exceed 60 KB")); await host.StopAsync(); } }
public async Task UnhandledActivityExceptionWithRetry() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.ActivityThrowWithRetry) }; string activityFunctionName = nameof(TestActivities.Throw); using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(UnhandledActivityExceptionWithRetry))) { await host.StartAsync(); string message = "Kah-BOOOOM!!!"; var client = await host.StartOrchestratorAsync(orchestratorFunctionNames[0], message, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(40), this.output); Assert.Equal(OrchestrationRuntimeStatus.Failed, status?.RuntimeStatus); // There aren't any exception details in the output: https://github.com/Azure/azure-functions-durable-extension/issues/84 Assert.StartsWith($"The activity function '{activityFunctionName}' failed.", (string)status?.Output); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "UnhandledActivityExceptionWithRetry", orchestratorFunctionNames, activityFunctionName); } }
public async Task EventCollectorOnly() { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { // disable the aggregator _hostConfig.Aggregator.IsEnabled = false; // add a FunctionEventCollector var eventCollector = new TestFunctionEventCollector(); _hostConfig.AddService <IAsyncCollector <FunctionInstanceLogEntry> >(eventCollector); JobHost host = new JobHost(_hostConfig); await host.StartAsync(); await host.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue")); await WaitForFunctionCompleteAsync(); // ensure all logs have had a chance to flush await Task.Delay(3000); await host.StopAsync(); // Make sure the aggregator was logged to var logger = _loggerProvider.CreatedLoggers.Where(l => l.Category == LogCategories.Aggregator).SingleOrDefault(); Assert.Null(logger); // Make sure the eventCollector was logged eventCollector.AssertFunctionCount(4, _loggerProvider.GetLogString()); } }
public async Task FunctionTraceLevelOverride_ProducesExpectedOutput() { TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose); _hostConfig.Tracing.Tracers.Add(trace); JobHost host = new JobHost(_hostConfig); try { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { await host.StartAsync(); CloudQueueMessage message = new CloudQueueMessage("test message"); _testQueue.AddMessage(message); _functionCompletedEvent.WaitOne(); // wait for logs to flush await Task.Delay(3000); // expect no function output TraceEvent[] traces = trace.Traces.ToArray(); Assert.Equal(5, traces.Length); Assert.False(traces.Any(p => p.Message.Contains("test message"))); } } finally { host.Stop(); } }
public async Task ActivityTriggerAsPOCO() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.ActivityTriggerAsPOCO))) { await host.StartAsync(); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToPOCO); var input = new { Foo = "Bar" }; startArgs.Input = input; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); // The function echos back the 'Foo' input property value Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); Assert.Equal(input.Foo, status?.Output); await host.StopAsync(); } }
private async Task RunTimeoutTest(IWebJobsExceptionHandler exceptionHandler, Type expectedExceptionType, string functionName) { TestTraceWriter trace = new TestTraceWriter(TraceLevel.Verbose); _hostConfig.Tracing.Tracers.Add(trace); _hostConfig.AddService <IWebJobsExceptionHandler>(exceptionHandler); JobHost host = new JobHost(_hostConfig); try { await host.StartAsync(); MethodInfo methodInfo = GetType().GetMethod(functionName); Exception ex = await Assert.ThrowsAnyAsync <Exception>(async() => { await host.CallAsync(methodInfo); }); Assert.IsType(expectedExceptionType, ex); } finally { host.Stop(); } // We expect 3 error messages total TraceEvent[] traceErrors = trace.Traces.Where(p => p.Level == TraceLevel.Error).ToArray(); Assert.Equal(3, traceErrors.Length); Assert.True(traceErrors[0].Message.StartsWith(string.Format("Timeout value of 00:00:01 exceeded by function 'AsyncChainEndToEndTests.{0}'", functionName))); Assert.True(traceErrors[1].Message.StartsWith(string.Format("Executed: 'AsyncChainEndToEndTests.{0}' (Failed)", functionName))); Assert.True(traceErrors[2].Message.Trim().StartsWith("Function had errors. See Azure WebJobs SDK dashboard for details.")); }
public void StartAsync_WhenStopping_Throws() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { host.Start(); // Replace (and cleanup) the exsiting runner to hook StopAsync. IListener oldListener = host.Listener; oldListener.StopAsync(CancellationToken.None).GetAwaiter().GetResult(); TaskCompletionSource <object> stopTaskSource = new TaskCompletionSource <object>(); Mock <IListener> listenerMock = new Mock <IListener>(MockBehavior.Strict); listenerMock.Setup(r => r.StopAsync(It.IsAny <CancellationToken>())).Returns(stopTaskSource.Task); listenerMock.Setup(r => r.Dispose()); host.Listener = listenerMock.Object; Task stopping = host.StopAsync(); // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => host.StartAsync(), "Start has already been called."); // Cleanup stopTaskSource.SetResult(null); stopping.GetAwaiter().GetResult(); } }
public async Task BindToBlobViaPOCO() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.ActivityTriggerAsNumber))) { await host.StartAsync(); string connectionString = AmbientConnectionStringProvider.Instance.GetConnectionString(ConnectionStringNames.Storage); CloudStorageAccount account = CloudStorageAccount.Parse(connectionString); this.output.WriteLine($"Using storage account: {account.Credentials.AccountName}"); // Blob and container names need to be kept in sync with the activity code. var data = new { InputPrefix = "Input", OutputPrefix = "Output", Suffix = 42, }; const string ContainerName = "test"; string inputBlobName = $"{data.InputPrefix}-{data.Suffix}"; string outputBlobName = $"{data.OutputPrefix}-{data.Suffix}"; CloudBlobClient blobClient = account.CreateCloudBlobClient(); CloudBlobContainer container = blobClient.GetContainerReference(ContainerName); if (await container.CreateIfNotExistsAsync()) { this.output.WriteLine($"Created container '{container.Name}'."); } string randomData = Guid.NewGuid().ToString("N"); this.output.WriteLine($"Creating blob named {outputBlobName}..."); CloudBlockBlob blob = container.GetBlockBlobReference(inputBlobName); await blob.UploadTextAsync(randomData); this.output.WriteLine($"Uploaded text '{randomData}' to {blob.Name}."); // Using StartOrchestrationArgs to start an activity function because it's easier than creating a new type. var startArgs = new StartOrchestrationArgs(); startArgs.FunctionName = nameof(TestActivities.BindToBlobViaJsonPayload); startArgs.Input = data; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Completed, status?.RuntimeStatus); this.output.WriteLine($"Searching for blob named {outputBlobName}..."); CloudBlockBlob newBlob = container.GetBlockBlobReference(outputBlobName); string copiedData = await newBlob.DownloadTextAsync(); this.output.WriteLine($"Downloaded text '{copiedData}' from {newBlob.Name}."); Assert.Equal(randomData, copiedData); await host.StopAsync(); } }
public async Task ApplicationInsights_SuccessfulFunction() { string testName = nameof(TestApplicationInsightsInformation); LogCategoryFilter filter = new LogCategoryFilter(); filter.DefaultLevel = LogLevel.Information; var loggerFactory = new LoggerFactory() .AddApplicationInsights( new TestTelemetryClientFactory(filter.Filter, _channel)); JobHostConfiguration config = new JobHostConfiguration { LoggerFactory = loggerFactory, TypeLocator = new FakeTypeLocator(GetType()), }; config.Aggregator.IsEnabled = false; config.AddService <IWebJobsExceptionHandler>(new TestExceptionHandler()); using (JobHost host = new JobHost(config)) { await host.StartAsync(); var methodInfo = GetType().GetMethod(testName, BindingFlags.Public | BindingFlags.Static); await host.CallAsync(methodInfo, new { input = "function input" }); await host.StopAsync(); } Assert.Equal(7, _channel.Telemetries.Count); // Validate the traces. Order by message string as the requests may come in // slightly out-of-order or on different threads TraceTelemetry[] telemetries = _channel.Telemetries .OfType <TraceTelemetry>() .OrderBy(t => t.Message) .ToArray(); ValidateTrace(telemetries[0], "Found the following functions:\r\n", LogCategories.Startup); ValidateTrace(telemetries[1], "Job host started", LogCategories.Startup); ValidateTrace(telemetries[2], "Job host stopped", LogCategories.Startup); ValidateTrace(telemetries[3], "Logger", LogCategories.Function, testName, hasCustomScope: true); ValidateTrace(telemetries[4], "Trace", LogCategories.Function, testName); // We should have 1 custom metric. MetricTelemetry metric = _channel.Telemetries .OfType <MetricTelemetry>() .Single(); ValidateMetric(metric, testName); // Finally, validate the request RequestTelemetry request = _channel.Telemetries .OfType <RequestTelemetry>() .Single(); ValidateRequest(request, testName, true); }
public async Task Generate_EndToEnd() { // construct our TimerTrigger attribute ([TimerTrigger("00:00:02", RunOnStartup = true)]) Collection <ParameterDescriptor> parameters = new Collection <ParameterDescriptor>(); ParameterDescriptor parameter = new ParameterDescriptor("timerInfo", typeof(TimerInfo)); ConstructorInfo ctorInfo = typeof(TimerTriggerAttribute).GetConstructor(new Type[] { typeof(string) }); PropertyInfo runOnStartupProperty = typeof(TimerTriggerAttribute).GetProperty("RunOnStartup"); CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder( ctorInfo, new object[] { "00:00:02" }, new PropertyInfo[] { runOnStartupProperty }, new object[] { true }); parameter.CustomAttributes.Add(attributeBuilder); parameters.Add(parameter); // create the FunctionDefinition FunctionMetadata metadata = new FunctionMetadata(); TestInvoker invoker = new TestInvoker(); FunctionDescriptor function = new FunctionDescriptor("TimerFunction", invoker, metadata, parameters, null, null, null); Collection <FunctionDescriptor> functions = new Collection <FunctionDescriptor>(); functions.Add(function); // Get the Type Attributes (in this case, a TimeoutAttribute) ScriptHostConfiguration scriptConfig = new ScriptHostConfiguration(); scriptConfig.FunctionTimeout = TimeSpan.FromMinutes(5); Collection <CustomAttributeBuilder> typeAttributes = new Collection <CustomAttributeBuilder>(); // generate the Type Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions); // verify the generated function MethodInfo method = functionType.GetMethod("TimerFunction"); ParameterInfo triggerParameter = method.GetParameters()[0]; TimerTriggerAttribute triggerAttribute = triggerParameter.GetCustomAttribute <TimerTriggerAttribute>(); Assert.NotNull(triggerAttribute); // start the JobHost which will start running the timer function JobHostConfiguration config = new JobHostConfiguration() { TypeLocator = new TypeLocator(functionType), LoggerFactory = new LoggerFactory() }; config.UseTimers(); JobHost host = new JobHost(config); await host.StartAsync(); await Task.Delay(3000); await host.StopAsync(); // verify our custom invoker was called Assert.True(invoker.InvokeCount >= 2); }
public async Task ActorOrchestration() { using (JobHost host = GetJobHost()) { await host.StartAsync(); int initialValue = 0; var client = await host.StartFunctionAsync(nameof(Orchestrations.Counter), initialValue, this.output); // Need to wait for the instance to start before sending events to it. // TODO: This requirement may not be ideal and should be revisited. // BUG: https://github.com/Azure/azure-webjobs-sdk-script-pr/issues/37 await client.WaitForStartupAsync(TimeSpan.FromSeconds(10), this.output); // Perform some operations await client.RaiseEventAsync("operation", "incr"); // TODO: Sleeping to avoid a race condition where multiple ContinueAsNew messages // are processed by the same instance at the same time, resulting in a corrupt // storage failure in DTFx. // BUG: https://github.com/Azure/azure-webjobs-sdk-script-pr/issues/38 await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "decr"); await Task.Delay(2000); await client.RaiseEventAsync("operation", "incr"); await Task.Delay(2000); // Make sure it's still running and didn't complete early (or fail). var status = await client.GetStatusAsync(); Assert.Equal("Running", status?.RuntimeStatus); // The end message will cause the actor to complete itself. await client.RaiseEventAsync("operation", "end"); status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(10), this.output); Assert.Equal("Completed", status?.RuntimeStatus); Assert.Equal(3, (int)status?.Output); // When using ContinueAsNew, the original input is discarded and replaced with the most recent state. Assert.NotEqual(initialValue, status?.Input); await host.StopAsync(); } }
public async Task SubOrchestration_ComplexType() { const string TaskHub = nameof(SubOrchestration_ComplexType); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, TaskHub)) { await host.StartAsync(); var complexTypeDataInput = new ComplexType { A = -42, B = new List <DateTime> { DateTime.UtcNow, DateTime.UtcNow.AddYears(1) }, C = ComplexType.CustomEnum.Value2, D = new ComplexType.ComplexInnerType { E = Guid.NewGuid().ToString(), F = TimeSpan.FromHours(1.5), }, }; var input = new StartOrchestrationArgs { FunctionName = nameof(TestOrchestrations.CallActivity), Input = new StartOrchestrationArgs { FunctionName = nameof(TestActivities.Echo), Input = complexTypeDataInput, }, }; string parentOrchestrator = nameof(TestOrchestrations.CallOrchestrator); var client = await host.StartOrchestratorAsync(parentOrchestrator, input, this.output); var status = await client.WaitForCompletionAsync( Debugger.IsAttached?TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(20), this.output); Assert.NotNull(status); Assert.Equal(OrchestrationRuntimeStatus.Completed, status.RuntimeStatus); Assert.Equal(client.InstanceId, status.InstanceId); Assert.NotNull(status.Output); ComplexType complextTypeDataOutput = status.Output.ToObject <ComplexType>(); Assert.NotNull(complextTypeDataOutput); Assert.Equal(complexTypeDataInput.A, complextTypeDataOutput.A); Assert.Equal(complexTypeDataInput.B[0], complextTypeDataOutput.B[0]); Assert.Equal(complexTypeDataInput.B[1], complextTypeDataOutput.B[1]); Assert.NotNull(complextTypeDataOutput.D); Assert.Equal(complexTypeDataInput.D.E, complextTypeDataOutput.D.E); Assert.Equal(complexTypeDataInput.D.F, complextTypeDataOutput.D.F); await host.StopAsync(); } }
public void StartAsync_WhenNotStarted_DoesNotThrow() { // Arrange using (JobHost host = new JobHost(new OptionsWrapper <JobHostOptions>(new JobHostOptions()), new Mock <IJobHostContextFactory>().Object)) { // Act & Assert host.StartAsync().GetAwaiter().GetResult(); } }
public void StartAsync_WhenNotStarted_DoesNotThrow() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { // Act & Assert host.StartAsync().GetAwaiter().GetResult(); } }
public void StartAsync_WhenNotStarted_DoesNotThrow() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { // Act & Assert host.StartAsync().GetAwaiter().GetResult(); } }
public async Task Generate_EndToEnd() { // construct our TimerTrigger attribute ([TimerTrigger("00:00:02", RunOnStartup = true)]) Collection<ParameterDescriptor> parameters = new Collection<ParameterDescriptor>(); ParameterDescriptor parameter = new ParameterDescriptor("timerInfo", typeof(TimerInfo)); ConstructorInfo ctorInfo = typeof(TimerTriggerAttribute).GetConstructor(new Type[] { typeof(string) }); PropertyInfo runOnStartupProperty = typeof(TimerTriggerAttribute).GetProperty("RunOnStartup"); CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder( ctorInfo, new object[] { "00:00:02" }, new PropertyInfo[] { runOnStartupProperty }, new object[] { true }); parameter.CustomAttributes.Add(attributeBuilder); parameters.Add(parameter); // create the FunctionDefinition FunctionMetadata metadata = new FunctionMetadata(); TestInvoker invoker = new TestInvoker(); FunctionDescriptor function = new FunctionDescriptor("TimerFunction", invoker, metadata, parameters); Collection<FunctionDescriptor> functions = new Collection<FunctionDescriptor>(); functions.Add(function); // Get the Type Attributes (in this case, a TimeoutAttribute) ScriptHostConfiguration scriptConfig = new ScriptHostConfiguration(); scriptConfig.FunctionTimeout = TimeSpan.FromMinutes(5); Collection<CustomAttributeBuilder> typeAttributes = ScriptHost.CreateTypeAttributes(scriptConfig); // generate the Type Type functionType = FunctionGenerator.Generate("TestScriptHost", "TestFunctions", typeAttributes, functions); // verify the generated function MethodInfo method = functionType.GetMethod("TimerFunction"); TimeoutAttribute timeoutAttribute = (TimeoutAttribute)functionType.GetCustomAttributes().Single(); Assert.Equal(TimeSpan.FromMinutes(5), timeoutAttribute.Timeout); Assert.True(timeoutAttribute.ThrowOnTimeout); Assert.True(timeoutAttribute.TimeoutWhileDebugging); ParameterInfo triggerParameter = method.GetParameters()[0]; TimerTriggerAttribute triggerAttribute = triggerParameter.GetCustomAttribute<TimerTriggerAttribute>(); Assert.NotNull(triggerAttribute); // start the JobHost which will start running the timer function JobHostConfiguration config = new JobHostConfiguration() { TypeLocator = new TypeLocator(functionType) }; config.UseTimers(); JobHost host = new JobHost(config); await host.StartAsync(); await Task.Delay(3000); await host.StopAsync(); // verify our custom invoker was called Assert.True(invoker.InvokeCount >= 2); }
// $$$ Reconcile with TestJobHost. internal static async Task <TResult> RunTriggerAsync <TResult>( StorageAccount account, Type programType, Action <TaskCompletionSource <TResult> > setTaskSource, IEnumerable <string> ignoreFailureFunctions = null, bool signalOnFirst = false) { TaskCompletionSource <TResult> src = new TaskCompletionSource <TResult>(); setTaskSource(src); var host = new HostBuilder() .ConfigureDefaultTestHost(builder => { builder.AddAzureStorageBlobs().AddAzureStorageQueues() .UseStorage(account) .ConfigureCatchFailures(src, signalOnFirst, ignoreFailureFunctions); builder.Services.AddSingleton <IConfigureOptions <QueuesOptions>, FakeQueuesOptionsSetup>(); }, programType) .Build(); try { using (JobHost jobHost = host.GetJobHost()) { try { // start listeners. One of them will set the completition task await jobHost.StartAsync(); var result = await src.Task.AwaitWithTimeout(); // blocks return(result); } finally { await jobHost.StopAsync(); } } } catch (Exception exception) { // Unwrap var e = exception; while (e != null) { if (e is InvalidOperationException) { throw e; } e = e.InnerException; } throw; } }
public void StartAsync_WhenStarted_Throws() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { host.Start(); // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => host.StartAsync(), "Start has already been called."); } }
public async Task MultipleAccountTest() { try { TestTraceWriter trace = new TestTraceWriter(TraceLevel.Info); _serviceBusConfig = new ServiceBusConfiguration(); _serviceBusConfig.MessagingProvider = new CustomMessagingProvider(_serviceBusConfig, trace); JobHostConfiguration config = new JobHostConfiguration() { NameResolver = _nameResolver, TypeLocator = new FakeTypeLocator(typeof(ServiceBusTestJobs)) }; config.Tracing.Tracers.Add(trace); config.UseServiceBus(_serviceBusConfig); JobHost host = new JobHost(config); string queueName = ResolveName(StartQueueName); string queuePrefix = queueName.Replace("-queue-start", ""); string firstTopicName = string.Format("{0}-topic/Subscriptions/{0}-queue-topic-1", queuePrefix); WriteQueueMessage(_secondaryNamespaceManager, _secondaryConnectionString, queueName, "Test"); _topicSubscriptionCalled1 = new ManualResetEvent(initialState: false); await host.StartAsync(); _topicSubscriptionCalled1.WaitOne(SBTimeout); // ensure all logs have had a chance to flush await Task.Delay(3000); // Wait for the host to terminate await host.StopAsync(); host.Dispose(); Assert.Equal("Test-topic-1", _resultMessage1); } finally { Cleanup(); } }
public void StartAsync_WhenStarting_Throws() { // Arrange TaskCompletionSource<IStorageAccount> getAccountTaskSource = new TaskCompletionSource<IStorageAccount>(); TestJobHostConfiguration configuration = CreateConfiguration(new LambdaStorageAccountProvider( (i1, i2) => getAccountTaskSource.Task)); using (JobHost host = new JobHost(configuration)) { Task starting = host.StartAsync(); Assert.False(starting.IsCompleted); // Guard // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => host.StartAsync(), "Start has already been called."); // Cleanup getAccountTaskSource.SetResult(null); starting.GetAwaiter().GetResult(); } }
public void StartAsync_WhenStopping_Throws() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { host.Start(); // Replace (and cleanup) the exsiting runner to hook StopAsync. IListener oldListener = host.Listener; oldListener.StopAsync(CancellationToken.None).GetAwaiter().GetResult(); TaskCompletionSource<object> stopTaskSource = new TaskCompletionSource<object>(); Mock<IListener> listenerMock = new Mock<IListener>(MockBehavior.Strict); listenerMock.Setup(r => r.StopAsync(It.IsAny<CancellationToken>())).Returns(stopTaskSource.Task); listenerMock.Setup(r => r.Dispose()); host.Listener = listenerMock.Object; Task stopping = host.StopAsync(); // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => host.StartAsync(), "Start has already been called."); // Cleanup stopTaskSource.SetResult(null); stopping.GetAwaiter().GetResult(); } }
public void CallCancellationToken_WhenUsingTriggeredFunction_DoesNotTriggerCancellationToken() { using (CancellationTokenSource tokenSource = new CancellationTokenSource()) using (JobHost host = new JobHost(_hostConfiguration)) { _invokeInFunction = () => { tokenSource.Cancel(); }; PrepareHostForTrigger(host, startHost: false); Assert.True(host.StartAsync(tokenSource.Token).WaitUntilCompleted(DefaultTimeout)); EvaluateTriggeredCancellation(expectedCancellation: false); } }