Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Data.SqlClient
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Reflection;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.Data.SqlClient.Tests.Common
{
/// <summary>
/// Extensions on the <see cref="SqlDataReader"/> class.
/// </summary>
public static class SqlDataReaderExtensions
{
/// <summary>
/// Reads all result sets in the provided <paramref name="dataReader"/> and discards them.
/// </summary>
/// <param name="dataReader">Reader to flush results from.</param>
public static void FlushAllResults(this SqlDataReader dataReader)
{
do
{
dataReader.FlushResultSet();
} while (dataReader.NextResult());
}

/// <summary>
/// Reads all results in the current result set of the provided <paramref name="dataReader"/>
/// and discards them.
/// </summary>
/// <param name="dataReader">Reader to flush results from.</param>
public static void FlushResultSet(this SqlDataReader dataReader)
{
while (dataReader.Read())
{
// Discard results.
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System;
using System.Runtime.InteropServices;

namespace Microsoft.Data.SqlClient.Tests
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using Microsoft.Data.Sql;
using Xunit;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Data;
using Xunit;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.using System;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline between license comment and using statement.

Suggested change
// See the LICENSE file in the project root for more information.using System;
// See the LICENSE file in the project root for more information.

Copilot uses AI. Check for mistakes.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using Xunit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,51 +292,38 @@ private static Task<string> AcquireTokenAsync(string authorityURL, string userID
#nullable enable

/// <summary>
/// Returns the current test name as:
///
/// ClassName.MethodName
///
/// xUnit v2 doesn't provide access to a test context, so we use
/// reflection into the ITestOutputHelper to get the test name.
/// Returns the current test name as: ClassName.MethodName
/// xUnit v2 doesn't provide access to a test context, so we use reflection into the
/// ITestOutputHelper to get the test name.
/// </summary>
///
/// <param name="outputHelper">
/// The output helper instance for the currently running test.
/// </param>
///
/// <returns>The current test name.</returns>
/// <exception cref="Exception">
/// Thrown if any intermediate step of getting to the test name fails or is inaccessible.
/// </exception>
/// <param name="outputHelper">Output helper instance for the currently running test</param>
/// <returns>Current test name</returns>
public static string CurrentTestName(ITestOutputHelper outputHelper)
{
// Reflect our way to the ITest instance.
var type = outputHelper.GetType();
Assert.NotNull(type);
var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(testMember);
var test = testMember.GetValue(outputHelper) as ITest;
Assert.NotNull(test);

// The DisplayName is in the format:
//
// Namespace.ClassName.MethodName(args)
//
// We only want the ClassName.MethodName portion.
//
Match match = TestNameRegex.Match(test.DisplayName);
Assert.True(match.Success);
// There should be 2 groups: the overall match, and the capture
// group.
Assert.Equal(2, match.Groups.Count);

// The portion we want is in the capture group.
return match.Groups[1].Value;
}

private static readonly Regex TestNameRegex = new(
// Capture the ClassName.MethodName portion, which may terminate
// the name, or have (args...) appended.
@"\.((?:[^.]+)\.(?:[^.\(]+))(?:\(.*\))?$",
RegexOptions.Compiled);

// Reflect our way to the ITestMethod.
Type type = outputHelper.GetType();

FieldInfo testField = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new Exception("Could not find field 'test' on ITestOutputHelper");

ITest test = testField.GetValue(outputHelper) as ITest
?? throw new Exception("Field 'test' on outputHelper is null or not an ITest object.");

ITestMethod testMethod = test.TestCase.TestMethod;

// Class name will be fully-qualified. We only want the class name, so take the last part.
string[] testClassNameParts = testMethod.TestClass.Class.Name.Split('.');
string testClassName = testClassNameParts[testClassNameParts.Length - 1];

string testMethodName = testMethod.Method.Name;

// Reconstitute the test name as classname.methodname
return $"{testClassName}.{testMethodName}";
}

/// <summary>
/// SQL Server properties we can query.
///
Expand Down Expand Up @@ -1295,191 +1282,6 @@ protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData)

#nullable enable

public readonly ref struct XEventScope : IDisposable
{
#region Private Fields

// Maximum dispatch latency for XEvents, in seconds.
private const int MaxDispatchLatencySeconds = 5;

// The connection to use for all operations.
private readonly SqlConnection _connection;

// True if connected to an Azure SQL instance.
private readonly bool _isAzureSql;

// True if connected to a non-Azure SQL Server 2025 (version 17) or
// higher.
private readonly bool _isVersion17OrHigher;

// Duration for the XEvent session, in minutes.
private readonly ushort _durationInMinutes;

#endregion

#region Properties

/// <summary>
/// The name of the XEvent session, derived from the session name
/// provided at construction time, with a unique suffix appended.
/// </summary>
public string SessionName { get; }

#endregion

#region Construction

/// <summary>
/// Construct with the specified parameters.
///
/// This will use the connection to query the server properties and
/// setup and start the XEvent session.
/// </summary>
/// <param name="sessionName">The base name of the session.</param>
/// <param name="connection">The SQL connection to use. (Must already be open.)</param>
/// <param name="eventSpecification">The event specification T-SQL string.</param>
/// <param name="targetSpecification">The target specification T-SQL string.</param>
/// <param name="durationInMinutes">The duration of the session in minutes.</param>
public XEventScope(
string sessionName,
// The connection must already be open.
SqlConnection connection,
string eventSpecification,
string targetSpecification,
ushort durationInMinutes = 5)
{
SessionName = GenerateRandomCharacters(sessionName);

_connection = connection;
Assert.Equal(ConnectionState.Open, _connection.State);

_durationInMinutes = durationInMinutes;

// EngineEdition 5 indicates Azure SQL.
_isAzureSql = GetSqlServerProperty(connection, ServerProperty.EngineEdition) == "5";

// Determine if we're connected to a SQL Server instance version
// 17 or higher.
if (!_isAzureSql)
{
int majorVersion;
Assert.True(
int.TryParse(
GetSqlServerProperty(connection, ServerProperty.ProductMajorVersion),
out majorVersion));
_isVersion17OrHigher = majorVersion >= 17;
}

// Setup and start the XEvent session.
string sessionLocation = _isAzureSql ? "DATABASE" : "SERVER";

// Both Azure SQL and SQL Server 2025+ support setting a maximum
// duration for the XEvent session.
string duration =
_isAzureSql || _isVersion17OrHigher
? $"MAX_DURATION={_durationInMinutes} MINUTES,"
: string.Empty;

string xEventCreateAndStartCommandText =
$@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}
{eventSpecification}
{targetSpecification}
WITH (
{duration}
MAX_MEMORY=16 MB,
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY={MaxDispatchLatencySeconds} SECONDS,
MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=ON,
STARTUP_STATE=OFF)

ALTER EVENT SESSION [{SessionName}] ON {sessionLocation} STATE = START ";

using SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection);
createXEventSession.ExecuteNonQuery();
}

/// <summary>
/// Disposal stops and drops the XEvent session.
/// </summary>
/// <remarks>
/// Disposal isn't perfect - tests can abort without cleaning up the
/// events they have created. For Azure SQL targets that outlive the
/// test pipelines, it is beneficial to periodically log into the
/// database and drop old XEvent sessions using T-SQL similar to
/// this:
///
/// DECLARE @sql NVARCHAR(MAX) = N'';
///
/// -- Identify inactive (stopped) event sessions and generate DROP commands
/// SELECT @sql += N'DROP EVENT SESSION [' + name + N'] ON SERVER;' + CHAR(13) + CHAR(10)
/// FROM sys.server_event_sessions
/// WHERE running = 0; -- Filter for sessions that are not running (inactive)
///
/// -- Print the generated commands for review (optional, but recommended)
/// PRINT @sql;
///
/// -- Execute the generated commands
/// EXEC sys.sp_executesql @sql;
/// </remarks>
public void Dispose()
{
string dropXEventSessionCommand = _isAzureSql
// We choose the sys.(database|server)_event_sessions views
// here to ensure we find sessions that may not be running.
? $"IF EXISTS (select * from sys.database_event_sessions where name ='{SessionName}')" +
$" DROP EVENT SESSION [{SessionName}] ON DATABASE"
: $"IF EXISTS (select * from sys.server_event_sessions where name ='{SessionName}')" +
$" DROP EVENT SESSION [{SessionName}] ON SERVER";

using SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection);
command.ExecuteNonQuery();
}

#endregion

#region Public Methods

/// <summary>
/// Query the XEvent session for its collected events, returning
/// them as an XML document.
///
/// This always blocks the thread for MaxDispatchLatencySeconds to
/// ensure that all events have been flushed into the ring buffer.
/// </summary>
public System.Xml.XmlDocument GetEvents()
{
string xEventQuery = _isAzureSql
? $@"SELECT xet.target_data
FROM sys.dm_xe_database_session_targets AS xet
INNER JOIN sys.dm_xe_database_sessions AS xe
ON (xe.address = xet.event_session_address)
WHERE xe.name = '{SessionName}'"
: $@"SELECT xet.target_data
FROM sys.dm_xe_session_targets AS xet
INNER JOIN sys.dm_xe_sessions AS xe
ON (xe.address = xet.event_session_address)
WHERE xe.name = '{SessionName}'";

using SqlCommand command = new SqlCommand(xEventQuery, _connection);

// Wait for maximum dispatch latency to ensure all events
// have been flushed to the ring buffer.
Thread.Sleep(MaxDispatchLatencySeconds * 1000);

string? targetData = command.ExecuteScalar() as string;
Assert.NotNull(targetData);

System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument();

xmlDocument.LoadXml(targetData);
return xmlDocument;
}

#endregion
}

/// <summary>
/// Resolves the machine's fully qualified domain name if it is applicable.
/// </summary>
Expand Down
Loading
Loading