/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2010 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 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 or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.client.test; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.ByteArrayInputStream; import java.io.IOException; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParser; //import org.xmlpull.v1.XmlPullParserFactory; import com.funambol.org.kxml2.io.KXmlParser; import com.funambol.client.test.basic.BasicCommandRunner; import com.funambol.client.test.util.SyncMonitor; import com.funambol.client.test.util.TestFileManager; import com.funambol.util.Log; import com.funambol.util.HttpTransportAgent; import com.funambol.util.StringUtil; import com.funambol.sync.SyncConfig; import com.funambol.platform.DeviceInfo; /** * The CommandRunner container implementation to run commands that are common to * all the clients. This is not only a CommandRunner object itself, but also a * container for any other command runner used by the given client test suite. * refer to this class using the basic contructor, but remember to add * explicitly implemented command runners in order to extend the command pool * available to the tester. Use addcommandRunner(CommandRunner) method to * achieve this goal. If no commad runner are added, no command will be * effective and the run of the test suite will be unuseful. * This class uses a CheckSyncClient object, a SyncMonitor and an * AuthSyncMonitor to check that the requested sync operations went fine both on * the client and the server side. */ public class BasicScriptRunner extends CommandRunner { private static final String TAG_LOG = "BasicScriptRunner"; private static final int SUCCESS_STATUS = 0; private static final int CLIENT_TEST_EXCEPTION_STATUS = -1; // Commands private static final String INCLUDE_COMMAND = "Include"; private static final String ON_COMMAND = "On"; private static final String ON_OTHERS = "Others"; private static String baseUrl = null; // The list of command runners private Vector commandRunners = new Vector(); private int chainedTestsCounter; private int nestingDepth = 0; private String mainTestName = ""; protected int errorCode = SUCCESS_STATUS; private boolean stopOnFailure = false; private TestFileManager fileManager = null; private DeviceInfo devInfo; private Hashtable definedVars = new Hashtable(); private Hashtable testKeys = null; private Vector testResults = null; /** * Default constructor */ public BasicScriptRunner(TestFileManager fileManager, DeviceInfo devInfo) { super(null); this.fileManager = fileManager; this.devInfo = devInfo; } /** * Add a specific CommandRunner implementation to the current * BasicScriptRunner instance * @param runner th CommandRunner object to be added. */ public void addCommandRunner(CommandRunner runner) { commandRunners.addElement(runner); } /** * This method is responsible to interpret the file (being it on the device * storage or taken via http), interpreting and running the commands defined * by the tester. First of all the it loads the script given the remote or * local url. Once the script is loaded it is parsed and the commands given * to the CommandRunner array that tries to manage them using the previously * added Command runners objects. The actual implementation avoid to stop * whenever an error or an exception occurs. In particular: if an exception * occurs it can be due to syntax errors contained into the tester script or * a client test error. In both cases if the error is detected the script * is entirely aborted starting from the line where the error was found. The * implementation also supports inner scripts to be invoked: this means that * if the error is located into an inner script the system will ignore all * of the calling scripts until level 0 is reached. For example if script1 * needs to include script2 that again needs script3 (chained execution) * for its correct execution and an error is detected on script3: script3 * and script 2 will be aborted (adding this info to the report object) and * the tester will see that test case wirtten in script1 failed. This apply * for all inner level script in which an error is detected. The first level * script is ignored until the latest EndtTest command is found and then * when the next BeginTest command is detected the test suite execution * restart normally form the next text on. When such those failures occur * the global report content is updated and the general error code is set to * error. * @param scriptUrl the script url String formatted * @param mainScript boolean to declare that this is a main test script * (not yet in use) * @throws Throwable if an error occurred while retrieving a script content. * This is the only case in which the test suite is entirely aborted. */ public void runScriptFile(String scriptUrl, boolean mainScript, Hashtable vars) throws Throwable { testResults = new Vector(); testKeys = new Hashtable(); if (vars != null) { definedVars = vars; } else { // Set predefined variables definedVars = new Hashtable(); } // Add more variables (platform independent) if (devInfo.getDeviceRole() == DeviceInfo.DeviceRole.TABLET) { definedVars.put("devicetype", "table"); } else { definedVars.put("devicetype", "phone"); } definedVars.put("devicemodel", devInfo.getDeviceModel()); long startTime = System.currentTimeMillis(); try { runScriptFileI(scriptUrl, mainScript); } finally { long endTime = System.currentTimeMillis(); dumpResults(startTime, endTime); } } protected void runScriptFileI(String scriptUrl, boolean mainScript) throws Throwable { baseUrl = fileManager.getBaseUrl(scriptUrl); if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Running script at URL = " + scriptUrl); } if (scriptUrl != null) { try { String script = fileManager.getFile(scriptUrl); if (scriptUrl.endsWith("xml")) { runXmlScript(script, scriptUrl); } else { runScript(script, scriptUrl); } } catch (Exception e) { throw new Exception("Cannot run script " + scriptUrl + " because " + e); } } else { Log.error(TAG_LOG, "Cannot load script at " + scriptUrl); throw new ClientTestException("The script url is NULL"); } } protected void runXmlScript(String script, String scriptUrl) throws Throwable { // Start parsing the XML script file XmlPullParser parser = new KXmlParser(); try { ByteArrayInputStream is = new ByteArrayInputStream(script.getBytes("UTF-8")); parser.setInput(is, "UTF-8"); // Begin parsing nextSkipSpaces(parser); // If the first tag is not the SyncML start tag, then this is an // invalid message require(parser, parser.START_TAG, null, "Script"); nextSkipSpaces(parser); // Keep track of the nesting level depth nestingDepth++; String currentCommand = null; boolean condition = false; boolean evaluatedCondition = false; Vector args = null; boolean ignoreCurrentScript = false; boolean ignoreCurrentBranch = false; boolean ignoreFinalization = false; while (parser.getEventType() != parser.END_DOCUMENT) { // Each tag here is a command. All commands have the same // format: // <Command> // <Arg>arg1</Arg> // <Arg>arg2</Arg> // </Command> // // The only exception is for conditional statements // <Condition> // <If>condition</If> // <Then><command>...</command></Then> // <Else><command>...</command>/Else> // </Condition> if (parser.getEventType() == parser.START_TAG) { String tagName = parser.getName(); if ("Condition".equals(tagName)) { condition = true; } else if ("If".equals(tagName)) { // We just read the "<If>" tag, now we read the rest of the condition // until the </If> nextSkipSpaces(parser); evaluatedCondition = evaluateCondition(parser); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "If"); } else if ("Then".equals(tagName)) { if (!condition) { throw new ClientTestException("Syntax error: found Then tag without Condition"); } ignoreCurrentBranch = !evaluatedCondition; } else if ("Else".equals(tagName)) { if (!condition) { throw new ClientTestException("Syntax error: found Then tag without Condition"); } ignoreCurrentBranch = evaluatedCondition; } else { if (currentCommand == null) { currentCommand = tagName; args = new Vector(); Log.trace(TAG_LOG, "Found command " + currentCommand); } else { // This can only be an <arg> tag if ("Arg".equals(tagName)) { parser.next(); // Concatenate all the text tags until the end // of the argument StringBuffer arg = new StringBuffer(); while(parser.getEventType() == parser.TEXT) { arg.append(parser.getText()); parser.next(); } String a = arg.toString().trim(); Log.trace(TAG_LOG, "Found argument " + a); a = processArg(a); args.addElement(a); require(parser, parser.END_TAG, null, "Arg"); } } } } else if (parser.getEventType() == parser.END_TAG) { String tagName = parser.getName(); if ("Condition".equals(tagName)) { condition = false; currentCommand = null; ignoreCurrentBranch = false; } else if (tagName.equals(currentCommand)) { try { Log.trace(TAG_LOG, "Executing accumulated command: " + currentCommand + "," + ignoreCurrentScript + "," + ignoreCurrentBranch); if ((!ignoreCurrentScript && !ignoreCurrentBranch) || ("EndTest".equals(currentCommand) && !ignoreFinalization)) { runCommand(currentCommand, args); // If we succesfully execute at least one // statement of the script, then we shall // execute its finalization at the end if ("BeginTest".equals(currentCommand)) { ignoreFinalization = false; } } } catch (IgnoreScriptException ise) { // This script must be ignored ignoreCurrentScript = true; nestingDepth = 0; TestStatus status = new TestStatus(scriptUrl); status.setStatus(TestStatus.SKIPPED); testResults.addElement(status); testKeys.put(scriptUrl, status); ignoreFinalization = true; } catch (Throwable t) { Log.error(TAG_LOG, "Error running command", t); TestStatus status = new TestStatus(scriptUrl); status.setStatus(TestStatus.FAILURE); status.setDetailedError("Error " + t.toString() + " at line " + parser.getLineNumber()); testResults.addElement(status); testKeys.put(scriptUrl, status); if (stopOnFailure) { throw t; } else { ignoreCurrentScript = true; nestingDepth = 0; } } currentCommand = null; } else if ("Script".equals(tagName)) { // end script found // If we get here and the current script is not being // ignored, then the execution has been successful if (!ignoreCurrentScript) { if (testKeys.get(scriptUrl) == null) { // This test is not a utility test, save its // status TestStatus status = new TestStatus(scriptUrl); status.setStatus(TestStatus.SUCCESS); testResults.addElement(status); testKeys.put(scriptUrl, status); } } if (nestingDepth == 0 && ignoreCurrentScript) { // The script to be ignored is completed. Start // execution again ignoreCurrentScript = false; } } } nextSkipSpaces(parser); } } catch (Exception e) { // This will block the entire execution TestStatus status = new TestStatus(scriptUrl); status.setStatus(TestStatus.FAILURE); status.setDetailedError("Syntax error in file " + scriptUrl + " at line " + parser.getLineNumber()); testResults.addElement(status); testKeys.put(scriptUrl, status); Log.error(TAG_LOG, "Error parsing command", e); throw new ClientTestException("Script syntax error"); } } private boolean evaluateCondition(XmlPullParser parser) throws ClientTestException, XmlPullParserException, IOException { String tagName = parser.getName(); if ("And".equals(tagName)) { nextSkipSpaces(parser); boolean firstCond = evaluateCondition(parser); nextSkipSpaces(parser); boolean secondCond = evaluateCondition(parser); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "And"); return firstCond && secondCond; } else if ("Or".equals(tagName)) { nextSkipSpaces(parser); boolean firstCond = evaluateCondition(parser); nextSkipSpaces(parser); boolean secondCond = evaluateCondition(parser); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "Or"); return firstCond || secondCond; } else if ("Not".equals(tagName)) { nextSkipSpaces(parser); boolean firstCond = evaluateCondition(parser); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "Not"); return !firstCond; } else if ("Equals".equals(tagName)) { // Grab the arguments nextSkipSpaces(parser); // Only an Arg tag is allowed here require(parser, parser.START_TAG, null, "Arg"); parser.next(); require(parser, parser.TEXT, null, null); String arg1 = parser.getText(); arg1 = processArg(arg1); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "Arg"); // Now grab the second arg nextSkipSpaces(parser); // Only an Arg tag is allowed here require(parser, parser.START_TAG, null, "Arg"); parser.next(); require(parser, parser.TEXT, null, null); String arg2 = parser.getText(); arg2 = processArg(arg2); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "Arg"); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "Equals"); Log.trace(TAG_LOG, "Found equals with arguments: " + arg1 + "," + arg2); return StringUtil.equalsIgnoreCase(arg1, arg2); } else if ("CheckOS".equals(tagName)) { // Grab the arguments nextSkipSpaces(parser); // Only an Arg tag is allowed here require(parser, parser.START_TAG, null, "Arg"); parser.next(); String arg1 = null; if (parser.getEventType() == parser.TEXT) { arg1 = parser.getText(); arg1 = processArg(arg1); nextSkipSpaces(parser); } require(parser, parser.END_TAG, null, "Arg"); // Now grab the second arg nextSkipSpaces(parser); // Only an Arg tag is allowed here require(parser, parser.START_TAG, null, "Arg"); parser.next(); String arg2 = null; if (parser.getEventType() == parser.TEXT) { arg2 = parser.getText(); arg2 = processArg(arg2); nextSkipSpaces(parser); } require(parser, parser.END_TAG, null, "Arg"); nextSkipSpaces(parser); require(parser, parser.END_TAG, null, "CheckOS"); int osVersion = Integer.parseInt(devInfo.getOSVersion()); Log.trace(TAG_LOG, "Found CheckOS with arguments: " + arg1 + "," + arg2); Log.trace(TAG_LOG, "OS version " + osVersion); if (arg1 != null) { int low = Integer.parseInt(arg1); if (osVersion < low) { return false; } } if (arg2 != null) { int high = Integer.parseInt(arg2); if (osVersion > high) { return false; } } return true; } else { throw new ClientTestException("Syntax error: unknown condition " + tagName); } } private void nextSkipSpaces(XmlPullParser parser) throws ClientTestException, XmlPullParserException, IOException { int eventType = parser.next(); if (eventType == parser.TEXT) { if (!parser.isWhitespace()) { String t = parser.getText(); if (t.length() > 0) { Log.error(TAG_LOG, "Unexpected text: " + t); throw new ClientTestException("Unexpected text: " + t); } } parser.next(); } } private void require(XmlPullParser parser, int type, String namespace, String name) throws XmlPullParserException { if (type != parser.getEventType() || (namespace != null && !namespace.equals(parser.getNamespace())) || (name != null && !name.equals(parser.getName()))) { StringBuffer desc = new StringBuffer(); desc.append("Expected ").append(parser.TYPES[ type ]).append(parser.getPositionDescription()) .append(" -- Found ").append(parser.TYPES[parser.getEventType()]); throw new XmlPullParserException(desc.toString()); } } /** * Execute the given script by interpreting it */ public void runScript(String script, String scriptUrl) throws Throwable { int idx = 0; int lineNumber = 0; boolean onExecuted = false; //If an exception is catched the remaining lines of this script are //ignored boolean ignoreCurrentScript = false; String syntaxError = null; boolean eol; do { try { // The end of the command is recognized with the following // sequence ;( *)\n StringBuffer l = new StringBuffer(); eol = false; String line = null; while(idx<script.length()) { char ch = script.charAt(idx); l.append(ch); if (ch == ';') { eol = true; if (idx < script.length() - 1) { // This may be the end of line while(idx<script.length()) { char nextCh = script.charAt(++idx); if (nextCh == '\n') { break; } else if (nextCh == ' ' || nextCh == '\r') { // Keep searching l.append(nextCh); } else { // This is not the end of line l.append(nextCh); eol = false; break; } } } else { // This is the last char ++idx; } } else if (ch == '#' && l.length() == 1) { if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "Found a comment, consuming line"); } // This line is a comment, skip everything until an EOL // is found ++idx; for(;idx<script.length();++idx) { char nextChar = script.charAt(idx); if (nextChar == '\n') { break; } else { l.append(nextChar); } } eol = true; } else if (ch == '\n') { // We found a EOL without the ; // This maybe an empty line that we just ignore String currentLine = l.toString().trim(); if (currentLine.length() == 0) { l = new StringBuffer(); } else { // If otherwise this is an EOL in the middle of a // command, we just ignore it (EOL shall be represented // as \n) l.deleteCharAt(l.length() - 1); } } if (eol) { // Remove trailing end of line (everything after the ;) while(l.length() > 0 && l.charAt(l.length() - 1) != ';') { l.deleteCharAt(l.length() - 1); } line = l.toString(); break; } ++idx; } if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "Executing line: " + line); } if (line == null) { return; } lineNumber++; syntaxError = null; line = line.trim(); if (line.length() > 0 && !line.startsWith("#")) { if (line.startsWith(ON_COMMAND + " ")) { // This is a conditional statement. Check if it must be // executed boolean exec = false; try { exec = checkCandidateStatement(line, onExecuted); } catch (Throwable t) { errorCode = CLIENT_TEST_EXCEPTION_STATUS; ignoreCurrentScript = true; exec = false; } if (exec) { onExecuted = true; // Get the real command int colPos = line.indexOf(":"); if (colPos == -1) { String msg = "Syntax error in script, missing ':' in: " + line + " at line " + lineNumber; Log.error(TAG_LOG, msg); errorCode = CLIENT_TEST_EXCEPTION_STATUS; ignoreCurrentScript = true; //throw new ClientTestException("Script syntax error"); } if (colPos + 1 >= line.length()) { String msg = "Syntax error in script, missing command in: " + line + " at line " + lineNumber; Log.error(TAG_LOG, msg); errorCode = CLIENT_TEST_EXCEPTION_STATUS; ignoreCurrentScript = true; //throw new ClientTestException("Script syntax error"); } line = line.substring(colPos + 1); line = line.trim(); } else { // skip the rest if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "Skipping conditional statement"); } continue; } } else { // Reset the conditional statement status onExecuted = false; } int parPos = line.indexOf('('); if (parPos == -1) { syntaxError = "Syntax error in script " + scriptUrl + "\nmissing '(' in: " + line + " at line " + lineNumber; Log.error(syntaxError); errorCode = CLIENT_TEST_EXCEPTION_STATUS; ignoreCurrentScript = true; // Force this script to be terminated idx = script.length(); } String command = line.substring(0, parPos); command = command.trim(); String pars; if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "line=" + line); } if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "parPos = " + parPos); } if (line.endsWith(";")) { pars = line.substring(parPos, line.length() - 1); } else { pars = line.substring(parPos); } //Increments the test counter to if (BasicCommandRunner.BEGIN_TEST_COMMAND.equals(command)) { chainedTestsCounter++; if (chainedTestsCounter == 1) { mainTestName = pars; } } else if (BasicCommandRunner.END_TEST_COMMAND.equals(command)) { chainedTestsCounter--; if (chainedTestsCounter == 0) { ignoreCurrentScript = false; } } if (!ignoreCurrentScript) { // Extract parameters and put them into a vector Vector args = new Vector(); int i = 0; String arg; do { arg = getParameter(pars, i++); if (arg != null) { args.addElement(arg); } } while(arg != null); runCommand(command, args); } } } catch (IgnoreScriptException ise) { ignoreCurrentScript = true; } catch (Throwable t) { errorCode = CLIENT_TEST_EXCEPTION_STATUS; StringBuffer msg = new StringBuffer(); msg.append("\nTEST FAILED: ").append(mainTestName); msg.append("\n\tException: ").append(t); msg.append(syntaxError != null ? "\n\t" + syntaxError : ""); msg.append("\n\t(").append(scriptUrl).append(": ") .append(lineNumber).append(")"); Log.error(msg.toString()); Log.error(TAG_LOG, "Exception details", t); //tell the scriptrunner to ignore all of the chained tests //commands ignoreCurrentScript = true; if(stopOnFailure) { throw new ClientTestException("TEST FAILED"); } } } while (true); } /** * Accessor Method to get the base url related to the tests location * @return String the String formatted url to be used as the base url for * tests. Example: be the main script url * "http://url.somewhere.com/folder1/folder2/Test.txt", this method will * return be "http://url.somewhere.com/folder1/folder2". This method just * return the url after the computation. Can return null if the baseUrl * has not yet been calculated. */ public static String getBaseUrl() { return baseUrl; } /** * Tells if the script runner shall be interrupted at the first failure * * @param stop */ public void setStopOnFailure(boolean stop) { stopOnFailure = stop; } /** * Checks the given command runner in order to execute the given command * with the given arguments. If no runner is defined for a given command the * error is detected returning false. * @param command the String formatted command to be passed to the runners * array * @param pars the command's parameter. * @return true if a CommandRunner defined for this object can manage the * given command with the given parameters, false otherwise. * @throws Throwable if an error occurred executing the command. */ public boolean runCommand(String command, Vector pars) throws Throwable { if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "command=" + command); } if (Log.isLoggable(Log.TRACE)) { Log.trace(TAG_LOG, "pars=" + pars); } if (INCLUDE_COMMAND.equals(command)) { includeScript(command, pars); } else { boolean ok = false; for (int i = 0; i < commandRunners.size(); i++) { CommandRunner runner = (CommandRunner) commandRunners.elementAt(i); if (runner.runCommand(command, pars)) { ok = true; break; } } if (!ok) { throw new IllegalArgumentException("Unknown command " + command); } } return true; } /** * Includes a given script in the current executing one.This avoid testers * to write a huge amount of lines to reach the same goal (for example * initialize an atomic test set). * @param command the String formatted command to include a script * @param args the command's related String formatted arguments * @throws Throwable if an error occurs */ public void includeScript(String command, Vector args) throws Throwable { String scriptUrl = (String)args.elementAt(0); checkArgument(scriptUrl, "Missing script url in " + command); if (!scriptUrl.startsWith("http:") && !scriptUrl.startsWith("file:")) { int upCount = 0; while(scriptUrl.startsWith("..")) { scriptUrl = scriptUrl.substring("../".length()); upCount++; } if (baseUrl != null) { String newBaseUrl = baseUrl; for(int i=0; i<upCount; i++) { int index = newBaseUrl.lastIndexOf('/'); newBaseUrl = newBaseUrl.substring(0, index); } scriptUrl = newBaseUrl + "/" + scriptUrl; } } String tmpBaseUrl = baseUrl; runScriptFileI(scriptUrl, false); baseUrl = tmpBaseUrl; } /** * Accessor method to retrieve the global status of the entire test suite. * @return int the value related to this test failure or success. */ public int getErrorCode() { return this.errorCode; } /** * Accessor method to set the global suite error code from outside (external * syntax error) * @param errorCode the int representation of the error code */ public void setErrorCode(int errorCode) { this.errorCode = errorCode; } /** * Accessor method to retrieve the details of the failed tests * @return String the String formatted failed test report content. */ private void dumpResults(long startTime, long endTime) { Log.info(TAG_LOG, "***************************************"); if (testResults != null) { int tot = 0; int failed = 0; int success = 0; int skipped = 0; for(int i=0;i<testResults.size();++i) { StringBuffer res = new StringBuffer(); TestStatus status = (TestStatus)testResults.elementAt(i); String url = status.getScriptName(); res.append("Script=").append(url); String r; switch (status.getStatus()) { case TestStatus.SUCCESS: r = "SUCCESS"; success++; break; case TestStatus.FAILURE: r = "FAILURE"; failed++; // Record that we had an error errorCode = -1; break; case TestStatus.SKIPPED: r = "SKIPPED"; skipped++; break; default: r = "UNDEFINED"; break; } res.append(" Result=").append(r); String detailedError = status.getDetailedError(); tot++; if (detailedError != null) { res.append(" Error=").append(detailedError); } Log.info(TAG_LOG, res.toString()); } Log.info(TAG_LOG, "---------------------------------------"); Log.info(TAG_LOG, "Total number of tests: " + tot); Log.info(TAG_LOG, "Total number of success: " + success); Log.info(TAG_LOG, "Total number of failures: " + failed); Log.info(TAG_LOG, "Total number of skipped: " + skipped); long secs = (endTime - startTime) / 1000; Log.info(TAG_LOG, "Total execution time: " + secs); } else { Log.info(TAG_LOG, "No tests performed"); } Log.info(TAG_LOG, "***************************************"); } /** * Accessor method to retrieve the OS version * @return the empty String. Other values are implementation specific. */ protected String getOsVersion() { return ""; } private String[] createVersionArray(String version[]) { String tmp[] = new String[3]; for (int i = 0; i < 3; ++i) { if (i < version.length) { tmp[i] = version[i]; } else { tmp[i] = "0"; } } return tmp; } /** * Check the candidate statement to be executed given a conditional command * @param command the conditional statement command * @param onExecuted tries to predict the condition set in the command * @return true if the command is validand must be executed, false otherwise * @throws Throwable if an error occurs. */ protected boolean checkCandidateStatement(String command, boolean onExecuted) throws Throwable { String currentOs = getOsVersion(); String commandTokens[] = StringUtil.split(command, " "); boolean onOther = false; if (commandTokens.length < 3) { // This can only be the on others case if (commandTokens.length == 2) { String tmp = commandTokens[1].trim(); if (ON_OTHERS.equals(tmp)) { onOther = true; } } if (!onOther) { String msg = "Syntax error in On command, missing os versions" + command; Log.error(TAG_LOG, msg); throw new ClientTestException("Script syntax error"); } } if (!onOther) { String minOS = commandTokens[1]; String maxOS = commandTokens[2]; String min[] = StringUtil.split(minOS.trim(), "."); String max[] = StringUtil.split(maxOS.trim(), "."); String os[] = StringUtil.split(currentOs.trim(), "."); min = createVersionArray(min); max = createVersionArray(max); os = createVersionArray(os); int low = Integer.parseInt(min[0]) * 100 + Integer.parseInt(min[1]) * 10 + Integer.parseInt(min[0]); int high = Integer.parseInt(max[0]) * 100 + Integer.parseInt(max[1]) * 10 + Integer.parseInt(max[0]); int value = Integer.parseInt(os[0]) * 100 + Integer.parseInt(os[1]) * 10 + Integer.parseInt(os[0]); if (Log.isLoggable(Log.INFO)) { Log.info(TAG_LOG, "current version =" + value + ", min=" + min + ", max=" + max); } if (value >= low && value <= high) { // The on condition matches if (onExecuted) { String msg = "Syntax error in On command, more conditions matching" + command; Log.error(TAG_LOG, msg); throw new ClientTestException("Script syntax error"); } return true; } else { return false; } } else { boolean res = !onExecuted; return res; } } /** * Set the SyncMonitor object for this CommandRunner container * @param monitor the SyncMonitor to be set */ public void setSyncMonitor(SyncMonitor monitor) { super.setSyncMonitor(monitor); for (int i = 0; i < commandRunners.size(); i++) { CommandRunner runner = (CommandRunner) commandRunners.elementAt(i); runner.setSyncMonitor(monitor); } } /** * Set the AuthSyncMonitor object for this CommandRunner container * @param monitor the AuthSyncMonitor to be set */ public void setAuthSyncMonitor(SyncMonitor monitor) { super.setAuthSyncMonitor(monitor); for (int i = 0; i < commandRunners.size(); i++) { CommandRunner runner = (CommandRunner) commandRunners.elementAt(i); runner.setAuthSyncMonitor(monitor); } } private String processArg(String arg) { // We must replace any variable occurrence // Variables are defined as ${name} int start = arg.indexOf("${"); int end = arg.indexOf("}"); while (start >= 0 && end >= 0 && (start + 2 < arg.length())) { String varName = arg.substring(start + 2, end); // Is this var defined? String value = (String)definedVars.get(varName); Log.trace(TAG_LOG, "Replacing variable " + varName + " with value " + value); arg = StringUtil.replaceAll(arg, "${" + varName + "}", value); start = arg.indexOf("${"); end = arg.indexOf("}"); } return arg; } /** * Create a transport agent useful for the tests framework * @param config the SyncConfig used to configure the TransportAgent * @return HttpTransportAgent the instance of the HTTPTransportAgent correctly configured */ public static HttpTransportAgent createTestTransportAgent(SyncConfig config) { HttpTransportAgent ta = new HttpTransportAgent( config.syncUrl, config.userAgent, "UTF-8", config.compress, config.forceCookies); // Force messages to be resent in case of errors ta.setResendMessageOnErrors(true); return ta; } private class TestStatus { public static final int SUCCESS = 0; public static final int FAILURE = 1; public static final int SKIPPED = 2; private String scriptName; private int status; private String detailedError; public TestStatus(String scriptName) { this.scriptName = scriptName; } public void setStatus(int status) { this.status = status; } public void setDetailedError(String detailedError) { this.detailedError = detailedError; } public String getScriptName() { return scriptName; } public int getStatus() { return status; } public String getDetailedError() { return detailedError; } } }