Skip to content

Commit 3795144

Browse files
edwardnealmdaigle
andauthored
Tests | Address additional flaky tests (#4305)
* Update LocalAppContextSwitchesTest to use RAII helper This prevents checks of the default AppContext switch values which would run in parallel to other tests which modify these values via the RAII helper. * Address duplicate AKV key names * Swap System.Random with the same RNG which SqlClient uses. * Make a fixed number of attempts to reattempt key creation if a key with the generated name already exists. * Remove hardcoded stored procedure name * Accept either a SqlException or an InvalidOperationException to indicate operation cancellation * Update assertion for UseLegacyFailoverAlternationOnLoginSqlErrors * Correct test failures LocalAppContextSwitchesHelper was returning the field values, skipping the default values exposed by LocalAppContextSwitches when the AppContext switch had not been set one way or another. * Modify AppContext switch helper to include UseOverallConnectTimeoutForPoolWait --------- Co-authored-by: Malcolm Daigle <mdaigle@microsoft.com> Co-authored-by: Malcolm Daigle <daiglemalcolm@gmail.com>
1 parent 56d2b56 commit 3795144

5 files changed

Lines changed: 130 additions & 114 deletions

File tree

src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Security.Cryptography;
56
using Azure.Core;
67
using Azure.Security.KeyVault.Keys;
78

@@ -18,20 +19,41 @@ namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures;
1819
public abstract class AzureKeyVaultKeyFixtureBase : IDisposable
1920
{
2021
private readonly KeyClient _keyClient;
21-
private readonly Random _randomGenerator;
22+
private readonly RandomNumberGenerator _randomGenerator;
2223

2324
private readonly List<KeyVaultKey> _createdKeys = new List<KeyVaultKey>();
2425

2526
protected AzureKeyVaultKeyFixtureBase(Uri keyVaultUri, TokenCredential keyVaultToken)
2627
{
2728
_keyClient = new KeyClient(keyVaultUri, keyVaultToken);
28-
_randomGenerator = new Random();
29+
_randomGenerator = RandomNumberGenerator.Create();
2930
}
3031

3132
protected Uri CreateKey(string name, int keySize)
3233
{
33-
CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize };
34-
KeyVaultKey created = _keyClient.CreateRsaKey(createOptions);
34+
const int MaxConflictResolutions = 5;
35+
KeyVaultKey created;
36+
int i = 0;
37+
38+
while (true)
39+
{
40+
CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize };
41+
42+
try
43+
{
44+
created = _keyClient.CreateRsaKey(createOptions);
45+
break;
46+
}
47+
// It's possible for a key to already exist with the same name, even in a deleted state. If so, CreateRsaKey
48+
// will throw an exception with HTTP status code 409 (Conflict.)
49+
// We can't assume we possess permissions to purge or to recover the key, so regenerate the name and try again.
50+
// Only make MaxConflictResolutions attempts, to avoid possible infinite loops.
51+
catch (Azure.RequestFailedException conflictException)
52+
when (conflictException.Status == 409 && i < MaxConflictResolutions)
53+
{
54+
i++;
55+
}
56+
}
3557

3658
_createdKeys.Add(created);
3759
return created.Id;
@@ -41,7 +63,7 @@ private string GenerateUniqueName(string name)
4163
{
4264
byte[] rndBytes = new byte[16];
4365

44-
_randomGenerator.NextBytes(rndBytes);
66+
_randomGenerator.GetBytes(rndBytes);
4567
return name + "-" + BitConverter.ToString(rndBytes);
4668
}
4769

@@ -64,5 +86,7 @@ protected virtual void Dispose(bool disposing)
6486
continue;
6587
}
6688
}
89+
90+
_randomGenerator.Dispose();
6791
}
6892
}

src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ public void Dispose()
214214

215215
#region Switch Value Getters and Setters
216216

217-
// These properties get or set the like-named underlying switch field value.
217+
// These properties get the like-named underlying switch *property* value and set the underlying
218+
// switch *field* value. This allows tests to verify the default switch values.
218219
//
219220
// They all throw if the value cannot be retrieved or set.
220221

@@ -224,7 +225,7 @@ public void Dispose()
224225
/// </summary>
225226
public bool? DisableTnirByDefault
226227
{
227-
get => GetSwitchValue("s_disableTnirByDefault");
228+
get => GetSwitchPropertyValue(nameof(DisableTnirByDefault));
228229
set => SetSwitchValue("s_disableTnirByDefault", value);
229230
}
230231
#endif
@@ -234,7 +235,7 @@ public bool? DisableTnirByDefault
234235
/// </summary>
235236
public bool? EnableMultiSubnetFailoverByDefault
236237
{
237-
get => GetSwitchValue("s_enableMultiSubnetFailoverByDefault");
238+
get => GetSwitchPropertyValue(nameof(EnableMultiSubnetFailoverByDefault));
238239
set => SetSwitchValue("s_enableMultiSubnetFailoverByDefault", value);
239240
}
240241

