1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/tests/SessionTest.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,398 @@ 1.4 +package org.mozilla.gecko.tests; 1.5 + 1.6 +import java.io.File; 1.7 +import java.io.FileReader; 1.8 +import java.io.FileWriter; 1.9 +import java.io.IOException; 1.10 + 1.11 +import org.json.JSONArray; 1.12 +import org.json.JSONException; 1.13 +import org.json.JSONObject; 1.14 +import org.mozilla.gecko.Actions; 1.15 +import org.mozilla.gecko.Assert; 1.16 +import org.mozilla.gecko.FennecMochitestAssert; 1.17 + 1.18 +public abstract class SessionTest extends BaseTest { 1.19 + protected Navigation mNavigation; 1.20 + 1.21 + @Override 1.22 + public void setUp() throws Exception { 1.23 + super.setUp(); 1.24 + 1.25 + mNavigation = new Navigation(mDevice); 1.26 + } 1.27 + 1.28 + /** 1.29 + * A generic session object representing a collection of items that has a 1.30 + * selected index. 1.31 + */ 1.32 + protected abstract class SessionObject<T> { 1.33 + private final int mIndex; 1.34 + private final T[] mItems; 1.35 + 1.36 + public SessionObject(int index, T... items) { 1.37 + mIndex = index; 1.38 + mItems = items; 1.39 + } 1.40 + 1.41 + public int getIndex() { 1.42 + return mIndex; 1.43 + } 1.44 + 1.45 + public T[] getItems() { 1.46 + return mItems; 1.47 + } 1.48 + } 1.49 + 1.50 + protected class PageInfo { 1.51 + private String url; 1.52 + private String title; 1.53 + 1.54 + public PageInfo(String key) { 1.55 + if (key.startsWith("about:")) { 1.56 + url = key; 1.57 + } else { 1.58 + url = getPage(key); 1.59 + } 1.60 + title = key; 1.61 + } 1.62 + } 1.63 + 1.64 + protected class SessionTab extends SessionObject<PageInfo> { 1.65 + public SessionTab(int index, PageInfo... items) { 1.66 + super(index, items); 1.67 + } 1.68 + } 1.69 + 1.70 + protected class Session extends SessionObject<SessionTab> { 1.71 + public Session(int index, SessionTab... items) { 1.72 + super(index, items); 1.73 + } 1.74 + } 1.75 + 1.76 + /** 1.77 + * Walker for visiting items in a browser-like navigation order. 1.78 + */ 1.79 + protected abstract class NavigationWalker<T> { 1.80 + private final T[] mItems; 1.81 + private final int mIndex; 1.82 + 1.83 + public NavigationWalker(SessionObject<T> obj) { 1.84 + mItems = obj.getItems(); 1.85 + mIndex = obj.getIndex(); 1.86 + } 1.87 + 1.88 + /** 1.89 + * Walks over the list of items, calling the onItem() callback for each. 1.90 + * 1.91 + * The selected item is the first item visited. Each item after the 1.92 + * selected item is then visited in ascending index order. Finally, the 1.93 + * list is iterated in reverse, and each item before the selected item 1.94 + * is visited in descending index order. 1.95 + */ 1.96 + public void walk() { 1.97 + onItem(mItems[mIndex], mIndex); 1.98 + for (int i = mIndex + 1; i < mItems.length; i++) { 1.99 + goForward(); 1.100 + onItem(mItems[i], i); 1.101 + } 1.102 + if (mIndex > 0) { 1.103 + for (int i = mItems.length - 2; i >= 0; i--) { 1.104 + goBack(); 1.105 + if (i < mIndex) { 1.106 + onItem(mItems[i], i); 1.107 + } 1.108 + } 1.109 + } 1.110 + } 1.111 + 1.112 + /** 1.113 + * Callback when an item is visited during a walk. 1.114 + * 1.115 + * Only one callback is executed per item. 1.116 + */ 1.117 + public abstract void onItem(T item, int currentIndex); 1.118 + 1.119 + /** 1.120 + * Callback executed for each back step of the walk. 1.121 + */ 1.122 + public void goBack() {} 1.123 + 1.124 + /** 1.125 + * Callback executed for each forward step of the walk. 1.126 + */ 1.127 + public void goForward() {} 1.128 + } 1.129 + 1.130 + /** 1.131 + * Loads a set of tabs in the browser specified by the given session. 1.132 + * 1.133 + * @param session Session to load 1.134 + */ 1.135 + protected void loadSessionTabs(Session session) { 1.136 + // Verify initial about:home tab 1.137 + verifyTabCount(1); 1.138 + verifyUrl("about:home"); 1.139 + 1.140 + SessionTab[] tabs = session.getItems(); 1.141 + for (int i = 0; i < tabs.length; i++) { 1.142 + final SessionTab tab = tabs[i]; 1.143 + final PageInfo[] pages = tab.getItems(); 1.144 + 1.145 + // New tabs always start with about:home, so make sure about:home 1.146 + // is always the first entry. 1.147 + mAsserter.is(pages[0].url, "about:home", "first page in tab is about:home"); 1.148 + 1.149 + // If this is the first tab, the tab already exists, so no need to 1.150 + // create a new one. Otherwise, create a new tab if we're loading 1.151 + // the first the first page in the set. 1.152 + if (i > 0) { 1.153 + addTab(); 1.154 + } 1.155 + 1.156 + for (int j = 1; j < pages.length; j++) { 1.157 + Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); 1.158 + 1.159 + loadUrl(pages[j].url); 1.160 + 1.161 + pageShowExpecter.blockForEvent(); 1.162 + pageShowExpecter.unregisterListener(); 1.163 + } 1.164 + 1.165 + final int index = tab.getIndex(); 1.166 + for (int j = pages.length - 1; j > index; j--) { 1.167 + mNavigation.back(); 1.168 + } 1.169 + } 1.170 + 1.171 + selectTabAt(session.getIndex()); 1.172 + } 1.173 + 1.174 + /** 1.175 + * Verifies that the set of open tabs matches the given session. 1.176 + * 1.177 + * @param session Session to verify 1.178 + */ 1.179 + protected void verifySessionTabs(Session session) { 1.180 + verifyTabCount(session.getItems().length); 1.181 + 1.182 + (new NavigationWalker<SessionTab>(session) { 1.183 + boolean mFirstTabVisited; 1.184 + 1.185 + @Override 1.186 + public void onItem(SessionTab tab, int currentIndex) { 1.187 + // The first tab to check should already be selected at startup 1.188 + if (mFirstTabVisited) { 1.189 + selectTabAt(currentIndex); 1.190 + } else { 1.191 + mFirstTabVisited = true; 1.192 + } 1.193 + 1.194 + (new NavigationWalker<PageInfo>(tab) { 1.195 + @Override 1.196 + public void onItem(PageInfo page, int currentIndex) { 1.197 + if (page.url.equals("about:home")) { 1.198 + waitForText("Enter Search or Address"); 1.199 + verifyUrl(page.url); 1.200 + } else { 1.201 + waitForText(page.title); 1.202 + verifyPageTitle(page.title); 1.203 + } 1.204 + } 1.205 + 1.206 + @Override 1.207 + public void goBack() { 1.208 + mNavigation.back(); 1.209 + } 1.210 + 1.211 + @Override 1.212 + public void goForward() { 1.213 + mNavigation.forward(); 1.214 + } 1.215 + }).walk(); 1.216 + } 1.217 + }).walk(); 1.218 + } 1.219 + 1.220 + /** 1.221 + * Gets session restore JSON corresponding to the open session. 1.222 + * 1.223 + * The JSON format follows the format used in Gecko for session restore and 1.224 + * should be interchangeable with the Gecko's generated sessionstore.js. 1.225 + * 1.226 + * @param session Session to serialize 1.227 + * @return JSON string of session 1.228 + */ 1.229 + protected String buildSessionJSON(Session session) { 1.230 + final SessionTab[] sessionTabs = session.getItems(); 1.231 + String sessionString = null; 1.232 + 1.233 + try { 1.234 + final JSONArray tabs = new JSONArray(); 1.235 + 1.236 + for (int i = 0; i < sessionTabs.length; i++) { 1.237 + final JSONObject tab = new JSONObject(); 1.238 + final JSONArray entries = new JSONArray(); 1.239 + final SessionTab sessionTab = sessionTabs[i]; 1.240 + final PageInfo[] pages = sessionTab.getItems(); 1.241 + 1.242 + for (int j = 0; j < pages.length; j++) { 1.243 + final PageInfo page = pages[j]; 1.244 + final JSONObject entry = new JSONObject(); 1.245 + entry.put("url", page.url); 1.246 + entry.put("title", page.title); 1.247 + entries.put(entry); 1.248 + } 1.249 + 1.250 + tab.put("entries", entries); 1.251 + tab.put("index", sessionTab.getIndex() + 1); 1.252 + tabs.put(tab); 1.253 + } 1.254 + 1.255 + JSONObject window = new JSONObject(); 1.256 + window.put("tabs", tabs); 1.257 + window.put("selected", session.getIndex() + 1); 1.258 + sessionString = new JSONObject().put("windows", new JSONArray().put(window)).toString(); 1.259 + } catch (JSONException e) { 1.260 + mAsserter.ok(false, "JSON exception", getStackTraceString(e)); 1.261 + } 1.262 + 1.263 + return sessionString; 1.264 + } 1.265 + 1.266 + /** 1.267 + * @see SessionTest#verifySessionJSON(Session, String, Assert) 1.268 + */ 1.269 + protected void verifySessionJSON(Session session, String sessionString) { 1.270 + verifySessionJSON(session, sessionString, mAsserter); 1.271 + } 1.272 + 1.273 + /** 1.274 + * Verifies a session JSON string against the given session. 1.275 + * 1.276 + * @param session Session to verify against 1.277 + * @param sessionString JSON string to verify 1.278 + * @param asserter Assert class to use during verification 1.279 + */ 1.280 + protected void verifySessionJSON(Session session, String sessionString, Assert asserter) { 1.281 + final SessionTab[] sessionTabs = session.getItems(); 1.282 + 1.283 + try { 1.284 + final JSONObject window = new JSONObject(sessionString).getJSONArray("windows").getJSONObject(0); 1.285 + final JSONArray tabs = window.getJSONArray("tabs"); 1.286 + final int optSelected = window.optInt("selected", -1); 1.287 + 1.288 + asserter.is(optSelected, session.getIndex() + 1, "selected tab matches"); 1.289 + 1.290 + for (int i = 0; i < tabs.length(); i++) { 1.291 + final JSONObject tab = tabs.getJSONObject(i); 1.292 + final int index = tab.getInt("index"); 1.293 + final JSONArray entries = tab.getJSONArray("entries"); 1.294 + final SessionTab sessionTab = sessionTabs[i]; 1.295 + final PageInfo[] pages = sessionTab.getItems(); 1.296 + 1.297 + asserter.is(index, sessionTab.getIndex() + 1, "selected page index matches"); 1.298 + 1.299 + for (int j = 0; j < entries.length(); j++) { 1.300 + final JSONObject entry = entries.getJSONObject(j); 1.301 + final String url = entry.getString("url"); 1.302 + final String title = entry.optString("title"); 1.303 + final PageInfo page = pages[j]; 1.304 + 1.305 + asserter.is(url, page.url, "URL in JSON matches session URL"); 1.306 + if (!page.url.startsWith("about:")) { 1.307 + asserter.is(title, page.title, "title in JSON matches session title"); 1.308 + } 1.309 + } 1.310 + } 1.311 + } catch (JSONException e) { 1.312 + asserter.ok(false, "JSON exception", getStackTraceString(e)); 1.313 + } 1.314 + } 1.315 + 1.316 + /** 1.317 + * Exception thrown by NonFatalAsserter for assertion failures. 1.318 + */ 1.319 + public static class AssertException extends RuntimeException { 1.320 + public AssertException(String msg) { 1.321 + super(msg); 1.322 + } 1.323 + } 1.324 + 1.325 + /** 1.326 + * Asserter that throws an AssertException on failure instead of aborting 1.327 + * the test. 1.328 + * 1.329 + * This can be used in methods called via waitForCondition() where an assertion 1.330 + * might not immediately succeed. 1.331 + */ 1.332 + public class NonFatalAsserter extends FennecMochitestAssert { 1.333 + @Override 1.334 + public void ok(boolean condition, String name, String diag) { 1.335 + if (!condition) { 1.336 + String details = (diag == null ? "" : " | " + diag); 1.337 + throw new AssertException("Assertion failed: " + name + details); 1.338 + } 1.339 + mAsserter.ok(condition, name, diag); 1.340 + } 1.341 + } 1.342 + 1.343 + /** 1.344 + * Gets a URL for a dynamically-generated page. 1.345 + * 1.346 + * The page will have a URL unique to the given ID, and the page's title 1.347 + * will match the given ID. 1.348 + * 1.349 + * @param id ID used to generate page URL 1.350 + * @return URL of the page 1.351 + */ 1.352 + protected String getPage(String id) { 1.353 + return getAbsoluteUrl("/robocop/robocop_dynamic.sjs?id=" + id); 1.354 + } 1.355 + 1.356 + protected String readProfileFile(String filename) { 1.357 + try { 1.358 + return readFile(new File(mProfile, filename)); 1.359 + } catch (IOException e) { 1.360 + mAsserter.ok(false, "Error reading" + filename, getStackTraceString(e)); 1.361 + } 1.362 + return null; 1.363 + } 1.364 + 1.365 + protected void writeProfileFile(String filename, String data) { 1.366 + try { 1.367 + writeFile(new File(mProfile, filename), data); 1.368 + } catch (IOException e) { 1.369 + mAsserter.ok(false, "Error writing to " + filename, getStackTraceString(e)); 1.370 + } 1.371 + } 1.372 + 1.373 + private String readFile(File target) throws IOException { 1.374 + if (!target.exists()) { 1.375 + return null; 1.376 + } 1.377 + 1.378 + FileReader fr = new FileReader(target); 1.379 + try { 1.380 + StringBuffer sb = new StringBuffer(); 1.381 + char[] buf = new char[8192]; 1.382 + int read = fr.read(buf); 1.383 + while (read >= 0) { 1.384 + sb.append(buf, 0, read); 1.385 + read = fr.read(buf); 1.386 + } 1.387 + return sb.toString(); 1.388 + } finally { 1.389 + fr.close(); 1.390 + } 1.391 + } 1.392 + 1.393 + private void writeFile(File target, String data) throws IOException { 1.394 + FileWriter writer = new FileWriter(target); 1.395 + try { 1.396 + writer.write(data); 1.397 + } finally { 1.398 + writer.close(); 1.399 + } 1.400 + } 1.401 +}