diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs index 1de6ca10b5..ac7ab9a1e4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.netcore.cs @@ -62,10 +62,6 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private static bool _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; #endif - - private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(); - private bool _parentOperationStarted = false; - internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; private _SqlRPC[] _rpcArrayOf1 = null; // Used for RPC executes diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs index a88fa63319..df5c372890 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.NonQuery.cs @@ -84,9 +84,7 @@ public override int ExecuteNonQuery() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - #if NET using var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction); - #endif using var eventScope = TryEventScope.Create($"SqlCommand.ExecuteNonQuery | API | Object Id {ObjectID}"); SqlClientEventSource.Log.TryCorrelationTraceEvent( @@ -128,9 +126,7 @@ public override int ExecuteNonQuery() } catch (Exception ex) { - #if NET diagnosticScope.SetException(ex); - #endif if (ex is SqlException sqlException) { @@ -326,26 +322,20 @@ private void CleanupAfterExecuteNonQueryAsync(Task task, TaskCompletionSour { Exception e = task.Exception?.InnerException; - #if NET s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - #endif source.SetException(e); } else if (task.IsCanceled) { - #if NET s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - #endif source.SetCanceled(); } else { // Task successful - #if NET s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - #endif source.SetResult(task.Result); } @@ -652,11 +642,7 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - #if NET Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); - #else - Guid operationId = Guid.Empty; - #endif // Connection can be used as state in RegisterForConnectionCloseNotification continuation // to avoid an allocation so use it as the state value if possible but it can be changed if @@ -715,9 +701,7 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok } catch (Exception e) { - #if NET s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - #endif source.SetException(e); context.Dispose(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs index 7c6c90a79b..3779be222f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Reader.cs @@ -109,10 +109,8 @@ public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) _pendingCancel = false; // @TODO: Do we want to use a command scope here like nonquery and xml? or is operation id ok? - #if NET Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); Exception e = null; - #endif using var eventScope = TryEventScope.Create($"SqlCommand.ExecuteReader | API | Object Id {ObjectID}"); // @TODO: Do we want to have a correlation trace event here like nonquery and xml? @@ -136,9 +134,7 @@ public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) // @TODO: CER Exception Handling was removed here (see GH#3581) catch (Exception ex) { - #if NET e = ex; - #endif if (ex is SqlException sqlException) { @@ -152,7 +148,6 @@ public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) SqlStatistics.StopTimer(statistics); WriteEndExecuteEvent(success, sqlExceptionNumber, synchronous: true); - #if NET if (e is not null) { s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); @@ -161,7 +156,6 @@ public SqlDataReader EndExecuteReader(IAsyncResult asyncResult) { s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); } - #endif } } @@ -573,23 +567,19 @@ private void CleanupExecuteReaderAsync( { Exception e = task.Exception.InnerException; - #if NET if (!_parentOperationStarted) { s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); } - #endif source.SetException(e); } else { - #if NET if (!_parentOperationStarted) { s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); } - #endif if (task.IsCanceled) { @@ -940,13 +930,7 @@ private Task InternalExecuteReaderAsync( $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - Guid operationId = Guid.Empty; - #if NET - if (!_parentOperationStarted) - { - operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); - } - #endif + Guid operationId = !_parentOperationStarted ? s_diagnosticListener.WriteCommandBefore(this, _transaction) : Guid.Empty; // Connection can be used as state in RegisterForConnectionCloseNotification // continuation to avoid an allocation so use it as the state value if possible, but it @@ -1015,12 +999,10 @@ private Task InternalExecuteReaderAsync( } catch (Exception e) { - #if NET if (!_parentOperationStarted) { s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); } - #endif source.SetException(e); context?.Dispose(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs index f7c7931565..f06f85c0a5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Scalar.cs @@ -28,9 +28,7 @@ public override object ExecuteScalar() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - #if NET using var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction); - #endif using var eventScope = TryEventScope.Create($"SqlCommand.ExecuteScalar | API | Object Id {ObjectID}"); SqlClientEventSource.Log.TryCorrelationTraceEvent( @@ -60,9 +58,7 @@ public override object ExecuteScalar() } catch (Exception ex) { - #if NET diagnosticScope.SetException(ex); - #endif if (ex is SqlException sqlException) { @@ -233,10 +229,8 @@ private Task ExecuteScalarAsyncInternal(CancellationToken cancellationTo $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - #if NET Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); _parentOperationStarted = true; - #endif // @TODO: Use continue with state? This would be a good candidate for rewriting async/await return ExecuteReaderAsync(cancellationToken).ContinueWith(executeTask => @@ -249,13 +243,11 @@ private Task ExecuteScalarAsyncInternal(CancellationToken cancellationTo } else if (executeTask.IsFaulted) { - #if NET s_diagnosticListener.WriteCommandError( operationId, this, _transaction, executeTask.Exception.InnerException); - #endif source.SetException(executeTask.Exception.InnerException); } @@ -279,13 +271,11 @@ private Task ExecuteScalarAsyncInternal(CancellationToken cancellationTo { reader.Dispose(); - #if NET s_diagnosticListener.WriteCommandError( operationId, this, _transaction, readTask.Exception.InnerException); - #endif source.SetException(readTask.Exception.InnerException); } @@ -316,17 +306,13 @@ private Task ExecuteScalarAsyncInternal(CancellationToken cancellationTo if (exception is not null) { - #if NET s_diagnosticListener.WriteCommandError(operationId, this, _transaction, exception); - #endif source.SetException(exception); } else { - #if NET s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - #endif source.SetResult(result); } @@ -341,9 +327,7 @@ private Task ExecuteScalarAsyncInternal(CancellationToken cancellationTo TaskScheduler.Default); } - #if NET _parentOperationStarted = false; - #endif return source.Task; }, diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs index 3391c300d7..1a9a778cd0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Xml.cs @@ -86,9 +86,7 @@ public XmlReader ExecuteXmlReader() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - #if NET using var diagnosticScope = s_diagnosticListener.CreateCommandScope(this, _transaction); - #endif using var eventScope = TryEventScope.Create($"SqlCommand.ExecuteXmlReader | API | Object Id {ObjectID}"); SqlClientEventSource.Log.TryCorrelationTraceEvent( @@ -116,9 +114,7 @@ public XmlReader ExecuteXmlReader() } catch (Exception ex) { - #if NET diagnosticScope.SetException(ex); - #endif if (ex is SqlException sqlException) { @@ -365,25 +361,19 @@ private void CleanupAfterExecuteXmlReaderAsync( { Exception e = task.Exception?.InnerException; - #if NET s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - #endif source.SetException(e); } else if (task.IsCanceled) { - #if NET s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - #endif source.SetCanceled(); } else { - #if NET s_diagnosticListener.WriteCommandAfter(operationId, this, _transaction); - #endif source.SetResult(task.Result); } @@ -475,11 +465,7 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella $"Client Connection Id {_activeConnection?.ClientConnectionId}, " + $"Command Text '{CommandText}'"); - #if NET Guid operationId = s_diagnosticListener.WriteCommandBefore(this, _transaction); - #else - Guid operationId = Guid.Empty; - #endif // Connection can be used as state in RegisterForConnectionCloseNotification continuation // to avoid an allocation so use it as the state value if possible but it can be changed if @@ -547,9 +533,7 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella } catch (Exception e) { - #if NET s_diagnosticListener.WriteCommandError(operationId, this, _transaction, e); - #endif source.SetException(e); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs index ef39945ea4..205d71679b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -10,10 +10,7 @@ using System.Threading; using Microsoft.Data.Common; using Microsoft.Data.Sql; - -#if NET using Microsoft.Data.SqlClient.Diagnostics; -#endif namespace Microsoft.Data.SqlClient { @@ -52,6 +49,18 @@ public sealed partial class SqlCommand : DbCommand, ICloneable /// private static int _objectTypeCount = 0; + /// + /// Static instance of the used for capturing and emitting + /// diagnostic events related to SqlCommand operations. + /// + private static readonly SqlDiagnosticListener s_diagnosticListener = new(); + + /// + /// Prevents the completion events for ExecuteReader from being fired if ExecuteReader is being + /// called as part of a parent operation (e.g. ExecuteScalar, or SqlBatch.ExecuteScalar.) + /// + private bool _parentOperationStarted = false; + /// /// Connection that will be used to process the current instance. /// @@ -571,12 +580,8 @@ internal SqlStatistics Statistics { if (_activeConnection is not null) { - #if NET bool isStatisticsEnabled = _activeConnection.StatisticsEnabled || s_diagnosticListener.IsEnabled(SqlClientCommandAfter.Name); - #else - bool isStatisticsEnabled = _activeConnection.StatisticsEnabled; - #endif if (isStatisticsEnabled) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index bdbf7169ae..29272784a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -25,6 +25,16 @@ public class DiagnosticTest { private const string BadConnectionString = "data source = bad; initial catalog = bad; integrated security = true; connection timeout = 1;"; + private const string WriteCommandBefore = "Microsoft.Data.SqlClient.WriteCommandBefore"; + private const string WriteCommandAfter = "Microsoft.Data.SqlClient.WriteCommandAfter"; + private const string WriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError"; + private const string WriteConnectionOpenBefore = "Microsoft.Data.SqlClient.WriteConnectionOpenBefore"; + private const string WriteConnectionOpenAfter = "Microsoft.Data.SqlClient.WriteConnectionOpenAfter"; + private const string WriteConnectionOpenError = "Microsoft.Data.SqlClient.WriteConnectionOpenError"; + private const string WriteConnectionCloseBefore = "Microsoft.Data.SqlClient.WriteConnectionCloseBefore"; + private const string WriteConnectionCloseAfter = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; + private const string WriteConnectionCloseError = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; + [Fact] public void ExecuteScalarTest() { @@ -41,7 +51,7 @@ public void ExecuteScalarTest() conn.Open(); cmd.ExecuteScalar(); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -62,7 +72,7 @@ public void ExecuteScalarErrorTest() conn.Open(); Assert.Throws(() => cmd.ExecuteScalar()); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -83,7 +93,7 @@ public void ExecuteNonQueryTest() conn.Open(); cmd.ExecuteNonQuery(); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -107,7 +117,7 @@ public void ExecuteNonQueryErrorTest() Assert.Throws(() => cmd.ExecuteNonQuery()); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -132,7 +142,7 @@ public void ExecuteReaderTest() // Read until end. } } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -154,7 +164,7 @@ public void ExecuteReaderErrorTest() // @TODO: TestTdsServer should not throw on ExecuteReader, it should throw on reader.Read Assert.Throws(() => cmd.ExecuteReader()); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -179,7 +189,7 @@ public void ExecuteReaderWithCommandBehaviorTest() // Read to end } } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -206,7 +216,7 @@ public void ExecuteXmlReaderTest() // Read to end } } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -228,7 +238,7 @@ public void ExecuteXmlReaderErrorTest() // @TODO: TestTdsServer should not throw on ExecuteXmlReader, should throw on reader.Read Assert.Throws(() => cmd.ExecuteXmlReader()); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -254,7 +264,7 @@ public void ExecuteScalarAsyncTest() conn.Open(); await cmd.ExecuteScalarAsync(); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -280,7 +290,7 @@ public void ExecuteScalarAsyncErrorTest() conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -306,7 +316,7 @@ public void ExecuteNonQueryAsyncTest() conn.Open(); await cmd.ExecuteNonQueryAsync(); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -332,7 +342,7 @@ public void ExecuteNonQueryAsyncErrorTest() conn.Open(); await Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -362,7 +372,7 @@ public void ExecuteReaderAsyncTest() // Read to end } } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -389,7 +399,7 @@ public void ExecuteReaderAsyncErrorTest() // @TODO: TestTdsServer should not throw on ExecuteReader, should throw on reader.Read await Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandError, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -421,7 +431,7 @@ public void ExecuteXmlReaderAsyncTest() // Read to end } } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -449,11 +459,14 @@ public void ExecuteXmlReaderAsyncErrorTest() // @TODO: Since this test uses a real database connection, the exception is // thrown during reader.Read. (ie, TestTdsServer does not obey proper // exception behavior) + // NB: As a result of the exception being thrown during reader.Read, + // cmd.ExecuteXmlReaderAsync returns successfully. This means that we receive + // a WriteCommandAfter event rather than WriteCommandError. await conn.OpenAsync(); XmlReader reader = await cmd.ExecuteXmlReaderAsync(); await Assert.ThrowsAsync(() => reader.ReadAsync()); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteCommandBefore, WriteCommandAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -469,7 +482,7 @@ public void ConnectionOpenTest() { sqlConnection.Open(); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -485,7 +498,7 @@ public void ConnectionOpenErrorTest() { Assert.Throws(() => sqlConnection.Open()); } - }); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -505,7 +518,7 @@ public void ConnectionOpenAsyncTest() { await sqlConnection.OpenAsync(); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenAfter, WriteConnectionCloseBefore, WriteConnectionCloseAfter]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } @@ -525,12 +538,12 @@ public void ConnectionOpenAsyncErrorTest() { await Assert.ThrowsAsync(() => sqlConnection.OpenAsync()); } - }).Wait(); + }, [WriteConnectionOpenBefore, WriteConnectionOpenError]).Wait(); return RemoteExecutor.SuccessExitCode; }).Dispose(); } - private static void CollectStatisticsDiagnostics(Action sqlOperation, [CallerMemberName] string methodName = "") + private static void CollectStatisticsDiagnostics(Action sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -738,6 +751,10 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, [C Assert.True(statsLogged); diagnosticListenerObserver.Disable(); + foreach (string expected in expectedDiagnostics) + { + Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); + } Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } @@ -746,7 +763,7 @@ private static void CollectStatisticsDiagnostics(Action sqlOperation, [C Console.WriteLine(string.Format("Test: {0} Listeners Disposed Successfully", methodName)); } - private static async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, [CallerMemberName] string methodName = "") + private static async Task CollectStatisticsDiagnosticsAsync(Func sqlOperation, string[] expectedDiagnostics, [CallerMemberName] string methodName = "") { bool statsLogged = false; bool operationHasError = false; @@ -936,6 +953,10 @@ private static async Task CollectStatisticsDiagnosticsAsync(Func s Assert.True(statsLogged); diagnosticListenerObserver.Disable(); + foreach (string expected in expectedDiagnostics) + { + Assert.True(diagnosticListenerObserver.HasReceivedDiagnostic(expected), $"Missing diagnostic '{expected}'"); + } Console.WriteLine(string.Format("Test: {0} Listeners Disabled", methodName)); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/FakeDiagnosticListenerObserver.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/FakeDiagnosticListenerObserver.cs index 36198c3bb5..789c369b9d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/FakeDiagnosticListenerObserver.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/FakeDiagnosticListenerObserver.cs @@ -13,10 +13,12 @@ public sealed class FakeDiagnosticListenerObserver : IObserver> { private readonly Action> _writeCallback; + private readonly List _receivedDiagnosticNames; - public FakeDiagnosticSourceWriteObserver(Action> writeCallback) + public FakeDiagnosticSourceWriteObserver(Action> writeCallback, List receivedDiagnosticNames) { _writeCallback = writeCallback; + _receivedDiagnosticNames = receivedDiagnosticNames; } public void OnCompleted() @@ -29,16 +31,22 @@ public void OnError(Exception error) public void OnNext(KeyValuePair value) { + lock (_receivedDiagnosticNames) + { + _receivedDiagnosticNames.Add(value.Key); + } _writeCallback(value); } } + private readonly List _receivedDiagnosticNames; private readonly Action> _writeCallback; private bool _writeObserverEnabled; public FakeDiagnosticListenerObserver(Action> writeCallback) { _writeCallback = writeCallback; + _receivedDiagnosticNames = []; } public void OnCompleted() @@ -53,7 +61,15 @@ public void OnNext(DiagnosticListener value) { if (value.Name.Equals("SqlClientDiagnosticListener")) { - value.Subscribe(new FakeDiagnosticSourceWriteObserver(_writeCallback), IsEnabled); + value.Subscribe(new FakeDiagnosticSourceWriteObserver(_writeCallback, _receivedDiagnosticNames), IsEnabled); + } + } + + public bool HasReceivedDiagnostic(string diagnosticName) + { + lock (_receivedDiagnosticNames) + { + return _receivedDiagnosticNames.Contains(diagnosticName); } }