blob: 5c11548e82056e4070f24944c3e8dddf8ed141a5 [file] [log] [blame]
#!/usr/bin/python2
#
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Call `cros_config` from the DUT while respecting new fallback commands.
When `cros_config` is called on a non-Unibuild DUT, the usual config
files (and thus behavior) are not available, so it resorts to plan B.
The following file's kCommandMap contains a list of fallback commands:
platform2/chromeos-config/libcros_config/cros_config_fallback.cc
If the requested cros_config path/property are mapped to a fallback
command, then that command is called, and its results is returned.
That behavior is all native to cros_config.
Let's say you define a new fallback command in cros_config_fallback.cc:
`cros_config /foo bar` --> `mosys baz quux`
You then call `cros_config /foo bar` during an Autotest test.
But, alas! Your test breaks on non-Unibuild boards during qual tests!
Why? Your new fallback needs to get promoted from ToT to Canary to Dev
to Beta to Stable. In contrast, Autotest is typically run from whatever
version is available: often ToT.
So, ToT Autotest runs `cros_config /foo bar` on a DUT with a Dev image.
`cros_config` looks for a fallback command, but cannot find the new one.
It returns the empty string and an error code of 1, and your test fails.
This file serves to replicate the fallbacks in cros_config_fallback.cc,
so that you don't have to wait for your new fallbacks to get promoted.
"""
import logging
import common
from autotest_lib.client.common_lib import error
FALLBACKS = {
'/firmware image-name': 'mosys platform model',
'/ name': 'mosys platform model',
'/ brand-code': 'mosys platform brand',
'/identity sku-id': 'mosys platform sku',
'/identity platform-name': 'mosys platform name'
}
def get_fallback(cros_config_args):
"""
Get the fallback command for a cros_config command, if one exists.
NOTE: Does not strip trailing newlines.
@param cros_config_args: '$path $property', as used by cros_config
@return: The fallback command if one exists, else empty string
@type cros_config_args: string
@rtype: string | None
"""
return FALLBACKS.get(cros_config_args)
def call_cros_config_with_fallback(cros_config_args, run, **run_kwargs):
"""
Try to call cros_config via a supplied run function.
If it fails, and a fallback command exists, call its fallback.
In order to replicate the behavior of the real cros_config, we
(attempt to) strip exactly one trailing \n from the fallback result.
@param cros_config_args: '$path $property', as used by cros_config
@param run: A function which passes a command to the DUT, and
returns a client.common_lib.utils.CmdResult object.
@param **run_kwargs: Any kwargs to be passed into run()
@return: The CmdResult of either cros_config or its fallback,
whichever ran more recently.
@type cros_config: string
@type run: func(string, **kwargs) -> CmdResult
@rtype: client.common_lib.utils.CmdResult
@raises CmdError or CmdTimeoutError if either:
1. cros_config raised one, and there was no fallback, or
2. the fallback command raised one
"""
cros_config_cmd = 'cros_config %s' % cros_config_args
fallback_cmd = get_fallback(cros_config_args)
try:
result = run(cros_config_cmd, **run_kwargs)
except error.CmdError as e:
if fallback_cmd:
logging.debug('<%s> raised an error.', cros_config_cmd)
result = e.result_obj
else:
raise
else:
if result.exit_status:
logging.debug('<%s> returned with an exit code.', cros_config_cmd)
else:
return result
if fallback_cmd:
logging.debug('Trying fallback cmd: <%s>', fallback_cmd)
result = run(fallback_cmd, **run_kwargs)
if result.stdout and result.stdout[-1] == '\n':
result.stdout = result.stdout[:-1]
else:
logging.debug('No fallback command found for <%s>.', cros_config_cmd)
return result
def call_cros_config_get_output(cros_config_args, run, **run_kwargs):
"""
Get the stdout from cros_config (or its fallback).
@param cros_config_args: '$path $property', as used by cros_config
@param run: A function which passes a command to the DUT, and
returns a client.common_lib.utils.CmdResult object.
@param **run_kwargs: Any kwargs to be passed into run()
@return: The string stdout of either cros_config or its fallback,
or empty string in the case of error.
@type cros_config: string
@type run: func(string, **kwargs) -> CmdResult
@rtype: string
"""
try:
result = call_cros_config_with_fallback(cros_config_args, run,
**run_kwargs)
except error.CmdError:
return ''
if result.exit_status:
return ''
return result.stdout