//===============================================================================
// Copyright © Microsoft Corporation and Mark Seemann. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security.Permissions;
namespace Ploeh.Common.DBTest
{
///
/// Enables integration testing against SQL Server.
///
///
///
/// SqlServerIntegrationTester contains helper methods to create and delete SQL server
/// databases for testing purposes. To create a transient database for testing purposes, use
/// the . To delete it again, use the
/// method.
///
///
/// The and collections are
/// used to define T-SQL scripts for defining the database schema, etc.
///
///
public class SqlServerIntegrationTester
{
private string server_;
private string testDatabaseName_;
private List initializationScripts_;
private List cleanUpScripts_;
///
/// Initializes a new instance of the class.
///
///
///
/// Before a new instance of the class is used,
/// the and properties must be set.
///
///
public SqlServerIntegrationTester()
{
this.initializationScripts_ = new List();
this.cleanUpScripts_ = new List();
}
///
/// Gets or sets the name of the database server.
///
public string Server
{
get { return this.server_; }
set { this.server_ = value; }
}
///
/// Gets or sets the name of the test database that will be created by
/// .
///
public string TestDatabaseName
{
get { return this.testDatabaseName_; }
set { this.testDatabaseName_ = value; }
}
///
/// Gets the connection string for the test database.
///
public string TestDatabaseConnectionString
{
get
{
SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder();
csb.DataSource = this.Server;
csb.InitialCatalog = this.TestDatabaseName;
csb.IntegratedSecurity = true;
csb.Pooling = false;
return csb.ConnectionString;
}
}
///
/// Gets the connection string for the master database.
///
public string MasterDatabaseConnectionString
{
get
{
SqlConnectionStringBuilder csb = new SqlConnectionStringBuilder();
csb.DataSource = this.Server;
csb.InitialCatalog = "master";
csb.IntegratedSecurity = true;
csb.Pooling = false;
return csb.ConnectionString;
}
}
///
/// Gets the list of initialization scripts.
///
public IList InitializationScripts
{
get { return this.initializationScripts_; }
}
///
/// Gets the list of clean-up scripts.
///
public IList CleanUpScripts
{
get { return this.cleanUpScripts_; }
}
///
/// Creates the test database and database objects.
///
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
public void CreateTestDatabase()
{
this.DeleteTestDatabase();
// Create the database.
SqlServerIntegrationTester.ExecuteNonQuery(this.MasterDatabaseConnectionString,
"CREATE DATABASE {0}", this.TestDatabaseName);
// Create the objects in the database.
foreach(string scriptFile in this.InitializationScripts)
{
this.RunSqlScript(scriptFile);
}
}
///
/// Deletes the test database.
///
public void DeleteTestDatabase()
{
SqlServerIntegrationTester.ExecuteNonQuery(this.MasterDatabaseConnectionString,
"IF EXISTS (SELECT [name] FROM sys.databases WHERE [name] = '{0}') BEGIN DROP DATABASE {0} END",
this.TestDatabaseName);
}
///
/// Executes a SQL statement with no return values.
///
/// The SQL statement to execute.
/// Arguments for string formatting, if any.
public void ExecuteNonQuery(string sqlStatement, params object[] args)
{
SqlServerIntegrationTester.ExecuteNonQuery(this.TestDatabaseConnectionString,
sqlStatement, args);
}
///
/// Executes a SQL statement and returns a .
///
/// The T-SQL statement to execute.
/// Arguments for string formatting, if any.
///
/// A over the result set for .
///
///
///
/// The client is responsible for closing the returned after
/// use. Closing the reader will automatically close the underlying connection as well.
///
///
public SqlDataReader ExecuteReader(string sqlStatement, params object[] args)
{
SqlConnection con = new SqlConnection(this.TestDatabaseConnectionString);
SqlCommand cmd = new SqlCommand(string.Format(sqlStatement, args), con);
con.Open();
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
///
/// Executes a SQL statement and returns a single return value.
///
/// The SQL statement to execute.
/// Arguments for string formatting, if any.
/// The return value from the query.
public object ExecuteScalar(string sqlStatement, params object[] args)
{
using (SqlConnection conn = new SqlConnection(this.TestDatabaseConnectionString))
{
using (SqlCommand cmd = new SqlCommand(string.Format(CultureInfo.CurrentCulture, sqlStatement, args), conn))
{
conn.Open();
return cmd.ExecuteScalar();
}
}
}
///
/// Resets all tables after each test.
///
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
public void ResetTables()
{
foreach (string scriptFile in this.CleanUpScripts)
{
this.RunSqlScript(scriptFile);
}
}
///
/// Executes a non-query
///
/// The connection string used for the query.
/// The T-SQL statement to execute.
/// Any parameters that will be applied to the statement.
private static void ExecuteNonQuery(string connectionString, string sqlStatement, params object[] args)
{
using (SqlConnection con = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand(string.Format(CultureInfo.CurrentCulture, sqlStatement, args), con))
{
con.Open();
cmd.ExecuteNonQuery();
}
}
}
///
/// Runs a SQL script file.
///
/// The file name of the script file.
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
private void RunSqlScript(string scriptFile)
{
using (Process p = new Process())
{
p.StartInfo.FileName = "sqlcmd.exe";
p.StartInfo.Arguments =
string.Format(CultureInfo.CurrentCulture,
"-b -S {0} -d {1} -i \"{2}\"",
this.Server,
this.TestDatabaseName, Path.GetFullPath(scriptFile));
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
if (p.ExitCode != 0)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "An error occurred while executing a SQL script file. \nScript output: {0}\nScript error: {1}", output, error));
}
}
}
}
}