blob: 5d6125853fa59daa2c95fa1566f3f65bbfbcb753 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.framework;
import java.io.IOException;
import java.io.Writer;
import java.lang.ref.WeakReference;
import jakarta.persistence.EntityManager;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.SessionEventAdapter;
import org.eclipse.persistence.sessions.server.Server;
import org.eclipse.persistence.sessions.server.ServerSession;
/**
* <p><b>Purpose</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.
*/
@Override
public String getName() {
if (super.getName() == null) {
setName(this.name);
}
return super.getName();
}
/**
* Store the test name locally to ensure it can serialize.
*/
@Override
public void setName(String name) {
super.setName(name);
this.name = name;
}
/**
* Append test case result to the test results summary.
*/
@Override
public void appendTestResult(TestResultsSummary summary) {
summary.appendTestCaseResult(this);
}
/**
* Computes the level for indentation.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public void execute(TestExecutor executor) {
boolean executeFailed = false;
setTestResult(new TestResult(this, "Passed"));
setExecutor(executor);
long startTime = System.nanoTime();
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.nanoTime();
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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
*/
@Override
public int getNestedCounter() {
return nestedCounter;
}
/**
* Return the test result. The testResult stores the result of this
* test.
*/
@Override
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;
}
@Override
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
*/
@Override
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.
*/
@Override
public void logResult(Writer log, boolean shouldLogOnlyErrors) {
logResult(log);
}
/**
* The result of the test is logged on to the specified print stream.
*/
@Override
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);
}
@Override
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.
*/
@Override
public void resetEntity() {
try {
reset();
} catch (Throwable runtimeException) {
TestProblemException validationException = new TestProblemException("Reset problem occurred.", runtimeException);
if (getTestException() == null) {
setTestException(validationException);
}
throw validationException;
}
}
@Override
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;
}
@Override
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.
*/
@Override
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.
*/
@Override
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
*/
@Override
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.
*/
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.isDerby() || platform.isSymfoware()) {
listener = new TransactionIsolationLevelSwitchListener(platform);
} 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");
}
}
}