/******************************************************************************* | |
* 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.tests.simultaneous; | |
import java.io.*; | |
import java.util.*; | |
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; | |
} | |
public void addError(junit.framework.Test test, Throwable error) { | |
} | |
public void addFailure(junit.framework.Test test, junit.framework.AssertionFailedError error) { | |
} | |
public void endTest(junit.framework.Test test) { | |
state = FINISHED; | |
} | |
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; | |
} | |
public void finishedTest() { | |
state = FINISHED; | |
} | |
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]; | |
} | |
} | |
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 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. | |
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); | |
} | |
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. | |
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(); | |
} | |
} | |
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; | |
} | |
} |