1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.browser; 18 19 import android.app.Instrumentation; 20 import android.content.Intent; 21 import android.net.Uri; 22 import android.net.http.SslError; 23 import android.os.Environment; 24 import android.test.ActivityInstrumentationTestCase2; 25 import android.util.Log; 26 import android.webkit.DownloadListener; 27 import android.webkit.HttpAuthHandler; 28 import android.webkit.JsPromptResult; 29 import android.webkit.JsResult; 30 import android.webkit.SslErrorHandler; 31 import android.webkit.WebView; 32 33 import java.io.BufferedReader; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.FileReader; 37 import java.io.FileWriter; 38 import java.io.IOException; 39 import java.io.OutputStreamWriter; 40 import java.util.Iterator; 41 import java.util.LinkedList; 42 import java.util.List; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.TimeUnit; 45 46 /** 47 * 48 * Iterates over a list of URLs from a file and outputs the time to load each. 49 */ 50 public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserActivity> { 51 52 private final static String TAG = "PopularUrlsTest"; 53 private final static String newLine = System.getProperty("line.separator"); 54 private final static String sInputFile = "popular_urls.txt"; 55 private final static String sOutputFile = "test_output.txt"; 56 private final static String sStatusFile = "test_status.txt"; 57 private final static File sExternalStorage = Environment.getExternalStorageDirectory(); 58 59 private final static int PERF_LOOPCOUNT = 10; 60 private final static int STABILITY_LOOPCOUNT = 1; 61 private final static int PAGE_LOAD_TIMEOUT = 120000; // 2 minutes 62 63 private BrowserActivity mActivity = null; 64 private Instrumentation mInst = null; 65 private CountDownLatch mLatch = new CountDownLatch(1); 66 private RunStatus mStatus; 67 private boolean pageLoadFinishCalled, pageProgressFull; 68 PopularUrlsTest()69 public PopularUrlsTest() { 70 super(BrowserActivity.class); 71 } 72 73 @Override setUp()74 protected void setUp() throws Exception { 75 super.setUp(); 76 77 Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("about:blank")); 78 setActivityIntent(i); 79 mActivity = getActivity(); 80 mInst = getInstrumentation(); 81 mInst.waitForIdleSync(); 82 83 mStatus = RunStatus.load(); 84 } 85 86 @Override tearDown()87 protected void tearDown() throws Exception { 88 if (mStatus != null) { 89 mStatus.cleanUp(); 90 } 91 92 super.tearDown(); 93 } 94 getInputStream()95 static BufferedReader getInputStream() throws FileNotFoundException { 96 return getInputStream(sInputFile); 97 } 98 getInputStream(String inputFile)99 static BufferedReader getInputStream(String inputFile) throws FileNotFoundException { 100 String path = sExternalStorage + File.separator + inputFile; 101 FileReader fileReader = new FileReader(path); 102 BufferedReader bufferedReader = new BufferedReader(fileReader); 103 104 return bufferedReader; 105 } 106 getOutputStream()107 OutputStreamWriter getOutputStream() throws IOException { 108 return getOutputStream(sOutputFile); 109 } 110 getOutputStream(String outputFile)111 OutputStreamWriter getOutputStream(String outputFile) throws IOException { 112 String path = sExternalStorage + File.separator + outputFile; 113 114 File file = new File(path); 115 116 return new FileWriter(file, mStatus.getIsRecovery()); 117 } 118 119 /** 120 * Gets the browser ready for testing by starting the application 121 * and wrapping the WebView's helper clients. 122 */ setUpBrowser()123 void setUpBrowser() { 124 Tab tab = mActivity.getTabControl().getCurrentTab(); 125 WebView webView = tab.getWebView(); 126 127 webView.setWebChromeClient(new TestWebChromeClient(webView.getWebChromeClient()) { 128 129 @Override 130 public void onProgressChanged(WebView view, int newProgress) { 131 super.onProgressChanged(view, newProgress); 132 if (newProgress >= 100) { 133 if (!pageProgressFull) { 134 // void duplicate calls 135 pageProgressFull = true; 136 if (pageLoadFinishCalled) { 137 //reset latch and move forward only if both indicators are true 138 resetLatch(); 139 } 140 } 141 } 142 } 143 144 /** 145 * Dismisses and logs Javascript alerts. 146 */ 147 @Override 148 public boolean onJsAlert(WebView view, String url, String message, 149 JsResult result) { 150 String logMsg = String.format("JS Alert '%s' received from %s", message, url); 151 Log.w(TAG, logMsg); 152 result.confirm(); 153 154 return true; 155 } 156 157 /** 158 * Confirms and logs Javascript alerts. 159 */ 160 @Override 161 public boolean onJsConfirm(WebView view, String url, String message, 162 JsResult result) { 163 String logMsg = String.format("JS Confirmation '%s' received from %s", 164 message, url); 165 Log.w(TAG, logMsg); 166 result.confirm(); 167 168 return true; 169 } 170 171 /** 172 * Confirms and logs Javascript alerts, providing the default value. 173 */ 174 @Override 175 public boolean onJsPrompt(WebView view, String url, String message, 176 String defaultValue, JsPromptResult result) { 177 String logMsg = String.format("JS Prompt '%s' received from %s; " + 178 "Giving default value '%s'", message, url, defaultValue); 179 Log.w(TAG, logMsg); 180 result.confirm(defaultValue); 181 182 return true; 183 } 184 185 /* 186 * Skip the unload confirmation 187 */ 188 @Override 189 public boolean onJsBeforeUnload( 190 WebView view, String url, String message, JsResult result) { 191 result.confirm(); 192 return true; 193 } 194 }); 195 196 webView.setWebViewClient(new TestWebViewClient(webView.getWebViewClient()) { 197 198 /** 199 * Bypasses and logs errors. 200 */ 201 @Override 202 public void onReceivedError(WebView view, int errorCode, 203 String description, String failingUrl) { 204 String message = String.format("Error '%s' (%d) loading url: %s", 205 description, errorCode, failingUrl); 206 Log.w(TAG, message); 207 } 208 209 /** 210 * Ignores and logs SSL errors. 211 */ 212 @Override 213 public void onReceivedSslError(WebView view, SslErrorHandler handler, 214 SslError error) { 215 Log.w(TAG, "SSL error: " + error); 216 handler.proceed(); 217 } 218 219 /** 220 * Ignores http auth with dummy username and password 221 */ 222 @Override 223 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, 224 String host, String realm) { 225 handler.proceed("user", "passwd"); 226 } 227 228 /* (non-Javadoc) 229 * @see com.android.browser.TestWebViewClient#onPageFinished(android.webkit.WebView, java.lang.String) 230 */ 231 @Override 232 public void onPageFinished(WebView view, String url) { 233 if (!pageLoadFinishCalled) { 234 pageLoadFinishCalled = true; 235 if (pageProgressFull) { 236 //reset latch and move forward only if both indicators are true 237 resetLatch(); 238 } 239 } 240 } 241 242 @Override 243 public boolean shouldOverrideUrlLoading(WebView view, String url) { 244 if (!(url.startsWith("http://") || url.startsWith("https://"))) { 245 Log.v(TAG, String.format("suppressing non-http url scheme: %s", url)); 246 return true; 247 } 248 return super.shouldOverrideUrlLoading(view, url); 249 } 250 }); 251 252 webView.setDownloadListener(new DownloadListener() { 253 254 @Override 255 public void onDownloadStart(String url, String userAgent, String contentDisposition, 256 String mimetype, long contentLength) { 257 Log.v(TAG, String.format("Download request ignored: %s", url)); 258 } 259 }); 260 } 261 resetLatch()262 void resetLatch() { 263 if (mLatch.getCount() != 1) { 264 Log.w(TAG, "Expecting latch to be 1, but it's not!"); 265 } else { 266 mLatch.countDown(); 267 } 268 } 269 resetForNewPage()270 void resetForNewPage() { 271 mLatch = new CountDownLatch(1); 272 pageLoadFinishCalled = false; 273 pageProgressFull = false; 274 } 275 waitForLoad()276 void waitForLoad() throws InterruptedException { 277 boolean timedout = !mLatch.await(PAGE_LOAD_TIMEOUT, TimeUnit.MILLISECONDS); 278 if (timedout) { 279 Log.w(TAG, "page timeout. trying to stop."); 280 // try to stop page load 281 mInst.runOnMainSync(new Runnable(){ 282 public void run() { 283 mActivity.getTabControl().getCurrentTab().getWebView().stopLoading(); 284 } 285 }); 286 // try to wait for count down latch again 287 timedout = !mLatch.await(5000, TimeUnit.MILLISECONDS); 288 if (timedout) { 289 throw new RuntimeException("failed to stop timedout site, is browser pegged?"); 290 } 291 } 292 } 293 294 private static class RunStatus { 295 private File mFile; 296 private int iteration; 297 private int page; 298 private String url; 299 private boolean isRecovery; 300 RunStatus(String file)301 private RunStatus(String file) throws IOException { 302 mFile = new File(file); 303 FileReader input = null; 304 BufferedReader reader = null; 305 isRecovery = false; 306 iteration = 0; 307 page = 0; 308 try { 309 input = new FileReader(mFile); 310 isRecovery = true; 311 reader = new BufferedReader(input); 312 String line = reader.readLine(); 313 if (line == null) 314 return; 315 iteration = Integer.parseInt(line); 316 line = reader.readLine(); 317 if (line == null) 318 return; 319 page = Integer.parseInt(line); 320 } catch (FileNotFoundException ex) { 321 return; 322 } catch (NumberFormatException nfe) { 323 Log.wtf(TAG, "unexpected data in status file, will start from begining"); 324 return; 325 } finally { 326 try { 327 if (reader != null) { 328 reader.close(); 329 } 330 } finally { 331 if (input != null) { 332 input.close(); 333 } 334 } 335 } 336 } 337 load()338 public static RunStatus load() throws IOException { 339 return load(sStatusFile); 340 } 341 load(String file)342 public static RunStatus load(String file) throws IOException { 343 return new RunStatus(sExternalStorage + File.separator + file); 344 } 345 write()346 public void write() throws IOException { 347 FileWriter output = null; 348 if (mFile.exists()) { 349 mFile.delete(); 350 } 351 try { 352 output = new FileWriter(mFile); 353 output.write(iteration + newLine); 354 output.write(page + newLine); 355 output.write(url + newLine); 356 } finally { 357 if (output != null) { 358 output.close(); 359 } 360 } 361 } 362 cleanUp()363 public void cleanUp() { 364 if (mFile.exists()) { 365 mFile.delete(); 366 } 367 } 368 resetPage()369 public void resetPage() { 370 page = 0; 371 } 372 incrementPage()373 public void incrementPage() { 374 ++page; 375 } 376 incrementIteration()377 public void incrementIteration() { 378 ++iteration; 379 } 380 getPage()381 public int getPage() { 382 return page; 383 } 384 getIteration()385 public int getIteration() { 386 return iteration; 387 } 388 getIsRecovery()389 public boolean getIsRecovery() { 390 return isRecovery; 391 } 392 setUrl(String url)393 public void setUrl(String url) { 394 this.url = url; 395 } 396 } 397 398 /** 399 * Loops over a list of URLs, points the browser to each one, and records the time elapsed. 400 * 401 * @param input the reader from which to get the URLs. 402 * @param writer the writer to which to output the results. 403 * @param clearCache determines whether the cache is cleared before loading each page 404 * @param loopCount the number of times to loop through the list of pages 405 * @throws IOException unable to read from input or write to writer. 406 * @throws InterruptedException the thread was interrupted waiting for the page to load. 407 */ loopUrls(BufferedReader input, OutputStreamWriter writer, boolean clearCache, int loopCount)408 void loopUrls(BufferedReader input, OutputStreamWriter writer, 409 boolean clearCache, int loopCount) 410 throws IOException, InterruptedException { 411 Tab tab = mActivity.getTabControl().getCurrentTab(); 412 WebView webView = tab.getWebView(); 413 414 List<String> pages = new LinkedList<String>(); 415 416 String page; 417 while (null != (page = input.readLine())) { 418 pages.add(page); 419 } 420 421 Iterator<String> iterator = pages.iterator(); 422 for (int i = 0; i < mStatus.getPage(); ++i) { 423 iterator.next(); 424 } 425 426 if (mStatus.getIsRecovery()) { 427 Log.e(TAG, "Recovering after crash: " + iterator.next()); 428 mStatus.incrementPage(); 429 } 430 431 while (mStatus.getIteration() < loopCount) { 432 if (clearCache) { 433 webView.clearCache(true); 434 } 435 while(iterator.hasNext()) { 436 page = iterator.next(); 437 mStatus.setUrl(page); 438 mStatus.write(); 439 Log.i(TAG, "start: " + page); 440 Uri uri = Uri.parse(page); 441 final Intent intent = new Intent(Intent.ACTION_VIEW, uri); 442 443 long startTime = System.currentTimeMillis(); 444 resetForNewPage(); 445 mInst.runOnMainSync(new Runnable() { 446 447 public void run() { 448 mActivity.onNewIntent(intent); 449 } 450 451 }); 452 waitForLoad(); 453 long stopTime = System.currentTimeMillis(); 454 455 String url = webView.getUrl(); 456 Log.i(TAG, "finish: " + url); 457 458 if (writer != null) { 459 writer.write(page + "|" + (stopTime - startTime) + newLine); 460 writer.flush(); 461 } 462 463 mStatus.incrementPage(); 464 } 465 mStatus.incrementIteration(); 466 mStatus.resetPage(); 467 iterator = pages.iterator(); 468 } 469 } 470 testLoadPerformance()471 public void testLoadPerformance() throws IOException, InterruptedException { 472 setUpBrowser(); 473 474 OutputStreamWriter writer = getOutputStream(); 475 try { 476 BufferedReader bufferedReader = getInputStream(); 477 try { 478 loopUrls(bufferedReader, writer, true, PERF_LOOPCOUNT); 479 } finally { 480 if (bufferedReader != null) { 481 bufferedReader.close(); 482 } 483 } 484 } catch (FileNotFoundException fnfe) { 485 Log.e(TAG, fnfe.getMessage(), fnfe); 486 fail("Test environment not setup correctly"); 487 } finally { 488 if (writer != null) { 489 writer.close(); 490 } 491 } 492 } 493 testStability()494 public void testStability() throws IOException, InterruptedException { 495 setUpBrowser(); 496 497 BufferedReader bufferedReader = getInputStream(); 498 try { 499 loopUrls(bufferedReader, null, true, STABILITY_LOOPCOUNT); 500 } catch (FileNotFoundException fnfe) { 501 Log.e(TAG, fnfe.getMessage(), fnfe); 502 fail("Test environment not setup correctly"); 503 } finally { 504 if (bufferedReader != null) { 505 bufferedReader.close(); 506 } 507 } 508 } 509 } 510