public static void Run(string connectionString) { CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString); CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient(); CreateTestQueues(queueClient); try { CloudQueue firstQueue = queueClient.GetQueueReference(_nameResolver.ResolveInString(FunctionChainingPerfTest.FirstQueueName)); firstQueue.AddMessage(new CloudQueueMessage("Test")); _startBlock = MeasurementBlock.BeginNew(0, HostStartMetric); JobHostConfiguration hostConfig = new JobHostConfiguration(connectionString); hostConfig.NameResolver = _nameResolver; hostConfig.TypeLocator = new FakeTypeLocator(typeof(FunctionChainingPerfTest)); JobHost host = new JobHost(hostConfig); _tokenSource = new CancellationTokenSource(); Task stopTask = null; _tokenSource.Token.Register(() => stopTask = host.StopAsync()); host.RunAndBlock(); stopTask.GetAwaiter().GetResult(); } finally { DeleteTestQueues(queueClient); } }
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); }
public async Task Orchestration_OnUnregisteredActivity() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.CallActivity) }; const string activityFunctionName = "UnregisteredActivity"; string errorMessage = $"The function '{activityFunctionName}' doesn't exist, is disabled, or is not an activity function"; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(Orchestration_OnUnregisteredActivity))) { await host.StartAsync(); var startArgs = new StartOrchestrationArgs { FunctionName = activityFunctionName, Input = new { Foo = "Bar" } }; var client = await host.StartFunctionAsync(orchestratorFunctionNames[0], startArgs, this.output); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal("Failed", status?.RuntimeStatus); // There aren't any exception details in the output: https://github.com/Azure/azure-webjobs-sdk-script-pr/issues/36 Assert.True(status?.Output.ToString().Contains(errorMessage)); await host.StopAsync(); } if (this.useTestLogger) { TestHelpers.AssertLogMessageSequence(loggerProvider, "Orchestration_OnUnregisteredActivity", orchestratorFunctionNames); } }
public async Task SingletonListener_MultipleHosts_OnlyOneHostRunsListener() { // create and start multiple hosts concurrently int numHosts = 3; List <JobHost> hosts = new List <JobHost>(); Task[] tasks = new Task[numHosts]; for (int i = 0; i < numHosts; i++) { JobHost host = CreateTestJobHost(i); hosts.Add(host); tasks[i] = host.StartAsync(); } await Task.WhenAll(tasks); // verify that only 2 listeners were started (one for each of the singleton functions) Assert.Equal(3, TestTriggerAttributeBindingProvider.TestTriggerBinding.TestTriggerListener.StartCount); MethodInfo singletonListenerMethod = typeof(TestJobs).GetMethod("TriggerJob_SingletonListener"); VerifyLeaseState(singletonListenerMethod, "Listener", LeaseState.Leased, LeaseStatus.Locked); MethodInfo singletonListenerAndFunctionMethod = typeof(TestJobs).GetMethod("SingletonTriggerJob_SingletonListener"); VerifyLeaseState(singletonListenerAndFunctionMethod, "Listener", LeaseState.Leased, LeaseStatus.Locked); // stop all the hosts foreach (JobHost host in hosts) { await host.StopAsync(); host.Dispose(); } VerifyLeaseState(singletonListenerMethod, "Listener", LeaseState.Available, LeaseStatus.Unlocked); VerifyLeaseState(singletonListenerAndFunctionMethod, "Listener", LeaseState.Available, LeaseStatus.Unlocked); }
public async Task ServiceBusEndToEnd_CreatesEntities() { JobHost host = null; var startName = ResolveName(StartQueueName); var topicName = ResolveName(TopicName); var queueName = ResolveName(QueueNamePrefix); try { host = CreateHost(typeof(ServiceBusTestJobs_EntityCreation)); await host.StartAsync(); CreateStartMessage(_serviceBusConfig.ConnectionString, startName); CreateStartMessage(_serviceBusConfig.ConnectionString, startName + '1'); CreateStartMessage(_serviceBusConfig.ConnectionString, startName + '2'); await TestHelpers.Await(() => { return(_namespaceManager.TopicExists(topicName) && _namespaceManager.QueueExists(queueName + '1') && _namespaceManager.QueueExists(queueName + '2')); }, 30000); Assert.Throws <MessagingException>(() => _namespaceManager.QueueExists(topicName)); Assert.Throws <MessagingException>(() => _namespaceManager.TopicExists(queueName + '1')); Assert.Throws <MessagingException>(() => _namespaceManager.TopicExists(queueName + '2')); } finally { host?.StopAsync(); host?.Dispose(); Cleanup(); CleanupQueue(startName + '1'); CleanupQueue(startName + '2'); CleanupQueue(queueName + '2'); } }
public async Task EventCollectorOnly() { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { // aggregator is disabled by default in these tests // add a FunctionEventCollector var eventCollector = new TestFunctionEventCollector(); _hostBuilder.ConfigureServices(services => { services.AddSingleton <IAsyncCollector <FunctionInstanceLogEntry> >(eventCollector); }); IHost host = _hostBuilder.Build(); JobHost jobHost = host.GetJobHost(); await jobHost.StartAsync(); await jobHost.CallAsync(typeof(AsyncChainEndToEndTests).GetMethod("WriteStartDataMessageToQueue")); var loggerProvider = host.GetTestLoggerProvider(); await WaitForFunctionCompleteAsync(loggerProvider); // ensure all logs have had a chance to flush await Task.Delay(3000); await jobHost.StopAsync(); // Make sure the aggregator was logged to var logger = host.GetTestLoggerProvider().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 TimerExpiration() { using (JobHost host = GetJobHost()) { await host.StartAsync(); var timeout = TimeSpan.FromSeconds(10); var client = await host.StartFunctionAsync(nameof(Orchestrations.Approval), 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-webjobs-sdk-script-pr/issues/37 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("Completed", status?.RuntimeStatus); Assert.Equal("Expired", status?.Output); await host.StopAsync(); } }
public async Task TerminateOrchestration() { using (JobHost host = GetJobHost()) { await host.StartAsync(); // Using the counter orchestration because it will wait indefinitely for input. var client = await host.StartFunctionAsync(nameof(Orchestrations.Counter), 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-webjobs-sdk-script-pr/issues/37 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("Terminated", status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); await host.StopAsync(); } }
public async Task RaiseEventToSubOrchestration() { string taskHub = nameof(RaiseEventToSubOrchestration); using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, taskHub)) { await host.StartAsync(); var orchestrator = nameof(TestOrchestrations.CallOrchestrator); var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var input = new StartOrchestrationArgs { FunctionName = nameof(TestOrchestrations.Approval), InstanceId = "SubOrchestration-" + Guid.NewGuid().ToString("N"), Input = TimeSpan.FromMinutes(5) }; var client = await host.StartOrchestratorAsync(orchestrator, input, this.output); var status = await client.WaitForStartupAsync(timeout, this.output); Assert.Equal(OrchestrationRuntimeStatus.Running, status?.RuntimeStatus); // Wait long enough for the sub-orchestration to be started and waiting for input. await Task.Delay(TimeSpan.FromSeconds(2)); await client.InnerClient.RaiseEventAsync(input.InstanceId, "approval", true); status = await client.WaitForCompletionAsync(timeout, this.output); Assert.Equal("Approved", status?.Output); await host.StopAsync(); } }
public async Task ActivityTriggerAsPOCO() { using (JobHost host = TestHelpers.GetJobHost(this.loggerProvider, nameof(this.ActivityTriggerAsPOCO), false)) { 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 client = await host.StartOrchestratorAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(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 AsyncChainEndToEndInternal() { JobHost host = new JobHost(_hostConfig); Assert.Null(_hostConfig.HostId); await host.StartAsync(); Assert.NotEmpty(_hostConfig.HostId); 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); // 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 AggregatorAndEventCollector() { using (_functionCompletedEvent = new ManualResetEvent(initialState: false)) { _hostConfig.Tracing.ConsoleLevel = TraceLevel.Off; // enable the aggregator _hostConfig.Aggregator.IsEnabled = true; _hostConfig.Aggregator.BatchSize = 1; // 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")); _functionCompletedEvent.WaitOne(); // 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); // Make sure the eventCollector was logged to // The aggregator ignores 'start' evetns, so this will be double Assert.Equal(8, eventCollector.LogCount); } }
public async Task ActivityTriggerAsJObject() { using (JobHost host = TestHelpers.GetJobHost(loggerFactory, nameof(ActivityTriggerAsJObject))) { 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.BindToJObject); startArgs.Input = new { Foo = "Bar" }; var timeout = Debugger.IsAttached ? TimeSpan.FromMinutes(5) : TimeSpan.FromSeconds(30); var client = await host.StartFunctionAsync(nameof(TestOrchestrations.CallActivity), startArgs, this.output); var status = await client.WaitForCompletionAsync(timeout, this.output); // The function checks to see if there is a property called "Foo" which is set to a value // called "Bar" and returns true if this is the case. Otherwise returns false. Assert.Equal("Completed", status?.RuntimeStatus); Assert.Equal(true, status?.Output); await host.StopAsync(); } }
public async Task QuickPulse_Works_EvenIfFiltered(LogLevel defaultLevel, int expectedTelemetryItems) { LogCategoryFilter filter = new LogCategoryFilter(); filter.DefaultLevel = defaultLevel; var loggerFactory = new LoggerFactory() .AddApplicationInsights( new TestTelemetryClientFactory(filter.Filter, _channel)); JobHostConfiguration config = new JobHostConfiguration { LoggerFactory = loggerFactory, TypeLocator = new FakeTypeLocator(GetType()), }; config.Aggregator.IsEnabled = false; var listener = new ApplicationInsightsTestListener(); int requests = 5; try { listener.StartListening(); using (JobHost host = new JobHost(config)) { await host.StartAsync(); var methodInfo = GetType().GetMethod(nameof(TestApplicationInsightsWarning), BindingFlags.Public | BindingFlags.Static); for (int i = 0; i < requests; i++) { await host.CallAsync(methodInfo); } await host.StopAsync(); } } finally { listener.StopListening(); } // wait for everything to flush await Task.Delay(2000); // Sum up all req/sec calls that we've received. var reqPerSec = listener .QuickPulseItems.Select(p => p.Metrics.Where(q => q.Name == @"\ApplicationInsights\Requests/Sec").Single()); double sum = reqPerSec.Sum(p => p.Value); // All requests will go to QuickPulse. // The calculated RPS may off, so give some wiggle room. The important thing is that it's generating // RequestTelemetry and not being filtered. double max = requests + 3; double min = requests - 2; Assert.True(sum > min && sum < max, $"Expected sum to be greater than {min} and less than {max}. DefaultLevel: {defaultLevel}. Actual: {sum}"); // These will be filtered based on the default filter. var infos = _channel.Telemetries.OfType <TraceTelemetry>().Where(t => t.SeverityLevel == SeverityLevel.Information); var warns = _channel.Telemetries.OfType <TraceTelemetry>().Where(t => t.SeverityLevel == SeverityLevel.Warning); var errs = _channel.Telemetries.OfType <TraceTelemetry>().Where(t => t.SeverityLevel == SeverityLevel.Error); Assert.Equal(expectedTelemetryItems, _channel.Telemetries.Count()); }
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); }
public void StopAsync_WhenAlreadyStopping_ReturnsSameTask() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { host.Start(); // Replace (and cleanup) the existing listener to hook StopAsync. IListener oldRunner = host.Listener; oldRunner.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 alreadyStopping = host.StopAsync(); // Act Task stoppingAgain = host.StopAsync(); // Assert Assert.Same(alreadyStopping, stoppingAgain); // Cleanup stopTaskSource.SetResult(null); alreadyStopping.GetAwaiter().GetResult(); stoppingAgain.GetAwaiter().GetResult(); } }
public void StopAsync_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.StopAsync(), "The host has not yet started."); // Cleanup getAccountTaskSource.SetResult(null); starting.GetAwaiter().GetResult(); } }
public void StopAsync_WhenNotStarted_Throws() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { // Act & Assert ExceptionAssert.ThrowsInvalidOperation(() => host.StopAsync(), "The host has not yet started."); } }
public void StopAsync_WhenStopped_DoesNotThrow() { // Arrange using (JobHost host = new JobHost(CreateConfiguration())) { host.Start(); host.Stop(); // Act & Assert host.StopAsync().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 async Task OrchestrationStartAndCompleted(bool extendedSessionsEnabled) { var functionName = nameof(TestOrchestrations.SayHelloInline); var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; using (JobHost host = TestHelpers.GetJobHost( this.loggerFactory, nameof(this.OrchestrationStartAndCompleted), extendedSessionsEnabled, eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); string createdInstanceId = Guid.NewGuid().ToString("N"); Func <HttpRequestMessage, HttpResponseMessage> responseGenerator = (HttpRequestMessage req) => req.CreateResponse(HttpStatusCode.OK, "{\"message\":\"OK!\"}"); int callCount = 0; List <Action> eventGridRequestValidators = this.ConfigureEventGridMockHandler( host, functionName, createdInstanceId, eventGridKeyValue, eventGridEndpoint, responseGenerator, handler: (JObject eventPayload) => { dynamic o = eventPayload; if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Running", (string)o.data.runtimeStatus); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Completed", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Completed", (string)o.data.runtimeStatus); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } callCount++; }); var client = await host.StartOrchestratorAsync( functionName, "World", this.output, createdInstanceId); 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); // There should be one validator for each Event Grid request. // Each validator is a delegate with several Assert statements. Assert.NotEmpty(eventGridRequestValidators); foreach (Action validator in eventGridRequestValidators) { validator.Invoke(); } Assert.Equal(2, callCount); await host.StopAsync(); } }
public async Task ApplicationInsights_FailedFunction() { string testName = nameof(TestApplicationInsightsFailure); 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 Assert.ThrowsAsync <FunctionInvocationException>(() => host.CallAsync(methodInfo, new { input = "function input" })); await host.StopAsync(); } Assert.Equal(8, _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); // Validate the exception ExceptionTelemetry[] exceptions = _channel.Telemetries .OfType <ExceptionTelemetry>() .OrderBy(t => t.Timestamp) .ToArray(); Assert.Equal(2, exceptions.Length); ValidateException(exceptions[0], LogCategories.Function, testName); ValidateException(exceptions[1], LogCategories.Results, testName); // Finally, validate the request RequestTelemetry request = _channel.Telemetries .OfType <RequestTelemetry>() .Single(); ValidateRequest(request, testName, false); }
/// <summary> /// <see cref="IJobHost.StopAsync"/> /// </summary> /// <returns></returns> public async Task StopAsync() { await _jobHost.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(9, _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(); string expectedFunctionCategory = LogCategories.CreateFunctionCategory(testName); string expectedFunctionUserCategory = LogCategories.CreateFunctionUserCategory(testName); ValidateTrace(telemetries[0], "Executed ", expectedFunctionCategory, testName); ValidateTrace(telemetries[1], "Executing ", expectedFunctionCategory, testName); ValidateTrace(telemetries[2], "Found the following functions:\r\n", LogCategories.Startup); ValidateTrace(telemetries[3], "Job host started", LogCategories.Startup); ValidateTrace(telemetries[4], "Job host stopped", LogCategories.Startup); ValidateTrace(telemetries[5], "Logger", expectedFunctionUserCategory, testName, hasCustomScope: true); ValidateTrace(telemetries[6], "Trace", expectedFunctionUserCategory, 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); }
private async Task ServiceBusEndToEndInternal(Type jobContainerType, JobHost host = null, bool verifyLogs = true) { StringWriter consoleOutput = null; TextWriter hold = null; if (verifyLogs) { consoleOutput = new StringWriter(); hold = Console.Out; Console.SetOut(consoleOutput); } if (host == null) { host = CreateHost(jobContainerType); } await WriteQueueMessage(_serviceBusConfig.ConnectionString, FirstQueueName, "E2E"); _topicSubscriptionCalled1 = new ManualResetEvent(initialState: false); _topicSubscriptionCalled2 = new ManualResetEvent(initialState: false); await host.StartAsync(); _topicSubscriptionCalled1.WaitOne(SBTimeout); _topicSubscriptionCalled2.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("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-1", _resultMessage1); Assert.Equal("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-2", _resultMessage2); if (verifyLogs) { Console.SetOut(hold); string[] consoleOutputLines = consoleOutput.ToString().Trim().Split(new string[] { Environment.NewLine }, StringSplitOptions.None).OrderBy(p => p).ToArray(); string[] expectedOutputLines = new string[] { "Found the following functions:", string.Format("{0}.SBQueue2SBQueue", jobContainerType.FullName), string.Format("{0}.MultipleAccounts", jobContainerType.FullName), string.Format("{0}.SBQueue2SBTopic", jobContainerType.FullName), string.Format("{0}.SBTopicListener1", jobContainerType.FullName), string.Format("{0}.SBTopicListener2", jobContainerType.FullName), "Job host started", string.Format("Executing '{0}.SBQueue2SBQueue' (Reason='New ServiceBus message detected on '{1}'.', Id=", jobContainerType.Name, FirstQueueName), string.Format("Executed '{0}.SBQueue2SBQueue' (Succeeded, Id=", jobContainerType.Name), string.Format("Executing '{0}.SBQueue2SBTopic' (Reason='New ServiceBus message detected on '{1}'.', Id=", jobContainerType.Name, SecondQueueName), string.Format("Executed '{0}.SBQueue2SBTopic' (Succeeded, Id=", jobContainerType.Name), string.Format("Executing '{0}.SBTopicListener1' (Reason='New ServiceBus message detected on '{1}'.', Id=", jobContainerType.Name, EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName1)), string.Format("Executed '{0}.SBTopicListener1' (Succeeded, Id=", jobContainerType.Name), string.Format("Executing '{0}.SBTopicListener2' (Reason='New ServiceBus message detected on '{1}'.', Id=", jobContainerType.Name, EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName2)), string.Format("Executed '{0}.SBTopicListener2' (Succeeded, Id=", jobContainerType.Name), "Job host stopped" }.OrderBy(p => p).ToArray(); bool hasError = consoleOutputLines.Any(p => p.Contains("Function had errors")); if (!hasError) { for (int i = 0; i < expectedOutputLines.Length; i++) { Assert.StartsWith(expectedOutputLines[i], consoleOutputLines[i]); } } } }
public async Task OrchestrationStartAndCompleted() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.SayHelloInline), }; var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; var callCount = 0; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.OrchestrationStartAndCompleted), eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); var extensionRegistry = (IExtensionRegistry)host.Services.GetService(typeof(IExtensionRegistry)); var extensionProviders = extensionRegistry.GetExtensions(typeof(IExtensionConfigProvider)) .Where(x => x is DurableTaskExtension) .ToList(); if (extensionProviders.Any()) { var extension = (DurableTaskExtension)extensionProviders.First(); var mock = new Mock <HttpMessageHandler>(); mock.Protected() .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>()) .Returns((HttpRequestMessage request, CancellationToken cancellationToken) => { Assert.True(request.Headers.Any(x => x.Key == "aeg-sas-key")); var values = request.Headers.GetValues("aeg-sas-key").ToList(); Assert.Single(values); Assert.Equal(eventGridKeyValue, values[0]); Assert.Equal(eventGridEndpoint, request.RequestUri.ToString()); var json = request.Content.ReadAsStringAsync().GetAwaiter().GetResult(); dynamic content = JsonConvert.DeserializeObject(json); foreach (dynamic o in content) { Assert.Equal("1.0", o.dataVersion.ToString()); Assert.Equal(nameof(this.OrchestrationStartAndCompleted), o.data.HubName.ToString()); Assert.Equal(orchestratorFunctionNames[0], o.data.FunctionName.ToString()); if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("0", o.data.EventType.ToString()); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Completed", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("1", o.data.EventType.ToString()); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } } callCount++; var message = new HttpResponseMessage(HttpStatusCode.OK); message.Content = new StringContent("{\"message\":\"OK!\"}"); return(Task.FromResult(message)); }); extension.LifeCycleNotificationHelper.SetHttpMessageHandler(mock.Object); } 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); Assert.Equal(2, callCount); await host.StopAsync(); } }
public async Task OrchestrationTerminate() { string[] orchestratorFunctionNames = { nameof(TestOrchestrations.Counter), }; var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; var callCount = 0; using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(this.OrchestrationTerminate), eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); var extensionRegistry = (IExtensionRegistry)host.Services.GetService(typeof(IExtensionRegistry)); var extensionProviders = extensionRegistry.GetExtensions(typeof(IExtensionConfigProvider)) .Where(x => x is DurableTaskExtension) .ToList(); if (extensionProviders.Any()) { var extension = (DurableTaskExtension)extensionProviders.First(); var mock = new Mock <HttpMessageHandler>(); mock.Protected() .Setup <Task <HttpResponseMessage> >("SendAsync", ItExpr.IsAny <HttpRequestMessage>(), ItExpr.IsAny <CancellationToken>()) .Returns((HttpRequestMessage request, CancellationToken cancellationToken) => { Assert.True(request.Headers.Any(x => x.Key == "aeg-sas-key")); var values = request.Headers.GetValues("aeg-sas-key").ToList(); Assert.Single(values); Assert.Equal(eventGridKeyValue, values[0]); Assert.Equal(eventGridEndpoint, request.RequestUri.ToString()); var json = request.Content.ReadAsStringAsync().GetAwaiter().GetResult(); dynamic content = JsonConvert.DeserializeObject(json); foreach (dynamic o in content) { Assert.Equal("1.0", o.dataVersion.ToString()); Assert.Equal(nameof(this.OrchestrationTerminate), o.data.HubName.ToString()); Assert.Equal(orchestratorFunctionNames[0], o.data.FunctionName.ToString()); if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("0", o.data.EventType.ToString()); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Terminated", o.subject.ToString()); Assert.Equal("orchestratorEvent", o.eventType.ToString()); Assert.Equal("5", o.data.EventType.ToString()); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } } callCount++; var message = new HttpResponseMessage(HttpStatusCode.OK); message.Content = new StringContent("{\"message\":\"OK!\"}"); return(Task.FromResult(message)); }); extension.LifeCycleNotificationHelper.SetHttpMessageHandler(mock.Object); } // 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(30), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); await host.StopAsync(); } }
public async Task ActorOrchestration() { using (JobHost host = TestHelpers.GetJobHost(this.loggerFactory, nameof(ActorOrchestration))) { await host.StartAsync(); int initialValue = 0; var client = await host.StartFunctionAsync(nameof(TestOrchestrations.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(); } if (this.useTestLogger) { var logger = loggerProvider.CreatedLoggers.Single(l => l.Category == TestHelpers.LogCategory); var logMessages = logger.LogMessages.ToList(); Assert.Equal(49, logMessages.Count); } }
public async Task OrchestrationTerminate(bool extendedSessionsEnabled) { // Using the counter orchestration because it will wait indefinitely for input. var functionName = nameof(TestOrchestrations.Counter); var eventGridKeyValue = "testEventGridKey"; var eventGridKeySettingName = "eventGridKeySettingName"; var eventGridEndpoint = "http://dymmy.com/"; using (JobHost host = TestHelpers.GetJobHost( this.loggerFactory, nameof(this.OrchestrationTerminate), extendedSessionsEnabled, eventGridKeySettingName, eventGridKeyValue, eventGridEndpoint)) { await host.StartAsync(); string createdInstanceId = Guid.NewGuid().ToString("N"); Func <HttpRequestMessage, HttpResponseMessage> responseGenerator = (HttpRequestMessage req) => req.CreateResponse(HttpStatusCode.OK, "{\"message\":\"OK!\"}"); int callCount = 0; List <Action> eventGridRequestValidators = this.ConfigureEventGridMockHandler( host, functionName, createdInstanceId, eventGridKeyValue, eventGridEndpoint, responseGenerator, handler: (JObject eventPayload) => { dynamic o = eventPayload; if (callCount == 0) { Assert.Equal("durable/orchestrator/Running", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Running", (string)o.data.runtimeStatus); } else if (callCount == 1) { Assert.Equal("durable/orchestrator/Terminated", (string)o.subject); Assert.Equal("orchestratorEvent", (string)o.eventType); Assert.Equal("Terminated", (string)o.data.runtimeStatus); } else { Assert.True(false, "The calls to Event Grid should be exactly 2 but we are registering more."); } callCount++; }); var client = await host.StartOrchestratorAsync( functionName, 0, this.output, createdInstanceId); await client.WaitForStartupAsync(TimeSpan.FromSeconds(30), this.output); await client.TerminateAsync("sayōnara"); var status = await client.WaitForCompletionAsync(TimeSpan.FromSeconds(30), this.output); Assert.Equal(OrchestrationRuntimeStatus.Terminated, status?.RuntimeStatus); Assert.Equal("sayōnara", status?.Output); // There should be one validator for each Event Grid request. // Each validator is a delegate with several Assert statements. Assert.NotEmpty(eventGridRequestValidators); foreach (Action validator in eventGridRequestValidators) { validator.Invoke(); } // TODO: There should be two calls, but the termination notification is not being fired. // https://github.com/Azure/azure-functions-durable-extension/issues/286 Assert.Equal(1, callCount); await host.StopAsync(); } }
private async Task ServiceBusEndToEndInternal(Type jobContainerType, JobHost host = null, bool verifyLogs = true) { if (host == null) { host = CreateHost(jobContainerType); } await WriteQueueMessage(_serviceBusConfig.ConnectionString, FirstQueueName, "E2E"); _topicSubscriptionCalled1 = new ManualResetEvent(initialState: false); _topicSubscriptionCalled2 = new ManualResetEvent(initialState: false); await host.StartAsync(); _topicSubscriptionCalled1.WaitOne(SBTimeout); _topicSubscriptionCalled2.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("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-1", _resultMessage1); Assert.Equal("E2E-SBQueue2SBQueue-SBQueue2SBTopic-topic-2", _resultMessage2); if (verifyLogs) { IEnumerable <LogMessage> consoleOutput = _loggerProvider.GetAllLogMessages(); Assert.DoesNotContain(consoleOutput, p => p.Level == LogLevel.Error); string[] consoleOutputLines = consoleOutput .Where(p => p.FormattedMessage != null) .SelectMany(p => p.FormattedMessage.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)) .OrderBy(p => p) .ToArray(); string[] expectedOutputLines = new string[] { "Found the following functions:", $"{jobContainerType.FullName}.SBQueue2SBQueue", $"{jobContainerType.FullName}.MultipleAccounts", $"{jobContainerType.FullName}.SBQueue2SBTopic", $"{jobContainerType.FullName}.SBTopicListener1", $"{jobContainerType.FullName}.SBTopicListener2", $"{jobContainerType.FullName}.ServiceBusBinderTest", "Job host started", $"Executing '{jobContainerType.Name}.SBQueue2SBQueue' (Reason='New ServiceBus message detected on '{FirstQueueName}'.', Id=", $"Executed '{jobContainerType.Name}.SBQueue2SBQueue' (Succeeded, Id=", $"Executing '{jobContainerType.Name}.SBQueue2SBTopic' (Reason='New ServiceBus message detected on '{SecondQueueName}'.', Id=", $"Executed '{jobContainerType.Name}.SBQueue2SBTopic' (Succeeded, Id=", $"Executing '{jobContainerType.Name}.SBTopicListener1' (Reason='New ServiceBus message detected on '{EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName1)}'.', Id=", $"Executed '{jobContainerType.Name}.SBTopicListener1' (Succeeded, Id=", $"Executing '{jobContainerType.Name}.SBTopicListener2' (Reason='New ServiceBus message detected on '{EntityNameHelper.FormatSubscriptionPath(TopicName, TopicSubscriptionName2)}'.', Id=", $"Executed '{jobContainerType.Name}.SBTopicListener2' (Succeeded, Id=", "Job host stopped" }.OrderBy(p => p).ToArray(); Action <string>[] inspectors = expectedOutputLines.Select <string, Action <string> >(p => (string m) => m.StartsWith(p)).ToArray(); Assert.Collection(consoleOutputLines, inspectors); } }
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(); } }