1# Copyright 2019 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import json 6import logging 7import os 8 9import common 10from autotest_lib.client.common_lib import error 11 12 13# Path to the local checkout of the fw-testing-configs repo 14_CONFIG_DIR = os.path.abspath(os.path.join( 15 os.path.dirname(os.path.realpath(__file__)), os.pardir, 16 'fw-testing-configs')) 17_CONSOLIDATED_JSON_BASENAME = 'CONSOLIDATED.json' 18 19 20def _consolidated_json_fp(): 21 """Get the absolute path to CONSOLIDATED.json.""" 22 return os.path.join(_CONFIG_DIR, _CONSOLIDATED_JSON_BASENAME) 23 24 25class Config(object): 26 """Configuration for FAFT tests. 27 28 This object is meant to be the interface to all configuration required 29 by FAFT tests, including device specific overrides. 30 31 It gets the values from the JSON files in _CONFIG_DIR. 32 Default values are declared in the DEFAULTS.json. 33 Platform-specific overrides come from <platform>.json. 34 If the platform has model-specific overrides, then those take precedence 35 over the platform's config. 36 If the platform inherits overrides from a parent platform, then the child 37 platform's overrides take precedence over the parent's. 38 39 @ivar platform: string containing the board name being tested. 40 @ivar model: string containing the model name being tested 41 """ 42 43 def __init__(self, platform, model=None): 44 """Initialize an object with FAFT settings. 45 Load JSON in order of importance (model, platform, parent/s, DEFAULTS). 46 47 @param platform: The name of the platform being tested. 48 """ 49 self._precedence_list = [] 50 self._precedence_names = [] 51 with open(_consolidated_json_fp()) as f: 52 consolidated_json = json.load(f) 53 # Load the most specific JSON config possible by splitting `platform` 54 # at '_'/'-' and reversing ([::-1]). For example, veyron_minnie should 55 # load minnie.json. octopus_fleex should look for fleex.json. It 56 # doesn't exist, so instead it loads octopus.json. 57 platform = platform.lower().replace('-', '_') 58 for p in platform.rsplit('_')[::-1]: 59 logging.debug('Looking for %s config', p) 60 if p in consolidated_json: 61 logging.info('Found %s config', p) 62 self.platform = p 63 break 64 else: 65 self.platform = platform 66 if self.platform in consolidated_json: 67 platform_config = consolidated_json[self.platform] 68 seen_platforms = [self.platform] 69 self._add_cfg_to_precedence(self.platform, platform_config) 70 model_configs = platform_config.get('models', {}) 71 model_config = model_configs.get(model, None) 72 if model_config is not None: 73 self._add_cfg_to_precedence( 74 'MODEL:%s' % model, model_config, prepend=True) 75 logging.debug('Using model override for %s', model) 76 parent_platform = self._precedence_list[-1].get('parent', None) 77 while parent_platform is not None: 78 if parent_platform in seen_platforms: 79 loop = ' -> '.join(seen_platforms + [parent_platform]) 80 raise error.TestError('fw-testing-configs for platform %s ' 81 'contains an inheritance loop: %s' % ( 82 self.platform, loop)) 83 parent_config = consolidated_json[parent_platform] 84 seen_platforms.append(parent_platform) 85 self._add_cfg_to_precedence(parent_platform, parent_config) 86 parent_platform = self._precedence_list[-1].get('parent', None) 87 else: 88 logging.debug('Platform %s not found in %s. Using DEFAULTS.', 89 self.platform, consolidated_json) 90 default_config = consolidated_json['DEFAULTS'] 91 self._add_cfg_to_precedence('DEFAULTS', default_config) 92 93 # Set attributes 94 all_attributes = self._precedence_list[-1].keys() 95 self.attributes = {} 96 self.attributes['platform'] = self.platform 97 for attribute in all_attributes: 98 if attribute.endswith('.DOC') or attribute == 'models': 99 continue 100 for config_dict in self._precedence_list: 101 if attribute in config_dict: 102 self.attributes[attribute] = config_dict[attribute] 103 break 104 105 def _add_cfg_to_precedence(self, cfg_name, cfg, prepend=False): 106 """Add a configuration to self._precedence_list. 107 108 @ivar cfg_name: The name of the config. 109 @ivar cfg: The config dict. 110 @ivar prepend: If true, add to the beginning of self._precedence_list. 111 Otherwise, add it to the end. 112 """ 113 position = 0 if prepend else len(self._precedence_list) 114 self._precedence_list.insert(position, cfg) 115 self._precedence_names.insert(position, cfg_name) 116 117 def __getattr__(self, attr): 118 if attr in self.attributes: 119 return self.attributes[attr] 120 raise AttributeError('FAFT config has no attribute named %s' % attr) 121 122 def __str__(self): 123 str_list = [] 124 str_list.append('----------[ FW Testing Config Variables ]----------') 125 str_list.append('--- Precedence list: %s ---' % self._precedence_names) 126 for attr in sorted(self.attributes): 127 str_list.append(' %s: %s' % (attr, self.attributes[attr])) 128 str_list.append('---------------------------------------------------') 129 return '\n'.join(str_list) 130