blob: afb24494b3193312aaf0785233590592891e7240 [file] [log] [blame]
# Copyright 2016 Google Inc.
#
# 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.
from builtins import str
import copy
import io
import pprint
import os
import yaml
from mobly import keys
from mobly import utils
# An environment variable defining the base location for Mobly logs.
ENV_MOBLY_LOGPATH = 'MOBLY_LOGPATH'
_DEFAULT_LOG_PATH = '/tmp/logs/mobly/'
class MoblyConfigError(Exception):
"""Raised when there is a problem in test configuration file."""
def _validate_test_config(test_config):
"""Validates the raw configuration loaded from the config file.
Making sure the required key 'TestBeds' is present.
"""
required_key = keys.Config.key_testbed.value
if required_key not in test_config:
raise MoblyConfigError('Required key %s missing in test config.' %
required_key)
def _validate_testbed_name(name):
"""Validates the name of a test bed.
Since test bed names are used as part of the test run id, it needs to meet
certain requirements.
Args:
name: The test bed's name specified in config file.
Raises:
MoblyConfigError: The name does not meet any criteria.
"""
if not name:
raise MoblyConfigError("Test bed names can't be empty.")
name = str(name)
for char in name:
if char not in utils.valid_filename_chars:
raise MoblyConfigError(
'Char "%s" is not allowed in test bed names.' % char)
def _validate_testbed_configs(testbed_configs):
"""Validates the testbed configurations.
Args:
testbed_configs: A list of testbed configuration dicts.
Raises:
MoblyConfigError: Some parts of the configuration is invalid.
"""
seen_names = set()
# Cross checks testbed configs for resource conflicts.
for config in testbed_configs:
# Check for conflicts between multiple concurrent testbed configs.
# No need to call it if there's only one testbed config.
name = config[keys.Config.key_testbed_name.value]
_validate_testbed_name(name)
# Test bed names should be unique.
if name in seen_names:
raise MoblyConfigError('Duplicate testbed name %s found.' % name)
seen_names.add(name)
def load_test_config_file(test_config_path, tb_filters=None):
"""Processes the test configuration file provied by user.
Loads the configuration file into a dict, unpacks each testbed
config into its own dict, and validate the configuration in the
process.
Args:
test_config_path: Path to the test configuration file.
tb_filters: A subset of test bed names to be pulled from the config
file. If None, then all test beds will be selected.
Returns:
A list of test configuration dicts to be passed to
test_runner.TestRunner.
"""
configs = _load_config_file(test_config_path)
if tb_filters:
tbs = []
for tb in configs[keys.Config.key_testbed.value]:
if tb[keys.Config.key_testbed_name.value] in tb_filters:
tbs.append(tb)
if len(tbs) != len(tb_filters):
raise MoblyConfigError(
'Expect to find %d test bed configs, found %d. Check if'
' you have the correct test bed names.' %
(len(tb_filters), len(tbs)))
configs[keys.Config.key_testbed.value] = tbs
mobly_params = configs.get(keys.Config.key_mobly_params.value, {})
# Decide log path.
log_path = mobly_params.get(keys.Config.key_log_path.value,
_DEFAULT_LOG_PATH)
if ENV_MOBLY_LOGPATH in os.environ:
log_path = os.environ[ENV_MOBLY_LOGPATH]
log_path = utils.abs_path(log_path)
# Validate configs
_validate_test_config(configs)
_validate_testbed_configs(configs[keys.Config.key_testbed.value])
# Transform config dict from user-facing key mapping to internal config object.
test_configs = []
for original_bed_config in configs[keys.Config.key_testbed.value]:
test_run_config = TestRunConfig()
test_run_config.test_bed_name = original_bed_config[
keys.Config.key_testbed_name.value]
test_run_config.log_path = log_path
test_run_config.controller_configs = original_bed_config.get(
keys.Config.key_testbed_controllers.value, {})
test_run_config.user_params = original_bed_config.get(
keys.Config.key_testbed_test_params.value, {})
test_configs.append(test_run_config)
return test_configs
def _load_config_file(path):
"""Loads a test config file.
The test config file has to be in YAML format.
Args:
path: A string that is the full path to the config file, including the
file name.
Returns:
A dict that represents info in the config file.
"""
with io.open(utils.abs_path(path), 'r', encoding='utf-8') as f:
conf = yaml.safe_load(f)
return conf
class TestRunConfig(object):
"""The data class that holds all the information needed for a test run.
Attributes:
log_path: string, specifies the root directory for all logs written by
a test run.
test_bed_name: string, the name of the test bed used by a test run.
controller_configs: dict, configs used for instantiating controller
objects.
user_params: dict, all the parameters to be consumed by the test logic.
summary_writer: records.TestSummaryWriter, used to write elements to
the test result summary file.
test_class_name_suffix: string, suffix to append to the class name for
reporting. This is used for differentiating the same class
executed with different parameters in a suite.
"""
def __init__(self):
self.log_path = None
self.test_bed_name = None
self.controller_configs = None
self.user_params = None
self.summary_writer = None
self.test_class_name_suffix = None
def copy(self):
"""Returns a deep copy of the current config.
"""
return copy.deepcopy(self)
def __str__(self):
content = dict(self.__dict__)
content.pop('summary_writer')
return pprint.pformat(content)