@@ -244,7 +245,7 @@ public bool? EnableMultiSubnetFailoverByDefault
244245
/// </summary>
245246
public bool? GlobalizationInvariantMode
246247
{
247-
get => GetSwitchValue("s_globalizationInvariantMode");
248+
get => GetSwitchPropertyValue(nameof(GlobalizationInvariantMode));
248249
set => SetSwitchValue("s_globalizationInvariantMode", value);
249250
}
250251
#endif
@@ -254,7 +255,7 @@ public bool? GlobalizationInvariantMode
254255
/// </summary>
255256
public bool? IgnoreServerProvidedFailoverPartner
256257
{
257-
get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner");
258+
get => GetSwitchPropertyValue(nameof(IgnoreServerProvidedFailoverPartner));
258259
set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value);
259260
}
260261

@@ -263,7 +264,7 @@ public bool? IgnoreServerProvidedFailoverPartner
263264
/// </summary>
264265
public bool? UseLegacyFailoverAlternationOnLoginSqlErrors
265266
{
266-
get => GetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors");
267+
get => GetSwitchPropertyValue(nameof(UseLegacyFailoverAlternationOnLoginSqlErrors));
267268
set => SetSwitchValue("s_useLegacyFailoverAlternationOnLoginSqlErrors", value);
268269
}
269270

@@ -272,7 +273,7 @@ public bool? UseLegacyFailoverAlternationOnLoginSqlErrors
272273
/// </summary>
273274
public bool? LegacyRowVersionNullBehavior
274275
{
275-
get => GetSwitchValue("s_legacyRowVersionNullBehavior");
276+
get => GetSwitchPropertyValue(nameof(LegacyRowVersionNullBehavior));
276277
set => SetSwitchValue("s_legacyRowVersionNullBehavior", value);
277278
}
278279

@@ -281,7 +282,7 @@ public bool? LegacyRowVersionNullBehavior
281282
/// </summary>
282283
public bool? LegacyVarTimeZeroScaleBehaviour
283284
{
284-
get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour");
285+
get => GetSwitchPropertyValue(nameof(LegacyVarTimeZeroScaleBehaviour));
285286
set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value);
286287
}
287288

@@ -290,7 +291,7 @@ public bool? LegacyVarTimeZeroScaleBehaviour
290291
/// </summary>
291292
public bool? MakeReadAsyncBlocking
292293
{
293-
get => GetSwitchValue("s_makeReadAsyncBlocking");
294+
get => GetSwitchPropertyValue(nameof(MakeReadAsyncBlocking));
294295
set => SetSwitchValue("s_makeReadAsyncBlocking", value);
295296
}
296297

@@ -299,7 +300,7 @@ public bool? MakeReadAsyncBlocking
299300
/// </summary>
300301
public bool? SuppressInsecureTlsWarning
301302
{
302-
get => GetSwitchValue("s_suppressInsecureTlsWarning");
303+
get => GetSwitchPropertyValue(nameof(SuppressInsecureTlsWarning));
303304
set => SetSwitchValue("s_suppressInsecureTlsWarning", value);
304305
}
305306

@@ -308,7 +309,7 @@ public bool? SuppressInsecureTlsWarning
308309
/// </summary>
309310
public bool? TruncateScaledDecimal
310311
{
311-
get => GetSwitchValue("s_truncateScaledDecimal");
312+
get => GetSwitchPropertyValue(nameof(TruncateScaledDecimal));
312313
set => SetSwitchValue("s_truncateScaledDecimal", value);
313314
}
314315

@@ -317,7 +318,7 @@ public bool? TruncateScaledDecimal
317318
/// </summary>
318319
public bool? UseCompatibilityAsyncBehaviour
319320
{
320-
get => GetSwitchValue("s_useCompatibilityAsyncBehaviour");
321+
get => GetSwitchPropertyValue(nameof(UseCompatibilityAsyncBehaviour));
321322
set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value);
322323
}
323324

@@ -326,7 +327,7 @@ public bool? UseCompatibilityAsyncBehaviour
326327
/// </summary>
327328
public bool? UseCompatibilityProcessSni
328329
{
329-
get => GetSwitchValue("s_useCompatibilityProcessSni");
330+
get => GetSwitchPropertyValue(nameof(UseCompatibilityProcessSni));
330331
set => SetSwitchValue("s_useCompatibilityProcessSni", value);
331332
}
332333

@@ -335,7 +336,7 @@ public bool? UseCompatibilityProcessSni
335336
/// </summary>
336337
public bool? UseConnectionPoolV2
337338
{
338-
get => GetSwitchValue("s_useConnectionPoolV2");
339+
get => GetSwitchPropertyValue(nameof(UseConnectionPoolV2));
339340
set => SetSwitchValue("s_useConnectionPoolV2", value);
340341
}
341342

