#!/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. import unittest import common from autotest_lib.client.common_lib import error from autotest_lib.client.common_lib import utils from autotest_lib.client.common_lib.cros import cros_config # Lots of command-line mocking in this file. # Mock cros_config results are based on the path and property provided. # (Remember, cros_config's syntax is `cros_config path property`.) # The path determines whether cros_config fails or succeeds. # The property determines whether there is a fallback command, and if so, # whether the fallback fails or succeeds. SUCCEEDS = 0 FAILS = 1 DOES_NOT_EXIST = 2 # cros_config path determines the mock behavior of cros_config. CC_PATHS = {SUCCEEDS: '/success', FAILS: '/error'} # cros_config property determines the mock behavior of the fallback command. CC_PROPERTIES = { SUCCEEDS: 'fallback_succeeds', FAILS: 'fallback_fails', DOES_NOT_EXIST: 'no_fallback' } CROS_CONFIG_SUCCESS_RESPONSE = 'cros_config succeeded' CROS_CONFIG_FALLBACK_RESPONSE = 'fallback succeeded' def get_cros_config_args(cros_config_result, fallback_result): """Build cros_config_args based on the desired outcome.""" cros_config_path = CC_PATHS[cros_config_result] cros_config_property = CC_PROPERTIES[fallback_result] return '%s %s' % (cros_config_path, cros_config_property) class _CrosConfigBaseTestCase(unittest.TestCase): """Base class which sets up mock fallback commands""" def setUp(self): """Add mock fallback command(s) to cros_config.FALLBACKS""" for path in CC_PATHS.values(): pass_args = '%s %s' % (path, CC_PROPERTIES[SUCCEEDS]) fail_args = '%s %s' % (path, CC_PROPERTIES[FAILS]) cros_config.FALLBACKS[pass_args] = \ 'echo %s' % CROS_CONFIG_FALLBACK_RESPONSE cros_config.FALLBACKS[fail_args] = 'this command does nothing' def tearDown(self): """Remove mock fallback command(s) from cros_config.FALLBACKS""" for path in CC_PATHS.values(): pass_args = '%s %s' % (path, CC_PROPERTIES[SUCCEEDS]) fail_args = '%s %s' % (path, CC_PROPERTIES[FAILS]) del cros_config.FALLBACKS[pass_args] del cros_config.FALLBACKS[fail_args] class GetFallbackTestCase(_CrosConfigBaseTestCase): """Verify cros_config.get_fallback""" def runTest(self): """Check handling for commands with and without fallbacks""" self.assertFalse( cros_config.get_fallback( '%s %s' % (CC_PATHS[SUCCEEDS], CC_PROPERTIES[DOES_NOT_EXIST]))) self.assertEqual( cros_config.get_fallback('%s %s' % (CC_PATHS[SUCCEEDS], CC_PROPERTIES[SUCCEEDS])), 'echo %s' % CROS_CONFIG_FALLBACK_RESPONSE) def _mock_cmd_runner(cmd, **kwargs): """ Mock running a DUT command, returning a CmdResult. We handle a few mock functions here: * cros_config $path $property: $path determines error or success. $property is not used here. * echo $text: Returns $text with a trailing newline. Additionally, if the kwarg `ignore_status` is passed in as True, then when cros_config would raise an error, it instead returns a CmdResult with an exit_status of 1. @param cmd: A command, as would be run on the DUT @param **kwargs: Kwargs that might be passed into, say, utils.run() @return: A mock response from the DUT @type cmd: string @rtype: client.common_lib.utils.CmdResult @raise error.CmdError if cros_config should raise an exception. @raise NotImplementedError if cros_config has an unexpected path """ result = utils.CmdResult(cmd) if cmd.startswith('cros_config '): _, path, _ = cmd.split() if path == CC_PATHS[SUCCEEDS]: result.stdout = CROS_CONFIG_SUCCESS_RESPONSE elif path == CC_PATHS[FAILS]: result.exit_status = 1 if not kwargs.get('ignore_status'): raise error.CmdError(cmd, result) else: raise NotImplementedError('Bad cros_config path: %s' % path) elif cmd.startswith('echo '): result.stdout = cmd.lstrip('echo ') + '\n' else: result.exit_status = 2 if not kwargs.get('ignore_status'): raise error.CmdError(cmd, result) return result class CallCrosConfigWithFallbackTestCase(_CrosConfigBaseTestCase): """Verify cros_config.call_cros_config_with_fallback""" def run_cc_w_fallback(self, cros_config_result, fallback_result, ignore_status=False): """ Helper function to call cros_config.call_cros_config_with_fallback() """ cc_args = get_cros_config_args(cros_config_result, fallback_result) if ignore_status: return cros_config.call_cros_config_with_fallback( cc_args, _mock_cmd_runner, ignore_status=True) else: return cros_config.call_cros_config_with_fallback( cc_args, _mock_cmd_runner) def test_cros_config_success(self): """ Verify that if cros_config is defined, we get the cros_config result, regardless of whether there is a fallback command. """ for fallback_status in (SUCCEEDS, FAILS, DOES_NOT_EXIST): for ignore_status in (True, False): output = self.run_cc_w_fallback(SUCCEEDS, fallback_status, ignore_status) self.assertEqual(output.stdout, CROS_CONFIG_SUCCESS_RESPONSE) self.assertFalse(output.exit_status) def test_fallback_success(self): """ Verify that if cros_config is not defined but a fallback is, we get the fallback result. """ for ignore_status in (True, False): output = self.run_cc_w_fallback(FAILS, SUCCEEDS, ignore_status) self.assertEqual(output.stdout, CROS_CONFIG_FALLBACK_RESPONSE) self.assertFalse(output.exit_status) def test_fallback_fails(self): """ Verify that if both cros_config and the fallback fail, a CmdError is raised. """ with self.assertRaises(error.CmdError): self.run_cc_w_fallback(FAILS, FAILS) def test_fallback_dne(self): """ Verify that if cros_config fails and the fallback does not exist, a CmdError is raised. """ with self.assertRaises(error.CmdError): self.run_cc_w_fallback(FAILS, DOES_NOT_EXIST) def test_fallback_fails_ignore_status(self): """ Verify that if both cros_config and the fallback fail, and the ignore_status kwarg is passed in, we get a CmdResult with a non-zero exit status. """ output = self.run_cc_w_fallback(FAILS, FAILS, True) self.assertTrue(output.exit_status) def test_fallback_dne_ignore_status(self): """ Verify that if cros_config fails and the fallback does not exist, and the ignore_status kwarg is passed in, we get a CmdResult with a non-zero exit status. """ output = self.run_cc_w_fallback(FAILS, DOES_NOT_EXIST, True) self.assertTrue(output.exit_status) class CallCrosConfigGetOutputTestCase(_CrosConfigBaseTestCase): """ Verify cros_config.call_cros_config_get_output. Basically the same as CallCrosConfigWithFallbackTestCase, except that the expected result is a string instead of a CmdResult, and it shouldn't raise exceptions. """ def run_cc_get_output(self, cros_config_result, fallback_result, ignore_status=False): """ Helper function to call cros_config.call_cros_config_get_output() """ cc_args = get_cros_config_args(cros_config_result, fallback_result) if ignore_status: return cros_config.call_cros_config_get_output( cc_args, _mock_cmd_runner, ignore_status=True) else: return cros_config.call_cros_config_get_output( cc_args, _mock_cmd_runner) def test_cros_config_success(self): """ Verify that if cros_config is defined, we get the cros_config result, regardless of whether there is a fallback command. """ for fallback_status in (SUCCEEDS, FAILS, DOES_NOT_EXIST): output = self.run_cc_get_output(SUCCEEDS, fallback_status) self.assertEqual(output, CROS_CONFIG_SUCCESS_RESPONSE) def test_fallback_success(self): """ Verify that if cros_config is not defined but a fallback is, we get the fallback result. """ output = self.run_cc_get_output(FAILS, SUCCEEDS) self.assertEqual(output, CROS_CONFIG_FALLBACK_RESPONSE) def test_fallback_fails(self): """ Verify that if both cros_config and the fallback fail, we get a falsey value. """ output = self.run_cc_get_output(FAILS, FAILS) self.assertFalse(output) def test_fallback_dne(self): """ Verify that if cros_config fails and the fallback does not exist, we get a falsey value. """ output = self.run_cc_get_output(FAILS, DOES_NOT_EXIST) self.assertFalse(output) def test_fallback_fails_ignore_status(self): """ Verify that if both cros_config and the fallback fail, and the ignore_status kwarg is passed in, we get a falsey value. """ output = self.run_cc_get_output(FAILS, FAILS, True) self.assertFalse(output) def test_fallback_dne_ignore_status(self): """ Verify that if cros_config fails and the fallback does not exist, and the ignore_status kwarg is passed in, we get a falsey value. """ output = self.run_cc_get_output(FAILS, DOES_NOT_EXIST, True) self.assertFalse(output) if __name__ == "__main__": unittest.main()