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.dev.shell.test;
017:
018: import com.google.gwt.core.client.GWT;
019: import com.google.gwt.junit.client.GWTTestCase;
020: import com.google.gwt.user.client.ui.Frame;
021: import com.google.gwt.user.client.ui.Label;
022: import com.google.gwt.user.client.ui.RootPanel;
023: import com.google.gwt.user.client.ui.VerticalPanel;
024:
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.Map;
028:
029: /**
030: * Tests unloading individual modules when more than one are loaded on a page,
031: * including in nested frames.
032: *
033: * The test will load up the initial configuration, then when all frames are
034: * done loading will toggle the first frame, then the second frame, then both at
035: * the same time. Buttons are provided for manual testing.
036: *
037: * Currently, there isn't much it can do to actually verify the proper behavior
038: * other than not crashing (which does not verify that the removed module isn't
039: * holding a lot of memory), but it is hard to do more than that without adding
040: * a lot of hooks that would only be used for this test. When tobyr's profiling
041: * changes are merged in, we will have to create some of the hooks to allow
042: * calls into the external object which could be used for the hooks for this
043: * test.
044: */
045: public class MultiModuleTest extends GWTTestCase {
046:
047: /**
048: * Used to setup the variable to keep track of things to be loaded, plus the
049: * JavaScript function used to communicate with the main module (the one that
050: * sets up the frames) from other modules.
051: *
052: * @param javaThis frameTest instance to use for callback
053: */
054: private static native void setupDoneLoading(MultiModuleTest javaThis) /*-{
055: $wnd.__count_to_be_loaded = 0;
056: $wnd.__done_loading = function() {
057: javaThis.@com.google.gwt.dev.shell.test.MultiModuleTest::doneLoading()();
058: };
059: }-*/;
060:
061: /**
062: * Used to setup the JavaScript function used to communicate with the main
063: * module (the one that sets up the frames) from other modules.
064: *
065: * @param javaThis frameTest instance to use for callback
066: */
067: private static native void setupTestComplete(
068: MultiModuleTest javaThis) /*-{
069: $wnd.__test_complete = function() {
070: javaThis.@com.google.gwt.dev.shell.test.MultiModuleTest::completedTest()();
071: };
072: }-*/;
073:
074: /**
075: * Child frames, which are unused in nested modules.
076: */
077: private Frame[] frame = new Frame[2];
078:
079: /**
080: * Flags indicating the "B" version of frame i is displayed, used to toggle
081: * the individual frames.
082: */
083: private boolean[] frameB = new boolean[2];
084:
085: /**
086: * The top-level panel for callbacks.
087: */
088: private VerticalPanel mainPanel = null;
089:
090: /**
091: * The state for automated frame toggles.
092: */
093: private int state;
094:
095: /**
096: * Get the name of the GWT module to use for this test.
097: *
098: * @return the fully-qualified module name
099: */
100: public String getModuleName() {
101: return "com.google.gwt.dev.shell.MultiModuleTest";
102: }
103:
104: /**
105: * Create the DOM elements for the module, based on the query string. The top
106: * level (query parameter frame=top) drives the process and sets up the
107: * automated state transition hooks.
108: *
109: * This function returns with no effect if gwt.junit.testfuncname is not
110: * passed as a query parameter, which means it is being run as a real test
111: * rather than as a "submodule" of testMultipleModules.
112: */
113: public void testInnerModules() {
114: String url = getURL();
115: Map params = getURLParams(url);
116: if (!params.containsKey("gwt.junit.testfuncname")) {
117: // if this test is being run as a normal JUnit test, return success
118: return;
119: }
120:
121: // we were invoked by testMultipleModules, get the frame to load
122: String frameName = (String) params.get("frame");
123:
124: VerticalPanel panel = new VerticalPanel();
125: RootPanel.get().add(panel);
126: if (frameName.equals("top")) {
127: // initial load
128: setupDoneLoading(this );
129: mainPanel = panel;
130: panel.add(new Label("Top level frame"));
131: state = 0;
132: params.put("frame", "1a");
133: frame[0] = new Frame(buildURL(url, params));
134: panel.add(frame[0]);
135: params.put("frame", "2a");
136: frame[1] = new Frame(buildURL(url, params));
137: panel.add(frame[1]);
138: addToBeLoaded(0, 2);
139: } else if (frameName.equals("1a")) {
140: panel.add(new Label("Frame 1a"));
141: markLoaded(1);
142: } else if (frameName.equals("1b")) {
143: panel.add(new Label("Frame 1b"));
144: markLoaded(1);
145: } else if (frameName.equals("2a")) {
146: panel.add(new Label("Frame 2a"));
147: params.put("frame", "2suba");
148: Frame sub = new Frame(buildURL(url, params));
149: panel.add(sub);
150: } else if (frameName.equals("2b")) {
151: panel.add(new Label("Frame 2b"));
152: params.put("frame", "2subb");
153: Frame sub = new Frame(buildURL(url, params));
154: panel.add(sub);
155: } else if (frameName.equals("2suba")) {
156: panel.add(new Label("Frame 2a inner"));
157: markLoaded(2);
158: } else if (frameName.equals("2subb")) {
159: panel.add(new Label("Frame 2b inner"));
160: markLoaded(2);
161: } else {
162: GWT.log("Unexpected frame name " + frameName, null);
163: }
164: }
165:
166: public void testMultipleModules() {
167: setupTestComplete(this );
168:
169: // build new URL from current one
170: String url = getURL();
171: Map params = getURLParams(url);
172: params.put("frame", "top");
173: params.put("gwt.junit.testfuncname", "testInnerModules");
174:
175: // open a new frame containing the module that drives the actual test
176: Frame frame = new Frame(buildURL(url, params));
177: frame.setHeight("100%");
178: frame.setWidth("100%");
179: RootPanel.get().add(frame);
180: // wait up to 60 seconds for inner frames module to do its job
181: delayTestFinish(60000);
182: }
183:
184: /**
185: * Increments the number of pages to be loaded. This count is kept in the
186: * context of the top-level module, so the depth parameter is provided to find
187: * it.
188: *
189: * @param depth nesting depth of this module, 0 = top level
190: * @param count number of pages due to be loaded
191: */
192: private native void addToBeLoaded(int depth, int count) /*-{
193: var frame = $wnd;
194: while (depth-- > 0) {
195: frame = frame.parent;
196: }
197: frame.__count_to_be_loaded += count;
198: }-*/;
199:
200: /**
201: * Create a URL given an old URL and a map of query parameters. The search
202: * portion of the original URL will be discarded and replaced with a string of
203: * the form ?param1¶m2=value2 etc., where param1 has a null value in the
204: * map.
205: *
206: * @param url the original URL to rewrite
207: * @param params a map of parameter names to values
208: * @return the revised URL
209: */
210: private String buildURL(String url, Map params) {
211:
212: // strip off the query string if present
213: int pos = url.indexOf("?");
214: if (pos >= 0) {
215: url = url.substring(0, pos);
216: }
217:
218: // flag if we are generating the first parameter in the URL
219: boolean firstParam = true;
220:
221: // gwt.hybrid must be first if present
222: if (params.containsKey("gwt.hybrid")) {
223: url += "?gwt.hybrid";
224: firstParam = false;
225: }
226:
227: // now add the rest of the parameters, excluding gwt.hybrid
228: for (Iterator it = params.entrySet().iterator(); it.hasNext();) {
229: Map.Entry entry = (Map.Entry) it.next();
230: String param = (String) entry.getKey();
231:
232: if (param.equals("gwt.hybrid")) {
233: // we already included gwt.hybrid if it was present
234: continue;
235: }
236:
237: // add the parameter name to the URL
238: if (firstParam) {
239: url += "?";
240: firstParam = false;
241: } else {
242: url += "&";
243: }
244: url += param;
245:
246: // add the value if necessary
247: String value = (String) entry.getValue();
248: if (value != null) {
249: url += "=" + value;
250: }
251: }
252: return url;
253: }
254:
255: /**
256: * Called via JSNI by testInnerModules when it successfully goes through
257: * all its iterations.
258: */
259: private void completedTest() {
260: // tell JUnit that we completed successfully
261: finishTest();
262: }
263:
264: /**
265: * Proceed to the next automatic state change if any. This is called in the
266: * context of the top-level module via JSNI calls when all modules being
267: * waited on are loaded.
268: */
269: private void doneLoading() {
270: String url = getURL();
271: Map params = getURLParams(url);
272: mainPanel.add(new Label("done loading"));
273: if (++state == 4) {
274: // all tests complete, notify parent
275: notifyParent();
276: }
277: if (state >= 4) {
278: return;
279: }
280: StringBuffer buf = new StringBuffer();
281: buf.append("Toggling frame(s)");
282: if ((state & 1) != 0) {
283: buf.append(" 0");
284: toggleFrame(0, url, params);
285: }
286: if ((state & 2) != 0) {
287: buf.append(" 1");
288: toggleFrame(1, url, params);
289: }
290: mainPanel.add(new Label(buf.toString()));
291: }
292:
293: /**
294: * Get the query string from the URL, including the question mark if present.
295: *
296: * @return the query string
297: */
298: private native String getURL() /*-{
299: return $wnd.location.href || '';
300: }-*/;
301:
302: /**
303: * Parse a URL and return a map of query parameters. If a parameter is
304: * supplied without =value, it will be defined as null.
305: *
306: * @param url the full or partial (ie, only location.search) URL to parse
307: * @return the map of parameter names to values
308: */
309: private Map getURLParams(String url) {
310: HashMap map = new HashMap();
311: int pos = url.indexOf("?");
312:
313: // loop precondition: pos is the index of the next ? or & character in url
314: while (pos >= 0) {
315: // skip over the separator character
316: url = url.substring(pos + 1);
317:
318: // find the end of this parameter, which is the next ? or &
319: pos = url.indexOf("?");
320: int posAlt = url.indexOf("&");
321: if (pos < 0 || (posAlt >= 0 && posAlt < pos)) {
322: pos = posAlt;
323: }
324: String param;
325: if (pos >= 0) {
326: // trim this parameter if there is a terminator
327: param = url.substring(0, pos);
328: } else {
329: param = url;
330: }
331:
332: // split value from parameter name if present
333: int equals = param.indexOf("=");
334: String value = null;
335: if (equals >= 0) {
336: value = param.substring(equals + 1);
337: param = param.substring(0, equals);
338: }
339:
340: map.put(param, value);
341: }
342: return map;
343: }
344:
345: /**
346: * Mark this page as loaded, using JSNI to mark it in the context of the
347: * top-level module space. If all outstanding modules have loaded, call the
348: * doneLoading method in the top-level module space (using JSNI and the depth
349: * to find it).
350: *
351: * @param depth nesting depth of this module, 0 = top level
352: */
353: private native void markLoaded(int depth) /*-{
354: var frame = $wnd;
355: while (depth-- > 0) {
356: frame = frame.parent;
357: }
358: if (!--frame.__count_to_be_loaded) {
359: frame.__done_loading();
360: }
361: }-*/;
362:
363: /**
364: * Notify our parent frame that the test is complete.
365: */
366: private native void notifyParent() /*-{
367: $wnd.parent.__test_complete();
368: }-*/;
369:
370: /**
371: * Replace the specified frame with its alternate version.
372: *
373: * @param frameNumber the number of the frame to replace, starting with 0
374: */
375: private void toggleFrame(int frameNumber, String url, Map params) {
376: params.put("frame", (frameNumber + 1)
377: + (frameB[frameNumber] ? "a" : "b"));
378: frame[frameNumber].setUrl(buildURL(url, params));
379: frameB[frameNumber] = !frameB[frameNumber];
380: addToBeLoaded(0, 1);
381: }
382: }
|