diff options
author | Abhisek Devkota <ciwrl@cyanogenmod.com> | 2015-08-28 17:43:17 -0700 |
---|---|---|
committer | Abhisek Devkota <ciwrl@cyanogenmod.com> | 2015-08-28 17:43:17 -0700 |
commit | f6ebac34baf99592f414a228490c2625a0fa8e37 (patch) | |
tree | 8f15ab75baa1eb3572309ac639c062a61de92705 /src/tests/src/com/android/browser | |
parent | 8b6a361c4b0d45d3e3e01019db1900d86ccba3b1 (diff) | |
download | android_packages_apps_Gello-f6ebac34baf99592f414a228490c2625a0fa8e37.tar.gz android_packages_apps_Gello-f6ebac34baf99592f414a228490c2625a0fa8e37.tar.bz2 android_packages_apps_Gello-f6ebac34baf99592f414a228490c2625a0fa8e37.zip |
Combine source & prebuilt to single project
Todo: Makefile logic
Diffstat (limited to 'src/tests/src/com/android/browser')
15 files changed, 2724 insertions, 0 deletions
diff --git a/src/tests/src/com/android/browser/BrowserLaunchPerformance.java b/src/tests/src/com/android/browser/BrowserLaunchPerformance.java new file mode 100644 index 00000000..c5d4279f --- /dev/null +++ b/src/tests/src/com/android/browser/BrowserLaunchPerformance.java @@ -0,0 +1,26 @@ +package com.android.browser; + +import android.app.Activity; +import android.os.Bundle; +import android.test.LaunchPerformanceBase; + +public class BrowserLaunchPerformance extends LaunchPerformanceBase { + + @Override + public void onCreate(Bundle arguments) { + super.onCreate(arguments); + + mIntent.setClassName(getTargetContext(), "com.android.browser.BrowserActivity"); + start(); + } + + /** + * Calls LaunchApp and finish. + */ + @Override + public void onStart() { + super.onStart(); + LaunchApp(); + finish(Activity.RESULT_OK, mResults); + } +} diff --git a/src/tests/src/com/android/browser/BrowserProviderTests.java b/src/tests/src/com/android/browser/BrowserProviderTests.java new file mode 100644 index 00000000..c63cad17 --- /dev/null +++ b/src/tests/src/com/android/browser/BrowserProviderTests.java @@ -0,0 +1,177 @@ +/* + * 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 com.android.browser.provider.BrowserProvider; +import com.android.browser.provider.BrowserProvider2; +import com.android.browser.tests.utils.ProviderTestCase3; + +import android.app.SearchManager; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BrowserContract; +import android.test.suitebuilder.annotation.MediumTest; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Unit tests for {@link BrowserProvider}. + */ +@MediumTest +public class BrowserProviderTests extends ProviderTestCase3<BrowserProvider2> { + + private ArrayList<Uri> mDeleteUris; + + public BrowserProviderTests() { + super(BrowserProvider2.class, + BrowserContract.AUTHORITY, BrowserProvider2.LEGACY_AUTHORITY); + } + + @Override + protected void setUp() throws Exception { + mDeleteUris = new ArrayList<Uri>(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + for (Uri uri : mDeleteUris) { + deleteUri(uri); + } + super.tearDown(); + } + + public void testHasDefaultBookmarks() { + Cursor c = getBookmarksSuggest(""); + try { + assertTrue("No default bookmarks", c.getCount() > 0); + } finally { + c.close(); + } + } + + public void testPartialFirstTitleWord() { + assertInsertQuery("http://www.example.com/rasdfe", "nfgjra sdfywe", "nfgj"); + } + + public void testFullFirstTitleWord() { + assertInsertQuery("http://www.example.com/", "nfgjra dfger", "nfgjra"); + } + + public void testFullFirstTitleWordPartialSecond() { + assertInsertQuery("http://www.example.com/", "nfgjra dfger", "nfgjra df"); + } + + public void testFullTitle() { + assertInsertQuery("http://www.example.com/", "nfgjra dfger", "nfgjra dfger"); + } + +// Not implemented in BrowserProvider +// public void testFullSecondTitleWord() { +// assertInsertQuery("http://www.example.com/rasdfe", "nfgjra sdfywe", "sdfywe"); +// } + + public void testFullTitleJapanese() { + String title = "\u30ae\u30e3\u30e9\u30ea\u30fc\u30fcGoogle\u691c\u7d22"; + assertInsertQuery("http://www.example.com/sdaga", title, title); + } + + public void testPartialTitleJapanese() { + String title = "\u30ae\u30e3\u30e9\u30ea\u30fc\u30fcGoogle\u691c\u7d22"; + String query = "\u30ae\u30e3\u30e9\u30ea\u30fc"; + assertInsertQuery("http://www.example.com/sdaga", title, query); + } + + // Test for http://b/issue?id=2152749 + public void testSoundmarkTitleJapanese() { + String title = "\u30ae\u30e3\u30e9\u30ea\u30fc\u30fcGoogle\u691c\u7d22"; + String query = "\u30ad\u30e3\u30e9\u30ea\u30fc"; + assertInsertQuery("http://www.example.com/sdaga", title, query); + } + + // + // Utilities + // + + private void assertInsertQuery(String url, String title, String query) { + addBookmark(url, title); + assertQueryReturns(url, title, query); + } + + private void assertQueryReturns(String url, String title, String query) { + Cursor c = getBookmarksSuggest(query); + try { + assertTrue(title + " not matched by " + query, c.getCount() > 0); + assertTrue("More than one result for " + query, c.getCount() == 1); + while (c.moveToNext()) { + String text1 = getCol(c, SearchManager.SUGGEST_COLUMN_TEXT_1); + assertNotNull(text1); + assertEquals("Bad title", title, text1); + String text2 = getCol(c, SearchManager.SUGGEST_COLUMN_TEXT_2); + assertNotNull(text2); + String data = getCol(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA); + assertNotNull(data); + assertEquals("Bad URL", url, data); + } + } finally { + c.close(); + } + } + + private Cursor getBookmarksSuggest(String query) { + Uri suggestUri = Uri.parse("content://browser/bookmarks/search_suggest_query"); + String[] selectionArgs = { query }; + Cursor c = getMockContentResolver().query(suggestUri, null, "url LIKE ?", + selectionArgs, null); + assertNotNull(c); + return c; + } + + private void addBookmark(String url, String title) { + Uri uri = insertBookmark(url, title); + assertNotNull(uri); + assertFalse(android.provider.Browser.BOOKMARKS_URI.equals(uri)); + mDeleteUris.add(uri); + } + + private Uri insertBookmark(String url, String title) { + ContentValues values = new ContentValues(); + values.put("title", title); + values.put("url", url); + values.put("visits", 0); + values.put("date", 0); + values.put("created", 0); + values.put("bookmark", 1); + return getMockContentResolver().insert(android.provider.Browser.BOOKMARKS_URI, + values); + } + + private void deleteUri(Uri uri) { + int count = getMockContentResolver().delete(uri, null, null); + assertEquals("Failed to delete " + uri, 1, count); + } + + private static String getCol(Cursor c, String name) { + int col = c.getColumnIndex(name); + String msg = "Column " + name + " not found, columns: " + + Arrays.toString(c.getColumnNames()); + assertTrue(msg, col >= 0); + return c.getString(col); + } +} diff --git a/src/tests/src/com/android/browser/IntentHandlerTests.java b/src/tests/src/com/android/browser/IntentHandlerTests.java new file mode 100644 index 00000000..50cf5029 --- /dev/null +++ b/src/tests/src/com/android/browser/IntentHandlerTests.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2011 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.Activity; +import android.content.Intent; +import android.net.Uri; +import android.provider.Browser; +import android.test.ActivityInstrumentationTestCase2; +import android.text.TextUtils; +import org.codeaurora.swe.WebView; + +public class IntentHandlerTests extends ActivityInstrumentationTestCase2<BrowserActivity> { + + // How long to wait to receive onPageStarted + static final int START_LOAD_TIMEOUT = 20000; // ms + static final int POLL_INTERVAL = 50; // ms + boolean mHasStarted = false; + + public IntentHandlerTests() { + super(BrowserActivity.class); + } + + public void testSwitchToTabWithUrl() throws Throwable { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("http://google.com/")); + sendIntent(intent); + Controller controller = getActivity().getController(); + Tab tabGoogle = controller.getCurrentTab(); + assertNotNull("Current tab (google.com", tabGoogle); + assertEquals("http://google.com/", tabGoogle.getOriginalUrl()); + assertEquals(1, controller.getTabs().size()); + intent.setData(Uri.parse("http://maps.google.com/")); + sendIntent(intent); + Tab tabMaps = controller.getCurrentTab(); + assertNotSame(tabGoogle, tabMaps); + assertNotNull("Current tab (maps.google.com)", tabMaps); + assertEquals(2, controller.getTabs().size()); + intent.setData(Uri.parse("http://google.com/")); + sendIntent(intent); + assertEquals(tabGoogle, controller.getCurrentTab()); + assertEquals(2, controller.getTabs().size()); + } + + public void testShortcut() throws Throwable { + Intent intent = BookmarkUtils.createShortcutIntent("http://google.com/"); + sendIntent(intent); + Controller controller = getActivity().getController(); + Tab tabGoogle = controller.getCurrentTab(); + assertEquals("http://google.com/", tabGoogle.getOriginalUrl()); + assertEquals(1, controller.getTabs().size()); + sendIntent(intent); + assertEquals(1, controller.getTabs().size()); + assertEquals(tabGoogle, controller.getCurrentTab()); + directlyLoadUrl(tabGoogle, "http://maps.google.com/"); + sendIntent(intent); + if (BrowserActivity.isTablet(getActivity())) { + assertEquals(2, controller.getTabs().size()); + assertNotSame(tabGoogle, controller.getCurrentTab()); + assertEquals("http://maps.google.com/", tabGoogle.getOriginalUrl()); + Tab currentTab = controller.getCurrentTab(); + assertEquals("http://google.com/", currentTab.getOriginalUrl()); + } else { + assertEquals(1, controller.getTabs().size()); + assertEquals(tabGoogle, controller.getCurrentTab()); + assertEquals("http://google.com/", tabGoogle.getOriginalUrl()); + } + } + + public void testApplication() throws Throwable { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("http://google.com/")); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, getClass().getName()); + sendIntent(intent); + Controller controller = getActivity().getController(); + Tab tabGoogle = controller.getCurrentTab(); + assertNotNull("Current tab (google.com", tabGoogle); + assertEquals("http://google.com/", tabGoogle.getOriginalUrl()); + assertEquals(1, controller.getTabs().size()); + intent.setData(Uri.parse("http://maps.google.com/")); + sendIntent(intent); + Tab tabMaps = controller.getCurrentTab(); + assertEquals("http://maps.google.com/", tabMaps.getOriginalUrl()); + if (BrowserActivity.isTablet(getActivity())) { + assertEquals(2, controller.getTabs().size()); + assertNotSame(tabGoogle, tabMaps); + assertEquals("http://google.com/", tabGoogle.getOriginalUrl()); + } else { + assertEquals(1, controller.getTabs().size()); + assertEquals(tabGoogle, tabMaps); + } + } + + /** + * Simulate clicking a link by loading a URL directly on the WebView, + * bypassing Tab, Controller, etc.. + * @throws Throwable + */ + private void directlyLoadUrl(final Tab tab, final String url) throws Throwable { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + WebView web = tab.getWebView(); + web.loadUrl(url); + } + }); + waitForLoadStart(tab, url); + } + + void waitForLoadStart(final Tab tab, final String url) throws InterruptedException { + long start = System.currentTimeMillis(); + while (!TextUtils.equals(tab.getOriginalUrl(), url)) { + if (start + START_LOAD_TIMEOUT < System.currentTimeMillis()) { + throw new RuntimeException("Didn't receive onPageStarted!"); + } + Thread.sleep(POLL_INTERVAL); + } + } + + private void sendIntent(final Intent intent) throws Throwable { + sendIntent(intent, true); + } + + private void sendIntent(final Intent intent, boolean waitForLoadStart) throws Throwable { + if (!mHasStarted) { + // Prevent crash recovery from happening + intent.putExtra(Controller.NO_CRASH_RECOVERY, true); + setActivityIntent(intent); + getActivity(); + } else { + final Activity activity = getActivity(); + runTestOnUiThread(new Runnable() { + @Override + public void run() { + getInstrumentation().callActivityOnNewIntent(activity, intent); + } + }); + } + if (waitForLoadStart) { + String url = intent.getDataString(); + Tab tab = getActivity().getController().getCurrentTab(); + waitForLoadStart(tab, url); + } + } + + @Override + public BrowserActivity getActivity() { + mHasStarted = true; + return super.getActivity(); + } +} diff --git a/src/tests/src/com/android/browser/PopularUrlsTest.java b/src/tests/src/com/android/browser/PopularUrlsTest.java new file mode 100644 index 00000000..0070948e --- /dev/null +++ b/src/tests/src/com/android/browser/PopularUrlsTest.java @@ -0,0 +1,569 @@ +/* + * 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.DownloadListener; +import android.webkit.HttpAuthHandler; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.SslErrorHandler; +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; + +import org.codeaurora.swe.WebView; +/** + * + * 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 http auth with dummy username and password + */ + @Override + public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, + String host, String realm) { + handler.proceed("user", "passwd"); + } + + @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"); + } + } +} diff --git a/src/tests/src/com/android/browser/TestWebChromeClient.java b/src/tests/src/com/android/browser/TestWebChromeClient.java new file mode 100644 index 00000000..f10452f2 --- /dev/null +++ b/src/tests/src/com/android/browser/TestWebChromeClient.java @@ -0,0 +1,204 @@ +/* + * 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.graphics.Bitmap; +import android.net.Uri; +import android.os.Message; +import android.view.View; +import android.webkit.ConsoleMessage; +import android.webkit.GeolocationPermissions; +import android.webkit.ValueCallback; +import android.webkit.WebStorage; + +import org.codeaurora.swe.JsPromptResult; +import org.codeaurora.swe.JsResult; +import org.codeaurora.swe.WebChromeClient; +import org.codeaurora.swe.WebView; + +/** + * + * WebChromeClient for browser tests. + * Wraps around existing client so that specific methods can be overridden if needed. + * + */ +abstract class TestWebChromeClient extends WebChromeClient { + + private WebChromeClient mWrappedClient; + + protected TestWebChromeClient(WebChromeClient wrappedClient) { + mWrappedClient = wrappedClient; + } + + /** {@inheritDoc} */ + @Override + public void onProgressChanged(WebView view, int newProgress) { + mWrappedClient.onProgressChanged(view, newProgress); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedTitle(WebView view, String title) { + mWrappedClient.onReceivedTitle(view, title); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + mWrappedClient.onReceivedIcon(view, icon); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedTouchIconUrl(WebView view, String url, + boolean precomposed) { + mWrappedClient.onReceivedTouchIconUrl(view, url, precomposed); + } + + /** {@inheritDoc} */ + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + mWrappedClient.onShowCustomView(view, callback); + } + + /** {@inheritDoc} */ + @Override + public void onHideCustomView() { + mWrappedClient.onHideCustomView(); + } + + /** {@inheritDoc} */ + @Override + public boolean onCreateWindow(WebView view, boolean dialog, + boolean userGesture, Message resultMsg) { + // do not open any new pop-ups + resultMsg.sendToTarget(); + return true; + } + + /** {@inheritDoc} */ + @Override + public void onRequestFocus(WebView view) { + mWrappedClient.onRequestFocus(view); + } + + /** {@inheritDoc} */ + @Override + public void onCloseWindow(WebView window) { + mWrappedClient.onCloseWindow(window); + } + + /** {@inheritDoc} */ + @Override + public boolean onJsAlert(WebView view, String url, String message, + JsResult result) { + return mWrappedClient.onJsAlert(view, url, message, result); + } + + /** {@inheritDoc} */ + @Override + public boolean onJsConfirm(WebView view, String url, String message, + JsResult result) { + return mWrappedClient.onJsConfirm(view, url, message, result); + } + + /** {@inheritDoc} */ + @Override + public boolean onJsPrompt(WebView view, String url, String message, + String defaultValue, JsPromptResult result) { + return mWrappedClient.onJsPrompt(view, url, message, defaultValue, result); + } + + /** {@inheritDoc} */ + @Override + public boolean onJsBeforeUnload(WebView view, String url, String message, + JsResult result) { + return mWrappedClient.onJsBeforeUnload(view, url, message, result); + } + + /** {@inheritDoc} */ + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, + long currentQuota, long estimatedSize, long totalUsedQuota, + WebStorage.QuotaUpdater quotaUpdater) { + mWrappedClient.onExceededDatabaseQuota(url, databaseIdentifier, currentQuota, + estimatedSize, totalUsedQuota, quotaUpdater); + } + + /** {@inheritDoc} */ + @Override + public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, + WebStorage.QuotaUpdater quotaUpdater) { + mWrappedClient.onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota, quotaUpdater); + } + + /** {@inheritDoc} */ + @Override + public void onGeolocationPermissionsShowPrompt(String origin, + GeolocationPermissions.Callback callback) { + mWrappedClient.onGeolocationPermissionsShowPrompt(origin, callback); + } + + /** {@inheritDoc} */ + @Override + public void onGeolocationPermissionsHidePrompt() { + mWrappedClient.onGeolocationPermissionsHidePrompt(); + } + + /** {@inheritDoc} */ + @Override + public boolean onJsTimeout() { + return mWrappedClient.onJsTimeout(); + } + + /** {@inheritDoc} */ + @Override + @Deprecated + public void onConsoleMessage(String message, int lineNumber, String sourceID) { + mWrappedClient.onConsoleMessage(message, lineNumber, sourceID); + } + + /** {@inheritDoc} */ + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + return mWrappedClient.onConsoleMessage(consoleMessage); + } + + /** {@inheritDoc} */ + @Override + public Bitmap getDefaultVideoPoster() { + return mWrappedClient.getDefaultVideoPoster(); + } + + /** {@inheritDoc} */ + @Override + public View getVideoLoadingProgressView() { + return mWrappedClient.getVideoLoadingProgressView(); + } + + /** {@inheritDoc} */ + @Override + public void getVisitedHistory(ValueCallback<String[]> callback) { + mWrappedClient.getVisitedHistory(callback); + } + + /** {@inheritDoc} */ + @Override + public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { + mWrappedClient.openFileChooser(uploadFile, acceptType, capture); + } +} diff --git a/src/tests/src/com/android/browser/TestWebViewClient.java b/src/tests/src/com/android/browser/TestWebViewClient.java new file mode 100644 index 00000000..c092c96e --- /dev/null +++ b/src/tests/src/com/android/browser/TestWebViewClient.java @@ -0,0 +1,128 @@ +/* + * 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.graphics.Bitmap; +import android.net.http.SslError; +import android.os.Message; +import android.view.KeyEvent; +import android.webkit.WebViewClientClassicExt; + +import org.codeaurora.swe.HttpAuthHandler; +import org.codeaurora.swe.SslErrorHandler; +import org.codeaurora.swe.WebView; +import org.codeaurora.swe.WebViewClient; +/** + * + * + * WebViewClient for browser tests. + * Wraps around existing client so that specific methods can be overridden if needed. + * + */ +abstract class TestWebViewClient extends WebViewClientClassicExt { + + private WebViewClient mWrappedClient; + + protected TestWebViewClient(WebViewClient wrappedClient) { + mWrappedClient = wrappedClient; + } + + /** {@inheritDoc} */ + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return mWrappedClient.shouldOverrideUrlLoading(view, url); + } + + /** {@inheritDoc} */ + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + mWrappedClient.onPageStarted(view, url, favicon); + } + + /** {@inheritDoc} */ + @Override + public void onPageFinished(WebView view, String url) { + mWrappedClient.onPageFinished(view, url); + } + + /** {@inheritDoc} */ + @Override + public void onLoadResource(WebView view, String url) { + mWrappedClient.onLoadResource(view, url); + } + + /** {@inheritDoc} */ + @Deprecated + @Override + public void onTooManyRedirects(WebView view, Message cancelMsg, + Message continueMsg) { + mWrappedClient.onTooManyRedirects(view, cancelMsg, continueMsg); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedError(WebView view, int errorCode, + String description, String failingUrl) { + mWrappedClient.onReceivedError(view, errorCode, description, failingUrl); + } + + /** {@inheritDoc} */ + @Override + public void onFormResubmission(WebView view, Message dontResend, + Message resend) { + mWrappedClient.onFormResubmission(view, dontResend, resend); + } + + /** {@inheritDoc} */ + @Override + public void doUpdateVisitedHistory(WebView view, String url, + boolean isReload) { + mWrappedClient.doUpdateVisitedHistory(view, url, isReload); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, + SslError error) { + mWrappedClient.onReceivedSslError(view, handler, error); + } + + /** {@inheritDoc} */ + @Override + public void onReceivedHttpAuthRequest(WebView view, + HttpAuthHandler handler, String host, String realm) { + mWrappedClient.onReceivedHttpAuthRequest(view, handler, host, realm); + } + + /** {@inheritDoc} */ + @Override + public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) { + return mWrappedClient.shouldOverrideKeyEvent(view, event); + } + + /** {@inheritDoc} */ + @Override + public void onUnhandledKeyEvent(WebView view, KeyEvent event) { + mWrappedClient.onUnhandledKeyEvent(view, event); + } + + /** {@inheritDoc} */ + @Override + public void onScaleChanged(WebView view, float oldScale, float newScale) { + mWrappedClient.onScaleChanged(view, oldScale, newScale); + } +} diff --git a/src/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java b/src/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java new file mode 100644 index 00000000..2beedf8b --- /dev/null +++ b/src/tests/src/com/android/browser/WebStorageSizeManagerUnitTests.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2009 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.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.webkit.WebStorage; + +/** + * This is a series of unit tests for the WebStorageSizeManager class. + * + */ +@MediumTest +public class WebStorageSizeManagerUnitTests extends AndroidTestCase { + // Used for testing the out-of-space callbacks. + private long mNewQuota; + // Callback functor that sets a new quota in case of out-of-space scenarios. + private class MockQuotaUpdater implements WebStorage.QuotaUpdater { + public void updateQuota(long newQuota) { + mNewQuota = newQuota; + } + } + + // Mock the DiskInfo. + private class MockDiskInfo implements WebStorageSizeManager.DiskInfo { + private long mFreeSize; + private long mTotalSize; + + public long getFreeSpaceSizeBytes() { + return mFreeSize; + } + + public long getTotalSizeBytes() { + return mTotalSize; + } + + public void setFreeSpaceSizeBytes(long freeSize) { + mFreeSize = freeSize; + } + + public void setTotalSizeBytes(long totalSize) { + mTotalSize = totalSize; + } + } + + // Mock the AppCacheInfo + public class MockAppCacheInfo implements WebStorageSizeManager.AppCacheInfo { + private long mAppCacheSize; + + public long getAppCacheSizeBytes() { + return mAppCacheSize; + } + + public void setAppCacheSizeBytes(long appCacheSize) { + mAppCacheSize = appCacheSize; + } + } + + private MockQuotaUpdater mQuotaUpdater = new MockQuotaUpdater(); + private final MockDiskInfo mDiskInfo = new MockDiskInfo(); + private final MockAppCacheInfo mAppCacheInfo = new MockAppCacheInfo(); + // Utility for making size computations easier to read. + private long bytes(double megabytes) { + return (new Double(megabytes * 1024 * 1024)).longValue(); + } + /** + * Test the onExceededDatabaseQuota and onReachedMaxAppCacheSize callbacks + */ + public void testCallbacks() { + long totalUsedQuota = 0; + final long quotaIncrease = WebStorageSizeManager.QUOTA_INCREASE_STEP; // 1MB + + // We have 75 MB total, 24MB free so the global limit will be 12 MB. + mDiskInfo.setTotalSizeBytes(bytes(75)); + mDiskInfo.setFreeSpaceSizeBytes(bytes(24)); + // We have an appcache file size of 0 MB. + mAppCacheInfo.setAppCacheSizeBytes(0); + // Create the manager. + WebStorageSizeManager manager = new WebStorageSizeManager(getContext(), mDiskInfo, + mAppCacheInfo); + // We add origin 1. + long origin1Quota = 0; + long origin1EstimatedSize = bytes(3.5); + manager.onExceededDatabaseQuota("1", "1", origin1Quota, origin1EstimatedSize, totalUsedQuota, mQuotaUpdater); + assertEquals(origin1EstimatedSize, mNewQuota); + origin1Quota = mNewQuota; + totalUsedQuota += origin1Quota; + + // We add origin 2. + long origin2Quota = 0; + long origin2EstimatedSize = bytes(2.5); + manager.onExceededDatabaseQuota("2", "2", origin2Quota, origin2EstimatedSize, totalUsedQuota, mQuotaUpdater); + assertEquals(origin2EstimatedSize, mNewQuota); + origin2Quota = mNewQuota; + totalUsedQuota += origin2Quota; + + // Origin 1 runs out of space. + manager.onExceededDatabaseQuota("1", "1", origin1Quota, 0, totalUsedQuota, mQuotaUpdater); + assertEquals(origin1EstimatedSize + quotaIncrease, mNewQuota); + totalUsedQuota -= origin1Quota; + origin1Quota = mNewQuota; + totalUsedQuota += origin1Quota; + + // Origin 2 runs out of space. + manager.onExceededDatabaseQuota("2", "2", origin2Quota, 0, totalUsedQuota, mQuotaUpdater); + assertEquals(origin2EstimatedSize + quotaIncrease, mNewQuota); + totalUsedQuota -= origin2Quota; + origin2Quota = mNewQuota; + totalUsedQuota += origin2Quota; + + // We add origin 3. TotalUsedQuota is 8 (3.5 + 2.5 + 1 + 1). AppCacheMaxSize is 3 (12 / 4). + // So we have 1 MB free. + long origin3Quota = 0; + long origin3EstimatedSize = bytes(5); + manager.onExceededDatabaseQuota("3", "3", origin3Quota, origin3EstimatedSize, totalUsedQuota, mQuotaUpdater); + assertEquals(0, mNewQuota); // We cannot satisfy the estimatedSize + origin3Quota = mNewQuota; + totalUsedQuota += origin3Quota; + + // Origin 1 runs out of space again. It should increase it's quota to take the last 1MB. + manager.onExceededDatabaseQuota("1", "1", origin1Quota, 0, totalUsedQuota, mQuotaUpdater); + assertEquals(origin1Quota + quotaIncrease, mNewQuota); + totalUsedQuota -= origin1Quota; + origin1Quota = mNewQuota; + totalUsedQuota += origin1Quota; + + // Origin 1 runs out of space again. It should inow fail to increase in size. + manager.onExceededDatabaseQuota("1", "1", origin1Quota, 0, totalUsedQuota, mQuotaUpdater); + assertEquals(origin1Quota, mNewQuota); + + // We try adding a new origin. Which will fail. + manager.onExceededDatabaseQuota("4", "4", 0, bytes(1), totalUsedQuota, mQuotaUpdater); + assertEquals(0, mNewQuota); + + // AppCache size increases to 2MB... + mAppCacheInfo.setAppCacheSizeBytes(bytes(2)); + // ... and wants 2MB more. Fail. + manager.onReachedMaxAppCacheSize(bytes(2), totalUsedQuota, mQuotaUpdater); + assertEquals(0, mNewQuota); + + // The user nukes origin 2 + totalUsedQuota -= origin2Quota; + origin2Quota = 0; + // TotalUsedQuota is 5.5 (9 - 3.5). AppCacheMaxSize is 3. AppCacheSize is 2. + // AppCache wants 1.5MB more + manager.onReachedMaxAppCacheSize(bytes(1.5), totalUsedQuota, mQuotaUpdater); + mAppCacheInfo.setAppCacheSizeBytes(mAppCacheInfo.getAppCacheSizeBytes() + bytes(2.5)); + assertEquals(mAppCacheInfo.getAppCacheSizeBytes(), mNewQuota - WebStorageSizeManager.APPCACHE_MAXSIZE_PADDING); + + // We try adding a new origin. This time we succeed. + // TotalUsedQuota is 5.5. AppCacheMaxSize is 5.0. So we have 12 - 10.5 = 1.5 available. + long origin4Quota = 0; + long origin4EstimatedSize = bytes(1.5); + manager.onExceededDatabaseQuota("4", "4", origin4Quota, origin4EstimatedSize, totalUsedQuota, mQuotaUpdater); + assertEquals(bytes(1.5), mNewQuota); + origin4Quota = mNewQuota; + totalUsedQuota += origin4Quota; + } + /** + * Test the application caches max size calculator. + */ + public void testCalculateGlobalLimit() { + long fileSystemSize = 78643200; // 75 MB + long freeSpaceSize = 25165824; // 24 MB + long maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(12582912, maxSize); // 12MB + + fileSystemSize = 78643200; // 75 MB + freeSpaceSize = 60 * 1024 * 1024; // 60MB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(19922944, maxSize); // 19MB + + fileSystemSize = 8589934592L; // 8 GB + freeSpaceSize = 4294967296L; // 4 GB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(536870912L, maxSize); // 512 MB + + fileSystemSize = -14; + freeSpaceSize = 21; + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(0, maxSize); + + fileSystemSize = 100; + freeSpaceSize = 101; + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(0, maxSize); + + fileSystemSize = 3774873; // ~4.2 MB + freeSpaceSize = 2560000; // ~2.4 MB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(2097152, maxSize); // 2 MB + + fileSystemSize = 4404019; // ~4.2 MB + freeSpaceSize = 3774873; // ~3.6 MB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(2097152, maxSize); // 2 MB + + fileSystemSize = 4404019; // ~4.2 MB + freeSpaceSize = 4404019; // ~4.2 MB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(3145728, maxSize); // 3 MB + + fileSystemSize = 1048576; // 1 MB + freeSpaceSize = 1048575; // 1 MB - 1 byte + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(0, maxSize); + + fileSystemSize = 3774873; // ~3.6 MB + freeSpaceSize = 2097151; // 2 MB - 1 byte + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(0, maxSize); + + fileSystemSize = 3774873; // ~3.6 MB + freeSpaceSize = 2097151; // 2 MB + maxSize = WebStorageSizeManager.calculateGlobalLimit(fileSystemSize, freeSpaceSize); + assertEquals(0, maxSize); + } + + public void testManyDatabasesOnOneOrigin() { + // This test ensures that if an origin creates more than one database, the quota that is + // assigned to the origin after the second creation is enough to satisfy all databases + // under that origin. + // See b/2417477. + + long totalUsedQuota = 0; + mDiskInfo.setTotalSizeBytes(bytes(100)); + mDiskInfo.setFreeSpaceSizeBytes(bytes(100)); + // This should give us a storage area of 13MB, with 3.25MB for appcache and 9.75MB for + // databases. + assertEquals(bytes(13), WebStorageSizeManager.calculateGlobalLimit( + mDiskInfo.getTotalSizeBytes(), mDiskInfo.getFreeSpaceSizeBytes())); + + // We have an appcache file size of 0 MB. + mAppCacheInfo.setAppCacheSizeBytes(0); + + // Create the manager. + WebStorageSizeManager manager = new WebStorageSizeManager(getContext(), mDiskInfo, + mAppCacheInfo); + + // We add an origin. + long originQuota = 0; + long database1EstimatedSize = bytes(2); + manager.onExceededDatabaseQuota("1", "1", originQuota, database1EstimatedSize, + totalUsedQuota, mQuotaUpdater); + assertEquals(database1EstimatedSize, mNewQuota); + originQuota = mNewQuota; + totalUsedQuota = originQuota; + + // Now try to create a new database under the origin, by invoking onExceededDatabaseQuota + // again. This time, request more space than the old quota + the quota increase step. + long database2EstimatedSize = bytes(3.5); + manager.onExceededDatabaseQuota("1", "2", originQuota, database2EstimatedSize, + totalUsedQuota, mQuotaUpdater); + assertEquals(database1EstimatedSize + database2EstimatedSize, mNewQuota); + originQuota = mNewQuota; + totalUsedQuota = originQuota; + + // Create another database, but this time use a size that will overflow the space on the + // device. It should be denied. + long database3EstimatedSize = bytes(50); + manager.onExceededDatabaseQuota("1", "3", originQuota, database3EstimatedSize, + totalUsedQuota, mQuotaUpdater); + assertEquals(originQuota, mNewQuota); + + // Create another database. This time, request less than the old quota. + long database4EstimatedSize = bytes(2); + manager.onExceededDatabaseQuota("1", "4", originQuota, database4EstimatedSize, + totalUsedQuota, mQuotaUpdater); + assertEquals(database1EstimatedSize + database2EstimatedSize + database4EstimatedSize, + mNewQuota); + originQuota = mNewQuota; + totalUsedQuota = originQuota; + + // Now have the first database overflow it's quota. We should get 1 more MB. + manager.onExceededDatabaseQuota("1", "1", originQuota, 0, totalUsedQuota, mQuotaUpdater); + assertEquals(database1EstimatedSize + database2EstimatedSize + database4EstimatedSize + + bytes(1), mNewQuota); + originQuota = mNewQuota; + totalUsedQuota = originQuota; + + // Create a db under the origin that uses a quota less than the usual quota increase step. + long database5EstimatedSize = bytes(0.5); + manager.onExceededDatabaseQuota("1", "5", originQuota, database5EstimatedSize, + totalUsedQuota, mQuotaUpdater); + assertEquals(database1EstimatedSize + database2EstimatedSize + database4EstimatedSize + + bytes(1) + database5EstimatedSize, mNewQuota); + } +} diff --git a/src/tests/src/com/android/browser/tests/BP1to2UpgradeTests.java b/src/tests/src/com/android/browser/tests/BP1to2UpgradeTests.java new file mode 100644 index 00000000..ea5cfaa5 --- /dev/null +++ b/src/tests/src/com/android/browser/tests/BP1to2UpgradeTests.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2011 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.tests; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.net.Uri; +import android.provider.Browser; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.browser.platformsupport.BookmarkColumns; +import com.android.browser.platformsupport.BrowserContract; +import com.android.browser.platformsupport.BrowserContract.Bookmarks; +import com.android.browser.platformsupport.BrowserContract.History; +import com.android.browser.platformsupport.BrowserContract.Images; +import com.android.browser.provider.BrowserProvider; +import com.android.browser.tests.utils.BP2TestCaseHelper; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + +@SmallTest +public class BP1to2UpgradeTests extends BP2TestCaseHelper { + + BrowserProvider mBp1; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mBp1 = new BrowserProvider(); + mBp1.attachInfo(getMockContext(), null); + } + + /** + * Test that simply makes sure BP1->BP2 with no changes works as intended + */ + public void testStockUpgrade() { + Cursor c = mBp1.query(Browser.BOOKMARKS_URI, + new String[] { BookmarkColumns.URL }, null, null, + BookmarkColumns.URL + " DESC"); + ArrayList<String> urls = new ArrayList<String>(c.getCount()); + while (c.moveToNext()) { + urls.add(c.getString(0)); + } + c.close(); + // First, test the public API (which will hit BP2) + c = getMockContentResolver().query(Browser.BOOKMARKS_URI, + new String[] { BookmarkColumns.URL }, null, null, + BookmarkColumns.URL + " DESC"); + assertEquals(urls.size(), c.getCount()); + int i = 0; + while (c.moveToNext()) { + assertEquals(urls.get(i++), c.getString(0)); + } + c.close(); + // Next, test BP2's new API (not a public API) + c = getMockContentResolver().query(Bookmarks.CONTENT_URI, + new String[] { Bookmarks.URL }, null, null, + Bookmarks.URL + " DESC"); + assertEquals(urls.size(), c.getCount()); + i = 0; + while (c.moveToNext()) { + assertEquals(urls.get(i++), c.getString(0)); + } + c.close(); + } + + public void testPreserveHistory() { + ContentValues values = new ContentValues(); + values.put(BookmarkColumns.URL, "http://slashdot.org/"); + values.put(BookmarkColumns.BOOKMARK, 0); + values.put(BookmarkColumns.DATE, 123456); + mBp1.insert(Browser.BOOKMARKS_URI, values); + // First, test internal API + Cursor c = getMockContentResolver().query(History.CONTENT_URI, + new String[] { History.URL, History.DATE_LAST_VISITED }, + null, null, null); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals("http://slashdot.org/", c.getString(0)); + assertEquals(123456, c.getInt(1)); + c.close(); + // Next, test public API + c = getMockContentResolver().query(Browser.BOOKMARKS_URI, + Browser.HISTORY_PROJECTION, BookmarkColumns.BOOKMARK + " = 0", + null, null); + assertEquals("public API", 1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals("http://slashdot.org/", + c.getString(Browser.HISTORY_PROJECTION_URL_INDEX)); + assertEquals(123456, c.getInt(Browser.HISTORY_PROJECTION_DATE_INDEX)); + c.close(); + } + + public void testPreserveBookmarks() { + // First, nuke 'er (deletes stock bookmarks) + mBp1.delete(Browser.BOOKMARKS_URI, null, null); + ContentValues values = new ContentValues(); + values.put(BookmarkColumns.URL, "http://slashdot.org/"); + values.put(BookmarkColumns.BOOKMARK, 1); + values.put(BookmarkColumns.CREATED, 123456); + mBp1.insert(Browser.BOOKMARKS_URI, values); + // First, test internal API + Cursor c = getMockContentResolver().query(Bookmarks.CONTENT_URI, + new String[] { Bookmarks.URL, Bookmarks.DATE_CREATED }, + null, null, null); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals("http://slashdot.org/", c.getString(0)); + assertEquals(123456, c.getInt(1)); + c.close(); + // Next, test public API + c = getMockContentResolver().query(Browser.BOOKMARKS_URI, + new String[] { BookmarkColumns.URL, BookmarkColumns.CREATED }, + BookmarkColumns.BOOKMARK + " = 1", null, null); + assertEquals("public API", 1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals("http://slashdot.org/", c.getString(0)); + assertEquals(123456, c.getInt(1)); + c.close(); + } + + public void testEmptyUpgrade() { + mBp1.delete(Browser.BOOKMARKS_URI, null, null); + Cursor c = getMockContentResolver().query(Bookmarks.CONTENT_URI, + null, null, null, null); + assertEquals(0, c.getCount()); + c.close(); + } + +} diff --git a/src/tests/src/com/android/browser/tests/BP2ProviderTests.java b/src/tests/src/com/android/browser/tests/BP2ProviderTests.java new file mode 100644 index 00000000..23b75d0e --- /dev/null +++ b/src/tests/src/com/android/browser/tests/BP2ProviderTests.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2011 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.tests; + +import com.android.browser.tests.utils.BP2TestCaseHelper; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.browser.platformsupport.BrowserContract; +import com.android.browser.platformsupport.BrowserContract.Images; + +import java.io.ByteArrayOutputStream; + +@SmallTest +public class BP2ProviderTests extends BP2TestCaseHelper { + + static final String[] PROJECTION = new String[] { + BrowserContract.Bookmarks.PARENT, + BrowserContract.Bookmarks.ACCOUNT_NAME, + BrowserContract.Bookmarks.ACCOUNT_TYPE, + }; + static final int INDEX_PARENT = 0; + static final int INDEX_ACCOUNT_NAME = 1; + static final int INDEX_ACCOUNT_TYPE = 2; + + public void testUpdateImage() { + String url = "http://stub1.com"; + insertBookmark(url, "stub 1"); + ContentValues values = new ContentValues(); + values.put(Images.URL, url); + Bitmap bitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, os); + values.put(Images.THUMBNAIL, os.toByteArray()); + // Use updateBookmarks because the bookmarks URI observer should + // be triggered, even though we aren't giving it a bookmarks URI + assertTrue(updateBookmark(Images.CONTENT_URI, values)); + } + + public void testIsValidParentNullAccount() { + doTestIsValidParent(null, null); + } + + public void testIsValidParentWithAccount() { + doTestIsValidParent("test@gmail.com", "com.google"); + } + + private void doTestIsValidParent(String accountName, String accountType) { + // Create the folder + ContentValues values = new ContentValues(); + values.put(BrowserContract.Bookmarks.TITLE, "New Folder"); + values.put(BrowserContract.Bookmarks.IS_FOLDER, 1); + values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountName); + values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountType); + Uri folderUri = insertBookmark(values); + assertNotNull(folderUri); + long folderId = ContentUris.parseId(folderUri); + assertTrue("Failed to parse folder id!", folderId > 0); + // Insert a bookmark with the same ACCOUNT_* info as parent + values.put(BrowserContract.Bookmarks.TITLE, "google"); + values.put(BrowserContract.Bookmarks.URL, "http://google.com"); + values.put(BrowserContract.Bookmarks.IS_FOLDER, 0); + values.put(BrowserContract.Bookmarks.PARENT, folderId); + Uri insertedUri = insertBookmark(values); + assertNotNull(insertedUri); + Cursor c = getMockContentResolver().query(insertedUri, + PROJECTION, null, null, null); + try { + assertNotNull(c); + assertTrue(c.moveToFirst()); + long insertedParentId = c.getLong(INDEX_PARENT); + String insertedAccountName = c.getString(INDEX_ACCOUNT_NAME); + String insertedAccountType = c.getString(INDEX_ACCOUNT_TYPE); + assertEquals(folderId, insertedParentId); + assertEquals(accountName, insertedAccountName); + assertEquals(accountType, insertedAccountType); + + // Insert a bookmark with no ACCOUNT_* set, BUT with a valid parent + // The inserted should end up with the ACCOUNT_* of the parent + values.remove(BrowserContract.Bookmarks.ACCOUNT_NAME); + values.remove(BrowserContract.Bookmarks.ACCOUNT_TYPE); + insertedUri = insertBookmark(values); + assertNotNull(insertedUri); + c.close(); + c = getMockContentResolver().query(insertedUri, + PROJECTION, null, null, null); + assertNotNull(c); + assertTrue(c.moveToFirst()); + insertedParentId = c.getLong(INDEX_PARENT); + insertedAccountName = c.getString(INDEX_ACCOUNT_NAME); + insertedAccountType = c.getString(INDEX_ACCOUNT_TYPE); + assertEquals(folderId, insertedParentId); + assertEquals(accountName, insertedAccountName); + assertEquals(accountType, insertedAccountType); + + // Insert a bookmark with a different ACCOUNT_* than it's parent + // ACCOUNT_* should override parent + accountName = accountName + "@something.else"; + accountType = "com.google"; + values.put(BrowserContract.Bookmarks.ACCOUNT_NAME, accountName); + values.put(BrowserContract.Bookmarks.ACCOUNT_TYPE, accountType); + insertedUri = insertBookmark(values); + assertNotNull(insertedUri); + c.close(); + c = getMockContentResolver().query(insertedUri, + PROJECTION, null, null, null); + assertNotNull(c); + assertTrue(c.moveToFirst()); + insertedParentId = c.getLong(INDEX_PARENT); + insertedAccountName = c.getString(INDEX_ACCOUNT_NAME); + insertedAccountType = c.getString(INDEX_ACCOUNT_TYPE); + assertNotSame(folderId, insertedParentId); + assertEquals(accountName, insertedAccountName); + assertEquals(accountType, insertedAccountType); + } finally { + c.close(); + } + } +} diff --git a/src/tests/src/com/android/browser/tests/BP2UriObserverTests.java b/src/tests/src/com/android/browser/tests/BP2UriObserverTests.java new file mode 100644 index 00000000..a3b001aa --- /dev/null +++ b/src/tests/src/com/android/browser/tests/BP2UriObserverTests.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011 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.tests; + +import android.content.ContentValues; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.net.Uri; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.browser.platformsupport.BrowserContract.Bookmarks; +import com.android.browser.platformsupport.BrowserContract.History; +import com.android.browser.tests.utils.BP2TestCaseHelper; + +import java.io.ByteArrayOutputStream; + +@SmallTest +public class BP2UriObserverTests extends BP2TestCaseHelper { + + public void testInsertBookmark() { + Uri insertedUri = insertBookmark("http://stub1.com", "Stub1"); + TriggeredObserver stubObs = new TriggeredObserver(insertedUri); + assertObserversTriggered(false, stubObs); + insertBookmark("http://stub2.com", "Stub2"); + perfIdeallyUntriggered(stubObs); + } + + public void testUpdateBookmark() { + Uri toUpdate = insertBookmark("http://stub1.com", "Stub1"); + Uri unchanged = insertBookmark("http://stub2.com", "Stub2"); + TriggeredObserver updateObs = new TriggeredObserver(toUpdate); + TriggeredObserver unchangedObs = new TriggeredObserver(unchanged); + assertObserversTriggered(false, updateObs, unchangedObs); + assertTrue(updateBookmark(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs")); + assertTrue("Update observer not notified!", updateObs.checkTriggered()); + perfIdeallyUntriggered(unchangedObs); + } + + public void testUpdateBookmarkImages() { + Uri toUpdate = insertBookmark("http://stub1.com", "Stub1"); + Uri unchanged = insertBookmark("http://stub2.com", "Stub2"); + Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + favicon.compress(Bitmap.CompressFormat.PNG, 100, os); + byte[] rawFavicon = os.toByteArray(); + ContentValues values = new ContentValues(); + values.put(Bookmarks.FAVICON, rawFavicon); + values.put(Bookmarks.TITLE, "Stub1"); + TriggeredObserver updateObs = new TriggeredObserver(toUpdate); + TriggeredObserver unchangedObs = new TriggeredObserver(unchanged); + assertTrue(updateBookmark(toUpdate, values)); + assertTrue("Update observer not notified!", updateObs.checkTriggered()); + perfIdeallyUntriggered(unchangedObs); + } + + public void testInsertHistory() { + Uri insertedUri = insertHistory("http://stub1.com", "Stub1"); + TriggeredObserver stubObs = new TriggeredObserver(insertedUri); + assertObserversTriggered(false, stubObs); + insertHistory("http://stub2.com", "Stub2"); + perfIdeallyUntriggered(stubObs); + } + + public void testUpdateHistory() { + Uri toUpdate = insertHistory("http://stub1.com", "Stub1"); + Uri unchanged = insertHistory("http://stub2.com", "Stub2"); + TriggeredObserver updateObs = new TriggeredObserver(toUpdate); + TriggeredObserver unchangedObs = new TriggeredObserver(unchanged); + assertObserversTriggered(false, updateObs, unchangedObs); + assertTrue(updateHistory(toUpdate, "http://stub1.com", "Stub1: Revenge of the stubs")); + assertTrue("Update observer not notified!", updateObs.checkTriggered()); + perfIdeallyUntriggered(unchangedObs); + } + + public void testUpdateHistoryImages() { + Uri toUpdate = insertHistory("http://stub1.com", "Stub1"); + Uri unchanged = insertHistory("http://stub2.com", "Stub2"); + Bitmap favicon = Bitmap.createBitmap(16, 16, Config.ARGB_8888); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + favicon.compress(Bitmap.CompressFormat.PNG, 100, os); + byte[] rawFavicon = os.toByteArray(); + ContentValues values = new ContentValues(); + values.put(History.FAVICON, rawFavicon); + values.put(History.TITLE, "Stub1"); + TriggeredObserver updateObs = new TriggeredObserver(toUpdate); + TriggeredObserver unchangedObs = new TriggeredObserver(unchanged); + assertTrue(updateHistory(toUpdate, values)); + assertTrue("Update observer not notified!", updateObs.checkTriggered()); + perfIdeallyUntriggered(unchangedObs); + } +} diff --git a/src/tests/src/com/android/browser/tests/BookmarksTests.java b/src/tests/src/com/android/browser/tests/BookmarksTests.java new file mode 100644 index 00000000..f4b91093 --- /dev/null +++ b/src/tests/src/com/android/browser/tests/BookmarksTests.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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.tests; + +import android.content.ContentResolver; +import android.database.Cursor; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.browser.Bookmarks; +import com.android.browser.tests.utils.BP2TestCaseHelper; + +/** + * Extends from BP2TestCaseHelper for the helper methods + * and to get the mock database + */ +@SmallTest +public class BookmarksTests extends BP2TestCaseHelper { + + public void testQueryCombinedForUrl() { + // First, add some bookmarks + assertNotNull(insertBookmark( + "http://google.com/search?q=test", "Test search")); + assertNotNull(insertBookmark( + "http://google.com/search?q=mustang", "Mustang search")); + assertNotNull(insertBookmark( + "http://google.com/search?q=aliens", "Aliens search")); + ContentResolver cr = getMockContentResolver(); + + Cursor c = null; + try { + // First, search for a match + String url = "http://google.com/search?q=test"; + c = Bookmarks.queryCombinedForUrl(cr, null, url); + assertEquals(1, c.getCount()); + assertTrue(c.moveToFirst()); + assertEquals(url, c.getString(0)); + c.close(); + + // Next, search for no match + url = "http://google.com/search"; + c = Bookmarks.queryCombinedForUrl(cr, null, url); + assertEquals(0, c.getCount()); + assertFalse(c.moveToFirst()); + c.close(); + } finally { + if (c != null) c.close(); + } + } + +} diff --git a/src/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java b/src/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java new file mode 100644 index 00000000..9133a872 --- /dev/null +++ b/src/tests/src/com/android/browser/tests/utils/BP2TestCaseHelper.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2011 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.tests.utils; + +import com.android.browser.provider.BrowserProvider2; + +import java.io.File; +import java.io.FilenameFilter; + +import android.content.ContentValues; +import android.database.ContentObserver; +import android.net.Uri; +import android.provider.Browser; +import android.util.Log; + +import com.android.browser.platformsupport.BrowserContract; +import com.android.browser.platformsupport.BrowserContract.Bookmarks; +import com.android.browser.platformsupport.BrowserContract.History; + +/** + * This is a replacement for ProviderTestCase2 that can handle notifyChange testing. + * It also has helper methods specifically for testing BrowserProvider2 + */ +public abstract class BP2TestCaseHelper extends ProviderTestCase3<BrowserProvider2> { + + // Tag for potential performance impacts + private static final String PERFTAG = "BP2-PerfCheck"; + + private TriggeredObserver mLegacyObserver; + private TriggeredObserver mRootObserver; + private TriggeredObserver mBookmarksObserver; + private TriggeredObserver mHistoryObserver; + private TriggeredObserver mWidgetObserver; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mLegacyObserver = new TriggeredObserver(Browser.BOOKMARKS_URI); + mRootObserver = new TriggeredObserver(BrowserContract.AUTHORITY_URI); + mBookmarksObserver = new TriggeredObserver(Bookmarks.CONTENT_URI); + mHistoryObserver = new TriggeredObserver(History.CONTENT_URI); + mWidgetObserver = new TriggeredObserver(); + // We don't need to worry about setting this back to null since this + // is a private instance local to the MockContentResolver + getProvider().setWidgetObserver(mWidgetObserver); + } + + public BP2TestCaseHelper() { + super(BrowserProvider2.class, + BrowserContract.AUTHORITY, BrowserProvider2.LEGACY_AUTHORITY); + } + + public void perfIdeallyUntriggered(TriggeredObserver... obs) { + for (TriggeredObserver ob : obs) { + if (ob.checkTriggered()) { + // Not ideal, unnecessary notification + Log.i(PERFTAG, ob.mUri + " onChange called but content unaltered!"); + } + } + } + + public void assertObserversTriggered(boolean triggered, + TriggeredObserver... observers) { + for (TriggeredObserver obs : observers) { + assertEquals(obs.mUri + ", descendents:" + obs.mNotifyForDescendents, + triggered, obs.checkTriggered()); + } + } + + public class TriggeredObserver extends ContentObserver { + private boolean mTriggered; + Uri mUri; + boolean mNotifyForDescendents; + + /** + * Creates an unmanaged TriggeredObserver + */ + public TriggeredObserver() { + super(null); + } + + /** + * Same as TriggeredObserver(uri, true); + */ + public TriggeredObserver(Uri uri) { + this(uri, true); + } + + /** + * Creates a managed TriggeredObserver that self-registers with the + * mock ContentResolver + */ + public TriggeredObserver(Uri uri, boolean notifyForDescendents) { + super(null); + mUri = uri; + mNotifyForDescendents = notifyForDescendents; + registerContentObserver(uri, notifyForDescendents, this); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mTriggered = true; + } + + public boolean checkTriggered() { + boolean ret = mTriggered; + mTriggered = false; + return ret; + } + } + + Uri mockInsert(Uri uri, ContentValues values) { + assertObserversTriggered(false, mLegacyObserver, mRootObserver); + Uri ret = getMockContentResolver().insert(uri, values); + assertObserversTriggered(true, mLegacyObserver, mRootObserver); + return ret; + } + + int mockUpdate(Uri uri, ContentValues values, String where, + String[] selectionArgs) { + assertObserversTriggered(false, mLegacyObserver, mRootObserver); + int ret = getMockContentResolver().update(uri, values, where, selectionArgs); + if (ret > 0) { + assertObserversTriggered(true, mLegacyObserver, mRootObserver); + } else { + perfIdeallyUntriggered(mLegacyObserver); + perfIdeallyUntriggered(mRootObserver); + } + return ret; + } + + public Uri insertBookmark(String url, String title) { + ContentValues values = new ContentValues(); + values.put(BrowserContract.Bookmarks.TITLE, title); + values.put(BrowserContract.Bookmarks.URL, url); + values.put(BrowserContract.Bookmarks.IS_FOLDER, 0); + return insertBookmark(values); + } + + public Uri insertBookmark(ContentValues values) { + assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver); + Uri ret = mockInsert(Bookmarks.CONTENT_URI, values); + assertObserversTriggered(true, mBookmarksObserver, mWidgetObserver); + perfIdeallyUntriggered(mHistoryObserver); + return ret; + } + + public boolean updateBookmark(Uri uri, String url, String title) { + ContentValues values = new ContentValues(); + values.put(BrowserContract.Bookmarks.TITLE, title); + values.put(BrowserContract.Bookmarks.URL, url); + return updateBookmark(uri, values); + } + + public boolean updateBookmark(Uri uri, ContentValues values) { + assertObserversTriggered(false, mBookmarksObserver, mWidgetObserver); + int modifyCount = mockUpdate(uri, values, null, null); + assertTrue("UpdatedBookmark modified too much! " + uri, modifyCount <= 1); + boolean updated = modifyCount == 1; + if (updated) { + assertObserversTriggered(updated, mBookmarksObserver, mWidgetObserver); + } else { + perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver); + } + perfIdeallyUntriggered(mHistoryObserver); + return updated; + } + + public Uri insertHistory(String url, String title) { + ContentValues values = new ContentValues(); + values.put(BrowserContract.History.TITLE, title); + values.put(BrowserContract.History.URL, url); + assertObserversTriggered(false, mHistoryObserver); + Uri ret = mockInsert(History.CONTENT_URI, values); + assertObserversTriggered(true, mHistoryObserver); + perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver); + return ret; + } + + public boolean updateHistory(Uri uri, String url, String title) { + ContentValues values = new ContentValues(); + values.put(BrowserContract.History.TITLE, title); + values.put(BrowserContract.History.URL, url); + return updateHistory(uri, values); + } + + public boolean updateHistory(Uri uri, ContentValues values) { + assertObserversTriggered(false, mHistoryObserver); + int modifyCount = mockUpdate(uri, values, null, null); + assertTrue("UpdatedHistory modified too much! " + uri, modifyCount <= 1); + boolean updated = modifyCount == 1; + if (updated) { + assertObserversTriggered(updated, mHistoryObserver); + } else { + perfIdeallyUntriggered(mHistoryObserver); + } + perfIdeallyUntriggered(mBookmarksObserver, mWidgetObserver); + return updated; + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + // Delete the test databases so that subsequent runs have a clean slate + File f = getMockContext().getDatabasePath("test"); + File dir = f.getParentFile(); + File testFiles[] = dir.listFiles(new FilenameFilter() { + + @Override + public boolean accept(File dir, String filename) { + return filename.startsWith(ProviderTestCase3.FILENAME_PREFIX); + } + }); + for (File testFile : testFiles) { + testFile.delete(); + } + } +} diff --git a/src/tests/src/com/android/browser/tests/utils/MockContentResolver2.java b/src/tests/src/com/android/browser/tests/utils/MockContentResolver2.java new file mode 100644 index 00000000..4fed65a0 --- /dev/null +++ b/src/tests/src/com/android/browser/tests/utils/MockContentResolver2.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 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.tests.utils; + +import com.google.android.collect.Maps; + +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.IContentProvider; +import android.database.ContentObserver; +import android.net.Uri; + +import java.util.Map; + +public class MockContentResolver2 extends ContentResolver { + + Map<String, ContentProvider> mProviders; + private final MockObserverNode mRootNode = new MockObserverNode(""); + + /* + * Creates a local map of providers. This map is used instead of the global map when an + * API call tries to acquire a provider. + */ + public MockContentResolver2() { + super(null); + mProviders = Maps.newHashMap(); + } + + /** + * Adds access to a provider based on its authority + * + * @param name The authority name associated with the provider. + * @param provider An instance of {@link android.content.ContentProvider} or one of its + * subclasses, or null. + */ + public void addProvider(String name, ContentProvider provider) { + + /* + * Maps the authority to the provider locally. + */ + mProviders.put(name, provider); + } + + /** @hide */ + @Override + protected IContentProvider acquireProvider(Context context, String name) { + return acquireExistingProvider(context, name); + } + + /** @hide */ + @Override + protected IContentProvider acquireExistingProvider(Context context, String name) { + + /* + * Gets the content provider from the local map + */ + final ContentProvider provider = mProviders.get(name); + + if (provider != null) { + return provider.getIContentProvider(); + } else { + return null; + } + } + + /** @hide */ + @Override + public boolean releaseProvider(IContentProvider provider) { + return true; + } + + /** @hide */ + protected IContentProvider acquireUnstableProvider(Context c, String name) { + return acquireProvider(c, name); + } + + /** @hide */ + public boolean releaseUnstableProvider(IContentProvider icp) { + return releaseProvider(icp); + } + + /** @hide */ + public void unstableProviderDied(IContentProvider icp) { + } + + @Override + public void notifyChange(Uri uri, ContentObserver observer, + boolean syncToNetwork) { + mRootNode.notifyMyObservers(uri, 0, observer, false); + } + + public void safeRegisterContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer) { + mRootNode.addObserver(uri, observer, notifyForDescendents); + } + + public void safeUnregisterContentObserver(ContentObserver observer) { + mRootNode.removeObserver(observer); + } + +} diff --git a/src/tests/src/com/android/browser/tests/utils/MockObserverNode.java b/src/tests/src/com/android/browser/tests/utils/MockObserverNode.java new file mode 100644 index 00000000..edcffd4f --- /dev/null +++ b/src/tests/src/com/android/browser/tests/utils/MockObserverNode.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 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.tests.utils; + +import android.database.ContentObserver; +import android.net.Uri; + +import java.util.ArrayList; + +public final class MockObserverNode { + private class MockObserverEntry { + public final ContentObserver observer; + public final boolean notifyForDescendents; + + public MockObserverEntry(ContentObserver o, boolean n) { + observer = o; + notifyForDescendents = n; + } + } + + public static final int INSERT_TYPE = 0; + public static final int UPDATE_TYPE = 1; + public static final int DELETE_TYPE = 2; + + private String mName; + private ArrayList<MockObserverNode> mChildren = new ArrayList<MockObserverNode>(); + private ArrayList<MockObserverEntry> mObservers = new ArrayList<MockObserverEntry>(); + + public MockObserverNode(String name) { + mName = name; + } + + private String getUriSegment(Uri uri, int index) { + if (uri != null) { + if (index == 0) { + return uri.getAuthority(); + } else { + return uri.getPathSegments().get(index - 1); + } + } else { + return null; + } + } + + private int countUriSegments(Uri uri) { + if (uri == null) { + return 0; + } + return uri.getPathSegments().size() + 1; + } + + public void addObserver(Uri uri, ContentObserver observer, + boolean notifyForDescendents) { + addObserver(uri, 0, observer, notifyForDescendents); + } + + private void addObserver(Uri uri, int index, ContentObserver observer, + boolean notifyForDescendents) { + // If this is the leaf node add the observer + if (index == countUriSegments(uri)) { + mObservers.add(new MockObserverEntry(observer, notifyForDescendents)); + return; + } + + // Look to see if the proper child already exists + String segment = getUriSegment(uri, index); + if (segment == null) { + throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); + } + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + MockObserverNode node = mChildren.get(i); + if (node.mName.equals(segment)) { + node.addObserver(uri, index + 1, observer, notifyForDescendents); + return; + } + } + + // No child found, create one + MockObserverNode node = new MockObserverNode(segment); + mChildren.add(node); + node.addObserver(uri, index + 1, observer, notifyForDescendents); + } + + public boolean removeObserver(ContentObserver observer) { + int size = mChildren.size(); + for (int i = 0; i < size; i++) { + boolean empty = mChildren.get(i).removeObserver(observer); + if (empty) { + mChildren.remove(i); + i--; + size--; + } + } + + size = mObservers.size(); + for (int i = 0; i < size; i++) { + MockObserverEntry entry = mObservers.get(i); + if (entry.observer == observer) { + mObservers.remove(i); + break; + } + } + + if (mChildren.size() == 0 && mObservers.size() == 0) { + return true; + } + return false; + } + + private void notifyMyObservers(boolean leaf, ContentObserver observer, + boolean selfNotify) { + int N = mObservers.size(); + for (int i = 0; i < N; i++) { + MockObserverEntry entry = mObservers.get(i); + + // Don't notify the observer if it sent the notification and isn't interesed + // in self notifications + if (entry.observer == observer && !selfNotify) { + continue; + } + + // Make sure the observer is interested in the notification + if (leaf || (!leaf && entry.notifyForDescendents)) { + entry.observer.onChange(selfNotify); + } + } + } + + public void notifyMyObservers(Uri uri, int index, ContentObserver observer, + boolean selfNotify) { + String segment = null; + int segmentCount = countUriSegments(uri); + if (index >= segmentCount) { + // This is the leaf node, notify all observers + notifyMyObservers(true, observer, selfNotify); + } else if (index < segmentCount){ + segment = getUriSegment(uri, index); + // Notify any observers at this level who are interested in descendents + notifyMyObservers(false, observer, selfNotify); + } + + int N = mChildren.size(); + for (int i = 0; i < N; i++) { + MockObserverNode node = mChildren.get(i); + if (segment == null || node.mName.equals(segment)) { + // We found the child, + node.notifyMyObservers(uri, index + 1, observer, selfNotify); + if (segment != null) { + break; + } + } + } + } +} diff --git a/src/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java b/src/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java new file mode 100644 index 00000000..75bc052e --- /dev/null +++ b/src/tests/src/com/android/browser/tests/utils/ProviderTestCase3.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2011 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.tests.utils; + +import android.content.ContentProvider; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.net.Uri; +import android.test.AndroidTestCase; +import android.test.IsolatedContext; +import android.test.RenamingDelegatingContext; +import android.test.mock.MockContext; + +import java.io.File; + +/** + * Replacement for ProviderTestCase2 that keeps calls to ContentResolver.notifyChanged + * internal to observers registered with ProviderTestCase3.registerContentObserver + */ +public abstract class ProviderTestCase3<T extends ContentProvider> extends AndroidTestCase { + + public static final String FILENAME_PREFIX = "test."; + + Class<T> mProviderClass; + String[] mProviderAuthority; + + private IsolatedContext mProviderContext; + private MockContentResolver2 mResolver; + + private class MockContext2 extends MockContext { + + @Override + public Resources getResources() { + return getContext().getResources(); + } + + @Override + public File getDir(String name, int mode) { + // name the directory so the directory will be separated from + // one created through the regular Context + return getContext().getDir("mockcontext2_" + name, mode); + } + + @Override + public String getPackageName() { + return getContext().getPackageName(); + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + return getContext().getSharedPreferences("mockcontext2_" + name, mode); + } + + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public Object getSystemService(String name) { + return null; + } + } + /** + * Constructor. + * + * @param providerClass The class name of the provider under test + * @param providerAuthorities The provider's authority string + */ + public ProviderTestCase3(Class<T> providerClass, String... providerAuthorities) { + mProviderClass = providerClass; + mProviderAuthority = providerAuthorities; + } + + private T mProvider; + + /** + * Returns the content provider created by this class in the {@link #setUp()} method. + * @return T An instance of the provider class given as a parameter to the test case class. + */ + public T getProvider() { + return mProvider; + } + + /** + * Sets up the environment for the test fixture. + * <p> + * Creates a new + * {@link com.android.browser.tests.utils.MockContentResolver2}, a new IsolatedContext + * that isolates the provider's file operations, and a new instance of + * the provider under test within the isolated environment. + * </p> + * + * @throws Exception + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + + mResolver = new MockContentResolver2(); + RenamingDelegatingContext targetContextWrapper = new + RenamingDelegatingContext( + new MockContext2(), // The context that most methods are + //delegated to + getContext(), // The context that file methods are delegated to + FILENAME_PREFIX); + // The default IsolatedContext has a mock AccountManager that doesn't + // work for us, so override getSystemService to always return null + mProviderContext = new IsolatedContext(mResolver, targetContextWrapper) { + + @Override + public Object getSystemService(String name) { + return null; + } + }; + + mProvider = mProviderClass.newInstance(); + mProvider.attachInfo(mProviderContext, null); + assertNotNull(mProvider); + for (String auth : mProviderAuthority) { + mResolver.addProvider(auth, getProvider()); + } + } + + /** + * Tears down the environment for the test fixture. + * <p> + * Calls {@link android.content.ContentProvider#shutdown()} on the + * {@link android.content.ContentProvider} represented by mProvider. + */ + @Override + protected void tearDown() throws Exception { + mProvider.shutdown(); + super.tearDown(); + } + + /** + * Gets the {@link MockContentResolver2} created by this class during initialization. You + * must use the methods of this resolver to access the provider under test. + * + * @return A {@link MockContentResolver2} instance. + */ + public MockContentResolver2 getMockContentResolver() { + return mResolver; + } + + /** + * Gets the {@link IsolatedContext} created by this class during initialization. + * @return The {@link IsolatedContext} instance + */ + public IsolatedContext getMockContext() { + return mProviderContext; + } + + public void registerContentObserver(Uri uri, boolean notifyForDescendents, + ContentObserver observer) { + mResolver.safeRegisterContentObserver(uri, notifyForDescendents, observer); + } + + public void unregisterContentObserver(ContentObserver observer) { + mResolver.safeUnregisterContentObserver(observer); + } + +} |