diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 1943ef0bbc..95595da81f 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -118,6 +118,7 @@ Manual Tests require the below setup to run: |AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ | |AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ | |LocalDbAppName | (Optional) If Local Db Testing is supported, this property configures the name of Local DB App instance available in client environment. Empty string value disables Local Db testing. | Name of Local Db App to connect to.| + |LocalDbSharedInstanceName | (Optional) If LocalDB testing is supported and the instance is shared, this property configures the name of the shared instance of LocalDB to connect to. | Name of shared instance of LocalDB. | |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| |FileStreamDirectory | (Optional) If File Stream is enabled on SQL Server, pass local directory path to be used for setting up File Stream enabled database. | `D:\\escaped\\absolute\\path\\to\\directory\\` | |UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index 24c8609876..0af8441333 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -9,6 +9,7 @@ using System.Net.Security; using System.Net.Sockets; using System.Text; +using System.Text.RegularExpressions; namespace Microsoft.Data.SqlClient.SNI { @@ -142,7 +143,7 @@ private static bool IsErrorStatus(SecurityStatusPalErrorCode errorCode) /// Used for DNS Cache /// Used for DNS Cache /// SNI handle - internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, + internal static SNIHandle CreateConnectionHandle(string fullServerName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, ref byte[][] spnBuffer, bool flushCache, bool async, bool parallel, bool isIntegratedSecurity, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { instanceName = new byte[1]; @@ -415,6 +416,7 @@ internal enum Protocol { TCP, NP, None, Admin }; private string _workingDataSource; private string _dataSourceAfterTrimmingProtocol; + internal bool IsBadDataSource { get; private set; } = false; internal bool IsSsrpRequired { get; private set; } = false; @@ -472,31 +474,39 @@ private void PopulateProtocol() } } + // LocalDbInstance name always starts with (localdb) + // possible scenarios: + // (localdb)\ + // or (localdb)\. which goes to default localdb + // or (localdb)\.\ internal static string GetLocalDBInstance(string dataSource, out bool error) { string instanceName = null; - string workingDataSource = dataSource.ToLowerInvariant(); - - string[] tokensByBackSlash = workingDataSource.Split(BackSlashCharacter); - + // ReadOnlySpan is not supported in netstandard 2.0, but installing System.Memory solves the issue + ReadOnlySpan input = dataSource.AsSpan().TrimStart(); error = false; - // All LocalDb endpoints are of the format host\instancename where host is always (LocalDb) (case-insensitive) - if (tokensByBackSlash.Length == 2 && LocalDbHost.Equals(tokensByBackSlash[0].TrimStart())) + // NetStandard 2.0 does not support passing a string to ReadOnlySpan + if (input.StartsWith(LocalDbHost.AsSpan().Trim(), StringComparison.InvariantCultureIgnoreCase)) { - if (!string.IsNullOrWhiteSpace(tokensByBackSlash[1])) + // When netcoreapp support for netcoreapp2.1 is dropped these slice calls could be converted to System.Range\System.Index + // Such ad input = input[1..]; + input = input.Slice(LocalDbHost.Length); + if (!input.IsEmpty && input[0] == BackSlashCharacter) { - instanceName = tokensByBackSlash[1].Trim(); + input = input.Slice(1); + } + if (!input.IsEmpty) + { + instanceName = input.Trim().ToString(); } else { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51); error = true; - return null; } } - return instanceName; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index b60b10e5d8..c3aa7b0486 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -39,6 +39,7 @@ public static class DataTestUtility public static readonly string AKVClientId = null; public static readonly string AKVClientSecret = null; public static readonly string LocalDbAppName = null; + public static readonly string LocalDbSharedInstanceName = null; public static List AEConnStrings = new List(); public static List AEConnStringsSetup = new List(); public static bool EnclaveEnabled { get; private set; } = false; @@ -84,6 +85,7 @@ static DataTestUtility() AADServicePrincipalId = c.AADServicePrincipalId; AADServicePrincipalSecret = c.AADServicePrincipalSecret; LocalDbAppName = c.LocalDbAppName; + LocalDbSharedInstanceName = c.LocalDbSharedInstanceName; SupportsIntegratedSecurity = c.SupportsIntegratedSecurity; FileStreamDirectory = c.FileStreamDirectory; EnclaveEnabled = c.EnclaveEnabled; @@ -441,7 +443,8 @@ public static void DropDatabase(SqlConnection sqlConnection, string dbName) cmd.ExecuteNonQuery(); } - public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()); + public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()) && IsIntegratedSecuritySetup(); + public static bool IsLocalDbSharedInstanceSetup() => !string.IsNullOrEmpty(LocalDbSharedInstanceName?.Trim()) && IsIntegratedSecuritySetup(); public static bool IsIntegratedSecuritySetup() => SupportsIntegratedSecurity; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs index 06defb1125..c2512259c4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/LocalDBTest/LocalDBTest.cs @@ -1,7 +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.Collections.Generic; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -9,49 +9,87 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public static class LocalDBTest { private static bool IsLocalDBEnvironmentSet() => DataTestUtility.IsLocalDBInstalled(); + private static bool IsLocalDbSharedInstanceSet() => DataTestUtility.IsLocalDbSharedInstanceSetup(); + private static readonly string s_localDbConnectionString = @$"server=(localdb)\{DataTestUtility.LocalDbAppName}"; + private static readonly string[] s_sharedLocalDbInstances = new string[] { @$"server=(localdb)\.\{DataTestUtility.LocalDbSharedInstanceName}", @$"server=(localdb)\." }; + private static readonly string s_badConnectionString = $@"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;"; + static string LocalDbName = DataTestUtility.LocalDbAppName; + #region LocalDbTests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP [ConditionalFact(nameof(IsLocalDBEnvironmentSet))] - public static void LocalDBConnectionTest() + public static void SqlLocalDbConnectionTest() { - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}"); - builder.IntegratedSecurity = true; - builder.ConnectTimeout = 2; - OpenConnection(builder.ConnectionString); + ConnectionTest(s_localDbConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP [ConditionalFact(nameof(IsLocalDBEnvironmentSet))] public static void LocalDBMarsTest() { - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(@$"server=(localdb)\{DataTestUtility.LocalDbAppName}"); - builder.IntegratedSecurity = true; - builder.MultipleActiveResultSets = true; - builder.ConnectTimeout = 2; - OpenConnection(builder.ConnectionString); + ConnectionWithMarsTest(s_localDbConnectionString); } [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP [ConditionalFact(nameof(IsLocalDBEnvironmentSet))] - public static void InvalidDBTest() + public static void InvalidLocalDBTest() { - using (var connection = new SqlConnection(@$"server=(localdb)\{DataTestUtility.LocalDbAppName};Database=DOES_NOT_EXIST;Pooling=false;")) + using var connection = new SqlConnection(s_badConnectionString); + DataTestUtility.AssertThrowsWrapper(() => connection.Open()); + } + #endregion + + #region SharedLocalDb tests + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP + [ConditionalFact(nameof(IsLocalDbSharedInstanceSet))] + public static void SharedLocalDbMarsTest() + { + foreach (string connectionString in s_sharedLocalDbInstances) { - DataTestUtility.AssertThrowsWrapper(() => connection.Open()); + ConnectionWithMarsTest(connectionString); } } - private static void OpenConnection(string connString) + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // No Registry support on UAP + [ConditionalFact(nameof(IsLocalDbSharedInstanceSet))] + public static void SqlLocalDbSharedInstanceConnectionTest() { - using (SqlConnection connection = new SqlConnection(connString)) + foreach (string connectionString in s_sharedLocalDbInstances) { - connection.Open(); - using (SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection)) - { - var result = command.ExecuteScalar(); - Assert.NotNull(result); - } + ConnectionTest(connectionString); } } + #endregion + + private static void ConnectionWithMarsTest(string connectionString) + { + SqlConnectionStringBuilder builder = new(connectionString) + { + IntegratedSecurity = true, + MultipleActiveResultSets = true, + ConnectTimeout = 2, + Encrypt = false + }; + OpenConnection(builder.ConnectionString); + } + private static void ConnectionTest(string connectionString) + { + SqlConnectionStringBuilder builder = new(connectionString) + { + IntegratedSecurity = true, + ConnectTimeout = 2, + Encrypt = false + }; + OpenConnection(builder.ConnectionString); + } + + private static void OpenConnection(string connString) + { + using SqlConnection connection = new(connString); + connection.Open(); + using SqlCommand command = new SqlCommand("SELECT @@SERVERNAME", connection); + var result = command.ExecuteScalar(); + Assert.NotNull(result); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs index ce1aaaca86..d3413622b2 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Config.cs @@ -24,6 +24,7 @@ public class Config public string AzureKeyVaultClientId = null; public string AzureKeyVaultClientSecret = null; public string LocalDbAppName = null; + public string LocalDbSharedInstanceName = null; public bool EnclaveEnabled = false; public bool TracingEnabled = false; public bool SupportsIntegratedSecurity = false; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json index 6b4e45ef8f..69bacdc3f9 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json @@ -16,6 +16,7 @@ "AzureKeyVaultClientSecret": "", "SupportsIntegratedSecurity": true, "LocalDbAppName": "", + "SupportsFileStream": false, "FileStreamDirectory": "", "UseManagedSNIOnWindows": false, "DNSCachingConnString": "",