Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d553f6d authored by Lorenzo Colitti's avatar Lorenzo Colitti
Browse files

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
parent d69a99e3
Loading
Loading
Loading
Loading
+130 −0
Original line number Original line Diff line number Diff line
/*
 * 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<String, HashMap<String, String>> mTables = new HashMap<>();

    public FakeSettingsProvider() {
        for (int i = 0; i < TABLES.length; i++) {
            mTables.put(TABLES[i], new HashMap<String, String>());
        }
    }

    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;
    }
}
+58 −0
Original line number Original line Diff line number Diff line
/*
 * 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));
    }
}
+14 −1
Original line number Original line Diff line number Diff line
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock;


import android.app.PendingIntent;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.Intent;
@@ -61,12 +62,15 @@ import android.os.Messenger;
import android.os.MessageQueue.IdleHandler;
import android.os.MessageQueue.IdleHandler;
import android.os.Process;
import android.os.Process;
import android.os.SystemClock;
import android.os.SystemClock;
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.util.Log;
import android.util.LogPrinter;
import android.util.LogPrinter;


import com.android.internal.util.FakeSettingsProvider;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.WakeupMessage;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
import com.android.server.connectivity.NetworkMonitor;
@@ -118,8 +122,12 @@ public class ConnectivityServiceTest extends AndroidTestCase {
    }
    }


    private class MockContext extends BroadcastInterceptingContext {
    private class MockContext extends BroadcastInterceptingContext {
        private final MockContentResolver mContentResolver;

        MockContext(Context base) {
        MockContext(Context base) {
            super(base);
            super(base);
            mContentResolver = new MockContentResolver();
            mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
        }
        }


        @Override
        @Override
@@ -139,6 +147,11 @@ public class ConnectivityServiceTest extends AndroidTestCase {
            if (name == Context.CONNECTIVITY_SERVICE) return mCm;
            if (name == Context.CONNECTIVITY_SERVICE) return mCm;
            return super.getSystemService(name);
            return super.getSystemService(name);
        }
        }

        @Override
        public ContentResolver getContentResolver() {
            return mContentResolver;
        }
    }
    }


    /**
    /**