From f3cf6e601a2a9f84e62258207aef5368fff1763f Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:30:34 +0100 Subject: [PATCH 01/15] Move SqlVectorTest, adjust namespace --- .../tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs | 3 +-- .../UnitTests/{ => Microsoft/Data/SqlTypes}/SqlVectorTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) rename src/Microsoft.Data.SqlClient/tests/UnitTests/{ => Microsoft/Data/SqlTypes}/SqlVectorTest.cs (99%) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs index faf44a4e29..d83cedd74d 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs @@ -7,10 +7,9 @@ using System; using System.Data.SqlTypes; using System.Text.Json; -using Microsoft.Data.SqlTypes; using Xunit; -namespace Microsoft.Data.SqlClient.UnitTests; +namespace Microsoft.Data.SqlTypes.UnitTests; public class SqlJsonTest { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs index 3390d95c02..9e6e520b63 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlVectorTest.cs @@ -4,12 +4,12 @@ // See the LICENSE file in the project root for more information. using System; -using Microsoft.Data.SqlTypes; +using Microsoft.Data.SqlClient; using Xunit; #nullable enable -namespace Microsoft.Data.SqlClient.Tests; +namespace Microsoft.Data.SqlTypes.UnitTests; public class SqlVectorTest { From 9b55f4c554d7ca79447154a2c735850d611b3dfe Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:39:06 +0100 Subject: [PATCH 02/15] Replace LocalAppContextSwitchesTests --- .../LocalAppContextSwitchesTests.cs | 34 ------------------- .../SqlClient/LocalAppContextSwitchesTest.cs | 34 +++++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) delete mode 100644 src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs create mode 100644 src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs deleted file mode 100644 index 170e39a322..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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.Reflection; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests -{ - public class LocalAppContextSwitchesTests - { - [Theory] - [InlineData("LegacyRowVersionNullBehavior", false)] - [InlineData("SuppressInsecureTlsWarning", false)] - [InlineData("MakeReadAsyncBlocking", false)] - [InlineData("UseMinimumLoginTimeout", true)] - [InlineData("LegacyVarTimeZeroScaleBehaviour", true)] - [InlineData("UseCompatibilityProcessSni", false)] - [InlineData("UseCompatibilityAsyncBehaviour", false)] - [InlineData("UseConnectionPoolV2", false)] - #if NETFRAMEWORK - [InlineData("DisableTnirByDefault", false)] - #endif - public void DefaultSwitchValue(string property, bool expectedDefaultValue) - { - var switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - - var switchValue = (bool)switchesType.GetProperty(property, BindingFlags.Public | BindingFlags.Static).GetValue(null); - - Assert.Equal(expectedDefaultValue, switchValue); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs new file mode 100644 index 0000000000..ba58ab292e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -0,0 +1,34 @@ +// 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 Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + /// + /// Provides unit tests for verifying the default values of all SqlClient-specific AppContext switches. + /// + public class LocalAppContextSwitchesTest + { + /// + /// Tests the default values of every AppContext switch used by SqlClient. + /// + [Fact] + public void TestDefaultAppContextSwitchValues() + { + Assert.False(LocalAppContextSwitches.LegacyRowVersionNullBehavior); + Assert.False(LocalAppContextSwitches.SuppressInsecureTlsWarning); + Assert.False(LocalAppContextSwitches.MakeReadAsyncBlocking); + Assert.True(LocalAppContextSwitches.UseMinimumLoginTimeout); + Assert.True(LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour); + Assert.False(LocalAppContextSwitches.UseCompatibilityProcessSni); + Assert.False(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); + Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); + #if NETFRAMEWORK + Assert.False(LocalAppContextSwitches.DisableTnirByDefault); + #endif + } + } +} From fae085a5c7b8b36bb5b4bb3e779bd36e5c4e888d Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:48:08 +0100 Subject: [PATCH 03/15] Move SqlBufferTests --- .../Microsoft.Data.SqlClient.FunctionalTests.csproj | 2 -- .../Microsoft/Data/SqlClient}/SqlBufferTests.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) rename src/Microsoft.Data.SqlClient/tests/{FunctionalTests => UnitTests/Microsoft/Data/SqlClient}/SqlBufferTests.cs (99%) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 56265208b4..b755d05423 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -30,10 +30,8 @@ - - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs index 2be8f5bd3b..1ffb3d9f50 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlBufferTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs @@ -9,7 +9,7 @@ using System.Reflection; using Xunit; -namespace Microsoft.Data.SqlClient.Tests +namespace Microsoft.Data.SqlClient.UnitTests { public sealed class SqlBufferTests { From 996e4601b0aee8be85ba4f0610022877405c2e89 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:55:55 +0100 Subject: [PATCH 04/15] Remove unnecessary reflection from SqlBufferTests --- .../Data/SqlClient/SqlBufferTests.cs | 160 +----------------- 1 file changed, 7 insertions(+), 153 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs index 1ffb3d9f50..9d556b7dd9 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs @@ -3,53 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Data.SqlTypes; -using System.Linq; -using System.Reflection; using Xunit; namespace Microsoft.Data.SqlClient.UnitTests { public sealed class SqlBufferTests { - static SqlBufferTests() - { - const string sqlBufferTypeFullName = "Microsoft.Data.SqlClient.SqlBuffer"; - const string storageTypeName = nameof(SqlBufferProxy.StorageType); - - var assembly = typeof(SqlClientFactory).Assembly; - _sqlBufferType = assembly.GetType(sqlBufferTypeFullName) - ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}]"); - _storageTypeType = _sqlBufferType.GetNestedTypes(BindingFlags.NonPublic) - .FirstOrDefault(x => x.Name == storageTypeName) - ?? throw new Exception($"Type not found [{sqlBufferTypeFullName}+{storageTypeName}]"); - } - - private static readonly Type _sqlBufferType; - private static readonly Type _storageTypeType; - private readonly SqlBufferProxy _target = new(); - - public static IEnumerable GetStorageTypeValues() - { -#if NET - return Enum.GetValues() - .Select(x => new object[] { x }); -#else - return Enum.GetValues(typeof(SqlBufferProxy.StorageType)) - .OfType() - .Select(x => new object[] { x }); -#endif - } - - [Theory] - [MemberData(nameof(GetStorageTypeValues))] - public void StorageTypeInProxyShouldHaveTheSameValueAsOriginal(SqlBufferProxy.StorageType expected) - { - var originalEnumName = Enum.GetName(_storageTypeType, (int)expected); - - Assert.Equal(expected.ToString(), originalEnumName); - } + private readonly SqlBuffer _target = new(); [Fact] public void GuidShouldThrowWhenSqlGuidNullIsSet() @@ -60,9 +21,9 @@ public void GuidShouldThrowWhenSqlGuidNullIsSet() } [Theory] - [InlineData(SqlBufferProxy.StorageType.Guid)] - [InlineData(SqlBufferProxy.StorageType.SqlGuid)] - public void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + [InlineData(SqlBuffer.StorageType.Guid)] + [InlineData(SqlBuffer.StorageType.SqlGuid)] + internal void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) { _target.SetToNullOfType(storageType); @@ -88,9 +49,9 @@ public void GuidShouldReturnExpectedWhenSqlGuidIsSet() } [Theory] - [InlineData(SqlBufferProxy.StorageType.Guid)] - [InlineData(SqlBufferProxy.StorageType.SqlGuid)] - public void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBufferProxy.StorageType storageType) + [InlineData(SqlBuffer.StorageType.Guid)] + [InlineData(SqlBuffer.StorageType.SqlGuid)] + internal void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) { _target.SetToNullOfType(storageType); @@ -142,112 +103,5 @@ public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() Assert.Equal(expected, _target.SqlValue); } - - public sealed class SqlBufferProxy - { - public enum StorageType - { - Empty = 0, - Boolean, - Byte, - DateTime, - Decimal, - Double, - Int16, - Int32, - Int64, - Guid, - Money, - Single, - String, - SqlBinary, - SqlCachedBuffer, - SqlGuid, - SqlXml, - Date, - DateTime2, - DateTimeOffset, - Time, - } - - private static readonly PropertyInfo _guidProperty; - private static readonly PropertyInfo _sqlGuidProperty; - private static readonly PropertyInfo _sqlValueProperty; - private static readonly MethodInfo _setToNullOfTypeMethod; - private readonly object _instance; - - static SqlBufferProxy() - { - var flags = BindingFlags.NonPublic | BindingFlags.Instance; - _guidProperty = _sqlBufferType.GetProperty(nameof(Guid), flags); - _sqlGuidProperty = _sqlBufferType.GetProperty(nameof(SqlGuid), flags); - _sqlValueProperty = _sqlBufferType.GetProperty(nameof(SqlValue), flags); - _setToNullOfTypeMethod = _sqlBufferType.GetMethod(nameof(SetToNullOfType), flags); - } - - public SqlBufferProxy() - { - _instance = Activator.CreateInstance(_sqlBufferType, true); - } - - public Guid Guid - { - get => GetPropertyValue(_guidProperty); - set => SetPropertyValue(_guidProperty, value); - } - - public SqlGuid SqlGuid - { - get => GetPropertyValue(_sqlGuidProperty); - set => SetPropertyValue(_sqlGuidProperty, value); - } - - public object SqlValue - { - get => GetPropertyValue(_sqlValueProperty); - } - - public void SetToNullOfType(StorageType storageType) - { -#if NET - _setToNullOfTypeMethod - .Invoke(_instance, BindingFlags.DoNotWrapExceptions, null, new object[] { (int)storageType }, null); -#else - _setToNullOfTypeMethod.Invoke(_instance, new object[] { (int)storageType }); -#endif - } - - private T GetPropertyValue(PropertyInfo property) - { -#if NET - return (T)property.GetValue(_instance, BindingFlags.DoNotWrapExceptions, null, null, null); -#else - try - { - return (T)property.GetValue(_instance); - } - catch (TargetInvocationException e) - { - throw e.InnerException!; - } -#endif - } - - private void SetPropertyValue(PropertyInfo property, object value) - { -#if NET - property.SetValue(_instance, value, BindingFlags.DoNotWrapExceptions, null, null, null); -#else - try - { - property.SetValue(_instance, value); - } - catch (TargetInvocationException e) - { - throw e.InnerException!; - } -#endif - } - } } } From c5f486310c34dee41ca2ef78cea8bd28038da507 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:15:12 +0100 Subject: [PATCH 05/15] Add descriptions to SqlBufferTests --- .../Data/SqlClient/SqlBufferTests.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs index 9d556b7dd9..f8966b1b26 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs @@ -8,10 +8,22 @@ namespace Microsoft.Data.SqlClient.UnitTests { + /// + /// Tests that null and non-null values assigned to the SqlBuffer round-trip correctly to their CLR and their + /// their SqlTypes representations. + /// + /// + /// Several methods in this class are internal. This is because their parameters are of SqlBuffer.StorageType, + /// which is non-public. + /// public sealed class SqlBufferTests { private readonly SqlBuffer _target = new(); + /// + /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its Guid property + /// throws a SqlNullValueException. + /// [Fact] public void GuidShouldThrowWhenSqlGuidNullIsSet() { @@ -20,6 +32,10 @@ public void GuidShouldThrowWhenSqlGuidNullIsSet() Assert.Throws(() => _target.Guid); } + /// + /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its Guid property throws + /// a SqlNullValueException. + /// [Theory] [InlineData(SqlBuffer.StorageType.Guid)] [InlineData(SqlBuffer.StorageType.SqlGuid)] @@ -30,6 +46,9 @@ internal void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType s Assert.Throws(() => _target.Guid); } + /// + /// Verifies that the Guid property round-trips correctly. + /// [Fact] public void GuidShouldReturnWhenGuidIsSet() { @@ -39,6 +58,9 @@ public void GuidShouldReturnWhenGuidIsSet() Assert.Equal(expected, _target.Guid); } + /// + /// Verifies that the SqlGuid property round-trips to the Guid property correctly. + /// [Fact] public void GuidShouldReturnExpectedWhenSqlGuidIsSet() { @@ -48,6 +70,10 @@ public void GuidShouldReturnExpectedWhenSqlGuidIsSet() Assert.Equal(expected, _target.Guid); } + /// + /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its SqlGuid property returns + /// SqlGuid.Null. + /// [Theory] [InlineData(SqlBuffer.StorageType.Guid)] [InlineData(SqlBuffer.StorageType.SqlGuid)] @@ -58,6 +84,10 @@ internal void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBuffer.St Assert.Equal(SqlGuid.Null, _target.SqlGuid); } + /// + /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its SqlGuid property + /// returns SqlGuid.Null. + /// [Fact] public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() { @@ -66,6 +96,9 @@ public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() Assert.Equal(SqlGuid.Null, _target.SqlGuid); } + /// + /// Verifies that the Guid property round-trips to the SqlGuid property correctly. + /// [Fact] public void SqlGuidShouldReturnExpectedWhenGuidIsSet() { @@ -76,6 +109,9 @@ public void SqlGuidShouldReturnExpectedWhenGuidIsSet() Assert.Equal(expected, _target.SqlGuid); } + /// + /// Verifies that the SqlGuid property round-trips correctly. + /// [Fact] public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() { @@ -85,6 +121,9 @@ public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() Assert.Equal(expected, _target.SqlGuid); } + /// + /// Verifies that the Guid property round-trips to the SqlValue property correctly. + /// [Fact] public void SqlValueShouldReturnExpectedWhenGuidIsSet() { @@ -95,6 +134,9 @@ public void SqlValueShouldReturnExpectedWhenGuidIsSet() Assert.Equal(expected, _target.SqlValue); } + /// + /// Verifies that the SqlGuid property round-trips to the SqlValue property correctly. + /// [Fact] public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() { From b713b2a125387e47f6d47de577731a79e378f1f2 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:20:49 +0100 Subject: [PATCH 06/15] Move SqlCommandSetTest --- .../Microsoft.Data.SqlClient.FunctionalTests.csproj | 1 - .../Microsoft/Data/SqlClient}/SqlCommandSetTest.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename src/Microsoft.Data.SqlClient/tests/{FunctionalTests => UnitTests/Microsoft/Data/SqlClient}/SqlCommandSetTest.cs (99%) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index b755d05423..91a5a505b9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -31,7 +31,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 7e8a3f0377..70a64b257d 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -4,7 +4,7 @@ using System.Reflection; using Xunit; -namespace Microsoft.Data.SqlClient.Tests +namespace Microsoft.Data.SqlClient.UnitTests { public class SqlCommandSetTest { From 7948faba61aad2772167a3b86caa4ddc375dccaa Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:52:01 +0100 Subject: [PATCH 07/15] Remove unnecessary reflection from SqlCommandSetTest --- .../Data/SqlClient/SqlCommandSetTest.cs | 157 ++++++++---------- 1 file changed, 69 insertions(+), 88 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 70a64b257d..240a355631 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -8,26 +8,26 @@ namespace Microsoft.Data.SqlClient.UnitTests { public class SqlCommandSetTest { - private static Assembly mds = Assembly.GetAssembly(typeof(SqlConnection)); - [Theory] [InlineData("BatchCommand")] [InlineData("CommandList")] public void GetDisposedProperty_Throws(string propertyName) { - var cmdSet = CreateInstance(); - CallMethod(cmdSet, "Dispose"); - Exception ex = GetProperty_Throws(cmdSet, propertyName); - VerifyException(ex, "disposed"); + SqlCommandSet cmdSet = new(); + cmdSet.Dispose(); + + ObjectDisposedException ode = GetProperty_Throws(cmdSet, propertyName); + Assert.Contains("disposed", ode.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AppendCommandWithEmptyString_Throws() { - var cmdSet = CreateInstance(); - SqlCommand cmd = new SqlCommand(""); - Exception ex = CallMethod_Throws(cmdSet, "Append", cmd); - VerifyException(ex, "CommandText property has not been initialized"); + SqlCommandSet cmdSet = new(); + SqlCommand cmd = new(""); + + InvalidOperationException ioe = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandText property has not been initialized", ioe.Message, StringComparison.OrdinalIgnoreCase); } public static IEnumerable CommandTypeData() @@ -52,21 +52,23 @@ public static IEnumerable CommandTypeData() )] public void AppendBadCommandType_Throws(CommandType commandType) { - var cmdSet = CreateInstance(); + SqlCommandSet cmdSet = new(); SqlCommand cmd = GenerateBadCommand(commandType); - Exception ex = CallMethod_Throws(cmdSet, "Append", cmd); - VerifyException(ex, "CommandType"); + + ArgumentOutOfRangeException aoore = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandType", aoore.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AppendBadParameterName_Throws() { - var cmdSet = CreateInstance(); - SqlCommand cmd = new SqlCommand("Test"); + SqlCommandSet cmdSet = new(); + SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("Test1;=", "1")); - Exception ex = CallMethod_Throws(cmdSet, "Append", cmd); - VerifyException(ex, "not valid"); + + ArgumentException ae = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("not valid", ae.Message, StringComparison.OrdinalIgnoreCase); } [Theory] @@ -74,15 +76,14 @@ public void AppendBadParameterName_Throws() [InlineData(new char[] { '1', '2', '3' })] public void AppendParameterArrayWithSize(object array) { - var cmdSet = CreateInstance(); - SqlCommand cmd = new SqlCommand("Test"); + SqlCommandSet cmdSet = new(); + SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.StoredProcedure; - SqlParameter parameter = new SqlParameter("@array", array); + SqlParameter parameter = new("@array", array); parameter.Size = 2; cmd.Parameters.Add(parameter); - CallMethod(cmdSet, "Append", cmd); - object p = CallMethod(cmdSet, "GetParameter", 0, 0); - SqlParameter result = p as SqlParameter; + cmdSet.Append(cmd); + SqlParameter result = cmdSet.GetParameter(0, 0); Assert.NotNull(result); Assert.Equal("@array", result.ParameterName); Assert.Equal(2, result.Size); @@ -91,13 +92,12 @@ public void AppendParameterArrayWithSize(object array) [Fact] public void GetParameter() { - var cmdSet = CreateInstance(); - SqlCommand cmd = new SqlCommand("Test"); + SqlCommandSet cmdSet = new(); + SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("@text", "value")); - CallMethod(cmdSet, "Append", cmd); - object p = CallMethod(cmdSet, "GetParameter", 0, 0); - SqlParameter result = p as SqlParameter; + cmdSet.Append(cmd); + SqlParameter result = cmdSet.GetParameter(0, 0); Assert.NotNull(result); Assert.Equal("@text", result.ParameterName); Assert.Equal("value", (string)result.Value); @@ -106,95 +106,76 @@ public void GetParameter() [Fact] public void GetParameterCount() { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - var cmdSet = Activator.CreateInstance(commandSetType, true); - SqlCommand cmd = new SqlCommand("Test"); + SqlCommandSet cmdSet = new(); + SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("@abc", "1")); cmd.Parameters.Add(new SqlParameter("@test", "2")); - commandSetType.GetMethod("Append", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(cmdSet, new object[] { cmd }); + cmdSet.Append(cmd); int index = 0; - int count = (int)commandSetType.GetMethod("GetParameterCount", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(cmdSet, new object[] { index }); + int count = cmdSet.GetParameterCount(index); Assert.Equal(2, count); } [Fact] public void InvalidCommandBehaviorValidateCommandBehavior_Throws() { - var cmdSet = CreateInstance(); - Exception ex = CallMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); - VerifyException(ex, "CommandBehavior"); + SqlCommandSet cmdSet = new(); + + ArgumentOutOfRangeException aoore = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); + Assert.Contains("CommandBehavior", aoore.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() { - var cmdSet = CreateInstance(); - Exception ex = CallMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); - VerifyException(ex, "not supported"); - } - - #region private methods - - private object CallMethod(object instance, string methodName, params object[] values) - { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - object returnValue = commandSetType.GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(instance, values); - return returnValue; - } - - private object CallMethod(object instance, string methodName) - { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - object returnValue = commandSetType.GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(instance, new object[] { }); - return returnValue; - } + SqlCommandSet cmdSet = new(); - private Exception CallMethod_Throws(object instance, string methodName, params object[] values) - { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - Exception ex = Assert.ThrowsAny(() => - { - commandSetType.GetMethod(methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Invoke(instance, values); - }); - return ex; + ArgumentOutOfRangeException aoore = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); + Assert.Contains("not supported", aoore.Message, StringComparison.OrdinalIgnoreCase); } - private object CreateInstance() - { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - object cmdSet = Activator.CreateInstance(commandSetType, true); - return cmdSet; - } + #region private methods - private Exception GetProperty_Throws(object instance, string propertyName) - { - var commandSetType = mds.GetType("Microsoft.Data.SqlClient.SqlCommandSet"); - var cmdSet = instance; - Exception ex = Assert.ThrowsAny(() => + private static T GetProperty_Throws(SqlCommandSet instance, string propertyName) + where T : Exception + => InvokeMethod_Throws(instance, + typeof(SqlCommandSet) + .GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance) + .GetGetMethod(true), + []); + + private static T InvokeMethod_Throws(SqlCommandSet instance, string methodName, params object[] values) + where T : Exception + => InvokeMethod_Throws(instance, + typeof(SqlCommandSet) + .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance), + values); + + private static T InvokeMethod_Throws(SqlCommandSet instance, MethodInfo methodInfo, params object[] values) + where T : Exception + { + return Assert.Throws(() => { - commandSetType.GetProperty(propertyName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetGetMethod(true).Invoke(cmdSet, new object[] { }); + try + { + methodInfo.Invoke(instance, values); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } }); - - return ex; } private SqlCommand GenerateBadCommand(CommandType cType) { - SqlCommand cmd = new SqlCommand("Test"); - Type sqlCommandType = cmd.GetType(); + SqlCommand cmd = new("Test"); // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. - sqlCommandType.GetField("_commandType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cmd, cType); + typeof(SqlCommand).GetField("_commandType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cmd, cType); return cmd; } - - private void VerifyException(Exception ex, string contains) - { - Assert.NotNull(ex); - Assert.IsType(ex.InnerException); - Assert.Contains(contains, ex.InnerException.Message, StringComparison.OrdinalIgnoreCase); - } #endregion } } From b415d5ec89093653ea12dfde4bc1eba807036ccf Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:59:51 +0100 Subject: [PATCH 08/15] Act on messages --- .../Data/SqlClient/SqlCommandSetTest.cs | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 240a355631..15205d11a3 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -16,27 +16,24 @@ public void GetDisposedProperty_Throws(string propertyName) SqlCommandSet cmdSet = new(); cmdSet.Dispose(); - ObjectDisposedException ode = GetProperty_Throws(cmdSet, propertyName); - Assert.Contains("disposed", ode.Message, StringComparison.OrdinalIgnoreCase); + ObjectDisposedException ex = GetProperty_Throws(cmdSet, propertyName); + Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AppendCommandWithEmptyString_Throws() { SqlCommandSet cmdSet = new(); - SqlCommand cmd = new(""); + using SqlCommand cmd = new(""); - InvalidOperationException ioe = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("CommandText property has not been initialized", ioe.Message, StringComparison.OrdinalIgnoreCase); + InvalidOperationException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandText property has not been initialized", ex.Message, StringComparison.OrdinalIgnoreCase); } public static IEnumerable CommandTypeData() { - return new object[][] - { - new object[] { CommandType.TableDirect }, - new object[] { (CommandType)5 } - }; + yield return [CommandType.TableDirect]; + yield return [(CommandType)5]; } [Theory] @@ -53,22 +50,22 @@ public static IEnumerable CommandTypeData() public void AppendBadCommandType_Throws(CommandType commandType) { SqlCommandSet cmdSet = new(); - SqlCommand cmd = GenerateBadCommand(commandType); + using SqlCommand cmd = GenerateBadCommand(commandType); - ArgumentOutOfRangeException aoore = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("CommandType", aoore.Message, StringComparison.OrdinalIgnoreCase); + ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandType", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AppendBadParameterName_Throws() { SqlCommandSet cmdSet = new(); - SqlCommand cmd = new("Test"); + using SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("Test1;=", "1")); - ArgumentException ae = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("not valid", ae.Message, StringComparison.OrdinalIgnoreCase); + ArgumentException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("not valid", ex.Message, StringComparison.OrdinalIgnoreCase); } [Theory] @@ -77,11 +74,9 @@ public void AppendBadParameterName_Throws() public void AppendParameterArrayWithSize(object array) { SqlCommandSet cmdSet = new(); - SqlCommand cmd = new("Test"); + using SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.StoredProcedure; - SqlParameter parameter = new("@array", array); - parameter.Size = 2; - cmd.Parameters.Add(parameter); + cmd.Parameters.Add(new SqlParameter("@array", array) { Size = 2 }); cmdSet.Append(cmd); SqlParameter result = cmdSet.GetParameter(0, 0); Assert.NotNull(result); @@ -93,7 +88,7 @@ public void AppendParameterArrayWithSize(object array) public void GetParameter() { SqlCommandSet cmdSet = new(); - SqlCommand cmd = new("Test"); + using SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("@text", "value")); cmdSet.Append(cmd); @@ -107,7 +102,7 @@ public void GetParameter() public void GetParameterCount() { SqlCommandSet cmdSet = new(); - SqlCommand cmd = new("Test"); + using SqlCommand cmd = new("Test"); cmd.CommandType = CommandType.Text; cmd.Parameters.Add(new SqlParameter("@abc", "1")); cmd.Parameters.Add(new SqlParameter("@test", "2")); @@ -122,8 +117,8 @@ public void InvalidCommandBehaviorValidateCommandBehavior_Throws() { SqlCommandSet cmdSet = new(); - ArgumentOutOfRangeException aoore = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); - Assert.Contains("CommandBehavior", aoore.Message, StringComparison.OrdinalIgnoreCase); + ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); + Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] @@ -131,8 +126,8 @@ public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() { SqlCommandSet cmdSet = new(); - ArgumentOutOfRangeException aoore = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); - Assert.Contains("not supported", aoore.Message, StringComparison.OrdinalIgnoreCase); + ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); + Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); } #region private methods @@ -168,7 +163,7 @@ private static T InvokeMethod_Throws(SqlCommandSet instance, MethodInfo metho }); } - private SqlCommand GenerateBadCommand(CommandType cType) + private static SqlCommand GenerateBadCommand(CommandType cType) { SqlCommand cmd = new("Test"); // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. From 7045eeacea1cb0ba18e262acc518c7c4ef963e58 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:45:35 +0100 Subject: [PATCH 09/15] Add descriptions to SqlCommandSetTests --- .../Data/SqlClient/SqlCommandSetTest.cs | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 15205d11a3..6b85595a7a 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -6,8 +6,17 @@ namespace Microsoft.Data.SqlClient.UnitTests { + /// + /// Provides unit tests for verifying the behavior of the SqlCommandSet class. + /// public class SqlCommandSetTest { + /// + /// Verifies that key properties throw an ObjectDisposedException after the SqlCommandSet has been disposed. + /// + /// + /// These properties are private, requiring reflection to access. + /// [Theory] [InlineData("BatchCommand")] [InlineData("CommandList")] @@ -20,6 +29,9 @@ public void GetDisposedProperty_Throws(string propertyName) Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); } + /// + /// Verifies that adding a SqlCommand with an empty CommandText to a SqlCommandSet throws an InvalidOperationException. + /// [Fact] public void AppendCommandWithEmptyString_Throws() { @@ -30,20 +42,24 @@ public void AppendCommandWithEmptyString_Throws() Assert.Contains("CommandText property has not been initialized", ex.Message, StringComparison.OrdinalIgnoreCase); } - public static IEnumerable CommandTypeData() - { - yield return [CommandType.TableDirect]; - yield return [(CommandType)5]; - } - + /// + /// Returns a set of invalid CommandType values. + /// + /// + /// + /// .NET Framework puts system enums in the Global Assembly Cache (GAC), and xUnit refuses to serialize enums that live there. + /// We make these system enum values a method, then disable enumeration of the test data to avoid warnings on the console when running tests. + /// + public static TheoryData CommandTypeData() + => new(CommandType.TableDirect, (CommandType)5); + + /// + /// Verifies that adding a SqlCommand with an invalid CommandType to a SqlCommandSet throws an ArgumentOutOfRangeException. + /// [Theory] [MemberData( nameof(CommandTypeData) #if NETFRAMEWORK - // .NET Framework puts system enums in something called the Global - // Assembly Cache (GAC), and xUnit refuses to serialize enums that - // live there. So for .NET Framework, we disable enumeration of the - // test data to avoid warnings on the console when running tests. , DisableDiscoveryEnumeration = true #endif )] @@ -56,6 +72,9 @@ public void AppendBadCommandType_Throws(CommandType commandType) Assert.Contains("CommandType", ex.Message, StringComparison.OrdinalIgnoreCase); } + /// + /// Verifies that adding a SqlCommand containing a SqlParameter with an invalid name to a SqlCommandSet throws an ArgumentException. + /// [Fact] public void AppendBadParameterName_Throws() { @@ -68,6 +87,9 @@ public void AppendBadParameterName_Throws() Assert.Contains("not valid", ex.Message, StringComparison.OrdinalIgnoreCase); } + /// + /// Verifies that a SqlParameter containing an array round-trips through a SqlCommandSet correctly. + /// [Theory] [InlineData(new byte[] { 1, 2, 3 })] [InlineData(new char[] { '1', '2', '3' })] @@ -84,6 +106,9 @@ public void AppendParameterArrayWithSize(object array) Assert.Equal(2, result.Size); } + /// + /// Verifies that a SqlParameter containing a scalar value round-trips through a SqlCommandSet correctly. + /// [Fact] public void GetParameter() { @@ -98,6 +123,10 @@ public void GetParameter() Assert.Equal("value", (string)result.Value); } + /// + /// Verifies that SqlCommandSet.GetParameterCount returns the correct number of parameters for a command + /// at the correct index. + /// [Fact] public void GetParameterCount() { @@ -112,6 +141,9 @@ public void GetParameterCount() Assert.Equal(2, count); } + /// + /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when an invalid CommandBehavior is specified. + /// [Fact] public void InvalidCommandBehaviorValidateCommandBehavior_Throws() { @@ -121,6 +153,9 @@ public void InvalidCommandBehaviorValidateCommandBehavior_Throws() Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); } + /// + /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when a valid but unsupported CommandBehavior is specified. + /// [Fact] public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() { From ef3927539c7993145262afe940f329608c3bcaff Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sat, 28 Jun 2025 23:48:58 +0100 Subject: [PATCH 10/15] Rename SqlBufferTests to align with standard --- .../Data/SqlClient/{SqlBufferTests.cs => SqlBufferTest.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/{SqlBufferTests.cs => SqlBufferTest.cs} (99%) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs rename to src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs index f8966b1b26..214ac01751 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.Data.SqlClient.UnitTests /// Several methods in this class are internal. This is because their parameters are of SqlBuffer.StorageType, /// which is non-public. /// - public sealed class SqlBufferTests + public sealed class SqlBufferTest { private readonly SqlBuffer _target = new(); From 2bcd59a556128c6f67d7034b06109db88b773fb9 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:05:11 +0100 Subject: [PATCH 11/15] Adhere to project code style: namespace statements --- .../SqlClient/LocalAppContextSwitchesTest.cs | 41 +- .../Microsoft/Data/SqlClient/SqlBufferTest.cs | 269 ++++++------ .../Data/SqlClient/SqlCommandSetTest.cs | 383 +++++++++--------- 3 files changed, 345 insertions(+), 348 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index ba58ab292e..ec61937db1 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -5,30 +5,29 @@ using System; using Xunit; -namespace Microsoft.Data.SqlClient.UnitTests +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Provides unit tests for verifying the default values of all SqlClient-specific AppContext switches. +/// +public class LocalAppContextSwitchesTest { /// - /// Provides unit tests for verifying the default values of all SqlClient-specific AppContext switches. + /// Tests the default values of every AppContext switch used by SqlClient. /// - public class LocalAppContextSwitchesTest + [Fact] + public void TestDefaultAppContextSwitchValues() { - /// - /// Tests the default values of every AppContext switch used by SqlClient. - /// - [Fact] - public void TestDefaultAppContextSwitchValues() - { - Assert.False(LocalAppContextSwitches.LegacyRowVersionNullBehavior); - Assert.False(LocalAppContextSwitches.SuppressInsecureTlsWarning); - Assert.False(LocalAppContextSwitches.MakeReadAsyncBlocking); - Assert.True(LocalAppContextSwitches.UseMinimumLoginTimeout); - Assert.True(LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour); - Assert.False(LocalAppContextSwitches.UseCompatibilityProcessSni); - Assert.False(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); - Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); - #if NETFRAMEWORK - Assert.False(LocalAppContextSwitches.DisableTnirByDefault); - #endif - } + Assert.False(LocalAppContextSwitches.LegacyRowVersionNullBehavior); + Assert.False(LocalAppContextSwitches.SuppressInsecureTlsWarning); + Assert.False(LocalAppContextSwitches.MakeReadAsyncBlocking); + Assert.True(LocalAppContextSwitches.UseMinimumLoginTimeout); + Assert.True(LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour); + Assert.False(LocalAppContextSwitches.UseCompatibilityProcessSni); + Assert.False(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); + Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); + #if NETFRAMEWORK + Assert.False(LocalAppContextSwitches.DisableTnirByDefault); + #endif } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs index 214ac01751..0b2b1fa998 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlBufferTest.cs @@ -6,144 +6,143 @@ using System.Data.SqlTypes; using Xunit; -namespace Microsoft.Data.SqlClient.UnitTests +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Tests that null and non-null values assigned to the SqlBuffer round-trip correctly to their CLR and their +/// their SqlTypes representations. +/// +/// +/// Several methods in this class are internal. This is because their parameters are of SqlBuffer.StorageType, +/// which is non-public. +/// +public sealed class SqlBufferTest { + private readonly SqlBuffer _target = new(); + + /// + /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its Guid property + /// throws a SqlNullValueException. + /// + [Fact] + public void GuidShouldThrowWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Throws(() => _target.Guid); + } + + /// + /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its Guid property throws + /// a SqlNullValueException. + /// + [Theory] + [InlineData(SqlBuffer.StorageType.Guid)] + [InlineData(SqlBuffer.StorageType.SqlGuid)] + internal void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) + { + _target.SetToNullOfType(storageType); + + Assert.Throws(() => _target.Guid); + } + + /// + /// Verifies that the Guid property round-trips correctly. + /// + [Fact] + public void GuidShouldReturnWhenGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.Guid = expected; + + Assert.Equal(expected, _target.Guid); + } + + /// + /// Verifies that the SqlGuid property round-trips to the Guid property correctly. + /// + [Fact] + public void GuidShouldReturnExpectedWhenSqlGuidIsSet() + { + var expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.Guid); + } + /// - /// Tests that null and non-null values assigned to the SqlBuffer round-trip correctly to their CLR and their - /// their SqlTypes representations. + /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its SqlGuid property returns + /// SqlGuid.Null. /// - /// - /// Several methods in this class are internal. This is because their parameters are of SqlBuffer.StorageType, - /// which is non-public. - /// - public sealed class SqlBufferTest + [Theory] + [InlineData(SqlBuffer.StorageType.Guid)] + [InlineData(SqlBuffer.StorageType.SqlGuid)] + internal void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) { - private readonly SqlBuffer _target = new(); - - /// - /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its Guid property - /// throws a SqlNullValueException. - /// - [Fact] - public void GuidShouldThrowWhenSqlGuidNullIsSet() - { - _target.SqlGuid = SqlGuid.Null; - - Assert.Throws(() => _target.Guid); - } - - /// - /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its Guid property throws - /// a SqlNullValueException. - /// - [Theory] - [InlineData(SqlBuffer.StorageType.Guid)] - [InlineData(SqlBuffer.StorageType.SqlGuid)] - internal void GuidShouldThrowWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) - { - _target.SetToNullOfType(storageType); - - Assert.Throws(() => _target.Guid); - } - - /// - /// Verifies that the Guid property round-trips correctly. - /// - [Fact] - public void GuidShouldReturnWhenGuidIsSet() - { - var expected = Guid.NewGuid(); - _target.Guid = expected; - - Assert.Equal(expected, _target.Guid); - } - - /// - /// Verifies that the SqlGuid property round-trips to the Guid property correctly. - /// - [Fact] - public void GuidShouldReturnExpectedWhenSqlGuidIsSet() - { - var expected = Guid.NewGuid(); - _target.SqlGuid = expected; - - Assert.Equal(expected, _target.Guid); - } - - /// - /// Verifies that if a SqlBuffer is set to null of type Guid or SqlGuid, accessing its SqlGuid property returns - /// SqlGuid.Null. - /// - [Theory] - [InlineData(SqlBuffer.StorageType.Guid)] - [InlineData(SqlBuffer.StorageType.SqlGuid)] - internal void SqlGuidShouldReturnSqlNullWhenSetToNullOfTypeIsCalled(SqlBuffer.StorageType storageType) - { - _target.SetToNullOfType(storageType); - - Assert.Equal(SqlGuid.Null, _target.SqlGuid); - } - - /// - /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its SqlGuid property - /// returns SqlGuid.Null. - /// - [Fact] - public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() - { - _target.SqlGuid = SqlGuid.Null; - - Assert.Equal(SqlGuid.Null, _target.SqlGuid); - } - - /// - /// Verifies that the Guid property round-trips to the SqlGuid property correctly. - /// - [Fact] - public void SqlGuidShouldReturnExpectedWhenGuidIsSet() - { - var guid = Guid.NewGuid(); - SqlGuid expected = guid; - _target.Guid = guid; - - Assert.Equal(expected, _target.SqlGuid); - } - - /// - /// Verifies that the SqlGuid property round-trips correctly. - /// - [Fact] - public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() - { - SqlGuid expected = Guid.NewGuid(); - _target.SqlGuid = expected; - - Assert.Equal(expected, _target.SqlGuid); - } - - /// - /// Verifies that the Guid property round-trips to the SqlValue property correctly. - /// - [Fact] - public void SqlValueShouldReturnExpectedWhenGuidIsSet() - { - var guid = Guid.NewGuid(); - SqlGuid expected = guid; - _target.Guid = guid; - - Assert.Equal(expected, _target.SqlValue); - } - - /// - /// Verifies that the SqlGuid property round-trips to the SqlValue property correctly. - /// - [Fact] - public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() - { - SqlGuid expected = Guid.NewGuid(); - _target.SqlGuid = expected; - - Assert.Equal(expected, _target.SqlValue); - } + _target.SetToNullOfType(storageType); + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + /// + /// Verifies that if a SqlBuffer is directly assigned the value of SqlGuid.Null, accessing its SqlGuid property + /// returns SqlGuid.Null. + /// + [Fact] + public void SqlGuidShouldReturnSqlGuidNullWhenSqlGuidNullIsSet() + { + _target.SqlGuid = SqlGuid.Null; + + Assert.Equal(SqlGuid.Null, _target.SqlGuid); + } + + /// + /// Verifies that the Guid property round-trips to the SqlGuid property correctly. + /// + [Fact] + public void SqlGuidShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlGuid); + } + + /// + /// Verifies that the SqlGuid property round-trips correctly. + /// + [Fact] + public void SqlGuidShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlGuid); + } + + /// + /// Verifies that the Guid property round-trips to the SqlValue property correctly. + /// + [Fact] + public void SqlValueShouldReturnExpectedWhenGuidIsSet() + { + var guid = Guid.NewGuid(); + SqlGuid expected = guid; + _target.Guid = guid; + + Assert.Equal(expected, _target.SqlValue); + } + + /// + /// Verifies that the SqlGuid property round-trips to the SqlValue property correctly. + /// + [Fact] + public void SqlValueShouldReturnExpectedWhenSqlGuidIsSet() + { + SqlGuid expected = Guid.NewGuid(); + _target.SqlGuid = expected; + + Assert.Equal(expected, _target.SqlValue); } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 6b85595a7a..6c4dfdab99 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -4,208 +4,207 @@ using System.Reflection; using Xunit; -namespace Microsoft.Data.SqlClient.UnitTests +namespace Microsoft.Data.SqlClient.UnitTests; + +/// +/// Provides unit tests for verifying the behavior of the SqlCommandSet class. +/// +public class SqlCommandSetTest { /// - /// Provides unit tests for verifying the behavior of the SqlCommandSet class. + /// Verifies that key properties throw an ObjectDisposedException after the SqlCommandSet has been disposed. /// - public class SqlCommandSetTest + /// + /// These properties are private, requiring reflection to access. + /// + [Theory] + [InlineData("BatchCommand")] + [InlineData("CommandList")] + public void GetDisposedProperty_Throws(string propertyName) { - /// - /// Verifies that key properties throw an ObjectDisposedException after the SqlCommandSet has been disposed. - /// - /// - /// These properties are private, requiring reflection to access. - /// - [Theory] - [InlineData("BatchCommand")] - [InlineData("CommandList")] - public void GetDisposedProperty_Throws(string propertyName) - { - SqlCommandSet cmdSet = new(); - cmdSet.Dispose(); - - ObjectDisposedException ex = GetProperty_Throws(cmdSet, propertyName); - Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Verifies that adding a SqlCommand with an empty CommandText to a SqlCommandSet throws an InvalidOperationException. - /// - [Fact] - public void AppendCommandWithEmptyString_Throws() - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = new(""); - - InvalidOperationException ex = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("CommandText property has not been initialized", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Returns a set of invalid CommandType values. - /// - /// - /// - /// .NET Framework puts system enums in the Global Assembly Cache (GAC), and xUnit refuses to serialize enums that live there. - /// We make these system enum values a method, then disable enumeration of the test data to avoid warnings on the console when running tests. - /// - public static TheoryData CommandTypeData() - => new(CommandType.TableDirect, (CommandType)5); - - /// - /// Verifies that adding a SqlCommand with an invalid CommandType to a SqlCommandSet throws an ArgumentOutOfRangeException. - /// - [Theory] - [MemberData( - nameof(CommandTypeData) + SqlCommandSet cmdSet = new(); + cmdSet.Dispose(); + + ObjectDisposedException ex = GetProperty_Throws(cmdSet, propertyName); + Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that adding a SqlCommand with an empty CommandText to a SqlCommandSet throws an InvalidOperationException. + /// + [Fact] + public void AppendCommandWithEmptyString_Throws() + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = new(""); + + InvalidOperationException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandText property has not been initialized", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Returns a set of invalid CommandType values. + /// + /// + /// + /// .NET Framework puts system enums in the Global Assembly Cache (GAC), and xUnit refuses to serialize enums that live there. + /// We make these system enum values a method, then disable enumeration of the test data to avoid warnings on the console when running tests. + /// + public static TheoryData CommandTypeData() + => new(CommandType.TableDirect, (CommandType)5); + + /// + /// Verifies that adding a SqlCommand with an invalid CommandType to a SqlCommandSet throws an ArgumentOutOfRangeException. + /// + [Theory] + [MemberData( + nameof(CommandTypeData) #if NETFRAMEWORK - , DisableDiscoveryEnumeration = true + , DisableDiscoveryEnumeration = true #endif - )] - public void AppendBadCommandType_Throws(CommandType commandType) - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = GenerateBadCommand(commandType); - - ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("CommandType", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Verifies that adding a SqlCommand containing a SqlParameter with an invalid name to a SqlCommandSet throws an ArgumentException. - /// - [Fact] - public void AppendBadParameterName_Throws() - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = new("Test"); - cmd.CommandType = CommandType.Text; - cmd.Parameters.Add(new SqlParameter("Test1;=", "1")); - - ArgumentException ex = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("not valid", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Verifies that a SqlParameter containing an array round-trips through a SqlCommandSet correctly. - /// - [Theory] - [InlineData(new byte[] { 1, 2, 3 })] - [InlineData(new char[] { '1', '2', '3' })] - public void AppendParameterArrayWithSize(object array) - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = new("Test"); - cmd.CommandType = CommandType.StoredProcedure; - cmd.Parameters.Add(new SqlParameter("@array", array) { Size = 2 }); - cmdSet.Append(cmd); - SqlParameter result = cmdSet.GetParameter(0, 0); - Assert.NotNull(result); - Assert.Equal("@array", result.ParameterName); - Assert.Equal(2, result.Size); - } - - /// - /// Verifies that a SqlParameter containing a scalar value round-trips through a SqlCommandSet correctly. - /// - [Fact] - public void GetParameter() - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = new("Test"); - cmd.CommandType = CommandType.Text; - cmd.Parameters.Add(new SqlParameter("@text", "value")); - cmdSet.Append(cmd); - SqlParameter result = cmdSet.GetParameter(0, 0); - Assert.NotNull(result); - Assert.Equal("@text", result.ParameterName); - Assert.Equal("value", (string)result.Value); - } - - /// - /// Verifies that SqlCommandSet.GetParameterCount returns the correct number of parameters for a command - /// at the correct index. - /// - [Fact] - public void GetParameterCount() - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = new("Test"); - cmd.CommandType = CommandType.Text; - cmd.Parameters.Add(new SqlParameter("@abc", "1")); - cmd.Parameters.Add(new SqlParameter("@test", "2")); - cmdSet.Append(cmd); - int index = 0; - int count = cmdSet.GetParameterCount(index); - Assert.Equal(2, count); - } - - /// - /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when an invalid CommandBehavior is specified. - /// - [Fact] - public void InvalidCommandBehaviorValidateCommandBehavior_Throws() - { - SqlCommandSet cmdSet = new(); + )] + public void AppendBadCommandType_Throws(CommandType commandType) + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = GenerateBadCommand(commandType); + + ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("CommandType", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that adding a SqlCommand containing a SqlParameter with an invalid name to a SqlCommandSet throws an ArgumentException. + /// + [Fact] + public void AppendBadParameterName_Throws() + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = new("Test"); + cmd.CommandType = CommandType.Text; + cmd.Parameters.Add(new SqlParameter("Test1;=", "1")); - ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); - Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); - } + ArgumentException ex = Assert.Throws(() => cmdSet.Append(cmd)); + Assert.Contains("not valid", ex.Message, StringComparison.OrdinalIgnoreCase); + } - /// - /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when a valid but unsupported CommandBehavior is specified. - /// - [Fact] - public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() - { - SqlCommandSet cmdSet = new(); - - ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); - Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); - } - - #region private methods - - private static T GetProperty_Throws(SqlCommandSet instance, string propertyName) - where T : Exception - => InvokeMethod_Throws(instance, - typeof(SqlCommandSet) - .GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance) - .GetGetMethod(true), - []); - - private static T InvokeMethod_Throws(SqlCommandSet instance, string methodName, params object[] values) - where T : Exception - => InvokeMethod_Throws(instance, - typeof(SqlCommandSet) - .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance), - values); - - private static T InvokeMethod_Throws(SqlCommandSet instance, MethodInfo methodInfo, params object[] values) - where T : Exception + /// + /// Verifies that a SqlParameter containing an array round-trips through a SqlCommandSet correctly. + /// + [Theory] + [InlineData(new byte[] { 1, 2, 3 })] + [InlineData(new char[] { '1', '2', '3' })] + public void AppendParameterArrayWithSize(object array) + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = new("Test"); + cmd.CommandType = CommandType.StoredProcedure; + cmd.Parameters.Add(new SqlParameter("@array", array) { Size = 2 }); + cmdSet.Append(cmd); + SqlParameter result = cmdSet.GetParameter(0, 0); + Assert.NotNull(result); + Assert.Equal("@array", result.ParameterName); + Assert.Equal(2, result.Size); + } + + /// + /// Verifies that a SqlParameter containing a scalar value round-trips through a SqlCommandSet correctly. + /// + [Fact] + public void GetParameter() + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = new("Test"); + cmd.CommandType = CommandType.Text; + cmd.Parameters.Add(new SqlParameter("@text", "value")); + cmdSet.Append(cmd); + SqlParameter result = cmdSet.GetParameter(0, 0); + Assert.NotNull(result); + Assert.Equal("@text", result.ParameterName); + Assert.Equal("value", (string)result.Value); + } + + /// + /// Verifies that SqlCommandSet.GetParameterCount returns the correct number of parameters for a command + /// at the correct index. + /// + [Fact] + public void GetParameterCount() + { + SqlCommandSet cmdSet = new(); + using SqlCommand cmd = new("Test"); + cmd.CommandType = CommandType.Text; + cmd.Parameters.Add(new SqlParameter("@abc", "1")); + cmd.Parameters.Add(new SqlParameter("@test", "2")); + cmdSet.Append(cmd); + int index = 0; + int count = cmdSet.GetParameterCount(index); + Assert.Equal(2, count); + } + + /// + /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when an invalid CommandBehavior is specified. + /// + [Fact] + public void InvalidCommandBehaviorValidateCommandBehavior_Throws() + { + SqlCommandSet cmdSet = new(); + + ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); + Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Verifies that SqlCommandSet.ValidateCommandBehavior throws an ArgumentOutOfRangeException when a valid but unsupported CommandBehavior is specified. + /// + [Fact] + public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() + { + SqlCommandSet cmdSet = new(); + + ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); + Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + #region private methods + + private static T GetProperty_Throws(SqlCommandSet instance, string propertyName) + where T : Exception + => InvokeMethod_Throws(instance, + typeof(SqlCommandSet) + .GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance) + .GetGetMethod(true), + []); + + private static T InvokeMethod_Throws(SqlCommandSet instance, string methodName, params object[] values) + where T : Exception + => InvokeMethod_Throws(instance, + typeof(SqlCommandSet) + .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance), + values); + + private static T InvokeMethod_Throws(SqlCommandSet instance, MethodInfo methodInfo, params object[] values) + where T : Exception + { + return Assert.Throws(() => { - return Assert.Throws(() => + try { - try - { - methodInfo.Invoke(instance, values); - } - catch (TargetInvocationException e) - { - throw e.InnerException; - } - }); - } - - private static SqlCommand GenerateBadCommand(CommandType cType) - { - SqlCommand cmd = new("Test"); - // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. - typeof(SqlCommand).GetField("_commandType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cmd, cType); + methodInfo.Invoke(instance, values); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + }); + } + + private static SqlCommand GenerateBadCommand(CommandType cType) + { + SqlCommand cmd = new("Test"); + // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. + typeof(SqlCommand).GetField("_commandType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cmd, cType); - return cmd; - } - #endregion + return cmd; } + #endregion } From c7760a8ba3a9150e4d8e673270e03874502ac2e5 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:06:02 +0100 Subject: [PATCH 12/15] Remove redundant using statement --- .../Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs | 1 - .../UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index ec61937db1..90ee094f8f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -2,7 +2,6 @@ // 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 Xunit; namespace Microsoft.Data.SqlClient.UnitTests; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 6c4dfdab99..a14de1303e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Data; using System.Reflection; using Xunit; From 95b1dccee776779c3c69d2499c4d30222c65f322 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Sun, 29 Jun 2025 19:06:37 +0100 Subject: [PATCH 13/15] Add missing license statement --- .../UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index a14de1303e..2406beb714 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -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.Data; using System.Reflection; using Xunit; From af251dcbee14763b7d164f45554531d801482c77 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:44:29 +0100 Subject: [PATCH 14/15] Remove all internal reflection on SqlCommandSetTest --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../Microsoft/Data/SqlClient/SqlCommandSet.cs | 6 +- .../Data/SqlClient/SqlCommandSetTest.cs | 55 +++---------------- 4 files changed, 14 insertions(+), 51 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index 47349b492e..ed240bd41f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -114,7 +114,7 @@ protected override void AfterCleared(SqlCommand owner) } private string _commandText; - private CommandType _commandType; + internal CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 97212843f5..0703796e37 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -119,7 +119,7 @@ protected override void AfterCleared(SqlCommand owner) } private string _commandText; - private CommandType _commandType; + internal CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs index b26af7fc69..5a025c786c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs @@ -30,7 +30,7 @@ internal SqlCommandSet() : base() _commandList = new List(); } - private SqlCommand BatchCommand + internal SqlCommand BatchCommand { get { @@ -45,7 +45,7 @@ private SqlCommand BatchCommand internal int CommandCount => CommandList.Count; - private List CommandList + internal List CommandList { get { @@ -279,7 +279,7 @@ internal bool GetBatchedAffected(int commandIdentifier, out int recordsAffected, internal int GetParameterCount(int commandIndex) => CommandList[commandIndex].Parameters.Count; - private void ValidateCommandBehavior(string method, CommandBehavior behavior) + internal void ValidateCommandBehavior(string method, CommandBehavior behavior) { if (0 != (behavior & ~(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection))) { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 2406beb714..490b98b17e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -4,7 +4,6 @@ using System; using System.Data; -using System.Reflection; using Xunit; namespace Microsoft.Data.SqlClient.UnitTests; @@ -17,18 +16,16 @@ public class SqlCommandSetTest /// /// Verifies that key properties throw an ObjectDisposedException after the SqlCommandSet has been disposed. /// - /// - /// These properties are private, requiring reflection to access. - /// - [Theory] - [InlineData("BatchCommand")] - [InlineData("CommandList")] - public void GetDisposedProperty_Throws(string propertyName) + [Fact] + public void GetDisposedProperty_Throws() { SqlCommandSet cmdSet = new(); cmdSet.Dispose(); - ObjectDisposedException ex = GetProperty_Throws(cmdSet, propertyName); + ObjectDisposedException ex = Assert.Throws(() => _ = cmdSet.BatchCommand); + Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); + + ex = Assert.Throws(() => _ = cmdSet.CommandList); Assert.Contains("disposed", ex.Message, StringComparison.OrdinalIgnoreCase); } @@ -152,7 +149,7 @@ public void InvalidCommandBehaviorValidateCommandBehavior_Throws() { SqlCommandSet cmdSet = new(); - ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", (CommandBehavior)64); + ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.ValidateCommandBehavior("ExecuteNonQuery", (CommandBehavior)64)); Assert.Contains("CommandBehavior", ex.Message, StringComparison.OrdinalIgnoreCase); } @@ -164,50 +161,16 @@ public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() { SqlCommandSet cmdSet = new(); - ArgumentOutOfRangeException ex = InvokeMethod_Throws(cmdSet, "ValidateCommandBehavior", "ExecuteNonQuery", CommandBehavior.KeyInfo); + ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.ValidateCommandBehavior("ExecuteNonQuery", CommandBehavior.KeyInfo)); Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); } - #region private methods - - private static T GetProperty_Throws(SqlCommandSet instance, string propertyName) - where T : Exception - => InvokeMethod_Throws(instance, - typeof(SqlCommandSet) - .GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance) - .GetGetMethod(true), - []); - - private static T InvokeMethod_Throws(SqlCommandSet instance, string methodName, params object[] values) - where T : Exception - => InvokeMethod_Throws(instance, - typeof(SqlCommandSet) - .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance), - values); - - private static T InvokeMethod_Throws(SqlCommandSet instance, MethodInfo methodInfo, params object[] values) - where T : Exception - { - return Assert.Throws(() => - { - try - { - methodInfo.Invoke(instance, values); - } - catch (TargetInvocationException e) - { - throw e.InnerException; - } - }); - } - private static SqlCommand GenerateBadCommand(CommandType cType) { SqlCommand cmd = new("Test"); // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. - typeof(SqlCommand).GetField("_commandType", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(cmd, cType); + cmd._commandType = cType; return cmd; } - #endregion } From 82e60e4a24bbfdd23d59dfe9ad342432f38d12b3 Mon Sep 17 00:00:00 2001 From: Edward Neal <55035479+edwardneal@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:55:36 +0100 Subject: [PATCH 15/15] Remove unnecessary validation logic (and covering test) --- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../Microsoft/Data/SqlClient/SqlCommand.cs | 2 +- .../Microsoft/Data/SqlClient/SqlCommandSet.cs | 16 ++------ .../Data/SqlClient/SqlCommandSetTest.cs | 39 ------------------- 4 files changed, 6 insertions(+), 53 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index ed240bd41f..47349b492e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -114,7 +114,7 @@ protected override void AfterCleared(SqlCommand owner) } private string _commandText; - internal CommandType _commandType; + private CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index 0703796e37..97212843f5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -119,7 +119,7 @@ protected override void AfterCleared(SqlCommand owner) } private string _commandText; - internal CommandType _commandType; + private CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; private bool _designTimeInvisible; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs index 5a025c786c..7265d82175 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandSet.cs @@ -98,24 +98,16 @@ internal void Append(SqlCommand command) { ADP.CheckArgumentNull(command, nameof(command)); SqlClientEventSource.Log.TryTraceEvent("SqlCommandSet.Append | API | Object Id {0}, Command '{1}', Parameter Count {2}", ObjectID, command.ObjectID, command.Parameters.Count); + + // SqlCommandSet only supports commands of type Text or StoredProcedure. This aligns with SqlCommand (validated by its CommandType setter.) + Debug.Assert(command.CommandType is CommandType.Text or CommandType.StoredProcedure); + string cmdText = command.CommandText; if (string.IsNullOrEmpty(cmdText)) { throw ADP.CommandTextRequired(nameof(Append)); } - CommandType commandType = command.CommandType; - switch (commandType) - { - case CommandType.Text: - case CommandType.StoredProcedure: - break; - case CommandType.TableDirect: - throw SQL.NotSupportedCommandType(commandType); - default: - throw ADP.InvalidCommandType(commandType); - } - SqlParameterCollection parameters = null; SqlParameterCollection collection = command.Parameters; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs index 490b98b17e..fbd6d6657f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlCommandSetTest.cs @@ -42,36 +42,6 @@ public void AppendCommandWithEmptyString_Throws() Assert.Contains("CommandText property has not been initialized", ex.Message, StringComparison.OrdinalIgnoreCase); } - /// - /// Returns a set of invalid CommandType values. - /// - /// - /// - /// .NET Framework puts system enums in the Global Assembly Cache (GAC), and xUnit refuses to serialize enums that live there. - /// We make these system enum values a method, then disable enumeration of the test data to avoid warnings on the console when running tests. - /// - public static TheoryData CommandTypeData() - => new(CommandType.TableDirect, (CommandType)5); - - /// - /// Verifies that adding a SqlCommand with an invalid CommandType to a SqlCommandSet throws an ArgumentOutOfRangeException. - /// - [Theory] - [MemberData( - nameof(CommandTypeData) -#if NETFRAMEWORK - , DisableDiscoveryEnumeration = true -#endif - )] - public void AppendBadCommandType_Throws(CommandType commandType) - { - SqlCommandSet cmdSet = new(); - using SqlCommand cmd = GenerateBadCommand(commandType); - - ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.Append(cmd)); - Assert.Contains("CommandType", ex.Message, StringComparison.OrdinalIgnoreCase); - } - /// /// Verifies that adding a SqlCommand containing a SqlParameter with an invalid name to a SqlCommandSet throws an ArgumentException. /// @@ -164,13 +134,4 @@ public void NotSupportedCommandBehaviorValidateCommandBehavior_Throws() ArgumentOutOfRangeException ex = Assert.Throws(() => cmdSet.ValidateCommandBehavior("ExecuteNonQuery", CommandBehavior.KeyInfo)); Assert.Contains("not supported", ex.Message, StringComparison.OrdinalIgnoreCase); } - - private static SqlCommand GenerateBadCommand(CommandType cType) - { - SqlCommand cmd = new("Test"); - // There's validation done on the CommandType property, but we need to create one that avoids the check for the test case. - cmd._commandType = cType; - - return cmd; - } }