/*
* 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.MasterComponentImpl.statemachine;
import java.util.logging.Logger;
import junit.framework.TestCase;
import alma.ACS.MasterComponentImpl.StateChangeSemaphore;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.acs.concurrent.DaemonThreadFactory;
import alma.acs.genfw.runtime.sm.AcsState;
import alma.acs.genfw.runtime.sm.AcsStateActionException;
import alma.acs.genfw.runtime.sm.AcsStateChangeListener;
import alma.acs.genfw.runtime.sm.AcsStateUtil;
import alma.acs.logging.AcsLogger;
/**
* Tests the state machine from <code>alma.ACS.MasterComponentImpl.statemachine</code>
* without the Master component on top of it. No running ACS needed.
*
* @author hsommer
* created Mar 3, 2004 3:27:56 PM
*/
public class StateMachineTest extends TestCase implements AcsStateChangeListener
{
private AlmaSubsystemContext m_context;
private DummyActionImpl m_actionImpl;
private AcsLogger m_logger;
private MyStateChangeSemaphore m_sync;
public void setUp() {
m_logger = AcsLogger.createUnconfiguredLogger("StateMachineTest", null);
m_actionImpl = new DummyActionImpl(m_logger);
m_context = new AlmaSubsystemContext(m_actionImpl, m_logger, new DaemonThreadFactory());
m_context.addAcsStateChangeListener(this);
m_sync = new MyStateChangeSemaphore(m_logger);
m_context.addAcsStateChangeListener(m_sync);
}
/**
* @see alma.ACS.MasterComponentImpl.statemachine.AcsStateChangeListener#stateChangedNotify(alma.ACS.MasterComponentImpl.statemachine.AcsState[], alma.ACS.MasterComponentImpl.statemachine.AcsState[])
*/
public void stateChangedNotify(AcsState[] oldStateHierarchy, AcsState[] currentStateHierarchy)
{
String oldHi = AcsStateUtil.stateHierarchyToString(oldStateHierarchy);
String newHi = AcsStateUtil.stateHierarchyToString(currentStateHierarchy);
m_logger.info("state machine switched from " + oldHi + " to " + newHi);
}
public void testLegalLifecycle() throws Exception
{
final int n = 20;
m_logger.info("============ starting testLegalLifecycle with " + n + " iterations ===========");
for (int i=0; i< n; i++) {
assertStateHierarchy("AVAILABLE/OFFLINE/SHUTDOWN");
m_logger.info("---> Event initPass1 will be sent.");
m_sync.reset();
m_context.initPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/PREINITIALIZED");
m_logger.info("---> Event initPass2 will be sent.");
m_sync.reset();
m_context.initPass2();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/ONLINE");
m_logger.info("---> Event start will be sent.");
m_context.start();
assertStateHierarchy("AVAILABLE/OPERATIONAL");
m_logger.info("---> Event start will be sent again.");
m_context.start();
assertStateHierarchy("AVAILABLE/OPERATIONAL");
m_logger.info("---> Event stop will be sent.");
m_context.stop();
assertStateHierarchy("AVAILABLE/ONLINE");
m_logger.info("---> Event shutdownPass1 will be sent.");
m_sync.reset();
m_context.shutdownPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/PRESHUTDOWN");
m_logger.info("---> Event shutdownPass2 will be sent.");
m_sync.reset();
m_context.shutdownPass2();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/SHUTDOWN");
}
m_logger.info("============ testLegalLifecycle successful! ===========\n");
}
public void testIllegalEvent() throws Exception {
m_logger.info("============ starting testIllegalEvent ===========");
m_sync.reset();
m_context.initPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/PREINITIALIZED");
try {
// illegal event "start" in "AVAILABLE/OFFLINE/PREINITIALIZED"
m_context.start();
fail("AcsJIllegalStateEventEx expected!");
}
catch (AcsJIllegalStateEventEx e) {
assertEquals("OFFLINE", e.getState());
assertEquals("start", e.getEvent());
}
assertStateHierarchy("AVAILABLE/OFFLINE/PREINITIALIZED");
m_logger.info("============ testIllegalEvent successful! ===========\n");
}
public void testActionException() throws Exception
{
m_logger.info("============ starting testActionException ===========");
m_actionImpl.throwExInInitPass1(true); // we enforce the exception
m_sync.reset();
m_context.initPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/ERROR");
try {
m_context.initPass2();
fail("AcsJIllegalStateEventEx expected, with state machine in ERROR state");
}
catch (AcsJIllegalStateEventEx e) {
assertEquals("initPass2", e.getEvent());
assertEquals("ERROR", e.getState());
}
// let's get out of error again...
m_context.shutdownPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/PRESHUTDOWN");
m_context.shutdownPass2();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/SHUTDOWN");
m_logger.info("============ testActionException successful! ===========\n");
}
public void testReentryToCompositeState() throws Exception {
assertStateHierarchy("AVAILABLE/OFFLINE/SHUTDOWN");
m_logger.info("---> Event initPass1 will be sent.");
m_sync.reset();
m_context.initPass1();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/OFFLINE/PREINITIALIZED");
m_logger.info("---> Event initPass2 will be sent.");
m_sync.reset();
m_context.initPass2();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/ONLINE");
m_logger.info("---> Event reinit will be sent.");
assertEquals(0, m_actionImpl.getCallsToReinit());
m_sync.reset();
m_context.reinit();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/ONLINE");
Thread.sleep(200); // for the lack of sync'ing with the reinit action method
assertEquals(1, m_actionImpl.getCallsToReinit());
// do another reinit. There used to be a bug (COMP-1776) with the reinit action method not called again,
// because from the point of view of its composite state (OFFLINE), the substate reinit did not change.
m_logger.info("---> Event reinit will be sent again.");
m_sync.reset();
m_context.reinit();
m_sync.waitForStateChanges(2);
assertStateHierarchy("AVAILABLE/ONLINE");
Thread.sleep(200); // for the lack of sync'ing with the reinit action method
assertEquals(2, m_actionImpl.getCallsToReinit());
}
private void assertStateHierarchy(String expectedPath) {
AcsState[] actualHierarchy = m_context.getCurrentTopLevelState().getStateHierarchy();
String actualPath = AcsStateUtil.stateHierarchyToString(actualHierarchy);
assertEquals("current state is not as expected!", expectedPath, actualPath);
}
/**
* Test implementation of the state machine's action interface.
* Methods will be called by the state machine.
*/
private static class DummyActionImpl implements AlmaSubsystemActions
{
private Logger m_actionLogger;
private boolean m_throwExInInitPass1;
private int callsToReinit;
DummyActionImpl(Logger logger) {
m_actionLogger = logger;
m_throwExInInitPass1 = false;
}
/**
* Determines the optional test feature of letting {@link #initSubsysPass1()}
* throw an exception.
*/
void throwExInInitPass1(boolean throwEx) {
m_throwExInInitPass1 = throwEx;
}
/**
* Allows the tests to check how often reinit has been called.
* @TODO: better use a real sync mechanism so that test can wait till the method was called, or fail with a time out.
*/
int getCallsToReinit() {
return callsToReinit;
}
public synchronized void initSubsysPass1() throws AcsStateActionException {
if (m_throwExInInitPass1) {
log("initSubsysPass1 (will throw AcsStateActionException)");
throw new AcsStateActionException("sorry, just a test: initPass1 failed!");
}
else {
log("initSubsysPass1");
}
}
public synchronized void initSubsysPass2() {
log("initSubsysPass2");
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void reinitSubsystem() {
callsToReinit++;
log("reinitSubsystem");
}
public synchronized void shutDownSubsysPass1() {
log("shutDownSubsysPass1");
}
public synchronized void shutDownSubsysPass2() {
log("shutDownSubsysPass2");
}
private void log(String action) {
m_actionLogger.info("*** action " + action + " called. ***");
}
}
private static class MyStateChangeSemaphore extends StateChangeSemaphore implements AcsStateChangeListener
{
MyStateChangeSemaphore(Logger logger) {
super(logger);
}
public synchronized void stateChangedNotify(AcsState[] oldStateHierarchy, AcsState[] currentStateHierarchy)
{
stateChangedNotify();
}
}
}