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

Commit 388dc11c authored by Linus Tufvesson's avatar Linus Tufvesson Committed by Android (Google) Code Review
Browse files

Merge "Add TestableDeviceConfig"

parents f6b8a284 9d501759
Loading
Loading
Loading
Loading
+25 −60
Original line number Diff line number Diff line
@@ -18,15 +18,11 @@ package android.provider;

import static android.provider.DeviceConfig.OnPropertyChangedListener;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.fail;
import static com.google.common.truth.Truth.assertThat;

import android.app.ActivityThread;
import android.content.ContentResolver;
import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;

import androidx.test.InstrumentationRegistry;
@@ -37,8 +33,8 @@ import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/** Tests that ensure appropriate settings are backed up. */
@Presubmit
@@ -51,8 +47,6 @@ public class DeviceConfigTest {
    private static final String sValue = "value1";
    private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec

    private final Object mLock = new Object();

    @After
    public void cleanUp() {
        deleteViaContentProvider(sNamespace, sKey);
@@ -61,14 +55,14 @@ public class DeviceConfigTest {
    @Test
    public void getProperty_empty() {
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertNull(result);
        assertThat(result).isNull();
    }

    @Test
    public void setAndGetProperty_sameNamespace() {
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertEquals(sValue, result);
        assertThat(result).isEqualTo(sValue);
    }

    @Test
@@ -76,7 +70,7 @@ public class DeviceConfigTest {
        String newNamespace = "namespace2";
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        String result = DeviceConfig.getProperty(newNamespace, sKey);
        assertNull(result);
        assertThat(result).isNull();
    }

    @Test
@@ -86,9 +80,9 @@ public class DeviceConfigTest {
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertEquals(sValue, result);
        assertThat(result).isEqualTo(sValue);
        result = DeviceConfig.getProperty(newNamespace, sKey);
        assertEquals(newValue, result);
        assertThat(result).isEqualTo(newValue);

        // clean up
        deleteViaContentProvider(newNamespace, sKey);
@@ -100,59 +94,30 @@ public class DeviceConfigTest {
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertEquals(newValue, result);
        assertThat(result).isEqualTo(newValue);
    }

    @Test
    public void testListener() {
        setPropertyAndAssertSuccessfulChange(sNamespace, sKey, sValue);
    }

    private void setPropertyAndAssertSuccessfulChange(String setNamespace, String setName,
            String setValue) {
        final AtomicBoolean success = new AtomicBoolean();

        OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
                    @Override
                    public void onPropertyChanged(String namespace, String name, String value) {
                        assertEquals(setNamespace, namespace);
                        assertEquals(setName, name);
                        assertEquals(setValue, value);
                        success.set(true);

                        synchronized (mLock) {
                            mLock.notifyAll();
                        }
                    }
    public void testListener() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);

        OnPropertyChangedListener changeListener = (namespace, name, value) -> {
            assertThat(namespace).isEqualTo(sNamespace);
            assertThat(name).isEqualTo(sKey);
            assertThat(value).isEqualTo(sValue);
            countDownLatch.countDown();
        };
        Executor executor = ActivityThread.currentApplication().getMainExecutor();
        DeviceConfig.addOnPropertyChangedListener(setNamespace, executor, changeListener);
        try {
            DeviceConfig.setProperty(setNamespace, setName, setValue, false);

            final long startTimeMillis = SystemClock.uptimeMillis();
            synchronized (mLock) {
                while (true) {
                    if (success.get()) {
                        return;
                    }
                    final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
                    if (elapsedTimeMillis >= WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS) {
                        fail("Could not change setting for "
                                + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS + " ms");
                    }
                    final long remainingTimeMillis = WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS
                            - elapsedTimeMillis;
        try {
                        mLock.wait(remainingTimeMillis);
                    } catch (InterruptedException ie) {
                        /* ignore */
                    }
                }
            }
            DeviceConfig.addOnPropertyChangedListener(sNamespace,
                    ActivityThread.currentApplication().getMainExecutor(), changeListener);
            DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
            assertThat(countDownLatch.await(
                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
        } finally {
            DeviceConfig.removeOnPropertyChangedListener(changeListener);
        }

    }

    private static boolean deleteViaContentProvider(String namespace, String key) {
@@ -160,7 +125,7 @@ public class DeviceConfigTest {
        String compositeName = namespace + "/" + key;
        Bundle result = resolver.call(
                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
        assertNotNull(result);
        assertThat(result).isNotNull();
        return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
    }

+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ android_test {
        "androidx.test.runner",
        "mockito-target-extended-minus-junit4",
        "platform-test-annotations",
        "truth-prebuilt",
    ],

    libs: [
+130 −158

File changed and moved.

Preview size limit exceeded, changes collapsed.

+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.server.testables;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;

import android.provider.DeviceConfig;
import android.util.Pair;

import com.android.dx.mockito.inline.extended.StaticMockitoSession;

import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

/**
 * TestableDeviceConfig uses ExtendedMockito to replace the real implementation of DeviceConfig
 * with essentially a local HashMap in the callers process. This allows for unit testing that do not
 * modify the real DeviceConfig on the device at all.
 *
 * <p>TestableDeviceConfig should be defined as a rule on your test so it can clean up after itself.
 * Like the following:</p>
 * <pre class="prettyprint">
 * &#064;Rule
 * public final TestableDeviceConfig mTestableDeviceConfig = new TestableDeviceConfig();
 * </pre>
 */
public final class TestableDeviceConfig implements TestRule {

    private StaticMockitoSession mMockitoSession;
    private Map<DeviceConfig.OnPropertyChangedListener, Pair<String, Executor>>
            mOnPropertyChangedListenerMap = new HashMap<>();
    private Map<String, String> mKeyValueMap = new ConcurrentHashMap<>();

    /**
     * Clears out all local overrides.
     */
    public void clearDeviceConfig() {
        mKeyValueMap.clear();
    }

    @Override
    public Statement apply(Statement base, Description description) {
        mMockitoSession = mockitoSession()
                .initMocks(this)
                .strictness(Strictness.LENIENT)
                .spyStatic(DeviceConfig.class)
                .startMocking();

        doAnswer((Answer<Void>) invocationOnMock -> {
            String namespace = invocationOnMock.getArgument(0);
            Executor executor = invocationOnMock.getArgument(1);
            DeviceConfig.OnPropertyChangedListener onPropertyChangedListener =
                    invocationOnMock.getArgument(2);
            mOnPropertyChangedListenerMap.put(
                    onPropertyChangedListener, new Pair<>(namespace, executor));
            return null;
        }).when(() -> DeviceConfig.addOnPropertyChangedListener(
                anyString(), any(Executor.class),
                any(DeviceConfig.OnPropertyChangedListener.class)));

        doAnswer((Answer<Boolean>) invocationOnMock -> {
                    String namespace = invocationOnMock.getArgument(0);
                    String name = invocationOnMock.getArgument(1);
                    String value = invocationOnMock.getArgument(2);
                    mKeyValueMap.put(getKey(namespace, name), value);
                    for (DeviceConfig.OnPropertyChangedListener listener :
                            mOnPropertyChangedListenerMap.keySet()) {
                        if (namespace.equals(mOnPropertyChangedListenerMap.get(listener).first)) {
                            mOnPropertyChangedListenerMap.get(listener).second.execute(
                                    () -> listener.onPropertyChanged(namespace, name, value));
                        }
                    }
                    return true;
                }
        ).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean()));

        doAnswer((Answer<String>) invocationOnMock -> {
            String namespace = invocationOnMock.getArgument(0);
            String name = invocationOnMock.getArgument(1);
            return mKeyValueMap.get(getKey(namespace, name));
        }).when(() -> DeviceConfig.getProperty(anyString(), anyString()));


        return new TestWatcher() {
            @Override
            protected void succeeded(Description description) {
                mMockitoSession.finishMocking();
                mOnPropertyChangedListenerMap.clear();
            }

            @Override
            protected void failed(Throwable e, Description description) {
                mMockitoSession.finishMocking(e);
                mOnPropertyChangedListenerMap.clear();
            }
        }.apply(base, description);
    }

    private static String getKey(String namespace, String name) {
        return namespace + "/" + name;
    }

}
+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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.server.testables;

import static android.provider.DeviceConfig.OnPropertyChangedListener;

import static com.google.common.truth.Truth.assertThat;

import android.app.ActivityThread;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/** Tests that ensure appropriate settings are backed up. */
@Presubmit
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestableDeviceConfigTest {
    private static final String sNamespace = "namespace1";
    private static final String sKey = "key1";
    private static final String sValue = "value1";
    private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec

    @Rule
    public TestableDeviceConfig mTestableDeviceConfig = new TestableDeviceConfig();

    @Test
    public void getProperty_empty() {
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertThat(result).isNull();
    }

    @Test
    public void setAndGetProperty_sameNamespace() {
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertThat(result).isEqualTo(sValue);
    }

    @Test
    public void setAndGetProperty_differentNamespace() {
        String newNamespace = "namespace2";
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        String result = DeviceConfig.getProperty(newNamespace, sKey);
        assertThat(result).isNull();
    }

    @Test
    public void setAndGetProperty_multipleNamespaces() {
        String newNamespace = "namespace2";
        String newValue = "value2";
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertThat(result).isEqualTo(sValue);
        result = DeviceConfig.getProperty(newNamespace, sKey);
        assertThat(result).isEqualTo(newValue);
    }

    @Test
    public void setAndGetProperty_overrideValue() {
        String newValue = "value2";
        DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
        DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
        String result = DeviceConfig.getProperty(sNamespace, sKey);
        assertThat(result).isEqualTo(newValue);
    }

    @Test
    public void testListener() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);

        OnPropertyChangedListener changeListener = (namespace, name, value) -> {
            assertThat(namespace).isEqualTo(sNamespace);
            assertThat(name).isEqualTo(sKey);
            assertThat(value).isEqualTo(sValue);
            countDownLatch.countDown();
        };
        try {
            DeviceConfig.addOnPropertyChangedListener(sNamespace,
                    ActivityThread.currentApplication().getMainExecutor(), changeListener);
            DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
            assertThat(countDownLatch.await(
                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
        } finally {
            DeviceConfig.removeOnPropertyChangedListener(changeListener);
        }
    }

}