/*
* Created on 28/07/2006
*
* Copyright 2005-2010 Ignis Software Tools Ltd. All rights reserved.
*/
package jsystem.runner.remote;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import jsystem.framework.FrameworkOptions;
import jsystem.framework.JSystemProperties;
import jsystem.framework.common.CommonResources;
import jsystem.framework.fixture.FixtureManager;
import jsystem.framework.report.ExecutionListener;
import jsystem.framework.report.JSystemListeners;
import jsystem.framework.report.ListenerstManager;
import jsystem.framework.report.RunnerListenersManager;
import jsystem.framework.scenario.RunnerTest;
import jsystem.framework.scenario.RunningProperties;
import jsystem.framework.scenario.ScenariosManager;
import jsystem.runner.ErrorLevel;
import jsystem.runner.WaitDialog;
import jsystem.runner.WaitDialog.WaitDialogListner;
import jsystem.runner.agent.publisher.Publisher;
import jsystem.runner.agent.publisher.PublisherManager;
import jsystem.runner.agent.tests.PublishTest;
import jsystem.runner.remote.RemoteTestRunner.RemoteMessage;
import jsystem.utils.StringUtils;
import jsystem.utils.exec.Command;
import jsystem.utils.exec.Execute;
/**
* Used to execute and manage a remote test environment. A remote java vm will
* be executed, the remote vm will connect to this process via object stream
* using socket. The remote side used to execute the tests is ant.
*
* @author guy.arieli TODO: remote executor does not work with flow control !
*/
public class RemoteExecutorImpl implements RemoteExecutor {
private static Logger log = Logger.getLogger(RemoteExecutorImpl.class.getName());
/**
* use to send report message
*/
private JSystemListeners reporter;
/**
* The object ouput steam. This stream will be used to send messages to the
* test vm.
*/
private ObjectOutputStream out;
/**
* The object input stream. This stream will be used to recieve messages
* from the test vm.
*/
private ObjectInputStream in;
/**
* The socket the message will be send on
*/
private Socket socket;
/**
* The server socket
*/
private ServerSocket ss = null;
/**
* The thread that read the test vm messages
*/
Thread reader;
/**
* The command to be executed
*/
Command cmd = null;
/**
* If true the scenario is running
*/
private volatile boolean running = false;
/**
* Indicate that the run was interrupted by the user
*/
private volatile boolean interrupted = false;
/**
* Notify on run end
*/
private ExecutionListener runEndListener = null;
/**
* if set to true will print the send and received messages
*/
boolean debugMessages = false;
boolean firstMessage = true;
boolean testStarted = false;
public RemoteExecutorImpl() {
reporter = RunnerListenersManager.getInstance();
}
/**
* Set the run end listener
*/
public void setRunEndListener(ExecutionListener runEndListener) {
this.runEndListener = runEndListener;
}
/*
* (non-Javadoc)
*
* @see jsystem.runner.remote.RemoteExecutor#connect()
*/
public void run(String antFile, String[] targets, Properties additional) throws Exception {
if (!(new File(antFile).exists())) {
throw new Exception("Scenario file " + antFile + " was not found");
}
int usedPort;
/*
* init server socket on any available port the remote vm will use this
* port to connect
*/
ss = new ServerSocket(0);
usedPort = ss.getLocalPort();
if (targets == null) {
targets = new String[0];
}
/*
* If configured to work in test.debug=true the execution of the tests
* will block The user will be prompt to execute the RemoteTestRunner
* (test vm) manually with the information as execution parameters:
* -port 1999 -host 127.0.0.1
*/
String vmParams = JSystemProperties.getInstance().getPreference(FrameworkOptions.TEST_VM_PARMS);
boolean testDebug = "true".equals(JSystemProperties.getInstance().getPreference(FrameworkOptions.TESTS_DEBUG));
String waitMessage;
if (testDebug || (vmParams != null && vmParams.contains("-Xdebug"))) {
waitMessage = "Waiting for remote debugging connection";
} else {
waitMessage = "Wait for remote VM";
}
File antHome = CommonResources.getAntDirectory();
WaitDialog.launchWaitDialog(waitMessage, new WaitDialogListner() {
@Override
public void cancel() {
interrupted = true;
if (ss != null) {
try {
// ITAI: This will cause the socket to throw exception
// and
// release the blocking state
ss.close();
} catch (IOException e) {
// Don't care
}
}
try {
// ITAI: Needs to give time for the ScenarioExecutor to get
// to the
// waitForRunEnd method.
Thread.sleep(100);
} catch (InterruptedException e) {
}
// ITAI: Telling everyone that the execution was ended.
runEndListener.endRun();
runEndListener.remoteExit();
// Let's close everything.
close();
}
});
File antLauncher = new File(antHome + File.separator + "lib", "ant-launcher.jar");
String[] vmParamsArr = new String[0];
if (vmParams != null) {
// replaces socket number templates with free socket numbers
vmParams = new TestVMParamsUtil().relpaceSocketNumber(vmParams);
vmParamsArr = vmParams.split(" ");
}
cmd = new Command();
ArrayList<String> cmdStringArray = new ArrayList<String>();
cmdStringArray.add(System.getProperty("java.home") + File.separatorChar + "bin" + File.separatorChar + "java");
// set up debug parameters for remote debugging via eclipse
if (testDebug) {
// see http://www.eclipsezone.com/eclipse/forums/t53459.html
cmdStringArray.add("-Xdebug");
cmdStringArray.add("-Xnoagent");
cmdStringArray.add("-Djava.compiler=NONE");
cmdStringArray.add("-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y");
}
cmdStringArray.add("-D" + RunningProperties.CURRENT_FIXTURE_BASE + "="
+ FixtureManager.getInstance().getCurrentFixture());
cmdStringArray.add("-D" + RunningProperties.CURRENT_SCENARIO_NAME + "="
+ ScenariosManager.getInstance().getCurrentScenario().getName());
cmdStringArray.add("-D" + RunningProperties.FIXTURE_DISABLE_TAG + "="
+ FixtureManager.getInstance().isDisableFixture());
cmdStringArray.add("-D" + RunningProperties.RUNNER_EXIST + "=true");
cmdStringArray.add("-D" + RunningProperties.JSYSTEM_AGENT + "="
+ System.getProperty(RunningProperties.JSYSTEM_AGENT));
for (int i = 0; i < vmParamsArr.length; i++) {
cmdStringArray.add(vmParamsArr[i]);
}
cmdStringArray.add("-classpath");
cmdStringArray.add(antLauncher.getAbsolutePath());
if (additional != null) {
Enumeration<Object> enum1 = additional.keys();
while (enum1.hasMoreElements()) {
String key = (String) enum1.nextElement();
String value = additional.getProperty(key);
cmdStringArray.add("-D" + key + "=" + value);
}
}
cmdStringArray.add("-Dant.home=" + antHome.getAbsolutePath());
cmdStringArray.add("org.apache.tools.ant.launch.Launcher");
cmdStringArray.add("-listener");
cmdStringArray.add(RemoteTestRunner.class.getName());
cmdStringArray.add("-cp");
cmdStringArray.add(System.getProperty("java.class.path"));
cmdStringArray.add("-buildfile");
cmdStringArray.add(antFile);
cmdStringArray.add("-D" + RunningProperties.USER_DIR + "=" + System.getProperty("user.dir"));
cmdStringArray.add("-D" + RunningProperties.SCENARIO_BASE + "=" + JSystemProperties.getCurrentTestsPath());
for (int i = 0; i < targets.length; i++) {
cmdStringArray.add(targets[i]);
}
cmd.setCmd((String[]) cmdStringArray.toArray(new String[0]));
cmd.setEnvParams(new String[] { RunningProperties.RUNNER_PORT + "=" + usedPort,
RunningProperties.RUNNER_HOST + "=" + "127.0.0.1", RunningProperties.ANT_EXECUTOR + "=" + "true" });
String cmdString = cmd.getCommandAsString();
log.fine(cmdString);
cmd.setDir(new File(System.getProperty("user.dir")));
Execute.execute(cmd, false, true, false);
try {
socket = ss.accept();
} catch (SocketException e) {
// ITAI: This will happen if the execution was cancelled by th user.
log.info("Execution was cancelled by the user ");
return;
}
in = new ObjectInputStream(socket.getInputStream());
out = new ObjectOutputStream(socket.getOutputStream());
running = true;
closed = false;
reader = new ReaderThread();
reader.start();
}
private class ReaderThread extends Thread {
public ReaderThread() {
super("RemoteExecuterImpl-ReaderThread");
}
public void run() {
try {
while (true) {
Message m = (Message) in.readObject();
if (firstMessage) {
WaitDialog.endWaitDialog();
firstMessage = false;
}
if (debugMessages) {
System.err.println("Rcv: R<--T " + m.getType().name());
}
if (m == null) {
if (running) {
RemoteExecutorImpl.this.close();
runEndListener.endRun();
notifyOnError("Remote Execution Error", "Remote side was closed unexpectedly",
ErrorLevel.Error);
}
return;
}
switch (m.getType()) {
case M_FLUSH_REPORTER:
ListenerstManager.getInstance().flushReporters();
break;
case M_INIT_REPORTER:
ListenerstManager.getInstance().initReporters();
break;
case M_REPORT:
if (!running) {
return;
}
try {
reporter.report(m.getField(0), m.getField(1), Integer.parseInt(m.getField(2)), Boolean
.valueOf(m.getField(3)).booleanValue(), Boolean.valueOf(m.getField(4))
.booleanValue(), Boolean.valueOf(m.getField(5)).booleanValue(),
Boolean.valueOf(m.getField(6)).booleanValue(), Long.parseLong(m.getField(7)));
} catch (Throwable t) {
throw new Exception("Fail to report:\n" + StringUtils.getStackTrace(t) + "\n"
+ m.toString());
}
break;
case M_LEVEL:
if (!running) {
return;
}
try {
reporter.startLevel(m.getField(0), Integer.parseInt(m.getField(1)));
} catch (Throwable t) {
throw new Exception("Fail to start a new Level:\n" + StringUtils.getStackTrace(t) + "\n"
+ m.toString());
}
break;
case M_STOP_LEVEL:
if (!running) {
return;
}
try {
reporter.stopLevel();
} catch (Throwable t) {
throw new Exception("Failed to stop level", t);
}
break;
case M_CLOSE_ALL_LEVELS:
if (!running) {
return;
}
try {
reporter.closeAllLevels();
} catch (Throwable t) {
throw new Exception("Fail to close all Levels:\n" + StringUtils.getStackTrace(t) + "\n"
+ m.toString());
}
break;
case M_SAVE_FILE:
reporter.saveFile(m.getField(0), StringUtils.stringToBytes(m.getField(1)));
break;
case M_START_REPORT:
reporter.startReport(m.getField(0), m.getField(1), m.getField(2), m.getField(3));
break;
case M_END_REPORT:
reporter.endReport(m.getField(0), m.getField(1));
break;
case M_SET_DATA:
reporter.setData(m.getField(0));
break;
case M_SET_FAIL_TO_PASS:
reporter.setFailToPass(Boolean.valueOf(m.getField(0)).booleanValue());
break;
case M_SET_FAIL_TO_WARNING:
reporter.setFailToWarning(Boolean.valueOf(m.getField(0)).booleanValue());
break;
case M_SET_SILENT:
reporter.setSilent(Boolean.valueOf(m.getField(0)).booleanValue());
break;
case M_SET_TIME_STAMP:
reporter.setTimeStamp(Boolean.valueOf(m.getField(0)).booleanValue());
break;
case M_STEP:
reporter.step(m.getField(0));
break;
case M_TEST_START:
if (!running) {
return;
}
String fullUuid = m.getField(0);
RunnerTest rt = ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(fullUuid);
reporter.startTest(rt.getTest());
testStarted = true;
break;
case M_TEST_END:
if (!running) {
return;
}
fullUuid = m.getField(0);
reporter.endTest(ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(fullUuid).getTest());
break;
case M_ADD_ERROR:
reporter.addError(
ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(m.getField(0)).getTest(), m.getField(1), m.getField(2));
break;
case M_ADD_FAILURE:
reporter.addFailure(
ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(m.getField(0)).getTest(), m.getField(1), m.getField(2),
Boolean.valueOf(m.getField(3)).booleanValue());
break;
case M_FIXTURE_START:
reporter.startFixturring();
break;
case M_FIXTURE_END:
reporter.endFixturring();
break;
case M_FIXTURE_ABOUT:
reporter.aboutToChangeTo(FixtureManager.getInstance().getFixture(m.getField(0)));
break;
case M_FIXTURE_CHANGED:
FixtureManager.getInstance().setCurrentFixture(m.getField(0));
reporter.fixtureChanged(FixtureManager.getInstance().getFixture(m.getField(0)));
break;
case M_OPERATION_FAIL:
notifyOnError(m.getField(0), m.getField(1), ErrorLevel.Error);
break;
case M_SUT_CHANGED:
log.fine("got change sut message");
reporter.sutChanged(m.getField(0));
break;
case M_PAUSED:
log.fine("got pause message");
runEndListener.remotePause();
break;
case M_EXIT:
log.fine("got exit message");
runEndListener.remoteExit();
close();
break;
case M_SAVE_STATE:
log.fine("got save state message");
fullUuid = m.getField(0);
reporter.saveState(ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(fullUuid).getTest());
break;
case M_CONTAINER_PROPERTY:
log.fine("got set container props");
String level = m.getField(0);
int anLevel = 0;
try {
anLevel = Integer.parseInt(level);
} catch (Exception e) {
}
String key = m.getField(1);
String val = m.getField(2);
reporter.setContainerProperties(anLevel, key, val);
break;
case M_ANT_MESSAGE_LOGED:
// TODO: log the message to log file
// log.log(Level.WARNING, m.getField(0));
break;
case M_BUILD_START:
break;
case M_PROPERTY:
// adding a property
if (!running) {
return;
}
try {
reporter.addProperty(m.getField(0), m.getField(1));
} catch (Throwable t) {
throw new Exception("Fail to report:\n" + StringUtils.getStackTrace(t) + "\n"
+ m.toString());
}
break;
case M_BUILD_FINISH:
log.fine("got build finished message");
interrupted = true;
runEndListener.endRun();
break;
case M_SYNCH:
log.fine("Got M_SYNCH message");
Message mm = new Message();
mm.setType(RemoteMessage.M_SYNCHED);
sendMessage(mm);
log.fine("Sent M_SYNCHED id message");
break;
case M_TARGET_FINISH:
break;
case M_TARGET_START:
break;
case M_TASK_FINISH:
break;
case M_TASK_START:
break;
case M_ADD_WARNING:
reporter.addWarning(ScenariosManager.getInstance().getCurrentScenario()
.getRunnerTestByFullId(m.getField(0)).getTest());
break;
case M_SHOW_CONFIRM_DIALOG:
/*
* launch a show confirm dialog in the runner every
* thing will block to the approvel
*/
int mr = reporter.showConfirmDialog(m.getField(0), m.getField(1),
Integer.parseInt(m.getField(2)), Integer.parseInt(m.getField(3)));
/*
* Send the approve message to the test with return
* value
*/
Message ms = new Message();
ms.setType(RemoteMessage.M_SHOW_CONFIRM_DIALOG);
ms.addField(Integer.toString(mr));
sendMessage(ms);
break;
case M_CHECK_SYSTEM_OBJECT:
// SystemObjectCheckWindow.getInstance().setSysObjStatus(m.getField(0),
// SOCheckStatus.valueOf(m.getField(1)), m.getField(2));
break;
default:
System.out.println("Unkown message type local: " + m.getType());
}
}
} catch (Throwable e) {
if (!running || interrupted) {
return;
}
notifyOnError("Remote test execution fail", StringUtils.getStackTrace(e), ErrorLevel.Error);
runEndListener.executionEnded("");
}
}
}
/**
*
*/
private void notifyOnError(String title, String fullMessage, ErrorLevel errorLevel) {
log.severe(title + ": " + fullMessage);
runEndListener.errorOccured(title, fullMessage, errorLevel);
}
/**
* create properties from message this properties contains the
* Build,Description and Version properties for publish
*/
private HashMap<String, Object> createPropertiesForPublish(Message m) {
HashMap<String, Object> p = new HashMap<String, Object>();
// skipping the first field since it's the test Full ID
for (int i = 1; i < m.getFields().size(); i++) {
String[] fieldWithValue = (m.getField(i)).split(PublishTest.delimiter);
String field = fieldWithValue[0];
String value = "";
if (fieldWithValue.length == 2)
value = fieldWithValue[1];
System.out.println("Publish field , value: " + field + "," + value);
p.put(field, value);
}
return p;
}
private volatile boolean closed = false;
/*
* (non-Javadoc)
*
* @see jsystem.runner.remote.RemoteExecutor#stop()
*/
public synchronized void close() {
if (closed) {
return;
}
closed = true;
running = false;
runEndListener = null;
if (cmd != null) {
Process p = cmd.getProcess();
if (p != null) {
long startTime = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - startTime > 5000) {
break;
}
try {
p.exitValue();
break;
} catch (Throwable t) {
// ignore
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// ignore
}
}
p.destroy();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
// ignore
}
out = null;
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
// ignore
}
ss = null;
}
if (reader != null) {
reader.interrupt();
reader = null;
}
synchronized (this) {
notifyAll();
}
if (cmd != null) {
Process p = cmd.getProcess();
if (p != null) {
p.destroy();
}
}
}
public void exit() {
Message m = new Message();
m.setType(RemoteMessage.M_EXIT);
sendMessage(m);
log.fine("sent exit message");
}
public void interruptTest() {
interrupted = true;
Message m = new Message();
m.setType(RemoteMessage.M_INTERRUPT);
sendMessage(m);
log.fine("sent interrupt message");
if (cmd != null) {
cmd.getProcess().destroy();
}
}
public void pause() {
Message m = new Message();
m.setType(RemoteMessage.M_PAUSE);
sendMessage(m);
log.fine("sent pause message");
}
public void gracefulStop() {
Message m = new Message();
m.setType(RemoteMessage.M_GRACEFUL_STOP);
sendMessage(m);
log.fine("sent graceful stop message");
}
public void resume() {
Message m = new Message();
m.setType(RemoteMessage.M_RESUME);
sendMessage(m);
log.fine("sent resume message");
}
private synchronized void sendMessage(Message m) {
if (debugMessages) {
System.err.println("Snd: R-->T " + m.getType().name());
}
if (out == null) {
return;
}
try {
out.writeObject(m);
out.flush();
/*
* Fix the object output stream memory issue
*/
out.reset();
} catch (Exception e) {
if (!interrupted) {
Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Fail to send message", e);
}
}
}
public boolean isTestStarted() {
return testStarted;
}
}