blob: 8231ddf871116b1d3e582f988acc7b399d071801 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
David Pursellf1d16a62015-03-25 13:31:04 -07002# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Unit tests for the flash module."""
7
8from __future__ import print_function
9
David Pursellf1d16a62015-03-25 13:31:04 -070010import os
Mike Frysinger3f087aa2020-03-20 06:03:16 -040011import sys
David Pursellf1d16a62015-03-25 13:31:04 -070012
Mike Frysinger6db648e2018-07-24 19:57:58 -040013import mock
14
David Pursellf1d16a62015-03-25 13:31:04 -070015from chromite.cli import flash
David Pursellf1d16a62015-03-25 13:31:04 -070016from chromite.lib import commandline
17from chromite.lib import cros_build_lib
Ralph Nathan9b997232015-05-15 13:13:12 -070018from chromite.lib import cros_logging as logging
David Pursellf1d16a62015-03-25 13:31:04 -070019from chromite.lib import cros_test_lib
20from chromite.lib import dev_server_wrapper
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070021from chromite.lib import osutils
David Pursellf1d16a62015-03-25 13:31:04 -070022from chromite.lib import partial_mock
23from chromite.lib import remote_access
Mike Frysingerdda695b2019-11-23 20:58:59 -050024from chromite.lib import remote_access_unittest
David Pursellf1d16a62015-03-25 13:31:04 -070025
Amin Hassanic0f06fa2019-01-28 15:24:47 -080026from chromite.lib.paygen import paygen_payload_lib
27from chromite.lib.paygen import paygen_stateful_payload_lib
28
David Pursellf1d16a62015-03-25 13:31:04 -070029
Mike Frysinger3f087aa2020-03-20 06:03:16 -040030assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
31
32
David Pursellf1d16a62015-03-25 13:31:04 -070033class RemoteDeviceUpdaterMock(partial_mock.PartialCmdMock):
34 """Mock out RemoteDeviceUpdater."""
Amin Hassani9800d432019-07-24 14:23:39 -070035 TARGET = 'chromite.lib.auto_updater.ChromiumOSUpdater'
xixuane851dfb2016-05-02 18:02:37 -070036 ATTRS = ('UpdateStateful', 'UpdateRootfs', 'SetupRootfsUpdate',
Amin Hassani3e87ce12020-10-22 10:39:36 -070037 'RebootAndVerify')
David Pursellf1d16a62015-03-25 13:31:04 -070038
39 def __init__(self):
40 partial_mock.PartialCmdMock.__init__(self)
41
42 def UpdateStateful(self, _inst, *_args, **_kwargs):
43 """Mock out UpdateStateful."""
44
45 def UpdateRootfs(self, _inst, *_args, **_kwargs):
46 """Mock out UpdateRootfs."""
47
48 def SetupRootfsUpdate(self, _inst, *_args, **_kwargs):
49 """Mock out SetupRootfsUpdate."""
50
xixuane851dfb2016-05-02 18:02:37 -070051 def RebootAndVerify(self, _inst, *_args, **_kwargs):
52 """Mock out RebootAndVerify."""
David Pursellf1d16a62015-03-25 13:31:04 -070053
Sanika Kulkarnie3b177b2019-11-26 14:42:48 -080054
Amin Hassanie0e14b92021-04-01 12:26:07 -070055class TransferMock(partial_mock.PartialCmdMock):
56 """Mock out all transfer functions in auto_updater_transfer.LocalTransfer."""
57 TARGET = 'chromite.lib.auto_updater_transfer.LocalTransfer'
58 ATTRS = ('CheckPayloads',)
59
60 def __init__(self):
61 partial_mock.PartialCmdMock.__init__(self)
62
63 def CheckPayloads(self, _inst, *_args, **_kwargs):
64 """Mock auto_updater_transfer.Transfer.CheckPayloads."""
65
66
Mike Frysingerdda695b2019-11-23 20:58:59 -050067class RemoteAccessMock(remote_access_unittest.RemoteShMock):
68 """Mock out RemoteAccess."""
69
70 ATTRS = ('RemoteSh', 'Rsync', 'Scp')
71
72 def Rsync(self, *_args, **_kwargs):
73 return cros_build_lib.CommandResult(returncode=0)
74
75 def Scp(self, *_args, **_kwargs):
76 return cros_build_lib.CommandResult(returncode=0)
77
78
David Pursellf1d16a62015-03-25 13:31:04 -070079class RemoteDeviceUpdaterTest(cros_test_lib.MockTempDirTestCase):
80 """Test the flow of flash.Flash() with RemoteDeviceUpdater."""
81
82 IMAGE = '/path/to/image'
83 DEVICE = commandline.Device(scheme=commandline.DEVICE_SCHEME_SSH,
Mike Frysingerb5a297f2019-11-23 21:17:41 -050084 hostname=remote_access.TEST_IP)
David Pursellf1d16a62015-03-25 13:31:04 -070085
86 def setUp(self):
87 """Patches objects."""
88 self.updater_mock = self.StartPatcher(RemoteDeviceUpdaterMock())
Amin Hassanie0e14b92021-04-01 12:26:07 -070089 self.transfer_mock = self.StartPatcher(TransferMock())
David Pursellf1d16a62015-03-25 13:31:04 -070090 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070091 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
92 'remote/taco-paladin/R36/test'))
Amin Hassanic0f06fa2019-01-28 15:24:47 -080093 self.PatchObject(paygen_payload_lib, 'GenerateUpdatePayload')
94 self.PatchObject(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
David Pursellf1d16a62015-03-25 13:31:04 -070095 self.PatchObject(remote_access, 'CHECK_INTERVAL', new=0)
Mike Frysingerdda695b2019-11-23 20:58:59 -050096 self.PatchObject(remote_access.ChromiumOSDevice, 'Pingable',
97 return_value=True)
98 m = self.StartPatcher(RemoteAccessMock())
99 m.AddCmdResult(['cat', '/etc/lsb-release'],
100 stdout='CHROMEOS_RELEASE_BOARD=board')
101 m.SetDefaultCmdResult()
David Pursellf1d16a62015-03-25 13:31:04 -0700102
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800103 def _ExistsMock(self, path, ret=True):
104 """Mock function for os.path.exists.
105
106 os.path.exists is used a lot; we only want to mock it for devserver/static,
107 and actually check if the file exists in all other cases (using os.access).
108
109 Args:
110 path: path to check.
111 ret: return value of mock.
112
113 Returns:
114 ret for paths under devserver/static, and the expected value of
115 os.path.exists otherwise.
116 """
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000117 if path.startswith(dev_server_wrapper.DEFAULT_STATIC_DIR):
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800118 return ret
119 return os.access(path, os.F_OK)
120
David Pursellf1d16a62015-03-25 13:31:04 -0700121 def testUpdateAll(self):
122 """Tests that update methods are called correctly."""
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800123 with mock.patch('os.path.exists', side_effect=self._ExistsMock):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -0700124 flash.Flash(self.DEVICE, self.IMAGE)
125 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
126 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -0700127
128 def testUpdateStateful(self):
129 """Tests that update methods are called correctly."""
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800130 with mock.patch('os.path.exists', side_effect=self._ExistsMock):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -0700131 flash.Flash(self.DEVICE, self.IMAGE, rootfs_update=False)
132 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
133 self.assertFalse(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -0700134
135 def testUpdateRootfs(self):
136 """Tests that update methods are called correctly."""
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800137 with mock.patch('os.path.exists', side_effect=self._ExistsMock):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -0700138 flash.Flash(self.DEVICE, self.IMAGE, stateful_update=False)
139 self.assertFalse(self.updater_mock.patched['UpdateStateful'].called)
140 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -0700141
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200142 def testFullPayload(self):
143 """Tests that we download full_payload and stateful using xBuddy."""
144 with mock.patch.object(
145 dev_server_wrapper,
146 'GetImagePathWithXbuddy',
147 return_value=('translated/xbuddy/path',
148 'resolved/xbuddy/path')) as mock_xbuddy:
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800149 with mock.patch('os.path.exists', side_effect=self._ExistsMock):
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200150 flash.Flash(self.DEVICE, self.IMAGE)
151
152 # Call to download full_payload and stateful. No other calls.
153 mock_xbuddy.assert_has_calls(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000154 [mock.call('/path/to/image/full_payload', 'board', None, silent=True),
155 mock.call('/path/to/image/stateful', 'board', None, silent=True)])
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200156 self.assertEqual(mock_xbuddy.call_count, 2)
157
158 def testTestImage(self):
159 """Tests that we download the test image when the full payload fails."""
160 with mock.patch.object(
161 dev_server_wrapper,
162 'GetImagePathWithXbuddy',
163 side_effect=(dev_server_wrapper.ImagePathError,
164 ('translated/xbuddy/path',
165 'resolved/xbuddy/path'))) as mock_xbuddy:
Achuith Bhandarkar1f54a212019-12-09 12:08:35 -0800166 with mock.patch('os.path.exists', side_effect=self._ExistsMock):
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200167 flash.Flash(self.DEVICE, self.IMAGE)
168
169 # Call to download full_payload and image. No other calls.
170 mock_xbuddy.assert_has_calls(
Achuith Bhandarkareda9b222020-05-02 10:36:16 +0000171 [mock.call('/path/to/image/full_payload', 'board', None, silent=True),
172 mock.call('/path/to/image', 'board', None)])
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200173 self.assertEqual(mock_xbuddy.call_count, 2)
174
David Pursellf1d16a62015-03-25 13:31:04 -0700175
176class USBImagerMock(partial_mock.PartialCmdMock):
177 """Mock out USBImager."""
178 TARGET = 'chromite.cli.flash.USBImager'
Amin Hassani04314b12020-12-15 15:59:54 -0800179 ATTRS = ('CopyImageToDevice', 'ChooseRemovableDevice',
180 'ListAllRemovableDevices', 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -0700181 VALID_IMAGE = True
182
183 def __init__(self):
184 partial_mock.PartialCmdMock.__init__(self)
185
186 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
187 """Mock out CopyImageToDevice."""
188
David Pursellf1d16a62015-03-25 13:31:04 -0700189 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
190 """Mock out ChooseRemovableDevice."""
191
192 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
193 """Mock out ListAllRemovableDevices."""
194 return ['foo', 'taco', 'milk']
195
196 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
197 """Mock out GetRemovableDeviceDescription."""
198
David Pursellf1d16a62015-03-25 13:31:04 -0700199
200class USBImagerTest(cros_test_lib.MockTempDirTestCase):
201 """Test the flow of flash.Flash() with USBImager."""
202 IMAGE = '/path/to/image'
203
204 def Device(self, path):
205 """Create a USB device for passing to flash.Flash()."""
206 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
207 path=path)
208
209 def setUp(self):
210 """Patches objects."""
211 self.usb_mock = USBImagerMock()
212 self.imager_mock = self.StartPatcher(self.usb_mock)
David Pursellf1d16a62015-03-25 13:31:04 -0700213 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700214 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
215 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -0700216 self.PatchObject(os.path, 'exists', return_value=True)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700217 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
218 return_value=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700219
220 def testLocalImagePathCopy(self):
221 """Tests that imaging methods are called correctly."""
222 with mock.patch('os.path.isfile', return_value=True):
223 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
224 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
225
David Pursellf1d16a62015-03-25 13:31:04 -0700226 def testLocalBadImagePath(self):
227 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700228 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -0700229 with mock.patch('os.path.isfile', return_value=True):
230 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
231 mock_prompt.return_value = False
232 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
233 self.assertTrue(mock_prompt.called)
234
235 def testNonLocalImagePath(self):
236 """Tests that we try to get the image path using xbuddy."""
237 with mock.patch.object(
238 dev_server_wrapper,
239 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700240 return_value=('translated/xbuddy/path',
241 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -0700242 with mock.patch('os.path.isfile', return_value=False):
243 with mock.patch('os.path.isdir', return_value=False):
244 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
245 self.assertTrue(mock_xbuddy.called)
246
247 def testConfirmNonRemovableDevice(self):
248 """Tests that we ask user to confirm if the device is not removable."""
249 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
250 flash.Flash(self.Device('/dev/dummy'), self.IMAGE)
251 self.assertTrue(mock_prompt.called)
252
253 def testSkipPromptNonRemovableDevice(self):
254 """Tests that we skip the prompt for non-removable with --yes."""
255 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
256 flash.Flash(self.Device('/dev/dummy'), self.IMAGE, yes=True)
257 self.assertFalse(mock_prompt.called)
258
259 def testChooseRemovableDevice(self):
260 """Tests that we ask user to choose a device if none is given."""
261 flash.Flash(self.Device(''), self.IMAGE)
262 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700263
264
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600265class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700266 """Tests for flash.UsbImagerOperation."""
267 # pylint: disable=protected-access
268
269 def setUp(self):
270 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
271
272 def testUsbImagerOperationCalled(self):
273 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
274 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800275 'oflag=direct', 'conv=fdatasync']
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000276 usb_imager = flash.USBImager('dummy_device', 'board', 'foo', 'latest')
Ralph Nathan9b997232015-05-15 13:13:12 -0700277 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
278 self.PatchObject(logging.Logger, 'getEffectiveLevel',
279 return_value=logging.NOTICE)
280 usb_imager.CopyImageToDevice('foo', 'bar')
281
282 # Check that flash.UsbImagerOperation.Run() is called correctly.
Mike Frysinger45602c72019-09-22 02:15:11 -0400283 run_mock.assert_called_with(cros_build_lib.sudo_run, expected_cmd,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400284 debug_level=logging.NOTICE, encoding='utf-8',
285 update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700286
287 def testSudoRunCommandCalled(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400288 """Test that sudo_run is called when log level > NOTICE."""
Ralph Nathan9b997232015-05-15 13:13:12 -0700289 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800290 'oflag=direct', 'conv=fdatasync']
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000291 usb_imager = flash.USBImager('dummy_device', 'board', 'foo', 'latest')
Mike Frysinger45602c72019-09-22 02:15:11 -0400292 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700293 self.PatchObject(logging.Logger, 'getEffectiveLevel',
294 return_value=logging.WARNING)
295 usb_imager.CopyImageToDevice('foo', 'bar')
296
Mike Frysinger45602c72019-09-22 02:15:11 -0400297 # Check that sudo_run() is called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700298 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
299 print_cmd=False)
300
301 def testPingDD(self):
302 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
303 expected_cmd = ['kill', '-USR1', '5']
Mike Frysinger45602c72019-09-22 02:15:11 -0400304 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700305 op = flash.UsbImagerOperation('foo')
306 op._PingDD(5)
307
Mike Frysinger45602c72019-09-22 02:15:11 -0400308 # Check that sudo_run was called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700309 run_mock.assert_called_with(expected_cmd, print_cmd=False)
310
311 def testGetDDPidFound(self):
312 """Check that the expected pid is returned for _GetDDPid()."""
313 expected_pid = 5
314 op = flash.UsbImagerOperation('foo')
315 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
316 self.rc.AddCmdResult(partial_mock.Ignore(),
317 output='%d\n10\n' % expected_pid)
318
319 pid = op._GetDDPid()
320
321 # Check that the correct pid was returned.
322 self.assertEqual(pid, expected_pid)
323
324 def testGetDDPidNotFound(self):
325 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
326 expected_pid = -1
327 op = flash.UsbImagerOperation('foo')
328 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
329 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
330
331 pid = op._GetDDPid()
332
333 # Check that the correct pid was returned.
334 self.assertEqual(pid, expected_pid)
335
336
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700337class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
338 """Tests the helpers from cli.flash."""
339
340 def testChooseImage(self):
341 """Tests that we can detect a GPT image."""
342 # pylint: disable=protected-access
343
344 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
345 # No images defined. Choosing the image should raise an error.
346 with self.assertRaises(ValueError):
347 flash._ChooseImageFromDirectory(self.tempdir)
348
349 file_a = os.path.join(self.tempdir, 'a')
350 osutils.Touch(file_a)
351 # Only one image available, it should be selected automatically.
352 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
353
354 osutils.Touch(os.path.join(self.tempdir, 'b'))
355 file_c = os.path.join(self.tempdir, 'c')
356 osutils.Touch(file_c)
357 osutils.Touch(os.path.join(self.tempdir, 'd'))
358
359 # Multiple images available, we should ask the user to select the right
360 # image.
361 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
362 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500363
364 def testIsFilePathGPTDiskImage(self):
365 """Tests the GPT image probing."""
366 # pylint: disable=protected-access
367
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400368 INVALID_PMBR = b' ' * 0x200
369 INVALID_GPT = b' ' * 0x200
370 VALID_PMBR = (b' ' * 0x1fe) + b'\x55\xaa'
371 VALID_GPT = b'EFI PART' + (b' ' * 0x1f8)
Mike Frysinger32759e42016-12-21 18:40:16 -0500372 TESTCASES = (
373 (False, False, INVALID_PMBR + INVALID_GPT),
374 (False, False, VALID_PMBR + INVALID_GPT),
375 (False, True, INVALID_PMBR + VALID_GPT),
376 (True, True, VALID_PMBR + VALID_GPT),
377 )
378
379 img = os.path.join(self.tempdir, 'img.bin')
380 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400381 osutils.WriteFile(img, data, mode='wb')
Mike Frysinger32759e42016-12-21 18:40:16 -0500382 self.assertEqual(
383 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
384 self.assertEqual(
385 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)