#!/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()