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
17package com.android.browser;
18
19import java.io.BufferedReader;
20import java.io.File;
21import java.io.FileNotFoundException;
22import java.io.FileReader;
23import java.io.FileWriter;
24import java.io.IOException;
25import java.io.OutputStreamWriter;
26import java.util.Iterator;
27import java.util.LinkedList;
28import java.util.List;
29import java.util.concurrent.CountDownLatch;
30import java.util.concurrent.TimeUnit;
31
32import android.app.Instrumentation;
33import android.content.Intent;
34import android.net.Uri;
35import android.net.http.SslError;
36import android.os.Environment;
37import android.test.ActivityInstrumentationTestCase2;
38import android.util.Log;
39import android.webkit.HttpAuthHandler;
40import android.webkit.JsPromptResult;
41import android.webkit.JsResult;
42import android.webkit.SslErrorHandler;
43import android.webkit.WebView;
44
45/**
46 *
47 * Iterates over a list of URLs from a file and outputs the time to load each.
48 */
49public class PopularUrlsTest extends ActivityInstrumentationTestCase2<BrowserActivity> {
50
51    private final static String TAG = "PopularUrlsTest";
52    private final static String newLine = System.getProperty("line.separator");
53    private final static String sInputFile = "popular_urls.txt";
54    private final static String sOutputFile = "test_output.txt";
55    private final static String sStatusFile = "test_status.txt";
56    private final static File sExternalStorage = Environment.getExternalStorageDirectory();
57
58    private final static int PERF_LOOPCOUNT = 10;
59    private final static int STABILITY_LOOPCOUNT = 1;
60    private final static int PAGE_LOAD_TIMEOUT = 120000; // 2 minutes
61
62    private BrowserActivity mActivity = null;
63    private Instrumentation mInst = null;
64    private CountDownLatch mLatch = new CountDownLatch(1);
65    private RunStatus mStatus;
66
67    public PopularUrlsTest() {
68        super(BrowserActivity.class);
69    }
70
71    @Override
72    protected void setUp() throws Exception {
73        super.setUp();
74
75        mActivity = getActivity();
76        mInst = getInstrumentation();
77        mInst.waitForIdleSync();
78
79        mStatus = RunStatus.load();
80    }
81
82    @Override
83    protected void tearDown() throws Exception {
84        if (mStatus != null) {
85            mStatus.cleanUp();
86        }
87
88        super.tearDown();
89    }
90
91    static BufferedReader getInputStream() throws FileNotFoundException {
92        return getInputStream(sInputFile);
93    }
94
95    static BufferedReader getInputStream(String inputFile) throws FileNotFoundException {
96        String path = sExternalStorage + File.separator + inputFile;
97        FileReader fileReader = new FileReader(path);
98        BufferedReader bufferedReader = new BufferedReader(fileReader);
99
100        return bufferedReader;
101    }
102
103    OutputStreamWriter getOutputStream() throws IOException {
104        return getOutputStream(sOutputFile);
105    }
106
107    OutputStreamWriter getOutputStream(String outputFile) throws IOException {
108        String path = sExternalStorage + File.separator + outputFile;
109
110        File file = new File(path);
111
112        return new FileWriter(file, mStatus.getIsRecovery());
113    }
114
115    /**
116     * Gets the browser ready for testing by starting the application
117     * and wrapping the WebView's helper clients.
118     */
119    void setUpBrowser() {
120        Tab tab = mActivity.getTabControl().getCurrentTab();
121        WebView webView = tab.getWebView();
122
123        webView.setWebChromeClient(new TestWebChromeClient(webView.getWebChromeClient()) {
124
125            /**
126             * Reset the latch whenever page progress reaches 100%.
127             */
128            @Override
129            public void onProgressChanged(WebView view, int newProgress) {
130                super.onProgressChanged(view, newProgress);
131                if (newProgress >= 100) {
132                    resetLatch();
133                }
134            }
135
136            /**
137             * Dismisses and logs Javascript alerts.
138             */
139            @Override
140            public boolean onJsAlert(WebView view, String url, String message,
141                    JsResult result) {
142                String logMsg = String.format("JS Alert '%s' received from %s", message, url);
143                Log.w(TAG, logMsg);
144                result.confirm();
145
146                return true;
147            }
148
149            /**
150             * Confirms and logs Javascript alerts.
151             */
152            @Override
153            public boolean onJsConfirm(WebView view, String url, String message,
154                    JsResult result) {
155                String logMsg = String.format("JS Confirmation '%s' received from %s",
156                        message, url);
157                Log.w(TAG, logMsg);
158                result.confirm();
159
160                return true;
161            }
162
163            /**
164             * Confirms and logs Javascript alerts, providing the default value.
165             */
166            @Override
167            public boolean onJsPrompt(WebView view, String url, String message,
168                    String defaultValue, JsPromptResult result) {
169                String logMsg = String.format("JS Prompt '%s' received from %s; " +
170                        "Giving default value '%s'", message, url, defaultValue);
171                Log.w(TAG, logMsg);
172                result.confirm(defaultValue);
173
174                return true;
175            }
176        });
177
178        webView.setWebViewClient(new TestWebViewClient(webView.getWebViewClient()) {
179
180            /**
181             * Bypasses and logs errors.
182             */
183            @Override
184            public void onReceivedError(WebView view, int errorCode,
185                    String description, String failingUrl) {
186                String message = String.format("Error '%s' (%d) loading url: %s",
187                        description, errorCode, failingUrl);
188                Log.w(TAG, message);
189            }
190
191            /**
192             * Ignores and logs SSL errors.
193             */
194            @Override
195            public void onReceivedSslError(WebView view, SslErrorHandler handler,
196                    SslError error) {
197                Log.w(TAG, "SSL error: " + error);
198                handler.proceed();
199            }
200
201            /**
202             * Ignores http auth with dummy username and password
203             */
204            @Override
205            public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler,
206                    String host, String realm) {
207                handler.proceed("user", "passwd");
208            }
209        });
210    }
211
212    void resetLatch() {
213        CountDownLatch temp = mLatch;
214        mLatch = new CountDownLatch(1);
215        if (temp != null) {
216            // Notify existing latch that it's done.
217            while (temp.getCount() > 0) {
218                temp.countDown();
219            }
220        }
221    }
222
223    void waitForLoad() throws InterruptedException {
224        boolean timedout = !mLatch.await(PAGE_LOAD_TIMEOUT, TimeUnit.MILLISECONDS);
225        if (timedout) {
226            Log.w(TAG, "page timeout. trying to stop.");
227            // try to stop page load
228            mInst.runOnMainSync(new Runnable(){
229                public void run() {
230                    mActivity.getTabControl().getCurrentTab().getWebView().stopLoading();
231                }
232            });
233            // try to wait for count down latch again
234            timedout = !mLatch.await(5000, TimeUnit.MILLISECONDS);
235            if (timedout) {
236                Log.e(TAG, "failed to stop the timedout site in 5s");
237            }
238        }
239    }
240
241    private static class RunStatus {
242        private File mFile;
243        private int iteration;
244        private int page;
245        private String url;
246        private boolean isRecovery;
247
248        private RunStatus(String file) throws IOException {
249            mFile = new File(file);
250            FileReader input = null;
251            BufferedReader reader = null;
252            try {
253                input = new FileReader(mFile);
254                isRecovery = true;
255                reader = new BufferedReader(input);
256                iteration = Integer.parseInt(reader.readLine());
257                page = Integer.parseInt(reader.readLine());
258            } catch (FileNotFoundException ex) {
259                isRecovery = false;
260                iteration = 0;
261                page = 0;
262            } finally {
263                try {
264                    if (reader != null) {
265                        reader.close();
266                    }
267                } finally {
268                    if (input != null) {
269                        input.close();
270                    }
271                }
272            }
273        }
274
275        public static RunStatus load() throws IOException {
276            return load(sStatusFile);
277        }
278
279        public static RunStatus load(String file) throws IOException {
280            return new RunStatus(sExternalStorage + File.separator + file);
281        }
282
283        public void write() throws IOException {
284            FileWriter output = null;
285            OutputStreamWriter writer = null;
286            if (mFile.exists()) {
287                mFile.delete();
288            }
289            try {
290                output = new FileWriter(mFile);
291                output.write(iteration + newLine);
292                output.write(page + newLine);
293                output.write(url + newLine);
294            } finally {
295                try {
296                    if (writer != null) {
297                        writer.close();
298                    }
299                } finally {
300                    if (output != null) {
301                        output.close();
302                    }
303                }
304            }
305        }
306
307        public void cleanUp() {
308            if (mFile.exists()) {
309                mFile.delete();
310            }
311        }
312
313        public void resetPage() {
314            page = 0;
315        }
316
317        public void incrementPage() {
318            ++page;
319        }
320
321        public void incrementIteration() {
322            ++iteration;
323        }
324
325        public int getPage() {
326            return page;
327        }
328
329        public int getIteration() {
330            return iteration;
331        }
332
333        public boolean getIsRecovery() {
334            return isRecovery;
335        }
336
337        public void setUrl(String url) {
338            this.url = url;
339        }
340    }
341
342    /**
343     * Loops over a list of URLs, points the browser to each one, and records the time elapsed.
344     *
345     * @param input the reader from which to get the URLs.
346     * @param writer the writer to which to output the results.
347     * @param clearCache determines whether the cache is cleared before loading each page
348     * @param loopCount the number of times to loop through the list of pages
349     * @throws IOException unable to read from input or write to writer.
350     * @throws InterruptedException the thread was interrupted waiting for the page to load.
351     */
352    void loopUrls(BufferedReader input, OutputStreamWriter writer,
353            boolean clearCache, int loopCount)
354            throws IOException, InterruptedException {
355        Tab tab = mActivity.getTabControl().getCurrentTab();
356        WebView webView = tab.getWebView();
357
358        List<String> pages = new LinkedList<String>();
359
360        String page;
361        while (null != (page = input.readLine())) {
362            pages.add(page);
363        }
364
365        Iterator<String> iterator = pages.iterator();
366        for (int i = 0; i < mStatus.getPage(); ++i) {
367            iterator.next();
368        }
369
370        if (mStatus.getIsRecovery()) {
371            Log.e(TAG, "Recovering after crash: " + iterator.next());
372        }
373
374        while (mStatus.getIteration() < loopCount) {
375            while(iterator.hasNext()) {
376                page = iterator.next();
377                mStatus.setUrl(page);
378                mStatus.write();
379                Log.i(TAG, "start: " + page);
380                Uri uri = Uri.parse(page);
381                if (clearCache) {
382                    webView.clearCache(true);
383                }
384                final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
385
386                long startTime = System.currentTimeMillis();
387                mInst.runOnMainSync(new Runnable() {
388
389                    public void run() {
390                        mActivity.onNewIntent(intent);
391                    }
392
393                });
394                waitForLoad();
395                long stopTime = System.currentTimeMillis();
396
397                String url = webView.getUrl();
398                Log.i(TAG, "finish: " + url);
399
400                if (writer != null) {
401                    writer.write(page + "|" + (stopTime - startTime) + newLine);
402                    writer.flush();
403                }
404
405                mStatus.incrementPage();
406            }
407            mStatus.incrementIteration();
408            mStatus.resetPage();
409            iterator = pages.iterator();
410        }
411    }
412
413    public void testLoadPerformance() throws IOException, InterruptedException {
414        setUpBrowser();
415
416        OutputStreamWriter writer = getOutputStream();
417        try {
418            BufferedReader bufferedReader = getInputStream();
419            try {
420                loopUrls(bufferedReader, writer, true, PERF_LOOPCOUNT);
421            } finally {
422                if (bufferedReader != null) {
423                    bufferedReader.close();
424                }
425            }
426        } finally {
427            if (writer != null) {
428                writer.close();
429            }
430        }
431    }
432
433    public void testStability() throws IOException, InterruptedException {
434        setUpBrowser();
435
436        BufferedReader bufferedReader = getInputStream();
437        try {
438            loopUrls(bufferedReader, null, true, STABILITY_LOOPCOUNT);
439        } finally {
440            if (bufferedReader != null) {
441                bufferedReader.close();
442            }
443        }
444    }
445}
446