/* * Copyright 2008 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.gwt.junit.server; import static com.google.gwt.user.client.rpc.RpcRequestBuilder.MODULE_BASE_HEADER; import com.google.gwt.core.server.impl.StackTraceDeobfuscator; import com.google.gwt.junit.JUnitFatalLaunchException; import com.google.gwt.junit.JUnitMessageQueue; import com.google.gwt.junit.JUnitMessageQueue.ClientInfoExt; import com.google.gwt.junit.JUnitShell; import com.google.gwt.junit.client.TimeoutException; import com.google.gwt.junit.client.impl.JUnitHost; import com.google.gwt.junit.client.impl.JUnitResult; import com.google.gwt.junit.linker.JUnitSymbolMapsLinker; import com.google.gwt.user.client.rpc.InvocationException; import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * An RPC servlet that serves as a proxy to JUnitTestShell. Enables * communication between the unit test code running in a browser and the real * test process. */ public class JUnitHostImpl extends RemoteServiceServlet implements JUnitHost { /** * A hook into GWTUnitTestShell, the underlying unit test process. */ private static JUnitMessageQueue sHost = null; /** * A maximum timeout to wait for the test system to respond with the next * test. The test system should respond nearly instantly if there are further * tests to run, unless the tests have not yet been compiled. */ private static final int TIME_TO_WAIT_FOR_TESTNAME = 300000; /** * Monotonic increase counter to create unique client session ids. */ private static final AtomicInteger uniqueSessionId = new AtomicInteger(); /** * Tries to grab the GWTUnitTestShell sHost environment to communicate with * the real test process. */ private static synchronized JUnitMessageQueue getHost() { if (sHost == null) { sHost = JUnitShell.getMessageQueue(); if (sHost == null) { throw new InvocationException( "Unable to find JUnitShell; is this servlet running under GWTTestCase?"); } } return sHost; } private StackTraceDeobfuscator deobfuscator; public InitialResponse getTestBlock(int blockIndex, ClientInfo clientInfo) throws TimeoutException { ClientInfoExt clientInfoExt; HttpServletRequest request = getThreadLocalRequest(); if (clientInfo.getSessionId() < 0) { clientInfoExt = createNewClientInfo(clientInfo.getUserAgent(), request); } else { clientInfoExt = createClientInfo(clientInfo, request); } TestBlock initialTestBlock = getHost().getTestBlock(clientInfoExt, blockIndex, TIME_TO_WAIT_FOR_TESTNAME); // Send back the updated session id. return new InitialResponse(clientInfoExt.getSessionId(), initialTestBlock); } public TestBlock reportResultsAndGetTestBlock( HashMap<TestInfo, JUnitResult> results, int testBlock, ClientInfo clientInfo) throws TimeoutException { for (JUnitResult result : results.values()) { initResult(getThreadLocalRequest(), result); } JUnitMessageQueue host = getHost(); ClientInfoExt clientInfoExt = createClientInfo(clientInfo, getThreadLocalRequest()); host.reportResults(clientInfoExt, results); return host.getTestBlock(clientInfoExt, testBlock, TIME_TO_WAIT_FOR_TESTNAME); } @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String requestURI = request.getRequestURI(); if (requestURI.endsWith("/junithost/error")) { String msg = RPCServletUtils.readContentAsGwtRpc(request); System.err.println("Warning: " + msg); } else if (requestURI.endsWith("/junithost/error/fatal")) { String msg = RPCServletUtils.readContentAsGwtRpc(request); System.err.println("Fatal error: " + msg); System.exit(1); } else if (requestURI.endsWith("/junithost/error/launch")) { String requestPayload = RPCServletUtils.readContentAsGwtRpc(request); JUnitResult result = new JUnitResult(); initResult(request, result); result.setException(new JUnitFatalLaunchException(requestPayload)); getHost().reportFatalLaunch(createNewClientInfo(null, request), result); } else { super.service(request, response); } } private ClientInfoExt createClientInfo(ClientInfo clientInfo, HttpServletRequest request) { assert (clientInfo.getSessionId() >= 0); return new ClientInfoExt(clientInfo.getSessionId(), clientInfo.getUserAgent(), getClientDesc(request)); } private ClientInfoExt createNewClientInfo(String userAgent, HttpServletRequest request) { return new ClientInfoExt(createSessionId(), userAgent, getClientDesc(request)); } private int createSessionId() { return uniqueSessionId.getAndIncrement(); } /** * Returns a client description for the current request. */ private String getClientDesc(HttpServletRequest request) { String machine = request.getRemoteHost(); String agent = request.getHeader("User-Agent"); return machine + " / " + agent; } /** * Extract the module's base path from the current request. * * @return the module's base path, modulo protocol and host, as reported by * {@link com.google.gwt.core.client.GWT#getModuleBaseURL()} or * <code>null</code> if the request did not contain the * {@value com.google.gwt.user.client.rpc.RpcRequestBuilder#MODULE_BASE_HEADER} header */ private String getRequestModuleBasePath() { try { String header = getThreadLocalRequest().getHeader(MODULE_BASE_HEADER); if (header == null) { return null; } String path = new URL(header).getPath(); String contextPath = getThreadLocalRequest().getContextPath(); if (!path.startsWith(contextPath)) { return null; } return path.substring(contextPath.length()); } catch (MalformedURLException e) { return null; } } private void initResult(HttpServletRequest request, JUnitResult result) { result.setAgent(request.getHeader("User-Agent")); result.setHost(request.getRemoteHost()); Throwable throwable = result.getException(); if (throwable != null) { deobfuscateStackTrace(throwable); } } private void deobfuscateStackTrace(Throwable throwable) { try { getDeobfuscator().deobfuscateStackTrace(throwable, getPermutationStrongName()); } catch (IOException e) { System.err.println("Unable to deobfuscate a stack trace due to an error:"); e.printStackTrace(); } } private StackTraceDeobfuscator getDeobfuscator() throws IOException { if (deobfuscator == null) { String path = getRequestModuleBasePath() + "/" + JUnitSymbolMapsLinker.SYMBOL_MAP_DIR; deobfuscator = StackTraceDeobfuscator.fromUrl(getServletContext().getResource(path)); } return deobfuscator; } }