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:
Jing Ji
2019-10-18 11:33:24 -07:00
parent 794318432f
commit 28611d3e3f
5 changed files with 225 additions and 0 deletions

View File

@@ -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" />

View File

@@ -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) {
}
}
}

View File

@@ -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,
},
}

View File

@@ -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>

View File

@@ -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;
}
}