summaryrefslogtreecommitdiff
path: root/tests/testables/src
diff options
context:
space:
mode:
authorJason Monk <jmonk@google.com>2017-04-27 13:46:48 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2017-04-27 13:46:54 +0000
commit2cddbcaa0f271d4a89cb61b017fc7fcde96e4378 (patch)
tree7ba038f6deae97ec0ef9ae67449a63c0cb452d0c /tests/testables/src
parent2e0e7f3c94f39e5c6629152d2abfd618a5a7407b (diff)
parentf06a317039a6502252c2b4b1a878520d166a38c6 (diff)
Merge "Guard against incorrect context use." into oc-dev
Diffstat (limited to 'tests/testables/src')
-rw-r--r--tests/testables/src/android/testing/TestableContext.java15
-rw-r--r--tests/testables/src/android/testing/TestableSettings.java318
-rw-r--r--tests/testables/src/android/testing/TestableSettingsProvider.java120
3 files changed, 127 insertions, 326 deletions
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index cb5d4cb8242f..630a287c6f4a 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -43,7 +43,7 @@ import org.junit.runners.model.Statement;
* <ul>
* <li>System services can be mocked out with {@link #addMockSystemService}</li>
* <li>Service binding can be mocked out with {@link #addMockService}</li>
- * <li>Settings support {@link TestableSettings}</li>
+ * <li>Settings support {@link TestableSettingsProvider}</li>
* <li>Has support for {@link LeakCheck} for services and receivers</li>
* </ul>
*
@@ -59,7 +59,7 @@ import org.junit.runners.model.Statement;
public class TestableContext extends ContextWrapper implements TestRule {
private final TestableContentResolver mTestableContentResolver;
- private final TestableSettings mSettingsProvider;
+ private final TestableSettingsProvider mSettingsProvider;
private ArrayMap<String, Object> mMockSystemServices;
private ArrayMap<ComponentName, IBinder> mMockServices;
@@ -79,9 +79,8 @@ public class TestableContext extends ContextWrapper implements TestRule {
mTestableContentResolver = new TestableContentResolver(base);
ContentProviderClient settings = base.getContentResolver()
.acquireContentProviderClient(Settings.AUTHORITY);
- mSettingsProvider = TestableSettings.getFakeSettingsProvider(settings,
- mTestableContentResolver);
- mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider.getProvider());
+ mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
+ mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
mReceiver = check != null ? check.getTracker("receiver") : null;
mService = check != null ? check.getTracker("service") : null;
mComponent = check != null ? check.getTracker("component") : null;
@@ -129,7 +128,7 @@ public class TestableContext extends ContextWrapper implements TestRule {
return super.getSystemService(name);
}
- public TestableSettings getSettingsProvider() {
+ TestableSettingsProvider getSettingsProvider() {
return mSettingsProvider;
}
@@ -236,12 +235,12 @@ public class TestableContext extends ContextWrapper implements TestRule {
return new TestWatcher() {
@Override
protected void succeeded(Description description) {
- mSettingsProvider.clearOverrides();
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
}
@Override
protected void failed(Throwable e, Description description) {
- mSettingsProvider.clearOverrides();
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
}
}.apply(base, description);
}
diff --git a/tests/testables/src/android/testing/TestableSettings.java b/tests/testables/src/android/testing/TestableSettings.java
deleted file mode 100644
index d19f1ef60b2e..000000000000
--- a/tests/testables/src/android/testing/TestableSettings.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.testing;
-
-import android.content.ContentProvider;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
-import android.test.mock.MockContentProvider;
-import android.testing.TestableSettings.SettingOverrider.Builder;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Allows calls to android.provider.Settings to be tested easier. A SettingOverride
- * can be acquired and a set of specific settings can be set to a value (and not changed
- * in the system when set), so that they can be tested without breaking the test device.
- * <p>
- * To use, in the before method acquire the override add all settings that will affect if
- * your test passes or not.
- *
- * <pre class="prettyprint">
- * {@literal
- * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder()
- * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0")
- * .build();
- * }
- * </pre>
- *
- * Then in the after free up the settings.
- *
- * <pre class="prettyprint">
- * {@literal
- * mSettingOverride.release();
- * }
- * </pre>
- */
-public class TestableSettings {
-
- private static final String TAG = "TestableSettings";
- private static final boolean DEBUG = false;
-
- // Number of times to try to acquire a setting if in use.
- private static final int MAX_TRIES = 10;
- // Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time
- // for a setting.
- private static final long WAIT_TIMEOUT = 1000;
-
- private static TestableSettingsProvider sInstance;
-
- private final TestableSettingsProvider mProvider;
-
- private TestableSettings(TestableSettingsProvider provider) {
- mProvider = provider;
- }
-
- public Builder acquireOverridesBuilder() {
- return new Builder(this);
- }
-
- public void clearOverrides() {
- List<SettingOverrider> overrides = mProvider.mOwners.remove(this);
- if (overrides != null) {
- overrides.forEach(override -> override.ensureReleased());
- }
- }
-
- private void acquireSettings(SettingOverrider overridder, Set<String> keys)
- throws AcquireTimeoutException {
- mProvider.acquireSettings(overridder, keys, this);
- }
-
- ContentProvider getProvider() {
- return mProvider;
- }
-
- @VisibleForTesting
- Object getLock() {
- return mProvider.mOverrideMap;
- }
-
- public static class SettingOverrider {
- private final Set<String> mValidKeys;
- private final Map<String, String> mValueMap = new ArrayMap<>();
- private final TestableSettings mSettings;
- private boolean mReleased;
- public Throwable mObtain;
-
- private SettingOverrider(Set<String> keys, TestableSettings provider) {
- mValidKeys = new ArraySet<>(keys);
- mSettings = provider;
- }
-
- private void ensureReleased() {
- if (!mReleased) {
- release();
- }
- }
-
- public void release() {
- mSettings.mProvider.releaseSettings(mValidKeys);
- mReleased = true;
- }
-
- private void putDirect(String key, String value) {
- mValueMap.put(key, value);
- }
-
- public void put(String table, String key, String value) {
- if (!mValidKeys.contains(key(table, key))) {
- throw new IllegalArgumentException("Key " + table + " " + key
- + " not acquired for this overrider");
- }
- mValueMap.put(key(table, key), value);
- }
-
- public void remove(String table, String key) {
- if (!mValidKeys.contains(key(table, key))) {
- throw new IllegalArgumentException("Key " + table + " " + key
- + " not acquired for this overrider");
- }
- mValueMap.remove(key(table, key));
- }
-
- public String get(String table, String key) {
- if (!mValidKeys.contains(key(table, key))) {
- throw new IllegalArgumentException("Key " + table + " " + key
- + " not acquired for this overrider");
- }
- Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key)));
- return mValueMap.get(key(table, key));
- }
-
- public static class Builder {
- private final TestableSettings mProvider;
- private Set<String> mKeys = new ArraySet<>();
- private Map<String, String> mValues = new ArrayMap<>();
-
- private Builder(TestableSettings provider) {
- mProvider = provider;
- }
-
- public Builder addSetting(String table, String key) {
- mKeys.add(key(table, key));
- return this;
- }
-
- public Builder addSetting(String table, String key, String value) {
- addSetting(table, key);
- mValues.put(key(table, key), value);
- return this;
- }
-
- public SettingOverrider build() throws AcquireTimeoutException {
- SettingOverrider overrider = new SettingOverrider(mKeys, mProvider);
- mProvider.acquireSettings(overrider, mKeys);
- mValues.forEach((key, value) -> overrider.putDirect(key, value));
- return overrider;
- }
- }
- }
-
- private static class TestableSettingsProvider extends MockContentProvider {
-
- private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>();
- private final Map<Object, List<SettingOverrider>> mOwners = new ArrayMap<>();
-
- private final ContentProviderClient mSettings;
- private final ContentResolver mResolver;
-
- public TestableSettingsProvider(ContentProviderClient settings, ContentResolver resolver) {
- mSettings = settings;
- mResolver = resolver;
- }
-
- private void releaseSettings(Set<String> keys) {
- synchronized (mOverrideMap) {
- for (String key : keys) {
- if (DEBUG) Log.d(TAG, "Releasing " + key);
- mOverrideMap.remove(key);
- }
- if (DEBUG) Log.d(TAG, "Notifying");
- mOverrideMap.notify();
- }
- }
-
- private boolean checkKeysLocked(Set<String> keys, boolean shouldThrow)
- throws AcquireTimeoutException {
- for (String key : keys) {
- if (mOverrideMap.containsKey(key)) {
- if (shouldThrow) {
- if (DEBUG) Log.e(TAG, "Lock obtained at",
- mOverrideMap.get(key).mObtain);
- throw new AcquireTimeoutException("Could not acquire " + key,
- mOverrideMap.get(key).mObtain);
- }
- return false;
- }
- }
- return true;
- }
-
- private void acquireSettings(SettingOverrider overridder, Set<String> keys,
- Object owner) throws AcquireTimeoutException {
- synchronized (mOwners) {
- List<SettingOverrider> list = mOwners.get(owner);
- if (list == null) {
- list = new ArrayList<>();
- mOwners.put(owner, list);
- }
- list.add(overridder);
- }
- synchronized (mOverrideMap) {
- for (int i = 0; i < MAX_TRIES; i++) {
- if (checkKeysLocked(keys, false)) break;
- try {
- if (DEBUG) Log.d(TAG, "Waiting for contention to finish");
- mOverrideMap.wait(WAIT_TIMEOUT);
- } catch (InterruptedException e) {
- }
- }
- overridder.mObtain = new Throwable();
- checkKeysLocked(keys, true);
- for (String key : keys) {
- if (DEBUG) Log.d(TAG, "Acquiring " + key);
- mOverrideMap.put(key, overridder);
- }
- }
- }
-
- public Bundle call(String method, String arg, Bundle extras) {
- // Methods are "GET_system", "GET_global", "PUT_secure", etc.
- final String[] commands = method.split("_", 2);
- final String op = commands[0];
- final String table = commands[1];
-
- synchronized (mOverrideMap) {
- SettingOverrider overrider = mOverrideMap.get(key(table, arg));
- if (overrider == null) {
- // Fall through to real settings.
- try {
- if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
- // TODO: Add our own version of caching to handle this.
- Bundle call = mSettings.call(method, arg, extras);
- call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
- return call;
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- String value;
- Bundle out = new Bundle();
- switch (op) {
- case "GET":
- value = overrider.get(table, arg);
- if (value != null) {
- out.putString(Settings.NameValueTable.VALUE, value);
- }
- break;
- case "PUT":
- value = extras.getString(Settings.NameValueTable.VALUE, null);
- if (value != null) {
- overrider.put(table, arg, value);
- } else {
- overrider.remove(table, arg);
- }
- break;
- default:
- throw new UnsupportedOperationException("Unknown command " + method);
- }
- return out;
- }
- }
- }
-
- public static class AcquireTimeoutException extends Exception {
- public AcquireTimeoutException(String str, Throwable cause) {
- super(str, cause);
- }
- }
-
- private static String key(String table, String key) {
- return table + "_" + key;
- }
-
- /**
- * Since the settings provider is cached inside android.provider.Settings, this must
- * be gotten statically to ensure there is only one instance referenced.
- */
- public static TestableSettings getFakeSettingsProvider(ContentProviderClient settings,
- ContentResolver resolver) {
- if (sInstance == null) {
- sInstance = new TestableSettingsProvider(settings, resolver);
- }
- return new TestableSettings(sInstance);
- }
-}
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
new file mode 100644
index 000000000000..13056cf677d6
--- /dev/null
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 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 android.testing;
+
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.test.mock.MockContentProvider;
+import android.util.Log;
+
+import java.util.HashMap;
+
+import static org.junit.Assert.*;
+
+/**
+ * Allows calls to android.provider.Settings to be tested easier.
+ *
+ * This provides a simple copy-on-write implementation of settings that gets cleared
+ * at the end of each test.
+ */
+public class TestableSettingsProvider extends MockContentProvider {
+
+ private static final String TAG = "TestableSettingsProvider";
+ private static final boolean DEBUG = false;
+ private static final String MY_UNIQUE_KEY = "Key_" + TestableSettingsProvider.class.getName();
+ private static TestableSettingsProvider sInstance;
+
+ private final ContentProviderClient mSettings;
+
+ private final HashMap<String, String> mValues = new HashMap<>();
+
+ private TestableSettingsProvider(ContentProviderClient settings) {
+ mSettings = settings;
+ }
+
+ void clearValuesAndCheck(Context context) {
+ mValues.put(key("global", MY_UNIQUE_KEY), MY_UNIQUE_KEY);
+ mValues.put(key("secure", MY_UNIQUE_KEY), MY_UNIQUE_KEY);
+ mValues.put(key("system", MY_UNIQUE_KEY), MY_UNIQUE_KEY);
+
+ // Verify that if any test is using TestableContext, they all have the correct settings
+ // provider.
+ assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
+ Settings.Global.getString(context.getContentResolver(), MY_UNIQUE_KEY));
+ assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
+ Settings.Secure.getString(context.getContentResolver(), MY_UNIQUE_KEY));
+ assertEquals("Incorrect settings provider, test using incorrect Context?", MY_UNIQUE_KEY,
+ Settings.System.getString(context.getContentResolver(), MY_UNIQUE_KEY));
+
+ mValues.clear();
+ }
+
+ public Bundle call(String method, String arg, Bundle extras) {
+ // Methods are "GET_system", "GET_global", "PUT_secure", etc.
+ final String[] commands = method.split("_", 2);
+ final String op = commands[0];
+ final String table = commands[1];
+
+ String k = key(table, arg);
+ String value;
+ Bundle out = new Bundle();
+ switch (op) {
+ case "GET":
+ if (mValues.containsKey(k)) {
+ value = mValues.get(k);
+ if (value != null) {
+ out.putString(Settings.NameValueTable.VALUE, value);
+ }
+ } else {
+ // Fall through to real settings.
+ try {
+ if (DEBUG) Log.d(TAG, "Falling through to real settings " + method);
+ // TODO: Add our own version of caching to handle this.
+ Bundle call = mSettings.call(method, arg, extras);
+ call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY);
+ return call;
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ break;
+ case "PUT":
+ value = extras.getString(Settings.NameValueTable.VALUE, null);
+ mValues.put(k, value);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown command " + method);
+ }
+ return out;
+ }
+
+ private static String key(String table, String key) {
+ return table + "_" + key;
+ }
+
+ /**
+ * Since the settings provider is cached inside android.provider.Settings, this must
+ * be gotten statically to ensure there is only one instance referenced.
+ */
+ static TestableSettingsProvider getFakeSettingsProvider(ContentProviderClient settings) {
+ if (sInstance == null) {
+ sInstance = new TestableSettingsProvider(settings);
+ }
+ return sInstance;
+ }
+}