Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | package org.mozilla.gecko.tests; |
michael@0 | 2 | |
michael@0 | 3 | import java.io.File; |
michael@0 | 4 | import java.io.FileReader; |
michael@0 | 5 | import java.io.FileWriter; |
michael@0 | 6 | import java.io.IOException; |
michael@0 | 7 | |
michael@0 | 8 | import org.json.JSONArray; |
michael@0 | 9 | import org.json.JSONException; |
michael@0 | 10 | import org.json.JSONObject; |
michael@0 | 11 | import org.mozilla.gecko.Actions; |
michael@0 | 12 | import org.mozilla.gecko.Assert; |
michael@0 | 13 | import org.mozilla.gecko.FennecMochitestAssert; |
michael@0 | 14 | |
michael@0 | 15 | public abstract class SessionTest extends BaseTest { |
michael@0 | 16 | protected Navigation mNavigation; |
michael@0 | 17 | |
michael@0 | 18 | @Override |
michael@0 | 19 | public void setUp() throws Exception { |
michael@0 | 20 | super.setUp(); |
michael@0 | 21 | |
michael@0 | 22 | mNavigation = new Navigation(mDevice); |
michael@0 | 23 | } |
michael@0 | 24 | |
michael@0 | 25 | /** |
michael@0 | 26 | * A generic session object representing a collection of items that has a |
michael@0 | 27 | * selected index. |
michael@0 | 28 | */ |
michael@0 | 29 | protected abstract class SessionObject<T> { |
michael@0 | 30 | private final int mIndex; |
michael@0 | 31 | private final T[] mItems; |
michael@0 | 32 | |
michael@0 | 33 | public SessionObject(int index, T... items) { |
michael@0 | 34 | mIndex = index; |
michael@0 | 35 | mItems = items; |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | public int getIndex() { |
michael@0 | 39 | return mIndex; |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | public T[] getItems() { |
michael@0 | 43 | return mItems; |
michael@0 | 44 | } |
michael@0 | 45 | } |
michael@0 | 46 | |
michael@0 | 47 | protected class PageInfo { |
michael@0 | 48 | private String url; |
michael@0 | 49 | private String title; |
michael@0 | 50 | |
michael@0 | 51 | public PageInfo(String key) { |
michael@0 | 52 | if (key.startsWith("about:")) { |
michael@0 | 53 | url = key; |
michael@0 | 54 | } else { |
michael@0 | 55 | url = getPage(key); |
michael@0 | 56 | } |
michael@0 | 57 | title = key; |
michael@0 | 58 | } |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | protected class SessionTab extends SessionObject<PageInfo> { |
michael@0 | 62 | public SessionTab(int index, PageInfo... items) { |
michael@0 | 63 | super(index, items); |
michael@0 | 64 | } |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | protected class Session extends SessionObject<SessionTab> { |
michael@0 | 68 | public Session(int index, SessionTab... items) { |
michael@0 | 69 | super(index, items); |
michael@0 | 70 | } |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | /** |
michael@0 | 74 | * Walker for visiting items in a browser-like navigation order. |
michael@0 | 75 | */ |
michael@0 | 76 | protected abstract class NavigationWalker<T> { |
michael@0 | 77 | private final T[] mItems; |
michael@0 | 78 | private final int mIndex; |
michael@0 | 79 | |
michael@0 | 80 | public NavigationWalker(SessionObject<T> obj) { |
michael@0 | 81 | mItems = obj.getItems(); |
michael@0 | 82 | mIndex = obj.getIndex(); |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | /** |
michael@0 | 86 | * Walks over the list of items, calling the onItem() callback for each. |
michael@0 | 87 | * |
michael@0 | 88 | * The selected item is the first item visited. Each item after the |
michael@0 | 89 | * selected item is then visited in ascending index order. Finally, the |
michael@0 | 90 | * list is iterated in reverse, and each item before the selected item |
michael@0 | 91 | * is visited in descending index order. |
michael@0 | 92 | */ |
michael@0 | 93 | public void walk() { |
michael@0 | 94 | onItem(mItems[mIndex], mIndex); |
michael@0 | 95 | for (int i = mIndex + 1; i < mItems.length; i++) { |
michael@0 | 96 | goForward(); |
michael@0 | 97 | onItem(mItems[i], i); |
michael@0 | 98 | } |
michael@0 | 99 | if (mIndex > 0) { |
michael@0 | 100 | for (int i = mItems.length - 2; i >= 0; i--) { |
michael@0 | 101 | goBack(); |
michael@0 | 102 | if (i < mIndex) { |
michael@0 | 103 | onItem(mItems[i], i); |
michael@0 | 104 | } |
michael@0 | 105 | } |
michael@0 | 106 | } |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | /** |
michael@0 | 110 | * Callback when an item is visited during a walk. |
michael@0 | 111 | * |
michael@0 | 112 | * Only one callback is executed per item. |
michael@0 | 113 | */ |
michael@0 | 114 | public abstract void onItem(T item, int currentIndex); |
michael@0 | 115 | |
michael@0 | 116 | /** |
michael@0 | 117 | * Callback executed for each back step of the walk. |
michael@0 | 118 | */ |
michael@0 | 119 | public void goBack() {} |
michael@0 | 120 | |
michael@0 | 121 | /** |
michael@0 | 122 | * Callback executed for each forward step of the walk. |
michael@0 | 123 | */ |
michael@0 | 124 | public void goForward() {} |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | /** |
michael@0 | 128 | * Loads a set of tabs in the browser specified by the given session. |
michael@0 | 129 | * |
michael@0 | 130 | * @param session Session to load |
michael@0 | 131 | */ |
michael@0 | 132 | protected void loadSessionTabs(Session session) { |
michael@0 | 133 | // Verify initial about:home tab |
michael@0 | 134 | verifyTabCount(1); |
michael@0 | 135 | verifyUrl("about:home"); |
michael@0 | 136 | |
michael@0 | 137 | SessionTab[] tabs = session.getItems(); |
michael@0 | 138 | for (int i = 0; i < tabs.length; i++) { |
michael@0 | 139 | final SessionTab tab = tabs[i]; |
michael@0 | 140 | final PageInfo[] pages = tab.getItems(); |
michael@0 | 141 | |
michael@0 | 142 | // New tabs always start with about:home, so make sure about:home |
michael@0 | 143 | // is always the first entry. |
michael@0 | 144 | mAsserter.is(pages[0].url, "about:home", "first page in tab is about:home"); |
michael@0 | 145 | |
michael@0 | 146 | // If this is the first tab, the tab already exists, so no need to |
michael@0 | 147 | // create a new one. Otherwise, create a new tab if we're loading |
michael@0 | 148 | // the first the first page in the set. |
michael@0 | 149 | if (i > 0) { |
michael@0 | 150 | addTab(); |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | for (int j = 1; j < pages.length; j++) { |
michael@0 | 154 | Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); |
michael@0 | 155 | |
michael@0 | 156 | loadUrl(pages[j].url); |
michael@0 | 157 | |
michael@0 | 158 | pageShowExpecter.blockForEvent(); |
michael@0 | 159 | pageShowExpecter.unregisterListener(); |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | final int index = tab.getIndex(); |
michael@0 | 163 | for (int j = pages.length - 1; j > index; j--) { |
michael@0 | 164 | mNavigation.back(); |
michael@0 | 165 | } |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | selectTabAt(session.getIndex()); |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | /** |
michael@0 | 172 | * Verifies that the set of open tabs matches the given session. |
michael@0 | 173 | * |
michael@0 | 174 | * @param session Session to verify |
michael@0 | 175 | */ |
michael@0 | 176 | protected void verifySessionTabs(Session session) { |
michael@0 | 177 | verifyTabCount(session.getItems().length); |
michael@0 | 178 | |
michael@0 | 179 | (new NavigationWalker<SessionTab>(session) { |
michael@0 | 180 | boolean mFirstTabVisited; |
michael@0 | 181 | |
michael@0 | 182 | @Override |
michael@0 | 183 | public void onItem(SessionTab tab, int currentIndex) { |
michael@0 | 184 | // The first tab to check should already be selected at startup |
michael@0 | 185 | if (mFirstTabVisited) { |
michael@0 | 186 | selectTabAt(currentIndex); |
michael@0 | 187 | } else { |
michael@0 | 188 | mFirstTabVisited = true; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | (new NavigationWalker<PageInfo>(tab) { |
michael@0 | 192 | @Override |
michael@0 | 193 | public void onItem(PageInfo page, int currentIndex) { |
michael@0 | 194 | if (page.url.equals("about:home")) { |
michael@0 | 195 | waitForText("Enter Search or Address"); |
michael@0 | 196 | verifyUrl(page.url); |
michael@0 | 197 | } else { |
michael@0 | 198 | waitForText(page.title); |
michael@0 | 199 | verifyPageTitle(page.title); |
michael@0 | 200 | } |
michael@0 | 201 | } |
michael@0 | 202 | |
michael@0 | 203 | @Override |
michael@0 | 204 | public void goBack() { |
michael@0 | 205 | mNavigation.back(); |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | @Override |
michael@0 | 209 | public void goForward() { |
michael@0 | 210 | mNavigation.forward(); |
michael@0 | 211 | } |
michael@0 | 212 | }).walk(); |
michael@0 | 213 | } |
michael@0 | 214 | }).walk(); |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | /** |
michael@0 | 218 | * Gets session restore JSON corresponding to the open session. |
michael@0 | 219 | * |
michael@0 | 220 | * The JSON format follows the format used in Gecko for session restore and |
michael@0 | 221 | * should be interchangeable with the Gecko's generated sessionstore.js. |
michael@0 | 222 | * |
michael@0 | 223 | * @param session Session to serialize |
michael@0 | 224 | * @return JSON string of session |
michael@0 | 225 | */ |
michael@0 | 226 | protected String buildSessionJSON(Session session) { |
michael@0 | 227 | final SessionTab[] sessionTabs = session.getItems(); |
michael@0 | 228 | String sessionString = null; |
michael@0 | 229 | |
michael@0 | 230 | try { |
michael@0 | 231 | final JSONArray tabs = new JSONArray(); |
michael@0 | 232 | |
michael@0 | 233 | for (int i = 0; i < sessionTabs.length; i++) { |
michael@0 | 234 | final JSONObject tab = new JSONObject(); |
michael@0 | 235 | final JSONArray entries = new JSONArray(); |
michael@0 | 236 | final SessionTab sessionTab = sessionTabs[i]; |
michael@0 | 237 | final PageInfo[] pages = sessionTab.getItems(); |
michael@0 | 238 | |
michael@0 | 239 | for (int j = 0; j < pages.length; j++) { |
michael@0 | 240 | final PageInfo page = pages[j]; |
michael@0 | 241 | final JSONObject entry = new JSONObject(); |
michael@0 | 242 | entry.put("url", page.url); |
michael@0 | 243 | entry.put("title", page.title); |
michael@0 | 244 | entries.put(entry); |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | tab.put("entries", entries); |
michael@0 | 248 | tab.put("index", sessionTab.getIndex() + 1); |
michael@0 | 249 | tabs.put(tab); |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | JSONObject window = new JSONObject(); |
michael@0 | 253 | window.put("tabs", tabs); |
michael@0 | 254 | window.put("selected", session.getIndex() + 1); |
michael@0 | 255 | sessionString = new JSONObject().put("windows", new JSONArray().put(window)).toString(); |
michael@0 | 256 | } catch (JSONException e) { |
michael@0 | 257 | mAsserter.ok(false, "JSON exception", getStackTraceString(e)); |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | return sessionString; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | /** |
michael@0 | 264 | * @see SessionTest#verifySessionJSON(Session, String, Assert) |
michael@0 | 265 | */ |
michael@0 | 266 | protected void verifySessionJSON(Session session, String sessionString) { |
michael@0 | 267 | verifySessionJSON(session, sessionString, mAsserter); |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | /** |
michael@0 | 271 | * Verifies a session JSON string against the given session. |
michael@0 | 272 | * |
michael@0 | 273 | * @param session Session to verify against |
michael@0 | 274 | * @param sessionString JSON string to verify |
michael@0 | 275 | * @param asserter Assert class to use during verification |
michael@0 | 276 | */ |
michael@0 | 277 | protected void verifySessionJSON(Session session, String sessionString, Assert asserter) { |
michael@0 | 278 | final SessionTab[] sessionTabs = session.getItems(); |
michael@0 | 279 | |
michael@0 | 280 | try { |
michael@0 | 281 | final JSONObject window = new JSONObject(sessionString).getJSONArray("windows").getJSONObject(0); |
michael@0 | 282 | final JSONArray tabs = window.getJSONArray("tabs"); |
michael@0 | 283 | final int optSelected = window.optInt("selected", -1); |
michael@0 | 284 | |
michael@0 | 285 | asserter.is(optSelected, session.getIndex() + 1, "selected tab matches"); |
michael@0 | 286 | |
michael@0 | 287 | for (int i = 0; i < tabs.length(); i++) { |
michael@0 | 288 | final JSONObject tab = tabs.getJSONObject(i); |
michael@0 | 289 | final int index = tab.getInt("index"); |
michael@0 | 290 | final JSONArray entries = tab.getJSONArray("entries"); |
michael@0 | 291 | final SessionTab sessionTab = sessionTabs[i]; |
michael@0 | 292 | final PageInfo[] pages = sessionTab.getItems(); |
michael@0 | 293 | |
michael@0 | 294 | asserter.is(index, sessionTab.getIndex() + 1, "selected page index matches"); |
michael@0 | 295 | |
michael@0 | 296 | for (int j = 0; j < entries.length(); j++) { |
michael@0 | 297 | final JSONObject entry = entries.getJSONObject(j); |
michael@0 | 298 | final String url = entry.getString("url"); |
michael@0 | 299 | final String title = entry.optString("title"); |
michael@0 | 300 | final PageInfo page = pages[j]; |
michael@0 | 301 | |
michael@0 | 302 | asserter.is(url, page.url, "URL in JSON matches session URL"); |
michael@0 | 303 | if (!page.url.startsWith("about:")) { |
michael@0 | 304 | asserter.is(title, page.title, "title in JSON matches session title"); |
michael@0 | 305 | } |
michael@0 | 306 | } |
michael@0 | 307 | } |
michael@0 | 308 | } catch (JSONException e) { |
michael@0 | 309 | asserter.ok(false, "JSON exception", getStackTraceString(e)); |
michael@0 | 310 | } |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | /** |
michael@0 | 314 | * Exception thrown by NonFatalAsserter for assertion failures. |
michael@0 | 315 | */ |
michael@0 | 316 | public static class AssertException extends RuntimeException { |
michael@0 | 317 | public AssertException(String msg) { |
michael@0 | 318 | super(msg); |
michael@0 | 319 | } |
michael@0 | 320 | } |
michael@0 | 321 | |
michael@0 | 322 | /** |
michael@0 | 323 | * Asserter that throws an AssertException on failure instead of aborting |
michael@0 | 324 | * the test. |
michael@0 | 325 | * |
michael@0 | 326 | * This can be used in methods called via waitForCondition() where an assertion |
michael@0 | 327 | * might not immediately succeed. |
michael@0 | 328 | */ |
michael@0 | 329 | public class NonFatalAsserter extends FennecMochitestAssert { |
michael@0 | 330 | @Override |
michael@0 | 331 | public void ok(boolean condition, String name, String diag) { |
michael@0 | 332 | if (!condition) { |
michael@0 | 333 | String details = (diag == null ? "" : " | " + diag); |
michael@0 | 334 | throw new AssertException("Assertion failed: " + name + details); |
michael@0 | 335 | } |
michael@0 | 336 | mAsserter.ok(condition, name, diag); |
michael@0 | 337 | } |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | /** |
michael@0 | 341 | * Gets a URL for a dynamically-generated page. |
michael@0 | 342 | * |
michael@0 | 343 | * The page will have a URL unique to the given ID, and the page's title |
michael@0 | 344 | * will match the given ID. |
michael@0 | 345 | * |
michael@0 | 346 | * @param id ID used to generate page URL |
michael@0 | 347 | * @return URL of the page |
michael@0 | 348 | */ |
michael@0 | 349 | protected String getPage(String id) { |
michael@0 | 350 | return getAbsoluteUrl("/robocop/robocop_dynamic.sjs?id=" + id); |
michael@0 | 351 | } |
michael@0 | 352 | |
michael@0 | 353 | protected String readProfileFile(String filename) { |
michael@0 | 354 | try { |
michael@0 | 355 | return readFile(new File(mProfile, filename)); |
michael@0 | 356 | } catch (IOException e) { |
michael@0 | 357 | mAsserter.ok(false, "Error reading" + filename, getStackTraceString(e)); |
michael@0 | 358 | } |
michael@0 | 359 | return null; |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | protected void writeProfileFile(String filename, String data) { |
michael@0 | 363 | try { |
michael@0 | 364 | writeFile(new File(mProfile, filename), data); |
michael@0 | 365 | } catch (IOException e) { |
michael@0 | 366 | mAsserter.ok(false, "Error writing to " + filename, getStackTraceString(e)); |
michael@0 | 367 | } |
michael@0 | 368 | } |
michael@0 | 369 | |
michael@0 | 370 | private String readFile(File target) throws IOException { |
michael@0 | 371 | if (!target.exists()) { |
michael@0 | 372 | return null; |
michael@0 | 373 | } |
michael@0 | 374 | |
michael@0 | 375 | FileReader fr = new FileReader(target); |
michael@0 | 376 | try { |
michael@0 | 377 | StringBuffer sb = new StringBuffer(); |
michael@0 | 378 | char[] buf = new char[8192]; |
michael@0 | 379 | int read = fr.read(buf); |
michael@0 | 380 | while (read >= 0) { |
michael@0 | 381 | sb.append(buf, 0, read); |
michael@0 | 382 | read = fr.read(buf); |
michael@0 | 383 | } |
michael@0 | 384 | return sb.toString(); |
michael@0 | 385 | } finally { |
michael@0 | 386 | fr.close(); |
michael@0 | 387 | } |
michael@0 | 388 | } |
michael@0 | 389 | |
michael@0 | 390 | private void writeFile(File target, String data) throws IOException { |
michael@0 | 391 | FileWriter writer = new FileWriter(target); |
michael@0 | 392 | try { |
michael@0 | 393 | writer.write(data); |
michael@0 | 394 | } finally { |
michael@0 | 395 | writer.close(); |
michael@0 | 396 | } |
michael@0 | 397 | } |
michael@0 | 398 | } |