You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pp...@apache.org on 2008/08/08 08:47:15 UTC

svn commit: r683878 - in /openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence: exception/ jdbc/unique/ jdbc/update/ test/

Author: ppoddar
Date: Thu Aug  7 23:47:14 2008
New Revision: 683878

URL: http://svn.apache.org/viewvc?rev=683878&view=rev
Log:
Assorted changes in test utilities
  1. Added a new Test class CombinatorialPersistenceTestCase to run test with auto-generated configurations as combination of possible values of many configuration properties
  2. Added a Test for Parent-Child insertion under different foreign key constraint + update startaegies scenarios as an exemplar of this new utility
  3. Moved exception handling routines to base Persistence TestCase

Added:
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Child.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Parent.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/TestParentChild.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinationGenerator.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialPersistenceTestCase.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialTestHelper.java
Modified:
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/exception/TestException.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraint.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraintWithXMLDescriptor.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/exception/TestException.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/exception/TestException.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/exception/TestException.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/exception/TestException.java Thu Aug  7 23:47:14 2008
@@ -163,7 +163,7 @@
 	 * Otherwise fails assertion and prints the given throwable and its nested
 	 * exception on the console. 
 	 */
-	void assertException(Throwable t, Class expectedType) {
+	public void assertException(Throwable t, Class expectedType) {
 		if (!isExpectedException(t, expectedType)) {
 			t.printStackTrace();
 			print(t, 0);

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraint.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraint.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraint.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraint.java Thu Aug  7 23:47:14 2008
@@ -53,27 +53,23 @@
 		List<String> sqls = super.sql;
 		
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_A",
-				"UNIQUE \\w*\\(a1, a2\\)", 
-				"UNIQUE \\w*\\(a3, a4\\)");
+				"UNIQUE .*\\(a1, a2\\)", 
+				"UNIQUE .*\\(a3, a4\\).*");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_B",
-				"UNIQUE \\w*\\(b1, b2\\)");
+				"UNIQUE .*\\(b1, b2\\).*");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_SECONDARY",
-				"UNIQUE \\w*\\(sa1\\)");
+				"UNIQUE .*\\(sa1\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_GENERATOR",
-				"UNIQUE \\w*\\(GEN1, GEN2\\)");
+				"UNIQUE .*\\(GEN1, GEN2\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_JOINTABLE",
-				"UNIQUE \\w*\\(FK_A, FK_B\\)");
+				"UNIQUE .*\\(FK_A, FK_B\\)");
 	}
 	
 	void assertSQLFragnments(List<String> list, String... keys) {
 		if (SQLSniffer.matches(list, keys))
 			return;
-		int i = 0;
-		for (String sql : list) {
-			i++;
-			System.out.println("" + i + ":" + sql);
-		}
-		fail("None of the " + sql.size() + " SQL contains all keys "
-				+ Arrays.toString(keys));
+		fail("None of the following " + sql.size() + " SQL \r\n" + 
+				toString(sql) + "\r\n contains all keys \r\n"
+				+ toString(Arrays.asList(keys)));
 	}
 }

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraintWithXMLDescriptor.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraintWithXMLDescriptor.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraintWithXMLDescriptor.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/unique/TestUniqueConstraintWithXMLDescriptor.java Thu Aug  7 23:47:14 2008
@@ -60,27 +60,23 @@
 		// Following verification techniques is fragile as databases DDL
 		// syntax vary greatly on UNIQUE CONSTRAINT
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_A_XML",
-				"UNIQUE \\w*\\(a1x, a2x\\)", 
-				"UNIQUE \\w*\\(a3x, a4x\\)");
+				"UNIQUE .*\\(a1x, a2x\\)", 
+				"UNIQUE .*\\(a3x, a4x\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_B_XML",
-				"UNIQUE \\w*\\(b1x, b2x\\)");
+				"UNIQUE .*\\(b1x, b2x\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_SECONDARY_XML",
-				"UNIQUE \\w*\\(sa1x\\)");
+				"UNIQUE .*\\(sa1x\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_GENERATOR_XML",
-				"UNIQUE \\w*\\(GEN1_XML, GEN2_XML\\)");
+				"UNIQUE .*\\(GEN1_XML, GEN2_XML\\)");
 		assertSQLFragnments(sqls, "CREATE TABLE UNIQUE_JOINTABLE_XML",
-				"UNIQUE \\w*\\(FK_A_XML, FK_B_XML\\)");
+				"UNIQUE .*\\(FK_A_XML, FK_B_XML\\)");
 	}
 
 	void assertSQLFragnments(List<String> list, String... keys) {
 		if (SQLSniffer.matches(list, keys))
 			return;
-		int i = 0;
-		for (String sql : list) {
-			i++;
-			System.out.println("" + i + ":" + sql);
-		}
-		fail("None of the " + sql.size() + " SQL contains all keys "
-				+ Arrays.toString(keys));
+		fail("None of the following " + sql.size() + " SQL \r\n" + 
+				toString(sql) + "\r\n contains all keys \r\n"
+				+ toString(Arrays.asList(keys)));
 	}
 }

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Child.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Child.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Child.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Child.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.update;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+
+/**
+ * Child in a bidirectional parent-child relationship.
+ * 
+ * Notes:
+ * a) there is no mutator for id because it is generated by JPA provider.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+@Entity
+public class Child {
+	@Id
+	@GeneratedValue
+	private String id;
+	
+	private String name;
+
+	@ManyToOne(fetch=FetchType.LAZY)
+	private Parent parent;
+
+	/**
+	 * Restrict access to constructor for Parent to create the Child.
+	 */
+	public Child() {
+		
+	}
+	
+	public String getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String city) {
+		this.name = city;
+	}
+
+	public Parent getParent() {
+		return parent;
+	}
+
+	void setParent(Parent owner) {
+		this.parent = owner;
+	}
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Parent.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Parent.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Parent.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/Parent.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.update;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+
+/**
+ * Parent in a bidirectional parent-child relationship.
+ * 
+ * Note:
+ * a) there is no mutator for id because it is generated by JPA provider.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+@Entity
+public class Parent {
+	@Id
+	@GeneratedValue
+	private long id;
+	
+	private String name;
+	
+	/**
+	 * This field is mapped by the child. The child's table will hold a foreign
+	 * key linking to the primary key of this Parent's table. In JPA terminology, 
+	 * that makes the Child the owner of this bi-directional relationship.
+	 */
+	@OneToMany(mappedBy="parent", cascade = CascadeType.ALL)
+	private Collection<Child> children;
+
+	public long getId() {
+		return id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Collection<Child> getChildren() {
+		return children;
+	}
+
+	/**
+	 * Creates and adds a child to this receiver. Creating child via the parent
+	 * is the preferred pattern to ensure referential integrity of domain model.
+	 */
+	public Child newChild(String name) {
+		Child child = new Child();
+		child.setName(name);
+		child.setParent(this);
+		if (children == null)
+			children = new ArrayList<Child>();
+		children.add(child);
+		return child;
+	}
+	
+	public boolean removeChild(Child child) {
+		return children != null && children.remove(child);
+	}
+	
+	/**
+	 * Unsafe way of adding a child. Does not warranty referential integrity.
+	 * The caller has to ensure bi-directionality of parent-child relation is 
+	 * consistent.
+	 */
+	public void add(Child child) {
+		if (children == null)
+			children = new ArrayList<Child>();
+		children.add(child);
+	}
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/TestParentChild.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/TestParentChild.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/TestParentChild.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jdbc/update/TestParentChild.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.jdbc.update;
+
+import javax.persistence.EntityManager;
+
+import org.apache.openjpa.jdbc.meta.FieldMapping;
+import org.apache.openjpa.jdbc.schema.ForeignKey;
+import org.apache.openjpa.meta.ClassMetaData;
+import org.apache.openjpa.meta.MetaDataRepository;
+import org.apache.openjpa.persistence.test.CombinatorialPersistenceTestCase;
+
+/**
+ * Tests for SQL statement ordering capabilities of different update strategies
+ * for a Parent-Child model against different physical database constraints.
+ * 
+ * SQL statement ordering is influenced by 
+ * 1. In-memory schema model: The in-memory schema model can be aware of logical 
+ *    or physical foreign keys. 
+ *    a) This is configured by <code>jdbc.SchemaFactory</code> property setting 
+ *       to <code>native(ForeignKeys=true|false)</code> which makes OpenJPA to 
+ *       read physical foreign key information from database 
+ *    b) @ForeignKey annotation on the relation -- OpenJPA then considers 
+ *       logical foreign key 
+ *       
+ *  2. Physical Schema: The database schema can be defined with physical foreign 
+ *     keys. This is configured by <code>jdbc.MappingDefaults</code> property 
+ *     setting to <code>ForeignKeyDeleteAction</code> 
+ *     
+ *  3. Update Strategy: the update manager is configured by 
+ *     <code>jdbc.UpdateManager</code> 
+ *     
+ *  4. Order of persistence operation: The order in which the application calls
+ *     persistence operations such as persist() or remove(). In this test, we
+ *     control this by PersistOrder enum. This application order is maintained
+ *     if the update manager is set to <code>'operation-order'</code>.
+ *     
+ * 
+ *   This test case also demonstrates how to write a test case that runs with
+ *   multiple combination of configurations. Each configuration discussed above
+ *   has multiple possible values and testing all possible combination can be 
+ *   an arduous task. The {@link CombinatorialPersistenceTestCase combinatorial}
+ *   test case utility helps to auto-generate these multiple configurations and
+ *   execute the same test with all the combinations.
+ *   
+ * @author Pinaki Poddar
+ * 
+ */
+public class TestParentChild extends CombinatorialPersistenceTestCase {
+	// Each of these property keys can take multiple possible values 
+	private static String Key_UpdateManager = "openjpa.jdbc.UpdateManager";
+	private static String Key_SchemaFactory = "openjpa.jdbc.SchemaFactory";
+	private static String Key_MappingDefaults = "openjpa.jdbc.MappingDefaults";
+	private static String Key_PersistOrder = "persist-order";
+
+	private static String[] Option_MappingDefaults = {
+		"ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict",
+		"ForeignKeyDeleteAction=none, JoinForeignKeyDeleteAction=none" };
+	
+	private static String[] Option_SchemaFactory = {
+		"native(ForeignKeys=false)", 
+		"native(ForeignKeys=true)" };
+
+	private static String[] Option_UpdateManager = { 
+		"operation-order",
+		"constraint" };
+	
+	private static enum PersistOrder {
+		IMPLICIT_CASCADE, 
+		CHILD_THEN_PARENT, 
+		PARENT_THEN_CHILD
+	};
+
+
+	// The options are added in a static block, so that we can count on
+	// total number of combinations before the test is set up.
+	static {
+		getHelper().addOption(Key_MappingDefaults, Option_MappingDefaults);
+		getHelper().addOption(Key_SchemaFactory, Option_SchemaFactory);
+		getHelper().addOption(Key_UpdateManager, Option_UpdateManager);
+
+		// The last argument tells that this is a runtime option. So the
+		// values are included to generate combinations but are excluded
+		// from generating OpenJPA configuration.
+		getHelper().addOption("persist-order", PersistOrder.values(), true);
+	}
+
+	public void setUp() {
+		// The options can also be added in setup() as well but then 
+		// coutTestCase() will only record test methods and not multiply them 
+		// with number of configuration combinations the same tests will run.
+		getHelper().addOption(Key_MappingDefaults, Option_MappingDefaults);
+		getHelper().addOption(Key_SchemaFactory, Option_SchemaFactory);
+		getHelper().addOption(Key_UpdateManager, Option_UpdateManager);
+
+		getHelper().addOption("persist-order", PersistOrder.values(), true);
+		
+		sql.clear();
+		super.setUp(DROP_TABLES, Parent.class, Child.class);
+	}
+
+	/**
+	 * This test will run in 2*2*2*3 = 24 times with different configurations.
+	 */
+	public void testInsert() {
+		Parent parent = createData(getPersistOrder(), 3);
+		validateData(parent.getId(), 3);
+
+		// verification can be challenging under multiple configuration options
+		// see the methods as exemplars how verification can vary based on
+		// configuration.
+		assertLogicalOrPhysicalForeignKey();
+		assertPostInsertUpdate();
+		assertPhysicalForeignKeyCreation();
+	}
+
+	Parent createData(PersistOrder order, int nChild) {
+		EntityManager em = emf.createEntityManager();
+		em.getTransaction().begin();
+
+		Parent parent = new Parent();
+		parent.setName("parent");
+		for (int i = 1; i <= nChild; i++)
+			parent.newChild("Child-" + i);
+		switch (order) {
+		case IMPLICIT_CASCADE:
+			em.persist(parent);
+			break;
+		case CHILD_THEN_PARENT:
+			for (Child child : parent.getChildren()) {
+				em.persist(child);
+			}
+			em.persist(parent);
+			break;
+		case PARENT_THEN_CHILD:
+			em.persist(parent);
+			for (Child child : parent.getChildren()) {
+				em.persist(child);
+			}
+			break;
+		default:
+			throw new RuntimeException("Bad order " + order);
+		}
+		em.getTransaction().commit();
+		em.clear();
+		return parent;
+	}
+
+	void validateData(Object pid, int childCount) {
+		EntityManager em = emf.createEntityManager();
+		em.getTransaction().begin();
+		Parent parent = em.find(Parent.class, pid);
+		assertNotNull(parent);
+		assertEquals(childCount, parent.getChildren().size());
+		em.getTransaction().rollback();
+	}
+	
+	/**
+	 * Asserts that foreign key constraint will be defined on the database
+	 * for certain combinations of configurations.
+	 */
+	void assertPhysicalForeignKeyCreation() {
+		String regex = "ALTER TABLE .* ADD FOREIGN KEY \\(PARENT_ID\\) " 
+		             + "REFERENCES Parent \\(id\\)";
+		if (getMappingDefaults().contains("restrict")) {
+			assertSQL(regex);
+		} else {
+			assertNotSQL(regex);
+		}
+	}
+
+	/**
+	 * Asserts that update SQL will be issued to set the foreign key value
+	 * after the insert under some configuration.
+	 */
+	void assertPostInsertUpdate() {
+		if (getPersistOrder().equals(PersistOrder.CHILD_THEN_PARENT)
+		 && getMappingDefaults().contains("restrict")) {
+			assertSQL("UPDATE .* SET PARENT_ID .* WHERE .*");
+		}
+	}
+	
+	/**
+	 * Asserts that foreign key will be logical or physical under different 
+	 * combination of configuration.
+	 */
+	void assertLogicalOrPhysicalForeignKey() {
+		ForeignKey fk = getChildParentForeignKey();
+		boolean physicalKeyExists = getMappingDefaults().contains("restrict");
+		boolean keyRead = getSchemaFactory().contains("ForeignKeys=true");
+		if (physicalKeyExists && keyRead)
+			assertFalse(fk.isLogical());
+		else if (keyRead)
+			assertTrue(fk.isLogical());
+	}
+	
+	ForeignKey getChildParentForeignKey() {
+		MetaDataRepository repos = emf.getConfiguration()
+				.getMetaDataRepositoryInstance();
+		ClassMetaData child = repos.getCachedMetaData(Child.class);
+		FieldMapping parent = (FieldMapping) child.getField("parent");
+		return parent.getForeignKey();
+	}
+
+	PersistOrder getPersistOrder() {
+		return (PersistOrder) getHelper().getOption(Key_PersistOrder);
+	}
+
+	String getMappingDefaults() {
+		return getHelper().getOptionAsString(Key_MappingDefaults);
+	}
+
+	String getSchemaFactory() {
+		return getHelper().getOptionAsString(Key_SchemaFactory);
+	}
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinationGenerator.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinationGenerator.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinationGenerator.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinationGenerator.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Generates combinations given multiple choices in each dimension.
+ * 
+ * Usage:
+ * <code>
+ *    CombinationGenerator combo = new CombinationGenerator();
+ *    combo.addDimension(new String[]{"A","B","C"});
+ *    combo.addDimension(new int[]{1,2});
+ *    List[] combos = combo.generate();
+ * </code>   
+ * will generate 3*2=6 combinations
+ * <code>
+ *    combos[0] => List("A",1);
+ *    combos[1] => List("B",1);
+ *    combos[2] => List("C",1);
+ *    combos[3] => List("A",2);
+ *    combos[4] => List("B",2);
+ *    combos[5] => List("C",2);
+ * </code>
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+public class CombinationGenerator {
+	private List<List> dimensions = new ArrayList();
+	
+	/**
+	 * Adds a dimension. null or empty argument has no effect.
+	 */
+	public void addDimension(List dim) {
+		if (dim == null || dim.isEmpty())
+			return;
+		dimensions.add(dim);
+		
+	}
+	
+	/**
+	 * Adds a dimension. null or empty argument has no effect.
+	 */
+	public void addDimension(Object[] dim) {
+		if (dim == null || dim.length == 0)
+			return;
+		dimensions.add(Arrays.asList(dim));
+	}
+	
+	/**
+	 * Generates all combinations.
+	 * Each array element is a list which has elements in the same order as 
+	 * the dimensions were added.
+	 */
+	public List[] generate() {
+		int n = getSize();
+		List[] result = new ArrayList[n];
+		for (int i = 0; i < n; i++) {
+			ArrayList elem = new ArrayList(dimensions.size());
+			for (int j=0; j < dimensions.size(); j++)
+				elem.add(null);
+			result[i] = elem;
+		}
+		
+		for (int i = 0; i < dimensions.size(); i++) {
+			fill(i, dimensions.get(i), result);
+		}
+		return result;
+	}
+	
+	private void fill(int where, List elements, List[] fill) {
+		if (fill.length%elements.size() != 0)
+			throw new RuntimeException();
+		int n = fill.length/elements.size();
+		int k = 0;
+		for (int i = 0; i < n; i++) {
+			for (Object e : elements)	
+				fill[k++].set(where, e);
+		}
+	}
+	
+	/**
+	 * Gets the total number of combinations generated. The total number is 
+	 * the product of cardinality of each dimension. 
+	 * 
+	 */
+	public int getSize() {
+		int size = 1;
+		for (List d : dimensions) 
+			size *= d.size();
+		return size;
+	}
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialPersistenceTestCase.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialPersistenceTestCase.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialPersistenceTestCase.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialPersistenceTestCase.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+
+package org.apache.openjpa.persistence.test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.AssertionFailedError;
+
+
+/**
+ * Adds ability to run tests under combinations of options.
+ * 
+ * @author Pinaki Poddar
+ *
+ */
+public abstract class CombinatorialPersistenceTestCase 
+	extends SQLListenerTestCase {
+	
+	protected static CombinatorialTestHelper helper;
+	
+	public void setUp(Object...props) {
+		super.setUp(getHelper().setCombinatorialOption(props));
+	}
+	
+	@Override
+	public int countTestCases() {
+		return super.countTestCases() * getHelper().getCombinationSize();
+	}
+	   
+    @Override
+    public void runBare() throws Throwable {
+    	Map<String, Throwable> errors = new HashMap<String, Throwable>();
+    	Map<String, AssertionFailedError> failures = new HashMap<String, AssertionFailedError>();
+    	do  {
+    		try {
+    			super.runBare();
+    		} catch (Throwable t) {
+    			if (t instanceof AssertionFailedError) {
+    				failures.put(getHelper().getOptionsAsString(), (AssertionFailedError)t);
+    				testResult.addFailure(this, (AssertionFailedError)t);
+    			} else {
+    				errors.put(getHelper().getOptionsAsString(), t);
+    				testResult.addError(this, t);
+    			}
+    		}
+    	} while (getHelper().hasMoreCombination());
+    	
+    	if (testResult.errorCount() + testResult.failureCount() > 0) {
+    		if (!failures.isEmpty())
+    			System.err.println(failures.size() + " assertion failures");
+    		for (String o : failures.keySet()) {
+    			System.err.println("Combination:\r\n" + o);
+    			failures.get(o).printStackTrace();
+    		}
+    		if (!errors.isEmpty())
+    			System.err.println(errors.size() + " errors");
+    		for (String o : errors.keySet()) {
+    			System.err.println("Combination:\r\n" + o);
+    			errors.get(o).printStackTrace();
+    		}
+    		throw new Throwable(getName() + ": " 
+    		  +	getHelper().getCombinationSize() + " combinations, "  
+    		  +	errors.size() + " errors, " + failures.size() + " failures\r\n"
+    		  + "Stack trace for each error/failure is printed on console");
+    	}
+    }
+    
+    
+   
+    public static CombinatorialTestHelper getHelper() {
+		if (helper == null)
+			helper = new CombinatorialTestHelper();
+		return helper;
+    }
+    
+    public void assertSQL(String sqlExp) {
+    	try {
+    		super.assertSQL(sqlExp);
+    	} catch (AssertionFailedError e) {
+    		String newMessage = "Combination\r\n" + getHelper().getOptionsAsString()
+    		   + " failed \r\n " + e.getMessage();
+    		fail(newMessage);
+    	}
+    }
+
+}

Added: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialTestHelper.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialTestHelper.java?rev=683878&view=auto
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialTestHelper.java (added)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/CombinatorialTestHelper.java Thu Aug  7 23:47:14 2008
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.persistence.test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+import org.apache.openjpa.persistence.jdbc.update.TestParentChild;
+
+/**
+ * Aids to run a single test under different combination of configuration
+ * parameters.
+ * 
+ * Each configurable property can be registered to this receiver with all its
+ * possible values. This class generates all combinations of all the possible
+ * property values as configuration and invokes the same test with each 
+ * configuration combination.
+ * The properties can be designated as <em>runtime</code> to be included in
+ * combination for execution but excluded from configuration.
+ * 
+ *  For example,
+ *  @see TestParentChild
+ *  
+ * @author Pinaki Poddar
+ *
+ */
+public class CombinatorialTestHelper {
+	private CombinationGenerator geneartor;
+	private List<String> propertyKeys;
+	private List currentOption;
+	private BitSet runtimeKeys = new BitSet();
+	
+	private List[] combos;
+	private int cursor;
+	
+	public CombinatorialTestHelper() {
+		geneartor = new CombinationGenerator();
+		propertyKeys = new ArrayList<String>();
+		currentOption = null;
+		runtimeKeys = new BitSet();
+		combos = null;
+		cursor = 0;
+	}
+	
+	/**
+	 * Generates the key-value property array as expected by its superclass
+	 * by appending the current combinatorially generated properties.
+	 * 
+	 * The important side effect of this method is to set the current 
+	 * configuration options.
+	 * 
+	 * If no property is configured for combinatorial generation then returns
+	 * the given list as it is.
+	 * 
+	 */
+	Object[] setCombinatorialOption(Object[] props) {
+		if (propertyKeys.isEmpty() || 
+			propertyKeys.size() == runtimeKeys.cardinality())
+			return props;
+		
+		if (combos == null) {
+			combos = geneartor.generate();
+			cursor = 0;
+		}
+		// Each non-runtime property contributes a key-value pair
+		Object[] options = new Object[2*(propertyKeys.size()- 
+				runtimeKeys.cardinality())];
+		currentOption = combos[cursor++];
+		int k = 0;
+		for (int i = 0; i < propertyKeys.size(); i++) {
+			if (runtimeKeys.get(i))
+				continue;
+			options[k++] = propertyKeys.get(i);
+			options[k++] = currentOption.get(i);
+		}
+		if (props == null || props.length == 0)
+			return options;
+		
+		Object[] newProps = new Object[props.length + options.length];
+		System.arraycopy(props, 0, newProps, 0, props.length);
+		System.arraycopy(options, 0, newProps, props.length, options.length);
+		return newProps;
+	}
+	
+	/**
+	 * Adds options for the given configuration property.
+	 */
+	public void addOption(String property, Object[] options) {
+		addOption(property, options, false);
+	}
+	
+	/**
+	 * Adds options for the given configuration property.
+	 */
+	public void addOption(String property, List options) {
+		addOption(property, options, false);
+	}
+	
+	/**
+	 * Adds options for the given property.
+	 * If runtime is true then this property is not added to configuration.
+	 */
+	public void addOption(String property, Object[] options, boolean runtime) {
+		addOption(property, Arrays.asList(options), runtime);
+	}
+
+	/**
+	 * Adds options for the given property.
+	 * If runtime is true then this property is not added to configuration.
+	 */
+	public void addOption(String property, List options, boolean runtime) {
+		if (geneartor == null) {
+			geneartor = new CombinationGenerator();
+		}
+		if (propertyKeys == null) {
+			propertyKeys = new ArrayList<String>();
+		}
+		if (!propertyKeys.contains(property)) {
+			geneartor.addDimension(options);
+			propertyKeys.add(property);
+			if (runtime) runtimeKeys.set(propertyKeys.size()-1);
+		}
+	}
+
+	/**
+	 * Gets the value of current option for the given key.
+	 * Raises exception if the given key is not an option.
+	 */
+	public Object getOption(String key) {
+		int index = propertyKeys.indexOf(key);
+		if (index == -1)
+			throw new IllegalArgumentException("Unknown option " + key);
+		return currentOption.get(index);
+	}
+	
+	/**
+	 * Gets the string value of current option for the given key.
+	 * Raises exception if the given key is not an option.
+	 */
+	public String getOptionAsString(String key) {
+		return getOption(key).toString();
+	}
+
+	/**
+	 * Gets the values of the current options.
+	 */
+	public List getOptions() {
+		return currentOption;
+	}
+	
+	/**
+	 * Gets the key and values of the current options as printable string.
+	 */
+	public String getOptionsAsString() {
+		StringBuffer buf = new StringBuffer();
+		for (int i = 0; i <propertyKeys.size(); i++) {
+			String key = propertyKeys.get(i);
+			if (!runtimeKeys.get(i))
+				buf.append(key +  " : " + getOption(key)).append("\r\n");
+		}
+		for (int i = 0; i <propertyKeys.size(); i++) {
+			String key = propertyKeys.get(i);
+			if (runtimeKeys.get(i))
+				buf.append("* " + key +  " : " + getOption(key)).append("\r\n");
+		}
+		return buf.toString();
+	}
+	
+	/**
+	 * Affirms if this receiver has more combinations.
+	 */
+	public boolean hasMoreCombination() {
+		return cursor < combos.length-1;
+	}
+	
+	/**
+	 * Gets total number of combinations.
+	 */
+	public int getCombinationSize() {
+		return geneartor == null ? 0 : geneartor.getSize();
+	}
+}

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/PersistenceTestCase.java Thu Aug  7 23:47:14 2008
@@ -19,6 +19,7 @@
 package org.apache.openjpa.persistence.test;
 
 import java.lang.reflect.Modifier;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -296,4 +297,87 @@
         else if (o1.equals(o2))
             fail("expected args to be different; compared equal.");
     }
+    
+    // ================================================ 
+    // Utility methods for exception handling
+    // ================================================
+    /**
+	 * Asserts that the given targetType is assignable from given actual 
+	 * Throwable.
+	 */
+    protected void assertException(final Throwable actual, Class targetType) {
+		assertException(actual, targetType, null);
+	}
+	
+	/**
+	 * Asserts that the given targetType is assignable from given actual 
+	 * Throwable. Asserts that the nestedType is nested (possibly recursively) 
+	 * within the given actual Throwable.
+	 * 
+	 * @param actual is the actual throwable to be tested
+	 * @param targetType is expected type or super type of actual. If null, then
+	 * the check is omitted.
+	 * @param nestedTargetType is expected type of exception nested within
+	 * actual. If null this search is omitted. 
+	 * 
+	 */
+    protected void assertException(final Throwable actual, Class targetType,
+			Class nestedTargetType) {
+		assertNotNull(actual);
+		Class actualType = actual.getClass();
+		if (targetType != null && !targetType.isAssignableFrom(actualType)) {
+			actual.printStackTrace();
+			fail(targetType.getName() + " is not assignable from "
+					+ actualType.getName());
+		}
+
+		if (nestedTargetType != null) {
+			Throwable nested = actual.getCause();
+			Class nestedActualType = (nested == null) ? null : nested.getClass();
+			while (nestedActualType != null) {
+				if (nestedTargetType.isAssignableFrom(nestedActualType)) {
+					return;
+				} else {
+					Throwable next = nested.getCause();
+					if (next == null || next == nested)
+						break;
+					nestedActualType = next.getClass();
+					nested     = next;
+				}
+			}
+			actual.printStackTrace();
+			fail("No nested type " + nestedTargetType + " in " + actual);
+		}
+	}
+
+	/**
+	 * Assert that each of given keys are present in the message of the given
+	 * Throwable.
+	 */
+    protected void assertMessage(Throwable actual, String... keys) {
+		if (actual == null || keys == null)
+			return;
+		String message = actual.getMessage();
+		for (String key : keys) {
+			assertTrue(key + " is not in " + message, message.contains(key));
+		}
+	}
+    
+    public void printException(Throwable t) {
+    	printException(t, 2);
+    }
+    
+    public void printException(Throwable t, int tab) {
+		if (t == null) return;
+		for (int i=0; i<tab*4;i++) System.out.print(" ");
+		String sqlState = (t instanceof SQLException) ? 
+			"(SQLState=" + ((SQLException)t).getSQLState() + ":" 
+				+ t.getMessage() + ")" : "";
+		System.out.println(t.getClass().getName() + sqlState);
+		if (t.getCause() == t) 
+			return;
+		printException(t.getCause(), tab+2);
+	}
+
+
 }

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SQLListenerTestCase.java Thu Aug  7 23:47:14 2008
@@ -18,6 +18,7 @@
  */
 package org.apache.openjpa.persistence.test;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Map;
@@ -57,10 +58,10 @@
                 return;
         }
 
