11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License. See License.txt in the project root for license information.
33
4- using Microsoft . Data . SqlClient ;
5- using Microsoft . Azure . WebJobs . Extensions . Sql . Tests . Common ;
64using System ;
5+ using System . Collections . Generic ;
76using System . Data . Common ;
87using System . Diagnostics ;
98using System . IO ;
9+ using System . Linq ;
1010using System . Net . Http ;
1111using System . Reflection ;
1212using System . Runtime . InteropServices ;
1313using System . Text ;
1414using System . Threading . Tasks ;
15+ using Microsoft . AspNetCore . WebUtilities ;
16+ using Microsoft . Azure . WebJobs . Extensions . Sql . Samples . Common ;
17+ using Microsoft . Azure . WebJobs . Extensions . Sql . Tests . Common ;
18+ using Microsoft . Data . SqlClient ;
1519using Xunit ;
1620using Xunit . Abstractions ;
17- using Microsoft . Azure . WebJobs . Extensions . Sql . Samples . Common ;
18- using Microsoft . AspNetCore . WebUtilities ;
19- using System . Collections . Generic ;
20- using System . Linq ;
21+
2122using static Microsoft . Azure . WebJobs . Extensions . Sql . Telemetry . Telemetry ;
2223
2324namespace Microsoft . Azure . WebJobs . Extensions . Sql . Tests . Integration
2425{
2526 public class IntegrationTestBase : IDisposable
2627 {
2728 /// <summary>
28- /// Host process for Azure Function CLI
29+ /// The first Function Host process that was started. Null if no process has been started yet.
30+ /// </summary>
31+ protected Process FunctionHost => this . FunctionHostList . FirstOrDefault ( ) ;
32+
33+ /// <summary>
34+ /// Host processes for Azure Function CLI.
2935 /// </summary>
30- protected Process FunctionHost { get ; private set ; }
36+ protected List < Process > FunctionHostList { get ; } = new List < Process > ( ) ;
3137
3238 /// <summary>
3339 /// Host process for Azurite local storage emulator. This is required for non-HTTP trigger functions:
@@ -184,12 +190,16 @@ protected void StartFunctionHost(string functionName, SupportedLanguages languag
184190 {
185191 throw new FileNotFoundException ( "Working directory not found at " + workingDirectory ) ;
186192 }
193+
194+ // Use a different port for each new host process, starting with the default port number: 7071.
195+ int port = this . Port + this . FunctionHostList . Count ;
196+
187197 var startInfo = new ProcessStartInfo
188198 {
189199 // The full path to the Functions CLI is required in the ProcessStartInfo because UseShellExecute is set to false.
190200 // We cannot both use shell execute and redirect output at the same time: https://docs.microsoft.com//dotnet/api/system.diagnostics.processstartinfo.redirectstandardoutput#remarks
191201 FileName = GetFunctionsCoreToolsPath ( ) ,
192- Arguments = $ "start --verbose --port { this . Port } --functions { functionName } ",
202+ Arguments = $ "start --verbose --port { port } --functions { functionName } ",
193203 WorkingDirectory = workingDirectory ,
194204 WindowStyle = ProcessWindowStyle . Hidden ,
195205 RedirectStandardOutput = true ,
@@ -205,24 +215,26 @@ protected void StartFunctionHost(string functionName, SupportedLanguages languag
205215 startInfo . EnvironmentVariables [ TelemetryOptoutEnvVar ] = "1" ;
206216
207217 this . LogOutput ( $ "Starting { startInfo . FileName } { startInfo . Arguments } in { startInfo . WorkingDirectory } ") ;
208- this . FunctionHost = new Process
218+
219+ var functionHost = new Process
209220 {
210221 StartInfo = startInfo
211222 } ;
212223
224+ this . FunctionHostList . Add ( functionHost ) ;
225+
213226 // Register all handlers before starting the functions host process.
214227 var taskCompletionSource = new TaskCompletionSource < bool > ( ) ;
215- this . FunctionHost . OutputDataReceived += this . TestOutputHandler ;
216- this . FunctionHost . OutputDataReceived += SignalStartupHandler ;
228+ functionHost . OutputDataReceived += SignalStartupHandler ;
217229 this . FunctionHost . OutputDataReceived += customOutputHandler ;
218230
219- this . FunctionHost . ErrorDataReceived += this . TestOutputHandler ;
231+ functionHost . Start ( ) ;
232+ functionHost . OutputDataReceived += this . GetTestOutputHandler ( functionHost . Id ) ;
233+ functionHost . ErrorDataReceived += this . GetTestOutputHandler ( functionHost . Id ) ;
234+ functionHost . BeginOutputReadLine ( ) ;
235+ functionHost . BeginErrorReadLine ( ) ;
220236
221- this . FunctionHost . Start ( ) ;
222- this . FunctionHost . BeginOutputReadLine ( ) ;
223- this . FunctionHost . BeginErrorReadLine ( ) ;
224-
225- this . LogOutput ( $ "Waiting for Azure Function host to start...") ;
237+ this . LogOutput ( "Waiting for Azure Function host to start..." ) ;
226238
227239 const int FunctionHostStartupTimeoutInSeconds = 60 ;
228240 bool isCompleted = taskCompletionSource . Task . Wait ( TimeSpan . FromSeconds ( FunctionHostStartupTimeoutInSeconds ) ) ;
@@ -233,7 +245,7 @@ protected void StartFunctionHost(string functionName, SupportedLanguages languag
233245 const int BufferTimeInSeconds = 5 ;
234246 Task . Delay ( TimeSpan . FromSeconds ( BufferTimeInSeconds ) ) . Wait ( ) ;
235247
236- this . LogOutput ( $ "Azure Function host started!") ;
248+ this . LogOutput ( "Azure Function host started!" ) ;
237249 this . FunctionHost . OutputDataReceived -= SignalStartupHandler ;
238250
239251 void SignalStartupHandler ( object sender , DataReceivedEventArgs e )
@@ -293,11 +305,16 @@ private void LogOutput(string output)
293305 }
294306 }
295307
296- private void TestOutputHandler ( object sender , DataReceivedEventArgs e )
308+ private DataReceivedEventHandler GetTestOutputHandler ( int processId )
297309 {
298- if ( e != null && ! string . IsNullOrEmpty ( e . Data ) )
310+ return TestOutputHandler ;
311+
312+ void TestOutputHandler ( object sender , DataReceivedEventArgs e )
299313 {
300- this . LogOutput ( e . Data ) ;
314+ if ( ! string . IsNullOrEmpty ( e . Data ) )
315+ {
316+ this . LogOutput ( $ "[{ processId } ] { e . Data } ") ;
317+ }
301318 }
302319 }
303320
@@ -377,14 +394,17 @@ public void Dispose()
377394 this . LogOutput ( $ "Failed to close connection. Error: { e1 . Message } ") ;
378395 }
379396
380- try
381- {
382- this . FunctionHost ? . Kill ( ) ;
383- this . FunctionHost ? . Dispose ( ) ;
384- }
385- catch ( Exception e2 )
397+ foreach ( Process functionHost in this . FunctionHostList )
386398 {
387- this . LogOutput ( $ "Failed to stop function host, Error: { e2 . Message } ") ;
399+ try
400+ {
401+ functionHost . Kill ( ) ;
402+ functionHost . Dispose ( ) ;
403+ }
404+ catch ( Exception e2 )
405+ {
406+ this . LogOutput ( $ "Failed to stop function host, Error: { e2 . Message } ") ;
407+ }
388408 }
389409
390410 try
0 commit comments