Merge "Add tests for validating cpu freq times in batterystats dump."

This commit is contained in:
Sudheer Shanka
2017-11-28 04:52:44 +00:00
committed by Android (Google) Code Review
9 changed files with 647 additions and 0 deletions

View File

@@ -68,6 +68,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.TEST_GRANTED" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
<uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />

View File

@@ -16,6 +16,7 @@
<configuration description="Runs Frameworks Core Tests.">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="FrameworksCoreTests.apk" />
<option name="test-file-name" value="BstatsTestApp.apk" />
</target_preparer>
<option name="test-suite-tag" value="apct" />

View File

@@ -0,0 +1,32 @@
# Copyright (C) 2017 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.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := BstatsTestApp
LOCAL_CERTIFICATE := platform
LOCAL_DEX_PREOPT := false
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.coretests.apps.bstatstestapp">
<application>
<activity android:name=".TestActivity"
android:exported="true" />
<service android:name=".IsolatedTestService"
android:exported="true"
android:isolatedProcess="true" />
</application>
</manifest>

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2017 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.coretests.apps.bstatstestapp;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
public class IsolatedTestService extends Service {
private static final String TAG = IsolatedTestService.class.getName();
@Override
public void onCreate() {
Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
}
@Override
public IBinder onBind(Intent intent) {
return mReceiver.asBinder();
}
private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
@Override
public void doSomeWork(int durationMs) {
final long endTime = SystemClock.uptimeMillis() + durationMs;
double x;
double y;
double z;
while (SystemClock.uptimeMillis() <= endTime) {
x = 0.02;
x *= 1000;
y = x % 5;
z = Math.sqrt(y / 100);
}
};
@Override
public void finishHost() {
stopSelf();
}
};
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2017 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.coretests.apps.bstatstestapp;
import com.android.frameworks.coretests.aidl.ICmdCallback;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
public class TestActivity extends Activity {
private static final String TAG = TestActivity.class.getName();
private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Log.d(TAG, "onCreate called");
notifyActivityLaunched();
}
private void notifyActivityLaunched() {
if (getIntent() == null) {
return;
}
final Bundle extras = getIntent().getExtras();
if (extras == null) {
return;
}
final ICmdCallback callback = ICmdCallback.Stub.asInterface(
extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
try {
callback.onActivityLaunched(mReceiver.asBinder());
} catch (RemoteException e) {
Log.e(TAG, "Error occured while notifying the test: " + e);
}
}
@Override
public void finish() {
super.finish();
Log.d(TAG, "finish called");
}
private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
@Override
public void doSomeWork(int durationMs) {
final long endTime = SystemClock.uptimeMillis() + durationMs;
double x;
double y;
double z;
while (SystemClock.uptimeMillis() <= endTime) {
x = 0.02;
x *= 1000;
y = x % 5;
z = Math.sqrt(y / 100);
}
};
@Override
public void finishHost() {
finish();
}
};
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2017 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.frameworks.coretests.aidl;
interface ICmdCallback {
void onActivityLaunched(IBinder receiver);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2017 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.frameworks.coretests.aidl;
interface ICmdReceiver {
void doSomeWork(int durationMs);
void finishHost();
}

View File

@@ -0,0 +1,399 @@
/*
* Copyright (C) 2017 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.internal.os;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
import static org.junit.Assume.assumeTrue;
import com.android.frameworks.coretests.aidl.ICmdCallback;
import com.android.frameworks.coretests.aidl.ICmdReceiver;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BstatsCpuTimesValidationTest {
private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
private static final int GENERAL_TIMEOUT_MS = 1000;
private static final int GENERAL_INTERVAL_MS = 100;
private static final int WORK_DURATION_MS = 2000;
private static Context sContext;
private static UiDevice sUiDevice;
private static int sTestPkgUid;
private static boolean sCpuFreqTimesAvailable;
@BeforeClass
public static void setupOnce() throws Exception {
sContext = InstrumentationRegistry.getContext();
sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
}
// Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
// kernel node is available.
private static boolean cpuFreqTimesAvailable() throws Exception {
final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
return cpuTimes != null;
}
@Test
public void testCpuFreqTimes() throws Exception {
assumeTrue(sCpuFreqTimesAvailable);
batteryOnScreenOn();
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
assertNull("Initial snapshot should be null", initialSnapshot);
doSomeWork();
forceStop();
final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
assertCpuTimesValid(cpuTimesMs);
long actualCpuTimeMs = 0;
for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
actualCpuTimeMs += cpuTimesMs[i];
}
assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
batteryOffScreenOn();
}
@Test
public void testCpuFreqTimes_screenOff() throws Exception {
assumeTrue(sCpuFreqTimesAvailable);
batteryOnScreenOff();
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
assertNull("Initial snapshot should be null", initialSnapshot);
doSomeWork();
forceStop();
final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
assertCpuTimesValid(cpuTimesMs);
long actualTotalCpuTimeMs = 0;
for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
actualTotalCpuTimeMs += cpuTimesMs[i];
}
assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualTotalCpuTimeMs);
long actualScreenOffCpuTimeMs = 0;
for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
actualScreenOffCpuTimeMs += cpuTimesMs[i];
}
assertApproximateValue("Incorrect screen-off cpu time",
WORK_DURATION_MS, actualScreenOffCpuTimeMs);
batteryOffScreenOn();
}
@Test
public void testCpuFreqTimes_isolatedProcess() throws Exception {
assumeTrue(sCpuFreqTimesAvailable);
batteryOnScreenOn();
forceStop();
resetBatteryStats();
final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
assertNull("Initial snapshot should be null", initialSnapshot);
doSomeWorkInIsolatedProcess();
forceStop();
final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid);
assertCpuTimesValid(cpuTimesMs);
long actualCpuTimeMs = 0;
for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
actualCpuTimeMs += cpuTimesMs[i];
}
assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs);
batteryOffScreenOn();
}
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
if (cpuTimes[i] < 0) {
fail("Malformed cpu times data (-ve values): " + Arrays.toString(cpuTimes));
}
}
final int numFreqs = cpuTimes.length / 2;
for (int i = 0; i < numFreqs; ++i) {
if (cpuTimes[i] < cpuTimes[numFreqs + i]) {
fail("Malformed cpu times data (screen-off > total)" + Arrays.toString(cpuTimes));
}
}
}
private void assertApproximateValue(String errorPrefix, long expectedValue, long actualValue) {
assertValueRange(errorPrefix, actualValue, expectedValue * 0.5, expectedValue * 1.5);
}
private void assertValueRange(String errorPrefix,
long actualvalue, double minValue, double maxValue) {
final String errorMsg = String.format(errorPrefix + "; actual=%s; min=%s; max=%s",
actualvalue, minValue, maxValue);
assertTrue(errorMsg, actualvalue < maxValue);
assertTrue(errorMsg, actualvalue > minValue);
}
private void doSomeWork() throws Exception {
final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startActivity());
receiver.doSomeWork(WORK_DURATION_MS);
receiver.finishHost();
}
private void doSomeWorkInIsolatedProcess() throws Exception {
final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
receiver.doSomeWork(WORK_DURATION_MS);
receiver.finishHost();
}
private IBinder startIsolatedService() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final IBinder[] binders = new IBinder[1];
final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binders[0] = service;
latch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
final Intent launchIntent = new Intent()
.setComponent(new ComponentName(TEST_PKG, ISOLATED_TEST_SERVICE));
sContext.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE
| Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_FOREGROUND);
if (latch.await(START_ISOLATED_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
if (binders[0] == null) {
fail("Receiver binder should not be null");
}
return binders[0];
} else {
fail("Timed out waiting for the isolated test service to start");
}
return null;
}
private IBinder startActivity() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final Intent launchIntent = new Intent()
.setComponent(new ComponentName(TEST_PKG, TEST_ACTIVITY));
final Bundle extras = new Bundle();
final IBinder[] binders = new IBinder[1];
extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
@Override
public void onActivityLaunched(IBinder receiver) {
binders[0] = receiver;
latch.countDown();
}
});
launchIntent.putExtras(extras);
sContext.startActivity(launchIntent);
if (latch.await(START_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
if (binders[0] == null) {
fail("Receiver binder should not be null");
}
return binders[0];
} else {
fail("Timed out waiting for the test activity to start; testUid=" + sTestPkgUid);
}
return null;
}
private static long[] getAllCpuFreqTimes(int uid) throws Exception {
final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
final Matcher matcher = pattern.matcher(checkinDump);
if (!matcher.find()) {
return null;
}
final String[] uidTimesStr = matcher.group(1).split(",");
final int freqCount = Integer.parseInt(uidTimesStr[0]);
if (uidTimesStr.length != (2 * freqCount + 1)) {
fail("Malformed data: " + Arrays.toString(uidTimesStr));
}
final long[] cpuTimes = new long[freqCount * 2];
for (int i = 0; i < cpuTimes.length; ++i) {
cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
}
return cpuTimes;
}
private void resetBatteryStats() throws Exception {
executeCmd("dumpsys batterystats --reset");
}
private void batteryOnScreenOn() throws Exception {
batteryOn();
screenOn();
}
private void batteryOnScreenOff() throws Exception {
batteryOn();
screenoff();
}
private void batteryOffScreenOn() throws Exception {
batteryOff();
screenOn();
}
private void batteryOn() throws Exception {
executeCmd("dumpsys battery unplug");
assertBatteryState(false);
}
private void batteryOff() throws Exception {
executeCmd("dumpsys battery reset");
assertBatteryState(true);
}
private void screenOn() throws Exception {
executeCmd("input keyevent KEYCODE_WAKEUP");
executeCmd("wm dismiss-keyguard");
assertKeyguardUnLocked();
assertScreenInteractive(true);
}
private void screenoff() throws Exception {
executeCmd("input keyevent KEYCODE_SLEEP");
assertScreenInteractive(false);
}
private void forceStop() throws Exception {
executeCmd("cmd activity force-stop " + TEST_PKG);
assertUidState(PROCESS_STATE_NONEXISTENT);
}
private void assertUidState(int state) throws Exception {
final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
assertEquals(state, uidState);
}
private void assertKeyguardUnLocked() {
final KeyguardManager keyguardManager =
(KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
assertDelayedCondition("Keyguard should be unlocked",
() -> !keyguardManager.isKeyguardLocked());
}
private void assertScreenInteractive(boolean interactive) {
final PowerManager powerManager =
(PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
assertDelayedCondition("Unexpected screen interactive state",
() -> interactive == powerManager.isInteractive());
}
private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
while (SystemClock.uptimeMillis() <= endTime) {
if (condition.isTrue()) {
return;
}
SystemClock.sleep(GENERAL_INTERVAL_MS);
}
if (!condition.isTrue()) {
fail(errorMsg);
}
}
private void assertBatteryState(boolean pluggedIn) throws Exception {
final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
}
if (isDevicePluggedIn() != pluggedIn) {
fail("Timed out waiting for the plugged-in state to change,"
+ " expected pluggedIn: " + pluggedIn);
}
}
private boolean isDevicePluggedIn() {
final Intent batteryIntent = sContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
}
private String executeCmd(String cmd) throws Exception {
final String result = sUiDevice.executeShellCommand(cmd).trim();
Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
return result;
}
private static String executeCmdSilent(String cmd) throws Exception {
return sUiDevice.executeShellCommand(cmd).trim();
}
private interface ExpectedCondition {
boolean isTrue();
}
}