Add test case for racing between process's death and service's stop
Bug: 136627142
Test: atest -c \
FrameworksServicesTests:ActivityManagerTest#testServiceUnbindAndKilling
Change-Id: I47f6bc5f384379c8e34b67ff42a4916c26ba00cc
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
<option name="test-file-name" value="JobTestApp.apk" />
|
||||
<option name="test-file-name" value="ConnTestApp.apk" />
|
||||
<option name="test-file-name" value="SuspendTestApp.apk" />
|
||||
<option name="test-file-name" value="SimpleServiceTestApp.apk" />
|
||||
</target_preparer>
|
||||
|
||||
<option name="test-tag" value="FrameworksServicesTests" />
|
||||
|
||||
@@ -19,20 +19,34 @@ package com.android.server.am;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RecentTaskInfo;
|
||||
import android.app.IActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Tests for {@link ActivityManager}.
|
||||
@@ -43,12 +57,22 @@ import java.util.List;
|
||||
@FlakyTest(detail = "Promote to presubmit if stable")
|
||||
@Presubmit
|
||||
public class ActivityManagerTest {
|
||||
private static final String TAG = "ActivityManagerTest";
|
||||
|
||||
private static final String TEST_APP = "com.android.servicestests.apps.simpleservicetestapp";
|
||||
private static final String TEST_CLASS = TEST_APP + ".SimpleService";
|
||||
private static final int TEST_LOOPS = 100;
|
||||
private static final long AWAIT_TIMEOUT = 2000;
|
||||
private static final long CHECK_INTERVAL = 100;
|
||||
|
||||
private IActivityManager mService;
|
||||
private IRemoteCallback mCallback;
|
||||
private Context mContext;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mService = ActivityManager.getService();
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -72,4 +96,112 @@ public class ActivityManagerTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceUnbindAndKilling() {
|
||||
for (int i = TEST_LOOPS; i > 0; i--) {
|
||||
runOnce(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void runOnce(long yieldDuration) {
|
||||
final PackageManager pm = mContext.getPackageManager();
|
||||
int uid = 0;
|
||||
try {
|
||||
uid = pm.getPackageUid(TEST_APP, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName(TEST_APP, TEST_CLASS);
|
||||
|
||||
// Create a service connection with auto creation.
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
final MyServiceConnection autoConnection = new MyServiceConnection(latch);
|
||||
mContext.bindService(intent, autoConnection, Context.BIND_AUTO_CREATE);
|
||||
try {
|
||||
assertTrue("Timeout to bind to service " + intent.getComponent(),
|
||||
latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException e) {
|
||||
fail("Unable to bind to service " + intent.getComponent());
|
||||
}
|
||||
|
||||
// Create a service connection without any flags.
|
||||
intent = new Intent();
|
||||
intent.setClassName(TEST_APP, TEST_CLASS);
|
||||
latch = new CountDownLatch(1);
|
||||
MyServiceConnection otherConnection = new MyServiceConnection(latch);
|
||||
mContext.bindService(intent, otherConnection, 0);
|
||||
try {
|
||||
assertTrue("Timeout to bind to service " + intent.getComponent(),
|
||||
latch.await(AWAIT_TIMEOUT, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException e) {
|
||||
fail("Unable to bind to service " + intent.getComponent());
|
||||
}
|
||||
|
||||
// Inform the remote process to kill itself
|
||||
try {
|
||||
mCallback.sendResult(null);
|
||||
// It's basically a test for race condition, we expect the bringDownServiceLocked()
|
||||
// would find out the hosting process is dead - to do this, technically we should
|
||||
// do killing and unbinding simultaneously; but in reality, the killing would take
|
||||
// a little while, before the signal really kills it; so we do it in the same thread,
|
||||
// and even wait a while after sending killing signal.
|
||||
Thread.sleep(yieldDuration);
|
||||
} catch (RemoteException | InterruptedException e) {
|
||||
fail("Unable to kill the process");
|
||||
}
|
||||
// Now unbind that auto connection, this should be equivalent to stopService
|
||||
mContext.unbindService(autoConnection);
|
||||
|
||||
// Now we don't expect the system_server crashes.
|
||||
|
||||
// Wait for the target process dies
|
||||
long total = 0;
|
||||
for (; total < AWAIT_TIMEOUT; total += CHECK_INTERVAL) {
|
||||
try {
|
||||
if (!targetPackageIsRunning(mContext, uid)) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(CHECK_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
assertTrue("Timeout to wait for the target package dies", total < AWAIT_TIMEOUT);
|
||||
mCallback = null;
|
||||
}
|
||||
|
||||
private boolean targetPackageIsRunning(Context context, int uid) {
|
||||
final String result = runShellCommand(
|
||||
String.format("cmd activity get-uid-state %d", uid));
|
||||
return !result.contains("(NONEXISTENT)");
|
||||
}
|
||||
|
||||
private static String runShellCommand(String cmd) {
|
||||
try {
|
||||
return UiDevice.getInstance(
|
||||
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private class MyServiceConnection implements ServiceConnection {
|
||||
private CountDownLatch mLatch;
|
||||
|
||||
MyServiceConnection(CountDownLatch latch) {
|
||||
this.mLatch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mCallback = IRemoteCallback.Stub.asInterface(service);
|
||||
mLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
android_test_helper_app {
|
||||
name: "SimpleServiceTestApp",
|
||||
|
||||
test_suites: ["device-tests"],
|
||||
|
||||
srcs: ["**/*.java"],
|
||||
|
||||
platform_apis: true,
|
||||
certificate: "platform",
|
||||
dex_preopt: {
|
||||
enabled: false,
|
||||
},
|
||||
optimize: {
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.servicestests.apps.simpleservicetestapp">
|
||||
|
||||
<application>
|
||||
<service android:name=".SimpleService"
|
||||
android:exported="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.servicestests.apps.simpleservicetestapp;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Process;
|
||||
|
||||
public class SimpleService extends Service {
|
||||
private final IRemoteCallback.Stub mBinder = new IRemoteCallback.Stub() {
|
||||
@Override
|
||||
public void sendResult(Bundle bundle) {
|
||||
Process.killProcess(Process.myPid());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user