blob: c2b5bd7102b445f95e98fa8b17de73a6a61b9af4 [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.tests.simultaneous;
import java.io.*;
import java.util.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.sessions.server.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.testing.framework.ui.SynchronizedTester;
import org.eclipse.persistence.testing.framework.ui.SynchronizedTestExecutor;
public class MultithreadTestCase extends AutoVerifyTestCase {
protected TestCase[] test;
protected int numberOfTests;
protected boolean useSequenceConnectionPool = false;
protected class TestEventListenerImpl implements junit.framework.TestListener {
public static final int INITIAL = 0;
public static final int STARTED = 1;
public static final int FINISHED = 2;
private int state;
private TestEventListenerImpl() {
state = INITIAL;
}
@Override
public void addError(junit.framework.Test test, Throwable error) {
}
@Override
public void addFailure(junit.framework.Test test, junit.framework.AssertionFailedError error) {
}
@Override
public void endTest(junit.framework.Test test) {
state = FINISHED;
}
@Override
public void startTest(junit.framework.Test test) {
state = STARTED;
}
public boolean isStarted() {
return state == STARTED;
}
public boolean isFinished() {
return state == FINISHED;
}
}
;
protected TestEventListenerImpl[] testExecutorListener;
protected TestExecutorWithClientSession[] testExecutorWithClientSession;
protected SynchronizedTestExecutor[] testThread;
protected class SynchronizedTesterImpl implements SynchronizedTester {
public static final int INITIAL = 0;
public static final int FINISHED = 1;
private int state;
private Throwable exception;
private SynchronizedTesterImpl() {
state = INITIAL;
}
@Override
public void finishedTest() {
state = FINISHED;
}
@Override
public void notifyException(Throwable exception) {
this.exception = exception;
}
public boolean isFinished() {
return state == FINISHED;
}
public Throwable getException() {
return exception;
}
}
;
protected SynchronizedTesterImpl[] testThreadListener;
protected Hashtable allowedExceptions;
private Session originalSession;
/**
* Default constructor for MultithreadTestCase
* Added to allow easier subclassing. This constructor should only be used by subclasses.
* addTests() must be called immediately after this constructor by the subclass
*/
protected MultithreadTestCase() {
super();
setDescription("Runs several tests simultaneously");
allowedExceptions = new Hashtable();
addAllowedException("org.eclipse.persistence.exceptions.OptimisticLockException");
}
// MultithreadTestCase runs simultaneously each of the TestCases
// passed in Vector tests.
public MultithreadTestCase(Vector test) {
this();
setTests(test);
}
// If an exception occur in one of concurrently run tests,
// MultithreadTestCase assigned this exception (and therefore status "Failed"),
// unless the exception is on allowedExceptions list (see verify()).
// By default, only org.eclipse.persistence.exceptions.OptimisticLockException
// is on this list.
// Use addAllowedException and removeAllowedException to add/remove
// an exception to allowedExceptions list.
public boolean addAllowedException(String exceptionClassName) {
try {
allowedExceptions.put(exceptionClassName, Class.forName(exceptionClassName));
return true;
} catch (ClassNotFoundException classNotFoundException) {
return false;
}
}
public void removeAllowedException(String exceptionClassName) {
allowedExceptions.remove(exceptionClassName);
}
/**
* Set the tests in this test case. Refactored for easier subclassing.
*/
public void setTests(Vector test) {
if ((test != null) && !test.isEmpty()) {
numberOfTests = test.size();
this.test = new TestCase[numberOfTests];
test.toArray(this.test);
testExecutorWithClientSession = new TestExecutorWithClientSession[numberOfTests];
testExecutorListener = new TestEventListenerImpl[numberOfTests];
testThread = new SynchronizedTestExecutor[numberOfTests];
testThreadListener = new SynchronizedTesterImpl[numberOfTests];
}
}
@Override
protected void setup() {
getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
setupSession();
for (int i = 0; i < numberOfTests; i++) {
// Need this for proper idention of while printing test results
test[i].setContainer(this);
// To run a test we need an executor.
// To run simultaneously several tests we need a personal
// executor for each of them.
// For each test an instance of a subclass of TestExecutor is created:
// TestExecutorWithClientSession:
// carries clientSession;
// readdresses some method calls to its parent - TestExecutor, disallows other ones;
testExecutorWithClientSession[i] = new TestExecutorWithClientSession(getExecutor());
// That's an optional TestExecutorListener.
// Currently not used.
testExecutorListener[i] = new TestEventListenerImpl();
testExecutorWithClientSession[i].setListener(testExecutorListener[i]);
// That's a ThreadListener - it receives call backs from
// the thread - currently used is testFinished notification.
testThreadListener[i] = new SynchronizedTesterImpl();
test[i].setExecutor(testExecutorWithClientSession[i]);
// That's a personal thread for the test
testThread[i] = new SynchronizedTestExecutor(testExecutorWithClientSession[i], test[i], testThreadListener[i]);
testThread[i].setName("Test Thread " + i);
}
}
protected void setupSession() {
originalSession = getSession();
Session newSession = setupNewSession(originalSession, useSequenceConnectionPool);
getExecutor().setSession(newSession);
}
// In order for MultithreadTestCase to run, it needs
// a ServerSession to be returned by TestExecutor.getSession().
// This method takes a session originally held by TestExecutor
// (typically DatabaseSession) - and returns the ServerSession
// which should be set as a Session into TestExecutor.
// The possible variants:
// case originalSession is a ServerSession - will use it as a new session;
// case originalSession is a ClientSession - will use its parent as a new session.
//
// This is a static method so that it could be used by other classes.
//
// Use resetOriginalSession before setting the originalSession
// back into TestExecutor.
static public Session setupNewSession(Session originalSession, boolean useSequenceConnectionPool) {
// Note that because ServerSession.isDatabaseSession() returns true
// it is important to call isServerSession() before
// isDatabaseSession() is called
if (originalSession.isServerSession()) {
return originalSession;
} else if (originalSession.isDatabaseSession()) {
// The following piece mostly copied from ClientServerTestModel
// The only thing added is initializePreallocatedSequences() -
// don't want to re-use the sequence numbers allocated by originalSession.
DatabaseSession databaseSession = (DatabaseSession)originalSession;
DatabaseLogin login = (DatabaseLogin)databaseSession.getLogin().clone();
databaseSession.getSequencingControl().initializePreallocated();
Server serverSession = new ServerSession(login, 5, 5);
serverSession.setSessionLog(databaseSession.getSessionLog());
if (useSequenceConnectionPool) {
serverSession.getSequencingControl().setShouldUseSeparateConnection(true);
} else {
serverSession.getSequencingControl().setShouldUseSeparateConnection(false);
}
serverSession.login();
Vector descriptors = new Vector();
for (Iterator<ClassDescriptor> iterator = databaseSession.getDescriptors().values().iterator();
iterator.hasNext();) {
descriptors.addElement(iterator.next());
}
serverSession.addDescriptors(descriptors);
return serverSession;
} else if (originalSession.isClientSession()) {
ClientSession clientSession = (ClientSession)originalSession;
ServerSession serverSession = clientSession.getParent();
return serverSession;
} else {
// Should never happen
return null;
}
}
// Note that currently there is nothing done
// to resolve possible deadlocks.
@Override
protected void test() {
//run test threads
for (int i = 0; i < numberOfTests; i++) {
testThread[i].start();
}
//waiting for test threads to complete
int numberOfCompletedTests;
do {
numberOfCompletedTests = 0;
for (int i = 0; i < numberOfTests; i++) {
if (testThreadListener[i].isFinished()) {
numberOfCompletedTests++;
}
}
} while (numberOfCompletedTests < numberOfTests);
}
@Override
protected void verify() {
EclipseLinkException exception = null;
for (int i = 0; (i < numberOfTests) && (exception == null); i++) {
exception = test[i].getTestResult().getException();
if (exception != null) {
for (Enumeration enumtr = allowedExceptions.elements();
enumtr.hasMoreElements() && (exception != null);) {
if (((Class)(enumtr.nextElement())).isInstance(exception)) {
exception = null;
}
}
if (exception != null) {
setTestException(exception);
}
}
}
}
// the originalSession != null check is needed
// in case reset is called more than once -
// which may happen in case of test failure due to
// the resent changes in AutoVerifyTestCase.
@Override
public void reset() {
getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
if (originalSession != null) {
resetSession();
}
}
protected void resetSession() {
resetOriginalSession(originalSession, getSession());
getExecutor().setSession(originalSession);
originalSession = null;
}
static public void resetOriginalSession(Session originalSession, Session newSession) {
// Note that because ServerSession.isDatabaseSession() returns true
// it is important to call isServerSession() before
// isDatabaseSession() is called
if (originalSession.isServerSession()) {
// Assuming that originalSession == newSession
// (see setNewSession(..))
return;
} else if (originalSession.isDatabaseSession()) {
((DatabaseSession)newSession).logout();
DatabaseSession databaseSession = (DatabaseSession)originalSession;
// Is this necessary? Don't know.
// Just copied it from ClientServerTestModel
databaseSession.logout();
databaseSession.login();
}
}
@Override
public void logResult(Writer log) {
super.logResult(log);
for (int i = 0; i < numberOfTests; i++) {
try {
log.write(org.eclipse.persistence.internal.helper.Helper.cr() + Helper.getTabs(getNestedCounter() + 1) + "Test Thread " + i);
} catch (IOException exception) {
}
test[i].logResult(log);
}
}
public void useSequenceConnectionPool() {
useSequenceConnectionPool = true;
}
}