1 /*
2  * Copyright (C) 2020 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 package com.android.tests.sysfs;
17 
18 import static org.junit.Assert.assertNotNull;
19 import static org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
24 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
25 import com.android.tradefed.util.FileUtil;
26 import com.android.tradefed.util.TargetFileUtils;
27 import com.android.tradefed.util.TargetFileUtils.FilePermission;
28 import com.google.common.base.Strings;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.UUID;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 /* A test to check check sysfs files. */
40 @RunWith(DeviceJUnit4ClassRunner.class)
41 public class KernelApiSysfsTest extends BaseHostJUnit4Test {
42     /* Check for the existence of required files in /sys/class/android_usb. */
43     @Test
testAndroidUSB()44     public void testAndroidUSB() throws Exception {
45         String state = "/sys/class/android_usb/android0/state";
46         assertTrue(TargetFileUtils.isReadOnly(state, getDevice()));
47         String content = getDevice().pullFileContents(state).trim();
48         HashSet<String> possibles =
49                 new HashSet<>(Arrays.asList("DISCONNECTED", "CONNECTED", "CONFIGURED"));
50         assertTrue(possibles.contains(content));
51     }
52 
53     /**
54      * Check the format of cpu online file.
55      *
56      * Confirm /sys/devices/system/cpu/online exists and is read-only.
57      * Parse contents to ensure it is a comma-separated series of ranges
58      * (%d-%d) and/or integers.
59      */
60     @Test
testCpuOnlineFormat()61     public void testCpuOnlineFormat() throws Exception {
62         String filePath = "/sys/devices/system/cpu/online";
63         assertTrue(TargetFileUtils.isReadOnly(filePath, getDevice()));
64         String content = getDevice().pullFileContents(filePath).trim();
65         assertTrue(content.matches("(\\d+(-\\d+)?)(,\\d+(-\\d+)?)*"));
66     }
67 
isReadOnlyAndIntegerContent(String filePath)68     private void isReadOnlyAndIntegerContent(String filePath) throws Exception {
69         assertTrue("Failed readonly check of " + filePath,
70                 TargetFileUtils.isReadOnly(filePath, getDevice()));
71         String content = getDevice().pullFileContents(filePath).trim();
72         // Confirm the content is integer.
73         Integer.parseInt(content);
74     }
75 
isReadWriteAndIntegerContent(String filePath)76     private void isReadWriteAndIntegerContent(String filePath) throws Exception {
77         assertTrue("Failed readwrite check of " + filePath,
78                 TargetFileUtils.isReadWriteOnly(filePath, getDevice()));
79         String content = getDevice().pullFileContents(filePath).trim();
80         // Confirm the content is integer.
81         Integer.parseInt(content);
82     }
83 
84     /**
85      * Check each cpu's scaling_cur_freq, scaling_min_freq, scaling_max_freq,
86      * scaling_available_frequencies, and time_in_state files.
87      */
88     @Test
testPerCpuCpufreq()89     public void testPerCpuCpufreq() throws Exception {
90         String filePath = "/sys/devices/system/cpu/present";
91         assertTrue(TargetFileUtils.isReadOnly(filePath, getDevice()));
92         String presentCpus = getDevice().pullFileContents(filePath).trim();
93         String[] cpuRanges = presentCpus.split(",");
94         List<Integer> cpuList = new ArrayList<>();
95         Pattern p = Pattern.compile("(\\d+)(-\\d+)?");
96         for (String range : cpuRanges) {
97             Matcher m = p.matcher(range);
98             assertTrue("Malformatted range in " + filePath, m.find());
99             int startCpu = Integer.parseInt(m.group(1));
100             int endCpu = startCpu;
101             if (m.group(2) != null)
102                 endCpu = Integer.parseInt(m.group(2).substring(1));
103             for (int i = startCpu; i <= endCpu; i++) {
104                 cpuList.add(i);
105             }
106         }
107 
108         String f;
109         String content;
110         for (int cpu : cpuList) {
111             f = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", cpu);
112             if (getDevice().doesFileExist(f)) {
113                 isReadOnlyAndIntegerContent(f);
114             }
115 
116             f = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_min_freq", cpu);
117             if (getDevice().doesFileExist(f)) {
118                 isReadWriteAndIntegerContent(f);
119             }
120 
121             f = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_max_freq", cpu);
122             if (getDevice().doesFileExist(f)) {
123                 isReadWriteAndIntegerContent(f);
124             }
125 
126             f = String.format(
127                     "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_available_frequencies", cpu);
128             if (getDevice().doesFileExist(f)) {
129                 assertTrue(TargetFileUtils.isReadOnly(f, getDevice()));
130                 content = getDevice().pullFileContents(f).trim();
131                 if (!Strings.isNullOrEmpty(content)) {
132                     String[] availFreqs = content.split(" ");
133                     for (String freq : availFreqs) {
134                         Integer.parseInt(freq);
135                     }
136                 }
137             }
138 
139             f = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state", cpu);
140             if (getDevice().doesFileExist(f)) {
141                 assertTrue(TargetFileUtils.isReadOnly(f, getDevice()));
142                 content = getDevice().pullFileContents(f).trim();
143                 if (!Strings.isNullOrEmpty(content)) {
144                     for (String line : content.split("\\n")) {
145                         String[] values = line.split(" ");
146                         for (String value : values) {
147                             Integer.parseInt(value);
148                         }
149                     }
150                 }
151             }
152         }
153     }
154 
155     /* Check /sys/kernel/wakeup_reasons/last_resume_reason. */
156     @Test
testLastResumeReason()157     public void testLastResumeReason() throws Exception {
158         String filePath = "/sys/kernel/wakeup_reasons/last_resume_reason";
159         assertTrue(TargetFileUtils.isReadOnly(filePath, getDevice()));
160     }
161 
162     /* Check the value of /sys/devices/system/cpu/kernel_max. */
163     @Test
testKernelMax()164     public void testKernelMax() throws Exception {
165         String filePath = "/sys/devices/system/cpu/kernel_max";
166         isReadOnlyAndIntegerContent(filePath);
167     }
168 
findFiles(String dir, String nameFilter)169     private String[] findFiles(String dir, String nameFilter) throws Exception {
170         String output = getDevice().executeShellCommand(
171                 String.format("find %s -name \"%s\" -maxdepth 1 -type l", dir, nameFilter));
172         if (output.trim().isEmpty()) {
173             return new String[0];
174         }
175         return output.split("\r?\n");
176     }
177 
178     /* Check for /sys/class/net/?/mtu. */
179     @Test
testNetMTU()180     public void testNetMTU() throws Exception {
181         String[] dirList = findFiles("/sys/class/net", "*");
182         for (String entry : dirList) {
183             isReadWriteAndIntegerContent(entry + "/mtu");
184         }
185     }
186 
187     /* Check that at least one rtc exists with hctosys = 1. */
188     @Test
testRtcHctosys()189     public void testRtcHctosys() throws Exception {
190         String[] rtcList = findFiles("/sys/class/rtc", "rtc*");
191         for (String entry : rtcList) {
192             String content = getDevice().pullFileContents(entry + "/hctosys");
193             if (Strings.isNullOrEmpty(content)) {
194                 continue;
195             }
196             try {
197                 int hctosys = Integer.parseInt(content.trim());
198                 if (hctosys == 1) {
199                     return;
200                 }
201             } catch (NumberFormatException e) {
202                 continue;
203             }
204         }
205         fail("No RTC with hctosys=1 present");
206     }
207 
208     /* Check that locking and unlocking a wake lock works.. */
209     @Test
testWakeLock()210     public void testWakeLock() throws Exception {
211         String wakeLockPath = "/sys/power/wake_lock";
212         String wakeUnLockPath = "/sys/power/wake_unlock";
213         String lockName = "KernelApiSysfsTestWakeLock" + UUID.randomUUID().toString();
214 
215         // Enable wake lock
216         getDevice().executeShellCommand(String.format("echo %s > %s", lockName, wakeLockPath));
217 
218         // Confirm wake lock is enabled
219         String results = getDevice().pullFileContents(wakeLockPath).trim();
220         HashSet<String> activeSources = new HashSet<>(Arrays.asList(results.split(" ")));
221         assertTrue(String.format("active wake lock not reported in %s", wakeLockPath),
222                 activeSources.contains(lockName));
223 
224         // Disable wake lock
225         getDevice().executeShellCommand(String.format("echo %s > %s", lockName, wakeUnLockPath));
226 
227         // Confirm wake lock is no longer enabled
228         results = getDevice().pullFileContents(wakeLockPath).trim();
229         activeSources = new HashSet<>(Arrays.asList(results.split(" ")));
230         assertTrue(String.format("inactive wake lock reported in %s", wakeLockPath),
231                 !activeSources.contains(lockName));
232 
233         results = getDevice().pullFileContents(wakeUnLockPath).trim();
234         activeSources = new HashSet<>(Arrays.asList(results.split(" ")));
235         assertTrue(String.format("inactive wake lock not reported in %s", wakeUnLockPath),
236                 activeSources.contains(lockName));
237     }
238 
239     @Test
testWakeupCount()240     public void testWakeupCount() throws Exception {
241         String filePath = "/sys/power/wakeup_count";
242         assertTrue("Failed readwrite check of " + filePath,
243                 TargetFileUtils.isReadWriteOnly(filePath, getDevice()));
244     }
245 
246     /* /sys/power/state controls the system sleep states. */
247     @Test
testSysPowerState()248     public void testSysPowerState() throws Exception {
249         String filePath = "/sys/power/state";
250         assertTrue("Failed readwrite check of " + filePath,
251                 TargetFileUtils.isReadWriteOnly(filePath, getDevice()));
252         String content = getDevice().pullFileContents(filePath).trim();
253         HashSet<String> allowedStates =
254                 new HashSet<String>(Arrays.asList("freeze", "mem", "disk", "standby"));
255         for (String state : content.split(" ")) {
256             assertTrue(String.format("Invalid system power state: '%s'", state),
257                     allowedStates.contains(state));
258         }
259     }
260 }
261