• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from builtins import str
16
17import copy
18import io
19import pprint
20import os
21import yaml
22
23from mobly import keys
24from mobly import utils
25
26# An environment variable defining the base location for Mobly logs.
27ENV_MOBLY_LOGPATH = 'MOBLY_LOGPATH'
28_DEFAULT_LOG_PATH = '/tmp/logs/mobly/'
29
30
31class MoblyConfigError(Exception):
32  """Raised when there is a problem in test configuration file."""
33
34
35def _validate_test_config(test_config):
36  """Validates the raw configuration loaded from the config file.
37
38  Making sure the required key 'TestBeds' is present.
39  """
40  required_key = keys.Config.key_testbed.value
41  if required_key not in test_config:
42    raise MoblyConfigError('Required key %s missing in test config.' %
43                           required_key)
44
45
46def _validate_testbed_name(name):
47  """Validates the name of a test bed.
48
49  Since test bed names are used as part of the test run id, it needs to meet
50  certain requirements.
51
52  Args:
53    name: The test bed's name specified in config file.
54
55  Raises:
56    MoblyConfigError: The name does not meet any criteria.
57  """
58  if not name:
59    raise MoblyConfigError("Test bed names can't be empty.")
60  name = str(name)
61  for char in name:
62    if char not in utils.valid_filename_chars:
63      raise MoblyConfigError('Char "%s" is not allowed in test bed names.' %
64                             char)
65
66
67def _validate_testbed_configs(testbed_configs):
68  """Validates the testbed configurations.
69
70  Args:
71    testbed_configs: A list of testbed configuration dicts.
72
73  Raises:
74    MoblyConfigError: Some parts of the configuration is invalid.
75  """
76  seen_names = set()
77  # Cross checks testbed configs for resource conflicts.
78  for config in testbed_configs:
79    # Check for conflicts between multiple concurrent testbed configs.
80    # No need to call it if there's only one testbed config.
81    name = config[keys.Config.key_testbed_name.value]
82    _validate_testbed_name(name)
83    # Test bed names should be unique.
84    if name in seen_names:
85      raise MoblyConfigError('Duplicate testbed name %s found.' % name)
86    seen_names.add(name)
87
88
89def load_test_config_file(test_config_path, tb_filters=None):
90  """Processes the test configuration file provied by user.
91
92  Loads the configuration file into a dict, unpacks each testbed
93  config into its own dict, and validate the configuration in the
94  process.
95
96  Args:
97    test_config_path: Path to the test configuration file.
98    tb_filters: A subset of test bed names to be pulled from the config
99      file. If None, then all test beds will be selected.
100
101  Returns:
102    A list of test configuration dicts to be passed to
103    test_runner.TestRunner.
104  """
105  configs = _load_config_file(test_config_path)
106  if tb_filters:
107    tbs = []
108    for tb in configs[keys.Config.key_testbed.value]:
109      if tb[keys.Config.key_testbed_name.value] in tb_filters:
110        tbs.append(tb)
111    if len(tbs) != len(tb_filters):
112      raise MoblyConfigError(
113          'Expect to find %d test bed configs, found %d. Check if'
114          ' you have the correct test bed names.' % (len(tb_filters), len(tbs)))
115    configs[keys.Config.key_testbed.value] = tbs
116  mobly_params = configs.get(keys.Config.key_mobly_params.value, {})
117  # Decide log path.
118  log_path = mobly_params.get(keys.Config.key_log_path.value, _DEFAULT_LOG_PATH)
119  if ENV_MOBLY_LOGPATH in os.environ:
120    log_path = os.environ[ENV_MOBLY_LOGPATH]
121  log_path = utils.abs_path(log_path)
122  # Validate configs
123  _validate_test_config(configs)
124  _validate_testbed_configs(configs[keys.Config.key_testbed.value])
125  # Transform config dict from user-facing key mapping to internal config object.
126  test_configs = []
127  for original_bed_config in configs[keys.Config.key_testbed.value]:
128    test_run_config = TestRunConfig()
129    test_run_config.testbed_name = original_bed_config[
130        keys.Config.key_testbed_name.value]
131    # Deprecated, use testbed_name
132    test_run_config.test_bed_name = test_run_config.testbed_name
133    test_run_config.log_path = log_path
134    test_run_config.controller_configs = original_bed_config.get(
135        keys.Config.key_testbed_controllers.value, {})
136    test_run_config.user_params = original_bed_config.get(
137        keys.Config.key_testbed_test_params.value, {})
138    test_configs.append(test_run_config)
139  return test_configs
140
141
142def _load_config_file(path):
143  """Loads a test config file.
144
145  The test config file has to be in YAML format.
146
147  Args:
148    path: A string that is the full path to the config file, including the
149      file name.
150
151  Returns:
152    A dict that represents info in the config file.
153  """
154  with io.open(utils.abs_path(path), 'r', encoding='utf-8') as f:
155    conf = yaml.safe_load(f)
156    return conf
157
158
159class TestRunConfig:
160  """The data class that holds all the information needed for a test run.
161
162  Attributes:
163    log_path: string, specifies the root directory for all logs written by
164      a test run.
165    test_bed_name: [Deprecated, use 'testbed_name' instead]
166      string, the name of the test bed used by a test run.
167    testbed_name: string, the name of the test bed used by a test run.
168    controller_configs: dict, configs used for instantiating controller
169      objects.
170    user_params: dict, all the parameters to be consumed by the test logic.
171    summary_writer: records.TestSummaryWriter, used to write elements to
172      the test result summary file.
173    test_class_name_suffix: string, suffix to append to the class name for
174        reporting. This is used for differentiating the same class
175        executed with different parameters in a suite.
176  """
177
178  def __init__(self):
179    self.log_path = _DEFAULT_LOG_PATH
180    # Deprecated, use 'testbed_name'
181    self.test_bed_name = None
182    self.testbed_name = None
183    self.controller_configs = {}
184    self.user_params = {}
185    self.summary_writer = None
186    self.test_class_name_suffix = None
187
188  def copy(self):
189    """Returns a deep copy of the current config.
190    """
191    return copy.deepcopy(self)
192
193  def __str__(self):
194    content = dict(self.__dict__)
195    content.pop('summary_writer')
196    return pprint.pformat(content)
197