From 91ad1f4ee71dc8e945e9be7b85f7d4e0ddcf4156 Mon Sep 17 00:00:00 2001 From: AlexRa Date: Mon, 17 May 2010 12:37:49 +0300 Subject: Added generic base classes for testing database services These are some generic classes that simplify writing tests for any of the data connectors and databases. Among other things, configuring the connection strings is done once, in a separate resource file. Tests based on the new BasicDataServiceTest class require NUnit 2.5 or better. --- OpenSim/Data/Tests/BasicDataServiceTest.cs | 171 +++++++++++++++++++++ OpenSim/Data/Tests/DefaultTestConns.cs | 63 ++++++++ .../Data/Tests/Resources/TestDataConnections.ini | 7 + 3 files changed, 241 insertions(+) create mode 100644 OpenSim/Data/Tests/BasicDataServiceTest.cs create mode 100644 OpenSim/Data/Tests/DefaultTestConns.cs create mode 100644 OpenSim/Data/Tests/Resources/TestDataConnections.ini diff --git a/OpenSim/Data/Tests/BasicDataServiceTest.cs b/OpenSim/Data/Tests/BasicDataServiceTest.cs new file mode 100644 index 0000000..82f29d6 --- /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 0000000..7b52af5 --- /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 0000000..d149744 --- /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 -- cgit v1.1