public async Task HttpTrigger_Model_Binding_V2CompatMode() { // We need a custom host to set this to v2 compat mode. using (var host = new TestFunctionHost(@"TestScripts\CSharp", Path.Combine(Path.GetTempPath(), "Functions"), configureWebHostServices: webHostServices => { var environment = new TestEnvironment(); environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionsV2CompatibilityModeKey, "true"); webHostServices.AddSingleton <IEnvironment>(_ => environment); }, configureScriptHostWebJobsBuilder: webJobsBuilder => { webJobsBuilder.Services.Configure <ScriptJobHostOptions>(o => { // Only load the functions we care about o.Functions = new[] { "HttpTrigger-Model-v2", }; }); })) { (JObject req, JObject res) = await MakeModelRequest(host.HttpClient, "-v2"); // in v2, we expect the response to have a null customEnumerable property. req["customEnumerable"] = null; Assert.True(JObject.DeepEquals(req, res), res.ToString()); } }
public async Task HostStatusReturns_IfHostJsonError() { string hostJsonPath = Path.Combine(_hostPath, ScriptConstants.HostMetadataFileName); // Simulate a non-empty file without a 'version' JObject hostConfig = JObject.FromObject(new { functionTimeout = TimeSpan.FromSeconds(30) }); await File.WriteAllTextAsync(hostJsonPath, hostConfig.ToString()); var host = new TestFunctionHost(_hostPath, _ => { }); // Ping the status endpoint to ensure we see the exception HostStatus status = await host.GetHostStatusAsync(); Assert.Equal("Error", status.State); Assert.Equal("Microsoft.Azure.WebJobs.Script: The host.json file is missing the required 'version' property. See https://aka.ms/functions-hostjson for steps to migrate the configuration file.", status.Errors.Single()); // Now update the file and make sure it auto-restarts. hostConfig["version"] = "2.0"; await File.WriteAllTextAsync(hostJsonPath, hostConfig.ToString()); await TestHelpers.Await(async() => { status = await host.GetHostStatusAsync(); return(status.State == $"{ScriptHostState.Running}"); }); Assert.Null(status.Errors); }
public async Task TestWarmupEndPoint_WhenHostStarts() { string testScriptPath = Path.Combine("TestScripts", "CSharp"); string testLogPath = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs", Guid.NewGuid().ToString(), @"Functions"); var settings = new Dictionary <string, string>() { ["WEBSITE_SKU"] = "ElasticPremium" }; var testEnvironment = new TestEnvironment(settings); _testHost = new TestFunctionHost(testScriptPath, testLogPath, configureWebHostServices: services => { services.AddSingleton <IScriptHostBuilder, PausingScriptHostBuilder>(); services.AddSingleton <IEnvironment>(testEnvironment); services.AddSingleton <IConfigureBuilder <IWebJobsBuilder> >(new DelegatedConfigureBuilder <IWebJobsBuilder>(b => { b.UseHostId("1234"); b.Services.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "ManualTrigger", "Scenarios" }); })); }); // Make sure host started properly and drain the semaphore count released // by inital start Assert.True(_hostBuild.Wait(SemaphoreWaitTimeout), "Host failed to start"); string uri = "admin/warmup"; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); HttpResponseMessage response = await _testHost.HttpClient.SendAsync(request); // Better be successful Assert.True(response.IsSuccessStatusCode, "Warmup endpoint did not return a success status. " + $"Instead found {response.StatusCode}"); }
public WebJobsScriptHostServiceTests() { // configure the monitor so it will fail within a couple seconds _healthMonitorOptions = new HostHealthMonitorOptions { HealthCheckInterval = TimeSpan.FromMilliseconds(100), HealthCheckWindow = TimeSpan.FromSeconds(1), HealthCheckThreshold = 5 }; var wrappedHealthMonitorOptions = new OptionsWrapper <HostHealthMonitorOptions>(_healthMonitorOptions); _mockApplicationLifetime = new Mock <IApplicationLifetime>(MockBehavior.Loose); _mockApplicationLifetime.Setup(p => p.StopApplication()) .Callback(() => { _shutdownCalled = true; }); _mockEnvironment = new Mock <IEnvironment>(); var mockServiceProvider = new Mock <IServiceProvider>(MockBehavior.Strict); _mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteInstanceId)).Returns("testapp"); _mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName)).Returns("testapp"); var mockHostPerformanceManager = new Mock <HostPerformanceManager>(_mockEnvironment.Object, wrappedHealthMonitorOptions, mockServiceProvider.Object, null); mockHostPerformanceManager.Setup(p => p.PerformanceCountersExceeded(It.IsAny <Collection <string> >(), It.IsAny <ILogger>())) .Callback <Collection <string>, ILogger>((c, l) => { if (_countersExceeded) { foreach (var counter in _exceededCounters) { c.Add(counter); } } }) .Returns(() => _countersExceeded); _testHost = new TestFunctionHost(TestScriptPath, TestLogPath, configureWebHostServices: services => { services.AddSingleton <IOptions <HostHealthMonitorOptions> >(wrappedHealthMonitorOptions); services.AddSingleton <IApplicationLifetime>(_mockApplicationLifetime.Object); services.AddSingleton <IEnvironment>(_mockEnvironment.Object); services.AddSingleton <HostPerformanceManager>(mockHostPerformanceManager.Object); services.AddSingleton <IConfigureBuilder <IWebJobsBuilder> >(new DelegatedConfigureBuilder <IWebJobsBuilder>(b => { b.UseHostId("1234"); b.Services.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "ManualTrigger", "Scenarios" }); })); }, configureScriptHostWebJobsBuilder: builder => { builder.AddExtension <TestWebHookExtension>(); }); _scriptHostService = _testHost.JobHostServices.GetService <IScriptHostManager>() as WebJobsScriptHostService; }
public async Task TestWarmupEndPoint_WhenHostStarts() { string testScriptPath = Path.Combine("TestScripts", "CSharp"); string testLogPath = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs", Guid.NewGuid().ToString(), @"Functions"); var settings = new Dictionary <string, string>() { ["WEBSITE_SKU"] = "ElasticPremium" }; var testEnvironment = new TestEnvironment(settings); _testHost = new TestFunctionHost(testScriptPath, testLogPath, configureWebHostServices: services => { services.AddSingleton <IScriptHostBuilder, PausingScriptHostBuilder>(); services.AddSingleton <IEnvironment>(testEnvironment); services.AddSingleton <IConfigureBuilder <IWebJobsBuilder> >(new DelegatedConfigureBuilder <IWebJobsBuilder>(b => { b.UseHostId("1234"); b.Services.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "ManualTrigger", "Scenarios" }); })); }); // Make sure host started properly and drain the semaphore count released // by inital start Assert.True(_hostBuild.Wait(SemaphoreWaitTimeout), "Host failed to start"); // Restart needs to be a separate thread as we need to pause the restart // and make the warmup call during the build step. // The IScriptHostBuilder.BuildHost is synchronous, so creating another thread // ensures that we can continue other tasks Thread hostRestart = new Thread(async() => { await _testHost.RestartAsync(CancellationToken.None); }); hostRestart.Start(); // Wait for restart to hit the build Assert.True(_hostBuild.Wait(SemaphoreWaitTimeout), "Host failed to start"); // Let's make the warmup request and not wait for it to finish, // as warmup call needs the host to be running while the host is currently paused string uri = "admin/warmup"; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); Task <HttpResponseMessage> responseTask = _testHost.HttpClient.SendAsync(request); // We wait for 5 seconds just to make sure warmup call was invoked properly await Task.Delay(TimeSpan.FromSeconds(5)); // We let the host continue to make sure it starts back up properly _hostSetup.Release(); // Now we can wait for the warmup call to complete var response = await responseTask; // Better be successful Assert.True(response.IsSuccessStatusCode, "Warmup endpoint did not return a success status. " + $"Instead found {response.StatusCode}"); }
public WebJobsScriptHostServiceTests() { string testScriptPath = @"TestScripts\CSharp"; string testLogPath = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs", Guid.NewGuid().ToString(), @"Functions"); // configure the monitor so it will fail within a couple seconds _healthMonitorOptions = new HostHealthMonitorOptions { HealthCheckInterval = TimeSpan.FromMilliseconds(100), HealthCheckWindow = TimeSpan.FromSeconds(1), HealthCheckThreshold = 5 }; var wrappedHealthMonitorOptions = new OptionsWrapper <HostHealthMonitorOptions>(_healthMonitorOptions); _mockJobHostEnvironment = new Mock <IScriptJobHostEnvironment>(MockBehavior.Strict); _mockJobHostEnvironment.Setup(p => p.Shutdown()) .Callback(() => { _shutdownCalled = true; }); var mockEnvironment = new Mock <IEnvironment>(); mockEnvironment.Setup(p => p.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteInstanceId)).Returns("testapp"); var mockHostPerformanceManager = new Mock <HostPerformanceManager>(mockEnvironment.Object, wrappedHealthMonitorOptions); mockHostPerformanceManager.Setup(p => p.IsUnderHighLoad(It.IsAny <Collection <string> >(), It.IsAny <ILogger>())) .Callback <Collection <string>, ILogger>((c, l) => { if (_underHighLoad) { foreach (var counter in _exceededCounters) { c.Add(counter); } } }) .Returns(() => _underHighLoad); _testHost = new TestFunctionHost(testScriptPath, testLogPath, configureWebHostServices: services => { services.AddSingleton <IOptions <HostHealthMonitorOptions> >(wrappedHealthMonitorOptions); services.AddSingleton <IScriptJobHostEnvironment>(_mockJobHostEnvironment.Object); services.AddSingleton <IEnvironment>(mockEnvironment.Object); services.AddSingleton <HostPerformanceManager>(mockHostPerformanceManager.Object); services.AddSingleton <IConfigureBuilder <IWebJobsBuilder> >(new DelegatedConfigureBuilder <IWebJobsBuilder>(b => { b.UseHostId("1234"); b.Services.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "ManualTrigger", "Scenarios" }); })); }); _scriptHostService = _testHost.JobHostServices.GetService <IScriptHostManager>() as WebJobsScriptHostService; }
private async Task RunScenario(TestScenario scenario) { var cancellationToken = new CancellationToken(); var host = new TestFunctionHost(); using (var scope = host.GetScope()) { await ReceiveEventsAsync(scope, scenario, cancellationToken); await ProcessEventsAsync(scope, cancellationToken); CheckState(scope, scenario); } }
private static TestFunctionHost GetHost(Action <IDictionary <string, string> > addEnvironmentVariables = null) { string scriptPath = @"TestScripts\DirectLoad\"; string logPath = Path.Combine(Path.GetTempPath(), @"Functions"); var host = new TestFunctionHost(scriptPath, logPath, configureWebHostServices: s => { IDictionary <string, string> dict = new Dictionary <string, string>(); addEnvironmentVariables?.Invoke(dict); s.AddSingleton <IEnvironment>(_ => new TestEnvironment(dict)); }); return(host); }
public async Task DisposedScriptLoggerFactory_UsesFullStackTrace() { var host = new TestFunctionHost(@"TestScripts\CSharp", configureScriptHostServices: s => { s.AddSingleton <IExtensionConfigProvider, CustomTriggerExtensionConfigProvider>(); s.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "CustomTrigger" }); }); await CustomListener.RunAsync("one"); host.Dispose(); // In this scenario, the logger throws an exception before we enter the try/catch for the function invocation. var ex = await Assert.ThrowsAsync <HostDisposedException>(() => CustomListener.RunAsync("two")); Assert.Equal($"The host is disposed and cannot be used. Disposed object: '{typeof(ScriptLoggerFactory).FullName}'; Found IListener in stack trace: '{typeof(CustomListener).AssemblyQualifiedName}'", ex.Message); Assert.Contains("CustomListener.RunAsync", ex.StackTrace); }
public ApplicationInsightsTestFixture(string scriptRoot, string testId) { string scriptPath = Path.Combine(Environment.CurrentDirectory, scriptRoot); string logPath = Path.Combine(Path.GetTempPath(), @"Functions"); Environment.SetEnvironmentVariable(EnvironmentSettingNames.FunctionWorkerRuntime, testId); WebHostOptions = new ScriptApplicationHostOptions { IsSelfHost = true, ScriptPath = scriptPath, LogPath = logPath, SecretsPath = Environment.CurrentDirectory // not used }; TestHost = new TestFunctionHost(scriptPath, logPath, configureScriptHostServices: s => { s.AddSingleton <ITelemetryChannel>(_ => Channel); s.Configure <ScriptJobHostOptions>(o => { o.Functions = new[] { "Scenarios", "HttpTrigger-Scenarios" }; }); s.AddSingleton <IMetricsLogger>(_ => MetricsLogger); }, configureScriptHostAppConfiguration: configurationBuilder => { configurationBuilder.AddInMemoryCollection(new Dictionary <string, string> { [EnvironmentSettingNames.AppInsightsInstrumentationKey] = ApplicationInsightsKey }); }); HttpClient = TestHost.HttpClient; TestHelpers.WaitForWebHost(HttpClient); }
public async Task HostStatusReturns_IfHostJsonError() { string hostJsonPath = Path.Combine(_hostPath, ScriptConstants.HostMetadataFileName); // Simulate a non-empty file without a 'version' JObject hostConfig = JObject.FromObject(new { functionTimeout = TimeSpan.FromSeconds(30) }); await File.WriteAllTextAsync(hostJsonPath, hostConfig.ToString()); string logPath = Path.Combine(Path.GetTempPath(), @"Functions"); _host = new TestFunctionHost(_hostPath, logPath, _ => { }); // Ping the status endpoint to ensure we see the exception HostStatus status = await _host.GetHostStatusAsync(); Assert.Equal("Error", status.State); Assert.Equal("Microsoft.Azure.WebJobs.Script: The host.json file is missing the required 'version' property. See https://aka.ms/functions-hostjson for steps to migrate the configuration file.", status.Errors.Single()); // Due to https://github.com/Azure/azure-functions-host/issues/1351, slow this down to ensure // we have a host running and watching for file changes. await TestHelpers.Await(() => { return(_host.GetLog().Contains("[Microsoft.Extensions.Hosting.Internal.Host] Hosting started")); }); // Now update the file and make sure it auto-restarts. hostConfig["version"] = "2.0"; await File.WriteAllTextAsync(hostJsonPath, hostConfig.ToString()); await TestHelpers.Await(async() => { status = await _host.GetHostStatusAsync(); return(status.State == $"{ScriptHostState.Running}"); }, userMessageCallback : _host.GetLog); Assert.Null(status.Errors); }
public async Task FileLogger_IOExceptionDuringInvocation_Recovers() { var fileWriterFactory = new TestFileWriterFactory(onAppendLine: null, onFlush: () => { // The below function will fail, causing an immediate flush. This exception // simulates the disk being full. ExecutionEvents should be logged as expected // and the "Finished" event should get logged. throw new IOException(); }); using (var host = new TestFunctionHost(_scriptRoot, _testLogPath, configureWebHostServices: s => { s.AddSingleton <IEventGenerator>(_ => _eventGenerator); }, configureScriptHostServices: s => { s.AddSingleton <IFileWriterFactory>(_ => fileWriterFactory); s.PostConfigure <ScriptJobHostOptions>(o => { o.FileLoggingMode = FileLoggingMode.Always; o.Functions = new[] { "HttpTrigger-Scenarios" }; }); })) { // Issue an invalid request that fails. var content = new StringContent(JsonConvert.SerializeObject(new { scenario = "invalid" })); var response = await host.HttpClient.PostAsync("/api/HttpTrigger-Scenarios", content); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); await TestHelpers.Await(() => { var executionEvents = _eventGenerator.GetFunctionExecutionEvents(); return(executionEvents.SingleOrDefault(p => p.ExecutionStage == ExecutionStage.Finished) != null); }); } }
private TestFunctionHost StartLocalHost(string baseTestPath, string sourceFunctionApp, string[] allowedList, IList <IFunctionProvider> providers, IEnvironment testEnvironment) { string appContent = Path.Combine(baseTestPath, "FunctionApp"); string testLogPath = Path.Combine(baseTestPath, "Logs"); var syncTriggerMock = new Mock <IFunctionsSyncManager>(MockBehavior.Strict); syncTriggerMock.Setup(p => p.TrySyncTriggersAsync(It.IsAny <bool>())).ReturnsAsync(new SyncTriggersResult { Success = true }); FileUtility.CopyDirectory(sourceFunctionApp, appContent); var host = new TestFunctionHost(sourceFunctionApp, testLogPath, configureScriptHostWebJobsBuilder: builder => { foreach (var provider in providers) { builder.Services.AddSingleton(provider); } if (allowedList != null && allowedList.Length != 0) { builder.Services.Configure <ScriptJobHostOptions>(o => { o.Functions = allowedList; }); } builder.Services.AddSingleton(testEnvironment); }, configureScriptHostServices: s => { s.AddSingleton(syncTriggerMock.Object); }); return(host); }
public async Task DisposedResolver_UsesFullStackTrace() { var host = new TestFunctionHost(@"TestScripts\CSharp", configureScriptHostServices: s => { s.AddSingleton <IExtensionConfigProvider, CustomTriggerExtensionConfigProvider>(); s.Configure <ScriptJobHostOptions>(o => o.Functions = new[] { "CustomTrigger" }); s.AddSingleton <ILoggerFactory, TestScriptLoggerFactory>(); }); await CustomListener.RunAsync("one"); host.Dispose(); // In this scenario, the function is considered failed even though the function itself was never called. var result = await CustomListener.RunAsync("two"); Assert.False(result.Succeeded); var ex = result.Exception; Assert.Equal($"The host is disposed and cannot be used. Disposed object: '{typeof(ScopedResolver).FullName}'; Found IListener in stack trace: '{typeof(CustomListener).AssemblyQualifiedName}'", ex.Message); Assert.Contains("CustomListener.RunAsync", ex.StackTrace); }