diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml index 289a113729..96175b87bd 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -4,8 +4,8 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# This step installs the latest SQL Server 2022 onto the macOS host and -# configures it for use. +# This step installs the latest SQL Server 2022 onto the macOS host as a docker +# container and configures it for use. parameters: - name: password @@ -21,39 +21,55 @@ steps: - bash: | # The "user" pipeline variable conflicts with homebrew, causing errors during install. Set it back to the pipeline user. USER=`whoami` + SQLCMD_ERRORS=$(Agent.TempDirectory)/sqlcmd_err.log - echo $SQLCMD_ERRORS + echo "Errors will be written to: $SQLCMD_ERRORS" + + # Configure the prompt to show the current timestamp so we can see how long each command takes. + export PS4='+ [$(date "+%Y-%m-%d %H:%M:%S")] ' + set -x + # Install Docker CLI (not Desktop -- Colima provides the daemon) and SQLCMD tools. brew install colima - brew install --cask docker + brew install docker brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release brew update HOMEBREW_ACCEPT_EULA=Y brew install mssql-tools18 - colima start --arch x86_64 + + # Start Colima with Virtualization.framework for x86_64 binary translation + # on Apple Silicon. Rosetta/binfmt emulation is enabled by default in + # Colima >= 0.8 when using --vm-type vz, which is dramatically faster than + # --arch x86_64 (full QEMU VM emulation). + # Requires macOS >= 13 (Ventura). + colima start --vm-type vz --cpu 4 --memory 4 docker --version - docker pull mcr.microsoft.com/mssql/server:2022-latest - + docker pull --platform linux/amd64 mcr.microsoft.com/mssql/server:2022-latest + + # Disable xtrace before any command that handles the SA password so it is + # not echoed (expanded) into pipeline logs. + { set +x; } 2>/dev/null + # Password for the SA user (required) - MSSQL_SA_PW=${{ parameters.password }} + MSSQL_SA_PW="${{ parameters.password }}" + + docker run --platform linux/amd64 -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$MSSQL_SA_PW" -p 1433:1433 -p 1434:1434 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2022-latest - docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$MSSQL_SA_PW" -p 1433:1433 -p 1434:1434 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2022-latest - - sleep 5 + sleep 10 docker ps -a # Connect to the SQL Server container and get its version. # - # It can take a while for the docker container to start listening and be - # ready for connections, so we will wait for up to 2 minutes, checking every - # 3 seconds. - - # Wait 3 seconds between attempts. - delay=3 - - # Try up to 40 times (2 minutes) to connect. - maxAttempts=40 - + # With Rosetta 2 emulation, SQL Server starts much faster than under full + # QEMU emulation, but it can still take a minute or two. We allow up to + # 6 minutes (72 attempts x 5 seconds) as a generous upper bound. + + # Wait 5 seconds between attempts. + delay=5 + + # Try up to 72 times (~6 minutes) to connect. + maxAttempts=72 + # Attempt counter. attempt=1 @@ -63,18 +79,29 @@ steps: while [ $attempt -le $maxAttempts ] do echo "Waiting for SQL Server to start (attempt #$attempt of $maxAttempts)..." - - sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" >> $SQLCMD_ERRORS 2>&1 - + + # -C trusts the self-signed certificate inside the container. + sqlcmd -S 127.0.0.1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" >> $SQLCMD_ERRORS 2>&1 + # If the command was successful, then the SQL Server is ready. if [ $? -eq 0 ]; then ready=1 break fi - + + # Verify the container is still running; no point retrying if it crashed. + if ! docker ps --filter "name=^/sql1$" --filter "status=running" --format '{{.Names}}' | grep -Fxq 'sql1'; then + echo "ERROR: sql1 container is no longer running." + docker ps -a --filter "name=^/sql1$" + echo "--- Container logs ---" + docker logs sql1 2>&1 | tail -50 + rm -f $SQLCMD_ERRORS + exit 1 + fi + # Increment the attempt counter. ((attempt++)) - + # Wait before trying again. sleep $delay done @@ -83,8 +110,13 @@ steps: if [ $ready -eq 0 ] then # No, so report the error(s) and exit. - echo Cannot connect to SQL Server; installation aborted; errors were: + echo "Cannot connect to SQL Server after $maxAttempts attempts; installation aborted." + echo "--- sqlcmd errors ---" cat $SQLCMD_ERRORS + echo "--- Container status ---" + docker ps -a --filter "name=^/sql1$" + echo "--- Container logs (last 80 lines) ---" + docker logs sql1 2>&1 | tail -80 rm -f $SQLCMD_ERRORS exit 1 fi @@ -93,22 +125,22 @@ steps: echo "Use sqlcmd to show which IP addresses are being listened on..." echo 0.0.0.0 - sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + sqlcmd -S 0.0.0.0 -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo 127.0.0.1 - sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + sqlcmd -S 127.0.0.1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo ::1 - sqlcmd -S ::1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + sqlcmd -S ::1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo localhost - sqlcmd -S localhost -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + sqlcmd -S localhost -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo "(sqlcmd default / not specified)" - sqlcmd -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + sqlcmd -No -C -U sa -P "$MSSQL_SA_PW" -Q "SELECT @@VERSION" -l 2 echo "Configuring Dedicated Administer Connections to allow remote connections..." - sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;" + sqlcmd -S 127.0.0.1 -No -C -U sa -P "$MSSQL_SA_PW" -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;" if [ $? = 1 ] then echo "Error configuring DAC for remote access." - exit $errstatus + exit 1 else echo "Configuration complete." fi 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 ae7b1d9901..b7ec798416 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 SqlBinaryCtor_NullInput() }; [Theory] - [MemberData(nameof(SqlDecimalExtractData_NonNullInput_Data))] + [MemberData(nameof(SqlDecimalExtractData_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlDecimalExtractData_NonNullInput(SqlDecimal input) { // Act @@ -156,7 +156,7 @@ public void SqlGuidCtor_ValidInput(byte[] input) }; [Theory] - [MemberData(nameof(SqlMoneyCtor_Data))] + [MemberData(nameof(SqlMoneyCtor_Data), DisableDiscoveryEnumeration = true)] public void SqlMoneyCtor(long input, SqlMoney expected) { // Act @@ -177,7 +177,7 @@ public void SqlMoneyCtor(long input, SqlMoney expected) }; [Theory] - [MemberData(nameof(SqlMoneyToSqlInternalRepresentation_NonNullInput_Data))] + [MemberData(nameof(SqlMoneyToSqlInternalRepresentation_NonNullInput_Data), DisableDiscoveryEnumeration = true)] public void SqlMoneyToSqlInternalRepresentation_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 57fe032d8f..cdb0e0a4cd 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 { @@ -71,7 +70,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.Login7Count); // 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, @@ -275,6 +275,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); try @@ -326,6 +327,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 @@ -337,7 +341,11 @@ 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); + // Login7 is sent to the primary but the client gives up during the + // server-side delay; the counter is still incremented when the + // Login7 message is received. + Assert.Equal(1, server.Login7Count); + Assert.Equal(1, failoverServer.Login7Count); } [Theory] @@ -370,7 +378,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); @@ -382,7 +391,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.Login7Count); } [Theory] @@ -468,7 +477,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(); @@ -477,7 +486,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.Login7Count); } [Theory] @@ -580,6 +589,10 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs // 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); @@ -593,9 +606,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.Login7Count); // 1 for the failover connection - Assert.Equal(1, failoverServer.PreLoginCount); + Assert.Equal(1, failoverServer.Login7Count); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionRoutingTests.cs index 6d89246776..185413df1b 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.Login7Count); + Assert.Equal(2, server.Login7Count); } [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..3880b18e54 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.Login7Count); + Assert.Equal(2, server.Login7Count); } [Theory] diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index faeaa309cb..6ad8223b5f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -30,7 +30,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; @@ -44,7 +45,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; @@ -62,9 +64,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; @@ -73,7 +76,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)] @@ -81,26 +83,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.Login7Count); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -124,10 +128,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.Login7Count); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -152,10 +155,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.Login7Count); } - [Trait("Category", "flaky")] [Theory] [InlineData(40613)] [InlineData(42108)] @@ -180,10 +182,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.Login7Count); } - [Trait("Category", "flaky")] [Theory] [InlineData(false)] [InlineData(true)] @@ -201,6 +202,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 @@ -217,11 +219,10 @@ public async Task NetworkError_RetryEnabled_ShouldSucceed_Async(bool multiSubnet } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.Login7Count); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -262,11 +263,10 @@ public async Task NetworkDelay_RetryDisabled_Async(bool multiSubnetFailoverEnabl } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.Login7Count); } } - [Trait("Category", "flaky")] [Theory] [InlineData(true)] [InlineData(false)] @@ -307,7 +307,7 @@ public void NetworkDelay_RetryDisabled(bool multiSubnetFailoverEnabled) } else { - Assert.Equal(1, server.PreLoginCount); + Assert.Equal(1, server.Login7Count); } } @@ -468,7 +468,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/AuthenticatingTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/AuthenticatingTdsServer.cs index 4db5439845..28d41ab302 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/AuthenticatingTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/AuthenticatingTdsServer.cs @@ -33,7 +33,7 @@ public AuthenticatingTdsServer(AuthenticatingTdsServerArguments arguments) : /// /// Handler for login request /// - public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; @@ -207,7 +207,7 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, } // Return login response from the base class - return base.OnLogin7Request(session, request); + return base.OnLogin7RequestCore(session, request); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/FederatedAuthenticationNegativeTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/FederatedAuthenticationNegativeTdsServer.cs index 53a68c70ea..14af8819f8 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/FederatedAuthenticationNegativeTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/FederatedAuthenticationNegativeTdsServer.cs @@ -33,10 +33,10 @@ public FederatedAuthenticationNegativeTdsServer(FederatedAuthenticationNegativeT /// /// Handler for login request /// - public override TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnPreLoginRequestCore(ITDSServerSession session, TDSMessage request) { // Get the collection from a valid On PreLogin Request - TDSMessageCollection preLoginCollection = base.OnPreLoginRequest(session, request); + TDSMessageCollection preLoginCollection = base.OnPreLoginRequestCore(session, request); // Find the is token carrying on TDSPreLoginToken TDSPreLoginToken preLoginToken = preLoginCollection.Find(message => message.Exists(packetToken => packetToken is TDSPreLoginToken)). @@ -76,10 +76,10 @@ public override TDSMessageCollection OnPreLoginRequest(ITDSServerSession session /// /// Handler for login request /// - public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Get the collection from the normal behavior On Login7 Request - TDSMessageCollection login7Collection = base.OnLogin7Request(session, request); + TDSMessageCollection login7Collection = base.OnLogin7RequestCore(session, request); // Get the Federated Authentication ExtAck from Login 7 TDSFeatureExtAckFederatedAuthenticationOption fedAutExtAct = GetFeatureExtAckFederatedAuthenticationOptionFromLogin7(login7Collection); 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 c17726fb8b..368d472803 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 @@ -65,6 +65,11 @@ public delegate void OnAuthenticationCompletedDelegate( /// private int _preLoginCount = 0; + /// + /// Counts Login7 requests to the server. + /// + private int _login7Count = 0; + private TDSServerEndPoint _endpoint; /// @@ -107,6 +112,15 @@ public GenericTdsServer(T arguments, QueryEngine queryEngine) /// public int PreLoginCount => _preLoginCount; + /// + /// Counts Login7 requests received by the server. The counter is + /// incremented in the non-virtual + /// dispatcher before any virtual handler runs, so it reflects every + /// received Login7 message even if a derived handler sleeps, throws, + /// or returns early. + /// + public int Login7Count => _login7Count; + /// /// Property for setting server version for vector feature extension. /// @@ -128,9 +142,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(); } @@ -162,12 +179,23 @@ public virtual void CloseSession(ITDSServerSession session) } /// - /// Handler for pre-login request + /// Handler for pre-login request. Non-virtual so the request counter is + /// always incremented exactly once per received PreLogin message. + /// Derived classes should override . /// - public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request) + public TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request) { Interlocked.Increment(ref _preLoginCount); + return OnPreLoginRequestCore(session, request); + } + /// + /// Virtual extension point for pre-login request handling. Override this + /// instead of so the counter remains + /// accurate. + /// + protected virtual TDSMessageCollection OnPreLoginRequestCore(ITDSServerSession session, TDSMessage request) + { // Inflate pre-login request from the message TDSPreLoginToken preLoginRequest = request[0] as TDSPreLoginToken; GenericTdsServerSession genericTdsServerSession = session as GenericTdsServerSession; @@ -221,9 +249,22 @@ public virtual TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, } /// - /// Handler for login request + /// Handler for login request. Non-virtual so the request counter is + /// always incremented exactly once per received Login7 message. + /// Derived classes should override . + /// + public TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + { + Interlocked.Increment(ref _login7Count); + return OnLogin7RequestCore(session, request); + } + + /// + /// Virtual extension point for login request handling. Override this + /// instead of so the counter remains + /// accurate. /// - public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected virtual TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/RoutingTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/RoutingTdsServer.cs index 8e119a54cd..17da88d2da 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/RoutingTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/RoutingTdsServer.cs @@ -37,10 +37,10 @@ public RoutingTdsServer(RoutingTdsServerArguments arguments) : /// /// Handler for pre-login request /// - public override TDSMessageCollection OnPreLoginRequest(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnPreLoginRequestCore(ITDSServerSession session, TDSMessage request) { // Delegate to the base class - TDSMessageCollection response = base.OnPreLoginRequest(session, request); + TDSMessageCollection response = base.OnPreLoginRequestCore(session, request); // Check if arguments are of the routing server if (Arguments is RoutingTdsServerArguments) @@ -72,7 +72,7 @@ public override TDSMessageCollection OnPreLoginRequest(ITDSServerSession session /// /// Handler for login request /// - public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; @@ -122,7 +122,7 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, } // Delegate to the base class - return base.OnLogin7Request(session, request); + return base.OnLogin7RequestCore(session, request); } /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs index d0a15e90ae..25e18aa29a 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientDelayTdsServer.cs @@ -35,7 +35,7 @@ public override void Dispose() /// /// Handler for login request /// - public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Check if we're still going to raise transient error if (Arguments.IsEnabledPermanentDelay || @@ -47,7 +47,7 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, } // Return login response from the base class - return base.OnLogin7Request(session, request); + return base.OnLogin7RequestCore(session, request); } /// 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..f568ce6f9c 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 @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Threading; using Microsoft.SqlServer.TDS.Done; using Microsoft.SqlServer.TDS.EndPoint; using Microsoft.SqlServer.TDS.Error; @@ -52,7 +51,7 @@ private static string GetErrorMessage(uint errorNumber) /// /// Handler for login request /// - public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, TDSMessage request) + protected override TDSMessageCollection OnLogin7RequestCore(ITDSServerSession session, TDSMessage request) { // Inflate login7 request from the message TDSLogin7Token loginRequest = request[0] as TDSLogin7Token; @@ -64,7 +63,7 @@ public override TDSMessageCollection OnLogin7Request(ITDSServerSession session, } // Return login response from the base class - return base.OnLogin7Request(session, request); + return base.OnLogin7RequestCore(session, request); } ///