/* 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.ant; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import com.gorillalogic.monkeytalk.CommandWorld; import com.gorillalogic.monkeytalk.agents.AndroidEmulatorAgent; import com.gorillalogic.monkeytalk.processor.Globals; import com.gorillalogic.monkeytalk.processor.PlaybackListener; import com.gorillalogic.monkeytalk.processor.PlaybackResult; import com.gorillalogic.monkeytalk.processor.PlaybackStatus; import com.gorillalogic.monkeytalk.processor.Runner; import com.gorillalogic.monkeytalk.processor.Scope; import com.gorillalogic.monkeytalk.processor.SuiteListener; import com.gorillalogic.monkeytalk.processor.report.Report; import com.gorillalogic.monkeytalk.sender.Response; import com.gorillalogic.monkeytalk.utils.AndroidUtils; import com.gorillalogic.monkeytalk.utils.FileUtils; public class RunTask extends Task { private String agent; private File script; private File suite; private String host; private int port; private File adb; private String callbackurl; private String jobrefparams; private String adbSerial; private int adbLocalPort; private int adbRemotePort; private File reportdir; private String text; private boolean verbose = false; private int timeout; private int thinktime; private int startup; private boolean screenshots = false; private boolean screenshotOnError = true; private String globals; private static final String TEMP_FILE = ".tmp" + CommandWorld.SCRIPT_EXT; private final PlaybackListener scriptListener = new PlaybackListener() { private String indent = ""; @Override public void onScriptStart(Scope scope) { onPrint((adbSerial != null ? adbSerial + ": " : "") + indent.replaceAll(" ", "-") + "-run: " + (scope.getFilename() == null ? "commands" : scope.getFilename())); indent += " "; } @Override public void onScriptComplete(Scope scope, PlaybackResult result) { indent = indent.substring(2); onPrint((adbSerial != null ? adbSerial + ": " : "") + indent.replaceAll(" ", "-") + "-end: " + (scope.getFilename() == null ? "commands" : scope.getFilename())); } @Override public void onStart(Scope scope) { if (!"debug".equalsIgnoreCase(scope.getCurrentCommand().getComponentType())) { onPrint((adbSerial != null ? adbSerial + ": " : "") + indent + scope.getCurrentCommand()); } } @Override public void onComplete(Scope scope, Response response) { if (response.getMessage() != null && response.getMessage().length() > 0) { onPrint((adbSerial != null ? adbSerial + ": " : "") + indent + "-> " + response.getStatus() + " : " + response.getMessage()); } } @Override public void onPrint(String message) { log(message.endsWith("\n") ? message.substring(0, message.length() - 1) : message); } }; private final PlaybackListener scriptListenerForSuite = new PlaybackListener() { private String indent = ""; @Override public void onScriptStart(Scope scope) { indent += " "; } @Override public void onScriptComplete(Scope scope, PlaybackResult result) { indent = indent.substring(2); } @Override public void onStart(Scope scope) { if (verbose) { onPrint((adbSerial != null ? adbSerial + ": " : "") + indent + scope.getCurrentCommand()); } } @Override public void onComplete(Scope scope, Response response) { if (verbose && response.getMessage() != null && response.getMessage().length() > 0) { onPrint((adbSerial != null ? adbSerial + ": " : "") + indent + "-> " + response.getStatus() + " : " + response.getMessage()); } } @Override public void onPrint(String message) { log(message); } }; private final SuiteListener suiteListener = new SuiteListener() { @Override public void onRunStart(int total) { } @Override public void onRunComplete(PlaybackResult result, Report report) { } @Override public void onTestStart(String name, int num, int total) { log((adbSerial != null ? adbSerial + ": " : "") + " " + num + " : " + name); } @Override public void onTestComplete(PlaybackResult result, Report report) { log((adbSerial != null ? adbSerial + ": " : "") + " -> " + result.getStatus() + (result.getMessage() != null && result.getMessage().length() > 0 ? " : " + result.getMessage() : "")); } @Override public void onSuiteStart(int total) { log((adbSerial != null ? adbSerial + ": " : "") + "-start suite (" + total + (total == 1 ? " test" : " tests") + ")"); } @Override public void onSuiteComplete(PlaybackResult result, Report report) { log((adbSerial != null ? adbSerial + ": " : "") + "-end suite"); if (callbackurl != null) { sendReport(); } } }; public RunTask() { port = -1; adbLocalPort = -1; adbRemotePort = -1; timeout = -1; thinktime = -1; startup = -1; } public void execute() throws BuildException { Runner runner = new Runner(agent, host, port); if (adb != null) { runner.setAdb(adb); } else if (agent != null && agent.equalsIgnoreCase("AndroidEmulator")) { File adb = AndroidUtils.getAdb(); runner.setAdb(adb); } if (adbSerial != null) { runner.setAgentProperty(AndroidEmulatorAgent.ADB_SERIAL_PROP, adbSerial); } if (adbLocalPort > 0) { runner.setAgentProperty(AndroidEmulatorAgent.ADB_LOCAL_PORT_PROP, Integer.toString(adbLocalPort)); } if (adbRemotePort > 0) { runner.setAgentProperty(AndroidEmulatorAgent.ADB_REMOTE_PORT_PROP, Integer.toString(adbRemotePort)); } runner.setReportdir(reportdir); runner.setScriptListener(scriptListener); runner.setSuiteListener(suiteListener); runner.setVerbose(false); runner.setGlobalTimeout(timeout); runner.setGlobalThinktime(thinktime); runner.setGlobalScreenshotOnError(screenshotOnError); runner.setTakeAfterScreenshot(screenshots); runner.setTakeAfterMetrics(screenshots); PlaybackResult result = null; try { if (script != null && suite != null) { throw new BuildException( "You cannot specify both script and suite in the run task."); } else if (script != null && text != null) { throw new BuildException( "You cannot specify both script and inline commands in the run task."); } else if (suite != null && text != null) { throw new BuildException( "You cannot specify both suite and inline commands in the run task."); } if (!runner.waitUntilReady(startup)) { throw new BuildException("Unable to startup MonkeyTalk connection - timeout after " + startup + "s"); } if (script != null) { if (verbose) { log("running script " + script.getName() + "..."); } result = runner.run(script, Globals.parse(globals)); } else if (suite != null) { if (verbose) { log("running suite " + suite.getName() + "..."); } runner.setScriptListener(scriptListenerForSuite); result = runner.run(suite, Globals.parse(globals)); } else if (text != null) { File tmp = new File(getProject().getBaseDir(), TEMP_FILE); String subbed = getProject().replaceProperties(text.trim()); try { FileUtils.writeFile(tmp, subbed); } catch (IOException ex) { throw new BuildException(ex); } result = runner.run(tmp, Globals.parse(globals)); tmp.delete(); } else { throw new BuildException("Nothing to run."); } } catch (RuntimeException ex) { throw new BuildException(ex.getMessage()); } if (result.getStatus() != PlaybackStatus.OK) { throw new BuildException(result.toString()); } if (verbose) { log("...done"); } } public void setAgent(String agent) { this.agent = agent; } public void setScript(File script) { if (script.getName().endsWith(CommandWorld.SUITE_EXT)) { // user gave us a suite instead of a script, so fix it for them this.suite = script; this.script = null; } else { this.script = script; } } public void setSuite(File suite) { this.suite = suite; } public void setHost(String host) { this.host = host; } public void setPort(int port) { this.port = port; } public void setAdb(File adb) { this.adb = adb; } public void setAdbSerial(String adbSerial) { this.adbSerial = adbSerial; } public void setAdbLocalPort(int adbLocalPort) { this.adbLocalPort = adbLocalPort; } public void setAdbRemotePort(int adbRemotePort) { this.adbRemotePort = adbRemotePort; } public void setReportdir(File reportdir) { this.reportdir = reportdir; } public void setVerbose(boolean verbose) { this.verbose = verbose; } public void setScreenshots(boolean screenshots) { this.screenshots = screenshots; } public void setScreenshotOnError(boolean screenshotOnError) { this.screenshotOnError = screenshotOnError; } public void setTimeout(int timeout) { this.timeout = timeout; } public void setThinktime(int thinktime) { this.thinktime = thinktime; } public void setStartup(int startup) { this.startup = startup; } public void setGlobals(String globals) { this.globals = globals; } public void setCallbackurl(String callbackurl) { this.callbackurl = callbackurl; } public void setJobrefparams(String jobrefparams) { this.jobrefparams = jobrefparams; } public void addText(String text) { this.text = text; } public void sendReport() { try { File zippedReports = FileUtils.zipDirectory(reportdir, true, false); System.out.println(zippedReports.getAbsolutePath()); Map<String, String> additionalParams = new HashMap<String, String>(); StringTokenizer st2 = new StringTokenizer(jobrefparams, ","); while (st2.hasMoreElements()) { String param = (String) st2.nextElement(); StringTokenizer st3 = new StringTokenizer(param, ":"); additionalParams.put((String) st3.nextElement(), (String) st3.nextElement()); } sendFormPost(callbackurl, zippedReports, additionalParams); } catch (IOException ex) { ex.printStackTrace(); } } private String sendFormPost(String url, File proj, Map<String, String> additionalParams) throws IOException { HttpClient base = new DefaultHttpClient(); SSLContext ctx = null; try { ctx = SSLContext.getInstance("TLS"); } catch (NoSuchAlgorithmException ex) { log("exception in sendFormPost():"); } X509TrustManager tm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws java.security.cert.CertificateException { } }; try { ctx.init(null, new TrustManager[] { tm }, null); } catch (KeyManagementException ex) { log("exception in sendFormPost():"); } SSLSocketFactory ssf = new SSLSocketFactory(ctx); ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); ClientConnectionManager ccm = base.getConnectionManager(); SchemeRegistry sr = ccm.getSchemeRegistry(); sr.register(new Scheme("https", ssf, 443)); HttpClient client = new DefaultHttpClient(ccm, base.getParams()); try { HttpPost post = new HttpPost(url); MultipartEntity multipart = new MultipartEntity(); for (String key : additionalParams.keySet()) multipart.addPart(key, new StringBody(additionalParams.get(key), Charset.forName("UTF-8"))); if (proj != null) { multipart.addPart("uploaded_file", new FileBody(proj)); } post.setEntity(multipart); HttpResponse resp = client.execute(post); HttpEntity out = resp.getEntity(); InputStream in = out.getContent(); return FileUtils.readStream(in); } catch (Exception ex) { throw new IOException("POST failed", ex); } finally { try { client.getConnectionManager().shutdown(); } catch (Exception ex) { // ignore } } } }