/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2002
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.component.client;
import java.util.logging.Level;
import junit.framework.TestCase;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import com.cosylab.CDB.DAL;
import com.cosylab.CDB.DALHelper;
import si.ijs.maci.Client;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.acs.concurrent.DaemonThreadFactory;
import alma.acs.container.AcsManagerProxy;
import alma.acs.container.CleaningDaemonThreadFactory;
import alma.acs.container.ContainerServices;
import alma.acs.container.ContainerServicesImpl;
import alma.acs.container.corba.AcsCorba;
import alma.acs.logging.AcsLogger;
import alma.acs.logging.ClientLogManager;
import alma.acs.logging.engine.LogReceiver;
import alma.acs.util.ACSPorts;
import alma.alarmsystem.source.ACSAlarmSystemInterfaceFactory;
/**
* Base class for writing JUnit test clients for ACS components.
* Takes care of the communication with the ACS manager (local or provided in property <code>ACS.manager</code>).
*
* Provides the {@link ContainerServices}.
*
* @author hsommer Nov 21, 2002 5:53:05 PM
*/
public class ComponentClientTestCase extends TestCase
{
protected AcsCorba acsCorba;
private ContainerServicesImpl m_containerServices;
/**
* The thread factory used by <code>m_containerServices</code>.
*/
private CleaningDaemonThreadFactory m_threadFactory;
/**
* Special tests that need to call directly the manager API could use this proxy object.
* To be used sparingly, as we need to exercise (and extend if necessary) the regular
* classes such as ContainerServices.
*/
protected AcsManagerProxy m_acsManagerProxy;
protected AcsLogger m_logger;
private LogReceiver logReceiver;
/** from property ACS.manager, or defaults to localhost */
protected String m_managerLoc;
// manager client object
private ManagerClient managerClientImpl;
private Client m_managerClient;
private String m_namePrefix;
/**
* Subclasses must call this ctor.
* @param name the name used for the test case, and to talk with the ACS manager
* @throws Exception
*/
public ComponentClientTestCase(String name) throws Exception
{
super(name);
m_namePrefix = name;
}
/**
* Executes a single test method.
* Stray exceptions are logged using the test logger, so that they show in system logs.
* @see junit.framework.TestCase#runTest()
* @since ACS 6.0
*/
protected void runTest() throws Throwable {
try {
super.runTest();
}
catch (Throwable thr) {
if (m_logger != null) {
m_logger.log(Level.WARNING, "JUnit test error in " + getFullName(), thr);
}
throw thr;
}
}
/**
* Starts CORBA in the client process and connects to the manager and logger.
* <p>
* <b>Subclasses that override this method must call <code>super.setUp()</code>,
* likely before any other code in that method.</b>
*
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception
{
m_logger = ClientLogManager.getAcsLogManager().getLoggerForApplication(getFullName(), true);
m_logger.info("-------------------------------");
m_logger.info("ComponentClientTestCase#setUp()");
POA rootPOA = null;
try
{
acsCorba = new AcsCorba(m_logger);
rootPOA = acsCorba.initCorbaForClient(false);
connectToManager();
DAL cdb = DALHelper.narrow(m_acsManagerProxy.get_service("CDB", false));
m_threadFactory = new CleaningDaemonThreadFactory(m_namePrefix, m_logger);
m_containerServices = new ContainerServicesImpl(m_acsManagerProxy, cdb, rootPOA, acsCorba,
m_logger, m_acsManagerProxy.getManagerHandle(),
this.getClass().getName(), null, m_threadFactory) {
public AcsLogger getLogger() {
return m_logger;
}
};
managerClientImpl.setContainerServices(m_containerServices);
initRemoteLogging();
ACSAlarmSystemInterfaceFactory.init(m_containerServices);
}
catch (Exception ex1)
{
m_logger.log(Level.SEVERE, "failed to initialize the ORB, or connect to the ACS Manager, " +
"or to set up the container services.", ex1);
if (acsCorba != null) {
try {
acsCorba.shutdownORB(true, false);
acsCorba.doneCorba();
} catch (Exception ex2) {
// to JUnit we want to forward the original exception ex1,
// not any other exception from cleaning up the orb
// which we would not have done without the first exception.
// Thus we simply print ex2 but let ex1 fly
ex2.printStackTrace();
}
}
throw ex1;
}
}
/**
* Connects to the ACS Manager using {@link AcsManagerProxy}.
* @throws Exception
*/
protected void connectToManager() throws Exception
{
if (System.getProperty("ACS.manager") != null) {
m_managerLoc = System.getProperty("ACS.manager").trim();
}
else {
// default = localhost
String host = ACSPorts.getIP();
m_managerLoc = "corbaloc::" + host + ":" + ACSPorts.getManagerPort() + "/Manager";
}
managerClientImpl = new ManagerClient(getFullName(), m_logger)
{
public void disconnect()
{
m_logger.info("disconnected from manager");
m_acsManagerProxy.logoutFromManager();
m_acsManagerProxy = null;
throw new RuntimeException("disconnected from the manager");
}
};
ORB orb = acsCorba.getORB();
m_managerClient = managerClientImpl._this(orb);
m_acsManagerProxy = new AcsManagerProxy(m_managerLoc, orb, m_logger);
m_acsManagerProxy.loginToManager(m_managerClient, 1);
}
protected String getFullName() {
String fullName = m_namePrefix + "#" + getName();
return fullName;
}
/**
* Gives access to the {@link ContainerServices} interface.
* This class plays the part of the role of the Java container that has to do with
* providing explicit services to the component, or test case respectively.
* <p>
*
* @return ContainerServices
*/
protected ContainerServices getContainerServices()
{
return m_containerServices;
}
/**
* Releases all previously obtained components (using manager), logs out from the manager,
* and terminates the CORBA ORB.
* <p>
* <b>Subclasses that override this method must call <code>super.tearDown()</code>, likely after any other code in that method.</b>
*
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
try {
m_acsManagerProxy.shutdownNotify();
m_containerServices.releaseAllComponents();
ACSAlarmSystemInterfaceFactory.done();
if (logReceiver != null && logReceiver.isInitialized()) {
logReceiver.stop();
}
m_containerServices.cleanUp();
m_acsManagerProxy.logoutFromManager();
ClientLogManager.getAcsLogManager().shutdown(true);
}
catch (Exception e) {
System.err.println("Exception in tearDown: ");
e.printStackTrace(System.err);
throw e;
}
finally {
m_threadFactory.cleanUp();
if (acsCorba != null) {
// @todo investigate COMP-2632 which happened here.
// Check if the wait_for_completion is buggy and returns too early.
// Then the overlapping call to doneCorba() ( -> orb.destroy()) would block this thread,
// and with bad timing luck it could block *after* the first shutdown called "shutdown_synch.notifyAll()"
// in ORB#shutdown, which could explain that the main thread hangs there forever.
acsCorba.shutdownORB(true, false);
// as a workaround for this problem, for now we run this async with a timeout
Thread destroyThread = (new DaemonThreadFactory("OrbDestroy")).newThread(new Runnable() {
public void run() {
acsCorba.doneCorba();
}
});
destroyThread.start();
destroyThread.join(20000);
}
// just in case... should give the OS time to reclaim ORB ports and so on
Thread.sleep(1000);
}
}
/**
* Sets up the test client logger(s) to send log records to the remote log service.
* Only one attempt to connect to the remote logger is made.
* If it fails, remote logging will be disabled.
* <p>
* Override this method to prevent remote logging.
*/
protected void initRemoteLogging()
{
String errMsg = null;
try {
if (!ClientLogManager.getAcsLogManager().initRemoteLogging(acsCorba.getORB(),
m_acsManagerProxy.getManager(), m_acsManagerProxy.getManagerHandle(), false) ) {
errMsg = "ACS central logging not available. ";
}
}
catch (Throwable thr) {
errMsg = "ACS central logging not available. Failed with unexpected error " + thr.toString();
}
if (errMsg != null) {
// if we can't get remote logging, then we disable it (saves memory to not add to the log record queue)
ClientLogManager.getAcsLogManager().suppressRemoteLogging();
m_logger.warning(errMsg);
}
}
/**
* Gets a {@link LogReceiver} which can be used
* to verify log messages from both local and remote processes.
* The returned <code>LogReceiver</code> is already initialized.
* If initialization fails, an exception is thrown.
* <p>
* To receive logs from the log service, use either
* {@link LogReceiver#getLogQueue()} or {@link LogReceiver#startCaptureLogs(java.io.PrintWriter)}.
*
* @throws AcsJContainerServicesEx if the LogReceiver fails to initialize within 20 seconds.
*/
protected LogReceiver getLogReceiver() throws AcsJContainerServicesEx {
if (logReceiver == null) {
boolean initOk = false;
try {
logReceiver = new LogReceiver();
// logReceiver.setVerbose(true);
initOk = logReceiver.initialize(acsCorba.getORB(), m_acsManagerProxy.getManager(), 20);
}
catch (Throwable thr) {
AcsJContainerServicesEx ex = new AcsJContainerServicesEx(thr);
ex.setContextInfo("Failed to obtain an initialized LogReceiver.");
throw ex;
}
if (!initOk) {
AcsJContainerServicesEx ex = new AcsJContainerServicesEx();
ex.setContextInfo("LogReceiver failed to initialize within 20 seconds.");
throw ex;
}
}
return logReceiver;
}
}