1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.appsecurity.cts;
18 
19 import com.android.tradefed.device.DeviceNotAvailableException;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.log.LogUtil.CLog;
22 import com.android.tradefed.testtype.ITestInformationReceiver;
23 import com.android.tradefed.util.CommandResult;
24 import com.android.tradefed.util.CommandStatus;
25 
26 import org.junit.rules.TestRule;
27 import org.junit.runner.Description;
28 import org.junit.runners.model.Statement;
29 
30 /**
31  * Checks that there are no unexpected reboots during a test. Before each expected reboot call
32  * {@link #increaseExpectedBootCountDifference(int)}
33  */
34 public class BootCountTrackerRule implements TestRule {
35 
36     private final ITestInformationReceiver mTestInformationReceiver;
37     private int mExpectedBootCountDifference;
38 
BootCountTrackerRule( ITestInformationReceiver testInformationReceiver, int expectedBootCountDifference)39     public BootCountTrackerRule(
40             ITestInformationReceiver testInformationReceiver, int expectedBootCountDifference) {
41         mTestInformationReceiver = testInformationReceiver;
42         mExpectedBootCountDifference = expectedBootCountDifference;
43 
44     }
45 
46     @Override
apply(Statement base, Description description)47     public Statement apply(Statement base, Description description) {
48         return new Statement() {
49             @Override
50             public void evaluate() throws Throwable {
51                 int preTestBootCount = getBootCount();
52                 try {
53                     base.evaluate();
54                 } catch (Exception error) {
55                     int postTestBootCount = getBootCount();
56                     int bootDifference = postTestBootCount - preTestBootCount;
57                     if (preTestBootCount >= 0 && postTestBootCount >= 0
58                             && bootDifference > mExpectedBootCountDifference) {
59                         throw new AssertionError(
60                                 "Boot-count increased more than expected at time of failure. "
61                                         + "Expected: "
62                                         + mExpectedBootCountDifference + " Actual: "
63                                         + bootDifference
64                                         + " An unexpected reboot may be the cause of failure:",
65                                 error);
66                     }
67                     throw error;
68                 }
69             }
70         };
71     }
72 
73     public void increaseExpectedBootCountDifference(int increment) {
74         mExpectedBootCountDifference += increment;
75     }
76 
77     private ITestDevice getDevice() {
78         return mTestInformationReceiver.getTestInformation().getDevice();
79     }
80 
81     /** Parses the boot count global setting. Returns -1 if it could not be parsed. */
82     private int getBootCount() throws DeviceNotAvailableException {
83         CommandResult result = getDevice().executeShellV2Command("settings get global boot_count");
84         if (result.getStatus() != CommandStatus.SUCCESS) {
85             CLog.w("Failed to get boot count. Status: %s, Exit code: %d, Error: %s",
86                     result.getStatus(), result.getExitCode(), result.getStderr());
87             return -1;
88         }
89         try {
90             return Integer.parseInt(result.getStdout().trim());
91         } catch (NumberFormatException e) {
92             CLog.w("Couldn't parse boot count.", e);
93             return -1;
94         }
95 
96     }
97 }
98