blob: 316259eeb405f47ad6629851517cacea119e440f [file] [log] [blame]
/*
* 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
<<<<<<< Updated upstream
*
* 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
=======
*
* https://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
>>>>>>> Stashed changes
* limitations under the License.
*/
package org.apache.jdo.tck;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.jdo.Constants;
import javax.jdo.Extent;
import javax.jdo.JDOException;
import javax.jdo.JDOFatalException;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.JDOHelper;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.LegacyJava;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class JDO_Test extends TestCase {
public static final int TRANSIENT = 0;
public static final int PERSISTENT_NEW = 1;
public static final int PERSISTENT_CLEAN = 2;
public static final int PERSISTENT_DIRTY = 3;
public static final int HOLLOW = 4;
public static final int TRANSIENT_CLEAN = 5;
public static final int TRANSIENT_DIRTY = 6;
public static final int PERSISTENT_NEW_DELETED = 7;
public static final int PERSISTENT_DELETED = 8;
public static final int PERSISTENT_NONTRANSACTIONAL = 9;
public static final int PERSISTENT_NONTRANSACTIONAL_DIRTY = 10;
public static final int DETACHED_CLEAN = 11;
public static final int DETACHED_DIRTY = 12;
public static final int NUM_STATES = 13;
public static final int ILLEGAL_STATE = 13;
protected static final String[] states = {
"transient",
"persistent-new",
"persistent-clean",
"persistent-dirty",
"hollow",
"transient-clean",
"transient-dirty",
"persistent-new-deleted",
"persistent-deleted",
"persistent-nontransactional",
"persistent-nontransactional-dirty",
"detached-clean",
"detached-dirty",
"illegal"
};
private static final int IS_PERSISTENT = 0;
private static final int IS_TRANSACTIONAL = 1;
private static final int IS_DIRTY = 2;
private static final int IS_NEW = 3;
private static final int IS_DELETED = 4;
private static final int IS_DETACHED = 5;
private static final int NUM_STATUSES = 6;
/*
* This table indicates the values returned by the status interrogation
* methods for each state. This is used to determine the current lifecycle
* state of an object.
*/
private static final boolean[][] state_statuses = {
// IS_PERSISTENT IS_TRANSACTIONAL IS_DIRTY IS_NEW IS_DELETED IS_DETACHED
// transient
{false, false, false, false, false, false},
// persistent-new
{true, true, true, true, false, false},
// persistent-clean
{true, true, false, false, false, false},
// persistent-dirty
{true, true, true, false, false, false},
// hollow
{true, false, false, false, false, false},
// transient-clean
{false, true, false, false, false, false},
// transient-dirty
{false, true, true, false, false, false},
// persistent-new-deleted
{true, true, true, true, true, false},
// persistent-deleted
{true, true, true, false, true, false},
// persistent-nontransactional
{true, false, false, false, false, false},
// persistent-nontransactional-dirty
{true, false, true, false, false, false},
// detached_clean
{false, false, false, false, false, true},
// detached_dirty
{false, false, true, false, false, true}
};
/** Name of the PersistenceManagerFactoryClass PMF property. */
public static final String PMF_CLASS_PROP = "javax.jdo.PersistenceManagerFactoryClass";
/** Name of the ConnectionURL PMF property. */
public static final String CONNECTION_URL_PROP = "javax.jdo.option.ConnectionURL";
/** Name of the ConnectionUserName PMF property. */
public static final String CONNECTION_USERNAME_PROP = "javax.jdo.option.ConnectionUserName";
/** Name of the ConnectionPassword PMF property. */
public static final String CONNECTION_PASSWORD_PROP = "javax.jdo.option.ConnectionPassword";
/** identitytype value for applicationidentity. */
public static final String APPLICATION_IDENTITY = "applicationidentity";
/** identitytype value for datastoreidentity. */
public static final String DATASTORE_IDENTITY = "datastoreidentity";
/** Map of transaction isolation String values to Integer */
protected static final Map<String, Integer> levelValues = new HashMap<>();
static {
levelValues.put(Constants.TX_READ_UNCOMMITTED, 0);
levelValues.put(Constants.TX_READ_COMMITTED, 1);
levelValues.put(Constants.TX_REPEATABLE_READ, 2);
levelValues.put(Constants.TX_SNAPSHOT, 3);
levelValues.put(Constants.TX_SERIALIZABLE, 4);
}
/**
* String indicating the type of identity used for the current test case. The value is either
* "applicationidentity" or "datastoreidentity".
*/
protected static final String IDENTITYTYPE = System.getProperty("jdo.tck.identitytype");
/** String indicating the name of the schema for the current test. */
protected static final String SCHEMANAME = System.getProperty("jdo.tck.schemaname");
/** Name of the file containing the properties for the PMF. */
protected static String PMFProperties = System.getProperty("PMFProperties");
/**
* Flag indicating whether to clean up data after tests or not. If false then test will not clean
* up data from database. The default value is true.
*/
protected static final boolean CLEANUP_DATA =
System.getProperty("jdo.tck.cleanupaftertest", "true").equalsIgnoreCase("true");
/** Flag indicating whether to close the PMF after each test or not. It defaults to false. */
protected static final boolean CLOSE_PMF_AFTER_EACH_TEST =
System.getProperty("jdo.tck.closePMFAfterEachTest", "false").equalsIgnoreCase("true");
/** Flag indicating whether to skip JNDI related tests. */
protected static final boolean SKIP_JNDI =
System.getProperty("jdo.tck.skipJndi", "false").equalsIgnoreCase("true");
/** The Properties object for the PersistenceManagerFactory. */
protected static Properties PMFPropertiesObject;
/** The PersistenceManagerFactory. */
protected static PersistenceManagerFactory pmf;
/** The collection of supported options of the pmf. */
protected static Collection<String> supportedOptions;
/** The name of the pmf supported options summary file. */
private static final String PMF_SUPPORTED_OPTIONS_FILE_NAME = "pmf_supported_options.txt";
/** The PersistenceManager. */
protected PersistenceManager pm;
// Flag indicating successful test run
protected boolean testSucceeded;
/** Logger */
protected final Log logger = LogFactory.getFactory().getInstance("org.apache.jdo.tck");
/** true if debug logging in enabled. */
protected final boolean debug = logger.isDebugEnabled();
/**
* Indicates an exception thrown in method <code>tearDown</code>. At the end of method <code>
* tearDown</code> this field is nullified.
*/
private Throwable tearDownThrowable;
/**
* A list of registered oid instances. Corresponding pc instances are deleted in <code>
* localTearDown</code>.
*/
private final Collection<Object> tearDownInstances = new LinkedList<>();
/**
* A list of registered pc classes. The extents of these classes are deleted in <code>
* localTearDown</code>.
*/
private final Collection<Class<?>> tearDownClasses = new LinkedList<>();
/**
* Intended for subclasses so that they may skip this class's normal set up procedure.
*
* @return true to run normal set up, false to skip normal set up
*/
protected boolean preSetUp() {
return true;
}
@Override
protected final void setUp() {
if (!preSetUp()) {
return;
}
pmf = getPMF();
localSetUp();
}
/**
* Subclasses may override this method to allocate any data and resources that they need in order
* to successfully execute this testcase.
*/
protected void localSetUp() {}
/**
* Runs the bare test sequence.
*
* @exception Throwable if any exception is thrown
*/
@Override
public final void runBare() throws Throwable {
try {
testSucceeded = false;
setUp();
runTest();
testSucceeded = true;
} catch (Throwable e) {
if (logger.isInfoEnabled()) logger.info("Exception during setUp or runtest: ", e);
throw e;
} finally {
tearDown();
if (debug) {
logger.debug("Free memory: " + Runtime.getRuntime().freeMemory());
}
}
}
/**
* Sets field <code>tearDownThrowable</code> if it is <code>null</code>. Else, the given throwable
* is logged using fatal log level.
*
* @param throwable the throwable
*/
private void setTearDownThrowable(String context, Throwable throwable) {
if (logger.isInfoEnabled()) logger.info("Exception during " + context + ": ", throwable);
if (this.tearDownThrowable == null) {
this.tearDownThrowable = throwable;
}
}
/**
* Intended for subclasses so that they may skip this class's normal tear down procedure.
*
* @return true to run normal tear down, false to skip normal tear down
*/
protected boolean preTearDown() {
return true;
}
/**
* This method clears data and resources allocated by testcases. It first closes the persistence
* manager of this testcase. Then it calls method <code>localTearDown</code>. Subclasses may
* override that method to clear any data and resources that they have allocated in method <code>
* localSetUp</code>. Finally, this method closes the persistence manager factory.
*
* <p><b>Note:</b>These methods are called always, regardless of any exceptions. The first caught
* exception is kept in field <code>tearDownThrowable</code>. That exception is thrown as a nested
* exception of <code>JDOFatalException</code> if and only if the testcase executed successful.
* Otherwise that exception is logged using fatal log level. All other exceptions are logged using
* fatal log level, always.
*
* <p><b>Note:</b>By default, the method tearDown does not close the pmf. This is done at the end
* of each configuration, unless the property jdo.tck.closePMFAfterEachTest is set to true.
*/
@Override
protected final void tearDown() {
if (!preTearDown()) {
return;
}
try {
cleanupPM();
} catch (Throwable t) {
setTearDownThrowable("cleanupPM", t);
}
if ((pmf == null || pmf.isClosed())
&& (this.tearDownInstances.size() > 0 || this.tearDownClasses.size() > 0))
throw new JDOFatalException(
"PMF must not be nullified or closed when tear down instances and /or classes have been added.");
if (pmf != null && pmf.isClosed()) pmf = null;
try {
if (CLEANUP_DATA) {
localTearDown();
}
} catch (Throwable t) {
setTearDownThrowable("localTearDown", t);
}
if (CLOSE_PMF_AFTER_EACH_TEST) {
try {
closePMF();
} catch (Throwable t) {
setTearDownThrowable("closePMF", t);
}
}
if (this.tearDownThrowable != null) {
Throwable t = this.tearDownThrowable;
this.tearDownThrowable = null;
if (testSucceeded) {
// runTest succeeded, but this method threw exception => error
throw new JDOFatalException("Exception during tearDown", t);
}
}
}
/**
* Deletes all registered pc instances and extents of all registered pc classes. Subclasses may
* override this method to clear any data and resources that they have allocated in method <code>
* localSetUp</code>.
*/
protected void localTearDown() {
deleteTearDownInstances();
deleteTearDownClasses();
}
protected void addTearDownObjectId(Object oid) {
// ensure that oid is not a PC instance
if (JDOHelper.getObjectId(oid) != null || JDOHelper.isTransactional(oid))
throw new IllegalArgumentException("oid");
this.tearDownInstances.add(oid);
}
protected void addTearDownInstance(Object pc) {
Object oid = JDOHelper.getObjectId(pc);
addTearDownObjectId(oid);
}
protected void addTearDownClass(Class<?> pcClass) {
this.tearDownClasses.add(pcClass);
}
protected void addTearDownClass(Class<?>[] pcClasses) {
if (pcClasses == null) return;
for (Class<?> pcClass : pcClasses) {
addTearDownClass(pcClass);
}
}
/**
* Deletes and removes tear down instances. If there are no tear down instances, the this method
* is a noop. Otherwise, tear down instances are deleted exactly in the order they have been
* added. Tear down instances are deleted in a separate transaction.
*/
protected void deleteTearDownInstances() {
if (this.tearDownInstances.size() > 0) {
getPM();
try {
this.pm.currentTransaction().begin();
for (Iterator<Object> i = this.tearDownInstances.iterator(); i.hasNext(); ) {
Object pc;
try {
pc = this.pm.getObjectById(i.next(), true);
} catch (JDOObjectNotFoundException e) {
pc = null;
}
// we only delete those persistent instances
// which have not been deleted by tests already.
if (pc != null) {
this.pm.deletePersistent(pc);
}
}
this.pm.currentTransaction().commit();
} finally {
this.tearDownInstances.clear();
cleanupPM();
}
}
}
/**
* Deletes and removes tear down classes. If there are no tear down classes, the this method is a
* noop. Otherwise, tear down classes are deleted exactly in the order they have been added. Tear
* down classes are deleted in a separate transaction. Deleting a tear down class means to delete
* the extent.
*/
protected void deleteTearDownClasses() {
if (this.tearDownClasses.size() > 0) {
getPM();
try {
this.pm.currentTransaction().begin();
for (Class<?> tearDownClass : this.tearDownClasses) {
this.pm.deletePersistentAll(getAllObjects(this.pm, tearDownClass));
}
this.pm.currentTransaction().commit();
} finally {
this.tearDownClasses.clear();
cleanupPM();
}
}
}
/**
* Returns a collection of persistence instances of the specified class.
*
* @param pm the PersistenceManager
* @param pcClass the class object of the PersistenceCapabale class
* @return a Collection of persistence objects
*/
protected <T> Collection<T> getAllObjects(PersistenceManager pm, Class<T> pcClass) {
Query<T> query = pm.newQuery(pcClass);
Extent<T> candidates = null;
try {
candidates = pm.getExtent(pcClass, false);
} catch (JDOException ex) {
if (debug) logger.debug("Exception thrown for getExtent of class " + pcClass.getName());
return Collections.emptyList();
}
query.setCandidates(candidates);
return query.executeList();
}
/**
* Get the <code>PersistenceManagerFactory</code> instance for the implementation under test.
*
* @return field <code>pmf</code> if it is not <code>null</code>, else sets field <code>pmf</code>
* to a new instance and returns that instance.
*/
protected PersistenceManagerFactory getPMF() {
if (pmf == null) {
PMFPropertiesObject = loadProperties(PMFProperties); // will exit here if no properties
pmf = JDOHelper.getPersistenceManagerFactory(PMFPropertiesObject);
if (supportedOptions == null) {
supportedOptions = pmf.supportedOptions();
}
}
return pmf;
}
protected Class<?> getPMFClass() {
if (pmf != null) {
return pmf.getClass();
}
PMFPropertiesObject = loadProperties(PMFProperties);
String name = PMFPropertiesObject.getProperty(PMF_CLASS_PROP);
try {
return Class.forName(name);
} catch (ClassNotFoundException ex) {
throw new JDOException("Cannot find PMF class '" + name + "'.", ex);
}
}
/**
* Get the <code>PersistenceManagerFactory</code> instance for the implementation under test. This
* method does NOT use the JDOHelper method to retrieve the PMF, instead it creates an instance of
* the class specified as javax.jdo.PersistenceManagerFactoryClass property. The returned PMF is
* not configured.
*
* @return field <code>pmf</code> if it is not <code>null</code>, else sets field <code>pmf</code>
* to a new instance and returns that instance.
*/
protected PersistenceManagerFactory getUnconfiguredPMF() {
if (pmf == null) {
String name = null;
try {
Class<?> pmfClass = getPMFClass();
name = pmfClass.getName();
pmf = (PersistenceManagerFactory) pmfClass.getDeclaredConstructor().newInstance();
if (supportedOptions == null) {
supportedOptions = pmf.supportedOptions();
}
} catch (NoSuchMethodException ex) {
throw new JDOException("No no-args constructor of PMF class '" + name + "'.", ex);
} catch (InvocationTargetException ex) {
throw new JDOException("Exception thrown by constructor of PMF class '" + name + "'.", ex);
} catch (InstantiationException ex) {
throw new JDOException("Cannot instantiate PMF class '" + name + "'.", ex);
} catch (IllegalAccessException ex) {
throw new JDOException(
"Cannot access PMF class '" + name + "' or its no-arg constructor.", ex);
}
}
return pmf;
}
/**
* Get the <code>PersistenceManager</code> instance for the implementation under test.
*
* @return the PersistenceManager
*/
protected PersistenceManager getPM() {
if (pm == null) {
pm = getPMF().getPersistenceManager();
}
return pm;
}
/**
* This method cleans up the environment: closes the <code>PersistenceManager</code>. This should
* avoid leaving multiple PersistenceManager instances around, in case the
* PersistenceManagerFactory performs PersistenceManager pooling.
*/
protected void cleanupPM() {
cleanupPM(pm);
pm = null;
}
/**
* This method cleans up the specified <code>PersistenceManager</code>. If the pm still has an
* open transaction, it will be rolled back, before closing the pm.
*
* @param pm the PersistenceManager
*/
protected static void cleanupPM(PersistenceManager pm) {
if ((pm != null) && !pm.isClosed()) {
if (pm.currentTransaction().isActive()) {
pm.currentTransaction().rollback();
}
pm.close();
}
}
/** Closes the pmf stored in this instance. */
public static void closePMF() {
JDOException failure = null;
while (pmf != null) {
try {
if (!pmf.isClosed()) {
closePMF(pmf);
}
pmf = null;
} catch (JDOException ex) {
// store failure of first call pmf.close
if (failure == null) failure = ex;
PersistenceManager[] pms = getFailedPersistenceManagers("closePMF", ex);
for (PersistenceManager persistenceManager : pms) {
cleanupPM(persistenceManager);
}
} catch (RuntimeException ex) {
pmf = null;
ex.printStackTrace(System.out);
throw ex;
}
}
// rethrow JDOException thrown by pmf.close
if (failure != null) throw failure;
}
/**
* Closes the pmf passed as a parameter. This must be done in a doPrivileged block.
*
* @param PMF the PersistenceManagerFactory
*/
public static void closePMF(final PersistenceManagerFactory PMF) {
if (PMF != null) {
if (!PMF.isClosed()) {
doPrivileged(
() -> {
PMF.close();
return null;
});
}
}
}
@SuppressWarnings("unchecked")
private static <T> T doPrivileged(PrivilegedAction<T> privilegedAction) {
try {
return (T) LegacyJava.doPrivilegedAction.invoke(null, privilegedAction);
} catch (IllegalAccessException | InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new JDOFatalInternalException(e.getMessage());
}
}
/**
* Returns failed PersistenceManagers
*
* @param assertionFailure failure
* @param ex exception
* @return failed PersistenceManagers
*/
protected static PersistenceManager[] getFailedPersistenceManagers(
String assertionFailure, JDOException ex) {
Throwable[] nesteds = ex.getNestedExceptions();
int numberOfExceptions = nesteds == null ? 0 : nesteds.length;
PersistenceManager[] result = new PersistenceManager[numberOfExceptions];
for (int i = 0; i < numberOfExceptions; ++i) {
JDOException exc = (JDOException) nesteds[i];
Object failedObject = exc.getFailedObject();
if (exc.getFailedObject() instanceof PersistenceManager) {
result[i] = (PersistenceManager) failedObject;
} else {
throw new JDOFatalException(
assertionFailure,
"Unexpected failed object of type: " + failedObject.getClass().getName());
}
}
return result;
}
/**
* This method load Properties from a given file.
*
* @param fileName the name of the properties file
* @return a Properties instance with the loaded properties
*/
protected Properties loadProperties(String fileName) {
if (fileName == null) {
fileName = System.getProperty("user.home") + "/.jdo/PMFProperties.properties";
}
Properties props = new Properties();
InputStream propStream = null;
try {
propStream = new FileInputStream(fileName);
} catch (IOException ex) {
System.out.println("Could not open properties file \"" + fileName + "\"");
System.out.println(
"Please specify a system property PMFProperties "
+ "with the PMF properties file name as value "
+ "(defaults to {user.home}/.jdo/PMFProperties.properties)");
System.exit(1);
}
try {
props.load(propStream);
} catch (IOException ex) {
System.out.println("Error loading properties file \"" + fileName + "\"");
ex.printStackTrace();
System.exit(1);
}
return props;
}
/**
* Prints the specified msg (if debug is true), before it aborts the test case.
*
* @param assertionFailure the assertion failure
* @param msg the message text
*/
public void fail(String assertionFailure, String msg) {
if (debug) logger.debug(msg);
fail(assertionFailure + NL + msg);
}
// Helper methods to check for supported options
/**
* Dump the supportedOptions to the a file in the specified directory.
*
* @param directory the directory the options are dumped to
*/
public static void dumpSupportedOptions(String directory) {
if (supportedOptions == null) return;
File file = new File(directory, PMF_SUPPORTED_OPTIONS_FILE_NAME);
if (file.exists())
// PMF supported options have been dumped before => return
return;
try (PrintStream resultStream = new PrintStream(new FileOutputStream(file))) {
for (String supportedOption : supportedOptions) {
resultStream.println(supportedOption);
}
} catch (FileNotFoundException e) {
throw new JDOFatalException("dumpSupportedOptions: cannot create file " + file.getName(), e);
}
}
/**
* Prints a message (if debug is true) saying the test with the specified name is not executed,
* because the JDO implementation under test does not support the specified optional feature.
*
* @param testName the name of the test method that is skipped.
* @param optionalFeature the name of the option not supported by the JDO implementation under
* tets.
*/
protected void printUnsupportedOptionalFeatureNotTested(String testName, String optionalFeature) {
if (debug) {
logger.debug(
"Test "
+ testName
+ " was not run, because optional feature "
+ optionalFeature
+ " is not supported by the JDO implementation under test");
}
}
/**
* Reports whether TransientTransactional is supported.
*
* @return true if TransientTransactional is supported.
*/
public boolean isTransientTransactionalSupported() {
return supportedOptions.contains("javax.jdo.option.TransientTransactional");
}
/**
* Reports whether NontransactionalRead is supported.
*
* @return true if NontransactionalRead is supported.
*/
public boolean isNontransactionalReadSupported() {
return supportedOptions.contains("javax.jdo.option.NontransactionalRead");
}
/**
* Reports whether NontransactionalWrite is supported.
*
* @return true if NontransactionalWrite is supported.
*/
public boolean isNontransactionalWriteSupported() {
return supportedOptions.contains("javax.jdo.option.NontransactionalWrite");
}
/**
* Reports whether RetainValues is supported.
*
* @return true if RetainValues is supported.
*/
public boolean isRetainValuesSupported() {
return supportedOptions.contains("javax.jdo.option.RetainValues");
}
/**
* Reports whether Optimistic is supported.
*
* @return true if Optimistic is supported.
*/
public boolean isOptimisticSupported() {
return supportedOptions.contains("javax.jdo.option.Optimistic");
}
/**
* Reports whether Application Identity is supported.
*
* @return true if Application Identity is supported.
*/
public boolean isApplicationIdentitySupported() {
return supportedOptions.contains("javax.jdo.option.ApplicationIdentity");
}
/**
* Reports whether Datastore Identity is supported.
*
* @return true if Datastore Identity is supported.
*/
public boolean isDatastoreIdentitySupported() {
return supportedOptions.contains("javax.jdo.option.DatastoreIdentity");
}
/**
* Reports whether Non-Durable Identity is supported.
*
* @return true if Non-Durable Identity is supported.
*/
public boolean isNonDurableIdentitySupported() {
return supportedOptions.contains("javax.jdo.option.NonDurableIdentity");
}
/**
* Reports whether an <code>ArrayList</code> collection is supported.
*
* @return true if an <code>ArrayList</code> collection is supported.
*/
public boolean isArrayListSupported() {
return supportedOptions.contains("javax.jdo.option.ArrayList");
}
/**
* Reports whether a <code>HashMap</code> collection is supported.
*
* @return true if a <code>HashMap</code> collection is supported.
*/
public boolean isHashMapSupported() {
return supportedOptions.contains("javax.jdo.option.HashMap");
}
/**
* Reports whether a <code>Hashtable</code> collection is supported.
*
* @return true if a <code>Hashtable</code> collection is supported.
*/
public boolean isHashtableSupported() {
return supportedOptions.contains("javax.jdo.option.Hashtable");
}
/**
* Reports whether a <code>LinkedList</code> collection is supported.
*
* @return true if a <code>LinkedList</code> collection is supported.
*/
public boolean isLinkedListSupported() {
return supportedOptions.contains("javax.jdo.option.LinkedList");
}
/**
* Reports whether a <code>TreeMap</code> collection is supported.
*
* @return true if a <code>TreeMap</code> collection is supported.
*/
public boolean isTreeMapSupported() {
return supportedOptions.contains("javax.jdo.option.TreeMap");
}
/**
* Reports whether a <code>TreeSet</code> collection is supported.
*
* @return true if a <code>TreeSet</code> collection is supported.
*/
public boolean isTreeSetSupported() {
return supportedOptions.contains("javax.jdo.option.TreeSet");
}
/**
* Reports whether a <code>Vector</code> collection is supported.
*
* @return true if a <code>Vector</code> collection is supported.
*/
public boolean isVectorSupported() {
return supportedOptions.contains("javax.jdo.option.Vector");
}
/**
* Reports whether a <code>Map</code> collection is supported.
*
* @return true if a <code>Map</code> collection is supported.
*/
public boolean isMapSupported() {
return supportedOptions.contains("javax.jdo.option.Map");
}
/**
* Reports whether a <code>List</code> collection is supported.
*
* @return true if a <code>List</code> collection is supported.
*/
public boolean isListSupported() {
return supportedOptions.contains("javax.jdo.option.List");
}
/**
* Reports whether arrays are supported.
*
* @return true if arrays are supported.
*/
public boolean isArraySupported() {
return supportedOptions.contains("javax.jdo.option.Array");
}
/**
* Reports whether a null collection is supported.
*
* @return true if a null collection is supported.
*/
public boolean isNullCollectionSupported() {
return supportedOptions.contains("javax.jdo.option.NullCollection");
}
/**
* Reports whether Changing Application Identity is supported.
*
* @return true if Changing Application Identity is supported.
*/
public boolean isChangeApplicationIdentitySupported() {
return supportedOptions.contains("javax.jdo.option.ChangeApplicationIdentity");
}
/**
* Reports whether Binary Compatibility is supported.
*
* @return true if Binary Compatibility is supported.
*/
public boolean isBinaryCompatibilitySupported() {
return supportedOptions.contains("javax.jdo.option.BinaryCompatibility");
}
/**
* Reports whether UnconstrainedVariables is supported.
*
* @return true if UnconstrainedVariables is supported.
*/
public boolean isUnconstrainedVariablesSupported() {
return supportedOptions.contains("javax.jdo.query.JDOQL.UnconstraintedQueryVariables");
}
/**
* Reports whether BitwiseOperations is supported.
*
* @return true if BitwiseOperations is supported.
*/
public boolean isBitwiseOperationsSupported() {
return supportedOptions.contains("javax.jdo.query.JDOQL.bitwiseOperations");
}
/**
* Reports whether SQL queries are supported.
*
* @return true if SQL queries are supported.
*/
public boolean isSQLSupported() {
return supportedOptions.contains("javax.jdo.query.SQL");
}
/**
* Reports whether getting the DataStoreConnection is supported.
*
* @return true if getting the DataStoreConnection is supported.
*/
public boolean isDataStoreConnectionSupported() {
return supportedOptions.contains("javax.jdo.option.GetDataStoreConnection");
}
/**
* Reports whether canceling a running query is supported.
*
* @return true if canceling a running query is supported.
*/
public boolean isQueryCancelSupported() {
return supportedOptions.contains("javax.jdo.option.QueryCancel");
}
/**
* Reports whether setting a Datastore timout is supported.
*
* @return true if setting a Datastore timout is supported.
*/
public boolean isDatastoreTimeoutSupported() {
return supportedOptions.contains(Constants.OPTION_DATASTORE_TIMEOUT);
}
/**
* Reports whether a feature is supported
*
* @param option the option
* @return true if the specified option is supported
*/
public boolean isSupported(String option) {
return supportedOptions.contains(option);
}
/**
* Determine if a class is loadable in the current environment.
*
* @param className the name of the class
* @return true if the class is loadable in the current environment.
*/
public static boolean isClassLoadable(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException ex) {
return false;
}
}
/**
* Determine if the environment is 1.4 version of JRE or better.
*
* @return true if 1.4 version of JRE or better.
*/
public static boolean isJRE14orBetter() {
return isClassLoadable("java.util.Currency");
}
/**
* This utility method returns a <code>String</code> that indicates the current state of an
* instance.
*
* @param o The object.
* @return The current state of the instance, by using the <code>JDOHelper</code> state
* interrogation methods.
*/
public static String getStateOfInstance(Object o) {
boolean existingEntries = false;
StringBuilder buff = new StringBuilder("{");
if (JDOHelper.isPersistent(o)) {
buff.append("persistent");
existingEntries = true;
}
if (JDOHelper.isTransactional(o)) {
if (existingEntries) buff.append(", ");
buff.append("transactional");
existingEntries = true;
}
if (JDOHelper.isDirty(o)) {
if (existingEntries) buff.append(", ");
buff.append("dirty");
existingEntries = true;
}
if (JDOHelper.isNew(o)) {
if (existingEntries) buff.append(", ");
buff.append("new");
existingEntries = true;
}
if (JDOHelper.isDeleted(o)) {
if (existingEntries) buff.append(", ");
buff.append("deleted");
}
if (JDOHelper.isDetached(o)) {
if (existingEntries) buff.append(", ");
buff.append("detached");
}
buff.append("}");
return buff.toString();
}
/**
* This method will return the current lifecycle state of an instance.
*
* @param o the object
* @return the current lifecycle state
*/
public static int currentState(Object o) {
boolean[] status = new boolean[NUM_STATUSES];
status[IS_PERSISTENT] = JDOHelper.isPersistent(o);
status[IS_TRANSACTIONAL] = JDOHelper.isTransactional(o);
status[IS_DIRTY] = JDOHelper.isDirty(o);
status[IS_NEW] = JDOHelper.isNew(o);
status[IS_DELETED] = JDOHelper.isDeleted(o);
status[IS_DETACHED] = JDOHelper.isDetached(o);
int i;
int j;
outerloop:
for (i = 0; i < NUM_STATES; ++i) {
for (j = 0; j < NUM_STATUSES; ++j) {
if (status[j] != state_statuses[i][j]) continue outerloop;
}
return i;
}
return NUM_STATES;
}
/**
* Tests if a found state matches an expected state.
*
* @param foundState the found state
* @param expectedState the expected state
* @return true if the found state matches the expected state
*/
public static boolean compareStates(int foundState, int expectedState) {
// status interrogation gives same values for PERSISTENT_NONTRANSACTIONAL and HOLLOW
return (expectedState < 0
|| foundState == expectedState
|| (foundState == HOLLOW && expectedState == PERSISTENT_NONTRANSACTIONAL)
|| (foundState == PERSISTENT_NONTRANSACTIONAL && expectedState == HOLLOW));
}
/**
* This method mangles an object by changing all its non-static, non-final fields. It returns true
* if the object was mangled, and false if there are no fields to mangle.
*
* @param oid the oid of the object
* @return a mangled object
* @throws Exception exception
*/
protected boolean mangleObject(Object oid) throws IllegalAccessException {
Field[] fields = getModifiableFields(oid);
if (fields.length == 0) return false;
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (fieldType == long.class) {
field.setLong(oid, 10000L + field.getLong(oid));
} else if (fieldType == int.class) {
field.setInt(oid, 10000 + field.getInt(oid));
} else if (fieldType == short.class) {
field.setShort(oid, (short) (10000 + field.getShort(oid)));
} else if (fieldType == byte.class) {
field.setByte(oid, (byte) (100 + field.getByte(oid)));
} else if (fieldType == char.class) {
field.setChar(oid, (char) (10 + field.getChar(oid)));
} else if (fieldType == String.class) {
field.set(oid, "This is certainly a challenge" + field.get(oid));
} else if (fieldType == Integer.class) {
field.set(oid, Integer.valueOf(10000 + ((Integer) field.get(oid)).intValue()));
} else if (fieldType == Long.class) {
field.set(oid, Long.valueOf(10000L + ((Long) field.get(oid)).longValue()));
} else if (fieldType == Short.class) {
field.set(oid, Short.valueOf((short) (10000 + ((Short) field.get(oid)).shortValue())));
} else if (fieldType == Byte.class) {
field.set(oid, Byte.valueOf((byte) (100 + ((Byte) field.get(oid)).byteValue())));
} else if (fieldType == Character.class) {
field.set(oid, Character.valueOf((char) (10 + ((Character) (field.get(oid))).charValue())));
}
}
return true;
}
/**
* Returns modifiable Fields of the class of the parameter. Fields are considered modifiable if
* they are not static or final. This method requires several permissions in order to run with a
* SecurityManager, hence the doPrivileged block:
*
* <ul>
* <li>ReflectPermission("suppressAccessChecks")
* <li>RuntimePermission("accessDeclaredMembers")
* </ul>
*
* @param obj the object
* @return an array of fields
*/
protected Field[] getModifiableFields(final Object obj) {
return doPrivileged(
() -> {
Class<?> cls = obj.getClass();
List<Field> result = new ArrayList<>();
Field[] fields = cls.getFields();
for (Field field : fields) {
int modifiers = field.getModifiers();
if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) continue;
field.setAccessible(true);
result.add(field);
}
return result.toArray(new Field[result.size()]);
});
}
/**
* Returns <code>true</code> if the current test runs with application identity. This means the
* system property jdo.tck.identitytype has the value applicationidentity.
*
* @return <code>true</code> if current test runs with application identity; <code>false</code>
* otherwise:
*/
public boolean runsWithApplicationIdentity() {
return APPLICATION_IDENTITY.equals(IDENTITYTYPE);
}
/**
* Prints a message (if debug is true) saying the test with the specified name is not executed,
* because the JDO implementation under test is run for an inapplicable identity type.
*
* @param testName the name of the test method that is skipped.
* @param requiredIdentityType the name of the required identity type.
*/
protected void printNonApplicableIdentityType(String testName, String requiredIdentityType) {
if (debug) {
logger.debug(
"Test "
+ testName
+ " was not run, because it is only applicable for identity type "
+ requiredIdentityType
+ ". The identity type of the current configuration is "
+ IDENTITYTYPE);
}
}
/**
* Returns the value of the PMF property given by argument <code>key</code>.
*
* @param key the key
* @return the value
*/
protected String getPMFProperty(String key) {
return PMFPropertiesObject.getProperty(key);
}
/**
* Returns <code>true</code> if the implementation under test supports all JDO options contained
* in system property <code>jdo.tck.requiredOptions</code>.
*
* @return <code>true</code> if the implementation under test supports all JDO options contained
* in system property <code>jdo.tck.requiredOptions</code>
*/
protected boolean isTestToBePerformed() {
boolean isTestToBePerformed = true;
String requiredOptions = System.getProperty("jdo.tck.requiredOptions");
// Collection supportedOptions = supportedOptions;
StringTokenizer tokenizer = new StringTokenizer(requiredOptions, " ,;\n\r\t");
while (tokenizer.hasMoreTokens()) {
String requiredOption = tokenizer.nextToken();
logger.debug("Required option: " + requiredOption);
if (!requiredOption.equals("") && !supportedOptions.contains(requiredOption)) {
isTestToBePerformed = false;
printUnsupportedOptionalFeatureNotTested(getClass().getName(), requiredOption);
}
}
return isTestToBePerformed;
}
/** New line. */
public static final String NL = System.getProperty("line.separator");
/** A buffer of of error messages. */
protected static StringBuffer messages;
/**
* Appends to error messages.
*
* @param message the message
*/
protected static synchronized void appendMessage(String message) {
if (messages == null) {
messages = new StringBuffer(NL);
}
messages.append(message);
messages.append(NL);
}
/**
* Appends to error messages.
*
* @param test test option
* @param context context
* @param message message
*/
protected static synchronized void deferredAssertTrue(
boolean test, String context, String message) {
if (!test) {
appendMessage(context + ": " + message);
}
}
/**
* Appends an error if the actual value does not equal the expected value. Primitive values are
* autoboxed. Null values are ok for both expected and actual.
*
* @param message the message
* @param expected the expected value
* @param actual the actual value
*/
protected void errorIfNotEqual(String message, Object expected, Object actual) {
if (expected == null) {
if (actual != null) {
appendMessage(message + " failed. expected: null; actual: " + actual);
}
} else {
if (!expected.equals(actual)) {
appendMessage(message + " failed. expected: " + expected + "; actual: " + actual);
}
}
}
/**
* Appends an error if the actual value equals the unexpected value. Primitive values are
* autoboxed. Null values are ok for both unexpected and actual.
*
* @param message the message
* @param unexpected the unexpected value
* @param actual the actual value
*/
protected void errorIfEqual(String message, Object unexpected, Object actual) {
if (unexpected == null) {
if (actual == null) {
appendMessage(message + " failed. unexpected: null");
}
} else {
if (unexpected.equals(actual)) {
appendMessage(message + " failed. unexpected: " + unexpected);
}
}
}
/**
* Returns collected error messages, or <code>null</code> if there are none, and clears the
* buffer.
*
* @return collected error messages
*/
protected static synchronized String retrieveMessages() {
if (messages == null) {
return null;
}
final String msg = messages.toString();
messages = null;
return msg;
}
/** Fail the test if there are any error messages. */
protected void failOnError() {
String errors = retrieveMessages();
if (errors != null) {
fail(errors);
}
}
/**
* Validate an actual isolation level against the requested level.
*
* @param requested requested level
* @param actual actual level
* @return true if the actual level is greater or equal the requsted level
*/
protected boolean validLevelSubstitution(String requested, String actual) {
int requestedLevel = (levelValues.get(requested)).intValue();
int actualLevel = (levelValues.get(actual)).intValue();
return actualLevel >= requestedLevel;
}
}