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