/** ** Copyright (C) SAS Institute, All rights reserved. ** General Public License: http://www.opensource.org/licenses/gpl-license.php **/ package com.jayway.android.robotium.remotecontrol.solo; import java.io.PrintStream; import java.util.Properties; import java.util.concurrent.TimeoutException; import org.safs.sockets.DebugListener; import org.safs.sockets.RemoteException; import org.safs.sockets.ShutdownInvocationException; import org.safs.sockets.SocketProtocol; /** * Normally, this class would not be used directly. Use the Solo class, instead. * Default abstract usage: * <p><pre> * SoloWorker solo = new SoloWorker(); * solo.setLogsInterface(alog); * solo.initialize(); * (use the API) * solo.shutdown(); * </pre> * @see Solo<br> * @author Carl Nagle, SAS Institute, Inc. * @since * <br>(LeiWang) Mar 09, 2012 Add methods to turn on/off debug log of protocol/runner. * Add method to shutdown remote service on device. * <br>(LeiWang) Mar 09, 2012 Move static method parseStringArrayList() to Message class. */ public class SoloWorker implements DebugListener{ public final String TAG = getClass().getSimpleName(); /** How long to wait for a remote connection before issuing timeout. Default 120 seconds. */ public static int default_connect_stimeout = 120; /** How long to wait for a READY signal before issuing timeout. Default 120 seconds. */ public static int default_ready_stimeout = 120; /** How long to wait for a RUNNING signal after dispatch before issuing timeout. Default 60 seconds. */ public static int default_running_stimeout = 60; /** How long to wait for a RESULT signal after dispatch before issuing timeout. Default 120 seconds. */ public static int default_result_stimeout = 120; /** How long to wait for remote shutdown confirmation after dispatch before aborting the wait. Default 7 seconds. */ public static int default_shutdown_stimeout = 7; /** Average Network TCP transaction latency allowance for sockets communications. Default 2 seconds. */ public static int tcp_delay = 2; //previous use of + 1 was insufficient in Solo subclass public static String listenername = "Solo"; /** Initializes to System.out */ public static PrintStream out = System.out; /** Initializes to System.err */ public static PrintStream err = System.err; protected LogsInterface log = null; protected SoloRemoteControl control = null; private int controllerPort = SocketProtocol.DEFAULT_CONTROLLER_PORT; private boolean portForwarding = true; public SoloWorker() { } /** * Set the LogsInterface to be used by the class instance. * This call also attempts to set the ProtocolRunner to use the same LogsInterface. * @param ilog * @see LogsInterface */ public void setLogsInterface(LogsInterface ilog){ log = ilog; try{ control.setLogsInterface(log);}catch(Exception x){} } /** *@see DebugListener */ public String getListenerName() {return listenername; } /** *@see DebugListener */ public void onReceiveDebug(String message) { debug(message); } /** * Output debug messages to our LogsInterface, or to our out PrintStream if the LogsInterface is not set. * @param message * @see #out */ protected void debug(String message) { try{log.debug(message);} catch(Exception x){ out.println(message); } } /** * Called internally by the initialize() routine to get the desired instance/subclass of * SoloRemoteControl. This routine returns a new instance of a SoloRemoteControl subclass * even if we already have a controller set. * @return new SoloRemoteControl(). Subclasses may wish to provide a different subclass of * SoloRemoteControl(). */ protected SoloRemoteControl createRemoteControl(){ return new SoloRemoteControl(); } /** * @return current SoloRemoteControl subclass or null if not set. */ public SoloRemoteControl getRemoteControl(){ return control; } /** * Set the SoloRemoteControl instance to be used by this worker. * We will not override an existing controller unless force = true. * @param controller * @param force true to overwrite an existing controller with a different one, or null. * @return calls {@link #getRemoteControl()} */ public SoloRemoteControl setRemoteControl(SoloRemoteControl controller, boolean force){ if(control == null || force){ control = controller; } return getRemoteControl(); } /** * Called to initialize RemoteControl communications with a remote client and get it to the Ready state. * However, the routine only performs this initialization if it is instancing a new remote controller. * If the remote controller already exists, then we must assume initialization of that remote control * object has opened or will happen elsewhere since it was not instanced by this class. * Initial default implementation performs: * <p><pre> * control = createRemoteControll(); * control.addListener(this); * control.setLogsInterface(log); * control.start(); * control.waitForRemoteConnected(default_connect_stimeout); * control.waitForRemoteReady(default_ready_stimeout); * </pre> * @throws RemoteException -- if there is a problem with RemoteControl initialization. * @throws TimeoutException -- if the initialization and remote connection does not complete in timeout period. * @throws ShutdownInvocationException -- if the remote client unexpectedly performs a shutdown. * @see #setRemoteControl(SoloRemoteControl, boolean) * @see SoloRemoteControl#waitForRemoteReady(int) * @see SoloRemoteControl#setLogsInterface(LogsInterface) */ public void initialize() throws RemoteException, TimeoutException, ShutdownInvocationException{ if(control == null){ control = createRemoteControl(); control.setPortForwarding(portForwarding); control.setControllerPort(controllerPort); control.addListener(this); control.setLogsInterface(log); control.start(); control.waitForRemoteConnected(default_connect_stimeout); control.waitForRemoteReady(default_ready_stimeout); } } /** * Set the controller port where we will connect for messenger service. * @param controllerPort */ public void setControllerPort(int controllerPort){ this.controllerPort = controllerPort; } /** * Set if we will forward 'controller port' to remote messenger service's port. * @param portForwarding */ public void setPortForwarding(boolean portForwarding){ this.portForwarding = portForwarding; } /** * Turn on/off the runner's debug message.<br> * This MUST be called after invoking {@link #initialize()}<br> * @param enableDebug */ public void turnRunnerDebug(boolean enableDebug){ String debugPrefix = TAG+".turnRunnerDebug() "; if(control==null || control.runner==null){ debug(debugPrefix+" you MUST call initialize() before calling this method."); }else{ control.runner._debugEnabled = enableDebug; } } /** * Turn on/off the protocol's debug message.<br> * This MUST be called after invoking {@link #initialize()}<br> * @param enableDebug */ public void turnProtocolDebug(boolean enableDebug){ String debugPrefix = TAG+".turnRunnerDebug() "; if(control==null || control.runner==null || control.runner.protocolserver==null){ debug(debugPrefix+" you MUST call initialize() before calling this method."); }else{ control.runner.protocolserver._debugEnabled = enableDebug; } } /** ": " */ public static String CAUSE_SEP = ": "; /** " OK " */ public static String PASS_SUFFIX = " OK "; /** "FAILED" */ public static String FAIL_SUFFIX = "FAILED"; /** * Set this value to false to bypass or ignore default failure processing. * This allows the API caller to handle the failures in their own way. * @see #processFailure(String, String) */ public boolean doProcessFailure = true; /** * Handle the reporting or logging of action or test failures. * <p> * This is enabled by default. Callers can override default failure processing by setting * doProcessFailure = false. * <p> * This implementation uses the LogsInterface that should be provided at or immediately * following the creation of the Class instance. Subclasses may wish to use true * jUnit reporting or other mechanisms. * <p> * If the LogsInterface call throws an Exception for any reason--including a NullPointerException * because it was never provided or initialized--the implementation will log to our err PrintStream * with the following format: cause +": "+ message * * @param cause -- Normally, the action or id of the call that generated the failure. * @param message -- The failure message provided for that action or id. If the message * is null the implementation will use FAIL_SUFFIX. * @see LogsInterface#fail(String, String) * @see #FAIL_SUFFIX */ public void processFailure(String action, String message){ if(doProcessFailure){ if(message == null) message = FAIL_SUFFIX; try{ log.fail(action, message);} catch(Exception x){ err.println(action +CAUSE_SEP+ message); } } } /** * Set this value to false to bypass or ignore default success processing. * This allows the API caller to handle success in their own way. * @see #processSuccess(String, String) */ public boolean doProcessSuccess = true; /** * Handle the reporting or logging of action or test success. * <p> * This is enabled by default. A truer jUnit experience can be achieved by setting * doHandleSuccess = false. * <p> * This implementation uses the LogsInterface that should be provided at or immediately * following the creation of the Class instance. Subclasses may wish to use true * jUnit reporting or other mechanisms. * <p> * If the LogsInterface call throws an Exception for any reason--including a NullPointerException * because it was never provided or initialized--the implementation will log to our out PrintStream * with the following format: cause +": "+ message * * @param cause -- Normally, the action or id of the call that generated the success. * @param message -- The success message provided for that action or id, if any. This can be * null. If the message is null this implementation will use PASS_SUFFIX. * @see #doHandleSuccess * @see LogsInterface#pass(String, String) * @see #PASS_SUFFIX */ public void processSuccess(String action, String message){ if(doProcessSuccess){ if(message == null) message = PASS_SUFFIX; try{ log.pass(action, message);} catch(Exception x){ out.println(action +CAUSE_SEP+ message); } } } /** * Initial default implementation performs: * <p><pre> * control.shutdown(); * This will stop the {@link SoloRemoteControlRunner} on the computer side. * </pre> * * <b>Note:</b> If you want to stop the remote service on the device side, <br> * you should call {@link #shutdownRemote()}, and you MUST call it before <br> * calling this {@link #shutdown()} method.<br> * * @see SoloRemoteControl#shutdown() * @see #shutdownRemote() */ public void shutdown(){ control.shutdown(); } /** * Initial default implementation performs: * <p><pre> * control.performRemoteShutdown(int,int,int); * </pre> * * <b>Note:</b> This method will stop the remote service on the device side, <br> * you MUST call it before calling {@link #shutdown()} method.<br> * * @see SoloRemoteControl#performRemoteShutdown(int, int, int) * @see #shutdown() */ public boolean shutdownRemote(){ String debugPrefix = TAG+".shutdownRemote() "; boolean remoteShutdown = false; try { control.performRemoteShutdown(default_ready_stimeout, default_running_stimeout, default_shutdown_stimeout); remoteShutdown = true; } catch (Exception e) { debug(debugPrefix+" During shutdown remote service, met Exception="+e.getMessage()); } return remoteShutdown; } protected Properties _props = new Properties(); /** * Prepare a dispatchProps object targeting a remote "instrument" command instead of a remote "solo" command. * @param command * @return Properties ready to be populated with command-specific parameters. */ protected Properties prepInstrumentDispatch(String command){ try{_props.clear();}catch(NullPointerException x){_props = new Properties();} _props.setProperty(Message.KEY_COMMAND, command); _props.setProperty(Message.KEY_TARGET, Message.target_instrument); return _props; } /** * Prepare a dispatchProps object targeting a remote "solo" command instead of a remote "instrument" command. * @param command * @return Properties ready to be populated with command-specific parameters. */ protected Properties prepSoloDispatch(String command){ try{_props.clear();}catch(NullPointerException x){_props = new Properties();} _props.setProperty(Message.KEY_COMMAND, command); _props.setProperty(Message.KEY_TARGET, Message.target_solo); return _props; } }