Add Power Policy Test Cases into CtsCarHostTestCases
- DeviceTestUtilBase and PowerPolicyTestUtil classes provide
functionalities to wrap up various adb shell commands.
- PowerPOlicyTestResult, TestResultTable and PowerPolicyTestAnalyzer
provide functionality to analyze and examine the test results from the
device
- PowerPolicyHostTest perform each test case and integrate with CTS.
Bug: 180056977
Test: atest CtsCarHostTestCases
Change-Id: I2d7d548f95e61e3e8228a3329e0f2d169288a27f
diff --git a/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java b/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java
new file mode 100644
index 0000000..da4bf93
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/PowerPolicyHostTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2021 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.car.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.cts.powerpolicy.PowerPolicyTestAnalyzer;
+import android.car.cts.powerpolicy.PowerPolicyTestResult;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class PowerPolicyHostTest extends CarHostJUnit4TestCase {
+ private static final String ANDROID_CLIENT_PKG = "android.car.cts.app";
+ private static final String ANDROID_CLIENT_ACTIVITY = ANDROID_CLIENT_PKG
+ + "/.PowerPolicyTestActivity";
+ private static final String SHELL_CMD_HEADER = "am start -n " + ANDROID_CLIENT_ACTIVITY;
+ private static final String TESTCASE_CMD_HEADER = SHELL_CMD_HEADER
+ + " --es \"powerpolicy\" \"TestCase%d,%s\"";
+ private static final String POWER_POLICY_TEST_RESULT_HEADER = "PowerPolicyTestClientResult";
+
+ private static final int MAX_TEST_CASES = 5;
+ private static final long LAUNCH_BUFFER_TIME_MS = 1_000L;
+
+ private final PowerPolicyTestAnalyzer mTestAnalyzer;
+
+ public PowerPolicyHostTest() {
+ mTestAnalyzer = new PowerPolicyTestAnalyzer(this);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ startAndroidClient();
+ makeSureAndroidClientRunning(ANDROID_CLIENT_PKG);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ killAndroidClient(ANDROID_CLIENT_PKG);
+ }
+
+ @Test
+ public void testDefaultPowerPolicyStateMachine() throws Exception {
+ boolean status = true;
+ // create expected test result
+ PowerPolicyTestResult testResult = startTestCase(1);
+
+ // populate the expected test result here.
+ testResult.addCriteria("dumpstate", "6", null);
+
+ // clear the device to the ON state
+ rebootDevice();
+
+ // execute the test sequence
+ dumpPowerState(testResult.getTestcaseNo());
+
+ // snapshot the test result
+ endTestCase(testResult);
+
+ //TODO (b/183449315): assign the return to the status variable
+ testResult.checkTestStatus();
+
+ assertWithMessage("testDefaultPowerPolicyStateMachine").that(status).isTrue();
+ }
+
+ @Test
+ public void testPowerPolicyChange() throws Exception {
+ boolean status = true;
+ // create expected test result
+ PowerPolicyTestResult testResult = startTestCase(2);
+
+ // populate the expected test result here.
+ testResult.addCriteria("dumpstate", "6", null);
+
+ // execute the test sequence
+ dumpPowerPolicy(testResult.getTestcaseNo());
+
+ // snapshot the test result
+ endTestCase(testResult);
+
+ //TODO (b/183449315): assign the return to the status variable
+ testResult.checkTestStatus();
+
+ assertWithMessage("testPowerPolicyChange").that(status).isTrue();
+ }
+
+ @Test
+ public void testPowerPolicySilentMode() throws Exception {
+ boolean status = true;
+ // create expected test result
+ PowerPolicyTestResult testResult = startTestCase(3);
+
+ // populate the expected test result here.
+ testResult.addCriteria("dumpstate", "2", null);
+
+ // execute the test sequence
+ rebootForcedSilent();
+ dumpPowerState(testResult.getTestcaseNo());
+
+ // snapshot the test result
+ endTestCase(testResult);
+
+ //TODO (b/183449315): assign the return to the status variable
+ testResult.checkTestStatus();
+
+ assertWithMessage("testPowerPolicySilentMode").that(status).isTrue();
+ }
+
+ @Test
+ public void testPowerPolicySuspendToRAM() throws Exception {
+ boolean status = true;
+ // create expected test result
+ PowerPolicyTestResult testResult = startTestCase(4);
+
+ // populate the expected test result here.
+ testResult.addCriteria("dumpstate", "6", null);
+
+ // reboot the device to clear it to ON state
+ rebootDevice();
+
+ // execute the test sequence
+ dumpPowerState(testResult.getTestcaseNo());
+
+ // snapshot the test result
+ endTestCase(testResult);
+
+ //TODO (b/183449315): assign the return to the status variable
+ testResult.checkTestStatus();
+
+ assertWithMessage("testPowerPolicySuspendToRAM").that(status).isTrue();
+ }
+
+ @Test
+ public void testNewPowerPolicy() throws Exception {
+ boolean status = true;
+ // create expected test result
+ PowerPolicyTestResult testResult = startTestCase(5);
+
+ // populate the expected test result here.
+ testResult.addCriteria("dumpstate", "6", null);
+
+ // execute the test sequence
+ // create a fake power policy for now to pass the test
+ definePowerPolicy("123", "0 2 4", "1 3 5");
+ applyPowerPolicy("123");
+ dumpPowerPolicy(testResult.getTestcaseNo());
+
+ // snapshot the test result
+ endTestCase(testResult);
+
+ //TODO (b/183449315): assign the return to the status variable
+ testResult.checkTestStatus();
+
+ assertWithMessage("testNewPowerPolicy").that(status).isTrue();
+ }
+
+ public String fetchActivityDumpsys() throws Exception {
+ return executeCommand("shell dumpsys activity %s | grep %s",
+ ANDROID_CLIENT_ACTIVITY, POWER_POLICY_TEST_RESULT_HEADER);
+ }
+
+ private void startAndroidClient() throws Exception {
+ executeCommand(SHELL_CMD_HEADER);
+ }
+
+ private PowerPolicyTestResult startTestCase(int caseNo)
+ throws Exception {
+ PowerPolicyTestResult testResult;
+
+ if (caseNo < 1 || caseNo > MAX_TEST_CASES) {
+ throw new Exception(String.format("invalid test case number %d", caseNo));
+ }
+
+ testResult = new PowerPolicyTestResult(caseNo, mTestAnalyzer);
+ testResult.takeStartSnapshot();
+ executeCommand(TESTCASE_CMD_HEADER, caseNo, "start");
+ return testResult;
+ }
+
+ private void endTestCase(PowerPolicyTestResult testResult) throws Exception {
+ executeCommand(TESTCASE_CMD_HEADER, testResult.getTestcaseNo(), "end");
+ testResult.takeEndSnapshot();
+ }
+
+ private void rebootDevice() throws Exception {
+ executeCommand("svc power reboot");
+ waitForDeviceAvailable();
+ }
+
+ private void rebootForcedSilent() throws Exception {
+ executeCommand("reboot forcedsilent");
+ waitForDeviceAvailable();
+ }
+
+ private void dumpPowerState(int caseNo) throws Exception {
+ executeCommand(TESTCASE_CMD_HEADER, caseNo, "dumpstate");
+ }
+
+ private void dumpPowerPolicy(int caseNo) throws Exception {
+ executeCommand(TESTCASE_CMD_HEADER, caseNo, "dumppolicy");
+ }
+
+ private void definePowerPolicy(String policyId, String enabledComps,
+ String disabledComps) throws Exception {
+ executeCommand("cmd car_service define-power-policy %s --enable %s --disable %s",
+ policyId, enabledComps, disabledComps);
+ }
+
+ private void applyPowerPolicy(String policyId) throws Exception {
+ executeCommand("cmd car_service apply-power-policy %s", policyId);
+ }
+
+ private void waitForDeviceAvailable() throws Exception {
+ // ITestDevice.waitForDeviceAvailable has default boot timeout
+ // Therefore, trying twice is sufficient
+ try {
+ getDevice().waitForDeviceAvailable();
+ } catch (Exception e) {
+ CLog.w("device is not available, trying one more time");
+ getDevice().waitForDeviceAvailable();
+ }
+ }
+
+ private void killAndroidClient(String clientPkgName) throws Exception {
+ executeCommand("am force-stop %s", clientPkgName);
+ }
+
+ private boolean makeSureAndroidClientRunning(String clientPkgName) {
+ int trialCount = 5;
+ while (trialCount > 0) {
+ RunUtil.getDefault().sleep(LAUNCH_BUFFER_TIME_MS);
+ if (checkAndroidClientRunning(clientPkgName)) {
+ return true;
+ }
+ trialCount--;
+ }
+ return false;
+ }
+
+ private boolean checkAndroidClientRunning(String clientPkgName) {
+ String[] pids = getPidsOfProcess(clientPkgName);
+ return pids.length == 1;
+ }
+
+ private String[] getPidsOfProcess(String... processNames) {
+ String output;
+ String param = String.join(" ", processNames);
+ try {
+ output = executeCommand("pidof %s", param).trim();
+ } catch (Exception e) {
+ CLog.w("Cannot get pids of %s", param);
+ return new String[0];
+ }
+ if (output.isEmpty()) {
+ return new String[0];
+ }
+ String[] tokens = output.split("\\s+");
+ return tokens;
+ }
+}
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestAnalyzer.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestAnalyzer.java
new file mode 100644
index 0000000..c304fd1
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestAnalyzer.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2021 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.car.cts.powerpolicy;
+
+import android.car.cts.PowerPolicyHostTest;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class PowerPolicyTestAnalyzer {
+ private final PowerPolicyHostTest mHostTest;
+
+ public PowerPolicyTestAnalyzer(PowerPolicyHostTest hostTest) {
+ mHostTest = hostTest;
+ }
+
+ /**
+ * Compares results.
+ */
+ public boolean checkIfTestResultMatch(TestResultTable result1, TestResultTable result2) {
+ int size = result1.size();
+ if (size != result2.size()) {
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ if (!result1.get(i).equals(result2.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public TestResultTable snapshotTestResult() throws Exception {
+ TestResultTable snapshot = new TestResultTable();
+ String shellOutput = mHostTest.fetchActivityDumpsys();
+ String[] lines = shellOutput.split("\n");
+ for (String line : lines) {
+ String[] tokens = line.split(",");
+ if (tokens.length != 3 || tokens.length != 4) {
+ CLog.w("Malformatted power policy test result: %s", line);
+ return null;
+ }
+ if (tokens.length == 3) {
+ snapshot.add(tokens[0], tokens[1], tokens[2], null);
+ } else {
+ snapshot.add(tokens[0], tokens[1], tokens[2], tokens[3]);
+ }
+ }
+ return snapshot;
+ }
+
+ /**
+ * Subtract the common front TestResultEntry items.
+ */
+ public TestResultTable getDiff(TestResultTable result1, TestResultTable result2) {
+ TestResultTable diff;
+
+ if (result1 != null && result2 != null) {
+ TestResultTable longResult = result1;
+ TestResultTable shortResult = result2;
+ if (longResult.size() < shortResult.size()) {
+ longResult = result2;
+ shortResult = result1;
+ }
+ int shortSize = shortResult.size();
+ int longSize = longResult.size();
+ int idx = 0;
+ diff = new TestResultTable();
+ for (; idx < shortSize; idx++) {
+ if (!shortResult.get(idx).equals(longResult.get(idx))) {
+ break;
+ }
+ }
+ for (; idx < longSize; idx++) {
+ diff.add(longResult.get(idx));
+ }
+ } else if (result1 == null) {
+ diff = result2;
+ } else {
+ diff = result1;
+ }
+ return diff;
+ }
+
+ public TestResultTable getTailDiff(TestResultTable result1, TestResultTable result2) {
+ TestResultTable diff = null;
+
+ if (result1 != null && result2 != null) {
+ TestResultTable longResult = result1;
+ TestResultTable shortResult = result2;
+ if (longResult.size() < shortResult.size()) {
+ longResult = result2;
+ shortResult = result1;
+ }
+ int shortSize = shortResult.size();
+ int longSize = longResult.size();
+ diff = new TestResultTable();
+ for (int idx = shortSize; idx < longSize; idx++) {
+ diff.add(longResult.get(idx));
+ }
+ } else if (result1 == null) {
+ diff = result2;
+ } else {
+ diff = result1;
+ }
+ return diff;
+ }
+}
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestResult.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestResult.java
new file mode 100644
index 0000000..5337288
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyTestResult.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 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.car.cts.powerpolicy;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class PowerPolicyTestResult {
+ private static final String TESTCASE_NAME_HEADER = "Testcase";
+ private final PowerPolicyTestAnalyzer mTestAnalyzer;
+ private final TestResultTable mExpected = new TestResultTable();
+ private TestResultTable mStartSnapshot;
+ private TestResultTable mEndSnapshot;
+ private int mTestcaseNo;
+ private String mTestcaseName;
+
+ public PowerPolicyTestResult(int caseNo, PowerPolicyTestAnalyzer testAnalyzer) {
+ mTestcaseNo = caseNo;
+ mTestcaseName = TESTCASE_NAME_HEADER + caseNo;
+ mTestAnalyzer = testAnalyzer;
+ }
+
+ public int getTestcaseNo() {
+ return mTestcaseNo;
+ }
+
+ /**
+ * Adds test passing criteria.
+ *
+ * <p> For multiple criteria, the order of adding them into this object matters.
+ */
+ public void addCriteria(String action, String powerState, String data) {
+ mExpected.add(mTestcaseName, action, powerState, data);
+ }
+
+ public void takeStartSnapshot() throws Exception {
+ if (mStartSnapshot != null) {
+ return;
+ }
+ mStartSnapshot = mTestAnalyzer.snapshotTestResult();
+ }
+
+ public void takeEndSnapshot() throws Exception {
+ if (mEndSnapshot != null) {
+ return;
+ }
+ mEndSnapshot = mTestAnalyzer.snapshotTestResult();
+ }
+
+ public boolean checkTestStatus() {
+ TestResultTable testResult = null;
+ if (mStartSnapshot == null || mEndSnapshot == null) {
+ CLog.e("start snapshot or end snapshot is null");
+ return false;
+ }
+
+ testResult = mTestAnalyzer.getTailDiff(mStartSnapshot, mEndSnapshot);
+ if (testResult == null) {
+ CLog.e("empty test result");
+ return false;
+ }
+
+ return mTestAnalyzer.checkIfTestResultMatch(mExpected, testResult);
+ }
+}
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/TestResultTable.java b/hostsidetests/car/src/android/car/cts/powerpolicy/TestResultTable.java
new file mode 100644
index 0000000..e8efef7
--- /dev/null
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/TestResultTable.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2021 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.car.cts.powerpolicy;
+
+import java.util.ArrayList;
+
+/**
+ * TestResultTable consists of a list of TestResultEntry records
+ *
+ * <p>Each record represents one entry line in the device data file,
+ * {@code /storage/emulated/obb/PowerPolicyData.txt}, which records the power
+ * state and policy behavior.
+ */
+public final class TestResultTable {
+ private final ArrayList<TestResultEntry> mTestResults = new ArrayList<TestResultEntry>();
+
+ public int size() {
+ return mTestResults.size();
+ }
+
+ public TestResultEntry get(int i) throws IndexOutOfBoundsException {
+ return mTestResults.get(i);
+ }
+
+ public void add(TestResultEntry entry) {
+ mTestResults.add(entry);
+ }
+
+ public void add(String testcase, String action, String powerState, String data) {
+ add(new TestResultEntry(testcase, action, powerState, data));
+ }
+
+ static final class TestResultEntry {
+ private final String mTestcase;
+ private final String mAction;
+ private final String mPowerState;
+ private final String mData;
+
+ TestResultEntry(String testcase, String action, String powerState, String data) {
+ mTestcase = testcase;
+ mAction = action;
+ mPowerState = powerState;
+ mData = data;
+ }
+
+ boolean equals(TestResultEntry peerEntry) {
+ if ((mTestcase == null && mTestcase != peerEntry.mTestcase)
+ && (mTestcase != null && !mTestcase.equals(peerEntry.mTestcase))) {
+ return false;
+ }
+ if ((mAction == null && mAction != peerEntry.mAction)
+ && (mAction != null && !mAction.equals(peerEntry.mAction))) {
+ return false;
+ }
+ if ((mPowerState == null && mPowerState != peerEntry.mPowerState)
+ && (mPowerState != null && !mPowerState.equals(peerEntry.mPowerState))) {
+ return false;
+ }
+ if ((mData == null && mData != peerEntry.mData)
+ && (mData != null && !mData.equals(peerEntry.mData))) {
+ return false;
+ }
+ return true;
+ }
+ }
+}