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