/* * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.jstestdriver; import static java.lang.String.format; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.google.jstestdriver.JsonCommand.CommandType; import com.google.jstestdriver.Response.ResponseType; import com.google.jstestdriver.browser.BrowserPanicException; import com.google.jstestdriver.util.StopWatch; /** * Handles the communication of a command to the JsTestDriverServer from the * JsTestDriverClient. * * @author jeremiele@google.com (Jeremie Lenfant-Engelmann) */ public class CommandTask { private static final Logger logger = LoggerFactory.getLogger(CommandTask.class); private static final List<String> EMPTY_ARRAYLIST = new ArrayList<String>(); private static final long WAIT_INTERVAL = 500L; public static final int CHUNK_SIZE = 50; private final Gson gson = new Gson(); private final JsTestDriverFileFilter filter; private final ResponseStream stream; private final Set<FileInfo> fileSet; private final String baseUrl; private final Server server; private final Map<String, String> params; private final HeartBeatManager heartBeatManager; private final FileLoader fileLoader; private final boolean upload; private final StopWatch stopWatch; public CommandTask(JsTestDriverFileFilter filter, ResponseStream stream, Set<FileInfo> fileSet, String baseUrl, Server server, Map<String, String> params, HeartBeatManager heartBeatManager, FileLoader fileLoader, boolean upload, StopWatch stopWatch) { this.filter = filter; this.stream = stream; this.fileSet = fileSet; this.baseUrl = baseUrl; this.server = server; this.params = params; this.heartBeatManager = heartBeatManager; this.fileLoader = fileLoader; this.upload = upload; this.stopWatch = stopWatch; } private String startSession() { String browserId = params.get("id"); String sessionId = server.startSession(baseUrl, browserId); if ("FAILED".equals(sessionId)) { while ("FAILED".equals(sessionId)) { try { Thread.sleep(WAIT_INTERVAL); } catch (InterruptedException e) { logger.error("Could not create session for browser: " + browserId); return ""; } sessionId = server.startSession(baseUrl, browserId); } } return sessionId; } private void stopSession(String sessionId) { server.stopSession(baseUrl, params.get("id"), sessionId); } /** * Throws an exception if the expected browser is not available for this task. */ private void checkBrowser() { String alive = server.fetch(baseUrl + "/heartbeat?id=" + params.get("id")); if (!alive.equals("OK")) { throw new FailureException( format("Browser is not available\n {} \nfor\n {}", alive, params)); } } // TODO(corysmith): remove this function once FileInfo is used exclusively. // Hate static crap. public static FileSource fileInfoToFileSource(FileInfo info) { if (info.getFilePath().startsWith("http://")) { return new FileSource(info.getFilePath(), info.getTimestamp()); } return new FileSource("/test/" + info.getFilePath(), info.getTimestamp()); } /** * @param response The response that might be a panic. * @param during A string that indicates when the browser paniced. */ private void shouldPanic(Response response, String during) { if (response.getResponseType() == ResponseType.BROWSER_PANIC) { BrowserPanic panic = gson.fromJson(response.getResponse(), response.getResponseType().type); BrowserPanicException exception = new BrowserPanicException(panic.getBrowserInfo(), during); logger.error("Browser not found : {}\n during: {} \n Exception: {}", new Object[]{response.getResponse(), during, exception}); throw exception; } } private void uploadFileSet() { stopWatch.start("determine upload %s", params); Map<String, String> fileSetParams = new LinkedHashMap<String, String>(); fileSetParams.put("id", params.get("id")); fileSetParams.put("fileSet", gson.toJson(fileSet)); String postResult = server.post(baseUrl + "/fileSet", fileSetParams); if (postResult.length() > 0) { Collection<FileInfo> filesToUpload = gson.fromJson(postResult, new TypeToken<Collection<FileInfo>>() {}.getType()); // should reset if the files are the same, because there could be other files on // the server. boolean shouldReset = sameFiles(filesToUpload, fileSet); Set<FileInfo> finalFilesToUpload = new LinkedHashSet<FileInfo>(); if (shouldReset) { JsonCommand cmd = new JsonCommand(CommandType.RESET, EMPTY_ARRAYLIST); Map<String, String> resetParams = new LinkedHashMap<String, String>(); resetParams.put("id", params.get("id")); resetParams.put("data", gson.toJson(cmd)); server.post(baseUrl + "/cmd", resetParams); logger.debug("Starting File Upload Refresh for {}", params.get("id")); String jsonResponse = server.fetch(baseUrl + "/cmd?id=" + params.get("id")); StreamMessage message = gson.fromJson(jsonResponse, StreamMessage.class); Response response = message.getResponse(); shouldPanic(response, "File upload reset"); logger.debug("Finished File Upload Refresh for {}", params.get("id")); finalFilesToUpload.addAll(filesToUpload); } else { for (FileInfo file : filesToUpload) { finalFilesToUpload.addAll(findDependencies(file)); } } List<FileInfo> loadedfiles = fileLoader.loadFiles(finalFilesToUpload, shouldReset); Map<String, String> uploadFileParams = new LinkedHashMap<String, String>(); uploadFileParams.put("id", params.get("id")); uploadFileParams.put("data", gson.toJson(loadedfiles)); stopWatch.stop("determine upload %s", params); stopWatch.start("upload to server %s", params); server.post(baseUrl + "/fileSet", uploadFileParams); stopWatch.stop("upload to server %s", params); List<FileSource> filesSrc = new LinkedList<FileSource>(filterFilesToLoad(loadedfiles)); int numberOfFilesToLoad = filesSrc.size(); stopWatch.start("load in browser %s", params); for (int i = 0; i < numberOfFilesToLoad; i += CHUNK_SIZE) { int chunkEndIndex = Math.min(i + CHUNK_SIZE, numberOfFilesToLoad); List<String> loadParameters = new LinkedList<String>(); List<FileSource> filesToLoad = filesSrc.subList(i, chunkEndIndex); loadParameters.add(gson.toJson(filesToLoad)); loadParameters.add("false"); JsonCommand cmd = new JsonCommand(CommandType.LOADTEST, loadParameters); Map<String, String> loadFileParams = new LinkedHashMap<String, String>(); loadFileParams.put("id", params.get("id")); loadFileParams.put("data", gson.toJson(cmd)); logger.debug("Sending LOADTEST for {}", params.get("id")); server.post(baseUrl + "/cmd", loadFileParams); String jsonResponse = server.fetch(baseUrl + "/cmd?id=" + params.get("id")); StreamMessage message = gson.fromJson(jsonResponse, StreamMessage.class); Response response = message.getResponse(); logger.debug("LOADTEST finished for ({}) {}", params.get("id"), response.getBrowser()); shouldPanic(response, "Loading files into the browser."); stream.stream(response); } stopWatch.stop("load in browser %s", params); } } private Collection<FileInfo> findDependencies(FileInfo file) { List<FileInfo> deps = new LinkedList<FileInfo>(); // TODO(jeremiele): replace filter with a plugin for (String fileName : filter.resolveFilesDeps(file.getFilePath())) { deps.add(new FileInfo(fileName, new File(fileName).lastModified(), false, false, null)); } return deps; } private List<FileSource> filterFilesToLoad(Collection<FileInfo> fileInfos) { List<FileSource> filteredFileSources = new LinkedList<FileSource>(); for (FileInfo fileInfo : fileInfos) { if (!fileInfo.isServeOnly()) { filteredFileSources.add(fileInfoToFileSource(fileInfo)); } } return filteredFileSources; } public void run() { heartBeatManager.startTimer(); String browserId = params.get("id"); String sessionId = null; try { stopWatch.start("session start %s", params.get("data")); sessionId = startSession(); stopWatch.stop("session start %s", params.get("data")); if (!"".equals(sessionId)) { heartBeatManager.startHeartBeat(baseUrl, browserId, sessionId); } else { throw new FailureException("Can't start a session on the server!" + params); } checkBrowser(); logger.debug("Starting upload for {}", browserId); if (upload) { uploadFileSet(); } logger.debug("Finished upload for {}", browserId); server.post(baseUrl + "/cmd", params); StreamMessage streamMessage = null; stopWatch.start("execution %s", params.get("data")); logger.debug("Starting {} for {}", params.get("data"), browserId); do { String response = server.fetch(baseUrl + "/cmd?id=" + browserId); streamMessage = gson.fromJson(response, StreamMessage.class); Response resObj = streamMessage.getResponse(); shouldPanic(resObj, "execution of command"); stream.stream(resObj); } while (!streamMessage.isLast()); stopWatch.stop("execution %s", params.get("data")); } finally { heartBeatManager.cancelTimer(); stopWatch.start("session stop %s", params.get("data")); stopSession(sessionId); stopWatch.stop("session stop %s", params.get("data")); logger.debug("finished {} for {}", params.get("data"), browserId); } } private boolean sameFiles(Collection<FileInfo> filesToUpload, Collection<FileInfo> fileSet) { for (FileInfo info : fileSet) { if (!info.isServeOnly() && !filesToUpload.contains(info)) { return false; } } return true; } }