/* MonkeyTalk - a cross-platform functional testing tool
Copyright (C) 2012 Gorilla Logic, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.gorillalogic.monkeytalk.processor;
import java.util.ArrayList;
import java.util.List;
import javax.script.ScriptException;
import com.gorillalogic.monkeytalk.Command;
import com.gorillalogic.monkeytalk.CommandWorld;
import com.gorillalogic.monkeytalk.processor.command.Vars;
import com.gorillalogic.monkeytalk.processor.js.MonkeyTalkJS;
import com.gorillalogic.monkeytalk.utils.FileUtils;
/**
* Run a Javascript script and return the result.
*/
public class JSProcessor extends BaseProcessor {
private MonkeyTalkJS mtjs;
/**
* Instantiate a script processor with the given script processor.
*
* @param processor
* the script processor
*/
public JSProcessor(ScriptProcessor processor) {
super(processor);
try {
mtjs = new MonkeyTalkJS(processor);
} catch (ScriptException ex) {
mtjs = null;
}
}
/**
* Set the MonkeyTalk JS engine to the given engine.
*
* @param mtjs
* the MonkeyTalk JS engine
*/
public void setMonkeyTalkJS(MonkeyTalkJS mtjs) {
this.mtjs = mtjs;
}
/**
* Run the JS script referenced by the supplied command. The command must not contain
* substitution vars, and cannot be a "runWith" (ex: {@code Script foo.js Run}).
*
* @param cmd
* the MonkeyTalk command
* @return the result of the script run
*/
public PlaybackResult runJavascript(Command cmd) {
return runJavascript(cmd, null);
}
/**
* Run the JS script referenced by the supplied command in the given scope. The command must not
* contain substitution vars, and cannot be a "runWith". Furthermore, the monkeyId must be the
* full javascript filename including extension (ex: {@code Script foo.js Run}). Assumes
* MonkeyTalkAPI.js and the project's generated js wrapper lib are in a {@code libs}
* subdirectory.
*
* @param cmd
* the MonkeyTalk command
* @param scope
* the script scope
* @return the result of the script run
*/
public PlaybackResult runJavascript(Command cmd, Scope scope) {
long startTime = System.currentTimeMillis();
if (scope == null) {
scope = new Scope("anonymous");
scope.setCurrentCommand(cmd);
}
if (cmd == null) {
return errorResult("command is null", scope, startTime);
} else if (!"script".equalsIgnoreCase(cmd.getComponentType())
|| !"run".equalsIgnoreCase(cmd.getAction())) {
return errorResult("command '" + cmd.getCommand()
+ "' is illegal JSProcessor command - only 'script.run' allowed", scope,
startTime);
}
String componentType = cmd.getComponentType();
String monkeyId = cmd.getMonkeyId();
String action = cmd.getAction();
if (!world.fileExists(monkeyId)) {
return errorResult("script '" + monkeyId + "' not found", scope, startTime);
}
if (monkeyId.matches("\\A[^\\.]+\\.[^\\.]+\\.js\\Z")) {
String[] parts = FileUtils.removeExt(monkeyId, CommandWorld.JS_EXT).split("\\.");
componentType = parts[0];
if (!componentType.matches(Vars.VALID_VARIABLE_PATTERN)) {
return errorResult(
"filename '"
+ monkeyId
+ "' has illegal component type -- both parts of custom commands in JSProcessor must begin with a letter and contain only letters, numbers, and underscores",
scope, startTime);
}
action = parts[1];
if (!action.matches(Vars.VALID_VARIABLE_PATTERN)) {
return errorResult(
"filename '"
+ monkeyId
+ "' has illegal action -- both parts of custom commands in JSProcessor must begin with a letter and contain only letters, numbers, and underscores",
scope, startTime);
}
} else {
componentType = FileUtils.removeExt(monkeyId, CommandWorld.JS_EXT);
if (!componentType.matches(Vars.VALID_VARIABLE_PATTERN)) {
return errorResult(
"filename '"
+ monkeyId
+ "' is illegal -- filenames in JSProcessor must begin with a letter and contain only letters, numbers, and underscores",
scope, startTime);
}
}
StringBuilder sb = new StringBuilder();
sb.append("load(\"" + cmd.getMonkeyId() + "\");\n");
sb.append("var app = new MT.Application(\"" + getHost() + "\", " + getPort() + ", \""
+ world.getRootDir() + "\"" + ");\n");
sb.append("app." + lowerFirst(componentType) + "()." + lowerFirst(action) + "("
+ getArgsAsString(cmd.getArgs()) + ");");
return runJavascript(sb.toString(), scope);
}
private PlaybackResult runJavascript(String js, Scope scope) {
if (mtjs == null) {
return new PlaybackResult(PlaybackStatus.ERROR, "bad js engine");
}
List<Step> steps = new ArrayList<Step>();
PlaybackResult result = new PlaybackResult(PlaybackStatus.OK);
result.setStartTime(System.currentTimeMillis());
try {
mtjs.getEngine().put("ScopeObj", scope);
mtjs.getEngine().put("StepsObj", steps);
if (Globals.getGlobals().size() > 0) {
mtjs.getEngine().eval(Globals.asJavascript());
}
mtjs.getEngine().eval(js);
} catch (ScriptException ex) {
// see if last step was a non-OK MT Command Execution
PlaybackResult lastResult = null;
if (steps != null && steps.size() > 0) {
lastResult = steps.get(steps.size() - 1).getResult();
}
if (lastResult != null && !lastResult.getStatus().equals(PlaybackStatus.OK)) {
result = copyResult(lastResult, scope, result.getStartTime());
} else {
result.setStatus(PlaybackStatus.ERROR);
result.setMessage(ex.getMessage());
}
}
result.setStopTime(System.currentTimeMillis());
result.setScope(scope);
result.setSteps(steps);
return result;
}
/** Helper to lowercase the first letter of the given string. */
private String lowerFirst(String s) {
if (s.length() < 2) {
return s.toLowerCase();
}
return s.substring(0, 1).toLowerCase() + s.substring(1);
}
/**
* Helper to convert the list of MonkeyTalk command args into a single comma-separated String.
*
* @param args
* the MonkeyTalk args
* @return args as a single comma-separated String
*/
private String getArgsAsString(List<String> args) {
StringBuilder sb = new StringBuilder();
for (String arg : args) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append('"').append(arg).append('"');
}
return sb.toString();
}
private PlaybackResult errorResult(String message, Scope scope, long startTime) {
if (startTime == 0) {
startTime = System.currentTimeMillis();
}
PlaybackResult result = new PlaybackResult(PlaybackStatus.ERROR, message);
result.setStartTime(startTime);
result.setStopTime(System.currentTimeMillis());
result.setScope(scope);
return result;
}
@Override
public String toString() {
return "JSProcessor:\nurl: http://" + getHost() + ":" + getPort() + "/\n"
+ super.toString();
}
}