Skip to content

Commit 28080aa

Browse files
authored
Add test for multiple function hosts (#344)
1 parent de73eed commit 28080aa

File tree

3 files changed

+132
-70
lines changed

3 files changed

+132
-70
lines changed

performance/SqlTriggerBindingPerformance.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ await this.WaitForProductChanges(
3232
() => { this.InsertProducts(1, count); return Task.CompletedTask; },
3333
id => $"Product {id}",
3434
id => id * 100,
35-
GetBatchProcessingTimeout(1, count));
35+
this.GetBatchProcessingTimeout(1, count));
3636
}
3737

3838
[IterationCleanup]

test/Integration/IntegrationTestBase.cs

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
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;
64
using System;
5+
using System.Collections.Generic;
76
using System.Data.Common;
87
using System.Diagnostics;
98
using System.IO;
9+
using System.Linq;
1010
using System.Net.Http;
1111
using System.Reflection;
1212
using System.Runtime.InteropServices;
1313
using System.Text;
1414
using 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;
1519
using Xunit;
1620
using 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+
2122
using static Microsoft.Azure.WebJobs.Extensions.Sql.Telemetry.Telemetry;
2223

2324
namespace 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

Comments
 (0)