/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.testing.framework; import java.io.*; import java.lang.ref.WeakReference; import javax.persistence.*; import org.eclipse.persistence.exceptions.*; import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; import org.eclipse.persistence.internal.helper.*; import org.eclipse.persistence.sessions.*; import org.eclipse.persistence.sessions.server.Server; import org.eclipse.persistence.sessions.server.ServerSession; /** * <p>Purpose<b></b>: * All the test cases are subclassed from this class. * Each test case tests single feature of TopLink. Ideally a test case consists of five steps. * Setup: Performs all the initial setup that is required by the test, * such as setting up database to some state on which test would run. * Test: The actual test to be performed, such as writing an object. * Verify: Verify the test if it was performed well or not. * Reset: Reset the database to the state from where the test started * Reset Verify: Check if reset performed well or not. */ public abstract class TestCase extends junit.framework.TestCase implements TestEntity { /** Store the name to allow serialization, for some reason JUnit name does not serialize. */ private String name; /** The result of the test. */ private TestResult testResult; /** The executor used to execute the test. */ private transient TestExecutor executor; /** To provide small description of the test case */ private String description; /** The test collection that contains this test */ private TestEntity container; /** This is used only for printing test results with proper indentation */ private int nestedCounter; /** The indentation string that is added to each line of result for printing. */ private String indentationString; public TestCase() { description = ""; nestedCounter = INITIAL_VALUE; testResult = new TestResult(this); setName(getClass().getName().substring(getClass().getName().lastIndexOf('.') + 1)); } /** * Reset the JUnit name in case serialization looses it. */ public String getName() { if (super.getName() == null) { setName(this.name); } return super.getName(); } /** * Store the test name locally to ensure it can serialize. */ public void setName(String name) { super.setName(name); this.name = name; } /** * Append test case result to the test results summary. */ public void appendTestResult(TestResultsSummary summary) { summary.appendTestCaseResult(this); } /** * Computes the level for indentation. */ public void computeNestedLevel() { TestEntity testContainer = getContainer(); if ((testContainer != null) && (testContainer.getNestedCounter() != INITIAL_VALUE)) { setNestedCounter(testContainer.getNestedCounter() + 1); } else { incrementNestedCounter(); } } /** * Return if the two objects match. */ public boolean compareObjects(Object source, Object target) { return getAbstractSession().compareObjects(source, target); } /** * Check the database and ensure the object and its parts have been fully deleted. */ public boolean verifyDelete(Object object) { return getAbstractSession().verifyDelete(object); } /** * The session is initialized to the default login from the Persistent System * if no explicit login is done for testing. This method must be overridden in * the subclasses if different login is required. */ public Session defaultLogin() { return (new TestSystem()).login(); } /** * Executes this test case. * Note: * Only RuntimeExceptions are caught because all EclipseLink Exceptions are derived from * RuntimeException. This takes care of other java runtime exceptions also. */ public void execute(TestExecutor executor) { boolean executeFailed = false; setTestResult(new TestResult(this, "Passed")); setExecutor(executor); long startTime = System.currentTimeMillis(); try { try { setUp(); } catch (EclipseLinkException exception) { executeFailed = true; setTestException(exception); throw exception; } catch (Throwable exception) { executeFailed = true; TestProblemException problem = new TestProblemException("Problem in the setup method of the test: " + getName()); problem.setInternalException(exception); setTestException(problem); throw problem; } try { executeTest(); verify(); } catch (EclipseLinkException exception) { executeFailed = true; if (getTestException() == null) { setTestException(exception); throw exception; } } catch (Throwable runtimeException) { executeFailed = true; TestErrorException topLinkException = new TestErrorException("Fatal error occurred.", runtimeException); if (getTestException() == null) { setTestException(topLinkException); throw topLinkException; } } } finally { try { tearDown(); } catch (EclipseLinkException exception) { executeFailed = true; if (getTestException() == null) { setTestException(exception); throw exception; } } catch (Throwable exception) { executeFailed = true; TestProblemException problem = new TestProblemException("Problem in the reset method of the test"); problem.setInternalException(exception); if (getTestException() == null) { setTestException(problem); throw problem; } } finally { long endTime = System.currentTimeMillis(); getTestResult().setTotalTime(endTime - startTime); // If a failure occurred allow recreation of the database and initialize the identity maps. if (executeFailed) { // If this test is not local allow for cleanup. if (!isLocalTest()) { cleanAfterExecuteFailed(); } } // Check for faulty tests leaving transaction open. if (getAbstractSession().isInTransaction()) { try { int count = 0; while (getAbstractSession().isInTransaction() && (count < 10)) { getAbstractSession().rollbackTransaction(); count++; } } catch (Throwable ignore) { } TestProblemException problem = new TestProblemException(this + " is a faulty test, transaction was left open and must always be closed."); problem.setInternalException(getTestException()); setTestException(problem); throw problem; } } } } /** * Sets up the fixture, for example, open a network connection. * This method is called before a test is executed. * Calls old setup method by default. */ protected void setUp() throws Exception { try { setup(); } catch (Throwable exception) { if (exception instanceof Exception) { throw (Exception)exception; } else { throw new TestErrorException("Fatal errored in setUp.", exception); } } } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. * Calls old reset method by default. */ protected void tearDown() throws Exception { try { reset(); resetVerify(); } catch (Throwable exception) { if (exception instanceof Exception) { throw (Exception)exception; } else { throw new TestErrorException("Fatal errored in tearDown.", exception); } } } /** * If there is no executor, * Create a default executor and run the test. */ public void runBare() throws Throwable { TestExecutor executor = getExecutor(); if (executor == null) { executor = TestExecutor.getDefaultExecutor(); } try { execute(executor); } catch (TestWarningException exception) { System.out.println("WARNING: " + exception); } } /** * Return test collection which contains this test entity. */ public TestEntity getContainer() { return container; } /** * Return the description of the test. */ public String getDescription() { return description; } /** * Return the executor. */ public TestExecutor getExecutor() { return executor; } /** * Get the indentaitonString */ public String getIndentationString() { return indentationString; } /** * Return the nested counter value */ public int getNestedCounter() { return nestedCounter; } /** * Return the test result. The testResult stores the result of this * test. */ public ResultInterface getReport() { return getTestResult(); } /** * Create a new entity manager from the entity manager factory. * This entity manager is initialized from META-INF/persistence.xml. */ public EntityManager createEntityManager() { return getExecutor().createEntityManager(); } /** * Return the session cast to DatabaseSession. */ public org.eclipse.persistence.sessions.DatabaseSession getDatabaseSession() { return (org.eclipse.persistence.sessions.DatabaseSession)getExecutor().getSession(); } /** * Return the session cast to AbstractSession. */ public org.eclipse.persistence.internal.sessions.AbstractSession getAbstractSession() { return (org.eclipse.persistence.internal.sessions.AbstractSession)getExecutor().getSession(); } /** * Return the database session. */ public org.eclipse.persistence.sessions.Session getSession() { return getExecutor().getSession(); } /** * Return and cast the session to a Server. */ public Server getServerSession() { return (Server)getExecutor().getSession(); } /** * Begin a transaction, cast to AbstractSession to work with all session types. */ public void beginTransaction() { getAbstractSession().beginTransaction(); } /** * Commit a transaction, cast to AbstractSession to work with all session types. */ public void commitTransaction() { getAbstractSession().commitTransaction(); } /** * Rollback a transaction, cast to AbstractSession to work with all session types. */ public void rollbackTransaction() { getAbstractSession().rollbackTransaction(); } public EclipseLinkException getTestException() { return getTestResult().getException(); } /** * Return the test result. The testResult stores the result of this * test. */ public TestResult getTestResult() { return testResult; } public void incrementNestedCounter() { setNestedCounter(getNestedCounter() + 1); } /** * The result of the test is logged on to the specified print stream. * This method is added to migrate tests to Ora*Tst */ public void logRegressionResult(Writer log) { computeNestedLevel(); setIndentationString(Helper.getTabs(getNestedCounter())); try { log.write(org.eclipse.persistence.internal.helper.Helper.cr() + getIndentationString() + "TEST NAME: " + getName() + org.eclipse.persistence.internal.helper.Helper.cr()); log.write(getIndentationString() + "TEST DESCRIPTION: " + getDescription() + org.eclipse.persistence.internal.helper.Helper.cr()); log.flush(); } catch (IOException exception) { } getTestResult().logRegressionResult(log); } /** * The result of the test is logged on to the specified print stream. */ public void logResult(Writer log, boolean shouldLogOnlyErrors) { logResult(log); } /** * The result of the test is logged on to the specified print stream. */ public void logResult(Writer log) { computeNestedLevel(); setIndentationString(Helper.getTabs(getNestedCounter())); try { log.write(org.eclipse.persistence.internal.helper.Helper.cr() + getIndentationString() + "VERSION: " + org.eclipse.persistence.sessions.DatabaseLogin.getVersion()); log.write(org.eclipse.persistence.internal.helper.Helper.cr() + getIndentationString() + "TEST NAME: " + getName() + org.eclipse.persistence.internal.helper.Helper.cr()); log.write(getIndentationString() + "TEST DESCRIPTION: " + getDescription() + org.eclipse.persistence.internal.helper.Helper.cr()); log.flush(); } catch (IOException exception) { } getTestResult().logResult(log); } public boolean requiresDatabase() { return true; } /** * This is a optional method in the test cases. It should be overridden only if somthing has to * be reset back to the state from where the test started. */ public void reset() throws Throwable { return; } /** * Reset the entity. */ public void resetEntity() { try { reset(); } catch (Throwable runtimeException) { TestProblemException validationException = new TestProblemException("Reset problem occurred.", runtimeException); if (getTestException() == null) { setTestException(validationException); } throw validationException; } } public void resetNestedCounter() { setNestedCounter(INITIAL_VALUE); } /** * This is a mandatory method in the test cases only if reset has been overridden. * The method should check if reset method really reset the databse back to the state * from where the test started. */ protected void resetVerify() throws Throwable { return; } public void setContainer(TestEntity testEntity) { container = testEntity; } /** * Set the description of the test. */ public void setDescription(String description) { this.description = description; } /** * Set the executor. */ public void setExecutor(TestExecutor anExecutor) { executor = anExecutor; } /** * Set the indentaitonString */ public void setIndentationString(String indentationString) { this.indentationString = indentationString; } /** * Set the nested counter value. */ public void setNestedCounter(int level) { this.nestedCounter = level; } /** * The exception raised by the test is stored in the result. Eventually the result * decides the outcome of the test depending upon the kind of exception. No exception is * stored if test runs well. */ public void setTestException(EclipseLinkException exception) { getTestResult().setException(exception); } /** * Set the test result. */ public void setReport(ResultInterface testResult) { setTestResult((TestResult)testResult); } /** * Set the test result. The testResult stores the result of the * test. */ public void setTestResult(TestResult testResult) { this.testResult = testResult; testResult.setTestCase(this); } /** * The first step in testing process. The method is overridden if their is something * that test should perform before running the actual test. */ protected void setup() throws Throwable { return; } /** * Override to run the test and assert its state. * @exception Throwable if any exception is thrown */ protected void runTest() throws Throwable { // Nothing by default. } /** * Allow for test to intercept running of test. */ public void executeTest() throws Throwable { test(); } /** * Test can be define as test, or runTest, * or if the name is a method on this class, * this method is run reflectively by default. */ protected void test() throws Throwable { runTest(); } /** * Print the test as its name. */ public String toString() { return getName(); } /** * Verification of test is done here if the test ran properly or not. Subclasses should * override it. */ protected void verify() throws Throwable { return; } /** * Throw a test error exception. * Errors indicate the test failed. */ public void throwError(String message) { throw new TestErrorException(message); } /** * Throw a test error exception. * Errors indicate the test failed. */ public void throwError(String message, Throwable exception) { throw new TestErrorException(message, exception); } /** * Throw a test warning exception. * Warning indicate the test did not fail, but did not pass either, * the test could not be run or the result could not be verified. */ public void throwWarning(String message) { throw new TestWarningException(message); } /** * Sometimes a test prefers to perform a number of small checks/assertions * in the test() method, rather than calling a single test() followed * by a single verify(). * <p> * In line with JUNIT style testing, would be called assert if assert * did not become a reserved keyword in JDK 1.4.2. */ public void strongAssert(boolean assertion, String errorMessage) { if (!assertion) { throw new TestErrorException(errorMessage); } } /** * Same as strongAssert but only throws a warning. I.e. if the check * is how the feature is implemented but not really crucial to how it works, * just throw a warning and fix the test later. * @param assertion * @param warningMessage */ public void weakAssert(boolean assertion, String warningMessage) { if (!assertion) { throw new TestWarningException(warningMessage); } } /** * Answer true to a local test check if the class name ends in "Local" */ private boolean isLocalTest() { return getClass().getName().endsWith("Local"); } /** * If the execute fails, do the necessary cleanup to ensure the next test gets no residue */ public void cleanAfterExecuteFailed() { } /** * Throws a warning of pessimistic locking/select for update is not supported for this test platform. * Currently testing supports select for update on Oracle, MySQL, SQLServer, TimesTen. * Some of the other platforms may have some support for select for update, but the databases we test with * for these do not have sufficient support to pass the tests. * Derby, Firebird and Symfoware (bug 304903) have some support, but does not work with joins (2008-12-01). */ public void checkSelectForUpateSupported() { DatabasePlatform platform = getSession().getPlatform(); if (platform.isFirebird() || platform.isAccess() || platform.isSybase() || platform.isSQLAnywhere() || platform.isDerby() || platform.isHSQL() || platform.isSymfoware()) { throw new TestWarningException("This database does not support FOR UPDATE"); } } /** * Throws a warning of pessimistic locking/select for update nowait is not supported for this test platform. * Currently testing supports nowait on Oracle, SQLServer, PostgreSQL. */ public void checkNoWaitSupported() { DatabasePlatform platform = getSession().getPlatform(); if (!(platform.isOracle() || platform.isSQLServer() || platform.isPostgreSQL())) { throw new TestWarningException("This database does not support NOWAIT"); } } /** * Throws a warning if the test database is using serializable transaction isolation. */ public SessionEventAdapter checkTransactionIsolation() { final SessionEventAdapter listener; DatabasePlatform platform = getSession().getPlatform(); if (platform.isSybase()) { if (SybaseTransactionIsolationListener.isDatabaseVersionSupported((ServerSession) getAbstractSession().getParent())) { listener = new SybaseTransactionIsolationListener(); } else { throw new TestWarningException("The test requires Sybase version " + SybaseTransactionIsolationListener.requiredVersion + " or higher"); } } else if (platform.isSQLServer()) { throw new TestWarningException("This test requires transaction isolation setup on SQLServer database which is currently not set in tlsvrdb6"); } else if (platform.isSQLAnywhere()) { throw new TestWarningException("This test requires transaction isolation setup on SQLAnywhere database which is currently not set"); } else if (platform.isDB2()) { throw new TestWarningException("This test requires transaction isolation setup on DB2 database which is currently not set"); } else if (platform.isSymfoware()) { listener = new TransactionIsolationLevelSwitchListener(); } else if (platform.isMaxDB()) { listener = new JDBCIsoLevelSwitchListener(); } else { return null; } getAbstractSession().getParent().getEventManager().addListener(listener); return listener; } /** * Return if stored procedures are supported for the database platform for the test database. */ public static boolean supportsStoredProcedures(Session session) { DatabasePlatform platform = session.getPlatform(); return platform.isOracle() || platform.isSybase() || platform.isMySQL() || platform.isSQLServer() || platform.isSymfoware(); } /** * Force a garbage collection. */ public void forceGC() { WeakReference ref = new WeakReference(new Object()); for (int loops = 0; loops < 10; loops++) { //List junk = new ArrayList (10); for (int i = 0; i < 10; i++) { //junk.add(new java.math.BigDecimal(i)); } // Force garbage collection. System.gc(); System.runFinalization(); } // Check if a garbage collect really occurred. if (ref.get() != null) { System.out.println("WARNING: gc did not occur"); } } }