diff --git a/OpenSim/Data/Tests/BasicDataServiceTest.cs b/OpenSim/Data/Tests/BasicDataServiceTest.cs
new file mode 100644
index 0000000000..82f29d6a02
--- /dev/null
+++ b/OpenSim/Data/Tests/BasicDataServiceTest.cs
@@ -0,0 +1,171 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using log4net.Config;
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+using OpenMetaverse;
+using OpenSim.Framework;
+using log4net;
+using System.Data;
+using System.Data.Common;
+using System.Reflection;
+
+namespace OpenSim.Data.Tests
+{
+ /// This is a base class for testing any Data service for any DBMS.
+ /// Requires NUnit 2.5 or better (to support the generics).
+ ///
+ ///
+ ///
+ public class BasicDataServiceTest
+ where TConn : DbConnection, new()
+ where TService : class, new()
+ {
+ protected string m_connStr;
+ private TService m_service;
+ private string m_file;
+
+ // TODO: Is this in the right place here?
+ protected static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ public BasicDataServiceTest()
+ : this("")
+ {
+ }
+
+ public BasicDataServiceTest(string conn)
+ {
+ m_connStr = !String.IsNullOrEmpty(conn) ? conn : DefaultTestConns.Get(typeof(TConn));
+
+ OpenSim.Tests.Common.TestLogging.LogToConsole(); // TODO: Is that right?
+ }
+
+ ///
+ /// To be overridden in derived classes. Do whatever init with the m_service, like setting the conn string to it.
+ /// You'd probably want to to cast the 'service' to a more specific type and store it in a member var.
+ /// This framework takes care of disposing it, if it's disposable.
+ ///
+ /// The service being tested
+ protected virtual void InitService(object service)
+ {
+ }
+
+ [TestFixtureSetUp]
+ public void Init()
+ {
+ // Sorry, some SQLite-specific stuff goes here (not a big deal, as its just some file ops)
+ if (typeof(TConn).Name.StartsWith("Sqlite"))
+ {
+ // SQLite doesn't work on power or z linux
+ if (Directory.Exists("/proc/ppc64") || Directory.Exists("/proc/dasd"))
+ Assert.Ignore();
+
+ // for SQLite, if no explicit conn string is specified, use a temp file
+ if (String.IsNullOrEmpty(m_connStr))
+ {
+ m_file = Path.GetTempFileName() + ".db";
+ m_connStr = "URI=file:" + m_file + ",version=3";
+ }
+ }
+
+ if (String.IsNullOrEmpty(m_connStr))
+ {
+ string msg = String.Format("Connection string for {0} is not defined, ignoring tests", typeof(TConn).Name);
+ m_log.Error(msg);
+ Assert.Ignore(msg);
+ }
+
+ // If we manage to connect to the database with the user
+ // and password above it is our test database, and run
+ // these tests. If anything goes wrong, ignore these
+ // tests.
+ try
+ {
+ m_service = new TService();
+ InitService(m_service);
+ }
+ catch (Exception e)
+ {
+ m_log.Error(e.ToString());
+ Assert.Ignore();
+ }
+ }
+
+ [TestFixtureTearDown]
+ public void Cleanup()
+ {
+ if (m_service != null)
+ {
+ if( m_service is IDisposable)
+ ((IDisposable)m_service).Dispose();
+ m_service = null;
+ }
+
+ if( !String.IsNullOrEmpty(m_file) && File.Exists(m_file) )
+ File.Delete(m_file);
+ }
+
+ protected virtual DbConnection Connect()
+ {
+ DbConnection cnn = new TConn();
+ cnn.ConnectionString = m_connStr;
+ cnn.Open();
+ return cnn;
+ }
+
+ protected virtual void ExecuteSql(string sql)
+ {
+ using (DbConnection dbcon = Connect())
+ {
+ using (DbCommand cmd = dbcon.CreateCommand())
+ {
+ cmd.CommandText = sql;
+ cmd.ExecuteNonQuery();
+ }
+ }
+ }
+
+ protected delegate bool ProcessRow(IDataReader reader);
+
+ protected virtual int ExecQuery(string sql, bool bSingleRow, ProcessRow action)
+ {
+ int nRecs = 0;
+ using (DbConnection dbcon = Connect())
+ {
+ using (DbCommand cmd = dbcon.CreateCommand())
+ {
+ cmd.CommandText = sql;
+ CommandBehavior cb = bSingleRow ? CommandBehavior.SingleRow : CommandBehavior.Default;
+ using (DbDataReader rdr = cmd.ExecuteReader(cb))
+ {
+ while (rdr.Read())
+ {
+ nRecs++;
+ if (!action(rdr))
+ break;
+ }
+ }
+ }
+ }
+ return nRecs;
+ }
+
+ /// Drop tables (listed as parameters). There is no "DROP IF EXISTS" syntax common for all
+ /// databases, so we just DROP and ignore an exception.
+ ///
+ ///
+ protected virtual void DropTables(params string[] tables)
+ {
+ foreach (string tbl in tables)
+ {
+ try
+ {
+ ExecuteSql("DROP TABLE " + tbl + ";");
+ }catch
+ {
+ }
+ }
+ }
+ }
+}
diff --git a/OpenSim/Data/Tests/DefaultTestConns.cs b/OpenSim/Data/Tests/DefaultTestConns.cs
new file mode 100644
index 0000000000..7b52af575a
--- /dev/null
+++ b/OpenSim/Data/Tests/DefaultTestConns.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+using System.IO;
+using Nini.Config;
+
+namespace OpenSim.Data.Tests
+{
+ /// This static class looks for TestDataConnections.ini file in the /bin directory to obtain
+ /// a connection string for testing one of the supported databases.
+ /// The connections must be in the section [TestConnections] with names matching the connection class
+ /// name for the specific database, e.g.:
+ ///
+ /// [TestConnections]
+ /// MySqlConnection="..."
+ /// SqlConnection="..."
+ /// SqliteConnection="..."
+ ///
+ /// Note that the conn string may also be set explicitly in the [TestCase()] attribute of test classes
+ /// based on BasicDataServiceTest.cs.
+ ///
+
+ static class DefaultTestConns
+ {
+ private static Dictionary conns = new Dictionary();
+
+ public static string Get(Type connType)
+ {
+ string sConn;
+
+ if (conns.TryGetValue(connType, out sConn))
+ return sConn;
+
+ Assembly asm = Assembly.GetExecutingAssembly();
+ string sType = connType.Name;
+
+ // Note: when running from NUnit, the DLL is located in some temp dir, so how do we get
+ // to the INI file? Ok, so put it into the resources!
+ // string iniName = Path.Combine(Path.GetDirectoryName(asm.Location), "TestDataConnections.ini");
+
+ string[] allres = asm.GetManifestResourceNames();
+ string sResFile = Array.Find(allres, s => s.Contains("TestDataConnections.ini"));
+
+ if (String.IsNullOrEmpty(sResFile))
+ throw new Exception(String.Format("Please add resource TestDataConnections.ini, with section [TestConnections] and settings like {0}=\"...\"",
+ sType));
+
+ using (Stream resource = asm.GetManifestResourceStream(sResFile))
+ {
+ IConfigSource source = new IniConfigSource(resource);
+ var cfg = source.Configs["TestConnections"];
+ sConn = cfg.Get(sType, "");
+ }
+
+ if (!String.IsNullOrEmpty(sConn))
+ conns[connType] = sConn;
+
+ return sConn;
+ }
+ }
+}
diff --git a/OpenSim/Data/Tests/Resources/TestDataConnections.ini b/OpenSim/Data/Tests/Resources/TestDataConnections.ini
new file mode 100644
index 0000000000..d149744fd6
--- /dev/null
+++ b/OpenSim/Data/Tests/Resources/TestDataConnections.ini
@@ -0,0 +1,7 @@
+; The default connections to the test databases. Used by tests based on BasicDataServiceTest.cs.
+; Read by code in DefaultTestConns.cs
+
+[TestConnections]
+MySqlConnection="Server=localhost;Port=3306;Database=opensim-nunit;User ID=opensim-nunit;Password=opensim-nunit;"
+;SqlConnection="..."
+;SqliteConnection="..."
\ No newline at end of file