Merge "WM Tests: Use a separate WindowManager instance per test" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
71c632367a
@@ -21,6 +21,7 @@ import android.app.Fragment;
|
|||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.testing.BaseFragmentTest;
|
import android.testing.BaseFragmentTest;
|
||||||
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
|
|
||||||
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
import com.android.systemui.utils.leaks.LeakCheckedTest;
|
||||||
import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck;
|
import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck;
|
||||||
@@ -36,6 +37,10 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
|
|||||||
@Rule
|
@Rule
|
||||||
public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck();
|
public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
protected final TestableDependency mDependency = new TestableDependency(mContext);
|
protected final TestableDependency mDependency = new TestableDependency(mContext);
|
||||||
protected SysuiTestableContext mSysuiContext;
|
protected SysuiTestableContext mSysuiContext;
|
||||||
private Instrumentation mRealInstrumentation;
|
private Instrumentation mRealInstrumentation;
|
||||||
@@ -46,7 +51,6 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void SysuiSetup() {
|
public void SysuiSetup() {
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
SystemUIFactory.createFromConfig(mContext);
|
SystemUIFactory.createFromConfig(mContext);
|
||||||
// TODO: Figure out another way to give reference to a SysuiTestableContext.
|
// TODO: Figure out another way to give reference to a SysuiTestableContext.
|
||||||
mSysuiContext = (SysuiTestableContext) mContext;
|
mSysuiContext = (SysuiTestableContext) mContext;
|
||||||
|
|||||||
@@ -19,21 +19,18 @@ import static org.mockito.Mockito.spy;
|
|||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.app.Instrumentation;
|
import android.app.Instrumentation;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.MessageQueue;
|
import android.os.MessageQueue;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.filters.SmallTest;
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
import android.testing.LeakCheck;
|
import android.testing.LeakCheck;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -51,12 +48,14 @@ public abstract class SysuiTestCase {
|
|||||||
@Rule
|
@Rule
|
||||||
public SysuiTestableContext mContext = new SysuiTestableContext(
|
public SysuiTestableContext mContext = new SysuiTestableContext(
|
||||||
InstrumentationRegistry.getContext(), getLeakCheck());
|
InstrumentationRegistry.getContext(), getLeakCheck());
|
||||||
|
@Rule
|
||||||
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
public TestableDependency mDependency = new TestableDependency(mContext);
|
public TestableDependency mDependency = new TestableDependency(mContext);
|
||||||
private Instrumentation mRealInstrumentation;
|
private Instrumentation mRealInstrumentation;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void SysuiSetup() throws Exception {
|
public void SysuiSetup() throws Exception {
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
mContext.setTheme(R.style.Theme_SystemUI);
|
mContext.setTheme(R.style.Theme_SystemUI);
|
||||||
SystemUIFactory.createFromConfig(mContext);
|
SystemUIFactory.createFromConfig(mContext);
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,13 @@ import android.content.pm.UserInfo;
|
|||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.support.test.filters.SmallTest;
|
import android.support.test.filters.SmallTest;
|
||||||
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
|
|
||||||
import com.android.internal.app.UnlaunchableAppActivity;
|
import com.android.internal.app.UnlaunchableAppActivity;
|
||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
@@ -64,6 +66,10 @@ public class ActivityStartInterceptorTest {
|
|||||||
0 /* flags */);
|
0 /* flags */);
|
||||||
private static final String TEST_PACKAGE_NAME = "com.test.package";
|
private static final String TEST_PACKAGE_NAME = "com.test.package";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -84,9 +90,6 @@ public class ActivityStartInterceptorTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
// This property is used to allow mocking of package private classes with mockito
|
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
|
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext,
|
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor, mContext,
|
||||||
mUserController);
|
mUserController);
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import static org.mockito.Mockito.doAnswer;
|
|||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
import com.android.server.wm.DisplayWindowController;
|
import com.android.server.wm.DisplayWindowController;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
|
||||||
import android.app.IApplicationThread;
|
import android.app.IApplicationThread;
|
||||||
@@ -47,6 +49,8 @@ import android.os.HandlerThread;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.service.voice.IVoiceInteractionSession;
|
import android.service.voice.IVoiceInteractionSession;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
|
|
||||||
import com.android.server.AttributeCache;
|
import com.android.server.AttributeCache;
|
||||||
import com.android.server.wm.AppWindowContainerController;
|
import com.android.server.wm.AppWindowContainerController;
|
||||||
import com.android.server.wm.PinnedStackWindowController;
|
import com.android.server.wm.PinnedStackWindowController;
|
||||||
@@ -64,6 +68,10 @@ import org.mockito.MockitoAnnotations;
|
|||||||
public class ActivityTestsBase {
|
public class ActivityTestsBase {
|
||||||
private static boolean sOneTimeSetupDone = false;
|
private static boolean sOneTimeSetupDone = false;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
private final Context mContext = InstrumentationRegistry.getContext();
|
private final Context mContext = InstrumentationRegistry.getContext();
|
||||||
private HandlerThread mHandlerThread;
|
private HandlerThread mHandlerThread;
|
||||||
|
|
||||||
@@ -77,9 +85,6 @@ public class ActivityTestsBase {
|
|||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
if (!sOneTimeSetupDone) {
|
if (!sOneTimeSetupDone) {
|
||||||
sOneTimeSetupDone = true;
|
sOneTimeSetupDone = true;
|
||||||
|
|
||||||
// Allows to mock package local classes and methods
|
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
}
|
}
|
||||||
mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
|
mHandlerThread = new HandlerThread("ActivityTestsBaseThread");
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import android.provider.Settings;
|
|||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
import android.support.test.filters.SmallTest;
|
import android.support.test.filters.SmallTest;
|
||||||
import android.telecom.TelecomManager;
|
import android.telecom.TelecomManager;
|
||||||
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
@@ -65,6 +66,7 @@ import com.android.server.wm.WindowManagerService;
|
|||||||
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
@@ -85,6 +87,10 @@ public class LockTaskControllerTest {
|
|||||||
private static final int TEST_USER_ID = 123;
|
private static final int TEST_USER_ID = 123;
|
||||||
private static final int TEST_UID = 10467;
|
private static final int TEST_UID = 10467;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
@Mock private ActivityStackSupervisor mSupervisor;
|
@Mock private ActivityStackSupervisor mSupervisor;
|
||||||
@Mock private IDevicePolicyManager mDevicePolicyManager;
|
@Mock private IDevicePolicyManager mDevicePolicyManager;
|
||||||
@Mock private IStatusBarService mStatusBarService;
|
@Mock private IStatusBarService mStatusBarService;
|
||||||
@@ -100,9 +106,6 @@ public class LockTaskControllerTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
// This property is used to allow mocking of package private classes with mockito
|
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
|
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
|
||||||
mContext = InstrumentationRegistry.getTargetContext();
|
mContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import java.util.List;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
|
||||||
import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
|
import static com.android.server.am.UserController.CONTINUE_USER_SWITCH_MSG;
|
||||||
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
|
import static com.android.server.am.UserController.REPORT_LOCKED_BOOT_COMPLETE_MSG;
|
||||||
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
|
import static com.android.server.am.UserController.REPORT_USER_SWITCH_COMPLETE_MSG;
|
||||||
@@ -102,6 +103,7 @@ public class UserControllerTest extends AndroidTestCase {
|
|||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
runWithDexmakerShareClassLoader(() -> {
|
||||||
mInjector = Mockito.spy(new TestInjector(getContext()));
|
mInjector = Mockito.spy(new TestInjector(getContext()));
|
||||||
doNothing().when(mInjector).clearAllLockedTasks(anyString());
|
doNothing().when(mInjector).clearAllLockedTasks(anyString());
|
||||||
doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
|
doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
|
||||||
@@ -109,6 +111,7 @@ public class UserControllerTest extends AndroidTestCase {
|
|||||||
doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
|
doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
|
||||||
mUserController = new UserController(mInjector);
|
mUserController = new UserController(mInjector);
|
||||||
setUpUser(TEST_USER_ID, 0);
|
setUpUser(TEST_USER_ID, 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.support.test.filters.SmallTest;
|
|||||||
import android.support.test.runner.AndroidJUnit4;
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@@ -41,12 +42,14 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class AppTransitionTests {
|
public class AppTransitionTests {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final WindowManagerServiceRule mRule = new WindowManagerServiceRule();
|
||||||
private WindowManagerService mWm;
|
private WindowManagerService mWm;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
final Context context = InstrumentationRegistry.getTargetContext();
|
final Context context = InstrumentationRegistry.getTargetContext();
|
||||||
mWm = TestWindowManagerPolicy.getWindowManagerService(context);
|
mWm = mRule.getWindowManagerService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -16,108 +16,47 @@
|
|||||||
|
|
||||||
package com.android.server.wm;
|
package com.android.server.wm;
|
||||||
|
|
||||||
import static android.view.Display.DEFAULT_DISPLAY;
|
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
|
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
|
||||||
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
|
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
|
||||||
|
|
||||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.any;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.spy;
|
|
||||||
|
|
||||||
import android.os.PowerSaveState;
|
|
||||||
import android.util.proto.ProtoOutputStream;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManagerInternal;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.CompatibilityInfo;
|
import android.content.res.CompatibilityInfo;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.hardware.display.DisplayManagerInternal;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.util.proto.ProtoOutputStream;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayCutout;
|
import android.view.DisplayCutout;
|
||||||
|
import android.view.IWindow;
|
||||||
import android.view.IWindowManager;
|
import android.view.IWindowManager;
|
||||||
import android.view.InputChannel;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.os.PowerManagerInternal;
|
|
||||||
|
|
||||||
import com.android.internal.policy.IKeyguardDismissCallback;
|
import com.android.internal.policy.IKeyguardDismissCallback;
|
||||||
import com.android.internal.policy.IShortcutService;
|
import com.android.internal.policy.IShortcutService;
|
||||||
import com.android.server.input.InputManagerService;
|
|
||||||
import com.android.server.LocalServices;
|
|
||||||
import com.android.server.policy.WindowManagerPolicy;
|
import com.android.server.policy.WindowManagerPolicy;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
class TestWindowManagerPolicy implements WindowManagerPolicy {
|
class TestWindowManagerPolicy implements WindowManagerPolicy {
|
||||||
private static final String TAG = "TestWindowManagerPolicy";
|
private static final String TAG = "TestWindowManagerPolicy";
|
||||||
|
|
||||||
private static WindowManagerService sWm = null;
|
private final Supplier<WindowManagerService> mWmSupplier;
|
||||||
|
|
||||||
int rotationToReport = 0;
|
int rotationToReport = 0;
|
||||||
|
|
||||||
private Runnable mRunnableWhenAddingSplashScreen;
|
private Runnable mRunnableWhenAddingSplashScreen;
|
||||||
|
|
||||||
static synchronized WindowManagerService getWindowManagerService(Context context) {
|
public TestWindowManagerPolicy(Supplier<WindowManagerService> wmSupplier) {
|
||||||
if (sWm == null) {
|
|
||||||
// We only want to do this once for the test process as we don't want WM to try to
|
|
||||||
// register a bunch of local services again.
|
|
||||||
if (LocalServices.getService(DisplayManagerInternal.class) == null) {
|
|
||||||
LocalServices.addService(DisplayManagerInternal.class,
|
|
||||||
mock(DisplayManagerInternal.class));
|
|
||||||
}
|
|
||||||
if (LocalServices.getService(PowerManagerInternal.class) == null) {
|
|
||||||
LocalServices.addService(PowerManagerInternal.class,
|
|
||||||
mock(PowerManagerInternal.class));
|
|
||||||
final PowerManagerInternal pm =
|
|
||||||
LocalServices.getService(PowerManagerInternal.class);
|
|
||||||
PowerSaveState state = new PowerSaveState.Builder().build();
|
|
||||||
doReturn(state).when(pm).getLowPowerState(anyInt());
|
|
||||||
}
|
|
||||||
if (LocalServices.getService(ActivityManagerInternal.class) == null) {
|
|
||||||
LocalServices.addService(ActivityManagerInternal.class,
|
|
||||||
mock(ActivityManagerInternal.class));
|
|
||||||
final ActivityManagerInternal am =
|
|
||||||
LocalServices.getService(ActivityManagerInternal.class);
|
|
||||||
doAnswer((InvocationOnMock invocationOnMock) -> {
|
|
||||||
final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
|
|
||||||
if (runnable != null) {
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}).when(am).notifyKeyguardFlagsChanged(any());
|
|
||||||
}
|
|
||||||
|
|
||||||
InputManagerService ims = mock(InputManagerService.class);
|
mWmSupplier = wmSupplier;
|
||||||
// InputChannel is final and can't be mocked.
|
|
||||||
InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
|
|
||||||
if (input != null && input.length > 1) {
|
|
||||||
doReturn(input[1]).when(ims).monitorInput(anyString());
|
|
||||||
}
|
|
||||||
|
|
||||||
sWm = WindowManagerService.main(context, ims, true, false,
|
|
||||||
false, new TestWindowManagerPolicy());
|
|
||||||
|
|
||||||
sWm.onInitReady();
|
|
||||||
|
|
||||||
// Display creation is driven by the ActivityManagerService via ActivityStackSupervisor.
|
|
||||||
// We emulate those steps here.
|
|
||||||
sWm.mRoot.createDisplayContent(sWm.mDisplayManager.getDisplay(DEFAULT_DISPLAY),
|
|
||||||
mock(DisplayWindowController.class));
|
|
||||||
}
|
|
||||||
return sWm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -216,10 +155,12 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
|
|||||||
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
|
int logo, int windowFlags, Configuration overrideConfig, int displayId) {
|
||||||
final com.android.server.wm.WindowState window;
|
final com.android.server.wm.WindowState window;
|
||||||
final AppWindowToken atoken;
|
final AppWindowToken atoken;
|
||||||
synchronized (sWm.mWindowMap) {
|
final WindowManagerService wm = mWmSupplier.get();
|
||||||
atoken = sWm.mRoot.getAppWindowToken(appToken);
|
synchronized (wm.mWindowMap) {
|
||||||
|
atoken = wm.mRoot.getAppWindowToken(appToken);
|
||||||
window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
|
window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken,
|
||||||
"Starting window");
|
"Starting window", 0 /* ownerId */, false /* internalWindows */, wm,
|
||||||
|
mock(Session.class), mock(IWindow.class));
|
||||||
atoken.startingWindow = window;
|
atoken.startingWindow = window;
|
||||||
}
|
}
|
||||||
if (mRunnableWhenAddingSplashScreen != null) {
|
if (mRunnableWhenAddingSplashScreen != null) {
|
||||||
@@ -227,7 +168,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
|
|||||||
mRunnableWhenAddingSplashScreen = null;
|
mRunnableWhenAddingSplashScreen = null;
|
||||||
}
|
}
|
||||||
return () -> {
|
return () -> {
|
||||||
synchronized (sWm.mWindowMap) {
|
synchronized (wm.mWindowMap) {
|
||||||
atoken.removeChild(window);
|
atoken.removeChild(window);
|
||||||
atoken.startingWindow = null;
|
atoken.startingWindow = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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.wm;
|
||||||
|
|
||||||
|
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
|
||||||
|
import static android.view.Display.DEFAULT_DISPLAY;
|
||||||
|
|
||||||
|
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
import android.app.ActivityManagerInternal;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.display.DisplayManagerInternal;
|
||||||
|
import android.os.PowerManagerInternal;
|
||||||
|
import android.os.PowerSaveState;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.view.InputChannel;
|
||||||
|
|
||||||
|
import com.android.server.LocalServices;
|
||||||
|
import com.android.server.input.InputManagerService;
|
||||||
|
import com.android.server.policy.WindowManagerPolicy;
|
||||||
|
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.runner.Description;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test rule that sets up a fresh WindowManagerService instance before each test and makes sure
|
||||||
|
* to properly tear it down after.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Usage:
|
||||||
|
* <pre>
|
||||||
|
* {@literal @}Rule
|
||||||
|
* public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class WindowManagerServiceRule implements TestRule {
|
||||||
|
|
||||||
|
private WindowManagerService mService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement apply(Statement base, Description description) {
|
||||||
|
return new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
runWithDexmakerShareClassLoader(this::setUp);
|
||||||
|
try {
|
||||||
|
base.evaluate();
|
||||||
|
} finally {
|
||||||
|
tearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUp() {
|
||||||
|
final Context context = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
removeServices();
|
||||||
|
|
||||||
|
LocalServices.addService(DisplayManagerInternal.class,
|
||||||
|
mock(DisplayManagerInternal.class));
|
||||||
|
|
||||||
|
LocalServices.addService(PowerManagerInternal.class,
|
||||||
|
mock(PowerManagerInternal.class));
|
||||||
|
final PowerManagerInternal pm =
|
||||||
|
LocalServices.getService(PowerManagerInternal.class);
|
||||||
|
PowerSaveState state = new PowerSaveState.Builder().build();
|
||||||
|
doReturn(state).when(pm).getLowPowerState(anyInt());
|
||||||
|
|
||||||
|
LocalServices.addService(ActivityManagerInternal.class,
|
||||||
|
mock(ActivityManagerInternal.class));
|
||||||
|
final ActivityManagerInternal am =
|
||||||
|
LocalServices.getService(ActivityManagerInternal.class);
|
||||||
|
doAnswer((InvocationOnMock invocationOnMock) -> {
|
||||||
|
final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
|
||||||
|
if (runnable != null) {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}).when(am).notifyKeyguardFlagsChanged(any());
|
||||||
|
|
||||||
|
InputManagerService ims = mock(InputManagerService.class);
|
||||||
|
// InputChannel is final and can't be mocked.
|
||||||
|
InputChannel[] input = InputChannel.openInputChannelPair(TAG_WM);
|
||||||
|
if (input != null && input.length > 1) {
|
||||||
|
doReturn(input[1]).when(ims).monitorInput(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
mService = WindowManagerService.main(context, ims, true, false,
|
||||||
|
false, new TestWindowManagerPolicy(
|
||||||
|
WindowManagerServiceRule.this::getWindowManagerService));
|
||||||
|
|
||||||
|
mService.onInitReady();
|
||||||
|
|
||||||
|
// Display creation is driven by the ActivityManagerService via ActivityStackSupervisor.
|
||||||
|
// We emulate those steps here.
|
||||||
|
mService.mRoot.createDisplayContent(
|
||||||
|
mService.mDisplayManager.getDisplay(DEFAULT_DISPLAY),
|
||||||
|
mock(DisplayWindowController.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeServices() {
|
||||||
|
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
|
||||||
|
LocalServices.removeServiceForTest(PowerManagerInternal.class);
|
||||||
|
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
|
||||||
|
LocalServices.removeServiceForTest(WindowManagerInternal.class);
|
||||||
|
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tearDown() {
|
||||||
|
waitUntilWindowManagerHandlersIdle();
|
||||||
|
removeServices();
|
||||||
|
mService = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public WindowManagerService getWindowManagerService() {
|
||||||
|
return mService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitUntilWindowManagerHandlersIdle() {
|
||||||
|
final WindowManagerService wm = getWindowManagerService();
|
||||||
|
if (wm != null) {
|
||||||
|
wm.mH.runWithScissors(() -> { }, 0);
|
||||||
|
wm.mAnimationHandler.runWithScissors(() -> { }, 0);
|
||||||
|
SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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.wm;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import android.platform.test.annotations.Presubmit;
|
||||||
|
import android.support.test.filters.SmallTest;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
@Presubmit
|
||||||
|
@SmallTest
|
||||||
|
public class WindowManagerServiceRuleTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final WindowManagerServiceRule mRule = new WindowManagerServiceRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWindowManagerSetUp() {
|
||||||
|
assertThat(mRule.getWindowManagerService(), notNullValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,13 +47,6 @@ import org.mockito.invocation.InvocationOnMock;
|
|||||||
public class WindowTestUtils {
|
public class WindowTestUtils {
|
||||||
public static int sNextTaskId = 0;
|
public static int sNextTaskId = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves an instance of {@link WindowManagerService}, creating it if necessary.
|
|
||||||
*/
|
|
||||||
public static WindowManagerService getWindowManagerService(Context context) {
|
|
||||||
return TestWindowManagerPolicy.getWindowManagerService(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an instance of a mock {@link WindowManagerService}.
|
* Retrieves an instance of a mock {@link WindowManagerService}.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ import static android.view.View.VISIBLE;
|
|||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.hardware.display.DisplayManagerGlobal;
|
import android.hardware.display.DisplayManagerGlobal;
|
||||||
|
import android.testing.DexmakerShareClassLoaderRule;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.DisplayInfo;
|
import android.view.DisplayInfo;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.mockito.Mock;
|
import org.junit.Rule;
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
@@ -66,16 +66,15 @@ import java.util.LinkedList;
|
|||||||
*/
|
*/
|
||||||
class WindowTestsBase {
|
class WindowTestsBase {
|
||||||
private static final String TAG = WindowTestsBase.class.getSimpleName();
|
private static final String TAG = WindowTestsBase.class.getSimpleName();
|
||||||
static WindowManagerService sWm = null;
|
WindowManagerService sWm = null; // TODO(roosa): rename to mWm in follow-up CL
|
||||||
private static final IWindow sIWindow = new TestIWindow();
|
private final IWindow mIWindow = new TestIWindow();
|
||||||
private static Session sMockSession;
|
private Session mMockSession;
|
||||||
// The default display is removed in {@link #setUp} and then we iterate over all displays to
|
// The default display is removed in {@link #setUp} and then we iterate over all displays to
|
||||||
// make sure we don't collide with any existing display. If we run into no other display, the
|
// make sure we don't collide with any existing display. If we run into no other display, the
|
||||||
// added display should be treated as default. This cannot be the default display
|
// added display should be treated as default. This cannot be the default display
|
||||||
private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
|
private static int sNextDisplayId = DEFAULT_DISPLAY + 1;
|
||||||
static int sNextStackId = 1000;
|
static int sNextStackId = 1000;
|
||||||
|
|
||||||
private static boolean sOneTimeSetupDone = false;
|
|
||||||
DisplayContent mDisplayContent;
|
DisplayContent mDisplayContent;
|
||||||
DisplayInfo mDisplayInfo = new DisplayInfo();
|
DisplayInfo mDisplayInfo = new DisplayInfo();
|
||||||
WindowState mWallpaperWindow;
|
WindowState mWallpaperWindow;
|
||||||
@@ -90,27 +89,27 @@ class WindowTestsBase {
|
|||||||
HashSet<WindowState> mCommonWindows;
|
HashSet<WindowState> mCommonWindows;
|
||||||
WallpaperController mWallpaperController;
|
WallpaperController mWallpaperController;
|
||||||
|
|
||||||
@Mock
|
@Rule
|
||||||
static WindowState.PowerManagerWrapper mPowerManagerWrapper;
|
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
|
||||||
|
new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final WindowManagerServiceRule mWmRule = new WindowManagerServiceRule();
|
||||||
|
|
||||||
|
static WindowState.PowerManagerWrapper mPowerManagerWrapper; // TODO(roosa): make non-static.
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
// If @Before throws an exception, the error isn't logged. This will make sure any failures
|
// If @Before throws an exception, the error isn't logged. This will make sure any failures
|
||||||
// in the set up are clear. This can be removed when b/37850063 is fixed.
|
// in the set up are clear. This can be removed when b/37850063 is fixed.
|
||||||
try {
|
try {
|
||||||
if (!sOneTimeSetupDone) {
|
mMockSession = mock(Session.class);
|
||||||
sOneTimeSetupDone = true;
|
mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
|
||||||
|
|
||||||
// Allows to mock package local classes and methods
|
|
||||||
System.setProperty("dexmaker.share_classloader", "true");
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
sMockSession = mock(Session.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Context context = InstrumentationRegistry.getTargetContext();
|
final Context context = InstrumentationRegistry.getTargetContext();
|
||||||
AttributeCache.init(context);
|
AttributeCache.init(context);
|
||||||
|
|
||||||
sWm = TestWindowManagerPolicy.getWindowManagerService(context);
|
sWm = mWmRule.getWindowManagerService();
|
||||||
beforeCreateDisplay();
|
beforeCreateDisplay();
|
||||||
|
|
||||||
mWallpaperController = new WallpaperController(sWm);
|
mWallpaperController = new WallpaperController(sWm);
|
||||||
@@ -217,9 +216,7 @@ class WindowTestsBase {
|
|||||||
* Waits until the main handler for WM has processed all messages.
|
* Waits until the main handler for WM has processed all messages.
|
||||||
*/
|
*/
|
||||||
void waitUntilHandlersIdle() {
|
void waitUntilHandlersIdle() {
|
||||||
sWm.mH.runWithScissors(() -> { }, 0);
|
mWmRule.waitUntilWindowManagerHandlersIdle();
|
||||||
sWm.mAnimationHandler.runWithScissors(() -> { }, 0);
|
|
||||||
SurfaceAnimationThread.getHandler().runWithScissors(() -> { }, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowToken createWindowToken(
|
private WindowToken createWindowToken(
|
||||||
@@ -285,20 +282,27 @@ class WindowTestsBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
|
WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
|
||||||
synchronized (sWm.mWindowMap) {
|
synchronized (sWm.mWindowMap) {
|
||||||
return createWindow(parent, type, token, name, 0 /* ownerId */,
|
return createWindow(parent, type, token, name, 0 /* ownerId */,
|
||||||
false /* ownerCanAddInternalSystemWindow */);
|
false /* ownerCanAddInternalSystemWindow */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
|
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
|
||||||
int ownerId, boolean ownerCanAddInternalSystemWindow) {
|
int ownerId, boolean ownerCanAddInternalSystemWindow) {
|
||||||
synchronized (sWm.mWindowMap) {
|
return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
|
||||||
|
sWm, mMockSession, mIWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
static WindowState createWindow(WindowState parent, int type, WindowToken token,
|
||||||
|
String name, int ownerId, boolean ownerCanAddInternalSystemWindow,
|
||||||
|
WindowManagerService service, Session session, IWindow iWindow) {
|
||||||
|
synchronized (service.mWindowMap) {
|
||||||
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
|
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
|
||||||
attrs.setTitle(name);
|
attrs.setTitle(name);
|
||||||
|
|
||||||
final WindowState w = new WindowState(sWm, sMockSession, sIWindow, token, parent,
|
final WindowState w = new WindowState(service, session, iWindow, token, parent,
|
||||||
OP_NONE,
|
OP_NONE,
|
||||||
0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
|
0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
|
||||||
mPowerManagerWrapper);
|
mPowerManagerWrapper);
|
||||||
@@ -357,7 +361,7 @@ class WindowTestsBase {
|
|||||||
WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs,
|
WindowTestUtils.TestWindowState createWindowState(WindowManager.LayoutParams attrs,
|
||||||
WindowToken token) {
|
WindowToken token) {
|
||||||
synchronized (sWm.mWindowMap) {
|
synchronized (sWm.mWindowMap) {
|
||||||
return new WindowTestUtils.TestWindowState(sWm, sMockSession, sIWindow, attrs, token);
|
return new WindowTestUtils.TestWindowState(sWm, mMockSession, mIWindow, attrs, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.testing;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.runner.Description;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the test such that mocks created in it don't use a dedicated classloader.
|
||||||
|
*
|
||||||
|
* This allows mocking package-private methods.
|
||||||
|
*
|
||||||
|
* WARNING: This is absolutely incompatible with running tests in parallel!
|
||||||
|
*/
|
||||||
|
public class DexmakerShareClassLoaderRule implements TestRule {
|
||||||
|
|
||||||
|
private static final String TAG = "ShareClassloaderRule";
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String DEXMAKER_SHARE_CLASSLOADER_PROPERTY = "dexmaker.share_classloader";
|
||||||
|
|
||||||
|
private static Thread sOwningThread = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Statement apply(Statement base, Description description) {
|
||||||
|
return apply(base::evaluate).toStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the runnable such that mocks created in it don't use a dedicated classloader.
|
||||||
|
*
|
||||||
|
* This allows mocking package-private methods.
|
||||||
|
*
|
||||||
|
* WARNING: This is absolutely incompatible with running tests in parallel!
|
||||||
|
*/
|
||||||
|
public static void runWithDexmakerShareClassLoader(Runnable r) {
|
||||||
|
apply(r::run).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a statement that first makes sure that only one thread at the time is modifying
|
||||||
|
* the property. Then actually sets the property, and runs the statement.
|
||||||
|
*/
|
||||||
|
private static <T extends Throwable> ThrowingRunnable<T> apply(ThrowingRunnable<T> r) {
|
||||||
|
return wrapInMutex(wrapInSetAndClearProperty(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends Throwable> ThrowingRunnable<T> wrapInSetAndClearProperty(
|
||||||
|
ThrowingRunnable<T> r) {
|
||||||
|
return () -> {
|
||||||
|
final String previousValue = System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
|
||||||
|
try {
|
||||||
|
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, "true");
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
if (previousValue != null) {
|
||||||
|
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, previousValue);
|
||||||
|
} else {
|
||||||
|
System.clearProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the given statement, and while doing so prevents other threads from running statements.
|
||||||
|
*/
|
||||||
|
private static <T extends Throwable> ThrowingRunnable<T> wrapInMutex(ThrowingRunnable<T> r) {
|
||||||
|
return () -> {
|
||||||
|
final boolean isOwner;
|
||||||
|
synchronized (DexmakerShareClassLoaderRule.class) {
|
||||||
|
isOwner = (sOwningThread == null);
|
||||||
|
if (isOwner) {
|
||||||
|
sOwningThread = Thread.currentThread();
|
||||||
|
} else if (sOwningThread != Thread.currentThread()) {
|
||||||
|
final RuntimeException e = new ConcurrentModificationException(
|
||||||
|
"Tried to set dexmaker.share_classloader from " + Thread.currentThread()
|
||||||
|
+ ", but was already set from " + sOwningThread);
|
||||||
|
// Also log in case exception gets swallowed.
|
||||||
|
Log.e(TAG, e.getMessage(), e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.run();
|
||||||
|
} finally {
|
||||||
|
synchronized (DexmakerShareClassLoaderRule.class) {
|
||||||
|
if (isOwner) {
|
||||||
|
sOwningThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface ThrowingRunnable<T extends Throwable> {
|
||||||
|
void run() throws T;
|
||||||
|
|
||||||
|
default Statement toStatement() {
|
||||||
|
return new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
ThrowingRunnable.this.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package android.testing;
|
||||||
|
|
||||||
|
import static android.testing.DexmakerShareClassLoaderRule.DEXMAKER_SHARE_CLASSLOADER_PROPERTY;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import android.support.test.filters.SmallTest;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.model.Statement;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class DexmakerShareClassLoaderRuleTest {
|
||||||
|
|
||||||
|
// Intentionally not @Rule, so we have more control.
|
||||||
|
private final DexmakerShareClassLoaderRule mRule = new DexmakerShareClassLoaderRule();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
System.clearProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rule_setsProperty() throws Throwable {
|
||||||
|
mRule.apply(ASSERT_STATEMENT, null).evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rule_resetsProperty() throws Throwable {
|
||||||
|
mRule.apply(ASSERT_STATEMENT, null).evaluate();
|
||||||
|
assertThat(readProperty(), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rule_resetsProperty_toExactValue() throws Throwable {
|
||||||
|
System.setProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY, "asdf");
|
||||||
|
mRule.apply(ASSERT_STATEMENT, null).evaluate();
|
||||||
|
assertThat(System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY), is("asdf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rule_preventsOtherThreadFromInterfering() throws Throwable {
|
||||||
|
mRule.apply(new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
assertThat(readProperty(), is(true));
|
||||||
|
|
||||||
|
final Throwable[] thrown = new Throwable[1];
|
||||||
|
final Thread t = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
new DexmakerShareClassLoaderRule().apply(ASSERT_STATEMENT, null).evaluate();
|
||||||
|
fail("Expected a ConcurrentModificationException");
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Success.
|
||||||
|
} catch (Throwable tr) {
|
||||||
|
thrown[0] = tr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
|
t.join();
|
||||||
|
|
||||||
|
if (thrown[0] != null) {
|
||||||
|
throw thrown[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null).evaluate();
|
||||||
|
assertThat(readProperty(), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rule_isReentrant() throws Throwable {
|
||||||
|
mRule.apply(new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
assertThat(readProperty(), is(true));
|
||||||
|
new DexmakerShareClassLoaderRule().apply(ASSERT_STATEMENT, null).evaluate();
|
||||||
|
assertThat(readProperty(), is(true));
|
||||||
|
}
|
||||||
|
}, null).evaluate();
|
||||||
|
assertThat(readProperty(), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean readProperty() {
|
||||||
|
return Boolean.parseBoolean(System.getProperty(DEXMAKER_SHARE_CLASSLOADER_PROPERTY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Statement ASSERT_STATEMENT = new Statement() {
|
||||||
|
@Override
|
||||||
|
public void evaluate() throws Throwable {
|
||||||
|
assertThat(readProperty(), is(true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user