| /* |
| * 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; |
| } |
| } |