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