From 6d553f6dfdcc188fa6b17d4abb11d6222009a8fb Mon Sep 17 00:00:00 2001 From: Lorenzo Colitti Date: Sun, 5 Jun 2016 02:20:29 +0900 Subject: [PATCH] Add a FakeSettingsProvider and use it in ConnectivityServiceTest. This class makes it easier to test code that uses Settings: 1. Real device or emulator settings don't affect the code under test; all settings always start off empty. 2. It's possible to change settings from the test without affecting system settings. 3. No changes are needed to the code under test. The changes to the tests are simple: just add a fake ContentResolver to whatever mock Context is already used by the test, and make that ContentResolver use the fake provider. Bug: 23113288 Change-Id: I5e7e5a87571444ae49ccf551705620675a36cd17 --- .../internal/util/FakeSettingsProvider.java | 130 ++++++++++++++++++ .../util/FakeSettingsProviderTest.java | 58 ++++++++ .../server/ConnectivityServiceTest.java | 15 +- 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java create mode 100644 services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java new file mode 100644 index 0000000000000..808f4dd7b84ef --- /dev/null +++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProvider.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 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.internal.util; + +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.test.mock.MockContentProvider; +import android.util.Log; + +import java.util.HashMap; + +/** + * Fake for system settings. + * + * To use, ensure that the Context used by the test code returns a ContentResolver that uses this + * provider for the Settings authority: + * + * class MyTestContext extends MockContext { + * ... + * private final MockContentResolver mContentResolver; + * public MyTestContext(...) { + * ... + * mContentResolver = new MockContentResolver(); + * mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + * } + * ... + * @Override + * public ContentResolver getContentResolver() { + * return mContentResolver; + * } + * + * As long as the code under test is using the test Context, the actual code under test does not + * need to be modified, and can access Settings using the normal static methods: + * + * Settings.Global.getInt(cr, "my_setting", 0); // Returns 0. + * Settings.Global.putInt(cr, "my_setting", 5); + * Settings.Global.getInt(cr, "my_setting", 0); // Returns 5. + * + * Note that this class cannot be used in the same process as real settings. This is because it + * works by passing an alternate ContentResolver to Settings operations. Unfortunately, the Settings + * class only fetches the content provider from the passed-in ContentResolver the first time it's + * used, and after that stores it in a per-process static. + * + * TODO: evaluate implementing settings change notifications. This would require: + * + * 1. Making ContentResolver#registerContentObserver non-final and overriding it in + * MockContentResolver. + * 2. Making FakeSettingsProvider take a ContentResolver argument. + * 3. Calling ContentResolver#notifyChange(getUriFor(table, arg), ...) on every settings change. + */ +public class FakeSettingsProvider extends MockContentProvider { + + private static final String TAG = FakeSettingsProvider.class.getSimpleName(); + private static final boolean DBG = false; + private static final String[] TABLES = { "system", "secure", "global" }; + + private final HashMap> mTables = new HashMap<>(); + + public FakeSettingsProvider() { + for (int i = 0; i < TABLES.length; i++) { + mTables.put(TABLES[i], new HashMap()); + } + } + + private Uri getUriFor(String table, String key) { + switch (table) { + case "system": + return Settings.System.getUriFor(key); + case "secure": + return Settings.Secure.getUriFor(key); + case "global": + return Settings.Global.getUriFor(key); + default: + throw new UnsupportedOperationException("Unknown settings table " + table); + } + } + + public Bundle call(String method, String arg, Bundle extras) { + // Methods are "GET_system", "GET_global", "PUT_secure", etc. + String[] commands = method.split("_", 2); + String op = commands[0]; + String table = commands[1]; + + Bundle out = new Bundle(); + String value; + switch (op) { + case "GET": + value = mTables.get(table).get(arg); + if (value != null) { + if (DBG) { + Log.d(TAG, String.format("Returning fake setting %s.%s = %s", + table, arg, value)); + } + out.putString(Settings.NameValueTable.VALUE, value); + } + break; + case "PUT": + value = extras.getString(Settings.NameValueTable.VALUE, null); + if (DBG) { + Log.d(TAG, String.format("Inserting fake setting %s.%s = %s", + table, arg, value)); + } + if (value != null) { + mTables.get(table).put(arg, value); + } else { + mTables.get(table).remove(arg); + } + break; + default: + throw new UnsupportedOperationException("Unknown command " + method); + } + + return out; + } +} diff --git a/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java new file mode 100644 index 0000000000000..a18737c42e529 --- /dev/null +++ b/services/tests/servicestests/src/com/android/internal/util/FakeSettingsProviderTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 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.internal.util; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.provider.Settings; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Unit tests for FakeSettingsProvider. + */ +public class FakeSettingsProviderTest extends AndroidTestCase { + + private MockContentResolver mCr; + + @Override + public void setUp() throws Exception { + mCr = new MockContentResolver(); + mCr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + } + + @SmallTest + public void testBasicOperation() throws Exception { + String settingName = Settings.System.SCREEN_BRIGHTNESS; + + try { + Settings.System.getInt(mCr, settingName); + fail("FakeSettingsProvider should start off empty."); + } catch (Settings.SettingNotFoundException expected) {} + + // Check that fake settings can be written and read back. + Settings.System.putInt(mCr, settingName, 123); + assertEquals(123, Settings.System.getInt(mCr, settingName)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index 4fae4a732aad3..00ba06988d869 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import android.app.PendingIntent; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; @@ -61,12 +62,15 @@ import android.os.Messenger; import android.os.MessageQueue.IdleHandler; import android.os.Process; import android.os.SystemClock; +import android.provider.Settings; import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import android.util.LogPrinter; +import com.android.internal.util.FakeSettingsProvider; import com.android.internal.util.WakeupMessage; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; @@ -118,8 +122,12 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private class MockContext extends BroadcastInterceptingContext { + private final MockContentResolver mContentResolver; + MockContext(Context base) { super(base); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); } @Override @@ -135,10 +143,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @Override - public Object getSystemService (String name) { + public Object getSystemService(String name) { if (name == Context.CONNECTIVITY_SERVICE) return mCm; return super.getSystemService(name); } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } } /**