| /* |
| * 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; |
| } |
| } |