• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
2#
3# Copyright 2019 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""
7Call `cros_config` from the DUT while respecting new fallback commands.
8
9When `cros_config` is called on a non-Unibuild DUT, the usual config
10files (and thus behavior) are not available, so it resorts to plan B.
11The following file's kCommandMap contains a list of fallback commands:
12    platform2/chromeos-config/libcros_config/cros_config_fallback.cc
13If the requested cros_config path/property are mapped to a fallback
14command, then that command is called, and its results is returned.
15That behavior is all native to cros_config.
16
17Let's say you define a new fallback command in cros_config_fallback.cc:
18    `cros_config /foo bar` --> `mosys baz quux`
19You then call `cros_config /foo bar` during an Autotest test.
20But, alas! Your test breaks on non-Unibuild boards during qual tests!
21Why? Your new fallback needs to get promoted from ToT to Canary to Dev
22to Beta to Stable. In contrast, Autotest is typically run from whatever
23version is available: often ToT.
24So, ToT Autotest runs `cros_config /foo bar` on a DUT with a Dev image.
25`cros_config` looks for a fallback command, but cannot find the new one.
26It returns the empty string and an error code of 1, and your test fails.
27
28This file serves to replicate the fallbacks in cros_config_fallback.cc,
29so that you don't have to wait for your new fallbacks to get promoted.
30
31"""
32
33import logging
34
35import common
36from autotest_lib.client.common_lib import error
37
38FALLBACKS = {
39        '/firmware image-name': 'mosys platform model',
40        '/ name': 'mosys platform model',
41        '/ brand-code': 'mosys platform brand',
42        '/identity sku-id': 'mosys platform sku',
43        '/identity platform-name': 'mosys platform name'
44}
45
46
47def get_fallback(cros_config_args):
48    """
49    Get the fallback command for a cros_config command, if one exists.
50    NOTE: Does not strip trailing newlines.
51
52    @param cros_config_args: '$path $property', as used by cros_config
53    @return: The fallback command if one exists, else empty string
54
55    @type cros_config_args: string
56    @rtype: string | None
57
58    """
59    return FALLBACKS.get(cros_config_args)
60
61
62def call_cros_config_with_fallback(cros_config_args, run, **run_kwargs):
63    """
64    Try to call cros_config via a supplied run function.
65    If it fails, and a fallback command exists, call its fallback.
66    In order to replicate the behavior of the real cros_config, we
67    (attempt to) strip exactly one trailing \n from the fallback result.
68
69    @param cros_config_args: '$path $property', as used by cros_config
70    @param run: A function which passes a command to the DUT, and
71               returns a client.common_lib.utils.CmdResult object.
72    @param **run_kwargs: Any kwargs to be passed into run()
73    @return: The CmdResult of either cros_config or its fallback,
74             whichever ran more recently.
75
76    @type cros_config: string
77    @type run: func(string, **kwargs) -> CmdResult
78    @rtype: client.common_lib.utils.CmdResult
79
80    @raises CmdError or CmdTimeoutError if either:
81            1. cros_config raised one, and there was no fallback, or
82            2. the fallback command raised one
83
84    """
85    cros_config_cmd = 'cros_config %s' % cros_config_args
86    fallback_cmd = get_fallback(cros_config_args)
87    try:
88        result = run(cros_config_cmd, **run_kwargs)
89    except error.CmdError as e:
90        if fallback_cmd:
91            logging.debug('<%s> raised an error.', cros_config_cmd)
92            result = e.result_obj
93        else:
94            raise
95    else:
96        if result.exit_status:
97            logging.debug('<%s> returned with an exit code.', cros_config_cmd)
98        else:
99            return result
100    if fallback_cmd:
101        logging.debug('Trying fallback cmd: <%s>', fallback_cmd)
102        result = run(fallback_cmd, **run_kwargs)
103        if result.stdout and result.stdout[-1] == '\n':
104            result.stdout = result.stdout[:-1]
105    else:
106        logging.debug('No fallback command found for <%s>.', cros_config_cmd)
107    return result
108
109
110def call_cros_config_get_output(cros_config_args, run, **run_kwargs):
111    """
112    Get the stdout from cros_config (or its fallback).
113
114    @param cros_config_args: '$path $property', as used by cros_config
115    @param run: A function which passes a command to the DUT, and
116               returns a client.common_lib.utils.CmdResult object.
117    @param **run_kwargs: Any kwargs to be passed into run()
118    @return: The string stdout of either cros_config or its fallback,
119             or empty string in the case of error.
120
121    @type cros_config: string
122    @type run: func(string, **kwargs) -> CmdResult
123    @rtype: string
124
125    """
126    try:
127        result = call_cros_config_with_fallback(cros_config_args, run,
128                                                **run_kwargs)
129    except error.CmdError:
130        return ''
131    if result.exit_status:
132        return ''
133    return result.stdout
134