package alma.acs.logging; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import junit.framework.TestCase; import org.omg.CORBA.Any; import org.omg.CORBA.ORB; import si.ijs.maci.Manager; import si.ijs.maci._ManagerStub; import alma.Logging.AcsLogServiceOperations; import alma.Logging.XmlLogRecord; import alma.acs.concurrent.DaemonThreadFactory; import alma.acs.logging.config.LogConfig; import alma.acs.logging.level.AcsLogLevelDefinition; import alma.acs.logging.mocks.LogEmptyImpl; /** * Tests remote logging, using mock objects, and therefore does not need a running ACS * which would not even exist in the strict module build order. */ public class RemoteLoggingTest extends TestCase { protected void setUp() throws Exception { System.out.println("\n------------ " + getName() + " --------------"); } /** * Tests the simple case where logging occurs after the call to * {@link ClientLogManager#initRemoteLogging(ORB, Manager, int, boolean)}. */ public void testSimpleRemoteLogging() throws Exception { ClientLogManagerStandalone clm = new ClientLogManagerStandalone(null); // sync call (waits till remote logging is initialized) initRemoteLogging(clm); Logger logger = clm.getLoggerForApplication("testNormalRemoteLogging", true); logger.info("A healthy info log"); logger.severe("And a severe log that actually is not severe"); clm.shutdown(true); } /** * Tests the situation in which the ClientLogManager is shut down before it has finished the * asynchronous <code>initRemoteLogging</code> call. * This can happen in a <code>ComponentClient</code>-based application that performs a short task. * <p> * It is important to make the stdout printing part of this test, * e.g. by using TAT without output suppression tricks, * because among other things we expect the string * <code>Will abort ClientLogManager#initRemoteLogging because remote logging seems no longer needed.</code> * when initRemoteLogging is interrupted by a shutdown. * <p> * The main test thread that logs some messages and the thread that calls initRemoteLogging * compete for the internal lock {@link ClientLogManager#logQueueLock}, whose functioning is being tested here. * <p> * <b>Unfortunately this cannot be totally deterministic, so we must accept occasional failures * of the kind that the output contains "Remote log: <Info .../>" instead of "Will abort ..." strings, or vice versa.</b> */ public void testConcurrentRemoteInitAndStop() throws InterruptedException { DaemonThreadFactory dtf = new DaemonThreadFactory(getName()); // we try this out for different simulated network delays and shutdown delays int[] networkDelays = {0, 2, 16, 21, 100}; // @TODO chose values that give deterministic results on most test machines. int[] shutdownDelays = {1, 20, 106}; // @TODO chose values that give deterministic results on most test machines. for (int networkDelay : networkDelays) { // shutdown delay is the time in milliseconds between the last log and the call to clientlogmanager#shutdown, // with the additional arrangement that initRemoteLogging was running when the last log was produced. for (int shutdownDelay : shutdownDelays) { System.out.println("\n*** Network delay = " + networkDelay + " and shutdown delay = " + shutdownDelay + " ***"); CountDownLatch syncOnPrepareRemoteLogging = new CountDownLatch(1); final ClientLogManagerStandalone clm = new ClientLogManagerStandalone(syncOnPrepareRemoteLogging); LogConfig logConfig = clm.getLogConfig(); logConfig.setDefaultMinLogLevel(AcsLogLevelDefinition.TRACE); logConfig.setDefaultMinLogLevelLocal(AcsLogLevelDefinition.TRACE); Logger logger = clm.getLoggerForApplication(getName(), true); // log something before we init the remote logging: logger.info("A healthy info log before initRemoteLogging ("+networkDelay+"/"+shutdownDelay+")"); Thread.sleep(2); // to keep these two logs in order, which makes manual ref file comparison easier. logger.fine("now that was a fine log before initRemoteLogging ("+networkDelay+"/"+shutdownDelay+")"); clm.setDelayMillis(networkDelay); // simulated network delay for initRemoteLogging-getLogService and write_records // call initRemoteLogging from a separate thread Thread initRemoteLoggingThread = dtf.newThread(new Runnable() { public void run() { initRemoteLogging(clm); } }); initRemoteLoggingThread.start(); // wait until this thread is actually running, which we check via notification from the ClientLogManager#prepareRemoteLogging method assertTrue("initRemoteLogging should have called prepareRemoteLogging by now...", syncOnPrepareRemoteLogging.await(10, TimeUnit.SECONDS)); // timeout should never apply, just used to stop the test if it gets messed up. logger.info("Another info log after initRemoteLogging ("+networkDelay+"/"+shutdownDelay+")"); // depending on the values of networkDelay and shutdownDelay, we may be calling shutdown while // our ClientLogManager is still delivering the log messages. Thread.sleep(shutdownDelay); clm.shutdown(true); // wait for the thread that called initRemoteLogging initRemoteLoggingThread.join(10000); // wait a bit more for the mock log dispatcher to print out its xml log record Thread.sleep(1500); } } } /////////////////////////////////////////////////////////// ////////////////// Helper methods /////////////////// /////////////////////////////////////////////////////////// /** * Calls {@linkplain ClientLogManagerStandalone#initRemoteLogging(ORB, Manager, int, boolean)} * with appropriate dummy parameters. * @param clm The instance to call initRemoteLogging on. * @return true if simulated remote logging was initialized successfully */ private boolean initRemoteLogging(ClientLogManagerStandalone clm) { // An unconfigured ORB will do, just needed to produce Any objects for sending remote logs. // There is however the ugly side effect that the constructor org.jacorb.orb.ORBSingleton // will use slf4j and thus call ClientLogManager#getAcsLogManager, so that we have another // instance of ClientLogManager in addition to our ClientLogManagerStandalone instance. ORB orb = ORB.init(); Manager managerDummy = new _ManagerStub(); // will only be used for != null check. return clm.initRemoteLogging(orb, managerDummy, 1, true); } /** * Modified {@link ClientLogManager} which skips the manager call and uses a mock Log service. * Remote communication delays are simulated, see {@link #setDelayMillis(long)}. */ private static class ClientLogManagerStandalone extends ClientLogManager { private final CountDownLatch syncOnPrepareRemoteLogging; private volatile long delayMillis = 100; /** * Mock impl of the Log service. All methods are total no-ops, * except for {@link LogOperations#write_records(Any[]) which prints the xml log records * contained in the given Any objects to stdout, and simulates network delay by sleeping * via a call to {@link #delay()}. */ private final AcsLogServiceOperations logServiceMock = new LogEmptyImpl() { public void write_records(Any[] records) { for (Any any : records) { // just print to stdout, to be verified by TAT System.out.println("Remote log: " + any.extract_string()); } delay(); } @Override public void writeRecords(XmlLogRecord[] xmlLogRecords) { for (XmlLogRecord record : xmlLogRecords) { // just print to stdout, to be verified by TAT System.out.println("Remote log: " + record.xml); } delay(); } }; /** * Constructor. * @param syncOnPrepareRemoteLogging * Optional synchronization aid (may be <code>null</code>). * Method {@linkplain #prepareRemoteLogging()} will call <code>countDown()</code> * to allow a test to wait until {@linkplain #initRemoteLogging(ORB, Manager, int, boolean)} is actually running * in cases where it gets started from a separate thread. * Note that the parent constructor will not call <code>countDown()</code> even though it calls <code>prepareRemoteLogging</code>, * because we only set the CountDownLatch after calling the parent ctor. Thus passing a <code>CountDownLatch(1)</code> will work. */ ClientLogManagerStandalone(CountDownLatch syncOnPrepareRemoteLogging) { super(); this.syncOnPrepareRemoteLogging = syncOnPrepareRemoteLogging; } /** * This is called by {@linkplain ClientLogManager#initRemoteLogging(ORB, Manager, int, boolean)} * and simulates the access to the Log service by sleeping via {@linkplain #delay()}. * @see alma.acs.logging.ClientLogManager#getLogService(si.ijs.maci.Manager, int) */ protected AcsLogServiceOperations getLogService(Manager manager, int managerHandle) { delay(); return logServiceMock; } /** * Sets the delay in milliseconds which subsequent calls to {@link #delay()} will sleep for. * Default is 100 ms if this method does not get called. */ void setDelayMillis(long delayMillis) { this.delayMillis = delayMillis; } /** * Sleeps for the time given in {@linkplain #setDelayMillis(long)}. */ void delay() { try { Thread.sleep(delayMillis); } catch (InterruptedException ex) { ex.printStackTrace(System.out); } } /** * Overloaded only to allow clients to sync with execution of {@link #initRemoteLogging(ORB, Manager, int, boolean)}. * @see #ClientLogManagerStandalone(CountDownLatch) */ protected void prepareRemoteLogging() { if (syncOnPrepareRemoteLogging != null) { syncOnPrepareRemoteLogging.countDown(); } super.prepareRemoteLogging(); } } }