001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.junit.server;
017:
018: import com.google.gwt.junit.JUnitMessageQueue;
019: import com.google.gwt.junit.JUnitShell;
020: import com.google.gwt.junit.client.impl.ExceptionWrapper;
021: import com.google.gwt.junit.client.impl.JUnitHost;
022: import com.google.gwt.junit.client.impl.StackTraceWrapper;
023: import com.google.gwt.junit.client.TestResults;
024: import com.google.gwt.junit.client.Trial;
025: import com.google.gwt.user.client.rpc.InvocationException;
026: import com.google.gwt.user.server.rpc.RemoteServiceServlet;
027:
028: import java.lang.reflect.Constructor;
029: import java.lang.reflect.Field;
030: import java.util.List;
031:
032: import javax.servlet.http.HttpServletRequest;
033:
034: /**
035: * An RPC servlet that serves as a proxy to JUnitTestShell. Enables
036: * communication between the unit test code running in a browser and the real
037: * test process.
038: */
039: public class JUnitHostImpl extends RemoteServiceServlet implements
040: JUnitHost {
041:
042: /**
043: * A maximum timeout to wait for the test system to respond with the next
044: * test. Practically speaking, the test system should respond nearly instantly
045: * if there are further tests to run.
046: */
047: private static final int TIME_TO_WAIT_FOR_TESTNAME = 300000;
048:
049: // DEBUG timeout
050: // TODO(tobyr) Make this configurable
051: // private static final int TIME_TO_WAIT_FOR_TESTNAME = 500000;
052:
053: /**
054: * A hook into GWTUnitTestShell, the underlying unit test process.
055: */
056: private static JUnitMessageQueue sHost = null;
057:
058: /**
059: * Tries to grab the GWTUnitTestShell sHost environment to communicate with
060: * the real test process.
061: */
062: private static synchronized JUnitMessageQueue getHost() {
063: if (sHost == null) {
064: sHost = JUnitShell.getMessageQueue();
065: if (sHost == null) {
066: throw new InvocationException(
067: "Unable to find JUnitShell; is this servlet running under GWTTestCase?");
068: }
069: }
070: return sHost;
071: }
072:
073: /**
074: * Simple helper method to set inaccessible fields via reflection.
075: */
076: private static <T> void setField(Class<T> cls, String fieldName,
077: T obj, Object value) throws SecurityException,
078: NoSuchFieldException, IllegalArgumentException,
079: IllegalAccessException {
080: Field fld = cls.getDeclaredField(fieldName);
081: fld.setAccessible(true);
082: fld.set(obj, value);
083: }
084:
085: public String getFirstMethod(String testClassName) {
086: return getHost().getNextTestName(getClientId(), testClassName,
087: TIME_TO_WAIT_FOR_TESTNAME);
088: }
089:
090: public String reportResultsAndGetNextMethod(String testClassName,
091: TestResults results) {
092: JUnitMessageQueue host = getHost();
093: HttpServletRequest request = getThreadLocalRequest();
094: String agent = request.getHeader("User-Agent");
095: results.setAgent(agent);
096: String machine = request.getRemoteHost();
097: results.setHost(machine);
098: List<Trial> trials = results.getTrials();
099: for (Trial trial : trials) {
100: ExceptionWrapper ew = trial.getExceptionWrapper();
101: trial.setException(deserialize(ew));
102: }
103: host.reportResults(testClassName, results);
104: return host.getNextTestName(getClientId(), testClassName,
105: TIME_TO_WAIT_FOR_TESTNAME);
106: }
107:
108: /**
109: * Deserializes an ExceptionWrapper back into a Throwable.
110: */
111: private Throwable deserialize(ExceptionWrapper ew) {
112: if (ew == null) {
113: return null;
114: }
115:
116: Throwable ex = null;
117: Throwable cause = deserialize(ew.cause);
118: try {
119: Class<?> exClass = Class.forName(ew.typeName);
120: try {
121: // try ExType(String, Throwable)
122: Constructor<?> ctor = exClass.getDeclaredConstructor(
123: String.class, Throwable.class);
124: ctor.setAccessible(true);
125: ex = (Throwable) ctor.newInstance(ew.message, cause);
126: } catch (Throwable e) {
127: // try ExType(String)
128: try {
129: Constructor<?> ctor = exClass
130: .getDeclaredConstructor(String.class);
131: ctor.setAccessible(true);
132: ex = (Throwable) ctor.newInstance(ew.message);
133: ex.initCause(cause);
134: } catch (Throwable e2) {
135: // try ExType(Throwable)
136: try {
137: Constructor<?> ctor = exClass
138: .getDeclaredConstructor(Throwable.class);
139: ctor.setAccessible(true);
140: ex = (Throwable) ctor.newInstance(cause);
141: setField(Throwable.class, "detailMessage", ex,
142: ew.message);
143: } catch (Throwable e3) {
144: // try ExType()
145: try {
146: Constructor<?> ctor = exClass
147: .getDeclaredConstructor();
148: ctor.setAccessible(true);
149: ex = (Throwable) ctor.newInstance();
150: ex.initCause(cause);
151: setField(Throwable.class, "detailMessage",
152: ex, ew.message);
153: } catch (Throwable e4) {
154: // we're out of options
155: this
156: .log(
157: "Failed to deserialize getException of type '"
158: + ew.typeName
159: + "'; no available constructor",
160: e4);
161:
162: // fall through
163: }
164: }
165: }
166: }
167:
168: } catch (Throwable e) {
169: this .log("Failed to deserialize getException of type '"
170: + ew.typeName + "'", e);
171: }
172:
173: if (ex == null) {
174: ex = new RuntimeException(ew.typeName + ": " + ew.message,
175: cause);
176: }
177:
178: ex.setStackTrace(deserialize(ew.stackTrace));
179: return ex;
180: }
181:
182: /**
183: * Deserializes a StackTraceWrapper back into a StackTraceElement.
184: */
185: private StackTraceElement deserialize(StackTraceWrapper stw) {
186: StackTraceElement ste = null;
187: Object[] args = new Object[] { stw.className, stw.methodName,
188: stw.fileName, stw.lineNumber };
189: try {
190: try {
191: // Try the 4-arg ctor (JRE 1.5)
192: Constructor<StackTraceElement> ctor = StackTraceElement.class
193: .getDeclaredConstructor(String.class,
194: String.class, String.class, int.class);
195: ctor.setAccessible(true);
196: ste = ctor.newInstance(args);
197: } catch (NoSuchMethodException e) {
198: // Okay, see if there's a zero-arg ctor we can use instead (JRE 1.4.2)
199: Constructor<StackTraceElement> ctor = StackTraceElement.class
200: .getDeclaredConstructor();
201: ctor.setAccessible(true);
202: ste = ctor.newInstance();
203: setField(StackTraceElement.class, "declaringClass",
204: ste, args[0]);
205: setField(StackTraceElement.class, "methodName", ste,
206: args[1]);
207: setField(StackTraceElement.class, "fileName", ste,
208: args[2]);
209: setField(StackTraceElement.class, "lineNumber", ste,
210: args[3]);
211: }
212: } catch (Throwable e) {
213: this .log("Error creating stack trace", e);
214: }
215: return ste;
216: }
217:
218: /**
219: * Deserializes a StackTraceWrapper[] back into a StackTraceElement[].
220: */
221: private StackTraceElement[] deserialize(
222: StackTraceWrapper[] stackTrace) {
223: int len = stackTrace.length;
224: StackTraceElement[] result = new StackTraceElement[len];
225: for (int i = 0; i < len; ++i) {
226: result[i] = deserialize(stackTrace[i]);
227: }
228: return result;
229: }
230:
231: /**
232: * Returns a "client id" for the current request.
233: */
234: private String getClientId() {
235: HttpServletRequest request = getThreadLocalRequest();
236: String agent = request.getHeader("User-Agent");
237: String machine = request.getRemoteHost();
238: return machine + " / " + agent;
239: }
240: }
|