diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c79e0e..5aee3926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,18 @@ Update this document for externally visible changes. Put most recent changes first. Once we push a new version to nuget.org add a double hash header for that version. -# 172.64.0 +## 172.76.0 + +- Fix scripting performance #165 +- Add new audit action type INFORMATION_PROTECTION_OPERATION_GROUP +- Add new database level permissions ALTER ANY EXTERNAL MODEL, CREATE EXTERNAL MODEL and ALTER ANY INFORMATION PROTECTION + +## 172.64.0 - Add DesignMode support to `QueryStoreOptions` class - Add Vector data type support -# 172.61.0 +## 172.61.0 - Remove major version restriction on Microsoft.Data.SqlClient dependency Fixes [Issue 188](https://github.com/microsoft/sqlmanagementobjects/issues/188) - Remove net6 binaries diff --git a/Directory.Packages.props b/Directory.Packages.props index b582455c..8c1c2c83 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,16 +2,13 @@ See https://github.com/Microsoft/MSBuildSdks/tree/main/src/CentralPackageVersions This file lists every nuget package dependency and its required version. Do not put Version attributes on PackageReference tags in individual projects. - Add explicit references to transitive package dependencies if the msbuild binlog shows there - are double writes due to multiple versions of a package being imported. - It's common that we could import packages A and B that both depend on package C but 2 different versions. - Without a PackageReference to C, our build could pick up either version of C in a non-deterministic manner. The binlog viewer will show both versions being copied to the output folder as a double write. --> 6.0.0 true + true - + + diff --git a/global.json b/global.json index ffa7d0b4..a6431c20 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.406", + "version": "8.0.409", "rollForward": "latestMinor" }, "msbuild-sdks": { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ce798220..75606b35 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -84,7 +84,7 @@ - 172.18.0 + 172.20.0 Microsoft.Data.SqlClient 5.1.6 diff --git a/src/FunctionalTest/Framework/Helpers/AzureKeyVaultHelper.cs b/src/FunctionalTest/Framework/Helpers/AzureKeyVaultHelper.cs index ea1aea05..aea27c30 100644 --- a/src/FunctionalTest/Framework/Helpers/AzureKeyVaultHelper.cs +++ b/src/FunctionalTest/Framework/Helpers/AzureKeyVaultHelper.cs @@ -3,12 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Security; -using System.Security.Cryptography.X509Certificates; using Azure.Identity; -using Azure.ResourceManager; -using Azure.ResourceManager.Storage; using Azure.Security.KeyVault.Secrets; using Microsoft.SqlServer.ADO.Identity; namespace Microsoft.SqlServer.Test.Manageability.Utils.Helpers @@ -16,14 +12,8 @@ namespace Microsoft.SqlServer.Test.Manageability.Utils.Helpers /// /// Retrieves a decrypted secret from Azure Key Vault or environment using certificate auth or client secret or managed identity /// - public class AzureKeyVaultHelper + public class AzureKeyVaultHelper : ICredential { - /// - /// The set of certificate thumbprints associated with the service principal. - /// If this collection is non-empty, AzureApplicationId and AzureTenantId must also be set to valid values. - /// Set these properties to use certificate-based authentication without relying on environment variables to specify the certificate. - /// - public IEnumerable CertificateThumbprints { get; set; } /// /// The Azure application id associated with the service principal /// @@ -39,14 +29,17 @@ public class AzureKeyVaultHelper /// /// The name of the Azure key vault where test secrets are stored. /// - public string KeyVaultName { get; private set; } - - private static readonly IDictionary secretCache = new Dictionary(); + public string KeyVaultName { get; set; } + + /// + /// The AzureStorageHelper instance used to access the storage account + /// + public AzureStorageHelper StorageHelper { get; set; } + private static readonly IDictionary secretCache = new Dictionary(); private static readonly object syncObj = new object(); public static readonly string SSMS_TEST_SECRET_PREFIX = "SQLA-SSMS-Test-"; private SecretClient secretClient = null; - private ArmClient armClient = null; /// /// Constructs a new AzureKeyVaultHelper that relies on an instance of Azure.Identity.DefaultAzureCredential to access the given vault. @@ -56,7 +49,6 @@ public AzureKeyVaultHelper(string keyVaultName) { KeyVaultName = keyVaultName; - CertificateThumbprints = Enumerable.Empty(); } /// @@ -101,7 +93,7 @@ public string GetDecryptedSecret(string secretName) Console.WriteLine(@"Got Exception fetching secret. Type:{0}, Inner:{1}, Outer:{2}", e.GetType(), e.InnerException, e); throw; } - // Note we aren't bothering to cache secrets we found from GetEnvironmentVariable since that API is already fast + // Note we aren't bothering to cache secrets we found from GetEnvironmentVariable since that API is already fast lock (syncObj) { secretCache[secretName] = secret.StringToSecureString(); @@ -110,71 +102,38 @@ public string GetDecryptedSecret(string secretName) return secret; } - private Azure.Core.TokenCredential GetCredential() + /// + /// Returns a TokenCredential that implements Managed Identity, DefaultAzureCredential, and AzurePipelinesCredential in that order. + /// + /// + public Azure.Core.TokenCredential GetCredential() { + TraceHelper.TraceInformation($"Getting credential for Azure in tenant {AzureTenantId}"); // prefer managed identity then local user on dev machine over the certificate var credentials = new List() { new ManagedIdentityCredential(AzureManagedIdentityClientId), new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeManagedIdentityCredential = true, TenantId = AzureTenantId }) }; - foreach (var thumbprint in CertificateThumbprints ?? Enumerable.Empty()) + var options = new AzureDevOpsFederatedTokenCredentialOptions() { TenantId = AzureTenantId, ClientId = AzureApplicationId }; + if (options.ServiceConnectionId != null) { - var certificate = FindCertificate(thumbprint); - if (certificate != null) - { - credentials.Add(new ClientCertificateCredential(AzureTenantId, AzureApplicationId, certificate)); - } + TraceHelper.TraceInformation($"Adding AzurePipelinesCredential for tenant id {options.TenantId} using service connection {options.ServiceConnectionId}"); + credentials.Insert(0, new AzurePipelinesCredential(options.TenantId, options.ClientId, options.ServiceConnectionId, options.SystemAccessToken)); } - credentials.Add(new AzureDevOpsFederatedTokenCredential(new AzureDevOpsFederatedTokenCredentialOptions() { TenantId = AzureTenantId, ClientId = AzureApplicationId })); return new ChainedTokenCredential(credentials.ToArray()); } + /// + /// Returns the account access key for the given storage account resource id. + /// + /// + /// public string GetStorageAccountAccessKey(string storageAccountResourceId) { TraceHelper.TraceInformation($"Fetching storage access key for {storageAccountResourceId}"); - if (armClient == null) - { - armClient = new ArmClient(GetCredential()); - } - var storageAccount = armClient.GetStorageAccountResource(new Azure.Core.ResourceIdentifier(storageAccountResourceId)); - return storageAccount.GetKeys().First().Value; + return new AzureStorageHelper(storageAccountResourceId, this).GetStorageAccountAccessKey(storageAccountResourceId); } + } - private static X509Certificate2 FindCertificate(string thumbprint) - { - X509Certificate2 certificate = null; - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) - { - var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); - try - { - store.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certificateCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false); - if (certificateCollection.Count == 0) - { - TraceHelper.TraceInformation("Couldn't find Smo cert {0} in local machine. Looking in current user", thumbprint); - var userStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - userStore.Open(OpenFlags.ReadOnly); - try - { - certificateCollection = userStore.Certificates.Find(X509FindType.FindByThumbprint, - thumbprint, validOnly: false); - } - finally - { - userStore.Close(); - } - } - if (certificateCollection.Count != 0) - { - TraceHelper.TraceInformation("Found cert {0}", thumbprint); - certificate = certificateCollection[0]; - } - } - finally - { - store.Close(); - } - } - return certificate; - } - + public interface ICredential + { + Azure.Core.TokenCredential GetCredential(); } } \ No newline at end of file diff --git a/src/FunctionalTest/Framework/Helpers/AzureStorageHelper.cs b/src/FunctionalTest/Framework/Helpers/AzureStorageHelper.cs new file mode 100644 index 00000000..f5a91424 --- /dev/null +++ b/src/FunctionalTest/Framework/Helpers/AzureStorageHelper.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + + +using System; +using System.IO; +using System.Linq; +using Azure.ResourceManager; +using Azure.ResourceManager.Storage; +using Azure.Storage.Blobs; +using Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.Helpers +{ + /// + /// Contains methods for configuring Azure storage access by SQL instances + /// + public class AzureStorageHelper + { + private ArmClient ArmClient => new ArmClient(CredentialProvider.GetCredential()); + + public string Id { get; private set; } + public ICredential CredentialProvider { get; } + + public AzureStorageHelper(string id, ICredential credentialProvider) + { + Id = id.Trim(); + CredentialProvider = credentialProvider; + } + + public string StorageAccountName => Id.Substring(Id.LastIndexOf('/') + 1); + public Credential EnsureCredential(Management.Smo.Server server) + { + var credential = new Credential(server, StorageAccountName + Guid.NewGuid()); + credential.Create(StorageAccountName, GetStorageAccountAccessKey(Id)); + return credential; + } + + public string GetStorageAccountAccessKey(string storageAccountResourceId) + { + TraceHelper.TraceInformation($"Fetching storage access key for {storageAccountResourceId}"); + var storageAccount = ArmClient.GetStorageAccountResource(new Azure.Core.ResourceIdentifier(storageAccountResourceId)); + return storageAccount.GetKeys().First().Value; + } + + /// + /// Downloads a blob from the given blob URL in Azure storage + /// + /// + /// + public string DownloadBlob(string blobUrl) + { + var ext = Path.GetExtension(blobUrl); + var path = Path.GetTempFileName(); + var finalPath = Path.ChangeExtension(path, ext); + File.Move(path, finalPath); + var blobClient = new BlobClient(new Uri(blobUrl), CredentialProvider.GetCredential()); + using (var rsp = blobClient.DownloadTo(finalPath)) + { + if (rsp.IsError) + { + throw new InvalidOperationException($"Unable to download blob '{blobUrl}'. Error: {rsp.ReasonPhrase}"); + } + } + return finalPath; + } + } +} diff --git a/src/FunctionalTest/Framework/Helpers/ConnectionHelpers.cs b/src/FunctionalTest/Framework/Helpers/ConnectionHelpers.cs index bdb5acf8..56be85f4 100644 --- a/src/FunctionalTest/Framework/Helpers/ConnectionHelpers.cs +++ b/src/FunctionalTest/Framework/Helpers/ConnectionHelpers.cs @@ -44,6 +44,10 @@ private class ConnectionData [ThreadStatic] private static ConnectionData _serverConnections; + // Fabric workspaces + [ThreadStatic] + private static List _fabricWorkspaces; + private static ConnectionData ServerConnections => _serverConnections ?? (_serverConnections = LoadConnStrings()); @@ -55,33 +59,51 @@ private class ConnectionData private static ConnectionData LoadConnStrings() { var serverConnections = new ConnectionData(); - IEnumerable descriptors = JsonTestServerSource.TryLoadServerConnections(); - if (!descriptors.Any()) + IEnumerable testServerdescriptors = JsonTestServerSource.TryLoadServerConnections(); + if (!testServerdescriptors.Any()) { var connectionDocument = LoadConnectionDocument(); var akvElement = connectionDocument.XPathSelectElement(@"//AkvAccess"); - if (akvElement != null && akvElement.Element("VaultName") != null) + if (akvElement != null) { - serverConnections.AzureKeyVaultHelper = new AzureKeyVaultHelper(akvElement.Element("VaultName").Value) - { - CertificateThumbprints = akvElement.Elements("Thumbprint").Select(s => s.Value).ToArray() - }; + serverConnections.AzureKeyVaultHelper = new AzureKeyVaultHelper(akvElement.Element("VaultName")?.Value ?? ""); serverConnections.AzureKeyVaultHelper.AzureApplicationId = akvElement.Element("AzureApplicationId")?.Value ?? serverConnections.AzureKeyVaultHelper.AzureApplicationId; serverConnections.AzureKeyVaultHelper.AzureTenantId = akvElement.Element("AzureTenantId")?.Value ?? serverConnections.AzureKeyVaultHelper.AzureTenantId; + var storageElement = akvElement.Element("AzureStorage"); + if (storageElement != null ) + { + serverConnections.AzureKeyVaultHelper.StorageHelper = new AzureStorageHelper(storageElement.Value, + serverConnections.AzureKeyVaultHelper); + } } - descriptors = TestServerDescriptor.GetServerDescriptors(connectionDocument, serverConnections.AzureKeyVaultHelper); + testServerdescriptors = TestServerDescriptor.GetTestDescriptors(connectionDocument, serverConnections.AzureKeyVaultHelper); } - foreach (var descriptor in descriptors) + foreach (var descriptor in testServerdescriptors) { - //SqlTestTargetServersFilter env variable was empty/didn't exist or it contained this server, add to our overall list - var svr = new SMO.Server(new ServerConnection(new SqlConnection(descriptor.ConnectionString))); - TraceHelper.TraceInformation("Loaded connection string '{0}' = '{1}'{2}", - descriptor.Name, - new SqlConnectionStringBuilder(descriptor.ConnectionString).DataSource, - descriptor.BackupConnectionStrings.Any() ? - "Backups = " + descriptor.BackupConnectionStrings : - string.Empty); - serverConnections.ServerDescriptors.Add(descriptor.Name, new Tuple(svr, descriptor)); + if (descriptor is FabricWorkspaceDescriptor workspaceDescriptor) + { + if (_fabricWorkspaces == null) + { + _fabricWorkspaces = new List(); + } + _fabricWorkspaces.Add(workspaceDescriptor); + continue; + } + else if (descriptor is TestServerDescriptor testServerDescriptor) + { + //SqlTestTargetServersFilter env variable was empty/didn't exist or it contained this server, add to our overall list + var svr = new SMO.Server(new ServerConnection(new SqlConnection(testServerDescriptor.ConnectionString))); + var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(testServerDescriptor.ConnectionString); + TraceHelper.TraceInformation("Loaded connection string '{0}' = '{1}'{2}", + descriptor.Name, + sqlConnectionStringBuilder.DataSource, + testServerDescriptor.BackupConnectionStrings.Any() ? + "Backups = " + testServerDescriptor.BackupConnectionStrings : + string.Empty); + // If the connectionString has database information, we need to reuse the database + testServerDescriptor.ReuseExistingDatabase = !string.IsNullOrEmpty(sqlConnectionStringBuilder.InitialCatalog) && sqlConnectionStringBuilder.InitialCatalog != "master"; + serverConnections.ServerDescriptors.Add(testServerDescriptor.Name, new Tuple(svr, testServerDescriptor)); + } } return serverConnections; @@ -90,27 +112,32 @@ private static ConnectionData LoadConnStrings() private static XDocument LoadConnectionDocument() { //Load up the connection string values from the embedded resource - using (Stream connStringsStream = GetConnectionXml()) + using (var connStringsStream = GetConnectionXml()) { return XDocument.Load(XmlReader.Create(connStringsStream)); } } - private static Stream GetConnectionXml() + private static FileStream GetConnectionXml() { - var privateConfigPath = - Environment.GetEnvironmentVariable("TestPath", EnvironmentVariableTarget.Process) ?? + + var defaultConfigPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Environment.CurrentDirectory; + var privateConfigPath = Environment.GetEnvironmentVariable("TestPath", EnvironmentVariableTarget.Process) ?? Environment.GetEnvironmentVariable("TestPath", EnvironmentVariableTarget.User) ?? Environment.GetEnvironmentVariable("TestPath", EnvironmentVariableTarget.Machine) ?? - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Environment.CurrentDirectory; - Trace.TraceInformation($"Using '{privateConfigPath}' to look for ToolsConnectionInfo.xml"); - var privateXmlPath = Path.Combine(privateConfigPath, "ToolsConnectionInfo.xml"); + defaultConfigPath; + + // If the path is a file, use it directly + var privateXmlPath = privateConfigPath.EndsWith(".xml", StringComparison.OrdinalIgnoreCase) + ? File.Exists(privateConfigPath) ? privateConfigPath : Path.Combine(defaultConfigPath, privateConfigPath) + : Path.Combine(privateConfigPath, "ToolsConnectionInfo.xml"); + if (File.Exists(privateXmlPath)) { TraceHelper.TraceInformation("Using private connection data from {0}", privateXmlPath); return File.OpenRead(privateXmlPath); } - throw new InvalidOperationException("No ToolsConnectionInfo.xml file found"); + throw new InvalidOperationException($"No file found at {privateXmlPath}"); } /// @@ -121,7 +148,7 @@ private static Stream GetConnectionXml() /// /// An optional filter that accepts the server friendly name and returns true if it should be included in the search /// - public static IDictionary> GetServerConnections(MethodInfo mi, Func filter = null) + public static IList GetServerConnections(MethodInfo mi, Func filter = null) { var requiredFeatureAttributes = mi.GetCustomAttributes(true) @@ -129,7 +156,7 @@ public static IDictionary> GetSe var requiredFeatures = requiredFeatureAttributes.SelectMany(feature => feature.RequiredFeatures).Distinct().ToArray(); - var serverConnections = new Dictionary>(); + var serverConnections = new List(); //We need to check each of the defined servers to see if they're flagged foreach (KeyValuePair> serverConnectionPair in ServerConnections.ServerDescriptors.Where(kvp => filter?.Invoke(kvp.Key) ?? true)) { @@ -139,67 +166,41 @@ public static IDictionary> GetSe continue; } - // Make sure the test requires at least a feature the server is researved for + // Make sure the test requires at least a feature the server is reserved for if (serverConnectionPair.Value.Item2.ReservedFor.Any() && !serverConnectionPair.Value.Item2.ReservedFor.Intersect(requiredFeatures).Any()) { continue; } - //For SqlSupportedDimensionAttributes we consider the server supported if - //ANY of the attributes return IsSupported is true - - //Note we look at attributes on both the method and the class the method is declared in - var supportedDimensions = - mi.GetCustomAttributes(true) - .Concat(mi.DeclaringType.GetCustomAttributes()).ToArray(); - //If we don't have any SupportedDimensionAttributes we default to it being supported for all servers - var exceptions = new List(); - bool isSupported = supportedDimensions.Length == 0 || - supportedDimensions.Any(a => - { - try - { - return a.IsSupported(serverConnectionPair.Value.Item1, serverConnectionPair.Value.Item2, - serverConnectionPair.Key); - } - catch (Exception e) - { - // Something went wrong, continue on for now to see if any of the UnsupportedAttributes exclude - // this server from even being included (in which case we'll just ignore the error anyways). If - // we DON'T exclude the server though we'll rethrow the error further down so the test still fails - // since we can't tell if the server is actually supported. - exceptions.Add(e); - return true; - } - }); + bool isSupported = EvaluateSupportedDimensions( + mi, + serverConnectionPair.Value.Item1, // SMO.Server + serverConnectionPair.Key, // Server friendly name + exceptions, + (attribute, server, name) => + attribute.IsSupported(server, + serverConnectionPair.Value.Item2, // TestServerDescriptor + name) + ); + if (isSupported) { //For SqlUnsupportedDimensionAttributes we consider the server supported only //if ALL of the unsupported attributes return IsSupported = true //Note we look at attributes on both the method and the class the method is declared in - isSupported &= mi.GetCustomAttributes(true) - .Concat(mi.DeclaringType.GetCustomAttributes()) - .Aggregate(true, (current, unsupportedDimensionAttribute) => - { - try - { - return current & unsupportedDimensionAttribute.IsSupported( - serverConnectionPair.Value.Item1, serverConnectionPair.Value.Item2, - serverConnectionPair.Key); - } - catch (Exception e) - { - // Something went wrong, continue on for now to see if any of the other UnsupportedAttributes exclude - // this server from even being included (in which case we'll just ignore the error anyways). If - // we DON'T exclude the server though we'll rethrow the error further down so the test still fails - // since we can't tell if the server is actually supported. - exceptions.Add(e); - return current; - } - }); + isSupported &= EvaluateUnsupportedDimensions( + mi, + serverConnectionPair.Value.Item1, // SMO.Server + serverConnectionPair.Key, // Server friendly name + exceptions, + (attribute, server, name) => + attribute.IsSupported(server, + serverConnectionPair.Value.Item2, // TestServerDescriptor + name) + ); if (isSupported) { if (exceptions.Any()) @@ -211,13 +212,24 @@ public static IDictionary> GetSe "Exceptions thrown when determining Supported/Unsupported status for server " + serverConnectionPair.Key, exceptions); } //Create a copy of the builder so clients can modify it as they wish without affecting other tests - serverConnections.Add(serverConnectionPair.Key, - serverConnectionPair.Value.Item2.AllConnectionStrings.Select( - connString => new SqlConnectionStringBuilder(connString))); + serverConnections.Add(new ServerConnectionInfo + { + FriendlyName = serverConnectionPair.Key, + ConnectionStrings = serverConnectionPair.Value.Item2.AllConnectionStrings.Select( + connString => new SqlConnectionStringBuilder(connString)), + TestDescriptor = serverConnectionPair.Value.Item2, + }); } } } + // Process Fabric workspaces + if (_fabricWorkspaces != null && _fabricWorkspaces.Any()) + { + ProcessFabricWorkspaces(mi, requiredFeatures, filter) + .ForEach(fabricConnection => serverConnections.Add(fabricConnection)); + } + return serverConnections; } @@ -251,5 +263,136 @@ public static AzureKeyVaultHelper GetAzureKeyVaultHelper() { return ServerConnections.AzureKeyVaultHelper; } + + private static bool EvaluateSupportedDimensions( + MethodInfo mi, + T target, + string targetName, + IList exceptions, + Func evaluateSupportedDimension) + { + //For SqlSupportedDimensionAttributes we consider the server supported if + //ANY of the attributes return IsSupported is true + var supportedDimensions = + mi.GetCustomAttributes(true) + .Concat(mi.DeclaringType.GetCustomAttributes()).ToArray(); + //If we don't have any SupportedDimensionAttributes we default to it being supported for all servers + + bool isSupported = supportedDimensions.Length == 0 || + supportedDimensions.Any(a => + { + try + { + return evaluateSupportedDimension( + a, + target, + targetName); + } + catch (Exception e) + { + // Something went wrong, continue on for now to see if any of the UnsupportedAttributes exclude + // this server from even being included (in which case we'll just ignore the error anyways). If + // we DON'T exclude the server though we'll rethrow the error further down so the test still fails + // since we can't tell if the server is actually supported. + exceptions.Add(e); + return true; + } + }); + return isSupported; + } + private static bool EvaluateUnsupportedDimensions( + MethodInfo mi, + T target, + string targetName, + IList exceptions, + Func evaluateUnsupportedDimension) + { + return mi.GetCustomAttributes(true) + .Concat(mi.DeclaringType.GetCustomAttributes()) + .Aggregate(true, (current, unsupportedDimensionAttribute) => + { + try + { + return current & evaluateUnsupportedDimension(unsupportedDimensionAttribute, target, targetName); + } + catch (Exception e) + { + // Something went wrong, continue on for now to see if any of the other UnsupportedAttributes exclude + // this server from even being included (in which case we'll just ignore the error anyways). If + // we DON'T exclude the server though we'll rethrow the error further down so the test still fails + // since we can't tell if the server is actually supported. + exceptions.Add(e); + return current; + } + }); + } + private static List ProcessFabricWorkspaces(MethodInfo mi, SqlFeature[] requiredFeatures, Func filter = null) + { + var fabricConnections = new List(); + foreach (var workspace in _fabricWorkspaces.Where(workspace => filter?.Invoke(workspace.Name) ?? true)) + { + // Make sure the test needs fabric workspace + if (requiredFeatures.Any(feature => !workspace.EnabledFeatures.Contains(feature))) + { + continue; + } + + // Make sure the test requires at least a feature the workspace is reserved for + if (workspace.ReservedFor.Any() && !workspace.ReservedFor.Intersect(requiredFeatures).Any()) + { + continue; + } + + // Check if the workspace is unsupported based on SqlUnsupportedDimensionAttribute + var exceptions = new List(); + bool isWorkspaceSupported = EvaluateSupportedDimensions( + mi, + workspace, + workspace.Name, + exceptions, + (attribute, targetWorkspace, name) => attribute.IsSupported(targetWorkspace, name) + ); + + + if(isWorkspaceSupported) + { + isWorkspaceSupported &= EvaluateUnsupportedDimensions( + mi, + workspace, + workspace.Name, + exceptions, + (attribute, targetWorkspace, name) => attribute.IsSupported(targetWorkspace, name) + ); + } + + if (!isWorkspaceSupported) + { + // Skip this workspace if it is unsupported + continue; + } + + if (exceptions.Any()) + { + // If exceptions occurred during evaluation, rethrow them + throw new AggregateException( + $"Exceptions thrown when determining Supported/Unsupported status for Fabric workspace {workspace.Name}", + exceptions); + } + try + { + fabricConnections.Add(new ServerConnectionInfo + { + FriendlyName = workspace.Name, + IsFabricWorkspace = true, + TestDescriptor = workspace, + }); + } + catch (Exception ex) + { + Trace.TraceError($"Failed to process Fabric workspace {workspace.WorkspaceName}: {ex.Message}"); + } + } + return fabricConnections; + } } } diff --git a/src/FunctionalTest/Framework/Helpers/FabricDatabaseManager.cs b/src/FunctionalTest/Framework/Helpers/FabricDatabaseManager.cs new file mode 100644 index 00000000..98637161 --- /dev/null +++ b/src/FunctionalTest/Framework/Helpers/FabricDatabaseManager.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.Helpers +{ + public static class FabricDatabaseManager + { + // Path to the fabric-cli executable + private static readonly string FabricCliPath = "fab.exe"; + // Default authentication method + private static readonly string DefaultAuthMethod = ";Authentication=ActiveDirectoryDefault"; + // Cache LogIn Status + private static bool? CachedLogInStatus = null; + private static DateTime LogInStatusCacheExpirationTime; + private static TimeSpan CacheExpirationTime = TimeSpan.FromHours(1); + + /// + /// Creates a fabric database using the fabric-cli tool. + /// + /// The workspace Name where fabric database needs to be created. + /// The name of the database to create. + /// The connection string of the created fabric database. + public static string CreateDatabase(string workspaceName, string dbName) + { + if (string.IsNullOrWhiteSpace(workspaceName)) + { + throw new ArgumentException("Workspace name cannot be null or empty.", nameof(workspaceName)); + } + if (string.IsNullOrWhiteSpace(dbName)) + { + throw new ArgumentException("Database name cannot be null or empty.", nameof(dbName)); + } + + // Make sure we are logged in to Fabric CLI + EnsureFabricCliLogin(); + + try + { + Trace.TraceInformation($"Creating fabric database '{dbName}' using fabric-cli."); + var dbPath = GetDatabasePath(workspaceName, dbName); + string output = ExecuteFabricCliCommand($"create {dbPath}"); + Trace.TraceInformation($"Fabric database created: {output}"); + // Get the connection string for the newly created database + string connectionString = ExecuteFabricCliCommand($"get {dbPath} -q properties.connectionString")+DefaultAuthMethod; + return connectionString; + } + catch (Exception ex) + { + Trace.TraceError($"Failed to create fabric database '{dbName}': {ex.Message}"); + throw new InvalidOperationException($"Error creating fabric database '{dbName}'", ex); + } + } + + /// + /// Drops a fabric database using the fabric-cli tool. + /// + /// Workspace Name + /// The name of the database to drop. + public static void DropDatabase(string workspaceName, string dbName) + { + if (string.IsNullOrWhiteSpace(workspaceName)) + { + throw new ArgumentException("Workspace name cannot be null or empty.", nameof(workspaceName)); + } + + if (string.IsNullOrWhiteSpace(dbName)) + { + throw new ArgumentException("Database name cannot be null or empty.", nameof(dbName)); + } + + // Make sure we are logged in to Fabric CLI + EnsureFabricCliLogin(); + + try + { + Trace.TraceInformation($"Dropping fabric database '{dbName}' using fabric-cli."); + var dbPath = $"/{workspaceName}.Workspace/{dbName}.SQLDatabase"; + ExecuteFabricCliCommand($"rm {dbPath} -f"); + Trace.TraceInformation($"Fabric database dropped: {dbName}"); + } + catch (Exception ex) + { + Trace.TraceError($"Failed to drop fabric database '{dbName}': {ex.Message}"); + throw new InvalidOperationException($"Error dropping fabric database '{dbName}'", ex); + } + } + + private static void EnsureFabricCliLogin() + { + if(Environment.UserInteractive && !IsLoggedInToFabricCli()) + { + ExecuteFabricCliCommand("auth login", true); + // After successful login, update the cached status to true + CachedLogInStatus = true; + LogInStatusCacheExpirationTime = DateTime.UtcNow.Add(CacheExpirationTime); + Trace.TraceInformation("Logged in to Fabric CLI Interactively."); + } + else + { + Trace.TraceInformation("Already logged in to Fabric CLI."); + } + } + private static bool IsLoggedInToFabricCli() + { + // Check if the login status is cached and not expired + if (CachedLogInStatus.HasValue && DateTime.UtcNow < LogInStatusCacheExpirationTime) + { + return CachedLogInStatus.Value; + } + // If Cached status is null or expired, check the login status + try + { + ExecuteFabricCliCommand("auth status", true); + CachedLogInStatus = true; + } + catch (Exception ex) + { + // When not logged in, the command throws an exception - x [AuthenticationFailed] Failed to get access token + Trace.TraceWarning($"Fabric CLI auth status: {ex.Message}"); + return false; + } + + // Set the cache expiration time to 1 hour from now + LogInStatusCacheExpirationTime = DateTime.UtcNow.Add(CacheExpirationTime); + return CachedLogInStatus.Value; + } + + /// + /// Executes Fabric-Cli Command and returns the output. + /// + private static string ExecuteFabricCliCommand(string arguments, bool interactiveInputNeeded = false) + { + string output = string.Empty; + string error = string.Empty; + + var processStartInfo = new ProcessStartInfo + { + FileName = FabricCliPath, + Arguments = arguments, + RedirectStandardOutput = !interactiveInputNeeded, + RedirectStandardError = !interactiveInputNeeded, + UseShellExecute = interactiveInputNeeded, + CreateNoWindow = !interactiveInputNeeded, + }; + using (var process = Process.Start(processStartInfo)) + { + if (process == null) + { + throw new InvalidOperationException("Failed to start Fabric CLI"); + } + + if (!interactiveInputNeeded) + { + output = process.StandardOutput.ReadToEnd(); + error = process.StandardError.ReadToEnd(); + } + + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"Fabric CLI command {processStartInfo.Arguments} failed with exit code {process.ExitCode}. Details: {output} {error}"); + } + return output?.Trim(); + } + } + + private static string GetDatabasePath(string workspaceName, string dbName) + { + return $"/{workspaceName}.Workspace/{dbName}.SQLDatabase"; + } + + } +} diff --git a/src/FunctionalTest/Framework/Helpers/ServerObjectHelpers.cs b/src/FunctionalTest/Framework/Helpers/ServerObjectHelpers.cs index a597f6a0..253e5f9d 100644 --- a/src/FunctionalTest/Framework/Helpers/ServerObjectHelpers.cs +++ b/src/FunctionalTest/Framework/Helpers/ServerObjectHelpers.cs @@ -31,7 +31,7 @@ namespace Microsoft.SqlServer.Test.Manageability.Utils /// public static class ServerObjectHelpers { - static readonly Semaphore azureDbCreateLock = new Semaphore(3, 3); + private static readonly Semaphore azureDbCreateLock = new Semaphore(3, 3); /// /// Restores a database from the specified backup file. It's the callers responsibility to ensure the server @@ -41,37 +41,53 @@ public static class ServerObjectHelpers /// /// Path of backup file for restoring database /// Name of restored database + /// /// Restored database - internal static Database RestoreDatabaseFromBackup(this SMO.Server server, string dbBackupFile, string dbName) + internal static Database RestoreDatabaseFromBackup(this SMO.Server server, string dbBackupFile, string dbName, AzureStorageHelper azureStorageHelper) { - // we may be using a file already on the server with a local path - if (!File.Exists(dbBackupFile)) + + var isUrl = dbBackupFile.StartsWith("http", StringComparison.OrdinalIgnoreCase); + if (isUrl && azureStorageHelper == null) + { + throw new ArgumentNullException("AzureStorageHelper is required to restore from a URL"); + } + // Check if the backup file exists + if (!isUrl && !File.Exists(dbBackupFile)) + { + Trace.TraceWarning($"DB Backup File '{dbBackupFile}' is not visible to the test"); + } + + if (dbBackupFile.IsPackageFileName()) { - Trace.TraceWarning("DB Backup File '{0}' is not visible to the test", dbBackupFile); + return RestoreDatabaseFromPackageFile(server, dbBackupFile, dbName, azureStorageHelper); } - // Get the default location where we should place the restored data files - string dataFilePath = String.IsNullOrEmpty(server.Settings.DefaultFile) ? server.MasterDBPath : server.Settings.DefaultFile; - if (String.IsNullOrWhiteSpace(dataFilePath)) + // Handle traditional backup files + var dataFilePath = string.IsNullOrEmpty(server.Settings.DefaultFile) ? server.MasterDBPath : server.Settings.DefaultFile; + if (string.IsNullOrWhiteSpace(dataFilePath)) { - // We failed to get the path throw new InvalidOperationException("Could not get database file path for restoring from backup"); } - // Get the default location where we should place the restored log files - string logFilePath = String.IsNullOrEmpty(server.Settings.DefaultLog) ? server.MasterDBLogPath : server.Settings.DefaultLog; - if (String.IsNullOrWhiteSpace(logFilePath)) + var logFilePath = string.IsNullOrEmpty(server.Settings.DefaultLog) ? server.MasterDBLogPath : server.Settings.DefaultLog; + if (string.IsNullOrWhiteSpace(logFilePath)) { - // We failed to get the path throw new InvalidOperationException("Could not get database log file path for restoring from backup"); } + Credential azureCredential = null; var restore = new Restore { Database = dbName, - Action = RestoreActionType.Database + Action = RestoreActionType.Database, }; - restore.Devices.AddDevice(dbBackupFile, DeviceType.File); + if (isUrl) + { + azureCredential = azureStorageHelper.EnsureCredential(server); + restore.CredentialName = azureCredential.Name; + restore.BlockSize = 512; + } + restore.Devices.AddDevice(dbBackupFile, isUrl ? DeviceType.Url : DeviceType.File); DataTable dt = restore.ReadFileList(server); //The files need to be moved to avoid collisions @@ -79,16 +95,90 @@ internal static Database RestoreDatabaseFromBackup(this SMO.Server server, strin foreach (DataRow row in dt.Rows) { //Type == L means it's a log file so put it in the log file location, all others go to data file location - string filePath = "L".Equals(row["Type"] as string, StringComparison.OrdinalIgnoreCase) + var filePath = "L".Equals(row["Type"] as string, StringComparison.OrdinalIgnoreCase) ? logFilePath : dataFilePath; //Unique filename so new files don't collide either - string fileName = dbName + "_" + index + Path.GetExtension(row["PhysicalName"] as string); - restore.RelocateFiles.Add(new RelocateFile(row["LogicalName"] as string, Path.Combine(filePath, fileName))); + var fileName = dbName + "_" + index + Path.GetExtension(row["PhysicalName"] as string); + _ = restore.RelocateFiles.Add(new RelocateFile(row["LogicalName"] as string, Path.Combine(filePath, fileName))); ++index; } - TraceHelper.TraceInformation(String.Format("Restoring database '{0}' from backup file '{1}'", dbName, dbBackupFile)); - restore.SqlRestore(server); + + TraceHelper.TraceInformation($"Restoring database '{dbName}' from backup file '{dbBackupFile}'"); + try + { + restore.SqlRestore(server); + } + finally + { + azureCredential?.Drop(); + } + + server.Databases.Refresh(); + return server.Databases[dbName]; + } + + private static Database RestoreDatabaseFromPackageFile(SMO.Server server, string dbBackupFile, string dbName, AzureStorageHelper azureStorageHelper) + { + TraceHelper.TraceInformation($"Restoring database '{dbName}' from package file '{dbBackupFile}' using sqlpackage.exe with a response file"); + + // Construct the sqlpackage.exe command + var action = dbBackupFile.EndsWith(".bacpac", StringComparison.OrdinalIgnoreCase) ? "Import" : "Publish"; + var connStr = new SqlConnectionStringBuilder(server.ConnectionContext.ConnectionString) { InitialCatalog = dbName }; + var sqlPackagePath = "sqlpackage.exe"; // Ensure sqlpackage.exe is in the PATH + var isUrl = dbBackupFile.StartsWith("http", StringComparison.OrdinalIgnoreCase); + if (isUrl) + { + dbBackupFile = azureStorageHelper.DownloadBlob(dbBackupFile); + } + // Create a temporary response file + var responseFilePath = Path.GetTempFileName(); + try + { + // Write parameters to the response file + File.WriteAllLines(responseFilePath, new[] + { + $"/Action:{action}", + $"/SourceFile:\"{dbBackupFile}\"", + $"/TargetConnectionString:\"{connStr.ConnectionString}\"" + }); + + // Execute the command with the response file + var processStartInfo = new ProcessStartInfo + { + FileName = sqlPackagePath, + Arguments = $"@\"{responseFilePath}\"", + RedirectStandardOutput = false, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(processStartInfo)) + { + if (process == null) + { + throw new InvalidOperationException("Failed to start sqlpackage.exe process."); + } + // For some reason, trying to read StandardOut from sqlpackage causes it to hang when there's an error. + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + Trace.TraceError($"*** sqlpackage.exe failed with exit code {process.ExitCode}.{Environment.NewLine}\t{error}"); + throw new InvalidOperationException($"Failed to restore database from package file '{dbBackupFile}': {error}"); + } + } + } + finally + { + if (isUrl) + { + File.Delete(dbBackupFile); + } + File.Delete(responseFilePath); + } server.Databases.Refresh(); return server.Databases[dbName]; @@ -109,7 +199,7 @@ internal static bool CheckDatabaseExistence(this SMO.Server server, string datab conn.Open(); using (var cmd = conn.CreateCommand()) { - cmd.CommandText = String.Format( + cmd.CommandText = string.Format( CultureInfo.InvariantCulture, "SELECT count(*) FROM sys.databases WHERE name = {0}", SmoObjectHelpers.SqlSingleQuoteString(databaseName)); @@ -193,6 +283,7 @@ public static Database CreateDatabaseDefinition( // No valid backup file location so default to creating our own switch (dbAzureDatabaseEdition) { + // VBUMP update compat level on Azure // We set ReadOnly to make sure we exercise code paths in Database.ScriptCreate based // on the property being non-null case SqlTestBase.AzureDatabaseEdition.NotApplicable: @@ -224,7 +315,7 @@ public static Database CreateDatabaseDefinition( Collation = "SQL_Latin1_General_CP1_CS_AS", CatalogCollation = CatalogCollationType.DatabaseDefault, MaxSizeInBytes = 0, - CompatibilityLevel = CompatibilityLevel.Version160, + CompatibilityLevel = CompatibilityLevel.Version170, ReadOnly = false }; break; @@ -238,7 +329,7 @@ public static Database CreateDatabaseDefinition( { AzureEdition = dbAzureDatabaseEdition.ToString(), MaxSizeInBytes = 1024.0 * 1024.0 * 1024.0, - CompatibilityLevel = CompatibilityLevel.Version160, + CompatibilityLevel = CompatibilityLevel.Version170, ReadOnly = false }; //1GB @@ -273,12 +364,31 @@ public static Database CreateDatabaseDefinition( /// The prefix to give the database name /// The Azure edition to use when creating an Azure database /// If specified the database backup file to use to create the server + /// /// The Database object representing the database on the server public static Database CreateDatabaseWithRetry( this SMO.Server server, string dbNamePrefix = "", SqlTestBase.AzureDatabaseEdition dbAzureDatabaseEdition = SqlTestBase.AzureDatabaseEdition.NotApplicable, - string dbBackupFile = "" + string dbBackupFile = "", + bool useEscapedCharacters = true + ) => CreateDatabaseWithRetry(server, new DatabaseParameters + { + NamePrefix = dbNamePrefix, + AzureDatabaseEdition = dbAzureDatabaseEdition, + BackupFile = dbBackupFile, + UseEscapedCharacters = useEscapedCharacters + }); + + /// + /// Creates a database with the specified parameters on the specified server. + /// + /// The server to create the database on + /// + /// The Database object representing the database on the server + public static Database CreateDatabaseWithRetry( + this SMO.Server server, + DatabaseParameters dbParameters ) { Database db = null; @@ -286,11 +396,15 @@ public static Database CreateDatabaseWithRetry( RetryHelper.RetryWhenExceptionThrown( () => { - string databaseName = SmoObjectHelpers.GenerateUniqueObjectName(dbNamePrefix); + var databaseName = SmoObjectHelpers.GenerateUniqueObjectName(dbParameters.NamePrefix, + includeClosingBracket: dbParameters.UseEscapedCharacters, + includeDoubleClosingBracket: dbParameters.UseEscapedCharacters, + includeSingleQuote: dbParameters.UseEscapedCharacters, + includeDoubleSingleQuote: dbParameters.UseEscapedCharacters); try { - - if (string.IsNullOrEmpty(dbBackupFile)) + // Bacpac files have to be applied after the database is created + if (string.IsNullOrEmpty(dbParameters.BackupFile) || dbParameters.BackupFile.IsPackageFileName()) { if (server.DatabaseEngineType != DatabaseEngineType.SqlAzureDatabase) { @@ -301,21 +415,16 @@ public static Database CreateDatabaseWithRetry( TraceHelper.TraceInformation("Creating new database '{0}' on server '{1}'", databaseName, server.Name); - // No valid backup file location so default to creating our own - switch (dbAzureDatabaseEdition) + switch (dbParameters.AzureDatabaseEdition) { // We set ReadOnly to make sure we exercise code paths in Database.ScriptCreate based // on the property being non-null case SqlTestBase.AzureDatabaseEdition.NotApplicable: { - if (server.DatabaseEngineEdition == DatabaseEngineEdition.SqlOnDemand) - { - db = new Database(server, databaseName); - } - else - { - db = new Database(server, databaseName) { ReadOnly = false, CompatibilityLevel = CompatibilityLevel.Version160 }; - } + // VBUMP + db = server.DatabaseEngineEdition == DatabaseEngineEdition.SqlOnDemand + ? new Database(server, databaseName) + : new Database(server, databaseName) { ReadOnly = false, CompatibilityLevel = CompatibilityLevel.Version170 }; break; } case SqlTestBase.AzureDatabaseEdition.DataWarehouse: @@ -323,13 +432,12 @@ public static Database CreateDatabaseWithRetry( db = new Database(server, databaseName, DatabaseEngineEdition.SqlDataWarehouse) { - AzureEdition = dbAzureDatabaseEdition.ToString(), + AzureEdition = dbParameters.AzureDatabaseEdition.ToString(), // newer regions don't support DW100c but dw1000c times out too often AzureServiceObjective = "DW100c", MaxSizeInBytes = 1024.0 * 1024.0 * 1024.0 * 500, ReadOnly = false }; - //500GB break; } case SqlTestBase.AzureDatabaseEdition.Hyperscale: @@ -337,7 +445,7 @@ public static Database CreateDatabaseWithRetry( db = new Database(server, databaseName, DatabaseEngineEdition.SqlDatabase) { - AzureEdition = dbAzureDatabaseEdition.ToString(), + AzureEdition = dbParameters.AzureDatabaseEdition.ToString(), AzureServiceObjective = "HS_Gen5_2", // Shake out issues that only arise in case sensitive collations Collation = "SQL_Latin1_General_CP1_CS_AS", @@ -355,12 +463,11 @@ public static Database CreateDatabaseWithRetry( db = new Database(server, databaseName, DatabaseEngineEdition.SqlDatabase) { - AzureEdition = dbAzureDatabaseEdition.ToString(), + AzureEdition = dbParameters.AzureDatabaseEdition.ToString(), MaxSizeInBytes = 1024.0 * 1024.0 * 1024.0, CompatibilityLevel = CompatibilityLevel.Version160, ReadOnly = false }; - //1GB break; } case SqlTestBase.AzureDatabaseEdition.GeneralPurpose: @@ -369,7 +476,7 @@ public static Database CreateDatabaseWithRetry( db = new Database(server, databaseName, DatabaseEngineEdition.SqlDatabase) { - AzureEdition = dbAzureDatabaseEdition.ToString(), + AzureEdition = dbParameters.AzureDatabaseEdition.ToString(), CompatibilityLevel = CompatibilityLevel.Version160, ReadOnly = false }; @@ -379,22 +486,26 @@ public static Database CreateDatabaseWithRetry( throw new InvalidOperationException( string.Format( "Can't recognize Azure SQL database edition '{0}' specified for current database", - dbAzureDatabaseEdition)); + dbParameters.AzureDatabaseEdition)); } } + if (!string.IsNullOrEmpty(dbParameters.Collation)) + { + db.Collation = dbParameters.Collation; + } // Reduce contention for Azure resources by limiting the number of simultaneous creates if (server.DatabaseEngineType == DatabaseEngineType.SqlAzureDatabase) { try { - azureDbCreateLock.WaitOne(); + _ = azureDbCreateLock.WaitOne(); db.Create(); // Give the db a few seconds to be ready for action Thread.Sleep(5000); } finally { - azureDbCreateLock.Release(); + _ = azureDbCreateLock.Release(); } } else @@ -402,8 +513,8 @@ public static Database CreateDatabaseWithRetry( try { db.Create(); - } - catch (SMO.SmoException se) when (se.BuildRecursiveExceptionMessage().Contains("Could not obtain exclusive lock on database 'model'") == true) + } + catch (SmoException se) when (se.BuildRecursiveExceptionMessage().Contains("Could not obtain exclusive lock on database 'model'")) { server.HandleModelLock(); throw; @@ -415,13 +526,13 @@ public static Database CreateDatabaseWithRetry( } } } - else + if (!string.IsNullOrEmpty(dbParameters.BackupFile)) { // Restore DB from the specified backup file TraceHelper.TraceInformation("Restoring database '{0}' from backup file '{1}'", databaseName, - dbBackupFile); - db = server.RestoreDatabaseFromBackup(dbBackupFile, databaseName); + dbParameters.BackupFile); + db = server.RestoreDatabaseFromBackup(dbParameters.BackupFile, databaseName, ConnectionHelpers.GetAzureKeyVaultHelper().StorageHelper); } } catch (Exception e) @@ -435,7 +546,7 @@ public static Database CreateDatabaseWithRetry( Trace.TraceError(message); throw new InternalTestFailureException(message, e); } - }, whenFunc: RetryWhenExceptionThrown, retries: 3, retryDelayMs: 30000, + }, whenFunc: RetryWhenExceptionThrown, retries: 3, retryDelayMs: 30000, retryMessage: "Creating Initial DB failed"); return db; } @@ -447,7 +558,7 @@ private static bool RetryWhenExceptionThrown(Exception e) var sqlException = e.FirstInstanceOf(); return sqlException != null && !sqlException.BuildRecursiveExceptionMessage().Contains("Execution Timeout"); } - + /// /// Creates a snapshot of the specified DB /// @@ -660,7 +771,7 @@ public static void HandleModelLock(this SMO.Server server) var programname = (string)row[2]; var loginname = (string)row[3]; var status = (string)row[4]; - var requesttime = (DateTime) row[5]; + var requesttime = (DateTime)row[5]; Trace.TraceWarning($"Model is locked by spid {spid}: {loginname}@{hostname} using {programname}. Status: {status} Last query time:{requesttime}"); var dbcc = server.ExecutionManager.ExecuteWithResults($"DBCC INPUTBUFFER({spid})"); foreach (var r in dbcc.Tables[0].Rows.Cast()) @@ -670,7 +781,7 @@ public static void HandleModelLock(this SMO.Server server) Trace.TraceWarning($"\tQuery text: {eventInfo}"); } } - + if (status == "sleeping") { var closeSpid = Environment.GetEnvironmentVariable("SMOTEST_BREAKMODELLOCK"); @@ -680,7 +791,7 @@ public static void HandleModelLock(this SMO.Server server) server.KillProcess(spid); } } - + } } catch { } diff --git a/src/FunctionalTest/Framework/Helpers/StringExtensions.cs b/src/FunctionalTest/Framework/Helpers/StringExtensions.cs index fbc00987..93a31415 100644 --- a/src/FunctionalTest/Framework/Helpers/StringExtensions.cs +++ b/src/FunctionalTest/Framework/Helpers/StringExtensions.cs @@ -188,5 +188,12 @@ public static string FixNewLines(this string input) return input; } + + /// + /// Checks if the file name is a DAC package + /// + /// + /// + public static bool IsPackageFileName(this string fileName) => System.IO.Path.GetExtension(fileName ?? "").EndsWith("acpac", StringComparison.OrdinalIgnoreCase); } } diff --git a/src/FunctionalTest/Framework/Microsoft.SqlServer.Test.Manageability.Utils.csproj b/src/FunctionalTest/Framework/Microsoft.SqlServer.Test.Manageability.Utils.csproj index 1041afd6..f2ec32b2 100644 --- a/src/FunctionalTest/Framework/Microsoft.SqlServer.Test.Manageability.Utils.csproj +++ b/src/FunctionalTest/Framework/Microsoft.SqlServer.Test.Manageability.Utils.csproj @@ -8,7 +8,6 @@ $(NoWarn);NU1603 - @@ -24,6 +23,7 @@ + diff --git a/src/FunctionalTest/Framework/TestFramework/AlternateTestServerSource.cs b/src/FunctionalTest/Framework/TestFramework/AlternateTestServerSource.cs index 4313c426..f6dc6fea 100644 --- a/src/FunctionalTest/Framework/TestFramework/AlternateTestServerSource.cs +++ b/src/FunctionalTest/Framework/TestFramework/AlternateTestServerSource.cs @@ -69,7 +69,8 @@ static public IList TryLoadServerConnections() // 12 is the Fabric Native edition. We can't use TSQL to drop or create the database. if (realEdition == 12) { - descriptor.EnabledFeatures = new[] { SqlFeature.NoDropCreate }; + // For Fabric database, when connection string is specified, database shouldn't be dropped. + descriptor.EnabledFeatures = new[] { SqlFeature.Fabric, SqlFeature.NoDropCreate}; } else { diff --git a/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerBase.cs b/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerBase.cs new file mode 100644 index 00000000..b07d6c6b --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerBase.cs @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +#if MICROSOFTDATA +using Microsoft.Data.SqlClient; +#else +using System.Data.SqlClient; +#endif +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; +using SMO = Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public abstract class DatabaseHandlerBase : IDatabaseHandler + { + public SMO.Server ServerContext { get; set; } + public TestDescriptor TestDescriptor { get; set; } + public string DatabaseDisplayName { get; set; } + + public abstract Database HandleDatabaseCreation(DatabaseParameters dbParameters = null); + + public virtual void HandleDatabaseDrop() + { + // Default behavior: skip dropping the database + Trace.TraceInformation("Skipping database drop."); + } + + protected SMO.Server InitializeServerContext(SqlConnectionStringBuilder sqlConnectionStringBuilder) + { + // 10 minute minimum for statement timeouts +#if MICROSOFTDATA + var commandTimeout = sqlConnectionStringBuilder.CommandTimeout > 0 ? Math.Max(600, sqlConnectionStringBuilder.CommandTimeout) : 0; +#else + var commandTimeout = 600; +#endif + var serverConnection = new ServerConnection(new SqlConnection(sqlConnectionStringBuilder.ConnectionString)) + { + StatementTimeout = commandTimeout, + }; + + return new SMO.Server(serverConnection); + } + } + +} diff --git a/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerFactory.cs b/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerFactory.cs new file mode 100644 index 00000000..e61edaca --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/DatabaseHandlerFactory.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public static class DatabaseHandlerFactory + { + public static IDatabaseHandler GetDatabaseHandler(TestDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + switch (descriptor) + { + case TestServerDescriptor serverDescriptor: + if (serverDescriptor.ReuseExistingDatabase) + { + return new ReuseExistingDatabaseHandler(serverDescriptor); + } + return new RegularDatabaseHandler(serverDescriptor); + + case FabricWorkspaceDescriptor fabricWorkspaceDescriptor: + return new FabricDatabaseHandler(fabricWorkspaceDescriptor); + + default: + throw new ArgumentException($"Unsupported TestDescriptor type: {descriptor.GetType().Name}"); + } + } + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/DatabaseParameters.cs b/src/FunctionalTest/Framework/TestFramework/DatabaseParameters.cs new file mode 100644 index 00000000..a5691b2f --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/DatabaseParameters.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + /// + /// Defines parameters for the database used in the tests when using ExecuteWithDbDrop + /// + public class DatabaseParameters + { + /// + /// The prefix to use for the database name. + /// + public string NamePrefix { get; set; } = string.Empty; + /// + /// The required Azure edition of the database. Default is NotApplicable. + /// + public SqlTestBase.AzureDatabaseEdition AzureDatabaseEdition { get; set; } = SqlTestBase.AzureDatabaseEdition.NotApplicable; + /// + /// The path to a bak file or DAC package used to initialize the database. + /// + public string BackupFile { get; set; } = string.Empty; + /// + /// Whether to also create a snapshot of the database. + /// + public bool CreateSnapshot { get; set; } + /// + /// The desired collation for the database. When empty, the server default is used. + /// + public string Collation { get; set; } = string.Empty; + /// + /// Whether to include escaped characters in the database name. Default is true. + /// + public bool UseEscapedCharacters { get; set; } = true; + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/FabricDatabaseHandler.cs b/src/FunctionalTest/Framework/TestFramework/FabricDatabaseHandler.cs new file mode 100644 index 00000000..a7ef0a92 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/FabricDatabaseHandler.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +#if MICROSOFTDATA +using Microsoft.Data.SqlClient; +#else +using System.Data.SqlClient; +#endif +using Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public class FabricDatabaseHandler : DatabaseHandlerBase + { + public FabricDatabaseHandler(TestDescriptor descriptor) + { + if (descriptor == null || !(descriptor is FabricWorkspaceDescriptor)) + { + throw new ArgumentException($"The descriptor must be of type {nameof(FabricWorkspaceDescriptor)}.", nameof(descriptor)); + } + + this.TestDescriptor = descriptor; + } + public override Database HandleDatabaseCreation(DatabaseParameters dbParameters) + { + var fabricWorkspaceDescriptor = (FabricWorkspaceDescriptor)this.TestDescriptor; + string fabricDbName; + var currentUtcTime = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); + var dbNamePrefix = $"{fabricWorkspaceDescriptor.DbNamePrefix}{currentUtcTime}-"; + if (dbParameters == null) + { + fabricDbName = SmoObjectHelpers.GenerateUniqueObjectName(dbNamePrefix); + } + else + { + fabricDbName = SmoObjectHelpers.GenerateUniqueObjectName(dbNamePrefix, + includeClosingBracket: dbParameters.UseEscapedCharacters, + includeDoubleClosingBracket: dbParameters.UseEscapedCharacters, + includeSingleQuote: dbParameters.UseEscapedCharacters, + includeDoubleSingleQuote: dbParameters.UseEscapedCharacters); + } + //create fabric database using fabric-cli + var connectionString = fabricWorkspaceDescriptor.CreateDatabase(fabricDbName); + Trace.TraceInformation($"Created fabric database {fabricDbName}"); + var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(connectionString); + this.ServerContext = InitializeServerContext(sqlConnectionStringBuilder); + // Fabric database InitialCatalog is slightly different from the database display name + // e.g. "SmoTestFabric-'']]]'{e2e334e4-043a-4f31-ad6c-b9649f886d2a}" is the display name + // but the InitialCatalog is "SmoTestFabric-'']]]'{e2e334e4-043a-4f31-ad6c-b9649f886d2a}-46ecae46-6627-43db-8c0d-53ae916a0a23" + var db = this.ServerContext.Databases[sqlConnectionStringBuilder.InitialCatalog]; + this.DatabaseDisplayName = fabricDbName; + + return db; + } + + public override void HandleDatabaseDrop() + { + var fabricWorkspaceDescriptor = this.TestDescriptor as FabricWorkspaceDescriptor; + if (!string.IsNullOrEmpty(this.DatabaseDisplayName)) + { + fabricWorkspaceDescriptor.DropDatabase(this.DatabaseDisplayName); + } + } + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/FabricWorkspaceDescriptor.cs b/src/FunctionalTest/Framework/TestFramework/FabricWorkspaceDescriptor.cs new file mode 100644 index 00000000..7db380b1 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/FabricWorkspaceDescriptor.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.SqlServer.Test.Manageability.Utils.Helpers; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public class FabricWorkspaceDescriptor : TestDescriptor + { + public string Environment { get; set; } + public string WorkspaceName { get; set; } + public string DbNamePrefix { get; set; } + + public string CreateDatabase(string databaseName) => FabricDatabaseManager.CreateDatabase(WorkspaceName, databaseName); + public void DropDatabase(string databaseName) => FabricDatabaseManager.DropDatabase(WorkspaceName, databaseName); + + } + +} diff --git a/src/FunctionalTest/Framework/TestFramework/IDatabaseHandler.cs b/src/FunctionalTest/Framework/TestFramework/IDatabaseHandler.cs new file mode 100644 index 00000000..c8033831 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/IDatabaseHandler.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using Microsoft.SqlServer.Management.Smo; +using SMO = Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public interface IDatabaseHandler + { + SMO.Server ServerContext { get; set; } + TestDescriptor TestDescriptor { get; set; } + string DatabaseDisplayName { get; set; } + Database HandleDatabaseCreation(DatabaseParameters dbParameters = null); + void HandleDatabaseDrop(); + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/RegularDatabaseHandler.cs b/src/FunctionalTest/Framework/TestFramework/RegularDatabaseHandler.cs new file mode 100644 index 00000000..fa6bc2a4 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/RegularDatabaseHandler.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +#if MICROSOFTDATA +using Microsoft.Data.SqlClient; +#else +using System.Data.SqlClient; +#endif +using Microsoft.SqlServer.Management.Smo; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public class RegularDatabaseHandler : DatabaseHandlerBase + { + + public RegularDatabaseHandler(TestDescriptor descriptor) + { + if (descriptor == null || !(descriptor is TestServerDescriptor)) + { + throw new ArgumentException($"The descriptor must be of type {nameof(TestServerDescriptor)}.", nameof(descriptor)); + } + this.TestDescriptor = descriptor; + + // Initialize ServerContext + var serverDescriptor = (TestServerDescriptor)this.TestDescriptor; + var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(serverDescriptor.ConnectionString); + this.ServerContext = InitializeServerContext(sqlConnectionStringBuilder); + } + public override Database HandleDatabaseCreation(DatabaseParameters dbParameters) + { + if (dbParameters == null) + { + return this.ServerContext.CreateDatabaseWithRetry(); + } + + var db = this.ServerContext.CreateDatabaseWithRetry( + dbParameters); + this.DatabaseDisplayName = db.Name; + + return db; + } + + public override void HandleDatabaseDrop() + { + if (this.ServerContext != null && !string.IsNullOrEmpty(this.DatabaseDisplayName)) + { + this.ServerContext.DropKillDatabaseNoThrow(this.DatabaseDisplayName); + } + } + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/ReuseExistingDatabaseHandler.cs b/src/FunctionalTest/Framework/TestFramework/ReuseExistingDatabaseHandler.cs new file mode 100644 index 00000000..3f1cb9d7 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/ReuseExistingDatabaseHandler.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System; +using System.Diagnostics; +using System.Linq; +#if MICROSOFTDATA +using Microsoft.Data.SqlClient; +#else +using System.Data.SqlClient; +#endif +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; + + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public class ReuseExistingDatabaseHandler : DatabaseHandlerBase + { + public ReuseExistingDatabaseHandler(TestDescriptor descriptor) + { + if (descriptor == null || !(descriptor is TestServerDescriptor)) + { + throw new ArgumentException($"The descriptor must be of type {nameof(TestServerDescriptor)}.", nameof(descriptor)); + } + this.TestDescriptor = descriptor; + + // Initialize ServerContext + var serverDescriptor = (TestServerDescriptor)this.TestDescriptor; + var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(serverDescriptor.ConnectionString); + this.ServerContext = InitializeServerContext(sqlConnectionStringBuilder); + } + + public override Database HandleDatabaseCreation(DatabaseParameters dbParameters = null) + { + Trace.TraceInformation("Reusing user database " + ServerContext.ConnectionContext.SqlConnectionObject.Database); + // For Azure databases, when connected directly to a user database specified in the + // connection file, we just reuse it for every test and don't run any tests in parallel. + + var db = this.ServerContext.Databases.Cast().First(d => d.Name != "master"); + db.DropAllObjects(); + Trace.TraceInformation("Resetting database state for reuse"); + if (db.UserAccess != DatabaseUserAccess.Multiple || db.ReadOnly || !db.AutoUpdateStatisticsEnabled || db.ChangeTrackingEnabled) + { + db.UserAccess = DatabaseUserAccess.Multiple; + db.ReadOnly = false; + db.AutoUpdateStatisticsEnabled = true; + if (db.ChangeTrackingEnabled) + { + db.ChangeTrackingAutoCleanUp = false; + db.ChangeTrackingEnabled = false; + } + db.Alter(); + } + + db.ExecutionManager.ConnectionContext.Disconnect(); + db.ExecutionManager.ConnectionContext.SqlExecutionModes = SqlExecutionModes.ExecuteSql; + this.ServerContext.Databases.ClearAndInitialize(null, null); + // We return a fresh Database object because after DropAllObjects the object has some incorrect internal state. + // It would take a long time to investigate the sources of inconsistency and that work would have little customer value. + db = this.ServerContext.Databases[db.Name]; + this.DatabaseDisplayName = db.Name; + + return db; + } + + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/ServerConnectionInfo.cs b/src/FunctionalTest/Framework/TestFramework/ServerConnectionInfo.cs new file mode 100644 index 00000000..7209a861 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/ServerConnectionInfo.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +#if MICROSOFTDATA +using Microsoft.Data.SqlClient; +#else +using System.Data.SqlClient; +#endif + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + /// + /// Represents a server connection, which can either be a traditional SQL connection or a Fabric workspace connector. + /// + public class ServerConnectionInfo + { + /// + /// The friendly name of the server or workspace. + /// + public string FriendlyName { get; set; } + + /// + /// The collection of SQL connection strings for traditional servers. + /// This will be null for Fabric workspaces. + /// + public IEnumerable ConnectionStrings { get; set; } + + /// + /// Indicates whether this connection info is for a Fabric workspace. + /// + public bool IsFabricWorkspace; + + public TestDescriptor TestDescriptor { get; set; } + } + +} diff --git a/src/FunctionalTest/Framework/TestFramework/SqlFeature.cs b/src/FunctionalTest/Framework/TestFramework/SqlFeature.cs index 907f7e9c..3ca75318 100644 --- a/src/FunctionalTest/Framework/TestFramework/SqlFeature.cs +++ b/src/FunctionalTest/Framework/TestFramework/SqlFeature.cs @@ -43,5 +43,10 @@ public enum SqlFeature /// Mark a server as NoDropCreate if it doesn't support database create/drop /// NoDropCreate, + + /// + /// Fabric Hosted + /// + Fabric, } } diff --git a/src/FunctionalTest/Framework/TestFramework/SqlTestBase.cs b/src/FunctionalTest/Framework/TestFramework/SqlTestBase.cs index 436548c4..61e4ab72 100644 --- a/src/FunctionalTest/Framework/TestFramework/SqlTestBase.cs +++ b/src/FunctionalTest/Framework/TestFramework/SqlTestBase.cs @@ -18,6 +18,7 @@ using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Test.Manageability.Utils.Helpers; + namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework { /// @@ -111,6 +112,13 @@ protected SqlConnectionStringBuilder SqlConnectionStringBuilder private set { this.TestContext.Properties[SqlConnectionStringBuilder_PropertyName] = value; } } + /// + /// When true, ExecuteWithDbDrop will create databases names that have escaped characters in them + /// + protected bool UseEscapedCharactersInDatabaseNames { get; set; } = true; + + protected TestDescriptor TestDescriptorContext { get; set; } + protected SMO.Server ServerContext { get; set; } protected MethodInfo TestMethod { get; set; } @@ -189,20 +197,14 @@ public virtual void PostExecuteTest() /// test method. Will call PreExecute() before the test method invocation and PostExecute() afterwards. /// /// - public virtual void ExecuteTest(Action testMethod) - { - ExecuteTestImpl(server => { testMethod.Invoke(); }); - } + public virtual void ExecuteTest(Action testMethod) => ExecuteTestImpl(server => { testMethod.Invoke(); }); /// /// Executes the specified test method once for each server specified in the SupportedSqlVersion attribute on the /// test method. Will call PreExecute() before the test method invocation and PostExecute() afterwards. /// /// Test method to execute with the server object for this test as a parameter - public virtual void ExecuteTest(Action testMethod) - { - ExecuteTestImpl(testMethod.Invoke); - } + public virtual void ExecuteTest(Action testMethod) => ExecuteTestImpl(testMethod.Invoke); /// /// Implementation of the ExecuteTest method, which will execute the specified test method once for each server @@ -221,11 +223,11 @@ private void ExecuteTestImpl(Action executeTestMethod) this.SqlConnectionStringBuilder = null; try { - TraceHelper.TraceInformation("Invoking PreExecute for Disconnected test"); + TraceHelper.TraceInformation("Invoking PreExecute for Disconnected test"); PreExecuteTest(); - TraceHelper.TraceInformation("Invoking test method {0} for Disconnected test", this.TestContext.TestName); + TraceHelper.TraceInformation("Invoking test method {0} for Disconnected test", this.TestContext.TestName); executeTestMethod.Invoke(null); - TraceHelper.TraceInformation("Invoking PostExecute for Disconnected test"); + TraceHelper.TraceInformation("Invoking PostExecute for Disconnected test"); PostExecuteTest(); } catch (Exception e) @@ -243,16 +245,30 @@ private void ExecuteTestImpl(Action executeTestMethod) { this.ExecuteTestMethodWithFailureRetry(() => { + IDatabaseHandler databaseHandler = null; + Database db = null; try { - TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.ServerContext.Name); + // Initialize the server context + databaseHandler = DatabaseHandlerFactory.GetDatabaseHandler(this.TestDescriptorContext); + // Fabric databases do not support have server context or master database, hence creating database + if (databaseHandler is FabricDatabaseHandler) + { + var dbParameters = new DatabaseParameters + { + UseEscapedCharacters = UseEscapedCharactersInDatabaseNames + }; + db = databaseHandler.HandleDatabaseCreation(dbParameters); + } + this.ServerContext = databaseHandler.ServerContext; + this.SqlConnectionStringBuilder = new SqlConnectionStringBuilder(this.ServerContext.ConnectionContext.ConnectionString); + TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.ServerContext.Name); PreExecuteTest(); - TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", - this.TestContext.TestName, this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", + this.TestContext.TestName, this.ServerContext.Name); executeTestMethod.Invoke(this.ServerContext); - TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", this.ServerContext.Name); PostExecuteTest(); - } catch (Exception e) { @@ -260,10 +276,17 @@ private void ExecuteTestImpl(Action executeTestMethod) throw new InternalTestFailureException( string.Format("Test '{0}' failed when targeting server {1}. Message:\n{2}\nStack Trace:\n{3}", this.TestContext.TestName, - this.ServerContext.Name, + this.TestDescriptorContext.Name, e.BuildRecursiveExceptionMessage(), e.StackTrace), e); } + finally + { + if (databaseHandler != null && db != null) + { + databaseHandler.HandleDatabaseDrop(); + } + } }); } @@ -275,10 +298,7 @@ private void ExecuteTestImpl(Action executeTestMethod) /// /// The test method to execute public void ExecuteFromDbPool( - Action testMethod) - { - ExecuteFromDbPool(TestContext.FullyQualifiedTestClassName, testMethod); - } + Action testMethod) => ExecuteFromDbPool(TestContext.FullyQualifiedTestClassName, testMethod); /// /// Executes the specified test method from the pool specified, creating a new Database in the pool if needed. Currently only supports @@ -288,25 +308,16 @@ public void ExecuteFromDbPool( /// The test method to execute public void ExecuteFromDbPool( string poolName, - Action testMethod) - { - this.ExecuteTestMethodWithFailureRetry( + Action testMethod) => this.ExecuteTestMethodWithFailureRetry( () => { - Database db; - ServerContext.SetDefaultInitFields(typeof(Database), nameof(Database.UserAccess), nameof(Database.ReadOnly)); - if (ServerContext.ConnectionContext.DatabaseEngineType == DatabaseEngineType.Standalone || - ServerContext.ConnectionContext.SqlConnectionObject.Database == "master" - || ServerContext.ConnectionContext.SqlConnectionObject.Database == "") + var databaseHandler = DatabaseHandlerFactory.GetDatabaseHandler(this.TestDescriptorContext); + var db = TestServerPoolManager.GetDbFromPool(poolName, databaseHandler); + this.ServerContext = databaseHandler.ServerContext ?? db.GetServerObject(); + if(this.ServerContext != null && this.ServerContext.ConnectionContext != null) { - db = TestServerPoolManager.GetDbFromPool(poolName, ServerContext); - } - else - { - db = ServerContext.Databases.Cast().First(d => d.Name != "master"); - db.DropAllObjects(); - - } + this.SqlConnectionStringBuilder = new SqlConnectionStringBuilder(this.ServerContext.ConnectionContext.ConnectionString); + } Trace.TraceInformation($"Returning database {db.Name} for pool {poolName}"); if (db.UserAccess == DatabaseUserAccess.Single || db.ReadOnly) { @@ -319,13 +330,13 @@ public void ExecuteFromDbPool( db.ExecutionManager.ConnectionContext.SqlExecutionModes = SqlExecutionModes.ExecuteSql; try { - TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.TestDescriptorContext.Name); PreExecuteTest(); - TraceHelper.TraceInformation("Invoking test method {0} with target server {1} using database from pool {2}", - this.TestContext.TestName, this.ServerContext.Name, poolName); + TraceHelper.TraceInformation("Invoking test method {0} with target server {1} using database from pool {2}", + this.TestContext.TestName, this.TestDescriptorContext.Name, poolName); testMethod.Invoke(db); - TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", - this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", + this.TestDescriptorContext.Name); PostExecuteTest(); } catch (Exception e) @@ -334,7 +345,7 @@ public void ExecuteFromDbPool( string message = string.Format( "Test '{0}' failed when targeting server {1}. Message:\n{2}\nStack Trace:\n{3}", this.TestContext.TestName, - this.ServerContext.Name, + this.TestDescriptorContext.Name, e.BuildRecursiveExceptionMessage(), e.StackTrace); Trace.TraceError(message); @@ -345,7 +356,7 @@ public void ExecuteFromDbPool( db.ExecutionManager.ConnectionContext.CapturedSql.Clear(); } }); - } + /// /// Creates a new database and calls the given test method with that database, then drops /// the database after execution if still exists. @@ -354,10 +365,7 @@ public void ExecuteFromDbPool( /// The test method to execute, with the newly created database passed as a parameter public virtual void ExecuteWithDbDrop( string dbNamePrefix, - Action testMethod) - { - ExecuteWithDbDrop(dbNamePrefix, dbBackupFile: null, testMethod: testMethod); - } + Action testMethod) => ExecuteWithDbDrop(dbNamePrefix, dbBackupFile: null, testMethod: testMethod); /// /// Creates a new database and calls the given test method with that database, then drops @@ -372,7 +380,7 @@ public virtual void ExecuteWithDbDrop( string dbNamePrefix = string.IsNullOrEmpty(this.TestContext.TestName) ? this.TestContext.TestName : this.GetType().Name; - ExecuteWithDbDrop(dbNamePrefix, dbBackupFile: null, testMethod: testMethod, dbAzureDatabaseEdition: dbAzureDatabaseEdition); + ExecuteWithDbDrop(dbNamePrefix, dbBackupFile: null, testMethod: testMethod, dbAzureDatabaseEdition: dbAzureDatabaseEdition); } /// @@ -389,15 +397,12 @@ public virtual void ExecuteWithDbDrop( string dbNamePrefix, string dbBackupFile, Action testMethod, - AzureDatabaseEdition dbAzureDatabaseEdition = AzureDatabaseEdition.NotApplicable) - { - ExecuteWithDbDropImpl( + AzureDatabaseEdition dbAzureDatabaseEdition = AzureDatabaseEdition.NotApplicable) => ExecuteWithDbDropImpl( dbNamePrefix: dbNamePrefix, dbAzureDatabaseEdition: dbAzureDatabaseEdition, dbBackupFile: dbBackupFile, createDbSnapshot: false, executeTestMethodMethod: (database) => { testMethod.Invoke(database); }); - } /// /// Restores a database from a backup file OR create a new database, with specific azure db edition if provided, @@ -413,15 +418,12 @@ public virtual void ExecuteWithDbDrop( string dbNamePrefix, AzureDatabaseEdition dbAzureEdition, string dbBackupFile, - Action testMethod) - { - ExecuteWithDbDropImpl( + Action testMethod) => ExecuteWithDbDropImpl( dbNamePrefix: dbNamePrefix, dbAzureDatabaseEdition: dbAzureEdition, dbBackupFile: dbBackupFile, createDbSnapshot: false, executeTestMethodMethod: testMethod); - } /// /// Restores a database from a backup file OR create a new database, with specific azure db edition if provided, @@ -439,17 +441,22 @@ public virtual void ExecuteWithDbDrop( AzureDatabaseEdition dbAzureEdition, string dbBackupFile, bool createDbSnapshot, - Action testMethod) - { - ExecuteWithDbDropImpl( + Action testMethod) => ExecuteWithDbDropImpl( dbNamePrefix: dbNamePrefix, dbAzureDatabaseEdition: dbAzureEdition, dbBackupFile: dbBackupFile, createDbSnapshot: createDbSnapshot, executeTestMethodMethod: testMethod); - } /// + /// Creates a new database and calls the given test method with that database, then drops it + /// + /// + /// + public virtual void ExecuteWithDbDrop(DatabaseParameters dbParameters, Action testMethod) => ExecuteWithDbDropImpl( + dbParameters: dbParameters, + executeTestMethodMethod: testMethod); + /// /// Implementation of the ExecuteWithDbDrop, calls executeTestMethodMethod once for each supported server version /// /// Name prefix for new database @@ -466,7 +473,32 @@ private void ExecuteWithDbDropImpl( bool createDbSnapshot, Action executeTestMethodMethod) { - var requestedEdition = dbAzureDatabaseEdition; + var dbParameters = new DatabaseParameters + { + NamePrefix = dbNamePrefix, + AzureDatabaseEdition = dbAzureDatabaseEdition, + BackupFile = dbBackupFile, + CreateSnapshot = createDbSnapshot, + UseEscapedCharacters = UseEscapedCharactersInDatabaseNames + }; + + // Call the new override + ExecuteWithDbDropImpl(dbParameters, executeTestMethodMethod); + } + + /// + /// Implementation of the ExecuteWithDbDrop, calls executeTestMethodMethod once for each supported server version + /// + /// Encapsulates database parameters such as name prefix, Azure edition, and backup file + /// + /// The action called to invoke the test method, this should simply just call the test method itself with whatever parameters it needs + /// + private void ExecuteWithDbDropImpl( + DatabaseParameters dbParameters, + Action executeTestMethodMethod) + { + var requestedEdition = dbParameters.AzureDatabaseEdition; + IDatabaseHandler databaseHandler = null; this.ExecuteTestMethodWithFailureRetry( () => { @@ -482,60 +514,28 @@ private void ExecuteWithDbDropImpl( } } Database db; - var noDrop = false; try { - if (ServerContext.DatabaseEngineType == DatabaseEngineType.Standalone || - ServerContext.ConnectionContext.SqlConnectionObject.Database == "master" - || ServerContext.ConnectionContext.SqlConnectionObject.Database == "") - { - db = ServerContext.CreateDatabaseWithRetry(dbNamePrefix, requestedEdition, dbBackupFile); - } - else - { - Trace.TraceInformation("Reusing user database " + ServerContext.ConnectionContext.SqlConnectionObject.Database); - // For Azure databases, when connected directly to a user database specified in the - // connection file, we just reuse it for every test and don't run any tests in parallel. - db = ServerContext.Databases.Cast().First(d => d.Name != "master"); - db.DropAllObjects(); - Trace.TraceInformation("Resetting database state for reuse"); - if (db.UserAccess != DatabaseUserAccess.Multiple || db.ReadOnly || !db.AutoUpdateStatisticsEnabled || db.ChangeTrackingEnabled) - { - db.UserAccess = DatabaseUserAccess.Multiple; - db.ReadOnly = false; - db.AutoUpdateStatisticsEnabled = true; - if (db.ChangeTrackingEnabled) - { - db.ChangeTrackingAutoCleanUp = false; - db.ChangeTrackingEnabled = false; - } - db.Alter(); - } - - db.ExecutionManager.ConnectionContext.Disconnect(); - db.ExecutionManager.ConnectionContext.SqlExecutionModes = SqlExecutionModes.ExecuteSql; - ServerContext.Databases.ClearAndInitialize(null, null); - // We return a fresh Database object because after DropAllObjects the object has some incorrect internal state. - // It would take a long time to investigate the sources of inconsistency and that work would have little customer value. - db = ServerContext.Databases[db.Name]; - noDrop = true; - } + databaseHandler = DatabaseHandlerFactory.GetDatabaseHandler(this.TestDescriptorContext); + db = databaseHandler.HandleDatabaseCreation(dbParameters); + this.ServerContext = databaseHandler.ServerContext; + this.SqlConnectionStringBuilder = new SqlConnectionStringBuilder(this.ServerContext.ConnectionContext.ConnectionString); } finally { requestedEdition = originalEdition; } - Database dbSnapshot = createDbSnapshot ? this.ServerContext.CreateDbSnapshotWithRetry(db) : null; + Database dbSnapshot = dbParameters.CreateSnapshot ? this.ServerContext.CreateDbSnapshotWithRetry(db) : null; try { - TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking PreExecute for target server {0}", this.ServerContext.Name); PreExecuteTest(); - TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", - this.TestContext.TestName, this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", + this.TestContext.TestName, this.ServerContext.Name); executeTestMethodMethod.Invoke(db); - TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", - this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking PostExecute for target server {0}", + this.ServerContext.Name); PostExecuteTest(); } catch (Exception e) @@ -544,7 +544,7 @@ private void ExecuteWithDbDropImpl( string message = string.Format( "Test '{0}' failed when targeting server {1}. Message:\n{2}\nStack Trace:\n{3}", this.TestContext.TestName, - this.ServerContext.Name, + this.TestDescriptorContext.Name, e.BuildRecursiveExceptionMessage(), e.StackTrace); Trace.TraceError(message); @@ -557,14 +557,15 @@ private void ExecuteWithDbDropImpl( { ServerContext.DropKillDatabaseNoThrow(dbSnapshot.Name); } - if (!noDrop) + if(databaseHandler != null) { - ServerContext.DropKillDatabaseNoThrow(db.Name); + // Drop the database + databaseHandler.HandleDatabaseDrop(); } } }); } - + /// /// Defines a new database on specific server, and then executes the specified action method on this database. /// After execution, the database is dropped if exists. @@ -581,7 +582,7 @@ public void ExecuteMethodWithDbDrop( Database database; try { - TraceHelper.TraceInformation("Creating new database '{0}' on server '{1}'", databaseName, server.Name); + TraceHelper.TraceInformation("Creating new database '{0}' on server '{1}'", databaseName, server.Name); database = new Database(server, databaseName); } catch (Exception e) @@ -598,8 +599,8 @@ public void ExecuteMethodWithDbDrop( try { - TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", - this.TestContext.TestName, this.ServerContext.Name); + TraceHelper.TraceInformation("Invoking test method {0} with target server {1}", + this.TestContext.TestName, this.ServerContext.Name); executeMethod.Invoke(database); } catch (Exception e) @@ -632,12 +633,18 @@ public void ExecuteWithMultipleServers(int numOfServers, bool requiresSameHostPl throw new ArgumentException(string.Format("Invalid value provided: {0}", numOfServers), "numOfServers"); } - TraceHelper.TraceInformation("Executing test against multiple servers. numOfServers: {0}, requiresSameHostPlatform, value: {1}, requiresSameMajorVersion: {2}", numOfServers, requiresSameHostPlatform, requiresSameMajorVersion); + TraceHelper.TraceInformation("Executing test against multiple servers. numOfServers: {0}, requiresSameHostPlatform, value: {1}, requiresSameMajorVersion: {2}", numOfServers, requiresSameHostPlatform, requiresSameMajorVersion); + + var connections = ConnectionHelpers.GetServerConnections(this.TestMethod, TestContext.SqlTestTargetServersFilter); - var servers = ConnectionHelpers.GetServerConnections(this.TestMethod, TestContext.SqlTestTargetServersFilter). - Select(connection => new SMO.Server(new ServerConnection(new SqlConnection(connection.Value.First().ConnectionString)))).ToArray(); + var servers = connections + .Where(c => !c.IsFabricWorkspace) // Exclude FabricWorkspaces + .SelectMany(c => c.ConnectionStrings + .Select(connString => + new SMO.Server(new ServerConnection(new SqlConnection(connString.ConnectionString))))) + .ToArray(); - TraceHelper.TraceInformation("Number of target servers for the test before grouping: {0}", servers.Length); + TraceHelper.TraceInformation("Number of target servers for the test before grouping: {0}", servers.Length); var groupResults = servers.GroupBy(server => { @@ -656,14 +663,14 @@ public void ExecuteWithMultipleServers(int numOfServers, bool requiresSameHostPl return groupKey; }).ToArray(); - TraceHelper.TraceInformation("Number of server groups for the test: {0}", groupResults.Count()); + TraceHelper.TraceInformation("Number of server groups for the test: {0}", groupResults.Count()); foreach (var groupResult in groupResults) { if (groupResult.Count() >= numOfServers) { var targetServers = groupResult.Take(numOfServers).ToArray(); - TraceHelper.TraceInformation("Server group: {0}, target servers: {1}", groupResult.Key, string.Join(",", targetServers.Select(srv => srv.NetNameWithInstance()))); + TraceHelper.TraceInformation("Server group: {0}, target servers: {1}", groupResult.Key, string.Join(",", targetServers.Select(srv => srv.NetNameWithInstance()))); this.SqlConnectionStringBuilder = new SqlConnectionStringBuilder(targetServers[0].ConnectionContext.ConnectionString); try @@ -687,11 +694,8 @@ public void ExecuteWithMultipleServers(int numOfServers, bool requiresSameHostPl /// Returns the master database in the given server context /// /// The test method to execute, with the newly created database passed as a parameter - public void ExecuteWithMasterDb(Action testMethod) - { - ExecuteWithMasterDbImpl(AzureDatabaseEdition.NotApplicable, + public void ExecuteWithMasterDb(Action testMethod) => ExecuteWithMasterDbImpl(AzureDatabaseEdition.NotApplicable, (database) => { testMethod.Invoke(database); }); - } /// @@ -701,15 +705,16 @@ public void ExecuteWithMasterDb(Action testMethod) /// /// The action called to invoke the test method, this should simply just call the test method itself with whatever parameters it needs /// - public void ExecuteWithMasterDbImpl(AzureDatabaseEdition edition, Action executeTestMethodMethod) - { - this.ExecuteTestMethodWithFailureRetry( + public void ExecuteWithMasterDbImpl(AzureDatabaseEdition edition, Action executeTestMethodMethod) => this.ExecuteTestMethodWithFailureRetry( () => { + // Initialize the server context + var databaseHandler = DatabaseHandlerFactory.GetDatabaseHandler(this.TestDescriptorContext); + this.ServerContext = databaseHandler.ServerContext; + this.SqlConnectionStringBuilder = new SqlConnectionStringBuilder(this.ServerContext.ConnectionContext.ConnectionString); Database database = this.ServerContext.Databases["master"]; executeTestMethodMethod(database); }); - } #region Private Helper Methods @@ -723,12 +728,11 @@ public void ExecuteWithMasterDbImpl(AzureDatabaseEdition edition, Action private void ExecuteTestMethodWithFailureRetry(Action testMethod) { - var targetServerExceptions = new LinkedList>(); + var targetServerExceptions = new LinkedList>(); Trace.TraceInformation($"Server filter:{TestContext.Properties["SqlTestTargetServersFilter"]}"); var first = true; - foreach ( - KeyValuePair> serverConnection in - ConnectionHelpers.GetServerConnections(this.TestMethod, TestContext.SqlTestTargetServersFilter)) + var connections = ConnectionHelpers.GetServerConnections(this.TestMethod, TestContext.SqlTestTargetServersFilter); + foreach (var connection in connections) { // Prevent nunit assert messages from accumulating between server version iterations using (new NUnit.Framework.Internal.TestExecutionContext.IsolatedContext()) @@ -741,34 +745,11 @@ KeyValuePair> serverConnection i // any test that matches multiple servers isn't doing that. if (!first || TargetServerFriendlyName == null) { - TargetServerFriendlyName = serverConnection.Key; + TargetServerFriendlyName = connection.FriendlyName; } first = false; - foreach (SqlConnectionStringBuilder conn in serverConnection.Value) - { - this.SqlConnectionStringBuilder = conn; - this.ServerContext = - new SMO.Server( - new ServerConnection( - new SqlConnection(this.SqlConnectionStringBuilder.ConnectionString)) - { - StatementTimeout = 600 - }); - try - { - testMethod.Invoke(); - passed = true; - break; //Test passed successfully so we're done here - } - catch (Exception e) - { - exceptions.AddLast(new Tuple( - this.SqlConnectionStringBuilder.DataSource, - e)); - continue; - } - } + passed = ExecuteTestOnConnection(connection, testMethod, exceptions); if (!passed) { @@ -807,7 +788,7 @@ KeyValuePair> serverConnection i "Test '{0}' failed against the following TargetServers : {1}\nExceptions : \n{2}", this.TestMethod.Name, string.Join(",", targetServerExceptions.Select(e => e.Item1)), //List of all failed server friendly names - //Formatted exception infor for each target server failure + //Formatted exception infor for each target server failure string.Join("\n", targetServerExceptions.Select( e => String.Format( @@ -820,7 +801,24 @@ KeyValuePair> serverConnection i } } - #endregion // Private Helper Methods + private bool ExecuteTestOnConnection(ServerConnectionInfo connection, Action testMethod, LinkedList> exceptions) + { + this.TestDescriptorContext = connection.TestDescriptor; + try + { + testMethod.Invoke(); + return true; // Test passed successfully + } + catch (Exception e) + { + exceptions.AddLast(new Tuple( + this.SqlConnectionStringBuilder?.DataSource ?? this.TestDescriptorContext.Name, + e)); + return false; + } + } + + #endregion // Private Helper Methods #region Helper Methods @@ -829,10 +827,7 @@ KeyValuePair> serverConnection i /// indicating that it is a disconnected test (will be ran without actually connecting to a server) /// /// - protected bool IsDisconnectedTest() - { - return this.TestMethod.GetCustomAttribute() != null; - } + protected bool IsDisconnectedTest() => this.TestMethod.GetCustomAttribute() != null; #endregion //Helper Methods } diff --git a/src/FunctionalTest/Framework/TestFramework/SqlTestCategoryAttribute.cs b/src/FunctionalTest/Framework/TestFramework/SqlTestCategoryAttribute.cs index 15375cdb..bac19685 100644 --- a/src/FunctionalTest/Framework/TestFramework/SqlTestCategoryAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/SqlTestCategoryAttribute.cs @@ -31,14 +31,18 @@ public enum SqlTestCategory /// Tests that are currently in staging - they will not be ran as part of "official" test runs /// until they've been validated as being stable /// - Staging + Staging, + /// + /// For tests that cover legacy functionality to skip during most runs + /// + Legacy } /// /// Helper attribute to mark test methods with different test categories which can be used to selectively /// group and run tests. /// - [AttributeUsage(AttributeTargets.Method)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class SqlTestCategoryAttribute : TestCategoryBaseAttribute { private IList _categories; diff --git a/src/FunctionalTest/Framework/TestFramework/SqlTestDimensionAttribute.cs b/src/FunctionalTest/Framework/TestFramework/SqlTestDimensionAttribute.cs index e341e499..c5138c5b 100644 --- a/src/FunctionalTest/Framework/TestFramework/SqlTestDimensionAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/SqlTestDimensionAttribute.cs @@ -45,6 +45,15 @@ public virtual bool IsSupported(SMO.Server server, TestServerDescriptor serverDe /// public abstract bool IsSupported(SMO.Server server); + /// + /// Checks whether the specified server/workspace is "supported", that is whether it meets the + /// criteria of the dimension that is being defined. + /// + /// + /// + /// + public abstract bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName); + } /// diff --git a/src/FunctionalTest/Framework/TestFramework/SupportedServerVersionRange.cs b/src/FunctionalTest/Framework/TestFramework/SupportedServerVersionRange.cs index 6c0c4b80..77a4a7b7 100644 --- a/src/FunctionalTest/Framework/TestFramework/SupportedServerVersionRange.cs +++ b/src/FunctionalTest/Framework/TestFramework/SupportedServerVersionRange.cs @@ -222,5 +222,18 @@ public override bool IsSupported(SMO.Server server) return serverVersion >= minVersion && serverVersion <= maxVersion; } + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + if(this.DatabaseEngineType != DatabaseEngineType.Unknown && this.DatabaseEngineType != serverDescriptor.DatabaseEngineType) + { + return false; + } + + var serverVersion = new Version(serverDescriptor.MajorVersion, 0); + Version minVersion = new Version(MinMajor, MinMinor, MinBuild, MinRevision); + Version maxVersion = new Version(MaxMajor, MaxMinor, MaxBuild, MaxRevision); + + return serverVersion >= minVersion && serverVersion <= maxVersion; + } } } diff --git a/src/FunctionalTest/Framework/TestFramework/SupportedTargetServerFriendlyName.cs b/src/FunctionalTest/Framework/TestFramework/SupportedTargetServerFriendlyName.cs index 0fcca40b..26a64642 100644 --- a/src/FunctionalTest/Framework/TestFramework/SupportedTargetServerFriendlyName.cs +++ b/src/FunctionalTest/Framework/TestFramework/SupportedTargetServerFriendlyName.cs @@ -43,5 +43,9 @@ public override bool IsSupported(SMO.Server server) throw new InvalidOperationException("The SupportedTargetServerFriendlyName attribute should never have IsSupported called without a TargetServerFriendlyName passed in"); } + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + return _targetServerFriendlyNames.Contains(targetServerFriendlyName); + } } } diff --git a/src/FunctionalTest/Framework/TestFramework/TestDescriptor.cs b/src/FunctionalTest/Framework/TestFramework/TestDescriptor.cs new file mode 100644 index 00000000..3b1f1597 --- /dev/null +++ b/src/FunctionalTest/Framework/TestFramework/TestDescriptor.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.SqlServer.Management.Common; + +namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework +{ + public class TestDescriptor + { + + /// + /// Expected DatabaseEngineType + /// + public DatabaseEngineType DatabaseEngineType { get; set; } + + /// + /// Enabled features on the server + /// + public IEnumerable EnabledFeatures { get; set; } + + /// + /// The features that the server is reserved for. + /// + public IEnumerable ReservedFor { get; set; } = Enumerable.Empty(); + + /// + /// Expected HostPlatform + /// + public string HostPlatform { get; set; } + + /// + /// Name used to identify the server in configuration + /// + public string Name { get; set; } + + /// + /// Expected DatabaseEngineEdition. Will be Unknown if not provided in the XML + /// + public DatabaseEngineEdition DatabaseEngineEdition { get; set; } + + /// + /// Major version number, eg 13 for SQL2016. 0 if not specified + /// + public int MajorVersion { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/FunctionalTest/Framework/TestFramework/TestServerDescriptor.cs b/src/FunctionalTest/Framework/TestFramework/TestServerDescriptor.cs index 26c518a5..2460305b 100644 --- a/src/FunctionalTest/Framework/TestFramework/TestServerDescriptor.cs +++ b/src/FunctionalTest/Framework/TestFramework/TestServerDescriptor.cs @@ -21,7 +21,7 @@ namespace Microsoft.SqlServer.Test.Manageability.Utils.TestFramework /// Data about a server used for Data Tools test runs /// [DebuggerDisplay("{Name}")] - public class TestServerDescriptor + public class TestServerDescriptor : TestDescriptor { /// /// Connection string for the connection @@ -51,46 +51,13 @@ public IEnumerable AllConnectionStrings } } - /// - /// Expected DatabaseEngineType - /// - public DatabaseEngineType DatabaseEngineType { get; set; } - - /// - /// Enabled features on the server - /// - public IEnumerable EnabledFeatures { get; set; } - - /// - /// The features that the server is reserved for. - /// - public IEnumerable ReservedFor { get; set; } = Enumerable.Empty(); - - /// - /// Expected HostPlatform - /// - public string HostPlatform { get; set; } - - /// - /// Name used to identify the server in configuration - /// - public string Name { get; set; } - - /// - /// Expected DatabaseEngineEdition. Will be Unknown if not provided in the XML - /// - public DatabaseEngineEdition DatabaseEngineEdition { get; set; } - - /// - /// Major version number, eg 13 for SQL2016. 0 if not specified - /// - public int MajorVersion { get; set; } + public bool ReuseExistingDatabase { get; set; } = false; /// /// Returns the set of server connection strings allotted for the current test. /// /// - public static IEnumerable GetServerDescriptors(XDocument connStringsDoc, AzureKeyVaultHelper azureKeyVaultHelper) + public static IEnumerable GetTestDescriptors(XDocument connStringsDoc, AzureKeyVaultHelper azureKeyVaultHelper) { string targetServersEnvVar = Environment.GetEnvironmentVariable("SqlTestTargetServersFilter", EnvironmentVariableTarget.Process) ?? @@ -106,10 +73,30 @@ public static IEnumerable GetServerDescriptors(XDocument c if (targetServers != null) { TraceHelper.TraceInformation("Limiting tests to these servers based on environment: {0}", targetServersEnvVar); - } - - return - connStringsDoc.XPathSelectElements(@"//ConnectionString") + } + + var workspaces = connStringsDoc.XPathSelectElements(@"//FabricWorkspaces/FabricWorkspace") + .Select(workspaceElement => new FabricWorkspaceDescriptor + { + Name = workspaceElement.GetStringAttribute("name"), + Environment = workspaceElement.GetStringAttribute("environment"), + WorkspaceName = workspaceElement.GetStringAttribute("workspaceName"), + DatabaseEngineType = workspaceElement.GetAttribute("databaseenginetype", + (s) => (DatabaseEngineType)Enum.Parse(typeof(DatabaseEngineType), s)), + DatabaseEngineEdition = + workspaceElement.GetAttribute("db_engine_edition", + (s) => + s == null + ? DatabaseEngineEdition.Unknown + : (DatabaseEngineEdition)Enum.Parse(typeof(DatabaseEngineEdition), s)), + Description = workspaceElement.GetStringAttribute("description"), + EnabledFeatures = workspaceElement.GetAttribute("enabled_features", GetFeaturesFromString), // For now, all workspaces are Fabric + MajorVersion = workspaceElement.GetAttribute("majorversion", + (s) => string.IsNullOrEmpty(s) ? 0 : int.Parse(s)), + DbNamePrefix = workspaceElement.GetStringAttribute("dbNamePrefix"), + }).Where((d) => targetServers == null || targetServers.Contains(d.Name)).ToList(); + + var servers = connStringsDoc.XPathSelectElements(@"//ConnectionString") .Select(connStringElement => new TestServerDescriptor { ConnectionString = GetConnectionString(connStringElement, azureKeyVaultHelper), @@ -130,6 +117,7 @@ public static IEnumerable GetServerDescriptors(XDocument c ReservedFor = connStringElement.GetAttribute("reserved_for", GetFeaturesFromString) }).Where((d) => targetServers == null || targetServers.Contains(d.Name)).ToList(); + return servers.Cast().Concat(workspaces); } private static string GetConnectionString(XElement connStringElement, AzureKeyVaultHelper azureKeyVaultHelper) diff --git a/src/FunctionalTest/Framework/TestFramework/TestServerPoolManager.cs b/src/FunctionalTest/Framework/TestFramework/TestServerPoolManager.cs index 0326065b..8caabb27 100644 --- a/src/FunctionalTest/Framework/TestFramework/TestServerPoolManager.cs +++ b/src/FunctionalTest/Framework/TestFramework/TestServerPoolManager.cs @@ -4,9 +4,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using Microsoft.SqlServer.Management.Smo; - -using SMO = Microsoft.SqlServer.Management.Smo; +using Microsoft.SqlServer.Test.Manageability.Utils.TestFramework; namespace Microsoft.SqlServer.Test.Manageability.Utils { @@ -21,8 +21,6 @@ namespace Microsoft.SqlServer.Test.Manageability.Utils /// public static class TestServerPoolManager { - public const string DEFAULT_POOL_NAME = "DEFAULT"; - /// /// DB pools for tests to share and reuse. Note this is shared across all tests that inherit from this test base. /// Key - Pool Name @@ -35,7 +33,7 @@ public static class TestServerPoolManager private static IDictionary> DatabasePools => databasePools ?? (databasePools = new Dictionary>()); - private static ConcurrentBag allDatabases = new ConcurrentBag(); + private static ConcurrentBag<(Database, IDatabaseHandler)> allDatabases = new ConcurrentBag<(Database, IDatabaseHandler)>(); static TestServerPoolManager() { // Note we can't use the AssemblyCleanup attribute because that only runs on classes marked as @@ -46,12 +44,18 @@ static TestServerPoolManager() } /// - /// Gets a database for the specified server from the specified pool + /// Gets a database for the specified test descriptor from the specified pool /// /// The name of the pool - /// The server to get the database from + /// Handler to create database /// - public static Database GetDbFromPool(string poolName, SMO.Server server) + public static Database GetDbFromPool(string poolName, IDatabaseHandler databaseHandler) + { + var serverName = databaseHandler.TestDescriptor.Name; + return GetOrCreateDatabase(poolName, serverName, databaseHandler); + } + + private static Database GetOrCreateDatabase(string poolName, string serverName, IDatabaseHandler handler) { if (!DatabasePools.ContainsKey(poolName)) { @@ -59,23 +63,13 @@ public static Database GetDbFromPool(string poolName, SMO.Server server) } var pool = DatabasePools[poolName]; - if (!pool.ContainsKey((server.Name))) + if (!pool.ContainsKey(serverName)) { - pool[server.Name] = server.CreateDatabaseWithRetry(); - allDatabases.Add(pool[server.Name]); + pool[serverName] = handler.HandleDatabaseCreation(); + allDatabases.Add((pool[serverName], handler)); } - return pool[server.Name]; - } - - /// - /// Gets a database for the specified server from the default pool - /// - /// The server to get the database for - /// - public static Database GetDbFromPool(SMO.Server server) - { - return GetDbFromPool(DEFAULT_POOL_NAME, server); + return pool[serverName]; } /// @@ -84,9 +78,17 @@ public static Database GetDbFromPool(SMO.Server server) private static void Cleanup() { // Clean up all the DBs in the pools - foreach(var database in allDatabases) - { - database.Parent.DropKillDatabaseNoThrow(database.Name); + foreach(var (database, handler) in allDatabases) + { + try + { + handler.HandleDatabaseDrop(); + } + catch(Exception ex) + { + // Log this but don't re-throw since we won't consider this a test failure + Trace.TraceWarning($"Failed to drop database '{database.Name}' using handler '{handler.GetType().Name}': {ex.Message}"); + } } } } diff --git a/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineEditionAttribute.cs b/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineEditionAttribute.cs index fac94fb7..cbb25be7 100644 --- a/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineEditionAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineEditionAttribute.cs @@ -53,7 +53,12 @@ public override bool IsSupported(SMO.Server server, TestServerDescriptor serverD /// public override bool IsSupported(SMO.Server server) { - return _unsupportedDatabaseEngineEditions.Contains(server.DatabaseEngineEdition) == false; + return !_unsupportedDatabaseEngineEditions.Contains(server.DatabaseEngineEdition); + } + + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + return !_unsupportedDatabaseEngineEditions.Contains(serverDescriptor.DatabaseEngineEdition); } } } diff --git a/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineTypeAttribute.cs b/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineTypeAttribute.cs index c4d71b99..7e5e3421 100644 --- a/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineTypeAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/UnsupportedDatabaseEngineTypeAttribute.cs @@ -28,7 +28,7 @@ public UnsupportedDatabaseEngineTypeAttribute(params DatabaseEngineType[] unsupp public override bool IsSupported(SMO.Server server, TestServerDescriptor serverDescriptor, string targetServerFriendlyName) { - return _unsupportedDatabaseEngineTypes.Contains(serverDescriptor.DatabaseEngineType) == false; + return !_unsupportedDatabaseEngineTypes.Contains(serverDescriptor.DatabaseEngineType); } /// @@ -39,7 +39,12 @@ public override bool IsSupported(SMO.Server server, TestServerDescriptor serverD /// public override bool IsSupported(SMO.Server server) { - return _unsupportedDatabaseEngineTypes.Contains(server.DatabaseEngineType) == false; + return !_unsupportedDatabaseEngineTypes.Contains(server.DatabaseEngineType); + } + + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + return !_unsupportedDatabaseEngineTypes.Contains(serverDescriptor.DatabaseEngineType); } } } diff --git a/src/FunctionalTest/Framework/TestFramework/UnsupportedFeatureAttribute.cs b/src/FunctionalTest/Framework/TestFramework/UnsupportedFeatureAttribute.cs index 1d7b1413..7539a8a0 100644 --- a/src/FunctionalTest/Framework/TestFramework/UnsupportedFeatureAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/UnsupportedFeatureAttribute.cs @@ -49,5 +49,10 @@ public override bool IsSupported(Management.Smo.Server server) { throw new NotImplementedException(); } + + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + return !serverDescriptor.EnabledFeatures.Any(f => f == feature); + } } } diff --git a/src/FunctionalTest/Framework/TestFramework/UnsupportedHostPlatformAttribute.cs b/src/FunctionalTest/Framework/TestFramework/UnsupportedHostPlatformAttribute.cs index 0971c5ba..0db7f927 100644 --- a/src/FunctionalTest/Framework/TestFramework/UnsupportedHostPlatformAttribute.cs +++ b/src/FunctionalTest/Framework/TestFramework/UnsupportedHostPlatformAttribute.cs @@ -41,7 +41,7 @@ public UnsupportedHostPlatformAttribute(params SqlHostPlatforms[] unsupportedHos /// public override bool IsSupported(SMO.Server server, TestServerDescriptor serverDescriptor, string targetServerFriendlyName) { - return _unsupportedHostPlatforms.Contains(serverDescriptor.HostPlatform) == false; + return !_unsupportedHostPlatforms.Contains(serverDescriptor.HostPlatform); } /// @@ -52,7 +52,12 @@ public override bool IsSupported(SMO.Server server, TestServerDescriptor serverD /// public override bool IsSupported(SMO.Server server) { - return _unsupportedHostPlatforms.Contains(server.HostPlatform) == false; + return !_unsupportedHostPlatforms.Contains(server.HostPlatform); + } + + public override bool IsSupported(TestDescriptor serverDescriptor, string targetServerFriendlyName) + { + return !_unsupportedHostPlatforms.Contains(serverDescriptor.HostPlatform); } } } diff --git a/src/FunctionalTest/Framework/functionaltest.runsettings b/src/FunctionalTest/Framework/functionaltest.runsettings index 2b34b949..f0fa4914 100644 --- a/src/FunctionalTest/Framework/functionaltest.runsettings +++ b/src/FunctionalTest/Framework/functionaltest.runsettings @@ -84,9 +84,9 @@ Included items must then not match any entries in the exclude list to remain inc - + - .*DesignMode.* + .*IsDesignMode .*FilterException .*GenerateStretchHeapWithClause.* .*GetPropertyDefaultValue\(.* @@ -115,11 +115,23 @@ Included items must then not match any entries in the exclude list to remain inc .*TestMailProfile .*TestNetSend .*ThrowIfBelowVersion80SP3.* + .*ThrowIfBelowVersion90.* + .*ThrowIfBelowVersion100.* + .*ThrowIfBelowVersion110.* + .*ThrowIfBelowVersion110Prop.* + .*ThrowIfCloudAndVersionBelow12.* + .*ThrowIfCloudAndVersionBelow120.* + .*ThrowIfCompatibilityLevelBelow80.* + .*ThrowIfCompatibilityLevelBelow90.* + .*ThrowIfCompatibilityLevelBelow100.* .*ThrowIfAboveVersion80.* .*ThrowIfAboveVersion100.* .*ThrowIfSourceOrDestBelowVersion80.* .*ThrowIfSourceOrDestBelowVersion90.* .*ThrowIfSourceOrDestBelowVersion100.* + .*ThrowIfSourceOrDestBelowVersion110.* + .*ThrowIfSourceOrDestBelowVersion120.* + .*ThrowIfSourceOrDestBelowVersion130.* .*UpgradeFromSqlServer2005.* diff --git a/src/FunctionalTest/Identity/AzureDevOpsFederatedTokenCredentialOptions.cs b/src/FunctionalTest/Identity/AzureDevOpsFederatedTokenCredentialOptions.cs index dd03b5f4..7172c5a0 100644 --- a/src/FunctionalTest/Identity/AzureDevOpsFederatedTokenCredentialOptions.cs +++ b/src/FunctionalTest/Identity/AzureDevOpsFederatedTokenCredentialOptions.cs @@ -5,14 +5,14 @@ namespace Microsoft.SqlServer.ADO.Identity { /// - /// Options for an + /// Options for an AzurePipelinesCredential constructor parameters. Gets default values from environment variables./> /// public class AzureDevOpsFederatedTokenCredentialOptions { /// /// The client id of the identity the service connection is configured for /// - public string ClientId { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); + public string ClientId { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID") ?? Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_CLIENT_ID"); /// /// A unique identifier for a single attempt of a single job. The value is unique to the current pipeline. @@ -27,7 +27,7 @@ public class AzureDevOpsFederatedTokenCredentialOptions /// /// The ID of the service connection we'd like to use /// - public string ServiceConnectionId { get; set; } = Environment.GetEnvironmentVariable("SERVICE_CONNECTION_ID"); + public string ServiceConnectionId { get; set; } = Environment.GetEnvironmentVariable("SERVICE_CONNECTION_ID") ?? Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_SERVICE_CONNECTION_ID"); /// /// The security token used by the running build. @@ -47,6 +47,6 @@ public class AzureDevOpsFederatedTokenCredentialOptions /// /// The tenant id of the tenant in which we want a token /// - public string TenantId { get; set; } = Environment.GetEnvironmentVariable("AZURE_TENANT_ID"); + public string TenantId { get; set; } = Environment.GetEnvironmentVariable("AZURESUBSCRIPTION_TENANT_ID"); } } diff --git a/src/FunctionalTest/Identity/AzureDevOpsSqlAuthenticationProvider.cs b/src/FunctionalTest/Identity/AzureDevOpsSqlAuthenticationProvider.cs index 128bc61c..2711b36c 100644 --- a/src/FunctionalTest/Identity/AzureDevOpsSqlAuthenticationProvider.cs +++ b/src/FunctionalTest/Identity/AzureDevOpsSqlAuthenticationProvider.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -54,7 +54,7 @@ private static TokenCredential CredentialFromUserId(string tenantId, string user { return new ManagedIdentityCredential(userId); } - ClientCertificateCredential clientCertificateCredential = null; + var credentials = new List(); // if password is provided, assume it's a cert thumbprint if (!string.IsNullOrEmpty(password)) { @@ -62,7 +62,7 @@ private static TokenCredential CredentialFromUserId(string tenantId, string user if (certificate != null) { Trace.TraceInformation($"Adding ClientCertificateCredential for thumbprint {password.Substring(0, 10)}"); - clientCertificateCredential = new ClientCertificateCredential(tenantId, userId, certificate); + credentials.Add(new ClientCertificateCredential(tenantId, userId, certificate)); } } // if using ActiveDirectoryDefault, the AZURE_CLIENT_ID variable has to be set to use service principal authentication in Azure devops @@ -71,9 +71,11 @@ private static TokenCredential CredentialFromUserId(string tenantId, string user { options.ClientId = userId; } - var adoCredential = new AzureDevOpsFederatedTokenCredential(options); - var credential = clientCertificateCredential == null ? (TokenCredential)adoCredential : new ChainedTokenCredential(clientCertificateCredential, adoCredential); - Trace.TraceInformation($"Adding AzureDevOpsFederatedTokenCredential for client id {options.ClientId ?? ""}"); + if (options.ServiceConnectionId != null) + { + credentials.Add(new AzurePipelinesCredential(options.TenantId, options.ClientId, options.ServiceConnectionId, options.SystemAccessToken)); + Trace.TraceInformation($"Adding AzurePipelinesCredential for client id {options.ClientId ?? ""}"); + } if (authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault) { Trace.TraceInformation($"Adding DefaultAzureCredential for tenant {tenantId}"); @@ -84,9 +86,14 @@ private static TokenCredential CredentialFromUserId(string tenantId, string user userId = Environment.GetEnvironmentVariable("AZURE_IDENTITY_CLIENT_ID"); } Trace.TraceInformation($"Adding ManagedIdentityCredential for client {userId ?? ""}"); - credential = new ChainedTokenCredential(new ManagedIdentityCredential(userId), new DefaultAzureCredential(defOptions), credential); + if (options.ServiceConnectionId == null) + { + credentials.Insert(0, new ManagedIdentityCredential(userId)); + } + credentials.Insert(0, new DefaultAzureCredential(defOptions)); + return new ChainedTokenCredential(credentials.ToArray()); } - return credential; + return credentials.Any() ? (TokenCredential)new ChainedTokenCredential(credentials.ToArray()) : new EnvironmentCredential(); } private static X509Certificate2 FindCertificate(string thumbprint) @@ -129,7 +136,7 @@ private static X509Certificate2 FindCertificate(string thumbprint) private class TokenData { public TokenCredential Credential { get; } - public AccessToken AccessToken { get; private set; } + public AccessToken AccessToken { get; private set; } = new AccessToken(string.Empty, DateTimeOffset.MinValue.AddMinutes(5)); // 5 minutes padding for expiration check private object SyncObj { get; } = new object(); public TokenData(TokenCredential credential) diff --git a/src/FunctionalTest/Identity/Microsoft.SqlServer.ADO.Identity.csproj b/src/FunctionalTest/Identity/Microsoft.SqlServer.ADO.Identity.csproj index 446045b4..05f8d592 100644 --- a/src/FunctionalTest/Identity/Microsoft.SqlServer.ADO.Identity.csproj +++ b/src/FunctionalTest/Identity/Microsoft.SqlServer.ADO.Identity.csproj @@ -5,6 +5,7 @@ true README.md Provides implementations of TokenCredential and SqlAuthenticationProvider suitable for use with federated service connection credentials in Azure Devops. + false $(BaseOutputPath)\Documentation\$(TargetFramework)\$(AssemblyName).xml diff --git a/src/FunctionalTest/Smo/GeneralFunctionality/DataClassificationSmoTests.cs b/src/FunctionalTest/Smo/GeneralFunctionality/DataClassificationSmoTests.cs index 63ec01f2..e015b789 100644 --- a/src/FunctionalTest/Smo/GeneralFunctionality/DataClassificationSmoTests.cs +++ b/src/FunctionalTest/Smo/GeneralFunctionality/DataClassificationSmoTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.SqlServer.Test.SMO.GeneralFunctionality /// [TestClass] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public class DataClassificationSmoTests : SmoObjectTestBase { /// diff --git a/src/FunctionalTest/Smo/GeneralFunctionality/DatabaseSmoTests.cs b/src/FunctionalTest/Smo/GeneralFunctionality/DatabaseSmoTests.cs index fd8fd453..0f1e8703 100644 --- a/src/FunctionalTest/Smo/GeneralFunctionality/DatabaseSmoTests.cs +++ b/src/FunctionalTest/Smo/GeneralFunctionality/DatabaseSmoTests.cs @@ -155,6 +155,7 @@ public void EnumObjects_Sets_Synonym_Schema_And_Other_Properties() [TestMethod] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.SqlDatabase)] [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_Drop_does_not_throw_when_Parent_is_user_database() { ExecuteWithDbDrop(db => @@ -221,7 +222,7 @@ public void Database_SpaceAvailable_Is_Zero_For_DW() [TestMethod] [TestCategory("Legacy")] /* slow test, not for PR validation */ [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_SpaceAvailable_Is_Zero_For_Hyperscale() { ExecuteTest( @@ -244,7 +245,7 @@ private void Database_SpaceAvailable_Is_Zero(AzureDatabaseEdition ade) [TestMethod] [TestCategory("Legacy")] /* test prone to race condition (so it seems), not for PR validation */ [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.Express)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] // alter has to run on the master + [UnsupportedFeature(SqlFeature.Fabric)] // alter has to run on the master public void Database_enabling_encryption_creates_encryption_key() { ExecuteWithDbDrop(db => @@ -303,7 +304,7 @@ CREATE CERTIFICATE DEK_SmoTestSuite_ServerCertificate /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_miscellaneous_methods_produce_correct_sql() { ExecuteWithDbDrop(db => @@ -328,7 +329,6 @@ public void Database_miscellaneous_methods_produce_correct_sql() Assert.That(commands, Has.Member($"UPDATE STATISTICS [dbo].{table.Name.SqlBracketQuoteString()}"), "UpdateIndexStatistics should include table t1"); Assert.That(commands, Has.Member($"UPDATE STATISTICS [dbo].{view.Name.SqlBracketQuoteString()}"), "UpdateIndexStatistics should include view v1"); PrefetchAllChildTypes(db); - GetArchiveReports(db); if (db.DatabaseEngineEdition != DatabaseEngineEdition.SqlDatabase && db.DatabaseEngineEdition != DatabaseEngineEdition.SqlManagedInstance) { CheckTablesDataOnlyAllRepairTypes(db); @@ -341,7 +341,7 @@ public void Database_miscellaneous_methods_produce_correct_sql() [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] // hyperscale doesn't support shrink + [UnsupportedFeature(SqlFeature.Fabric)] // hyperscale doesn't support shrink public void Database_shrink() { ExecuteFromDbPool((db) => @@ -471,23 +471,6 @@ private static void PrefetchAllChildTypes(_SMO.Database db) }); } - private static void GetArchiveReports(_SMO.Database db) - { - if (db.IsSupportedProperty(nameof(db.RemoteDataArchiveEnabled))) - { - - IEnumerable statusReports = Enumerable.Empty(); - var commands = db.ExecutionManager.RecordQueryText(() => statusReports = - db.GetRemoteDataArchiveMigrationStatusReports( - DateTime.UtcNow.Subtract(TimeSpan.FromDays(1)), 2).ToList(), alsoExecute: true).Cast(); - - Assert.That(statusReports, Is.Empty, "GetRemoteDataArchiveMigrationStatusReports should return empty list for non-archived DB"); - var select = commands.Single(c => c.Contains("INNER JOIN")); - Assert.That(select, Contains.Substring("SELECT\nTOP (2) dbs.name as database_name,"), "GetRemoteDataArchiveMigrationStatusReports should select 2 rows"); - Assert.That(select, Contains.Substring("FROM\nsys.dm_db_rda_migration_status rdams"), "GetRemoteDataArchiveMigrationStatusReports should query ys.dm_db_rda_migration_status"); - } - } - private static void CheckTablesDataOnlyAllRepairTypes(_SMO.Database db) { IEnumerable messages = Enumerable.Empty(); @@ -656,7 +639,7 @@ private static void CheckAllocationsAllRepairTypes(Database db) [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.SqlDataWarehouse)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_transaction_counts() { ExecuteWithDbDrop((db) => @@ -677,9 +660,19 @@ public void Database_transaction_counts() }); } + [TestMethod] + [SqlRequiredFeature(SqlFeature.Fabric)] + public void Database_Fabric_CreateDrop() + { + ExecuteWithDbDrop((db) => + { + Assert.That(db.IsFabricDatabase, Is.True, "IsFabricDatabase should be true"); + }); + } + [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.SqlDatabaseEdge)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_FullTextCatalogs() { ExecuteWithDbDrop((db) => @@ -793,7 +786,7 @@ public void Database_IsLocalPrimaryReplica_returns_false_appropriately() } [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_IsMember_returns_correct_value() { ExecuteFromDbPool(db => @@ -806,7 +799,7 @@ public void Database_IsMember_returns_correct_value() [TestMethod] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.Enterprise, MinMajor = 13)] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_PlanGuide_methods() { ExecuteFromDbPool(db => @@ -942,7 +935,7 @@ public void Script_HyperScale_Without_MaxSizeInBytes() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Script_HyperScale_DataBase() { string backupFile = null; @@ -995,6 +988,11 @@ public void Database_Alter_toggles_accelerated_recovery() { ExecuteFromDbPool(db => { + if (db.CompatibilityLevel >= CompatibilityLevel.Version170) + { + // needed to allow accelerated recovery to be disabled + db.ExecuteNonQuery("ALTER DATABASE SCOPED CONFIGURATION SET OPTIMIZED_HALLOWEEN_PROTECTION = OFF"); + } db.AcceleratedRecoveryEnabled = true; db.Alter(); db.Refresh(); @@ -1131,7 +1129,7 @@ public void Database_Size_succeeds_with_CaptureSql_on() #if MICROSOFTDATA [TestMethod] [SupportedServerVersionRange(Edition=DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_enumerating_databases_does_not_login_to_each_database() { ExecuteTest(() => diff --git a/src/FunctionalTest/Smo/GeneralFunctionality/DifferencingTests.cs b/src/FunctionalTest/Smo/GeneralFunctionality/DifferencingTests.cs index 5708b62e..0605218e 100644 --- a/src/FunctionalTest/Smo/GeneralFunctionality/DifferencingTests.cs +++ b/src/FunctionalTest/Smo/GeneralFunctionality/DifferencingTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.SqlServer.Test.SMO.GeneralFunctionality /// [TestClass] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public class DifferencingTests : SmoTestBase { [TestMethod] diff --git a/src/FunctionalTest/Smo/GeneralFunctionality/PermissionsEnumTests.cs b/src/FunctionalTest/Smo/GeneralFunctionality/PermissionsEnumTests.cs index bc7ae0ef..0f114326 100644 --- a/src/FunctionalTest/Smo/GeneralFunctionality/PermissionsEnumTests.cs +++ b/src/FunctionalTest/Smo/GeneralFunctionality/PermissionsEnumTests.cs @@ -6,6 +6,7 @@ using System.Data; using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; @@ -18,6 +19,7 @@ namespace Microsoft.SqlServer.Test.SMO.GeneralFunctionality { [TestClass] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] + [UnsupportedFeature(SqlFeature.Fabric)] public class PermissionsEnumTests : SqlTestBase { [TestMethod] @@ -46,6 +48,41 @@ public void PermEnum_ObjectPermissionSetValue_enum_is_complete() CompareEnumToServerPermissions(typeof(ObjectPermissionSetValue), @"select type, permission_name from sys.fn_builtin_permissions(DEFAULT) where class_desc <> 'SERVER' and class_desc <> 'DATABASE'"); }); } + + /// + /// Simple test that verifies that the permission options are defined in the permission enum and permission set classes. + /// This is primarily used for code coverage purposes - the "*_enum_is_complete" tests above are more comprehensive. + /// + /// The permission set value type + /// The permission object type + /// The permission set object type + /// What values to skip + [TestMethod] + [DisconnectedTest] + [DataRow(typeof(DatabasePermissionSetValue), typeof(DatabasePermission), typeof(DatabasePermissionSet), new string[] { })] + [DataRow(typeof(ServerPermissionSetValue), typeof(ServerPermission), typeof(ServerPermissionSet), new string[] { nameof(ServerPermissionSetValue.AlterAnyExternalDataSource), nameof(ServerPermissionSetValue.AlterAnyExternalFileFormat) })] + [DataRow(typeof(ObjectPermissionSetValue), typeof(ObjectPermission), typeof(ObjectPermissionSet), new string[] { })] + public void PermEnum_PermissionOptions_AreDefined(Type enumType, Type permissionType, Type permissionSetType, IEnumerable valuesToSkip) + { + foreach (var permissionEnumName in Enum.GetNames(enumType)) + { + if (valuesToSkip.Contains(permissionEnumName)) + { + // If values are removed then they will sometimes still be kept in the enum but have their + // corresponding permission and permission set properties removed. + continue; + } + var permissionObj = permissionType.GetProperty(permissionEnumName, BindingFlags.Static | BindingFlags.Public); + Assert.That(permissionObj, Is.Not.Null, $"{permissionEnumName} is missing from {permissionType.Name}."); + Assert.That(permissionObj.GetValue(null), Is.Not.Null, $"{permissionEnumName} is null in {permissionType.Name}."); + var setObj = Activator.CreateInstance(permissionSetType); + var setObjProperty = permissionSetType.GetProperty(permissionEnumName); + Assert.That(setObjProperty, Is.Not.Null, $"{permissionEnumName} is null in {permissionSetType.Name}."); + Assert.That(setObjProperty.GetValue(setObj), Is.Not.Null, $"{permissionEnumName} is null in {permissionSetType.Name}."); + Assert.DoesNotThrow(() => setObjProperty.SetValue(setObj, null), $"Setting {permissionEnumName} in {permissionSetType.Name} should not throw"); + } + } + private void CompareEnumToServerPermissions(Type enumType, string permissionQuery) { var permissionTypes = GetAttributeValues(enumType); diff --git a/src/FunctionalTest/Smo/GeneralFunctionality/ServerSmoTests.cs b/src/FunctionalTest/Smo/GeneralFunctionality/ServerSmoTests.cs index 4af077ce..13672cde 100644 --- a/src/FunctionalTest/Smo/GeneralFunctionality/ServerSmoTests.cs +++ b/src/FunctionalTest/Smo/GeneralFunctionality/ServerSmoTests.cs @@ -85,7 +85,7 @@ public void Server_DetachDatabase_and_AttachDatabase_round_trip() } [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Server_DetachDatabase_and_AttachDatabase_error_handling() { ExecuteTest(() => @@ -112,7 +112,7 @@ public void Server_DetachDatabase_and_AttachDatabase_error_handling() } [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Server_EnumMembers_returns_valid_list() { ExecuteFromDbPool((db) => @@ -160,7 +160,7 @@ public void Server_DeleteBackupHistory_succeeds() [SupportedServerVersionRange(Edition = DatabaseEngineEdition.Express)] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.SqlManagedInstance)] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] // Requires GetDatabaseConnection to return a new connection + [UnsupportedFeature(SqlFeature.Fabric)] // Requires GetDatabaseConnection to return a new connection public void Server_KillAllProcesses_succeeds() { ExecuteFromDbPool((db) => diff --git a/src/FunctionalTest/Smo/ScriptingTests/Certificate_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Certificate_SmoTestSuite.cs index 38021c05..6917b6c3 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Certificate_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Certificate_SmoTestSuite.cs @@ -50,6 +50,7 @@ protected override void VerifyIsSmoObjectDropped(_SMO.SqlSmoObject obj, _SMO.Sql /// Tests creating a certificate using ... FROM BINARY = 0x.... syntax /// [TestMethod] + [UnsupportedFeature(SqlFeature.Fabric)] public void CreateCertificateFromBinary() { this.ExecuteWithDbDrop( diff --git a/src/FunctionalTest/Smo/ScriptingTests/ColumnMasterKey_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/ColumnMasterKey_SmoTestSuite.cs index e38a5112..a818c0a9 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/ColumnMasterKey_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/ColumnMasterKey_SmoTestSuite.cs @@ -14,7 +14,7 @@ namespace Microsoft.SqlServer.Test.SMO.ScriptingTests /// Test suite for testing ColumnMasterKey properties and scripting /// [TestClass] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] public class ColumnMasterKey_SmoTestSuite : SmoObjectTestBase { diff --git a/src/FunctionalTest/Smo/ScriptingTests/Column_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Column_SmoTestSuite.cs index 1be4e15a..79056a2d 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Column_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Column_SmoTestSuite.cs @@ -172,7 +172,7 @@ public void Column_script_all_data_types() [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDataWarehouse)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Column_Create_Masked_Column_And_Alter() { ExecuteFromDbPool( diff --git a/src/FunctionalTest/Smo/ScriptingTests/DatabaseOptions_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/DatabaseOptions_SmoTestSuite.cs index a0b2bec1..fe00d501 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/DatabaseOptions_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/DatabaseOptions_SmoTestSuite.cs @@ -26,6 +26,8 @@ public class DatabaseOptions_SmoTestSuite : SqlTestBase /// still in the Creating state /// [TestMethod] + [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoDatabaseOptions_Verify_Properties_When_Creating() { this.ExecuteTest( diff --git a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs index ba217b98..1c4e610f 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs @@ -67,7 +67,7 @@ public void SmoDatabase_Can_Access_Size_Property_When_Database_Name_Has_Special_ [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoDatabase_TemporalRetentionProperty_AzureSterlingV12() { this.ExecuteWithDbDrop( @@ -210,6 +210,9 @@ from sys.dm_hadr_database_replica_states hadrd /// still in the Creating state /// [TestMethod] + [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] + public void SmoDatabase_Verify_Properties_When_Creating() { this.ExecuteTest( @@ -247,7 +250,7 @@ public void SmoDatabase_Verify_Properties_When_Creating() [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Trying_To_Access_AzureDB_With_DBManager_Role_Only_Does_Not_Throw() { // We use the ExecuteWithMasterDb() overload to get back "master", so we can create a "low-privileged" login/user @@ -402,7 +405,7 @@ public void SmoDatabase_does_not_generate_SqlException_accessing_default_propert /// Tests altering a database through SMO for all server versions /// [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoDatabaseScriptAlter_AllServers() { this.ExecuteWithDbDrop( @@ -550,7 +553,7 @@ public void Compatibility_Level_Can_Be_Scripted_Correctly_On_AllServers() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void CatalogCollation_IsScriptedCorrectly() { this.ExecuteTest(server => TestCatalogCollationType(server, CatalogCollationType.DatabaseDefault)); @@ -1171,7 +1174,7 @@ public void Database_HasMemoryOptimizedObjectsDoesNotThrow_WhenNoMemoryOptimized /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_CanChangeOwner() { this.ExecuteWithDbDrop( @@ -1208,7 +1211,7 @@ public void Database_CanChangeOwner() /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_CanChangeOwner_WithExistingUser() { this.ExecuteWithDbDrop( @@ -1245,7 +1248,7 @@ public void Database_CanChangeOwner_WithExistingUser() /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_ChangeOwnerWithExistingUserAndNoOverride_ThrowsException() { this.ExecuteWithDbDrop( @@ -1285,7 +1288,7 @@ public void Database_ChangeOwnerWithExistingUserAndNoOverride_ThrowsException() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_DBComparerTest() { this.ExecuteTest( @@ -1336,7 +1339,7 @@ public void Database_DBComparerTest() [SupportedServerVersionRange(MinMajor = 14, DatabaseEngineType = DatabaseEngineType.Standalone)] //[SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.Express)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_CheckTables_sets_LastGoodDbCheckTime() { ExecuteWithDbDrop((db) => @@ -1367,7 +1370,7 @@ public void Database_CheckTables_sets_LastGoodDbCheckTime() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_Rename_Does_Not_Break_Functionality() { ExecuteWithDbDrop((db) => @@ -1475,7 +1478,7 @@ public void Database_EnumCandidateKeys() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 16)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.Express)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_IsLedger_On() { this.ExecuteTest( @@ -1512,7 +1515,7 @@ public void Database_IsLedger_On() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 16)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand, DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.Express)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_IsLedger_OFF() { this.ExecuteTest( diff --git a/src/FunctionalTest/Smo/ScriptingTests/ExternalDataSource_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/ExternalDataSource_SmoTestSuite.cs index dacc8dfc..9174e56c 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/ExternalDataSource_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/ExternalDataSource_SmoTestSuite.cs @@ -82,6 +82,7 @@ public void VerifyPositiveExternalDataSourceCreateAlterDropPolybase() [VSTest.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] [SqlTestArea(SqlTestArea.Polybase)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyPositiveExternalDataSourceCreateAlterDropGQ() { this.ExecuteWithDbDrop("ExternalDataSourceSmo_", @@ -641,6 +642,7 @@ public void VerifyNegativeExternalDataSourceCreateAlterDropPolybase() [VSTest.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] [SqlTestArea(SqlTestArea.Polybase)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyNegativeExternalDataSourceCreateAlterDropGQ() { string[] externalDataSourceLocations = { "abc.xyz.com" }; diff --git a/src/FunctionalTest/Smo/ScriptingTests/Index_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Index_SmoTestSuite.cs index 3091f77a..6ead41ea 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Index_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Index_SmoTestSuite.cs @@ -272,7 +272,7 @@ public void SmoDropIfExists_PrimaryKey_Sql16AndAfterOnPrem() [SqlRequiredFeature(SqlFeature.Hekaton)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 13)] [SupportedServerVersionRange(Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void HekatonColumnstoreScripting() { ExecuteWithDbDrop( @@ -336,7 +336,7 @@ public void HekatonColumnstoreScripting() [_VSUT.TestMethod] [SupportedServerVersionRange(MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlDatabaseEdge)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Verify_Create_Spatial_Index() { this.ExecuteFromDbPool( @@ -703,7 +703,7 @@ private void TestResumableRebuildOnPrem(_SMO.Database database, bool useResumabl [_VSUT.TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_ResumableRebuildOperation_Azure() { // Test SMO Resumable Index rebuild through both the index, and resumable index objects. @@ -863,7 +863,7 @@ private void TestResumableRebuildOnAzure(_SMO.Database database, bool useResumab [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.Express, DatabaseEngineEdition.SqlDataWarehouse)] [SupportedServerVersionRange(MinMajor = 15)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_ResumableCreateOperation_SQL2018AndAfterOnPrem() { this.ExecuteFromDbPool( @@ -941,7 +941,7 @@ public void Index_ResumableCreateOperation_SQL2018AndAfterOnPrem() [_VSUT.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_ResumableCreateConstraintOperation() { this.ExecuteFromDbPool( @@ -1434,7 +1434,7 @@ private void DropIndexWithDifferentOptions(_SMO.Database database, _SMO.Table ta [_VSUT.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 15)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_TestIsOptimizedForSequentialKeyPropertyForRegularIndex() { this.ExecuteWithDbDrop( @@ -1454,7 +1454,7 @@ public void Index_TestIsOptimizedForSequentialKeyPropertyForRegularIndex() [_VSUT.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 15)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_TestIsOptimizedForSequentialKeyPropertyForKeyConstraint() { this.ExecuteFromDbPool( @@ -1478,7 +1478,7 @@ public void Index_TestIsOptimizedForSequentialKeyPropertyForKeyConstraint() [_VSUT.TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 15)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Index_TestIsOptimizedForSequentialKeyPropertyForUnsupportedIndexType() { this.ExecuteWithDbDrop( diff --git a/src/FunctionalTest/Smo/ScriptingTests/RegressionTests.cs b/src/FunctionalTest/Smo/ScriptingTests/RegressionTests.cs index 553ce81c..7488b4b1 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/RegressionTests.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/RegressionTests.cs @@ -115,7 +115,7 @@ public void TFS4531584_UnableToViewDependenciesOnFilteredTable() } [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] /// /// Regression test for https://github.com/microsoft/sqlmanagementobjects/issues/123 /// diff --git a/src/FunctionalTest/Smo/ScriptingTests/ScriptMakerTests.cs b/src/FunctionalTest/Smo/ScriptingTests/ScriptMakerTests.cs index 475fc111..d0ae2687 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/ScriptMakerTests.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/ScriptMakerTests.cs @@ -234,7 +234,7 @@ public void ScriptMaker_Verify_CreateOrAlter_Trigger() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 13)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void ScriptMaker_Verify_CreateOrAlter_ExtendedProperty() { ExecuteFromDbPool((db) => @@ -308,7 +308,7 @@ public void ScriptMaker_Verify_Create_StoredProcedure() [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, MinMajor = 13)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void ScriptMaker_Verify_CreateOrAlter_scripts_Table_as_Create() { ExecuteFromDbPool(TestContext.FullyQualifiedTestClassName, (db) => diff --git a/src/FunctionalTest/Smo/ScriptingTests/ScripterTests.cs b/src/FunctionalTest/Smo/ScriptingTests/ScripterTests.cs index 16ee725c..e8e8bc55 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/ScripterTests.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/ScripterTests.cs @@ -23,7 +23,7 @@ public class ScripterTests : SmoTestBase [SupportedServerVersionRange(MinMajor = 13, DatabaseEngineType = Management.Common.DatabaseEngineType.Standalone)] [SupportedServerVersionRange(DatabaseEngineType = Management.Common.DatabaseEngineType.SqlAzureDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Scripter_Script_supports_CreateOrAlter_for_sql2016_and_beyond() { ExecuteFromDbPool(db => diff --git a/src/FunctionalTest/Smo/ScriptingTests/SensitivityClassification_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/SensitivityClassification_SmoTestSuite.cs index 41996302..259e3b50 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/SensitivityClassification_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/SensitivityClassification_SmoTestSuite.cs @@ -21,7 +21,7 @@ namespace Microsoft.SqlServer.Test.SMO.ScriptingTests [TestClass] //[SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public class SensitivityClassification_SmoTestSuite: SmoTestBase { diff --git a/src/FunctionalTest/Smo/ScriptingTests/SmoTestFramework/SmoTestBase.cs b/src/FunctionalTest/Smo/ScriptingTests/SmoTestFramework/SmoTestBase.cs index 566a7101..db52f8da 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/SmoTestFramework/SmoTestBase.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/SmoTestFramework/SmoTestBase.cs @@ -125,7 +125,7 @@ public static IEnumerable> TestServerScriptingOp ExtendedProperties = true, TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDatabase, TargetDatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, - TargetServerVersion = SqlServerVersion.Version130, + TargetServerVersion = SqlServerVersion.Version170, IncludeScriptingParametersHeader = true, OptimizerData = true }); @@ -139,16 +139,6 @@ public static IEnumerable> TestServerScriptingOp IncludeScriptingParametersHeader = true, OptimizerData = true }); - yield return new Tuple( - "SqlDatabaseEdge", - new ScriptingOptions() - { - TargetDatabaseEngineEdition = DatabaseEngineEdition.SqlDatabaseEdge, - TargetDatabaseEngineType = DatabaseEngineType.Standalone, - TargetServerVersion = SqlServerVersion.Version150, - IncludeScriptingParametersHeader = true, - OptimizerData = true - }); yield return new Tuple( "SqlManagedInstance", new ScriptingOptions() @@ -184,18 +174,11 @@ public enum TestScriptingContext public static IEnumerable> TestSupportedServerScriptingOptions(Database db, TestScriptingContext testScriptingContext) { var serverOptions = TestServerScriptingOptions.ToList(); - if (db.ServerVersion.Major < 15) - { - // Edge only supports scripting options for servers that are greater than version 15 - // - serverOptions.RemoveAll((server) => server.Item1.Contains("SqlDatabaseEdge")); - } - if (testScriptingContext == TestScriptingContext.Transfer) { // Transfer scripting is not supported for SqlOnDemand and Managed Instance // - serverOptions.RemoveAll((server) => server.Item1.Contains("AzureSterlingV12_SqlOnDemand") || server.Item1.Contains("SqlManagedInstance")); + _ = serverOptions.RemoveAll((server) => server.Item1.Contains("AzureSterlingV12_SqlOnDemand") || server.Item1.Contains("SqlManagedInstance")); } if (testScriptingContext == TestScriptingContext.Baseline) @@ -204,14 +187,14 @@ public static IEnumerable> TestSupportedServerSc { // Only generate baseline script options when database engine edition is on demand // - serverOptions.RemoveAll((server) => server.Item1.Contains("AzureSterlingV12_SqlOnDemand")); + _ = serverOptions.RemoveAll((server) => server.Item1.Contains("AzureSterlingV12_SqlOnDemand")); } // Cover only MI->MI scenarios for baseline tests // if (db.DatabaseEngineEdition != DatabaseEngineEdition.SqlManagedInstance) { - serverOptions.RemoveAll((server) => server.Item1.Contains("SqlManagedInstance")); + _ = serverOptions.RemoveAll((server) => server.Item1.Contains("SqlManagedInstance")); } if (db.DatabaseEngineEdition == DatabaseEngineEdition.SqlManagedInstance) @@ -219,15 +202,32 @@ public static IEnumerable> TestSupportedServerSc return serverOptions.Where((server) => server.Item1.Contains("SqlManagedInstance")); } } - // DW is only supported for scripting to DW - if (db.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse) + if (db.DatabaseEngineEdition == DatabaseEngineEdition.SqlDatabase) { - return serverOptions.Where(s => s.Item2.TargetDatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse); + // Remove all on-prem versions less than latest + _ = serverOptions.RemoveAll(server => server.Item2.TargetDatabaseEngineType == DatabaseEngineType.Standalone && (int)server.Item2.TargetServerVersion < (int)SqlServerVersion.VersionLatest); } - else + // VBUMP + if (db.DatabaseEngineType == DatabaseEngineType.Standalone) { - return serverOptions.Where(s => s.Item2.TargetDatabaseEngineEdition != DatabaseEngineEdition.SqlDataWarehouse); + // only script to Azure when server is latest + var sqlServerVersion = ScriptingOptions.ConvertToSqlServerVersion(db.ServerVersion); + if (db.ServerVersion.Major < (int)SqlServerVersion.VersionLatest) + { + _ = serverOptions.RemoveAll(server => server.Item2.TargetDatabaseEngineType == DatabaseEngineType.SqlAzureDatabase); + } + // only script up to 3 versions: OldestSupported, same, and Latest. + // Allows basic coverage of downgrade and upgrade without allowing the baseline sizes to grow unbounded over time. + var versions = new List() { + (int)SqlServerVersion.VersionOldestSupported, + (int)ScriptingOptions.ConvertToSqlServerVersion(db.ServerVersion.Major, db.ServerVersion.Minor), + (int)SqlServerVersion.VersionLatest}; + _ = serverOptions.RemoveAll(server => server.Item2.TargetDatabaseEngineType == DatabaseEngineType.Standalone && !versions.Contains((int)server.Item2.TargetServerVersion)); } + // DW is only supported for scripting to DW + return db.DatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse + ? serverOptions.Where(s => s.Item2.TargetDatabaseEngineEdition == DatabaseEngineEdition.SqlDataWarehouse) + : serverOptions.Where(s => s.Item2.TargetDatabaseEngineEdition != DatabaseEngineEdition.SqlDataWarehouse); } /// @@ -236,12 +236,12 @@ public static IEnumerable> TestSupportedServerSc /// The object to script /// Optional : ScriptingOptions to pass to the Script call /// A single string containing the full script - protected string ScriptSmoObject(IScriptable smoObject, ScriptingOptions options = null) + protected static string ScriptSmoObject(IScriptable smoObject, ScriptingOptions options = null) { var sb = new StringBuilder(); - foreach (string line in options == null ? smoObject.Script() : smoObject.Script(options)) + foreach (var line in options == null ? smoObject.Script() : smoObject.Script(options)) { - sb.AppendLine(line); + sb = sb.AppendLine(line); } return sb.ToString(); } diff --git a/src/FunctionalTest/Smo/ScriptingTests/Table_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Table_SmoTestSuite.cs index 38852d92..24695a08 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Table_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Table_SmoTestSuite.cs @@ -182,7 +182,7 @@ public void SmoTableProperties_SQL2017() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTableAlter_AzureSterlingV12() { ExecuteFromDbPool( @@ -491,7 +491,7 @@ public void VerifyTemporalRetentionPeriod_AddDropColumns() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalRetentionPeriod_DefaultValues() { ExecuteFromDbPool( @@ -519,7 +519,7 @@ public void VerifyTemporalRetentionPeriod_DefaultValues() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalRetentionPeriod_ScriptRoundTrip() { ExecuteWithDbDrop( @@ -578,7 +578,7 @@ public void VerifyTemporalRetentionPeriod_ScriptRoundTrip() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalRetentionPeriod_ScriptRoundTrip_Retention() { ExecuteFromDbPool( @@ -649,7 +649,7 @@ public void VerifyTemporalRetentionPeriod_ScriptRoundTrip_Retention() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalRetentionPeriod_InvalidRetentionValues() { ExecuteFromDbPool( @@ -715,7 +715,7 @@ public void VerifyTemporalRetentionPeriod_InvalidRetentionValues() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalRetentionPeriod_AlterTable() { ExecuteFromDbPool( @@ -822,7 +822,7 @@ public void VerifyTemporalRetentionPeriod_AlterTable() [TestMethod] [SupportedServerVersionRange(MinMajor = 13)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyCreateTemporalSystemTimeTable() { var consistencyCheckValues = new List() { true, false }; @@ -1260,7 +1260,7 @@ public void VerifyAlterTemporalTable() [TestMethod] [SupportedServerVersionRange(MinMajor = 13)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void InvalidTemporalHistoryTableSpecs() { ExecuteFromDbPool( @@ -1467,7 +1467,7 @@ public void VerifyTemporalHiddenColumns_Sql16_And_After() /// [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, MinMajor = 12)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyTemporalHiddenColumns_AzureSterlingV12_And_After() { ExecuteFromDbPool( @@ -1600,7 +1600,7 @@ public void VerifyCorrectScriptingOrderWithTemporalTables() [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void VerifyPartitionDependenciesDiscovered() { // Extract the test script @@ -4118,7 +4118,7 @@ public void ScriptingInsertTableWithVectorColumnTest() /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void When_table_has_sparse_column_HasSparseColumn_is_true() { ExecuteWithDbDrop( @@ -4298,7 +4298,7 @@ SET ANSI_PADDING OFF [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTable_enable_and_disable_all_indexes() { ExecuteFromDbPool(db => @@ -4410,7 +4410,7 @@ [t_ftyp] ASC public static Database CreateDbWithLotsOfTables(_SMO.Server serverContext, int tableCount = 20000, bool withData = false) { var timeout = serverContext.ConnectionContext.StatementTimeout; - serverContext.ConnectionContext.StatementTimeout = 900; + serverContext.ConnectionContext.StatementTimeout = Math.Max(900, timeout); var db = serverContext.CreateDatabaseDefinition("lotsoftables"); var fileGroupName = "PRIMARY"; var indexFileGroupName = "PRIMARY"; @@ -4443,7 +4443,7 @@ public static Database CreateDbWithLotsOfTables(_SMO.Server serverContext, int t [TestMethod] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.Standalone, HostPlatform = HostPlatformNames.Windows, Edition = DatabaseEngineEdition.Enterprise, MinMajor = 11)] [SupportedServerVersionRange(DatabaseEngineType = DatabaseEngineType.SqlAzureDatabase, Edition = DatabaseEngineEdition.SqlDatabase)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTable_enumerating_twenty_thousand_tables_with_scripting_properties_runs_quickly() { ExecuteTest(() => @@ -4794,7 +4794,7 @@ public void SmoTable_IsSystemObject_is_true_when_extendedproperty_is_set() /// [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTable_SwitchPartition_supports_all_variations() { ExecuteFromDbPool(db => @@ -4915,7 +4915,7 @@ public void SmoTable_DBCC_methods() [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTable_DataTable_methods() { ExecuteFromDbPool(db => @@ -4957,7 +4957,7 @@ public void SmoTable_DataTable_methods() [TestMethod] [UnsupportedDatabaseEngineEdition(DatabaseEngineEdition.SqlDataWarehouse, DatabaseEngineEdition.SqlOnDemand)] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void SmoTable_Rename() { ExecuteFromDbPool(db => diff --git a/src/FunctionalTest/Smo/SqlAssessment/SqlAssessmentTests.cs b/src/FunctionalTest/Smo/SqlAssessment/SqlAssessmentTests.cs index 2c6b88ed..723b2a50 100644 --- a/src/FunctionalTest/Smo/SqlAssessment/SqlAssessmentTests.cs +++ b/src/FunctionalTest/Smo/SqlAssessment/SqlAssessmentTests.cs @@ -137,7 +137,7 @@ public void Server_TestGetAssessmentResults() /// [SqlTestCategory(SqlTestCategory.NoRegression)] [TestMethod] - [UnsupportedFeature(SqlFeature.NoDropCreate)] + [UnsupportedFeature(SqlFeature.Fabric)] public void Database_TestGetAssessmentResults() { ExecuteTest(testMethod: (Microsoft.SqlServer.Management.Smo.Server targetServer) => diff --git a/src/FunctionalTest/Smo/ToolsConnectionInfo.xml b/src/FunctionalTest/Smo/ToolsConnectionInfo.xml index 85a14b19..3e7d2bdd 100644 --- a/src/FunctionalTest/Smo/ToolsConnectionInfo.xml +++ b/src/FunctionalTest/Smo/ToolsConnectionInfo.xml @@ -19,7 +19,7 @@ Sql2012;Sql2017;SQL2017Linux --> - @@ -36,6 +36,17 @@ Sql2012;Sql2017;SQL2017Linux + + + + + + + + + + + - dtb.is_remote_data_archive_enabled - - NULL - NULL - NULL - 0 - NULL - - + 0 + N'' + N'' + N'' + 0 + N'' diff --git a/src/Microsoft/SqlServer/Management/SqlEnum/xml/table.xml b/src/Microsoft/SqlServer/Management/SqlEnum/xml/table.xml index aaab6700..71fb1106 100644 --- a/src/Microsoft/SqlServer/Management/SqlEnum/xml/table.xml +++ b/src/Microsoft/SqlServer/Management/SqlEnum/xml/table.xml @@ -28,9 +28,6 @@ fields='#DataSourceName#FileFormatName#FileFormatNameOd#ShardingColumnName#Location#LocationOd#RejectType#RejectValue#RejectSampleValue#ExternalTableDistribution#RemoteSchemaName#RemoteObjectName#' left_join='#external_data AS edt'>edt.object_id = tbl.object_id - - rdat.object_id = tbl.object_id - et.object_id = tbl.object_id tdp.object_id = tbl.object_id @@ -46,11 +43,11 @@ ctt.object_id = tbl.object_id + left_join="#change_tracking_tables AS ctt">ctt.object_id = tbl.object_id ft.object_id = tbl.object_id + left_join='#file_tables AS ft'>ft.object_id = tbl.object_id ledgertbl.table_object_id = tbl.object_id ledgerviewcoltbl.table_object_id = tbl.object_id + + + create table #change_tracking_tables + (object_id int null, is_track_columns_updated_on bit null) + insert into #change_tracking_tables + select t.object_id, c.is_track_columns_updated_on + from sys.tables t + inner join sys.change_tracking_tables as c on c.object_id = t.object_id + + + + + CREATE TABLE #file_tables + (object_id int not null, is_enabled bit null, directory_name nvarchar(256) null, filename_collation_name nvarchar(129) null) + insert into #file_tables + select t.object_id, f.is_enabled, f.directory_name, f.filename_collation_name + from sys.tables as t + inner join sys.filetables as f on t.object_id = f.object_id + + CREATE TABLE #tmp_ledger_details @@ -123,41 +140,6 @@ where col.is_data_deletion_filter_column = 1' - - - - CREATE TABLE #tmp_extended_remote_data_archive_tables - (object_id int not null, remote_table_name nvarchar(128) null, filter_predicate nvarchar(max) null, migration_state tinyint null) - - IF EXISTS(SELECT 1 FROM master.sys.syscolumns WHERE Name = N'remote_data_archive_migration_state' AND ID = Object_ID(N'sys.tables')) - EXECUTE(N'INSERT INTO #tmp_extended_remote_data_archive_tables SELECT rdat.object_id, rdat.remote_table_name, - SUBSTRING(rdat.filter_predicate, 2, LEN(rdat.filter_predicate) - 2) as filter_predicate, - CASE - WHEN tbl.remote_data_archive_migration_state_desc = N''PAUSED'' THEN 1 - WHEN tbl.remote_data_archive_migration_state_desc = N''OUTBOUND'' THEN 3 - WHEN tbl.remote_data_archive_migration_state_desc = N''INBOUND'' THEN 4 - WHEN tbl.remote_data_archive_migration_state_desc = N''DISABLED'' THEN 0 - ELSE 0 - END AS migration_state - FROM sys.tables tbl LEFT JOIN sys.remote_data_archive_tables rdat ON rdat.object_id = tbl.object_id - WHERE rdat.object_id IS NOT NULL') - ELSE - EXECUTE(N'INSERT INTO #tmp_extended_remote_data_archive_tables SELECT rdat.object_id, rdat.remote_table_name, - SUBSTRING(rdat.filter_predicate, 2, LEN(rdat.filter_predicate) - 2) as filter_predicate, - CASE - WHEN rdat.is_migration_paused = 1 AND rdat.migration_direction_desc = N''OUTBOUND'' THEN 1 - WHEN rdat.is_migration_paused = 1 AND rdat.migration_direction_desc = N''INBOUND'' THEN 2 - WHEN rdat.is_migration_paused = 0 AND rdat.migration_direction_desc = N''OUTBOUND'' THEN 3 - WHEN rdat.is_migration_paused = 0 AND rdat.migration_direction_desc = N''INBOUND'' THEN 4 - ELSE 0 - END AS migration_state - FROM sys.tables tbl LEFT JOIN sys.remote_data_archive_tables rdat ON rdat.object_id = tbl.object_id - WHERE rdat.object_id IS NOT NULL') - - - DROP TABLE #tmp_extended_remote_data_archive_tables - - @@ -177,6 +159,16 @@ DROP TABLE #tmp_ledger_view_column_names + + + DROP TABLE #file_tables + + + + + DROP TABLE #change_tracking_tables + + - tbl.is_remote_data_archive_enabled - - ISNULL(rdat.migration_state, 0) - - rdat.filter_predicate - rdat.remote_table_name - CASE WHEN rdat.remote_table_name IS NULL THEN 0 ELSE 1 END + 0 + 0 + '' + N'' + 0 diff --git a/src/Microsoft/SqlServer/Management/SqlScriptPublish/SqlScriptPublishModel.cs b/src/Microsoft/SqlServer/Management/SqlScriptPublish/SqlScriptPublishModel.cs index 3fba4895..8f12f3e6 100644 --- a/src/Microsoft/SqlServer/Management/SqlScriptPublish/SqlScriptPublishModel.cs +++ b/src/Microsoft/SqlServer/Management/SqlScriptPublish/SqlScriptPublishModel.cs @@ -121,8 +121,8 @@ private void InitServer() // tell the server object to initialize the Database with some expensive properties in one query // these are "least common denominator" since we don't know the edition of the db var initFields = Server.GetDefaultInitFields(typeof(Smo.Database), smoServer.DatabaseEngineEdition); - initFields.AddRange(new string[] { "CompatibilityLevel", "IsMirroringEnabled", "Collation" }); - smoServer.SetDefaultInitFields(typeof(Smo.Database), initFields); + initFields.AddRange(new string[] { "CompatibilityLevel", "IsMirroringEnabled", "Collation", nameof(Database.AnsiPaddingEnabled), "DefaultSchema" }); + smoServer.SetDefaultInitFields(typeof(Smo.Database), initFields); } #endregion