• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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