| # 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.testbed_name = original_bed_config[ |
| keys.Config.key_testbed_name.value] |
| # 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, {}) |
| 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: |
| """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: [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 |
| 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): |
| # 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__) |
| content.pop('summary_writer') |
| return pprint.pformat(content) |