| #!/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() |