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 7import unittest 8 9import common 10from autotest_lib.client.common_lib import error 11from autotest_lib.client.common_lib import utils 12from autotest_lib.client.common_lib.cros import cros_config 13 14# Lots of command-line mocking in this file. 15# Mock cros_config results are based on the path and property provided. 16# (Remember, cros_config's syntax is `cros_config path property`.) 17# The path determines whether cros_config fails or succeeds. 18# The property determines whether there is a fallback command, and if so, 19# whether the fallback fails or succeeds. 20 21SUCCEEDS = 0 22FAILS = 1 23DOES_NOT_EXIST = 2 24 25# cros_config path determines the mock behavior of cros_config. 26CC_PATHS = {SUCCEEDS: '/success', FAILS: '/error'} 27 28# cros_config property determines the mock behavior of the fallback command. 29CC_PROPERTIES = { 30 SUCCEEDS: 'fallback_succeeds', 31 FAILS: 'fallback_fails', 32 DOES_NOT_EXIST: 'no_fallback' 33} 34 35CROS_CONFIG_SUCCESS_RESPONSE = 'cros_config succeeded' 36CROS_CONFIG_FALLBACK_RESPONSE = 'fallback succeeded' 37 38 39def get_cros_config_args(cros_config_result, fallback_result): 40 """Build cros_config_args based on the desired outcome.""" 41 cros_config_path = CC_PATHS[cros_config_result] 42 cros_config_property = CC_PROPERTIES[fallback_result] 43 return '%s %s' % (cros_config_path, cros_config_property) 44 45 46class _CrosConfigBaseTestCase(unittest.TestCase): 47 """Base class which sets up mock fallback commands""" 48 49 def setUp(self): 50 """Add mock fallback command(s) to cros_config.FALLBACKS""" 51 for path in CC_PATHS.values(): 52 pass_args = '%s %s' % (path, CC_PROPERTIES[SUCCEEDS]) 53 fail_args = '%s %s' % (path, CC_PROPERTIES[FAILS]) 54 cros_config.FALLBACKS[pass_args] = \ 55 'echo %s' % CROS_CONFIG_FALLBACK_RESPONSE 56 cros_config.FALLBACKS[fail_args] = 'this command does nothing' 57 58 def tearDown(self): 59 """Remove mock fallback command(s) from cros_config.FALLBACKS""" 60 for path in CC_PATHS.values(): 61 pass_args = '%s %s' % (path, CC_PROPERTIES[SUCCEEDS]) 62 fail_args = '%s %s' % (path, CC_PROPERTIES[FAILS]) 63 del cros_config.FALLBACKS[pass_args] 64 del cros_config.FALLBACKS[fail_args] 65 66 67class GetFallbackTestCase(_CrosConfigBaseTestCase): 68 """Verify cros_config.get_fallback""" 69 70 def runTest(self): 71 """Check handling for commands with and without fallbacks""" 72 self.assertFalse( 73 cros_config.get_fallback( 74 '%s %s' % (CC_PATHS[SUCCEEDS], 75 CC_PROPERTIES[DOES_NOT_EXIST]))) 76 self.assertEqual( 77 cros_config.get_fallback('%s %s' % (CC_PATHS[SUCCEEDS], 78 CC_PROPERTIES[SUCCEEDS])), 79 'echo %s' % CROS_CONFIG_FALLBACK_RESPONSE) 80 81 82def _mock_cmd_runner(cmd, **kwargs): 83 """ 84 Mock running a DUT command, returning a CmdResult. 85 86 We handle a few mock functions here: 87 * cros_config $path $property: $path determines error or success. 88 $property is not used here. 89 * echo $text: Returns $text with a trailing newline. 90 91 Additionally, if the kwarg `ignore_status` is passed in as True, 92 then when cros_config would raise an error, it instead returns a 93 CmdResult with an exit_status of 1. 94 95 @param cmd: A command, as would be run on the DUT 96 @param **kwargs: Kwargs that might be passed into, say, utils.run() 97 @return: A mock response from the DUT 98 99 @type cmd: string 100 @rtype: client.common_lib.utils.CmdResult 101 102 @raise error.CmdError if cros_config should raise an exception. 103 @raise NotImplementedError if cros_config has an unexpected path 104 105 """ 106 result = utils.CmdResult(cmd) 107 if cmd.startswith('cros_config '): 108 _, path, _ = cmd.split() 109 if path == CC_PATHS[SUCCEEDS]: 110 result.stdout = CROS_CONFIG_SUCCESS_RESPONSE 111 elif path == CC_PATHS[FAILS]: 112 result.exit_status = 1 113 if not kwargs.get('ignore_status'): 114 raise error.CmdError(cmd, result) 115 else: 116 raise NotImplementedError('Bad cros_config path: %s' % path) 117 elif cmd.startswith('echo '): 118 result.stdout = cmd.lstrip('echo ') + '\n' 119 else: 120 result.exit_status = 2 121 if not kwargs.get('ignore_status'): 122 raise error.CmdError(cmd, result) 123 return result 124 125 126class CallCrosConfigWithFallbackTestCase(_CrosConfigBaseTestCase): 127 """Verify cros_config.call_cros_config_with_fallback""" 128 129 def run_cc_w_fallback(self, cros_config_result, fallback_result, 130 ignore_status=False): 131 """ 132 Helper function to call 133 cros_config.call_cros_config_with_fallback() 134 135 """ 136 cc_args = get_cros_config_args(cros_config_result, fallback_result) 137 if ignore_status: 138 return cros_config.call_cros_config_with_fallback( 139 cc_args, _mock_cmd_runner, ignore_status=True) 140 else: 141 return cros_config.call_cros_config_with_fallback( 142 cc_args, _mock_cmd_runner) 143 144 def test_cros_config_success(self): 145 """ 146 Verify that if cros_config is defined, we get the cros_config 147 result, regardless of whether there is a fallback command. 148 149 """ 150 for fallback_status in (SUCCEEDS, FAILS, DOES_NOT_EXIST): 151 for ignore_status in (True, False): 152 output = self.run_cc_w_fallback(SUCCEEDS, fallback_status, 153 ignore_status) 154 self.assertEqual(output.stdout, CROS_CONFIG_SUCCESS_RESPONSE) 155 self.assertFalse(output.exit_status) 156 157 def test_fallback_success(self): 158 """ 159 Verify that if cros_config is not defined but a fallback is, 160 we get the fallback result. 161 162 """ 163 for ignore_status in (True, False): 164 output = self.run_cc_w_fallback(FAILS, SUCCEEDS, ignore_status) 165 self.assertEqual(output.stdout, CROS_CONFIG_FALLBACK_RESPONSE) 166 self.assertFalse(output.exit_status) 167 168 def test_fallback_fails(self): 169 """ 170 Verify that if both cros_config and the fallback fail, a 171 CmdError is raised. 172 173 """ 174 with self.assertRaises(error.CmdError): 175 self.run_cc_w_fallback(FAILS, FAILS) 176 177 def test_fallback_dne(self): 178 """ 179 Verify that if cros_config fails and the fallback does not 180 exist, a CmdError is raised. 181 182 """ 183 with self.assertRaises(error.CmdError): 184 self.run_cc_w_fallback(FAILS, DOES_NOT_EXIST) 185 186 def test_fallback_fails_ignore_status(self): 187 """ 188 Verify that if both cros_config and the fallback fail, and the 189 ignore_status kwarg is passed in, we get a CmdResult with a 190 non-zero exit status. 191 192 """ 193 output = self.run_cc_w_fallback(FAILS, FAILS, True) 194 self.assertTrue(output.exit_status) 195 196 def test_fallback_dne_ignore_status(self): 197 """ 198 Verify that if cros_config fails and the fallback does not 199 exist, and the ignore_status kwarg is passed in, we get a 200 CmdResult with a non-zero exit status. 201 202 """ 203 output = self.run_cc_w_fallback(FAILS, DOES_NOT_EXIST, True) 204 self.assertTrue(output.exit_status) 205 206 207class CallCrosConfigGetOutputTestCase(_CrosConfigBaseTestCase): 208 """ 209 Verify cros_config.call_cros_config_get_output. 210 Basically the same as CallCrosConfigWithFallbackTestCase, except 211 that the expected result is a string instead of a CmdResult, and 212 it shouldn't raise exceptions. 213 214 """ 215 216 def run_cc_get_output(self, cros_config_result, fallback_result, 217 ignore_status=False): 218 """ 219 Helper function to call 220 cros_config.call_cros_config_get_output() 221 222 """ 223 cc_args = get_cros_config_args(cros_config_result, fallback_result) 224 if ignore_status: 225 return cros_config.call_cros_config_get_output( 226 cc_args, _mock_cmd_runner, ignore_status=True) 227 else: 228 return cros_config.call_cros_config_get_output( 229 cc_args, _mock_cmd_runner) 230 231 def test_cros_config_success(self): 232 """ 233 Verify that if cros_config is defined, we get the cros_config 234 result, regardless of whether there is a fallback command. 235 236 """ 237 for fallback_status in (SUCCEEDS, FAILS, DOES_NOT_EXIST): 238 output = self.run_cc_get_output(SUCCEEDS, fallback_status) 239 self.assertEqual(output, CROS_CONFIG_SUCCESS_RESPONSE) 240 241 def test_fallback_success(self): 242 """ 243 Verify that if cros_config is not defined but a fallback is, 244 we get the fallback result. 245 246 """ 247 output = self.run_cc_get_output(FAILS, SUCCEEDS) 248 self.assertEqual(output, CROS_CONFIG_FALLBACK_RESPONSE) 249 250 def test_fallback_fails(self): 251 """ 252 Verify that if both cros_config and the fallback fail, we get 253 a falsey value. 254 255 """ 256 output = self.run_cc_get_output(FAILS, FAILS) 257 self.assertFalse(output) 258 259 def test_fallback_dne(self): 260 """ 261 Verify that if cros_config fails and the fallback does not 262 exist, we get a falsey value. 263 264 """ 265 output = self.run_cc_get_output(FAILS, DOES_NOT_EXIST) 266 self.assertFalse(output) 267 268 def test_fallback_fails_ignore_status(self): 269 """ 270 Verify that if both cros_config and the fallback fail, and the 271 ignore_status kwarg is passed in, we get a falsey value. 272 273 """ 274 output = self.run_cc_get_output(FAILS, FAILS, True) 275 self.assertFalse(output) 276 277 def test_fallback_dne_ignore_status(self): 278 """ 279 Verify that if cros_config fails and the fallback does not 280 exist, and the ignore_status kwarg is passed in, we get a 281 falsey value. 282 283 """ 284 output = self.run_cc_get_output(FAILS, DOES_NOT_EXIST, True) 285 self.assertFalse(output) 286 287 288if __name__ == "__main__": 289 unittest.main() 290