blob: 0f31cdc0f1ce5a252699d67e31634ae0f865fb06 [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
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.
_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.' %
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.
name: The test bed's name specified in config file.
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.' %
def _validate_testbed_configs(testbed_configs):
"""Validates the testbed configurations.
testbed_configs: A list of testbed configuration dicts.
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]
# Test bed names should be unique.
if name in seen_names:
raise MoblyConfigError('Duplicate testbed name %s found.' % 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
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.
A list of test configuration dicts to be passed to
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:
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
# 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.testbed_name = original_bed_config[
# Deprecated, use testbed_name
test_run_config.test_bed_name = test_run_config.testbed_name
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, {})
return test_configs
def _load_config_file(path):
"""Loads a test config file.
The test config file has to be in YAML format.
path: A string that is the full path to the config file, including the
file name.
A dict that represents info in the config file.
with, 'r', encoding='utf-8') as f:
conf = yaml.safe_load(f)
return conf
class TestRunConfig:
"""The data class that holds all the information needed for a test run.
log_path: string, specifies the root directory for all logs written by
a test run.
test_bed_name: [Deprecated, use 'testbed_name' instead]
string, the name of the test bed used by a test run.
testbed_name: string, the name of the test bed used by a test run.
controller_configs: dict, configs used for instantiating controller
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):
# Init value is an empty string to avoid string joining errors.
self.log_path = ''
# Deprecated, use 'testbed_name'
self.test_bed_name = None
self.testbed_name = None
self.controller_configs = {}
self.user_params = {}
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__)
return pprint.pformat(content)