1#!/usr/bin/env python 2# 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18import gzip 19import logging 20import os 21import re 22import shutil 23import tempfile 24 25from vts.runners.host import asserts 26from vts.runners.host import base_test 27from vts.runners.host import const 28from vts.runners.host import keys 29from vts.runners.host import test_runner 30from vts.utils.python.controllers import android_device 31from vts.utils.python.file import file_utils 32 33 34class VtsKernelConfigTest(base_test.BaseTestClass): 35 """Test case which check config options in /proc/config.gz. 36 37 Attributes: 38 _temp_dir: The temporary directory to which /proc/config.gz is copied. 39 """ 40 41 PROC_FILE_PATH = "/proc/config.gz" 42 KERNEL_CONFIG_FILE_PATH = "vts/testcases/kernel/config/data" 43 SUPPORTED_KERNEL_VERSIONS = ["3.18", "4.4", "4.9"] 44 45 def setUpClass(self): 46 required_params = [ 47 keys.ConfigKeys.IKEY_DATA_FILE_PATH 48 ] 49 self.getUserParams(required_params) 50 self.dut = self.registerController(android_device)[0] 51 self.dut.shell.InvokeTerminal( 52 "KernelConfigTest") # creates a remote shell instance. 53 self.shell = self.dut.shell.KernelConfigTest 54 self._temp_dir = tempfile.mkdtemp() 55 56 def checkKernelVersion(self): 57 """Validate the kernel version of DUT is a valid O kernel version. 58 59 Returns: 60 string, kernel version of device 61 """ 62 cmd = "uname -a" 63 results = self.shell.Execute(cmd) 64 logging.info("Shell command '%s' results: %s", cmd, results) 65 66 match = re.search(r"\d+\.\d+", results[const.STDOUT][0]) 67 if match is None: 68 asserts.fail("Failed to detect kernel version of device.") 69 else: 70 kernel_version = match.group(0) 71 logging.info("Detected kernel version: %s", kernel_version) 72 73 asserts.assertTrue(kernel_version in self.SUPPORTED_KERNEL_VERSIONS, 74 "Detected kernel version '%s' is not one of %s" 75 % (kernel_version, self.SUPPORTED_KERNEL_VERSIONS)) 76 77 return kernel_version 78 79 def checkKernelArch(self, configs): 80 """Find arch of the device kernel. 81 82 Uses the kernel configuration to determine the architecture 83 it is compiled for. 84 85 Args: 86 configs: dict containing device kernel configuration options 87 88 Returns: 89 A string containing the architecture of the device kernel. If 90 the architecture cannot be determined, an empty string is 91 returned. 92 """ 93 94 CONFIG_ARM = "CONFIG_ARM" 95 CONFIG_ARM64 = "CONFIG_ARM64" 96 CONFIG_X86 = "CONFIG_X86" 97 98 if CONFIG_ARM in configs and configs[CONFIG_ARM] == "y": 99 return "arm" 100 elif CONFIG_ARM64 in configs and configs[CONFIG_ARM64] == "y": 101 return "arm64" 102 elif CONFIG_X86 in configs and configs[CONFIG_X86] == "y": 103 return "x86" 104 else: 105 print "Unable to determine kernel architecture." 106 return "" 107 108 def parseConfigFileToDict(self, file, configs): 109 """Parse kernel config file to a dictionary. 110 111 Args: 112 file: file object, android-base.cfg or unzipped /proc/config.gz 113 configs: dict to which config options in file will be added 114 115 Returns: 116 dict: {config_name: config_state} 117 """ 118 config_lines = [line.rstrip("\n") for line in file.readlines()] 119 120 for line in config_lines: 121 if line.startswith("#") and line.endswith("is not set"): 122 match = re.search(r"CONFIG_\S+", line) 123 if match is None: 124 asserts.fail("Failed to parse config file") 125 else: 126 config_name = match.group(0) 127 config_state = "n" 128 elif line.startswith("CONFIG_"): 129 config_name, config_state = line.split("=", 1) 130 if config_state.startswith(("'", '"')): 131 config_state = config_state[1:-1] 132 else: 133 continue 134 configs[config_name] = config_state 135 136 return configs 137 138 def testKernelConfigs(self): 139 """Ensures all kernel configs conform to Android requirements. 140 141 Detects kernel version of device and validates against appropriate 142 Common Android Kernel android-base.cfg and Android Treble 143 requirements. 144 """ 145 logging.info("Testing existence of %s" % self.PROC_FILE_PATH) 146 file_utils.assertPermissionsAndExistence( 147 self.shell, self.PROC_FILE_PATH, file_utils.IsReadOnly) 148 149 logging.info("Validating kernel version of device.") 150 kernel_version = self.checkKernelVersion() 151 152 # Pull configs from the universal config file. 153 configs = dict() 154 config_file_path = os.path.join(self.data_file_path, 155 self.KERNEL_CONFIG_FILE_PATH, "android-" + kernel_version, 156 "android-base.cfg") 157 with open(config_file_path, 'r') as config_file: 158 configs = self.parseConfigFileToDict(config_file, configs) 159 160 # Pull configs from device. 161 device_configs = dict() 162 self.dut.adb.pull("%s %s" % (self.PROC_FILE_PATH, self._temp_dir)) 163 logging.info("Adb pull %s to %s", self.PROC_FILE_PATH, self._temp_dir) 164 165 localpath = os.path.join(self._temp_dir, "config.gz") 166 with gzip.open(localpath, "rb") as device_config_file: 167 device_configs = self.parseConfigFileToDict(device_config_file, 168 device_configs) 169 170 # Check device architecture and pull arch-specific configs. 171 kernelArch = self.checkKernelArch(device_configs) 172 if kernelArch is not "": 173 config_file_path = os.path.join(self.data_file_path, 174 self.KERNEL_CONFIG_FILE_PATH, "android-" + kernel_version, 175 "android-base-%s.cfg" % kernelArch) 176 if os.path.isfile(config_file_path): 177 with open(config_file_path, 'r') as config_file: 178 configs = self.parseConfigFileToDict(config_file, configs) 179 180 # Determine any deviations from the required configs. 181 should_be_enabled = [] 182 should_not_be_set = [] 183 incorrect_config_state = [] 184 for config_name, config_state in configs.iteritems(): 185 if (config_state == "y" and 186 (config_name not in device_configs or 187 device_configs[config_name] not in ("y", "m"))): 188 should_be_enabled.append(config_name) 189 elif (config_state == "n" and (config_name in device_configs) and 190 device_configs[config_name] != "n"): 191 should_not_be_set.append(config_name + "=" + 192 device_configs[config_name]) 193 elif (config_name in device_configs and 194 device_configs[config_name] != config_state): 195 incorrect_config_state.append(config_name + "=" + 196 device_configs[config_name]) 197 198 if ("CONFIG_OF" not in device_configs and 199 "CONFIG_ACPI" not in device_configs): 200 should_be_enabled.append("CONFIG_OF | CONFIG_ACPI") 201 202 asserts.assertTrue( 203 len(should_be_enabled) == 0 and len(should_not_be_set) == 0 and 204 len(incorrect_config_state) == 0, 205 ("The following kernel configs should be enabled: [%s]\n" 206 "The following kernel configs should not be set: [%s]\n" 207 "THe following kernel configs have incorrect state: [%s]") % 208 (", ".join(should_be_enabled), ", ".join(should_not_be_set), 209 ", ".join(incorrect_config_state))) 210 211 def tearDownClass(self): 212 """Deletes the temporary directory.""" 213 logging.info("Delete %s", self._temp_dir) 214 shutil.rmtree(self._temp_dir) 215 216 217if __name__ == "__main__": 218 test_runner.main() 219