private static async Task <FunctionOutputBufferHandler> StartFunctionHostProcess( int port, string provider, string workingDirectory, FunctionConfiguration?functionConfiguration) { var startInfo = new ProcessStartInfo( await GetToolPath(), $"host start --port {port} --{provider}") { WorkingDirectory = workingDirectory, UseShellExecute = false, CreateNoWindow = false, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, WindowStyle = ProcessWindowStyle.Normal, }; if (functionConfiguration != null) { foreach (KeyValuePair <string, string> kvp in functionConfiguration.EnvironmentVariables) { startInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } var processHandler = new FunctionOutputBufferHandler(startInfo); processHandler.Start(); return(processHandler); }
/// <summary> /// Start a functions instance. /// </summary> /// <param name="path">The location of the functions project.</param> /// <param name="port">The port on which to start the functions instance.</param> /// <param name="runtime">The runtime version for use with the function host (e.g. netcoreapp3.1).</param> /// <param name="provider">The functions provider. Defaults to csharp.</param> /// <param name="configuration">A <see cref="FunctionConfiguration"/> instance, for conveying /// configuration values via environment variables to the function host process.</param> /// <returns>A task that completes once the function instance has started.</returns> public async Task StartFunctionsInstance(string path, int port, string runtime, string provider = "csharp", FunctionConfiguration?configuration = null) { this.functionLogScope = this.logger.BeginScope(this); if (IsSomethingAlreadyListeningOn(port)) { this.logger.LogWarning("Found a process listening on {Port}. Is this a debug instance?", port); this.logger.LogWarning("This test run will reuse this process, and so may produce unexpected results."); return; } this.logger.LogInformation("Starting a function instance for project {Path} on port {Port}", path, port); this.logger.LogDebug("Starting process"); FunctionOutputBufferHandler bufferHandler = await StartFunctionHostProcess( port, provider, FunctionProject.ResolvePath(path, runtime, this.logger), configuration).ConfigureAwait(false); lock (this.sync) { this.output.Add(bufferHandler); } this.logger.LogDebug("Process started; waiting for initialisation to complete"); await Task.WhenAny( bufferHandler.JobHostStarted, bufferHandler.ExitCode, Task.Delay(TimeSpan.FromSeconds(StartupTimeout))).ConfigureAwait(false); if (bufferHandler.ExitCode.IsCompleted) { int exitCode = await bufferHandler.ExitCode.ConfigureAwait(false); this.logger.LogError( @"Failed to start function host, process terminated unexpectedly with exit code {ExitCode}. StdOut: {StdOut} StdErr: {StdErr}", exitCode, bufferHandler.StandardOutputText, bufferHandler.StandardErrorText); throw new FunctionStartupException( $"Function host process terminated unexpectedly with exit code {exitCode}.", stdout: bufferHandler.StandardOutputText, stderr: bufferHandler.StandardErrorText); } if (!bufferHandler.JobHostStarted.IsCompleted) { throw new FunctionStartupException("Timed out while starting functions instance."); } this.logger.LogDebug("Initialisation completed"); this.logger.LogInformation("Function {Path} now running on port {Port}", path, port); }
/// <summary> /// Start a functions instance. /// </summary> /// <param name="path">The location of the functions project.</param> /// <param name="port">The port on which to start the functions instance.</param> /// <param name="runtime">The runtime version for use with the function host (e.g. netcoreapp3.1).</param> /// <param name="provider">The functions provider. Defaults to csharp.</param> /// <param name="configuration">A <see cref="FunctionConfiguration"/> instance, for conveying /// configuration values via environment variables to the function host process.</param> /// <returns>A task that completes once the function instance has started.</returns> public async Task StartFunctionsInstance(string path, int port, string runtime, string provider = "csharp", FunctionConfiguration?configuration = null) { if (IsSomethingAlreadyListeningOn(port)) { Console.WriteLine($"Found a process listening on {port}. Is this a debug instance?"); Console.WriteLine("This test run will reuse this process, and so may produce unexpected results."); return; } Console.WriteLine($"Starting a function instance for project {path} on port {port}"); Console.WriteLine("\tStarting process"); FunctionOutputBufferHandler bufferHandler = await StartFunctionHostProcess( port, provider, FunctionProject.ResolvePath(path, runtime), configuration); lock (this.sync) { this.output.Add(bufferHandler); } Console.WriteLine("\tProcess started; waiting for initialisation to complete"); await Task.WhenAny( bufferHandler.JobHostStarted, bufferHandler.ExitCode, Task.Delay(TimeSpan.FromSeconds(StartupTimeout))).ConfigureAwait(false); if (bufferHandler.ExitCode.IsCompleted) { int exitCode = await bufferHandler.ExitCode.ConfigureAwait(false); throw new FunctionStartupException( $"Function host process terminated unexpectedly with exit code {exitCode}.", stderr: bufferHandler.StandardErrorText); } if (!bufferHandler.JobHostStarted.IsCompleted) { throw new FunctionStartupException("Timed out while starting functions instance."); } Console.WriteLine(); Console.WriteLine("\tStarted"); }
/// <summary> /// Start a functions instance. /// </summary> /// <param name="path">The location of the functions project.</param> /// <param name="port">The port on which to start the functions instance.</param> /// <param name="runtime">The runtime version for use with the function host (e.g. netcoreapp3.1).</param> /// <param name="provider">The functions provider. Defaults to csharp.</param> /// <param name="configuration">A <see cref="FunctionConfiguration"/> instance, for conveying /// configuration values via environment variables to the function host process.</param> /// <returns>A task that completes once the function instance has started.</returns> public async Task StartFunctionsInstance(string path, int port, string runtime, string provider = "csharp", FunctionConfiguration?configuration = null) { Console.WriteLine($"Starting a function instance for project {path} on port {port}"); Console.WriteLine("\tStarting process"); FunctionOutputBufferHandler bufferHandler = StartFunctionHostProcess( port, provider, await GetToolPath(), GetWorkingDirectory(path, runtime), configuration); lock (this.sync) { this.output.Add(bufferHandler); } Console.WriteLine("\tProcess started; waiting for initialisation to complete"); await Task.WhenAny( bufferHandler.JobHostStarted, bufferHandler.ExitCode, Task.Delay(TimeSpan.FromSeconds(StartupTimeout))).ConfigureAwait(false); if (bufferHandler.ExitCode.IsCompleted) { int exitCode = await bufferHandler.ExitCode.ConfigureAwait(false); throw new FunctionStartupException( $"Function host process terminated unexpectedly with exit code {exitCode}.", stderr: bufferHandler.StandardErrorText); } if (!bufferHandler.JobHostStarted.IsCompleted) { throw new FunctionStartupException("Timed out while starting functions instance."); } Console.WriteLine(); Console.WriteLine("\tStarted"); }
private static async Task <FunctionOutputBufferHandler> StartFunctionHostProcess( int port, string provider, string workingDirectory, FunctionConfiguration?functionConfiguration) { var startInfo = new ProcessStartInfo( await GetToolPath().ConfigureAwait(false), $"host start --port {port} --{provider}") { WorkingDirectory = workingDirectory, UseShellExecute = false, CreateNoWindow = false, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, WindowStyle = ProcessWindowStyle.Normal, }; if (functionConfiguration != null) { foreach (KeyValuePair <string, string> kvp in functionConfiguration.EnvironmentVariables) { startInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } // Force the logging level to debug to ensure we can pick up the message that tells us the function is // ready to go. startInfo.EnvironmentVariables["AzureFunctionsJobHost:logging:logLevel:default"] = "Debug"; var processHandler = new FunctionOutputBufferHandler(startInfo); processHandler.Start(); return(processHandler); }
/// <summary> /// Start a functions instance. /// </summary> /// <param name="featureContext">The current feature context.</param> /// <param name="scenarioContext">The current scenario context. Not required if using this class per-feature.</param> /// <param name="path">The location of the functions project.</param> /// <param name="port">The port on which to start the functions instance.</param> /// <param name="runtime">The runtime version, defaults to netcoreapp2.1.</param> /// <param name="provider">The functions provider. Defaults to csharp.</param> /// <returns>A task that completes once the function instance has started.</returns> public async Task StartFunctionsInstance( FeatureContext featureContext, ScenarioContext?scenarioContext, string path, int port, string runtime = "netcoreapp2.1", string provider = "csharp") { Console.WriteLine($"Starting a function instance for project {path} on port {port}"); string directoryExtension = $"\\bin\\release\\{runtime}"; string lowerInvariantCurrentDirectory = TestContext.CurrentContext.TestDirectory.ToLowerInvariant(); if (lowerInvariantCurrentDirectory.Contains("debug")) { directoryExtension = $"\\bin\\debug\\{runtime}"; } Console.WriteLine($"\tCurrent directory: {lowerInvariantCurrentDirectory}"); string root = TestContext.CurrentContext.TestDirectory.Substring( 0, TestContext.CurrentContext.TestDirectory.IndexOf(@"\Solutions\") + 11); Console.WriteLine($"\tRoot: {root}"); string npmPrefix = await GetNpmPrefix().ConfigureAwait(false); string toolsFolder = Path.Combine( npmPrefix, @"node_modules\azure-functions-core-tools\bin"); Assert.IsTrue( Directory.Exists(toolsFolder), $"Azure Functions runtime not found at {toolsFolder}. Have you run: 'npm install -g azure-functions-core-tools --unsafe-perm true'?"); string toolPath = Path.Combine( toolsFolder, "func"); Console.WriteLine($"\tToolsPath: {toolPath}"); Console.WriteLine($"\tStarting process"); var startInfo = new ProcessStartInfo(toolPath, $"host start --port {port} --{provider}") { WorkingDirectory = root + path + directoryExtension, UseShellExecute = false, CreateNoWindow = false, RedirectStandardError = true, RedirectStandardOutput = true, RedirectStandardInput = true, WindowStyle = ProcessWindowStyle.Normal, }; FunctionConfiguration?functionConfiguration = null; scenarioContext?.TryGetValue(out functionConfiguration); if (functionConfiguration == null) { featureContext.TryGetValue(out functionConfiguration); } if (functionConfiguration != null) { foreach (KeyValuePair <string, string> kvp in functionConfiguration.EnvironmentVariables) { startInfo.EnvironmentVariables[kvp.Key] = kvp.Value; } } var bufferHandler = new FunctionOutputBufferHandler(startInfo); lock (this.sync) { this.output[bufferHandler.Process] = bufferHandler; } Console.WriteLine($"\tProcess started; waiting for initialisation to complete"); await Task.WhenAny( bufferHandler.JobHostStarted, bufferHandler.ExitCode, Task.Delay(TimeSpan.FromSeconds(StartupTimeout))).ConfigureAwait(false); if (bufferHandler.ExitCode.IsCompleted) { int exitCode = await bufferHandler.ExitCode.ConfigureAwait(false); Assert.Fail($"Function host process terminated unexpectedly with exit code {exitCode}. Error output: {bufferHandler.StandardErrorText}"); } else if (!bufferHandler.JobHostStarted.IsCompleted) { Assert.Fail("Timed out while starting functions instance."); } else { Console.WriteLine(); Console.WriteLine("\tStarted"); } }