4

Summary

I'm seeing some odd behaviour when setting up unit tests using appengine's High Replication Datastore.

I've put a full example below. The issue is that a persisted object from one test case is available to a later test case, but only when the later test case retrieves it within a transaction. As I understand it, the tearDown method should be clearing the datastore entirely.

I assume there's something wrong with my setup, or I'm missing something significant. Please help.

Specific Question

Given the code below, why is the output as follows? (in particular the last line which I've bolded)

17-Sep-2013 20:41:35 org.datanucleus.PersistenceConfiguration setProperty

INFO: Property datanucleus.appengine.singletonPMFForName unknown - will be ignored

17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate

INFO: Performing appengine-specific metadata validation for com.test.Thing

17-Sep-2013 20:41:36 com.google.appengine.datanucleus.MetaDataValidator validate

INFO: Finished performing appengine-specific metadata validation for com.test.Thing

17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init

INFO: Local Datastore initialized: Type: Master/Slave Storage: In-memory 17-Sep-2013 20:41:36 com.google.appengine.api.datastore.dev.LocalDatastoreService init

INFO: Local Datastore initialized: Type: Master/Slave Storage: In-memory

[OUTSIDE TRANSACTION] This correctly gets executed.

[INSIDE TRANSACTION] This should not be executed. THING:item

My comments

Unless I'm doing something very stupid, which is not unlikely, it appears that the item persisted in the first test case is still available to transactions in the second test case.

The second test case attempts to getObjectById outside of a transaction, and correctly throws an exception.

It then attempts the same within a transaction and retrieves an object that can only be the one persisted in the earlier test case, I think.

Why is this? What am I doing wrongly? Thanks very much in advance.

Code to replicate issue

I created a new Web Application Project in Eclipse, and added the classes and jars shown in this screenshot to it:

screenshot of package explorer

Class PMF looks like this:

package com.test;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {

    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PMF getInstance() {
        if (null == _instance) {
            _instance = new PMF();
        }
        return _instance;
    }

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }

    private static PMF _instance;
}

Class Thing looks like this:

package com.test;

import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@PersistenceCapable
public class Thing {

    public Thing(String item) {
        this.item = item;
        this.key = makeKey(item);
    }


    public static Key makeKey(String item) {
        return KeyFactory.createKey("Thing", item);
    }

    public String getId() {
        if (null == key) {
            return null;
        }
        return KeyFactory.keyToString(key);
    }

    public String getItem() {
        return item;
    }

    public String toString() {
        return "THING:" + item;
    }

    @PrimaryKey
    @Persistent
    private Key key;
    @Persistent
    private String item;
}

And finally the TestDatastore class looks like this:

package com.test;

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

import junit.framework.TestCase;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;

public class TestDatastore extends TestCase {

    @Override
    public void setUp() {
        helper.setUp(); 
    }

    @Override
    public void tearDown() {
        helper.tearDown();
    }

    public void testCreateThing() {

        String item = "item";
        Thing thing = new Thing(item);

        PersistenceManager pm = PMF.get().getPersistenceManager();
        Thing persisted = pm.makePersistent(thing);
        Thing result = pm.getObjectById(Thing.class, persisted.getId());

        assertEquals(item, result.getItem());
    }

    public void testThingDoesntExist() {

        String item = "item";
        Thing thing = new Thing(item);

        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try {
            Thing testThing = pm.getObjectById(Thing.class, Thing.makeKey(thing.getItem()));
        } catch (Exception e) {
            System.out.println("[OUTSIDE TRANSACTION] This correctly gets executed.");
        }

        try {
            tx.begin();
            Thing testThing = pm.getObjectById(Thing.class, Thing.makeKey(thing.getItem()));
            System.out.println("[INSIDE TRANSACTION] This should not be executed. " + testThing);
            tx.commit();
        } catch (Exception e) {
            System.out.println("This doesn't get executed, but it should");
        }
    }

    private final LocalServiceTestHelper helper = 
            new LocalServiceTestHelper(
                    new LocalDatastoreServiceTestConfig()
                    .setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
}
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
SJC
  • 652
  • 6
  • 14

1 Answers1

0

That does look weird. However, there are 2 things that caught my attention:

  1. You use setDefaultHighRepJobPolicyUnappliedJobPercentage(100). Was that intentional? Maybe set that to 0? Probably not it though.

  2. In the Google JDO examples they seem to close the pm all the time. Try doing that (especially in testCreateThing() ):

    finally { pm.close(); }

Christiaan
  • 2,637
  • 21
  • 26