aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-12-09 13:06:04 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2021-12-09 13:06:04 +0000
commit066f519bf8df1e6faa75206aa4a9f355a16241f0 (patch)
tree4c9540b8e3f0470aab8dfc82caa6da0143fd3361
parent42a25926e57265fb8321c1b1c732a20ee4387069 (diff)
parente9461ba5694bde2aeaf4f215c32ac62f48096874 (diff)
downloadmodules-utils-066f519bf8df1e6faa75206aa4a9f355a16241f0.tar.gz
Snap for 7984612 from e9461ba5694bde2aeaf4f215c32ac62f48096874 to mainline-resolv-releaseandroid-mainline-12.0.0_r80android-mainline-12.0.0_r65
Change-Id: I49f39fcec1ca0b83e464f19c2519d01832429dab
-rw-r--r--java/com/android/modules/utils/testing/Android.bp32
-rw-r--r--java/com/android/modules/utils/testing/StaticMockFixture.java56
-rw-r--r--java/com/android/modules/utils/testing/StaticMockFixtureRule.java141
-rw-r--r--java/com/android/modules/utils/testing/TestableDeviceConfig.java258
-rw-r--r--javatests/com/android/modules/utils/testing/Android.bp49
-rw-r--r--javatests/com/android/modules/utils/testing/AndroidManifest.xml27
-rw-r--r--javatests/com/android/modules/utils/testing/MultipleStaticMocksTest.java163
-rw-r--r--javatests/com/android/modules/utils/testing/StaticMockFixtureRuleTest.java258
-rw-r--r--javatests/com/android/modules/utils/testing/TestableDeviceConfigAndOtherStaticMocksTest.java172
-rw-r--r--javatests/com/android/modules/utils/testing/TestableDeviceConfigTest.java158
10 files changed, 1314 insertions, 0 deletions
diff --git a/java/com/android/modules/utils/testing/Android.bp b/java/com/android/modules/utils/testing/Android.bp
index 28bd82c..d39bf3c 100644
--- a/java/com/android/modules/utils/testing/Android.bp
+++ b/java/com/android/modules/utils/testing/Android.bp
@@ -27,3 +27,35 @@ java_library {
min_sdk_version: "29",
visibility: ["//visibility:public"],
}
+
+// Depend on modules-utils-testable-device-config-defaults instead of this library.
+java_library {
+ name: "modules-utils-testable-device-config",
+ srcs: [
+ "StaticMockFixture.java",
+ "StaticMockFixtureRule.java",
+ "TestableDeviceConfig.java",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "mockito-target-extended-minus-junit4",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "29",
+ visibility: ["//visibility:public"],
+}
+
+// Utility for mocking DeviceConfig and other device state.
+// In order to use these utils successfully, both Java code and native libraries will be required.
+// If your code is in a library, you will have to depend on these defaults both in the library as
+// well as in the android_test that it gets linked into.
+// You will also need to specify android:debuggable="true" in the test's manifest.
+java_defaults {
+ name: "modules-utils-testable-device-config-defaults",
+ static_libs: ["modules-utils-testable-device-config"],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+ defaults_visibility: ["//visibility:public"],
+}
diff --git a/java/com/android/modules/utils/testing/StaticMockFixture.java b/java/com/android/modules/utils/testing/StaticMockFixture.java
new file mode 100644
index 0000000..d800b4f
--- /dev/null
+++ b/java/com/android/modules/utils/testing/StaticMockFixture.java
@@ -0,0 +1,56 @@
+/*
+ * 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.modules.utils.testing;
+
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+/**
+ * Provides support for a set of static mocks for use within a single shared
+ * {@link StaticMockitoSession}.
+ */
+public interface StaticMockFixture {
+ /**
+ * Adds any required mock or spy classes managed by this {@link StaticMockFixture} to the
+ * {@link StaticMockitoSessionBuilder} provided.
+ *
+ * Call this to set up the classes that this expects to be mocked, by adding them to the
+ * {@link StaticMockitoSessionBuilder} using
+ * {@link StaticMockitoSessionBuilder#mockStatic(Class)},
+ * {@link StaticMockitoSessionBuilder#spyStatic(Class)} or similar as appropriate.
+ *
+ * @param sessionBuilder the {@link StaticMockitoSessionBuilder} to which the classes should be
+ * added to mock, spy, or otherwise as required
+ * @return sessionBuilder, to allow for fluent programming
+ */
+ StaticMockitoSessionBuilder setUpMockedClasses(StaticMockitoSessionBuilder sessionBuilder);
+
+ /**
+ * Configures the behaviours of any mock or spy classes managed by this
+ * {@link StaticMockFixture}.
+ *
+ * Call this after {@link StaticMockitoSessionBuilder#startMocking()} has been called.
+ * This sets up any default behaviors for the mocks, spys, etc.
+ */
+ void setUpMockBehaviors();
+
+ /**
+ * Tear everything down.
+ */
+ void tearDown();
+}
diff --git a/java/com/android/modules/utils/testing/StaticMockFixtureRule.java b/java/com/android/modules/utils/testing/StaticMockFixtureRule.java
new file mode 100644
index 0000000..0cdc9a2
--- /dev/null
+++ b/java/com/android/modules/utils/testing/StaticMockFixtureRule.java
@@ -0,0 +1,141 @@
+/*
+ * 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.modules.utils.testing;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.AssumptionViolatedException;
+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 java.util.function.Supplier;
+
+/**
+ * <p>StaticMockFixtureRule is a {@link TestRule} that wraps one or more {@link StaticMockFixture}s
+ * to set them up and tear it down automatically. This works well when you have no other static
+ * mocks than the ones supported by their respective {@link StaticMockFixture}s.</p>
+ *
+ * <p>StaticMockFixtureRule should be defined as a rule on your test so it can clean up after
+ * itself. Like the following:</p>
+ * <pre class="prettyprint">
+* public final StaticMockFixture mStaticMockFixtures = ...;
+ * &#064;Rule
+ * public final StaticMockFixtureRule mStaticMockFixtureRule =
+ * new StaticMockFixtureRule(mStaticMockFixtures);
+ * </pre>
+ */
+public class StaticMockFixtureRule implements TestRule {
+ private StaticMockitoSession mMockitoSession;
+ private StaticMockFixture[] mStaticMockFixtures;
+ private Supplier<? extends StaticMockFixture>[] mSupplier;
+
+ /**
+ * Constructs a StaticMockFixtureRule that always uses the same {@link StaticMockFixture}
+ * instance(s).
+ *
+ * @param staticMockFixtures the {@link StaticMockFixture}(s) to use.
+ */
+ public StaticMockFixtureRule(StaticMockFixture... staticMockFixtures) {
+ mStaticMockFixtures = staticMockFixtures;
+ mSupplier = null;
+ }
+
+ /**
+ * Constructs a StaticMockFixtureRule that retrieves a new {@link StaticMockFixture} instance
+ * from one or more {@link Supplier<? extends StaticMockFixture >}s for each test invocation.
+ *
+ * @param supplier the {@link Supplier<? extends StaticMockFixture >}(s) that will supply the
+ * {@link StaticMockFixture}(s).
+ */
+ @SafeVarargs
+ public StaticMockFixtureRule(Supplier<? extends StaticMockFixture>... supplier) {
+ mStaticMockFixtures = null;
+ mSupplier = supplier;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ StaticMockitoSessionBuilder sessionBuilder = getSessionBuilder();
+
+ if (mSupplier != null) {
+ mStaticMockFixtures = new StaticMockFixture[mSupplier.length];
+ for (int i = 0; i < mSupplier.length; i++) {
+ mStaticMockFixtures[i] = mSupplier[i].get();
+ }
+ }
+
+ for (int i = 0; i < mStaticMockFixtures.length; i++) {
+ sessionBuilder = mStaticMockFixtures[i].setUpMockedClasses(sessionBuilder);
+ }
+
+ mMockitoSession = sessionBuilder.startMocking();
+
+ for (int i = 0; i < mStaticMockFixtures.length; i++) {
+ mStaticMockFixtures[i].setUpMockBehaviors();
+ }
+
+ return new TestWatcher() {
+ @Override
+ protected void succeeded(Description description) {
+ tearDown(null);
+ }
+
+ @Override
+ protected void skipped(AssumptionViolatedException e, Description description) {
+ tearDown(e);
+ }
+
+ @Override
+ protected void failed(Throwable e, Description description) {
+ tearDown(e);
+ }
+ }.apply(base, description);
+ }
+
+ /**
+ * This allows overriding the creation of the builder for a new {@link StaticMockitoSession}.
+ * Mainly for testing, but also useful if you have other requirements for the session.
+ *
+ * @return a new {@link StaticMockitoSessionBuilder}.
+ */
+ public StaticMockitoSessionBuilder getSessionBuilder() {
+ return mockitoSession().strictness(Strictness.LENIENT);
+ }
+
+ private void tearDown(Throwable e) {
+ mMockitoSession.finishMocking(e);
+
+ for (int i = mStaticMockFixtures.length - 1; i >= 0; i--) {
+ mStaticMockFixtures[i].tearDown();
+ if (mSupplier != null) {
+ mStaticMockFixtures[i] = null;
+ }
+ }
+
+ if (mSupplier != null) {
+ mStaticMockFixtures = null;
+ }
+
+ mMockitoSession = null;
+ }
+}
diff --git a/java/com/android/modules/utils/testing/TestableDeviceConfig.java b/java/com/android/modules/utils/testing/TestableDeviceConfig.java
new file mode 100644
index 0000000..8f62e03
--- /dev/null
+++ b/java/com/android/modules/utils/testing/TestableDeviceConfig.java
@@ -0,0 +1,258 @@
+/*
+ * 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.modules.utils.testing;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.when;
+
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.rules.TestRule;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * TestableDeviceConfig is a {@link StaticMockFixture} that 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.
+ */
+public final class TestableDeviceConfig implements StaticMockFixture {
+
+ private Map<DeviceConfig.OnPropertiesChangedListener, Pair<String, Executor>>
+ mOnPropertiesChangedListenerMap = new HashMap<>();
+ private Map<String, String> mKeyValueMap = new ConcurrentHashMap<>();
+
+ /**
+ * Clears out all local overrides.
+ */
+ public void clearDeviceConfig() {
+ mKeyValueMap.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(DeviceConfig.class);
+ return sessionBuilder;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setUpMockBehaviors() {
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String namespace = invocationOnMock.getArgument(0);
+ Executor executor = invocationOnMock.getArgument(1);
+ DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener =
+ invocationOnMock.getArgument(2);
+ mOnPropertiesChangedListenerMap.put(
+ onPropertiesChangedListener, new Pair<>(namespace, executor));
+ return null;
+ }).when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(Executor.class),
+ any(DeviceConfig.OnPropertiesChangedListener.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.OnPropertiesChangedListener listener :
+ mOnPropertiesChangedListenerMap.keySet()) {
+ if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
+ mOnPropertiesChangedListenerMap.get(listener).second.execute(
+ () -> listener.onPropertiesChanged(
+ getProperties(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()));
+
+ doAnswer((Answer<Properties>) invocationOnMock -> {
+ String namespace = invocationOnMock.getArgument(0);
+ final int varargStartIdx = 1;
+ Map<String, String> keyValues = new ArrayMap<>();
+ if (invocationOnMock.getArguments().length == varargStartIdx) {
+ mKeyValueMap.entrySet().forEach(entry -> {
+ Pair<String, String> nameSpaceAndName = getNameSpaceAndName(entry.getKey());
+ if (!nameSpaceAndName.first.equals(namespace)) {
+ return;
+ }
+ keyValues.put(nameSpaceAndName.second.toLowerCase(), entry.getValue());
+ });
+ } else {
+ for (int i = varargStartIdx; i < invocationOnMock.getArguments().length; ++i) {
+ String name = invocationOnMock.getArgument(i);
+ keyValues.put(name.toLowerCase(), mKeyValueMap.get(getKey(namespace, name)));
+ }
+ }
+ return getProperties(namespace, keyValues);
+ }).when(() -> DeviceConfig.getProperties(anyString(), ArgumentMatchers.<String>any()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void tearDown() {
+ clearDeviceConfig();
+ mOnPropertiesChangedListenerMap.clear();
+ }
+
+ private static String getKey(String namespace, String name) {
+ return namespace + "/" + name;
+ }
+
+ private Pair<String, String> getNameSpaceAndName(String key) {
+ final String[] values = key.split("/");
+ return Pair.create(values[0], values[1]);
+ }
+
+ private Properties getProperties(String namespace, String name, String value) {
+ return getProperties(namespace, Collections.singletonMap(name.toLowerCase(), value));
+ }
+
+ private Properties getProperties(String namespace, Map<String, String> keyValues) {
+ Properties properties = Mockito.mock(Properties.class);
+ when(properties.getNamespace()).thenReturn(namespace);
+ when(properties.getKeyset()).thenReturn(keyValues.keySet());
+ when(properties.getBoolean(anyString(), anyBoolean())).thenAnswer(
+ invocation -> {
+ String key = invocation.getArgument(0);
+ boolean defaultValue = invocation.getArgument(1);
+ final String value = keyValues.get(key.toLowerCase());
+ if (value != null) {
+ return Boolean.parseBoolean(value);
+ } else {
+ return defaultValue;
+ }
+ }
+ );
+ when(properties.getFloat(anyString(), anyFloat())).thenAnswer(
+ invocation -> {
+ String key = invocation.getArgument(0);
+ float defaultValue = invocation.getArgument(1);
+ final String value = keyValues.get(key.toLowerCase());
+ if (value != null) {
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ } else {
+ return defaultValue;
+ }
+ }
+ );
+ when(properties.getInt(anyString(), anyInt())).thenAnswer(
+ invocation -> {
+ String key = invocation.getArgument(0);
+ int defaultValue = invocation.getArgument(1);
+ final String value = keyValues.get(key.toLowerCase());
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ } else {
+ return defaultValue;
+ }
+ }
+ );
+ when(properties.getLong(anyString(), anyLong())).thenAnswer(
+ invocation -> {
+ String key = invocation.getArgument(0);
+ long defaultValue = invocation.getArgument(1);
+ final String value = keyValues.get(key.toLowerCase());
+ if (value != null) {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ }
+ } else {
+ return defaultValue;
+ }
+ }
+ );
+ when(properties.getString(anyString(), nullable(String.class))).thenAnswer(
+ invocation -> {
+ String key = invocation.getArgument(0);
+ String defaultValue = invocation.getArgument(1);
+ final String value = keyValues.get(key.toLowerCase());
+ if (value != null) {
+ return value;
+ } else {
+ return defaultValue;
+ }
+ }
+ );
+
+ return properties;
+ }
+
+ /**
+ * <p>TestableDeviceConfigRule is a {@link TestRule} that wraps a {@link TestableDeviceConfig}
+ * to set it up and tear it down automatically. This works well when you have no other static
+ * mocks.</p>
+ *
+ * <p>TestableDeviceConfigRule 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 TestableDeviceConfigRule mTestableDeviceConfigRule =
+ * new TestableDeviceConfigRule();
+ * </pre>
+ */
+ public static class TestableDeviceConfigRule extends StaticMockFixtureRule {
+ public TestableDeviceConfigRule() {
+ super(TestableDeviceConfig::new);
+ }
+ }
+}
diff --git a/javatests/com/android/modules/utils/testing/Android.bp b/javatests/com/android/modules/utils/testing/Android.bp
new file mode 100644
index 0000000..89f7797
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/Android.bp
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "ModulesUtilsTestingTests",
+
+ sdk_version: "module_current",
+ min_sdk_version: "29",
+
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
+ ],
+
+ srcs: ["*.java"],
+
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "platform-test-annotations",
+ "truth-prebuilt",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.runner",
+ ],
+
+ test_suites: [
+ "general-tests",
+ "mts",
+ ],
+}
diff --git a/javatests/com/android/modules/utils/testing/AndroidManifest.xml b/javatests/com/android/modules/utils/testing/AndroidManifest.xml
new file mode 100644
index 0000000..d1077ce
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.modules.utils.testing">
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.modules.utils.testing"/>
+</manifest>
diff --git a/javatests/com/android/modules/utils/testing/MultipleStaticMocksTest.java b/javatests/com/android/modules/utils/testing/MultipleStaticMocksTest.java
new file mode 100644
index 0000000..b925a6c
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/MultipleStaticMocksTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.modules.utils.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MultipleStaticMocksTest {
+ @Rule
+ public StaticMockFixtureRule mStaticMockFixtureRule =
+ new StaticMockFixtureRule(AB::new, CD::new);
+
+ private List<String> mCollected;
+
+ @Test
+ public void testMultipleStaticMocks() throws Exception {
+ mCollected = new ArrayList<>();
+ int n = 0;
+
+ A.a();
+ n = verifyCollected(n, "A.a");
+
+ D.b();
+ n = verifyCollected(n, "D.b");
+
+ C.b();
+ n = verifyCollected(n, "C.b");
+
+ B.a();
+ n = verifyCollected(n, "B.a");
+ }
+
+ private int verifyCollected(int n, String... last) {
+ assertThat(mCollected).hasSize(n + last.length);
+ assertThat(mCollected.subList(n, mCollected.size()))
+ .containsExactlyElementsIn(last).inOrder();
+ return n + last.length;
+ }
+
+ private static class A {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class B {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class C {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class D {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ /**
+ * AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}.
+ */
+ private class AB implements StaticMockFixture {
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(A.class);
+ sessionBuilder.spyStatic(B.class);
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("A.a");
+ return null;
+ }).when(A::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("A.b");
+ return null;
+ }).when(A::b);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("B.a");
+ return null;
+ }).when(B::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("B.b");
+ return null;
+ }).when(B::b);
+ }
+
+ @Override
+ public void tearDown() {
+
+ }
+ }
+
+ /**
+ * AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}.
+ */
+ private class CD implements StaticMockFixture {
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(C.class);
+ sessionBuilder.spyStatic(D.class);
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("C.a");
+ return null;
+ }).when(C::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("C.b");
+ return null;
+ }).when(C::b);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("D.a");
+ return null;
+ }).when(D::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("D.b");
+ return null;
+ }).when(D::b);
+ }
+
+ @Override
+ public void tearDown() {
+
+ }
+ }
+}
diff --git a/javatests/com/android/modules/utils/testing/StaticMockFixtureRuleTest.java b/javatests/com/android/modules/utils/testing/StaticMockFixtureRuleTest.java
new file mode 100644
index 0000000..7336761
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/StaticMockFixtureRuleTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.modules.utils.testing;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.After;
+import org.junit.AssumptionViolatedException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+
+import java.util.function.Supplier;
+
+/** Tests that StaticMockFixture manages fixtures and suppliers correctly. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StaticMockFixtureRuleTest {
+ private MockitoSession mMockitoSession;
+
+ @Mock private StaticMockitoSessionBuilder mSessionBuilder;
+ @Mock private StaticMockitoSession mSession;
+ @Mock private StaticMockFixture mA1;
+ @Mock private StaticMockFixture mB1;
+ @Mock private StaticMockFixture mA2;
+ @Mock private StaticMockFixture mB2;
+ @Mock private Supplier<StaticMockFixture> mSupplyA;
+ @Mock private Supplier<StaticMockFixture> mSupplyB;
+ @Mock private Statement mStatement;
+ @Mock private Statement mSkipStatement;
+ @Mock private Statement mThrowStatement;
+ @Mock private Description mDescription;
+
+ @Before
+ public void setUp() throws Throwable {
+ mMockitoSession = Mockito.mockitoSession()
+ .strictness(LENIENT)
+ .initMocks(this)
+ .startMocking();
+ prepareMockBehaviours();
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ private void prepareFixtureMocks(StaticMockFixture... mocks) {
+ for (StaticMockFixture mock : mocks) {
+ when(mock.setUpMockedClasses(any())).thenAnswer(
+ invocation -> invocation.getArgument(0));
+ doNothing().when(mock).setUpMockBehaviors();
+ }
+ }
+
+ private void prepareMockBehaviours() throws Throwable {
+ when(mSessionBuilder.startMocking()).thenReturn(mSession);
+ when(mSupplyA.get()).thenReturn(mA1, mA2);
+ when(mSupplyB.get()).thenReturn(mB1, mB2);
+ prepareFixtureMocks(mA1, mA2, mB1, mB2);
+ when(mA1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0));
+ doNothing().when(mA1).setUpMockBehaviors();
+ when(mB1.setUpMockedClasses(any())).thenAnswer(invocation -> invocation.getArgument(0));
+ doNothing().when(mB1).setUpMockBehaviors();
+ doNothing().when(mStatement).evaluate();
+ doThrow(new AssumptionViolatedException("bad assumption, test should be skipped"))
+ .when(mSkipStatement).evaluate();
+ doThrow(new IllegalArgumentException("bad argument, test should be failed"))
+ .when(mThrowStatement).evaluate();
+ doNothing().when(mA1).tearDown();
+ doNothing().when(mB1).tearDown();
+ }
+
+ private InOrder mocksInOrder() {
+ return inOrder(mSessionBuilder, mSession, mSupplyA, mSupplyB, mA1, mA2, mB1, mB2,
+ mStatement, mSkipStatement, mThrowStatement, mDescription);
+ }
+
+ private void verifyNoMoreImportantMockInteractions() {
+ verifyNoMoreInteractions(mSupplyA, mSupplyB, mA1, mA2, mB1, mB2, mStatement,
+ mSkipStatement, mThrowStatement);
+ }
+
+ @Test
+ public void testRuleWorksWithExplicitFixtures() throws Throwable {
+ InOrder inOrder = mocksInOrder();
+
+ StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
+ @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+ return mSessionBuilder;
+ }
+ };
+ Statement runMe = rule.apply(mStatement, mDescription);
+
+ inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA1).setUpMockBehaviors();
+ inOrder.verify(mB1).setUpMockBehaviors();
+
+ runMe.evaluate();
+
+ inOrder.verify(mStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB1).tearDown();
+ inOrder.verify(mA1).tearDown();
+
+ // Round two: use the same fixtures again.
+ rule.apply(mStatement, mDescription).evaluate();
+
+ inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA1).setUpMockBehaviors();
+ inOrder.verify(mB1).setUpMockBehaviors();
+ inOrder.verify(mStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB1).tearDown();
+ inOrder.verify(mA1).tearDown();
+
+ verifyNoMoreImportantMockInteractions();
+ }
+
+ @Test
+ public void testRuleWorksWithFixtureSuppliers() throws Throwable {
+ InOrder inOrder = mocksInOrder();
+
+ StaticMockFixtureRule rule = new StaticMockFixtureRule(mSupplyA, mSupplyB) {
+ @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+ return mSessionBuilder;
+ }
+ };
+ Statement runMe = rule.apply(mStatement, mDescription);
+
+ inOrder.verify(mSupplyA).get();
+ inOrder.verify(mSupplyB).get();
+ inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA1).setUpMockBehaviors();
+ inOrder.verify(mB1).setUpMockBehaviors();
+
+ runMe.evaluate();
+
+ inOrder.verify(mStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB1).tearDown();
+ inOrder.verify(mA1).tearDown();
+
+ // Round two: use the same suppliers again to retrieve different fixtures: mA2 and mB2
+ rule.apply(mStatement, mDescription).evaluate();
+
+ inOrder.verify(mSupplyA).get();
+ inOrder.verify(mSupplyB).get();
+ inOrder.verify(mA2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB2).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA2).setUpMockBehaviors();
+ inOrder.verify(mB2).setUpMockBehaviors();
+ inOrder.verify(mStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB2).tearDown();
+ inOrder.verify(mA2).tearDown();
+
+ verifyNoMoreImportantMockInteractions();
+ }
+
+ @Test
+ public void testTearDownOnSkippedTests() throws Throwable {
+ InOrder inOrder = mocksInOrder();
+
+ StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
+ @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+ return mSessionBuilder;
+ }
+ };
+ Statement skipStatement = rule.apply(mSkipStatement, mDescription);
+
+ inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA1).setUpMockBehaviors();
+ inOrder.verify(mB1).setUpMockBehaviors();
+
+ try {
+ skipStatement.evaluate();
+ fail("AssumptionViolatedException should have been thrown");
+ } catch (AssumptionViolatedException e) {
+ // expected
+ }
+
+ inOrder.verify(mSkipStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB1).tearDown();
+ inOrder.verify(mA1).tearDown();
+
+ verifyNoMoreImportantMockInteractions();
+ }
+
+ @Test
+ public void testTearDownOnFailedTests() throws Throwable {
+ InOrder inOrder = mocksInOrder();
+
+ StaticMockFixtureRule rule = new StaticMockFixtureRule(mA1, mB1) {
+ @Override public StaticMockitoSessionBuilder getSessionBuilder() {
+ return mSessionBuilder;
+ }
+ };
+ Statement failStatement = rule.apply(mThrowStatement, mDescription);
+
+ inOrder.verify(mA1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mB1).setUpMockedClasses(any(StaticMockitoSessionBuilder.class));
+ inOrder.verify(mA1).setUpMockBehaviors();
+ inOrder.verify(mB1).setUpMockBehaviors();
+
+ try {
+ failStatement.evaluate();
+ fail("IllegalArgumentException should have been thrown");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ inOrder.verify(mThrowStatement).evaluate();
+ // note: tearDown in reverse order
+ inOrder.verify(mB1).tearDown();
+ inOrder.verify(mA1).tearDown();
+
+ verifyNoMoreImportantMockInteractions();
+ }
+}
diff --git a/javatests/com/android/modules/utils/testing/TestableDeviceConfigAndOtherStaticMocksTest.java b/javatests/com/android/modules/utils/testing/TestableDeviceConfigAndOtherStaticMocksTest.java
new file mode 100644
index 0000000..8ebe424
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/TestableDeviceConfigAndOtherStaticMocksTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.modules.utils.testing;
+
+import android.provider.DeviceConfig;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TestableDeviceConfigAndOtherStaticMocksTest {
+ @Rule
+ public StaticMockFixtureRule mStaticMockFixtureRule =
+ new StaticMockFixtureRule(TestableDeviceConfig::new, AB::new, CD::new);
+
+ private List<String> mCollected;
+
+ @Test
+ public void testDeviceConfigAndOtherStaticMocks() throws Exception {
+ mCollected = new ArrayList<>();
+ int n = 0;
+
+ String namespace = "foo";
+ String flag = "bar";
+ String flagValue = "new value";
+
+ Assert.assertNull(DeviceConfig.getProperty(namespace, flag));
+
+ A.a();
+ verifyCollected(++n, "A.a");
+
+ DeviceConfig.setProperty(namespace, flag, flagValue, false);
+
+ D.b();
+ verifyCollected(++n, "D.b");
+
+ Assert.assertEquals(flagValue, DeviceConfig.getProperty(namespace, flag));
+
+ C.b();
+ verifyCollected(++n, "C.b");
+
+ B.a();
+ verifyCollected(++n, "B.a");
+ }
+
+ private void verifyCollected(int n, String last) {
+ Assert.assertEquals(n, mCollected.size());
+ Assert.assertEquals(last, mCollected.get(n - 1));
+ }
+
+ private static class A {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class B {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class C {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ private static class D {
+ /* package */ static void a() {}
+ /* package */ static void b() {}
+ }
+
+ /**
+ * AB StaticMockFixture class that handles two mocked classes, {@link A} and {@link B}.
+ */
+ private class AB implements StaticMockFixture {
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(A.class);
+ sessionBuilder.spyStatic(B.class);
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("A.a");
+ return null;
+ }).when(A::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("A.b");
+ return null;
+ }).when(A::b);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("B.a");
+ return null;
+ }).when(B::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("B.b");
+ return null;
+ }).when(B::b);
+ }
+
+ @Override
+ public void tearDown() {
+
+ }
+ }
+
+ /**
+ * AB StaticMockFixture class that handles two mocked classes, {@link C} and {@link D}.
+ */
+ private class CD implements StaticMockFixture {
+ @Override
+ public StaticMockitoSessionBuilder setUpMockedClasses(
+ StaticMockitoSessionBuilder sessionBuilder) {
+ sessionBuilder.spyStatic(C.class);
+ sessionBuilder.spyStatic(D.class);
+ return sessionBuilder;
+ }
+
+ @Override
+ public void setUpMockBehaviors() {
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("C.a");
+ return null;
+ }).when(C::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("C.b");
+ return null;
+ }).when(C::b);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("D.a");
+ return null;
+ }).when(D::a);
+ ExtendedMockito.doAnswer(invocation -> {
+ mCollected.add("D.b");
+ return null;
+ }).when(D::b);
+ }
+
+ @Override
+ public void tearDown() {
+
+ }
+ }
+}
diff --git a/javatests/com/android/modules/utils/testing/TestableDeviceConfigTest.java b/javatests/com/android/modules/utils/testing/TestableDeviceConfigTest.java
new file mode 100644
index 0000000..64e007e
--- /dev/null
+++ b/javatests/com/android/modules/utils/testing/TestableDeviceConfigTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.modules.utils.testing;
+
+import static android.provider.DeviceConfig.OnPropertiesChangedListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+
+import androidx.test.core.app.ApplicationProvider;
+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.TestableDeviceConfigRule
+ mTestableDeviceConfig = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @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 getProperties_empty() {
+ String newKey = "key2";
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ Properties properties = DeviceConfig.getProperties(sNamespace);
+ assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
+ assertThat(properties.getString(newKey, null)).isNull();
+
+ DeviceConfig.setProperty(sNamespace, newKey, newValue, false);
+ properties = DeviceConfig.getProperties(sNamespace);
+ assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
+ assertThat(properties.getString(newKey, null)).isEqualTo(newValue);
+
+ }
+
+ @Test
+ public void getProperties() {
+ Properties properties = DeviceConfig.getProperties(sNamespace, sKey);
+ assertThat(properties.getString(sKey, null)).isNull();
+
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ properties = DeviceConfig.getProperties(sNamespace, sKey);
+ assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
+
+ String newKey = "key2";
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, newKey, newValue, false);
+ properties = DeviceConfig.getProperties(sNamespace, sKey, newKey);
+ assertThat(properties.getString(sKey, null)).isEqualTo(sValue);
+ assertThat(properties.getString(newKey, null)).isEqualTo(newValue);
+
+ String unsetKey = "key3";
+ properties = DeviceConfig.getProperties(sNamespace, newKey, unsetKey);
+ assertThat(properties.getKeyset()).containsExactly(newKey, unsetKey);
+ assertThat(properties.getString(newKey, null)).isEqualTo(newValue);
+ assertThat(properties.getString(unsetKey, null)).isNull();
+ }
+
+ @Test
+ public void testListener() throws InterruptedException {
+ CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ OnPropertiesChangedListener changeListener = (properties) -> {
+ assertThat(properties.getNamespace()).isEqualTo(sNamespace);
+ assertThat(properties.getKeyset().size()).isEqualTo(1);
+ assertThat(properties.getKeyset()).contains(sKey);
+ assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
+ assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
+ countDownLatch.countDown();
+ };
+ try {
+ DeviceConfig.addOnPropertiesChangedListener(sNamespace,
+ ApplicationProvider.getApplicationContext().getMainExecutor(), changeListener);
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ assertThat(countDownLatch.await(
+ WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ } finally {
+ DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+ }
+ }
+
+}
+
+