diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs index 72f07573a1..a826f70447 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/Common/MultipartIdentifierTests.cs @@ -26,6 +26,7 @@ public static TheoryData ValidSinglePartIdentifierVariations { ReadOnlySpan part1Words = ["word1", "word 1"]; TheoryData data = []; + HashSet seen = []; // Combination 1: embedded and non-embedded whitespace. // Combination 2: leading and/or trailing whitespace, and no whitespace @@ -36,6 +37,13 @@ public static TheoryData ValidSinglePartIdentifierVariations { foreach ((string p1Combination, string p1Expected) in GeneratePartCombinations(part1)) { + // Skip duplicates — different generation paths can produce + // identical (input, expected) pairs, which xUnit rejects. + if (!seen.Add(p1Combination)) + { + continue; + } + string onePartCombination = p1Combination; string[] onePartExpected = [p1Expected]; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs index cf97f21a39..0697ea6376 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs @@ -63,7 +63,7 @@ public void ByteArrayToSqlBinary_NullInput() }; [Theory] - [MemberData(nameof(SqlDecimalWriteTdsValue_NonNullInput_Data))] + [MemberData(nameof(SqlDecimalWriteTdsValue_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlDecimalWriteTdsValue_NonNullInput(SqlDecimal input) { // Arrange @@ -155,7 +155,7 @@ public void ByteArrayToSqlGuid_ValidInput(byte[] input) }; [Theory] - [MemberData(nameof(LongToSqlMoney_Data))] + [MemberData(nameof(LongToSqlMoney_Data), DisableDiscoveryEnumeration = true)] public void LongToSqlMoney(long input, SqlMoney expected) { // Act @@ -176,7 +176,7 @@ public void LongToSqlMoney(long input, SqlMoney expected) }; [Theory] - [MemberData(nameof(SqlMoneyToLong_NonNullInput_Data))] + [MemberData(nameof(SqlMoneyToLong_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlMoneyToLong_NonNullInput(SqlMoney input, long expected) { // Act diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index d23fc9c532..7c8e67a09a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -11,7 +11,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionFailoverTests { @@ -70,7 +69,7 @@ public void TransientFault_NoFailover_DoesNotClearPool(uint errorCode) Assert.Equal($"localhost,{initialServer.EndPoint.Port}", secondConnection.DataSource); // 1 for the initial connection, 2 for the second connection - Assert.Equal(3, initialServer.PreLoginCount); + Assert.Equal(3, initialServer.PreLoginCount - initialServer.AbandonedPreLoginCount); // A failover should not be triggered, so prelogin count to the failover server should be 0 Assert.Equal(0, failoverServer.PreLoginCount); } @@ -219,6 +218,7 @@ public void NetworkDelay_ShouldConnectToPrimary() InitialCatalog = "master", // Required for failover partner to work ConnectTimeout = 5, Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made MultiSubnetFailover = false, #if NETFRAMEWORK TransparentNetworkIPResolution = false, @@ -268,6 +268,7 @@ public void NetworkError_WithUserProvidedPartner_RetryDisabled_ShouldConnectToFa ConnectRetryCount = 0, // Disable retry FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", // User provided failover partner Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made on failover }; using SqlConnection connection = new(builder.ConnectionString); @@ -313,6 +314,9 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai ConnectRetryInterval = 1, FailoverPartner = $"localhost,{failoverServer.EndPoint.Port}", // User provided failover partner Encrypt = false, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false, +#endif }; using SqlConnection connection = new(builder.ConnectionString); // Act @@ -324,7 +328,8 @@ public void NetworkError_WithUserProvidedPartner_RetryEnabled_ShouldConnectToFai Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", connection.DataSource); Assert.Equal(1, server.PreLoginCount); - Assert.Equal(1, failoverServer.PreLoginCount); + Assert.Equal(0, server.Login7Count); + Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } [Theory] @@ -357,7 +362,8 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) InitialCatalog = "master", ConnectTimeout = 30, ConnectRetryInterval = 1, - Encrypt = false + Encrypt = false, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made }; using SqlConnection connection = new(builder.ConnectionString); @@ -369,7 +375,7 @@ public void TransientFault_ShouldConnectToPrimary(uint errorCode) Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] @@ -454,7 +460,7 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e FailoverPartner = $"localhost:{failoverServer.EndPoint.Port}", // User provided failover partner }; using SqlConnection connection = new(builder.ConnectionString); - + // Act connection.Open(); @@ -463,7 +469,7 @@ public void TransientFault_WithUserProvidedPartner_ShouldConnectToPrimary(uint e Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] @@ -559,11 +565,14 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // Close the connection to return it to the pool connection.Close(); - // Act // Dispose of the server to trigger a failover server.Dispose(); + // Clear the pool to ensure the next connection attempt doesn't reuse + // the pooled connection to the now-disposed primary server. + SqlConnection.ClearAllPools(); + // Opening a new connection will use the failover partner stored in the pool group. // This will fail if the server provided failover partner was stored to the pool group. using SqlConnection failoverConnection = new(builder.ConnectionString); @@ -573,9 +582,9 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs Assert.Equal(ConnectionState.Open, failoverConnection.State); Assert.Equal($"localhost,{failoverServer.EndPoint.Port}", failoverConnection.DataSource); // 1 for the initial connection - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); // 1 for the failover connection - Assert.Equal(1, failoverServer.PreLoginCount); + Assert.Equal(1, failoverServer.PreLoginCount - failoverServer.AbandonedPreLoginCount); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index 108118dda7..b763f2b55c 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionRoutingTests { @@ -58,8 +57,8 @@ public void TransientFaultAtRoutedLocation_ShouldReturnToGateway(uint errorCode) Assert.Equal($"localhost,{router.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, router.PreLoginCount); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, router.PreLoginCount - router.AbandonedPreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs index dd945e37f3..84c3452826 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTestsAzure.cs @@ -10,7 +10,6 @@ namespace Microsoft.Data.SqlClient.UnitTests.SimulatedServerTests { - [Trait("Category", "flaky")] [Collection("SimulatedServerTests")] public class ConnectionRoutingTestsAzure : IDisposable { @@ -76,8 +75,8 @@ public void TransientFaultAtRoutedLocation_ShouldReturnToGateway(uint errorCode) Assert.Equal($"localhost,{router.EndPoint.Port}", connection.DataSource); // Failures should prompt the client to return to the original server, resulting in a login count of 2 - Assert.Equal(2, router.PreLoginCount); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, router.PreLoginCount - router.AbandonedPreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } [Theory] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index a729e6afe2..21a6cb65de 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -29,7 +29,8 @@ public void ConnectionTest() { using TdsServer server = new(new TdsServerArguments() { }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, }.ConnectionString; @@ -43,7 +44,8 @@ public void IntegratedAuthConnectionTest() { using TdsServer server = new(new TdsServerArguments() { }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", Encrypt = SqlConnectionEncryptOption.Optional, }.ConnectionString; @@ -61,9 +63,10 @@ public void IntegratedAuthConnectionTest() [Fact] public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() { - using TdsServer server = new(new TdsServerArguments() {Encryption = TDSPreLoginTokenEncryptionType.None }); + using TdsServer server = new(new TdsServerArguments() { Encryption = TDSPreLoginTokenEncryptionType.None }); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}" }.ConnectionString; @@ -72,7 +75,6 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() Assert.Contains("The instance of SQL Server you attempted to connect to does not support encryption.", ex.Message, StringComparison.OrdinalIgnoreCase); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -80,26 +82,28 @@ public async Task RequestEncryption_ServerDoesNotSupportEncryption_ShouldFail() public async Task TransientFault_RetryEnabled_ShouldSucceed_Async(uint errorCode) { using TransientTdsErrorTdsServer server = new( - new TransientTdsErrorTdsServerArguments() + new TransientTdsErrorTdsServerArguments() { - IsEnabledTransientError = true, - Number = errorCode, + IsEnabledTransientError = true, + Number = errorCode, }); server.Start(); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.EndPoint.Port, - Encrypt = SqlConnectionEncryptOption.Optional + Encrypt = SqlConnectionEncryptOption.Optional, +#if NETFRAMEWORK + TransparentNetworkIPResolution = false +#endif }; using SqlConnection connection = new(builder.ConnectionString); await connection.OpenAsync(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -123,10 +127,9 @@ public void TransientFault_RetryEnabled_ShouldSucceed(uint errorCode) connection.Open(); Assert.Equal(ConnectionState.Open, connection.State); Assert.Equal($"localhost,{server.EndPoint.Port}", connection.DataSource); - Assert.Equal(2, server.PreLoginCount); + Assert.Equal(2, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -151,10 +154,9 @@ public async Task TransientFault_RetryDisabled_ShouldFail_Async(uint errorCode) SqlException e = await Assert.ThrowsAsync(async () => await connection.OpenAsync()); Assert.Equal((int)errorCode, e.Number); Assert.Equal(ConnectionState.Closed, connection.State); - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -179,10 +181,9 @@ public void TransientFault_RetryDisabled_ShouldFail(uint errorCode) SqlException e = Assert.Throws(() => connection.Open()); Assert.Equal((int)errorCode, e.Number); Assert.Equal(ConnectionState.Closed, connection.State); - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } - [Trait("Category", "flaky")] [Theory] [InlineData(false)] [InlineData(true)] @@ -200,6 +201,7 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet DataSource = "localhost," + server.EndPoint.Port, Encrypt = SqlConnectionEncryptOption.Optional, ConnectTimeout = 5, + Pooling = false, // Disable pooling to ensure a fresh connection attempt is made MultiSubnetFailover = multiSubnetFailoverEnabled, #if NETFRAMEWORK TransparentNetworkIPResolution = multiSubnetFailoverEnabled @@ -216,11 +218,10 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -261,11 +262,10 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -306,7 +306,7 @@ public void NetworkDelay_RetryDisabled(bool multiSubnetFailoverEnabled) } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.PreLoginCount - server.AbandonedPreLoginCount); } } @@ -467,7 +467,8 @@ public void ConnectionTimeoutTest(int timeout) //TODO: do we even need a server for this test? using TdsServer server = new(); server.Start(); - var connStr = new SqlConnectionStringBuilder() { + var connStr = new SqlConnectionStringBuilder() + { DataSource = $"localhost,{server.EndPoint.Port}", ConnectTimeout = timeout, Encrypt = SqlConnectionEncryptOption.Optional diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs index d2c81e6ca1..1eb8bdf8c6 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTdsServer.cs @@ -95,6 +95,11 @@ public delegate void OnAuthenticationCompletedDelegate( /// private int _preLoginCount = 0; + /// + /// Counts Login7 requests to the server. + /// + protected int _login7Count = 0; + private TDSServerEndPoint _endpoint; /// @@ -137,6 +142,18 @@ public GenericTdsServer(T arguments, QueryEngine queryEngine) /// public int PreLoginCount => _preLoginCount; + /// + /// Counts Login7 requests to the server. + /// + public int Login7Count => _login7Count; + + /// + /// Counts pre-login requests that did not result in a Login7 request, + /// which indicates the client abandoned the connection (e.g. interval + /// timer timeout during TNIR or failover). + /// + public int AbandonedPreLoginCount => _preLoginCount - _login7Count; + public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; } public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; } @@ -148,9 +165,12 @@ public void Start([CallerMemberName] string methodName = "") { throw new InvalidOperationException("Server is already started"); } - _endpoint = new TDSServerEndPoint(this) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; - _endpoint.EndpointName = methodName; - _endpoint.EventLog = Arguments.Log; + _endpoint = new TDSServerEndPoint(this) + { + ServerEndPoint = new IPEndPoint(IPAddress.Any, 0), + EndpointName = methodName, + EventLog = Arguments.Log + }; _endpoint.Start(); } @@ -245,6 +265,8 @@ public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, /// public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) { + Interlocked.Increment(ref _login7Count); + // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs index ecd89f5812..6ec7ac2528 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientTdsErrorTdsServer.cs @@ -60,6 +60,8 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, // Check if we're still going to raise transient error if (Arguments.IsEnabledTransientError && RequestCounter < Arguments.RepeatCount) { + // Increment Login7 count since we won't call base.OnLogin7Request + Interlocked.Increment(ref _login7Count); return GenerateErrorMessage(request); }