Skip to content

Commit 29aea8a

Browse files
authored
[in-proc backport] backport azurite emulator for tests (#10895)
* Add AzuriteFixture (#10871) * Add AzuriteFixture * Install azurite in CI * Only dispose Azurite process * Kill azurite process * Wait for Azurite to fully exit * kill entire process tree * Enclose bash command in quotes * Use AzuriteFixture in all integration tests (#10878) * Use Azurite in ScriptHostEndToEndTestFixture * Remove AzureWebJobsStorage from CI environment * Address tests missing azurite fixture * Address more tests for azurite emulator * Address WebJobsStartupEndToEndTests * Add Storage connection back to non-e2e tests for now * .Result -> await * Address TcpListener for net6 * Add Azurite to further tests * Stabilize test
1 parent cd17e15 commit 29aea8a

File tree

18 files changed

+361
-128
lines changed

18 files changed

+361
-128
lines changed

WebJobs.Script.sln

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -106,34 +106,34 @@ Global
106106
{766DE3D5-4FB1-4602-9BB5-3779EECC232D}.Debug|Any CPU.Build.0 = Debug|Any CPU
107107
{766DE3D5-4FB1-4602-9BB5-3779EECC232D}.Release|Any CPU.ActiveCfg = Release|Any CPU
108108
{766DE3D5-4FB1-4602-9BB5-3779EECC232D}.Release|Any CPU.Build.0 = Release|Any CPU
109-
{504B98C7-F27F-4C02-9881-33048D4283CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
110-
{504B98C7-F27F-4C02-9881-33048D4283CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
111-
{504B98C7-F27F-4C02-9881-33048D4283CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
112-
{504B98C7-F27F-4C02-9881-33048D4283CF}.Release|Any CPU.Build.0 = Release|Any CPU
113-
{A094FBDA-B6A7-48DA-BF7C-39FA661C1A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
114-
{A094FBDA-B6A7-48DA-BF7C-39FA661C1A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
115-
{A094FBDA-B6A7-48DA-BF7C-39FA661C1A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
116-
{A094FBDA-B6A7-48DA-BF7C-39FA661C1A07}.Release|Any CPU.Build.0 = Release|Any CPU
117-
{9D7556C5-C828-4C19-8EC9-E10030777E48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
118-
{9D7556C5-C828-4C19-8EC9-E10030777E48}.Debug|Any CPU.Build.0 = Debug|Any CPU
119-
{9D7556C5-C828-4C19-8EC9-E10030777E48}.Release|Any CPU.ActiveCfg = Release|Any CPU
120-
{9D7556C5-C828-4C19-8EC9-E10030777E48}.Release|Any CPU.Build.0 = Release|Any CPU
121-
{29167303-5857-4BD5-9DDA-9EEA8C26CACC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122-
{29167303-5857-4BD5-9DDA-9EEA8C26CACC}.Debug|Any CPU.Build.0 = Debug|Any CPU
123-
{29167303-5857-4BD5-9DDA-9EEA8C26CACC}.Release|Any CPU.ActiveCfg = Release|Any CPU
124-
{29167303-5857-4BD5-9DDA-9EEA8C26CACC}.Release|Any CPU.Build.0 = Release|Any CPU
125-
{A7A6A7C2-DD95-4425-916E-9AC629CD4DB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126-
{A7A6A7C2-DD95-4425-916E-9AC629CD4DB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
127-
{A7A6A7C2-DD95-4425-916E-9AC629CD4DB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
128-
{A7A6A7C2-DD95-4425-916E-9AC629CD4DB8}.Release|Any CPU.Build.0 = Release|Any CPU
129-
{C14B6921-A12B-4203-8E3A-34BA4E5ACC98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
130-
{C14B6921-A12B-4203-8E3A-34BA4E5ACC98}.Debug|Any CPU.Build.0 = Debug|Any CPU
131-
{C14B6921-A12B-4203-8E3A-34BA4E5ACC98}.Release|Any CPU.ActiveCfg = Release|Any CPU
132-
{C14B6921-A12B-4203-8E3A-34BA4E5ACC98}.Release|Any CPU.Build.0 = Release|Any CPU
133-
{51E41947-888E-43BE-A1A9-6607AAED7B57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
134-
{51E41947-888E-43BE-A1A9-6607AAED7B57}.Debug|Any CPU.Build.0 = Debug|Any CPU
135-
{51E41947-888E-43BE-A1A9-6607AAED7B57}.Release|Any CPU.ActiveCfg = Release|Any CPU
136-
{51E41947-888E-43BE-A1A9-6607AAED7B57}.Release|Any CPU.Build.0 = Release|Any CPU
109+
{39C73557-F796-4EFC-AE71-CBF0F6524936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
110+
{39C73557-F796-4EFC-AE71-CBF0F6524936}.Debug|Any CPU.Build.0 = Debug|Any CPU
111+
{39C73557-F796-4EFC-AE71-CBF0F6524936}.Release|Any CPU.ActiveCfg = Release|Any CPU
112+
{39C73557-F796-4EFC-AE71-CBF0F6524936}.Release|Any CPU.Build.0 = Release|Any CPU
113+
{C6EF1EBB-0EB2-4A72-A072-F1D5FD1DB7C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
114+
{C6EF1EBB-0EB2-4A72-A072-F1D5FD1DB7C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
115+
{C6EF1EBB-0EB2-4A72-A072-F1D5FD1DB7C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
116+
{C6EF1EBB-0EB2-4A72-A072-F1D5FD1DB7C5}.Release|Any CPU.Build.0 = Release|Any CPU
117+
{702AC603-96E1-44A0-A49C-92A8925BDB65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
118+
{702AC603-96E1-44A0-A49C-92A8925BDB65}.Debug|Any CPU.Build.0 = Debug|Any CPU
119+
{702AC603-96E1-44A0-A49C-92A8925BDB65}.Release|Any CPU.ActiveCfg = Release|Any CPU
120+
{702AC603-96E1-44A0-A49C-92A8925BDB65}.Release|Any CPU.Build.0 = Release|Any CPU
121+
{974054C1-BF68-4D75-8A3C-2DF209974A19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
122+
{974054C1-BF68-4D75-8A3C-2DF209974A19}.Debug|Any CPU.Build.0 = Debug|Any CPU
123+
{974054C1-BF68-4D75-8A3C-2DF209974A19}.Release|Any CPU.ActiveCfg = Release|Any CPU
124+
{974054C1-BF68-4D75-8A3C-2DF209974A19}.Release|Any CPU.Build.0 = Release|Any CPU
125+
{EA092328-80C4-4C85-8212-2B0F13F2EBAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
126+
{EA092328-80C4-4C85-8212-2B0F13F2EBAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
127+
{EA092328-80C4-4C85-8212-2B0F13F2EBAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
128+
{EA092328-80C4-4C85-8212-2B0F13F2EBAF}.Release|Any CPU.Build.0 = Release|Any CPU
129+
{BD05F1CE-5F52-48BC-AE6A-2C160F2AEF44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
130+
{BD05F1CE-5F52-48BC-AE6A-2C160F2AEF44}.Debug|Any CPU.Build.0 = Debug|Any CPU
131+
{BD05F1CE-5F52-48BC-AE6A-2C160F2AEF44}.Release|Any CPU.ActiveCfg = Release|Any CPU
132+
{BD05F1CE-5F52-48BC-AE6A-2C160F2AEF44}.Release|Any CPU.Build.0 = Release|Any CPU
133+
{8926678E-3354-4AA7-9B5E-8165F44AA99A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
134+
{8926678E-3354-4AA7-9B5E-8165F44AA99A}.Debug|Any CPU.Build.0 = Debug|Any CPU
135+
{8926678E-3354-4AA7-9B5E-8165F44AA99A}.Release|Any CPU.ActiveCfg = Release|Any CPU
136+
{8926678E-3354-4AA7-9B5E-8165F44AA99A}.Release|Any CPU.Build.0 = Release|Any CPU
137137
EndGlobalSection
138138
GlobalSection(SolutionProperties) = preSolution
139139
HideSolutionNode = FALSE

eng/ci/templates/official/jobs/run-integration-tests.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ jobs:
44

55
pool:
66
name: 1es-pool-azfunc
7-
image: 1es-windows-2022
7+
image: 1es-windows-2022
88
os: windows
99

1010
variables:
@@ -29,6 +29,10 @@ jobs:
2929
jdkArchitectureOption: x64
3030
jdkSourceOption: PreInstalled
3131

32+
- script: |
33+
npm install -g azurite
34+
displayName: Install Azurite
35+
3236
- task: PowerShell@2
3337
displayName: Install Az.Storage Powershell module
3438
inputs:
@@ -67,8 +71,6 @@ jobs:
6771
inputs:
6872
targetType: inline
6973
script: |
70-
Write-Host "##vso[task.setvariable variable=AzureWebJobsStorage]$env:AzureWebJobsStorageSecretMap"
71-
Write-Host "##vso[task.setvariable variable=AzureWebJobsSeconaryStorage]$env:AzureWebJobsSecondaryStorageSecretMap"
7274
Write-Host "##vso[task.setvariable variable=ConnectionStrings__CosmosDB]$env:CosmosDbSecretMap"
7375
Write-Host "##vso[task.setvariable variable=AzureWebJobsEventHubSender]$env:AzureWebJobsEventHubSenderSecretMap"
7476
Write-Host "##vso[task.setvariable variable=AzureWebJobsEventHubReceiver]$env:AzureWebJobsEventHubReceiverSecretMap"

eng/ci/templates/official/jobs/run-non-e2e-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ jobs:
2323
jdkArchitectureOption: x64
2424
jdkSourceOption: PreInstalled
2525

26+
- script: |
27+
npm install -g azurite
28+
displayName: Install Azurite
29+
2630
- task: PowerShell@2
2731
displayName: Install Az.Storage Powershell module
2832
inputs:

test/CSharpPrecompiledTestProjects/WebJobsStartupTests/Function1.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private static bool ValidateConfig(IConfiguration _config)
104104
{
105105
if (_config is ConfigurationRoot root)
106106
{
107-
if (root.Providers.Count() != 9)
107+
if (root.Providers.Count() != 10)
108108
{
109109
return false;
110110
}
@@ -119,8 +119,9 @@ private static bool ValidateConfig(IConfiguration _config)
119119
root.Providers.ElementAt(i++) is ChainedConfigurationProvider &&
120120
root.Providers.ElementAt(i++) is JsonConfigurationProvider &&
121121
root.Providers.ElementAt(i++) is EnvironmentVariablesConfigurationProvider &&
122-
root.Providers.ElementAt(i++) is MemoryConfigurationProvider && // From Startup.cs
123-
root.Providers.ElementAt(i++) is JsonConfigurationProvider; // From test settings; Always runs last in tests.
122+
root.Providers.ElementAt(i++) is MemoryConfigurationProvider && // From Startup.cs.
123+
root.Providers.ElementAt(i++) is JsonConfigurationProvider && // From test settings.
124+
root.Providers.ElementAt(i++) is MemoryConfigurationProvider; // From fixture injecting emulator settings.
124125
}
125126

126127
return false;
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Net.Sockets;
5+
using System.Net;
6+
using System.Runtime.ExceptionServices;
7+
using System.Threading.Tasks;
8+
using Xunit;
9+
using Xunit.Abstractions;
10+
using System.Threading;
11+
using Xunit.Sdk;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
15+
namespace Microsoft.Azure.WebJobs.Script.Tests.Integration.Fixtures
16+
{
17+
public class AzuriteFixture(IMessageSink sink = null) : IAsyncLifetime
18+
{
19+
public const string HostName = "127.0.0.1";
20+
21+
public const string AccountName = "devstoreaccount1";
22+
23+
[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification = "Well known account key for emulator. Used for testing.")]
24+
public const string AccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
25+
26+
private int _blobPort;
27+
private int _queuePort;
28+
private int _tablePort;
29+
30+
private Process _process;
31+
private ExceptionDispatchInfo _exceptionDispatchInfo;
32+
33+
public string GetConnectionString()
34+
{
35+
VerifyInitialized();
36+
IEnumerable<(string Key, string Value)> properties =
37+
[
38+
("DefaultEndpointsProtocol", Uri.UriSchemeHttps),
39+
("AccountName", AccountName),
40+
("AccountKey", AccountKey),
41+
("BlobEndpoint", GetBlobEndpoint()),
42+
("QueueEndpoint", GetQueueEndpoint()),
43+
("TableEndpoint", GetTableEndpoint()),
44+
];
45+
46+
return string.Join(";", properties.Select(p => $"{p.Key}={p.Value}"));
47+
}
48+
49+
/// <summary>
50+
/// Gets the blob endpoint
51+
/// </summary>
52+
/// <returns>The Azurite blob endpoint</returns>
53+
public string GetBlobEndpoint()
54+
{
55+
VerifyInitialized();
56+
return new UriBuilder(Uri.UriSchemeHttp, HostName, _blobPort, AccountName).ToString();
57+
}
58+
59+
/// <summary>
60+
/// Gets the queue endpoint
61+
/// </summary>
62+
/// <returns>The Azurite queue endpoint</returns>
63+
public string GetQueueEndpoint()
64+
{
65+
VerifyInitialized();
66+
return new UriBuilder(Uri.UriSchemeHttp, HostName, _queuePort, AccountName).ToString();
67+
}
68+
69+
/// <summary>
70+
/// Gets the table endpoint
71+
/// </summary>
72+
/// <returns>The Azurite table endpoint</returns>
73+
public string GetTableEndpoint()
74+
{
75+
VerifyInitialized();
76+
return new UriBuilder(Uri.UriSchemeHttp, HostName, _tablePort, AccountName).ToString();
77+
}
78+
79+
public async Task InitializeAsync()
80+
{
81+
try
82+
{
83+
await StartAzuriteAsync();
84+
}
85+
catch (Exception ex)
86+
{
87+
_exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex);
88+
}
89+
}
90+
91+
public async Task DisposeAsync()
92+
{
93+
if (Interlocked.Exchange(ref _process, null) is { } p)
94+
{
95+
if (!p.HasExited)
96+
{
97+
p.Kill(entireProcessTree: true);
98+
}
99+
100+
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(5));
101+
await p.WaitForExitAsync(cts.Token);
102+
103+
p.Dispose();
104+
}
105+
}
106+
107+
private static int GetFreeTcpPort()
108+
{
109+
TcpListener listener = new(IPAddress.Loopback, 0);
110+
try
111+
{
112+
listener.Start();
113+
return ((IPEndPoint)listener.LocalEndpoint).Port;
114+
}
115+
finally
116+
{
117+
listener.Stop();
118+
119+
#if NET8_0_OR_GREATER
120+
listener.Dispose();
121+
#endif
122+
}
123+
}
124+
125+
private Task StartAzuriteAsync()
126+
{
127+
_blobPort = GetFreeTcpPort();
128+
_queuePort = GetFreeTcpPort();
129+
_tablePort = GetFreeTcpPort();
130+
GetAzuriteCommand(out string process, out string arguments);
131+
ProcessStartInfo startInfo = new()
132+
{
133+
FileName = process,
134+
Arguments = arguments,
135+
UseShellExecute = false, // we need stdio, cannot set to true.
136+
RedirectStandardOutput = true,
137+
RedirectStandardError = true,
138+
CreateNoWindow = true,
139+
ErrorDialog = false,
140+
};
141+
142+
// We consider this startup complete when the first message is written.
143+
// stdout: successfully started.
144+
// stderr: failed to start.
145+
TaskCompletionSource tcs = new();
146+
_process = new() { StartInfo = startInfo };
147+
_process.ErrorDataReceived += (_, e) => OnError(e, tcs);
148+
_process.OutputDataReceived += (_, e) => OnMessage(e, tcs);
149+
_process.Start();
150+
_process.BeginErrorReadLine();
151+
_process.BeginOutputReadLine();
152+
return tcs.Task;
153+
}
154+
155+
private void GetAzuriteCommand(out string process, out string arguments)
156+
{
157+
// Azurite is not an executable itself, but a node module. However, it installs helper scripts on the path.
158+
// We will use cmd or bash to run the helper script.
159+
string azurite = $"azurite --silent --inMemoryPersistence --blobPort {_blobPort} --queuePort {_queuePort} --tablePort {_tablePort}";
160+
if (OperatingSystem.IsWindows())
161+
{
162+
process = "cmd";
163+
arguments = $"/C {azurite}";
164+
}
165+
else
166+
{
167+
process = "bash";
168+
arguments = $"-c \"{azurite}\"";
169+
}
170+
}
171+
172+
private void OnMessage(DataReceivedEventArgs e, TaskCompletionSource tcs)
173+
{
174+
if (e.Data is not null)
175+
{
176+
tcs.TrySetResult();
177+
sink?.OnMessage(new DiagnosticMessage($"[azurite] {e.Data}"));
178+
}
179+
}
180+
181+
private void OnError(DataReceivedEventArgs e, TaskCompletionSource tcs)
182+
{
183+
if (e.Data is null)
184+
{
185+
return;
186+
}
187+
188+
sink?.OnMessage(new DiagnosticMessage($"[error][azurite] {e.Data}"));
189+
try
190+
{
191+
throw new InvalidOperationException(e.Data);
192+
}
193+
catch (Exception ex)
194+
{
195+
tcs.TrySetException(ex);
196+
}
197+
}
198+
199+
private void VerifyInitialized()
200+
{
201+
if (_process is null)
202+
{
203+
throw new InvalidOperationException("AzuriteFixture is not initialized. Call InitializeAsync() before using this fixture.");
204+
}
205+
206+
_exceptionDispatchInfo?.Throw();
207+
}
208+
}
209+
}

test/WebJobs.Script.Tests.Integration/Host/StandbyManager/StandbyManagerE2ETestBase.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Microsoft.Azure.WebJobs.Script.Diagnostics;
1818
using Microsoft.Azure.WebJobs.Script.WebHost;
1919
using Microsoft.Azure.WebJobs.Script.WebHost.Configuration;
20+
using Microsoft.Extensions.Configuration;
2021
using Microsoft.Extensions.DependencyInjection;
2122
using Microsoft.Extensions.Logging;
2223
using Microsoft.WebJobs.Script.Tests;
@@ -46,6 +47,10 @@ public StandbyManagerE2ETestBase()
4647
StandbyManager.ResetChangeToken();
4748
}
4849

50+
protected virtual void ConfigureScriptHostConfiguration(IConfigurationBuilder builder)
51+
{
52+
}
53+
4954
protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirName, IEnvironment environment, string websiteSiteName = TestSiteName)
5055
{
5156
var httpConfig = new HttpConfiguration();
@@ -110,7 +115,8 @@ protected async Task<IWebHostBuilder> CreateWebHostBuilderAsync(string testDirNa
110115
// tests based on CPU limits being hit resulting in 429 responses
111116
o.DynamicThrottlesEnabled = false;
112117
});
113-
});
118+
})
119+
.ConfigureScriptHostAppConfiguration(ConfigureScriptHostConfiguration);
114120

115121
return webHostBuilder;
116122
}

0 commit comments

Comments
 (0)