Move TestableDeviceConfig to modules-utils.

This allows it to be reused by mainline modules.

Bug: 192108282
Test: AppSearchMockingServicesTests ModulesUtilsTests FrameworksMockingServicesTests
Change-Id: I605ae012902141717a0bc3001fb5166c81c53581
This commit is contained in:
Alexander Dorokhine
2021-11-09 12:02:34 -08:00
parent 578f707569
commit 0ba15f1568
14 changed files with 10 additions and 1312 deletions

View File

@@ -29,7 +29,10 @@ package {
android_test {
name: "FrameworksMockingServicesTests",
defaults: ["FrameworkMockingServicesTests-jni-defaults"],
defaults: [
"FrameworkMockingServicesTests-jni-defaults",
"modules-utils-testable-device-config-defaults",
],
srcs: [
"src/**/*.java",
@@ -67,9 +70,7 @@ android_test {
],
jni_libs: [
"libdexmakerjvmtiagent",
"libpsi",
"libstaticjvmtiagent",
],
certificate: "platform",

View File

@@ -38,10 +38,10 @@ import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
import com.android.server.testables.TestableDeviceConfig;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.Before;

View File

@@ -40,10 +40,10 @@ import android.text.TextUtils;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.modules.utils.testing.TestableDeviceConfig;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.appop.AppOpsService;
import com.android.server.testables.TestableDeviceConfig;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;

View File

@@ -29,7 +29,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.testables.TestableDeviceConfig;
import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
import org.junit.Rule;

View File

@@ -59,7 +59,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.CompatibilityOverridesToRemoveConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.server.testables.TestableDeviceConfig.TestableDeviceConfigRule;
import com.android.modules.utils.testing.TestableDeviceConfig.TestableDeviceConfigRule;
import org.junit.Before;
import org.junit.Rule;

View File

@@ -34,7 +34,7 @@ import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.testables.TestableDeviceConfig;
import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
import org.junit.ClassRule;

View File

@@ -36,7 +36,7 @@ import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.testables.TestableDeviceConfig;
import com.android.modules.utils.testing.TestableDeviceConfig;
import org.junit.Before;
import org.junit.ClassRule;

View File

@@ -1,163 +0,0 @@
/*
* 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.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() {
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* 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 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();
}

View File

@@ -1,141 +0,0 @@
/*
* 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.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;
}
}

View File

@@ -1,258 +0,0 @@
/*
* 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 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();
}
}

View File

@@ -1,284 +0,0 @@
/*
* 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 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);
invokeListeners(namespace, getProperties(namespace, name, value));
return true;
}
).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean()));
doAnswer((Answer<Boolean>) invocationOnMock -> {
String namespace = invocationOnMock.getArgument(0);
String name = invocationOnMock.getArgument(1);
mKeyValueMap.remove(getKey(namespace, name));
invokeListeners(namespace, getProperties(namespace, name, null));
return true;
}
).when(() -> DeviceConfig.deleteProperty(anyString(), anyString()));
doAnswer((Answer<Boolean>) invocationOnMock -> {
Properties properties = invocationOnMock.getArgument(0);
String namespace = properties.getNamespace();
Map<String, String> keyValues = new ArrayMap<>();
for (String name : properties.getKeyset()) {
String value = properties.getString(name, /* defaultValue= */ "");
mKeyValueMap.put(getKey(namespace, name), value);
keyValues.put(name.toLowerCase(), value);
}
invokeListeners(namespace, getProperties(namespace, keyValues));
return true;
}
).when(() -> DeviceConfig.setProperties(any(Properties.class)));
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 void invokeListeners(String namespace, Properties properties) {
for (DeviceConfig.OnPropertiesChangedListener listener :
mOnPropertiesChangedListenerMap.keySet()) {
if (namespace.equals(mOnPropertiesChangedListenerMap.get(listener).first)) {
mOnPropertiesChangedListenerMap.get(listener).second.execute(
() -> listener.onPropertiesChanged(properties));
}
}
}
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);
}
}
}

View File

@@ -1,172 +0,0 @@
/*
* 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 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() {
}
}
}

View File

@@ -1,229 +0,0 @@
/*
* 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.OnPropertiesChangedListener;
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityThread;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.BadConfigException;
import android.provider.DeviceConfig.Properties;
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 setProperties() throws BadConfigException {
String newKey = "key2";
String newValue = "value2";
DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
sValue).setString(newKey, newValue).build());
assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue);
assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue);
}
@Test
public void deleteProperty() {
DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue);
DeviceConfig.deleteProperty(sNamespace, sKey);
assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isNull();
String newNamespace = "namespace2";
String newValue = "value2";
DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isEqualTo(newValue);
DeviceConfig.deleteProperty(newNamespace, sKey);
assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isNull();
}
@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_setProperty() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnPropertiesChangedListener changeListener = (properties) -> {
assertThat(properties.getNamespace()).isEqualTo(sNamespace);
assertThat(properties.getKeyset()).containsExactly(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,
ActivityThread.currentApplication().getMainExecutor(), changeListener);
DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
assertThat(countDownLatch.await(
WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
} finally {
DeviceConfig.removeOnPropertiesChangedListener(changeListener);
}
}
@Test
public void testListener_setProperties() throws BadConfigException, InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
String newKey = "key2";
String newValue = "value2";
OnPropertiesChangedListener changeListener = (properties) -> {
assertThat(properties.getNamespace()).isEqualTo(sNamespace);
assertThat(properties.getKeyset()).containsExactly(sKey, newKey);
assertThat(properties.getString(sKey, "bogus_value")).isEqualTo(sValue);
assertThat(properties.getString(newKey, "bogus_value")).isEqualTo(newValue);
assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
countDownLatch.countDown();
};
try {
DeviceConfig.addOnPropertiesChangedListener(sNamespace,
ActivityThread.currentApplication().getMainExecutor(), changeListener);
DeviceConfig.setProperties(new Properties.Builder(sNamespace).setString(sKey,
sValue).setString(newKey, newValue).build());
assertThat(countDownLatch.await(
WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
} finally {
DeviceConfig.removeOnPropertiesChangedListener(changeListener);
}
}
@Test
public void testListener_deleteProperty() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnPropertiesChangedListener changeListener = (properties) -> {
assertThat(properties.getNamespace()).isEqualTo(sNamespace);
assertThat(properties.getKeyset()).containsExactly(sKey);
assertThat(properties.getString(sKey, "bogus_value")).isEqualTo("bogus_value");
assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value");
countDownLatch.countDown();
};
try {
DeviceConfig.addOnPropertiesChangedListener(sNamespace,
ActivityThread.currentApplication().getMainExecutor(), changeListener);
DeviceConfig.deleteProperty(sNamespace, sKey);
assertThat(countDownLatch.await(
WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
} finally {
DeviceConfig.removeOnPropertiesChangedListener(changeListener);
}
}
}