Random error when testing with NHibernate on an in-Memory SQLite db
Asked Answered
P

2

8

I have a system which after getting a message - enqueues it (write to a table), and another process polls the DB and dequeues it for processing. In my automatic tests I've merged the operations in the same process, but cannot (conceptually) merge the NH sessions from the two operations.

Naturally - problems arise.

I've read everything I could about getting the SQLite-InMemory-NHibernate combination to work in the testing world, but I've now ran into RANDOMLY failing tests, due to "no such table" errors. To make it clear - "random" means that the same test with the same exact configuration and code will sometimes fail.

I have the following SQLite configuration:

return SQLiteConfiguration
 .Standard
 .ConnectionString(x => x.Is("Data Source=:memory:; Version=3; New=True; Pooling=True; Max Pool Size=1;"))
 .Raw(NHibernate.Cfg.Environment.ReleaseConnections, "on_close");

At the beginning of my test (every test) I fetch the "static" session provider, and kindly ask it to flush the existing DB clean, and recreate the schema:

public void PurgeDatabaseOrCreateNew()
{
    using (var session = GetNewSession())
    using (var tx = session.BeginTransaction())
    {
            PurgeDatabaseOrCreateNew(session);
            tx.Commit();
    }
}

private void PurgeDatabaseOrCreateNew(ISession session)
{
    //http://ayende.com/Blog/archive/2009/04/28/nhibernate-unit-testing.aspx
    new SchemaExport(_Configuration)
        .Execute(false, true, false, session.Connection, null);
}

So yes, it's on a different session, but the connection is pooled on SQLite, so the next session I create will see the generated schema. Yet, while most of the times it works - sometimes the later "enqueue" operation will fail because it cannot see a table for my incoming messages. Also - that seems to happen at max one or twice per test suite run; not all the tests are failing, just the first one (and sometimes another one. Not quite sure if it's the second or not).

The worst part is the randomness, naturally. I've told myself I've fixed this several times now, just because it simply "stopped failing". At random.

This happens on FW4.0, System.Data.SQLite x86 version, Win7 64b and 2008R2 (three differen machine in total), NH2.1.2, configured with FNH, on TestDriven.NET 32b precesses and NUnit console 32b processes.

Help?

Peppercorn answered 14/4, 2011 at 11:57 Comment(1)
I have this problem too. Almost every test passes, but once in a while one or two tests will fail with "no such table" errors, and if I run them again, they pass. I think it's just that SQLite is recreating the connection in the connection pool, randomly.Sauterne
A
8

Hi I'm pretty sure I have the exact same problem as you. I open and close multiple sessions per integration test. After digging through the SQLite connection pooling and some experimenting of my own, I've come to the following conclusion:

The SQLite pooling code caches the connection using WeakReferences, which isn't the best option for caching, since the reference to the connection(s) will be cleared when there is no normal (strong) reference to the connection and the GC runs. Since you can't predict when the GC runs, this explains the "randomness". Try and add a GC.Collect(); between closing one and opening another session, your test will always fail.

My solution was to cache the connection myself between opening sessions, like this:

public class BaseIntegrationTest
{
    private static ISessionFactory _sessionFactory;
    private static Configuration _configuration;
    private static SchemaExport _schemaExport;

    // I cache the whole session because I don't want it and the
    // underlying connection to get closed.
    // The "Connection" property of the ISession is what we actually want.
    // Using the NHibernate SQLite Driver to get the connection would probably
    // work too.
    private static ISession _keepConnectionAlive;

    static BaseIntegrationTest()
    {
        _configuration = new Configuration();
        _configuration.Configure();
        _configuration.AddAssembly(typeof(Product).Assembly);
        _sessionFactory = _configuration.BuildSessionFactory();
        _schemaExport = new SchemaExport(_configuration);

        _keepConnectionAlive = _sessionFactory.OpenSession();
    }

    [SetUp]
    protected void RecreateDB()
    {
        _schemaExport.Execute(false, true, false, _keepConnectionAlive.Connection, null);
    }

    protected ISession OpenSession()
    {
        return _sessionFactory.OpenSession(_keepConnectionAlive.Connection);
    }
}

Each of my integrationtests inherits from this class, and calls OpenSession() to get a session. RecreateDB is called by NUnit before each test because of the [SetUp] attribute.

I hope this helps you or anyone else who gets this error.

Airs answered 5/1, 2012 at 14:55 Comment(2)
Changing the caching mechanism of SQLite would probably be the cleanest solution though, but I don't feel confident to do that just yet ;-)Airs
That's exactly how I solved it. Sorry for not posting the solution earlier.Peppercorn
C
0

Only thing that comes into mind that you are randomly leaving session open after the test. You must make sure any existing ISession is closed before you open another one. If you are not using the using() statement or calling Dispose() manually the session might still be alive somewhere causing those random exceptions.

Chavarria answered 21/12, 2011 at 17:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.