-        fail("Expected regular expression <" + sqlExp + "> to have"
-            + " existed in SQL statements: " + sql);
+        fail("Expected regular expression\r\n <" + sqlExp 
+           + ">\r\n to have existed in SQL statements: \r\n" + toString(sql));
     }
-
+    
     /**
      * Confirm that the specified SQL has not been executed.
      *
@@ -75,8 +76,8 @@
         }
 
         if (failed)
-            fail("Regular expression <" + sqlExp + ">"
-                + " should not have been executed in SQL statements: " + sql);
+            fail("Regular expression\r\n <" + sqlExp + ">\r\n should not have"
+                    + " been executed in SQL statements: \r\n" + toString(sql));
     }
 
     /**
@@ -90,8 +91,8 @@
                 return;
         }
 
-        fail("Expected regular expression <" + sqlExp + "> to be"
-            + " contained in SQL statements: " + sql);
+        fail("Expected regular expression\r\n <" + sqlExp + ">\r\n to be"
+                + " contained in SQL statements: \r\n" + toString(sql));
     }
     
     /**
@@ -111,6 +112,13 @@
     	return tmp;
     }
 
+    public String toString(List<String> list) {
+    	StringBuffer buf = new StringBuffer();
+    	for (String s : list)
+    		buf.append(s).append("\r\n");
+    	return buf.toString();
+    }
+
     public class Listener
         extends AbstractJDBCListener {
 

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java?rev=683878&r1=683877&r2=683878&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java (original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/test/SingleEMFTestCase.java Thu Aug  7 23:47:14 2008
@@ -18,6 +18,10 @@
  */
 package org.apache.openjpa.persistence.test;
 
+import java.util.List;
+
+import javax.persistence.EntityManager;
+
 import org.apache.openjpa.jdbc.meta.ClassMapping;
 import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
 
@@ -123,4 +127,25 @@
     public int count(Class c) {
     	return count(c.getSimpleName());
     }
+    
+    /**
+     * Get all the instances of given type.
+     * The returned instances are obtained without a persistence context. 
+     */
+    public <T> List<T> getAll(Class<T> t) {
+    	String alias = t.getSimpleName();
+    	return (List<T>)emf.createEntityManager()
+				   .createQuery("SELECT p FROM " + alias + " p")
+				   .getResultList();
+    }
+    
+    /**
+     * Get all the instances of given type.
+     * The returned instances are obtained within the given persistence context. 
+     */
+    public <T> List<T> getAll(EntityManager em, Class<T> t) {
+    	String alias = t.getSimpleName();
+    	return (List<T>)em.createQuery("SELECT p FROM " + alias + " p")
+				   .getResultList();
+    }
 }