#!/usr/bin/env python # # 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. # import gzip import logging import os import re import shutil import tempfile from vts.runners.host import asserts from vts.runners.host import base_test from vts.runners.host import const from vts.runners.host import keys from vts.runners.host import test_runner from vts.utils.python.controllers import android_device from vts.utils.python.file import file_utils class VtsKernelConfigTest(base_test.BaseTestClass): """Test case which check config options in /proc/config.gz. Attributes: _temp_dir: The temporary directory to which /proc/config.gz is copied. """ PROC_FILE_PATH = "/proc/config.gz" KERNEL_CONFIG_FILE_PATH = "vts/testcases/kernel/config/data" SUPPORTED_KERNEL_VERSIONS = ["3.18", "4.4", "4.9"] def setUpClass(self): required_params = [ keys.ConfigKeys.IKEY_DATA_FILE_PATH ] self.getUserParams(required_params) self.dut = self.registerController(android_device)[0] self.dut.shell.InvokeTerminal( "KernelConfigTest") # creates a remote shell instance. self.shell = self.dut.shell.KernelConfigTest self._temp_dir = tempfile.mkdtemp() def checkKernelVersion(self): """Validate the kernel version of DUT is a valid O kernel version. Returns: string, kernel version of device """ cmd = "uname -a" results = self.shell.Execute(cmd) logging.info("Shell command '%s' results: %s", cmd, results) match = re.search(r"\d+\.\d+", results[const.STDOUT][0]) if match is None: asserts.fail("Failed to detect kernel version of device.") else: kernel_version = match.group(0) logging.info("Detected kernel version: %s", kernel_version) asserts.assertTrue(kernel_version in self.SUPPORTED_KERNEL_VERSIONS, "Detected kernel version '%s' is not one of %s" % (kernel_version, self.SUPPORTED_KERNEL_VERSIONS)) return kernel_version def checkKernelArch(self, configs): """Find arch of the device kernel. Uses the kernel configuration to determine the architecture it is compiled for. Args: configs: dict containing device kernel configuration options Returns: A string containing the architecture of the device kernel. If the architecture cannot be determined, an empty string is returned. """ CONFIG_ARM = "CONFIG_ARM" CONFIG_ARM64 = "CONFIG_ARM64" CONFIG_X86 = "CONFIG_X86" if CONFIG_ARM in configs and configs[CONFIG_ARM] == "y": return "arm" elif CONFIG_ARM64 in configs and configs[CONFIG_ARM64] == "y": return "arm64" elif CONFIG_X86 in configs and configs[CONFIG_X86] == "y": return "x86" else: print "Unable to determine kernel architecture." return "" def parseConfigFileToDict(self, file, configs): """Parse kernel config file to a dictionary. Args: file: file object, android-base.cfg or unzipped /proc/config.gz configs: dict to which config options in file will be added Returns: dict: {config_name: config_state} """ config_lines = [line.rstrip("\n") for line in file.readlines()] for line in config_lines: if line.startswith("#") and line.endswith("is not set"): match = re.search(r"CONFIG_\S+", line) if match is None: asserts.fail("Failed to parse config file") else: config_name = match.group(0) config_state = "n" elif line.startswith("CONFIG_"): config_name, config_state = line.split("=", 1) if config_state.startswith(("'", '"')): config_state = config_state[1:-1] else: continue configs[config_name] = config_state return configs def testKernelConfigs(self): """Ensures all kernel configs conform to Android requirements. Detects kernel version of device and validates against appropriate Common Android Kernel android-base.cfg and Android Treble requirements. """ logging.info("Testing existence of %s" % self.PROC_FILE_PATH) file_utils.assertPermissionsAndExistence( self.shell, self.PROC_FILE_PATH, file_utils.IsReadOnly) logging.info("Validating kernel version of device.") kernel_version = self.checkKernelVersion() # Pull configs from the universal config file. configs = dict() config_file_path = os.path.join(self.data_file_path, self.KERNEL_CONFIG_FILE_PATH, "android-" + kernel_version, "android-base.cfg") with open(config_file_path, 'r') as config_file: configs = self.parseConfigFileToDict(config_file, configs) # Pull configs from device. device_configs = dict() self.dut.adb.pull("%s %s" % (self.PROC_FILE_PATH, self._temp_dir)) logging.info("Adb pull %s to %s", self.PROC_FILE_PATH, self._temp_dir) localpath = os.path.join(self._temp_dir, "config.gz") with gzip.open(localpath, "rb") as device_config_file: device_configs = self.parseConfigFileToDict(device_config_file, device_configs) # Check device architecture and pull arch-specific configs. kernelArch = self.checkKernelArch(device_configs) if kernelArch is not "": config_file_path = os.path.join(self.data_file_path, self.KERNEL_CONFIG_FILE_PATH, "android-" + kernel_version, "android-base-%s.cfg" % kernelArch) if os.path.isfile(config_file_path): with open(config_file_path, 'r') as config_file: configs = self.parseConfigFileToDict(config_file, configs) # Determine any deviations from the required configs. should_be_enabled = [] should_not_be_set = [] incorrect_config_state = [] for config_name, config_state in configs.iteritems(): if (config_state == "y" and (config_name not in device_configs or device_configs[config_name] not in ("y", "m"))): should_be_enabled.append(config_name) elif (config_state == "n" and (config_name in device_configs) and device_configs[config_name] != "n"): should_not_be_set.append(config_name + "=" + device_configs[config_name]) elif (config_name in device_configs and device_configs[config_name] != config_state): incorrect_config_state.append(config_name + "=" + device_configs[config_name]) if ("CONFIG_OF" not in device_configs and "CONFIG_ACPI" not in device_configs): should_be_enabled.append("CONFIG_OF | CONFIG_ACPI") asserts.assertTrue( len(should_be_enabled) == 0 and len(should_not_be_set) == 0 and len(incorrect_config_state) == 0, ("The following kernel configs should be enabled: [%s]\n" "The following kernel configs should not be set: [%s]\n" "THe following kernel configs have incorrect state: [%s]") % (", ".join(should_be_enabled), ", ".join(should_not_be_set), ", ".join(incorrect_config_state))) def tearDownClass(self): """Deletes the temporary directory.""" logging.info("Delete %s", self._temp_dir) shutil.rmtree(self._temp_dir) if __name__ == "__main__": test_runner.main()