diff --git a/.gitignore b/.gitignore
index 78b49ee..930ef52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+/tempServer
+
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
diff --git a/Example.cs b/Example.cs
new file mode 100644
index 0000000..f534442
--- /dev/null
+++ b/Example.cs
@@ -0,0 +1,52 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using MySql.Data.MySqlClient;
+using System;
+using System.IO;
+using MySql.Server;
+
+namespace Example
+{
+ [TestClass]
+ public class Example
+ {
+ //Starting the MySql server. Here it is done in the AssemblyInitialize method for performance purposes.
+ //It could also be restarted in every test using [TestInitialize] attribute
+ [AssemblyInitialize]
+ public static void Initialize(TestContext context)
+ {
+ MySqlServer dbServer = MySqlServer.Instance;
+ dbServer.StartServer();
+
+ //Let us create a table
+ dbServer.ExecuteNonQuery("CREATE TABLE testTable (`id` INT NOT NULL, `value` CHAR(150) NULL, PRIMARY KEY (`id`)) ENGINE = MEMORY;");
+
+ //Insert data. You could of course insert data from a *.sql file
+ dbServer.ExecuteNonQuery("INSERT INTO testTable (`value`) VALUES ('some value')");
+ }
+
+ //The server is shutdown as the test ends
+ [AssemblyCleanup]
+ public static void Cleanup()
+ {
+ MySqlServer dbServer = MySqlServer.Instance;
+
+ dbServer.ShutDown();
+ }
+
+ //Concrete test. Writes data and reads it again.
+ [TestMethod]
+ public void TestMethod()
+ {
+ MySqlServer database = MySqlServer.Instance;
+
+ database.ExecuteNonQuery("insert into testTable (`id`, `value`) VALUES (2, 'test value')");
+
+ using (MySqlDataReader reader = database.ExecuteReader("select * from testTable WHERE id = 2"))
+ {
+ reader.Read();
+
+ Assert.AreEqual("test value", reader.GetString("value"), "Inserted and read string should match");
+ }
+ }
+ }
+}
diff --git a/Library/BaseDirHelper.cs b/Library/BaseDirHelper.cs
new file mode 100644
index 0000000..a1ad1d1
--- /dev/null
+++ b/Library/BaseDirHelper.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace MySql.Server
+{
+ internal class BaseDirHelper
+ {
+ static string baseDir;
+ public static string GetBaseDir()
+ {
+ if (baseDir == null)
+ {
+ baseDir = new DirectoryInfo(Directory.GetCurrentDirectory()).Parent.Parent.FullName.ToString();
+ }
+
+ return baseDir;
+ }
+ }
+}
diff --git a/Library/DBTestConnectionStringFactory.cs b/Library/DBTestConnectionStringFactory.cs
new file mode 100644
index 0000000..c159253
--- /dev/null
+++ b/Library/DBTestConnectionStringFactory.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace MySql.Server
+{
+ /*
+ * Static class used to serve connection strings
+ */
+ internal class DBTestConnectionStringFactory : IDBConnectionStringFactory
+ {
+ /*
+ * Returns a connection string of the server
+ */
+ public string Server(){
+ // return "Server=localhost;Protocol=pipe;";
+ return "Server=" + "127.0.0.1" + ";Protocol=pipe;";
+ }
+
+ /*
+ * Returns a connection string of the default database (the test server)
+ */
+ public string Database()
+ {
+ return "Server=" + "127.0.0.1" + ";Database=testserver;Protocol=pipe;";
+ }
+
+ /**
+ * Returns a connection string of a specific database
+ */
+ public string Database(string databaseName)
+ {
+ return "Server=" + "127.0.0.1" + ";Database=" + databaseName + ";Protocol=pipe;";
+ }
+ }
+}
diff --git a/Library/IDBConnectionStringFactory.cs b/Library/IDBConnectionStringFactory.cs
new file mode 100644
index 0000000..5aad6c1
--- /dev/null
+++ b/Library/IDBConnectionStringFactory.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MySql.Server
+{
+ internal interface IDBConnectionStringFactory
+ {
+ string Server();
+ string Database();
+ string Database(string databaseName);
+ }
+}
diff --git a/Library/MySqlServer.cs b/Library/MySqlServer.cs
new file mode 100644
index 0000000..5cfbceb
--- /dev/null
+++ b/Library/MySqlServer.cs
@@ -0,0 +1,306 @@
+using MySql.Data.MySqlClient;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading;
+
+namespace MySql.Server
+{
+ /**
+ * A singleton class controlling test database initializing and cleanup
+ */
+ public class MySqlServer : IDisposable
+ {
+ //The Instance is running the private constructor. This way, the class is implemented as a singleton
+ private static MySqlServer instance;
+ public static MySqlServer Instance
+ {
+ get
+ {
+ if (instance == null)
+ {
+ instance = new MySqlServer(new DBTestConnectionStringFactory());
+ }
+
+ return instance;
+ }
+ }
+
+
+ private string _mysqlDirectory;
+ private string _dataDirectory;
+ private string _dataRootDirectory;
+
+ private IDBConnectionStringFactory _conStrFac;
+
+ private MySqlConnection _myConnection;
+ public MySqlConnection Connection {
+ get {
+ if (this._myConnection == null)
+ {
+ this.OpenConnection(this._conStrFac.Database());
+ }
+ return this._myConnection;
+ }
+ }
+
+ private Process _process;
+
+ private MySqlServer(IDBConnectionStringFactory conStrFac)
+ {
+ this._mysqlDirectory = BaseDirHelper.GetBaseDir() + "\\tempServer";
+ this._dataRootDirectory = this._mysqlDirectory + "\\data";
+ this._dataDirectory = this._dataRootDirectory + "\\" + Guid.NewGuid() + "";
+
+ this.killProcesses();
+
+ this.createDirs();
+
+ this.extractMySqlFiles();
+
+ this._conStrFac = conStrFac;
+ }
+
+ /* private void removeDirs()
+ {
+ //Removing any previous data directories
+ new DirectoryInfo(this._dataRootDirectory).GetDirectories().ToList().ForEach(delegate(DirectoryInfo dir)
+ {
+ try
+ {
+ dir.Delete(true);
+ }
+ catch (Exception)
+ {
+ System.Console.WriteLine("Could not delete data directory" + dir.FullName);
+ }
+ });
+ }
+ */
+ private void createDirs()
+ {
+ string[] dirs = { this._mysqlDirectory, this._dataRootDirectory, this._dataDirectory };
+
+ foreach (string dir in dirs) {
+ DirectoryInfo checkDir = new DirectoryInfo(dir);
+ try
+ {
+ if (checkDir.Exists)
+ checkDir.Delete(true);
+
+ checkDir.Create();
+ }
+ catch(Exception)
+ {
+ System.Console.WriteLine("Could not create or delete directory: ", checkDir);
+ }
+ }
+ }
+
+ private void extractMySqlFiles()
+ {
+ try {
+ if (!new FileInfo(this._mysqlDirectory + "\\mysqld.exe").Exists) {
+ //Extracting the two MySql files needed for the standalone server
+ File.WriteAllBytes(this._mysqlDirectory + "\\mysqld.exe", Properties.Resources.mysqld);
+ File.WriteAllBytes(this._mysqlDirectory + "\\errmsg.sys", Properties.Resources.errmsg);
+ }
+ }
+ catch
+ {
+ throw;
+ }
+ }
+
+ private void killProcesses()
+ {
+ //Killing all processes with the name mysqld.exe
+ foreach (var process in Process.GetProcessesByName("mysqld"))
+ {
+ try
+ {
+ process.Kill();
+ }
+ catch (Exception)
+ {
+ System.Console.WriteLine("Tried to kill already existing mysqld process without success");
+ }
+ }
+ }
+
+ public void StartServer()
+ {
+ _process = new Process();
+
+ var arguments = new[]
+ {
+ "--standalone",
+ "--console",
+ "--basedir=" + "\"" + this._mysqlDirectory + "\"",
+ "--lc-messages-dir=" + "\"" + this._mysqlDirectory + "\"",
+ "--datadir=" + "\"" + this._dataDirectory + "\"",
+ "--skip-grant-tables",
+ "--enable-named-pipe",
+ "--skip-networking",
+ "--innodb_fast_shutdown=2",
+ "--innodb_doublewrite=OFF",
+ "--innodb_log_file_size=1048576",
+ "--innodb_data_file_path=ibdata1:10M;ibdata2:10M:autoextend"
+ };
+
+ _process.StartInfo.FileName = "\"" + this._mysqlDirectory + "\\mysqld.exe" + "\"";
+ _process.StartInfo.Arguments = string.Join(" ", arguments);
+ _process.StartInfo.UseShellExecute = false;
+ _process.StartInfo.CreateNoWindow = true;
+
+ System.Console.WriteLine("Running " + _process.StartInfo.FileName + " " + String.Join(" ", arguments));
+
+ try {
+ _process.Start();
+ }
+ catch(Exception e){
+ throw new Exception("Could not start server process: " + e.Message);
+ }
+
+ this.waitForStartup();
+ }
+
+ /**
+ * Checks if the server is started. The most reliable way is simply to check
+ * if we can connect to it
+ **/
+ private void waitForStartup()
+ {
+ bool connected = false;
+ int waitTime = 0;
+
+ while (!connected)
+ {
+ if (waitTime > 10000)
+ throw new Exception("Server could not be started");
+
+ waitTime = waitTime + 500;
+
+ try
+ {
+ this.OpenConnection(this._conStrFac.Server());
+ connected = true;
+
+ this.ExecuteNonQuery("CREATE DATABASE testserver;USE testserver;", false);
+
+ System.Console.WriteLine("Database connection established after " + waitTime.ToString() + " miliseconds");
+ }
+ catch (Exception)
+ {
+ Thread.Sleep(500);
+ connected = false;
+ }
+ }
+ }
+
+ private void ExecuteNonQuery(string query, bool useDatabase)
+ {
+ string connectionString = useDatabase ? this._conStrFac.Database() : this._conStrFac.Server();
+ this.OpenConnection(connectionString);
+ try
+ {
+ MySqlCommand command = new MySqlCommand(query, this._myConnection);
+ command.ExecuteNonQuery();
+ }
+ catch (Exception e)
+ {
+ System.Console.WriteLine("Could not execute non query: " + e.Message);
+ throw;
+ }
+ finally{
+ this.CloseConnection();
+ }
+ }
+
+ public void ExecuteNonQuery(string query)
+ {
+ this.ExecuteNonQuery(query, true);
+ }
+
+ public MySqlDataReader ExecuteReader(string query)
+ {
+ this.OpenConnection(this._conStrFac.Database());
+
+ try {
+ MySqlCommand command = new MySqlCommand(query, this._myConnection);
+ return command.ExecuteReader();
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+
+ private void OpenConnection(string connectionString)
+ {
+ if (this._myConnection == null)
+ {
+ this._myConnection = new MySqlConnection(connectionString);
+
+ }
+
+ if (this._myConnection.State != System.Data.ConnectionState.Open)
+ {
+ this._myConnection.Open();
+ }
+ }
+
+ public void CloseConnection()
+ {
+ if(this._myConnection.State != System.Data.ConnectionState.Closed)
+ this._myConnection.Close();
+ }
+
+ public void ShutDown()
+ {
+ try
+ {
+ this.CloseConnection();
+ if (!this._process.HasExited)
+ {
+ this._process.Kill();
+ }
+ //System.Console.WriteLine("Process killed");
+ this._process.Dispose();
+ this._process = null;
+ this.killProcesses();
+ }
+ catch (Exception e)
+ {
+ System.Console.WriteLine("Could not close database server process: " + e.Message);
+ throw;
+ }
+ }
+
+ private bool disposed = false;
+
+ // Public implementation of Dispose pattern callable by consumers.
+ public void Dispose()
+ {
+ Dispose(true);
+ // GC.SuppressFinalize(this);
+ }
+
+ // Protected implementation of Dispose pattern.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposed)
+ return;
+
+ if (disposing)
+ {
+ this.CloseConnection();
+ this._myConnection.Dispose();
+ }
+
+ disposed = true;
+ }
+ }
+}
diff --git a/MySql.Server.csproj b/MySql.Server.csproj
new file mode 100644
index 0000000..0d38b51
--- /dev/null
+++ b/MySql.Server.csproj
@@ -0,0 +1,91 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {983C634C-28A0-4447-96CF-4839051A92ED}
+ Library
+ Properties
+ MySql.Server
+ MySql.Server
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+ False
+ ..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
\ No newline at end of file
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..2b16145
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MySql.Server")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MySql.Server")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ec2b217f-4563-4d02-b21c-a521302a64b4")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..1957677
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,83 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.18444
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace MySql.Server.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MySql.Server.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] errmsg {
+ get {
+ object obj = ResourceManager.GetObject("errmsg", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+
+ ///
+ /// Looks up a localized resource of type System.Byte[].
+ ///
+ internal static byte[] mysqld {
+ get {
+ object obj = ResourceManager.GetObject("mysqld", resourceCulture);
+ return ((byte[])(obj));
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..392da60
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\MySqlServer\errmsg.sys;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\MySqlServer\mysqld.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..61ed6f2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,76 @@
+# MySqlStandAloneServer
+MySql standalone server for C# unit tests
+
+## Use
+Download with NuGet, or download the [release](https://github.com/stumpdk/MySqlStandAloneServer/releases) and include **MySqlStandAloneServer.dll** as a reference in your project.
+
+## How does it work?
+MySqlStandAloneServer is simply running a minimal instance of MySql (currently version 5.6.26). Necessary data and log files are created at run time (and are cleaned up afterwards).
+
+The software makes it possible to create and run unit tests on a real MySql server without spending time on server setup.
+
+## Example
+
+### Create server, table and data.
+See [Example.cs](https://github.com/stumpdk/MySqlStandAloneServer/blob/master/Example.cs) for a complete example.
+```c#
+ //Starting the MySql server. Here it is done in the AssemblyInitialize method for performance purposes.
+ //It could also be restarted in every test using [TestInitialize] attribute
+ [AssemblyInitialize]
+ public static void Initialize(TestContext context)
+ {
+ MySqlServer dbServer = MySqlServer.Instance;
+ dbServer.StartServer();
+
+ //Let us create a table
+ dbServer.ExecuteNonQuery("CREATE TABLE testTable (`id` INT NOT NULL, `value` CHAR(150) NULL, PRIMARY KEY (`id`)) ENGINE = MEMORY;");
+
+ //Insert data
+ dbServer.ExecuteNonQuery("INSERT INTO testTable (`value`) VALUES ('some value')");
+ }
+```
+
+### Make a test
+```c#
+ //Concrete test. Writes data and reads it again.
+ [TestMethod]
+ public void TestMethod()
+ {
+ MySqlServer database = MySqlServer.Instance;
+
+ database.ExecuteNonQuery("insert into testTable (`id`, `value`) VALUES (2, 'test value')");
+
+ using (MySqlDataReader reader = database.ExecuteReader("select * from testTable WHERE id = 2"))
+ {
+ reader.Read();
+
+ Assert.AreEqual("test value", reader.GetString("value"), "Inserted and read string should match");
+ }
+ }
+```
+
+### Shut down server
+```c#
+ //The server is shutdown as the test ends
+ [AssemblyCleanup]
+ public static void Cleanup()
+ {
+ MySqlServer dbServer = MySqlServer.Instance;
+
+ dbServer.ShutDown();
+ }
+```
+
+## API
+* **MySqlServer.Instance**: Retrieves an Instance of the server API.
+
+* **MySqlServer.StartServer()**: Starts the server and creates a database ("testdatabase"). Optimally this is run once during a test run. But it can be run as many times as needed (to get independent tests).
+
+* **MySqlServer.ShutDown()**: Shuts down the server.
+
+* **MySqlServer.ExecuteNonQuery(string query)**: Executes a query with no return on default database ("testdatabase").
+
+* **MySqlServer.ExecuteReader(string query)**: Executes query and returns a MySqlDataReader object.
+
+* **MySqlServer.Connection**: Returns a connection object to the default database ("testdatabase"). This can be used to make more specific operations on the server with a MySqlCommand object (like prepared statements or ExecuteReaderAsync).
+
diff --git a/app.config b/app.config
new file mode 100644
index 0000000..eea9a1c
--- /dev/null
+++ b/app.config
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages.config b/packages.config
new file mode 100644
index 0000000..d315b8f
--- /dev/null
+++ b/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file