/******************************************************************************* | |
* Copyright (c) 1998, 2013 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"); | |
} | |
} | |
} |