views:

74

answers:

2

I have an extremely odd problem in my JUnit tests that I just can't seem to nail down. I have a multi-module java webapp project with a fairly standard structure (DAO's, service clasess, etc...). Within this project I have a 'core' project which contains some abstracted setup code which inserts a test user along with the necessary items for a user (in this case an 'enterprise', so a user must belong to an enterprise and this is enforced at the database level)

Fairly simple so far... but here is where the strangeness begins

  1. some tests fail to run and throw a database exception where it complains that a user cannot be inserted because an enterprise does not exist. But it just created the enterprise in the preceding line of code! And there was no errors in the insertion of the enterprise.
  2. Stranger yet, if this test class is run by itself everything works fine. It is only when the test is run as part of the project that it fails!
  3. And the exact same abstracted code was run by 10+ tests before the one that fails!
  4. f

I have been banging my head against a wall with this for days and haven't really made any progress. I'm not even sure what information to offer up to help diagnose this.

  • Using JUnit 4.4, Spring 2.5.6, iBatis 2.3.0, Postgresql 8.3
  • Switching to org.springframework.jdbc.datasource.DriverManagerDataSource from org.apache.commons.dbcp.BasicDataSource changed the problem. Using DriverManagerDataSource the tests work for the first time, but now all of a sudden a lot of data isn't rolled back in the database! It leaves everything behind. All with no errors
  • Tests fail when run via Eclipse & Maven

Please ask for any info which may help me solve my problem!

Update: I have turned up the logging to the max. There is only one slight difference between this test that fails, and another just like it which succeeds. The difference is highlighted. After the error occurs I see a number of "Creating [java.util.concurrent.ConcurrentHashMap]" lines and then the error handling code begins

+1  A: 

This is a hard problem to address on SO, but I'm going to take a wild guess based on the details you have given.

Hypothesis: The inserts in the test all happen in a transaction, which gets rolled back. (This is why BasicDataSource leaves the database clean - it rolls back at the end). When that stops happening (by using DriverManagerDataSource) the test passes but the database is not rolled back.

This suggests that even though "it just created the enterprise in the preceding line of code!" the transaction can be rolled back to remove the record.

Further hypothesis (ok that over states it, try wild guess), something (either in BasicDataSource or more likely your own framework code) calls the rollback of the transaction in a finalizer. This is why it works 10+ times and then fails, and only as part of the project - it is that run which triggers garbage collection at that point, causing the transaction to roll back, causing the new line of code to start a new transaction with the records in an invalid state.

EDIT: From your edit, it seems that for some reason on the failing test you get a new database connection. That fits my scenario above, but could also be explained by stating that for some reason at that point in the code gets a new transaction when the test is run as part of the whole project. The new transaction does not see the insert of the enterprise record which happens in a different transaction and the transaction remains open and uncommitted. A way to test for that is to set the transaction isolation level on this run up to the max, so that nothing can scan the enterprise table while there is a transaction on it. Then your code would deadlock if this scenario is correct.

Further EDIT: What I mean by a finalizer that calls close is something like this:

 public class SomeConnectionWrapper {
      private Connection dbConnection;

      protected void finalize() {
           dbConnection.close();
      }
 }

If SomeConnectionWrapper is garbage collected and the connection closed, your database connection pool will return a different connection.

Yishai
Thanks for the help Yishai. How would it be possible for a transaction to be rolled back with nothing triggering that rollback? i.e. there are no errors at all and I am not triggering a rollback.I just amped up the logging to the max and there is only one difference that I can see between this test that is failing and another just like it. I will post those results in a minute
Collin Peters
@Collin, as I said, I'm in the wild-guess territory. A rollback can be triggered in code a couple of ways: Closing a database connection that is set to `autocommit:false` or explicitly rolling back. I would check your code for a finalizer that closes the connection.
Yishai
Results posted in main question
Collin Peters
This looks like a similar issue... but I am no closer to a solution: http://jira.springframework.org/browse/SPR-6949In regards to your edit above, I wasn't able to make any difference with the isolation level. I tried all 5 options spring has at the test method level and class level with no difference.What exactly do you mean by a 'finalizer that closes the connection'? What has me confused is that the logs show (see above links) that in the problem code it suddenly just switches ConnectionHolder instances halfway through the test with no indication of why
Collin Peters
A: 

Well... I seem to have stumbled upon the solution to this problem, although I can't really put my finger on what exactly it is. I was rebuilding my tests by moving them all and then restoring them one by one. Low and behold it wasn't until I added the final test class that the problem showed up again

This test class extended from a base test class which defined all the Spring related setup annotations like @RunWith and @ContextConfiguration. For some reason this test class redefined the @RunWith and @ContextConfiguration attributes and as soon as I removed them things started working again. The other odd thing about this test class was that I was using @Ignore on each test in it (it contained some old tests).

So... if anybody ever has this strange problem, you can try removing the extra annotations to fix it up

Collin Peters