| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.browser; |
| |
| import android.app.Instrumentation; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.net.http.SslError; |
| import android.os.Environment; |
| import android.provider.Browser; |
| import android.test.ActivityInstrumentationTestCase2; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.webkit.ClientCertRequestHandler; |
| import android.webkit.DownloadListener; |
| import android.webkit.HttpAuthHandler; |
| import android.webkit.JsPromptResult; |
| import android.webkit.JsResult; |
| import android.webkit.SslErrorHandler; |
| import android.webkit.WebView; |
| import android.webkit.WebViewClassic; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * |
| * Iterates over a list of URLs from a file and outputs the time to load each. |
| */ |
| public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserActivity> { |
| |
| private final static String TAG = "PopularUrlsTest"; |
| private final static String newLine = System.getProperty("line.separator"); |
| private final static String sInputFile = "popular_urls.txt"; |
| private final static String sOutputFile = "test_output.txt"; |
| private final static String sStatusFile = "test_status.txt"; |
| private final static File sExternalStorage = Environment.getExternalStorageDirectory(); |
| |
| private final static int PERF_LOOPCOUNT = 10; |
| private final static int STABILITY_LOOPCOUNT = 1; |
| private final static int PAGE_LOAD_TIMEOUT = 120000; // 2 minutes |
| |
| private BrowserActivity mActivity = null; |
| private Controller mController = null; |
| private Instrumentation mInst = null; |
| private CountDownLatch mLatch = new CountDownLatch(1); |
| private RunStatus mStatus; |
| private boolean pageLoadFinishCalled, pageProgressFull; |
| |
| public PopularUrlsTest() { |
| super(BrowserActivity.class); |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("about:blank")); |
| i.putExtra(Controller.NO_CRASH_RECOVERY, true); |
| setActivityIntent(i); |
| mActivity = getActivity(); |
| mController = mActivity.getController(); |
| mInst = getInstrumentation(); |
| mInst.waitForIdleSync(); |
| |
| mStatus = RunStatus.load(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (mStatus != null) { |
| mStatus.cleanUp(); |
| } |
| |
| super.tearDown(); |
| } |
| |
| BufferedReader getInputStream() throws FileNotFoundException { |
| return getInputStream(sInputFile); |
| } |
| |
| BufferedReader getInputStream(String inputFile) throws FileNotFoundException { |
| FileReader fileReader = new FileReader(new File(sExternalStorage, inputFile)); |
| BufferedReader bufferedReader = new BufferedReader(fileReader); |
| |
| return bufferedReader; |
| } |
| |
| OutputStreamWriter getOutputStream() throws IOException { |
| return getOutputStream(sOutputFile); |
| } |
| |
| OutputStreamWriter getOutputStream(String outputFile) throws IOException { |
| return new FileWriter(new File(sExternalStorage, outputFile), mStatus.getIsRecovery()); |
| } |
| |
| /** |
| * Gets the browser ready for testing by starting the application |
| * and wrapping the WebView's helper clients. |
| */ |
| void setUpBrowser() { |
| mInst.runOnMainSync(new Runnable() { |
| @Override |
| public void run() { |
| setupBrowserInternal(); |
| } |
| }); |
| } |
| |
| void setupBrowserInternal() { |
| Tab tab = mController.getTabControl().getCurrentTab(); |
| WebView webView = tab.getWebView(); |
| |
| webView.setWebChromeClient(new TestWebChromeClient( |
| WebViewClassic.fromWebView(webView).getWebChromeClient()) { |
| |
| @Override |
| public void onProgressChanged(WebView view, int newProgress) { |
| super.onProgressChanged(view, newProgress); |
| if (newProgress >= 100) { |
| if (!pageProgressFull) { |
| // void duplicate calls |
| pageProgressFull = true; |
| if (pageLoadFinishCalled) { |
| //reset latch and move forward only if both indicators are true |
| resetLatch(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Dismisses and logs Javascript alerts. |
| */ |
| @Override |
| public boolean onJsAlert(WebView view, String url, String message, |
| JsResult result) { |
| String logMsg = String.format("JS Alert '%s' received from %s", message, url); |
| Log.w(TAG, logMsg); |
| result.confirm(); |
| |
| return true; |
| } |
| |
| /** |
| * Confirms and logs Javascript alerts. |
| */ |
| @Override |
| public boolean onJsConfirm(WebView view, String url, String message, |
| JsResult result) { |
| String logMsg = String.format("JS Confirmation '%s' received from %s", |
| message, url); |
| Log.w(TAG, logMsg); |
| result.confirm(); |
| |
| return true; |
| } |
| |
| /** |
| * Confirms and logs Javascript alerts, providing the default value. |
| */ |
| @Override |
| public boolean onJsPrompt(WebView view, String url, String message, |
| String defaultValue, JsPromptResult result) { |
| String logMsg = String.format("JS Prompt '%s' received from %s; " + |
| "Giving default value '%s'", message, url, defaultValue); |
| Log.w(TAG, logMsg); |
| result.confirm(defaultValue); |
| |
| return true; |
| } |
| |
| /* |
| * Skip the unload confirmation |
| */ |
| @Override |
| public boolean onJsBeforeUnload( |
| WebView view, String url, String message, JsResult result) { |
| result.confirm(); |
| return true; |
| } |
| }); |
| |
| webView.setWebViewClient(new TestWebViewClient( |
| WebViewClassic.fromWebView(webView).getWebViewClient()) { |
| |
| /** |
| * Bypasses and logs errors. |
| */ |
| @Override |
| public void onReceivedError(WebView view, int errorCode, |
| String description, String failingUrl) { |
| String message = String.format("Error '%s' (%d) loading url: %s", |
| description, errorCode, failingUrl); |
| Log.w(TAG, message); |
| } |
| |
| /** |
| * Ignores and logs SSL errors. |
| */ |
| @Override |
| public void onReceivedSslError(WebView view, SslErrorHandler handler, |
| SslError error) { |
| Log.w(TAG, "SSL error: " + error); |
| handler.proceed(); |
| } |
| |
| /** |
| * Ignores and logs SSL client certificate requests. |
| */ |
| @Override |
| public void onReceivedClientCertRequest(WebView view, ClientCertRequestHandler handler, |
| String host_and_port) { |
| Log.w(TAG, "SSL client certificate request: " + host_and_port); |
| handler.cancel(); |
| } |
| |
| /** |
| * Ignores http auth with dummy username and password |
| */ |
| @Override |
| public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, |
| String host, String realm) { |
| handler.proceed("user", "passwd"); |
| } |
| |
| /* (non-Javadoc) |
| * @see com.android.browser.TestWebViewClient#onPageFinished(android.webkit.WebView, java.lang.String) |
| */ |
| @Override |
| public void onPageFinished(WebView view, String url) { |
| super.onPageFinished(view, url); |
| if (!pageLoadFinishCalled) { |
| pageLoadFinishCalled = true; |
| if (pageProgressFull) { |
| //reset latch and move forward only if both indicators are true |
| resetLatch(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean shouldOverrideUrlLoading(WebView view, String url) { |
| if (!(url.startsWith("http://") || url.startsWith("https://"))) { |
| Log.v(TAG, String.format("suppressing non-http url scheme: %s", url)); |
| return true; |
| } |
| return super.shouldOverrideUrlLoading(view, url); |
| } |
| }); |
| |
| webView.setDownloadListener(new DownloadListener() { |
| |
| @Override |
| public void onDownloadStart(String url, String userAgent, String contentDisposition, |
| String mimetype, long contentLength) { |
| Log.v(TAG, String.format("Download request ignored: %s", url)); |
| } |
| }); |
| } |
| |
| void resetLatch() { |
| if (mLatch.getCount() != 1) { |
| Log.w(TAG, "Expecting latch to be 1, but it's not!"); |
| } else { |
| mLatch.countDown(); |
| } |
| } |
| |
| void resetForNewPage() { |
| mLatch = new CountDownLatch(1); |
| pageLoadFinishCalled = false; |
| pageProgressFull = false; |
| } |
| |
| void waitForLoad() throws InterruptedException { |
| boolean timedout = !mLatch.await(PAGE_LOAD_TIMEOUT, TimeUnit.MILLISECONDS); |
| if (timedout) { |
| Log.w(TAG, "page timeout. trying to stop."); |
| // try to stop page load |
| mInst.runOnMainSync(new Runnable(){ |
| public void run() { |
| mController.getTabControl().getCurrentTab().getWebView().stopLoading(); |
| } |
| }); |
| // try to wait for count down latch again |
| timedout = !mLatch.await(5000, TimeUnit.MILLISECONDS); |
| if (timedout) { |
| throw new RuntimeException("failed to stop timedout site, is browser pegged?"); |
| } |
| } |
| } |
| |
| private static class RunStatus { |
| private File mFile; |
| private int iteration; |
| private int page; |
| private String url; |
| private boolean isRecovery; |
| private boolean allClear; |
| |
| private RunStatus(File file) throws IOException { |
| mFile = file; |
| FileReader input = null; |
| BufferedReader reader = null; |
| isRecovery = false; |
| allClear = false; |
| iteration = 0; |
| page = 0; |
| try { |
| input = new FileReader(mFile); |
| isRecovery = true; |
| reader = new BufferedReader(input); |
| String line = reader.readLine(); |
| if (line == null) |
| return; |
| iteration = Integer.parseInt(line); |
| line = reader.readLine(); |
| if (line == null) |
| return; |
| page = Integer.parseInt(line); |
| } catch (FileNotFoundException ex) { |
| return; |
| } catch (NumberFormatException nfe) { |
| Log.wtf(TAG, "unexpected data in status file, will start from begining"); |
| return; |
| } finally { |
| try { |
| if (reader != null) { |
| reader.close(); |
| } |
| } finally { |
| if (input != null) { |
| input.close(); |
| } |
| } |
| } |
| } |
| |
| public static RunStatus load() throws IOException { |
| return load(sStatusFile); |
| } |
| |
| public static RunStatus load(String file) throws IOException { |
| return new RunStatus(new File(sExternalStorage, file)); |
| } |
| |
| public void write() throws IOException { |
| FileWriter output = null; |
| if (mFile.exists()) { |
| mFile.delete(); |
| } |
| try { |
| output = new FileWriter(mFile); |
| output.write(iteration + newLine); |
| output.write(page + newLine); |
| output.write(url + newLine); |
| } finally { |
| if (output != null) { |
| output.close(); |
| } |
| } |
| } |
| |
| public void cleanUp() { |
| // only perform cleanup when allClear flag is set |
| // i.e. when the test was not interrupted by a Java crash |
| if (mFile.exists() && allClear) { |
| mFile.delete(); |
| } |
| } |
| |
| public void resetPage() { |
| page = 0; |
| } |
| |
| public void incrementPage() { |
| ++page; |
| allClear = true; |
| } |
| |
| public void incrementIteration() { |
| ++iteration; |
| } |
| |
| public int getPage() { |
| return page; |
| } |
| |
| public int getIteration() { |
| return iteration; |
| } |
| |
| public boolean getIsRecovery() { |
| return isRecovery; |
| } |
| |
| public void setUrl(String url) { |
| this.url = url; |
| allClear = false; |
| } |
| } |
| |
| /** |
| * Loops over a list of URLs, points the browser to each one, and records the time elapsed. |
| * |
| * @param input the reader from which to get the URLs. |
| * @param writer the writer to which to output the results. |
| * @param clearCache determines whether the cache is cleared before loading each page |
| * @param loopCount the number of times to loop through the list of pages |
| * @throws IOException unable to read from input or write to writer. |
| * @throws InterruptedException the thread was interrupted waiting for the page to load. |
| */ |
| void loopUrls(BufferedReader input, OutputStreamWriter writer, |
| boolean clearCache, int loopCount) |
| throws IOException, InterruptedException { |
| Tab tab = mController.getTabControl().getCurrentTab(); |
| WebView webView = tab.getWebView(); |
| |
| List<String> pages = new LinkedList<String>(); |
| |
| String page; |
| while (null != (page = input.readLine())) { |
| if (!TextUtils.isEmpty(page)) { |
| pages.add(page); |
| } |
| } |
| |
| Iterator<String> iterator = pages.iterator(); |
| for (int i = 0; i < mStatus.getPage(); ++i) { |
| iterator.next(); |
| } |
| |
| if (mStatus.getIsRecovery()) { |
| Log.e(TAG, "Recovering after crash: " + iterator.next()); |
| mStatus.incrementPage(); |
| } |
| |
| while (mStatus.getIteration() < loopCount) { |
| if (clearCache) { |
| clearCacheUiThread(webView, true); |
| } |
| while(iterator.hasNext()) { |
| page = iterator.next(); |
| mStatus.setUrl(page); |
| mStatus.write(); |
| Log.i(TAG, "start: " + page); |
| Uri uri = Uri.parse(page); |
| final Intent intent = new Intent(Intent.ACTION_VIEW, uri); |
| intent.putExtra(Browser.EXTRA_APPLICATION_ID, |
| getInstrumentation().getTargetContext().getPackageName()); |
| |
| long startTime = System.currentTimeMillis(); |
| resetForNewPage(); |
| mInst.runOnMainSync(new Runnable() { |
| |
| public void run() { |
| mActivity.onNewIntent(intent); |
| } |
| |
| }); |
| waitForLoad(); |
| long stopTime = System.currentTimeMillis(); |
| |
| String url = getUrlUiThread(webView); |
| Log.i(TAG, "finish: " + url); |
| |
| if (writer != null) { |
| writer.write(page + "|" + (stopTime - startTime) + newLine); |
| writer.flush(); |
| } |
| |
| mStatus.incrementPage(); |
| } |
| mStatus.incrementIteration(); |
| mStatus.resetPage(); |
| iterator = pages.iterator(); |
| } |
| } |
| |
| public void testLoadPerformance() throws IOException, InterruptedException { |
| setUpBrowser(); |
| |
| OutputStreamWriter writer = getOutputStream(); |
| try { |
| BufferedReader bufferedReader = getInputStream(); |
| try { |
| loopUrls(bufferedReader, writer, true, PERF_LOOPCOUNT); |
| } finally { |
| if (bufferedReader != null) { |
| bufferedReader.close(); |
| } |
| } |
| } catch (FileNotFoundException fnfe) { |
| Log.e(TAG, fnfe.getMessage(), fnfe); |
| fail("Test environment not setup correctly"); |
| } finally { |
| if (writer != null) { |
| writer.close(); |
| } |
| } |
| } |
| |
| public void testStability() throws IOException, InterruptedException { |
| setUpBrowser(); |
| |
| BufferedReader bufferedReader = getInputStream(); |
| try { |
| loopUrls(bufferedReader, null, true, STABILITY_LOOPCOUNT); |
| } catch (FileNotFoundException fnfe) { |
| Log.e(TAG, fnfe.getMessage(), fnfe); |
| fail("Test environment not setup correctly"); |
| } finally { |
| if (bufferedReader != null) { |
| bufferedReader.close(); |
| } |
| } |
| } |
| |
| private void clearCacheUiThread(final WebView webView, final boolean includeDiskFiles) { |
| Runnable runner = new Runnable() { |
| |
| @Override |
| public void run() { |
| webView.clearCache(includeDiskFiles); |
| } |
| }; |
| getInstrumentation().runOnMainSync(runner); |
| } |
| |
| private String getUrlUiThread(final WebView webView) { |
| WebViewUrlGetter urlGetter = new WebViewUrlGetter(webView); |
| getInstrumentation().runOnMainSync(urlGetter); |
| return urlGetter.getUrl(); |
| } |
| |
| private class WebViewUrlGetter implements Runnable { |
| |
| private WebView mWebView; |
| private String mUrl; |
| |
| public WebViewUrlGetter(WebView webView) { |
| mWebView = webView; |
| } |
| |
| @Override |
| public void run() { |
| mUrl = null; |
| mUrl = mWebView.getUrl(); |
| } |
| |
| public String getUrl() { |
| if (mUrl != null) { |
| return mUrl; |
| } else |
| throw new IllegalStateException("url has not been fetched yet"); |
| } |
| } |
| } |