@@ -344,7 +345,7 @@ public bool? UseConnectionPoolV2
344345
/// </summary>
345346
public bool? UseLegacyIdleTimeoutBehavior
346347
{
347-
get => GetSwitchValue("s_useLegacyIdleTimeoutBehavior");
348+
get => GetSwitchPropertyValue(nameof(UseLegacyIdleTimeoutBehavior));
348349
set => SetSwitchValue("s_useLegacyIdleTimeoutBehavior", value);
349350
}
350351

@@ -353,7 +354,7 @@ public bool? UseLegacyIdleTimeoutBehavior
353354
/// </summary>
354355
public bool? UseOverallConnectTimeoutForPoolWait
355356
{
356-
get => GetSwitchValue("s_useOverallConnectTimeoutForPoolWait");
357+
get => GetSwitchPropertyValue(nameof(UseOverallConnectTimeoutForPoolWait));
357358
set => SetSwitchValue("s_useOverallConnectTimeoutForPoolWait", value);
358359
}
359360

@@ -363,7 +364,7 @@ public bool? UseOverallConnectTimeoutForPoolWait
363364
/// </summary>
364365
public bool? UseManagedNetworking
365366
{
366-
get => GetSwitchValue("s_useManagedNetworking");
367+
get => GetSwitchPropertyValue(nameof(UseManagedNetworking));
367368
set => SetSwitchValue("s_useManagedNetworking", value);
368369
}
369370
#endif
@@ -373,7 +374,7 @@ public bool? UseManagedNetworking
373374
/// </summary>
374375
public bool? UseMinimumLoginTimeout
375376
{
376-
get => GetSwitchValue("s_useMinimumLoginTimeout");
377+
get => GetSwitchPropertyValue(nameof(UseMinimumLoginTimeout));
377378
set => SetSwitchValue("s_useMinimumLoginTimeout", value);
378379
}
379380

@@ -386,19 +387,7 @@ public bool? UseMinimumLoginTimeout
386387
/// </summary>
387388
private static bool? GetSwitchValue(string fieldName)
388389
{
389-
var assembly = Assembly.GetAssembly(typeof(SqlConnection));
390-
if (assembly is null)
391-
{
392-
throw new InvalidOperationException(
393-
"Could not get assembly for Microsoft.Data.SqlClient");
394-
}
395-
396-
var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
397-
if (type is null)
398-
{
399-
throw new InvalidOperationException(
400-
"Could not get type LocalAppContextSwitches");
401-
}
390+
var type = GetLocalAppContextSwitchesType();
402391

403392
var field = type.GetField(
404393
fieldName,
@@ -433,19 +422,7 @@ public bool? UseMinimumLoginTimeout
433422
/// </summary>
434423
private static void SetSwitchValue(string fieldName, bool? value)
435424
{
436-
var assembly = Assembly.GetAssembly(typeof(SqlConnection));
437-
if (assembly is null)
438-
{
439-
throw new InvalidOperationException(
440-
"Could not get assembly for Microsoft.Data.SqlClient");
441-
}
442-
443-
var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
444-
if (type is null)
445-
{
446-
throw new InvalidOperationException(
447-
"Could not get type LocalAppContextSwitches");
448-
}
425+
var type = GetLocalAppContextSwitchesType();
449426

450427
var field = type.GetField(
451428
fieldName,
@@ -470,5 +447,49 @@ private static void SetSwitchValue(string fieldName, bool? value)
470447
field.SetValue(null, Enum.ToObject(field.FieldType, byteValue));
471448
}
472449

450+
/// <summary>
451+
/// Use reflection to get a switch property value from LocalAppContextSwitches.
452+
/// </summary>
453+
/// <remarks>
454+
/// Each property in LocalAppContextSwitchHelper corresponds to a like-named property in
455+
/// LocalAppContextSwitches, which may return a different value when the AppContext switch
456+
/// has not been set.
457+
/// </remarks>
458+
private static bool GetSwitchPropertyValue(string propertyName)
459+
{
460+
var type = GetLocalAppContextSwitchesType();
461+
var property = type.GetProperty(
462+
propertyName,
463+
BindingFlags.Static | BindingFlags.Public);
464+
465+
if (property == null)
466+
{
467+
throw new InvalidOperationException(
468+
$"Property '{propertyName}' not found in LocalAppContextSwitches");
469+
}
470+
471+
object? value = property.GetValue(null);
472+
473+
return value is bool boolValue
474+
? boolValue
475+
: throw new InvalidOperationException($"Property '{propertyName}' is not of type bool.");
476+
}
477+
478+
private static Type GetLocalAppContextSwitchesType()
479+
{
480+
var assembly = Assembly.GetAssembly(typeof(SqlConnection));
481+
if (assembly is null)
482+
{
483+
throw new InvalidOperationException("Could not get assembly for Microsoft.Data.SqlClient");
484+
}
485+
486+
var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches");
487+
if (type is null)
488+
{
489+
throw new InvalidOperationException("Could not get type LocalAppContextSwitches");
490+
}
491+
return type;
492+
}
493+
473494
#endregion
474495
}

0 commit comments

Comments
 (0)