/*
* Copyright 2005-2010 Ignis Software Tools Ltd. All rights reserved.
*/
package jsystem.runner.agent.server;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import jsystem.extensions.report.html.HtmlCodeWriter;
import jsystem.framework.FrameworkOptions;
import jsystem.framework.JSystemProperties;
import jsystem.framework.RunProperties;
import jsystem.framework.RunnerStatePersistencyManager;
import jsystem.framework.fixture.FixtureManager;
import jsystem.framework.report.RunnerListenersManager;
import jsystem.framework.scenario.JTest;
import jsystem.framework.scenario.Scenario;
import jsystem.framework.scenario.ScenarioHelpers;
import jsystem.framework.scenario.ScenariosManager;
import jsystem.framework.sut.SutFactory;
import jsystem.runner.loader.LoadersManager;
import jsystem.runner.projectsync.AutomationProjectUtils;
import jsystem.runner.projectsync.MD5Calculator;
import jsystem.runner.remote.ScenarioExecutor;
import jsystem.utils.ClassSearchUtil;
import jsystem.utils.FileUtils;
import jsystem.utils.StringUtils;
/**
* Agent execution engine.<br>
*
* @see RunnerEngine
* @author goland
*/
public class RunnerEngineImpl implements RunnerEngine {
private ScenarioExecutor executor;
/**
* Tracks engine execution state
*/
private ExecutionStateListener executionStateListener;
private static Logger log = Logger.getLogger(RunnerEngineImpl.class.getName());
/**
* Single point where project path properties are set.
*
* @param testClassesPAth
*/
public static void setTestsPath(String testClassesPath) {
JSystemProperties.getInstance().setPreference(FrameworkOptions.TESTS_CLASS_FOLDER, testClassesPath);
File parent = null;
try {
parent = new File(testClassesPath).getCanonicalFile().getParentFile();
} catch (IOException e) {
log.log(Level.WARNING,"Failed to get parent of " + testClassesPath);
}
if (parent != null) {
File testsSrcFolder = new File(parent, "tests");
File resourcesSrcFolder = new File(parent,"tests");
if (!testsSrcFolder.exists()) {
//ITAI: This is adaptation to Maven project structure.
testsSrcFolder = new File(parent.getParentFile(), "src/main/java");
if (!testsSrcFolder.exists()) {
log.log(Level.WARNING, "Tests source folder doesn't exist. " + testsSrcFolder.getAbsolutePath());
}
resourcesSrcFolder = new File(parent.getParentFile(), "src/main/resources");
if (!resourcesSrcFolder.exists()) {
log.log(Level.WARNING, "Tests source folder doesn't exist. " + testsSrcFolder.getAbsolutePath());
}
}
JSystemProperties.getInstance().setPreference(FrameworkOptions.TESTS_SOURCE_FOLDER,
testsSrcFolder.getPath());
JSystemProperties.getInstance().setPreference(FrameworkOptions.RESOURCES_SOURCE_FOLDER,
resourcesSrcFolder.getPath());
}
}
/**
*/
public RunnerEngineImpl() throws Exception {
executor = new ScenarioExecutor();
redirectOutputStream();
RunnerListenersManager.getInstance().addListener(new ActiveTestStateListener());
executionStateListener = new ExecutionStateListener();
RunnerListenersManager.getInstance().addListener(executionStateListener);
}
/**
* @see RunnerEngine#getId()
*/
public String getId() {
return "local";
}
/**
* @see RunnerEngine#changeProject(String)
*/
public void changeProject(String classesPath) throws Exception {
File testsClassesPathFile = new File(classesPath);
if (!testsClassesPathFile.exists()) {
throw new Exception("Selected automation project folder doesn't exist. "
+ testsClassesPathFile.getAbsolutePath());
}
if (!testsClassesPathFile.isDirectory()) {
throw new Exception("Selected automation project path should be a folder. "
+ testsClassesPathFile.getAbsolutePath());
}
setTestsPath(testsClassesPathFile.getPath());
JSystemProperties.getInstance().setPreference(FrameworkOptions.CURRENT_SCENARIO, "");
ScenariosManager.getInstance().setScenariosDirectoryFiles(new File(testsClassesPathFile.getPath()));
refresh();
Scenario scenario = ScenariosManager.getInstance().getScenario(null);
setActiveScenario(scenario);
}
/**
* Checks whether engine was signaled to start execution upon start<br>
* if so, starts executing current scenario. This method was written to
* support continuous execution of scenario after restart of the engine.
*
* should be called by the agent before the {@link #init()} method. (since
* the {@link #init()} method sets engine state to idle.
*
* @see RunnerStatePersistencyManager
*/
public void checkAndRunOnStart() throws Exception {
Scenario s = null;
// if we changes scenario state, let's reverse it back
int[] enabledTests = RunnerStatePersistencyManager.getInstance().getEnabledTests(true);
if (enabledTests != null) {
s = ScenariosManager.getInstance().getCurrentScenario();
s.loadParametersAndValues();
s.setEnabledTestsIndexes(enabledTests);
s.save();
}
// check whether to execute
if (!RunnerStatePersistencyManager.getInstance().getRunOnStart()) {
log.info("Engine was not set to start running.");
return;
}
// turn off the flag so next time engine will start, it will not start
// running.
RunnerStatePersistencyManager.getInstance().setRunOnStart(false);
// get the index of the test to start running from
int lastActivatedTestIndex = RunnerStatePersistencyManager.getInstance().getActiveTestIndex();
if (lastActivatedTestIndex == -1) {
log.info("Invalid start test index");
return;
}
lastActivatedTestIndex++;
log.info("Starting to run scenario from test number " + lastActivatedTestIndex);
if (s == null) {
// get scenario to activate and load scenario
s = ScenariosManager.getInstance().getCurrentScenario();
s.loadParametersAndValues();
}
// disable all tests before the start test.
Vector<JTest> v = s.getTests();
List<JTest> listOfTest = ScenarioHelpers.filterFixtures(v);
if (lastActivatedTestIndex >= listOfTest.size()) {
log.info("Start test index equals or bigger then number of tests in scenario");
return;
}
// before changing selected tests, saving current state
enabledTests = s.getEnabledTestsIndexes();
RunnerStatePersistencyManager.getInstance().setEnabledTests(enabledTests);
// now update scenario tests
Iterator<JTest> testIter = listOfTest.iterator();
for (int i = 0; i < lastActivatedTestIndex; i++) {
JTest t = testIter.next();
t.setDisable(true);
}
// save scenario
s.save();
log.info("Starting to run. Scenario name is " + s.getName() + " from test index " + lastActivatedTestIndex);
try {
//
// Check if before restart engine was signaled to run a sub
// scenario.
// if so, run the sub scenario.
//
String internalScenarioId = RunnerStatePersistencyManager.getInstance().getInternalScenarioUUID();
if (!StringUtils.isEmpty(internalScenarioId)) {
try {
run(internalScenarioId);
} finally {
RunnerStatePersistencyManager.getInstance().setInternalScenarioUUID("");
}
} else {
run();
}
} finally {
// if we changes scenario state, let's reverse it back
enabledTests = RunnerStatePersistencyManager.getInstance().getEnabledTests(true);
if (enabledTests != null) {
s.setEnabledTestsIndexes(enabledTests);
s.save();
}
}
log.info("run on start ended");
}
/**
* @see RunnerEngine#changeSut(String)
*/
public void changeSut(String sutName) throws Exception {
if (sutName == null) {
sutName = "";
log.warning("Sut file name is set to empty value");
}
SutFactory.getInstance().setSut(sutName);
}
/**
* @see RunnerEngine#enableRepeat(boolean)
*/
@Override
public void enableRepeat(boolean isEnabled) throws Exception {
executor.setRepeat(isEnabled);
}
/**
* @see RunnerEngine#pause()
*/
@Override
public void pause() throws Exception {
executor.pause();
}
/**
* @see RunnerEngine#run()
*/
@Override
public void run() throws Exception {
executor.execute();
}
@Override
public void run(String uuid) throws Exception {
// note that is is important that setting runner internal scenario will
// be done before executing scenario
// so scenario id will be saved in case agent is restarted.
RunnerStatePersistencyManager.getInstance().setInternalScenarioUUID(uuid);
executor.execute(uuid);
}
/**
* @see RunnerEngine#stop()
*/
public void stop() throws Exception {
executor.stop();
}
/**
* @see RunnerEngine#gracefulStop()
*/
public void gracefulStop() throws Exception {
executor.gracefulStop();
}
/**
* @see RunnerEngine#resume()
*/
public void resume() throws Exception {
executor.resume();
}
/**
* @see RunnerEngine#setRepeat(int)
*/
public void setRepeat(int number) throws Exception {
executor.setRepeatNumber(number);
}
/**
* @see RunnerEngine#setActiveScenario(Scenario)
*/
public void setActiveScenario(Scenario scenario) throws Exception {
ScenariosManager.getInstance().setCurrentScenario(scenario);
}
/**
* @see RunnerEngine#addListener(Object)
*/
public void addListener(Object eventListsner) {
RunnerListenersManager.getInstance().addListener(eventListsner);
}
/**
* @see RunnerEngine#removeListener(Object)
*/
public void removeListener(Object eventListsner) {
RunnerListenersManager.getInstance().removeListener(eventListsner);
}
/**
* @see RunnerEngine#removeListener(Object)
*/
public void initReporters() {
((RunnerListenersManager) RunnerListenersManager.getInstance()).initReporters();
}
/**
* If a scenario is executed, stops scenario execution.
*
* @see RunnerEngine#close()
*/
public void close() {
try {
stop();
} catch (Exception e) {
log.log(Level.FINE, "Failed stopping execution", e);
}
}
/**
*
*/
public void init() throws Exception {
// set engine state to idle.
executionStateListener.executionEnded("");
}
/**
* @see RunnerEngine#getLogUrl()
*/
public URL getLogUrl() throws Exception {
String logDir = JSystemProperties.getInstance().getPreference(FrameworkOptions.LOG_FOLDER);
File f = new File(logDir, "current/index.html");
String uri = f.toURI().toString().replace("\\", "/");
return new URL(uri);
}
/**
* @see RunnerEngine#refresh()
*/
public void refresh() throws Exception {
LoadersManager.getInstance().dropAll();
JSystemProperties.getInstance().rereadPropertiesFile();
FixtureManager.getInstance().initFixtureModel();
SutFactory.resetSutFactory();
// ScenariosManager.init();
HtmlCodeWriter.init();
}
/**
* in case that the path of file is relative, it converts it to absolute
* path to the tests classes directory
*
* @param path
* @return
* @throws Exception
*/
private File getFileUsingPath(String path) throws Exception {
if (!FileUtils.isRelativePath(path)) {
return new File(path);
}
File projectClassesDir = new File(JSystemProperties.getCurrentTestsPath().substring(0,
JSystemProperties.getCurrentTestsPath().length())).getParentFile();
return new File(projectClassesDir, path);
}
/**
* Takes the stdout and stderr of the process and redirects them to a file
* and to the console to enable it use "stdout.file.name" attribute. <br>
* It should be added to the jsystem.properties file with the file name to
* redirect the output to. <br>
* If "console.disable" is set to true the console output will be blocked.<br>
* The class that is used to merge the streams is StdFilePrintStream.
*/
private void redirectOutputStream() throws Exception {
String stdOutFile = JSystemProperties.getInstance().getPreference(FrameworkOptions.STDOUT_FILE_NAME);
// if set will redirect stdout/stderr to a file
if (stdOutFile == null) {
return;
}
boolean isAppend = Boolean.parseBoolean(JSystemProperties.getInstance().getPreference(
FrameworkOptions.STDOUT_FILE_APPEND));
PrintStream fileStream = new PrintStream(new BufferedOutputStream(new FileOutputStream(
getFileUsingPath(stdOutFile), isAppend)));
StdFilePrintStream streamOut;
StdFilePrintStream streamErr;
// if disabled console send null as the second stream
if ("true".equals(JSystemProperties.getInstance().getPreference(FrameworkOptions.CONSOLE_DISABLE))) {
streamOut = new StdFilePrintStream(fileStream, null);
streamErr = new StdFilePrintStream(fileStream, null);
} else {
streamOut = new StdFilePrintStream(fileStream, System.out);
streamErr = new StdFilePrintStream(fileStream, System.err);
}
System.setOut(streamOut);
System.setErr(streamErr);
}
/**
*
*/
@Override
public String getActiveScenario() throws Exception {
return ScenariosManager.getInstance().getCurrentScenario().getName();
}
/**
*
*/
@Override
public String getCurrentProjectName() throws Exception {
String classesFolder = JSystemProperties.getInstance().getPreference(FrameworkOptions.TESTS_CLASS_FOLDER);
return new File(classesFolder).getParentFile().getPath();
}
/**
*
*/
@Override
public RunnerEngineExecutionState getEngineExecutionState() {
return executionStateListener.getExecutionState();
}
/**
* Calculates project's MD5
*/
public String calculateCurrentProjectMD5() throws Exception {
String classesFolder = JSystemProperties.getInstance().getPreference(FrameworkOptions.TESTS_CLASS_FOLDER);
MD5Calculator calculator = new MD5Calculator(new File(classesFolder));
if (!AutomationProjectUtils.isValidProject(new File(classesFolder))) {
throw new Exception("Invalid current project");
}
Scenario s = ScenariosManager.getInstance().getCurrentScenario();
return calculator.calculateProjectMD5(s.getName());
}
@Override
public ConnectionState getConnectionState() {
return ConnectionState.connected;
}
@Override
public String getEngineVersion() throws Exception {
return ClassSearchUtil.getPropertyFromClassPath("META-INF/jsystemAgent.build.properties", "jversion");
}
@Override
public Properties getRunProperties() throws Exception {
return RunProperties.getInstance().getRunProperties();
}
public void setEnabledTests(int[] enabledTestsIndices) throws Exception {
Scenario s = ScenariosManager.getInstance().getCurrentScenario();
s.loadParametersAndValues();
s.setEnabledTestsIndexes(enabledTestsIndices);
s.save();
}
}