普通文本  |  253行  |  9.78 KB

#!/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.android import api
from vts.utils.python.controllers import android_device
from vts.utils.python.file import target_file_utils

from vts.testcases.kernel.lib import version


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"

    def setUpClass(self):
        required_params = [keys.ConfigKeys.IKEY_DATA_FILE_PATH]
        self.getUserParams(required_params)
        self.dut = self.android_devices[0]
        self.shell = self.dut.shell
        self._temp_dir = tempfile.mkdtemp()
        self.supported_kernel_versions = version.getSupportedKernels(self.dut)
        self.release_dir = self.getReleaseDir()

    def getReleaseDir(self):
        """Return the appropriate subdirectory in kernel/configs.

        Returns the directory in kernel/configs corresponding to
        the device's first_api_level.

        Returns:
            string: a directory in kernel configs
        """
        api_level = self.dut.getLaunchApiLevel(strict=False)

        if (api_level == 0):
            logging.info("Cound not detect api level, using last release")
            return "p"
        elif api_level == api.PLATFORM_API_LEVEL_P:
            return "p"
        elif api_level == api.PLATFORM_API_LEVEL_O_MR1:
            return "o-mr1"
        elif api_level <= api.PLATFORM_API_LEVEL_O:
            return "o"
        else:
            return "."

    def checkKernelVersion(self):
        """Validate the kernel version of DUT is a valid 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 = int(match.group(1))
            kernel_patchlevel = int(match.group(2))
        logging.info("Detected kernel version: %s", match.group(0))

        for v in self.supported_kernel_versions:
            if (kernel_version == v[0] and kernel_patchlevel == v[1]):
                return match.group(0)
        asserts.fail("Detected kernel version is not one of %s" %
                     self.supported_kernel_versions)

    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)
        target_file_utils.assertPermissionsAndExistence(
            self.shell, self.PROC_FILE_PATH, target_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,
            self.release_dir, "android-" + kernel_version, "android-base.cfg")
        logging.info("Pulling base cfg from %s", config_file_path)
        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,
                                            self.release_dir,
                                            "android-" + kernel_version,
                                            "android-base-%s.cfg" % kernelArch)
            if os.path.isfile(config_file_path):
                logging.info("Pulling arch cfg from %s", 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")

        if ("CONFIG_ANDROID_LOW_MEMORY_KILLER" not in device_configs and
                ("CONFIG_MEMCG" not in device_configs or
                 "CONFIG_MEMCG_SWAP" not in device_configs)):
            should_be_enabled.append("CONFIG_ANDROID_LOW_MEMORY_KILLER | "
                                     "(CONFIG_MEMCG & CONFIG_MEMCG_SWAP)")

        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()