blob: 3e398db9d9e85eb9b9aa0d3baf12a071c01cd244 [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
11
Mike Frysinger6db648e2018-07-24 19:57:58 -040012import mock
13
David Pursellf1d16a62015-03-25 13:31:04 -070014from chromite.cli import flash
Sanika Kulkarnia8c4e3a2019-09-20 16:47:25 -070015from chromite.lib import auto_updater_transfer
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
30class RemoteDeviceUpdaterMock(partial_mock.PartialCmdMock):
31 """Mock out RemoteDeviceUpdater."""
Amin Hassani9800d432019-07-24 14:23:39 -070032 TARGET = 'chromite.lib.auto_updater.ChromiumOSUpdater'
xixuane851dfb2016-05-02 18:02:37 -070033 ATTRS = ('UpdateStateful', 'UpdateRootfs', 'SetupRootfsUpdate',
Amin Hassanif85be122019-09-19 17:10:34 -070034 'RebootAndVerify', 'ResolveAPPIDMismatchIfAny')
David Pursellf1d16a62015-03-25 13:31:04 -070035
36 def __init__(self):
37 partial_mock.PartialCmdMock.__init__(self)
38
39 def UpdateStateful(self, _inst, *_args, **_kwargs):
40 """Mock out UpdateStateful."""
41
42 def UpdateRootfs(self, _inst, *_args, **_kwargs):
43 """Mock out UpdateRootfs."""
44
45 def SetupRootfsUpdate(self, _inst, *_args, **_kwargs):
46 """Mock out SetupRootfsUpdate."""
47
xixuane851dfb2016-05-02 18:02:37 -070048 def RebootAndVerify(self, _inst, *_args, **_kwargs):
49 """Mock out RebootAndVerify."""
David Pursellf1d16a62015-03-25 13:31:04 -070050
Amin Hassanif85be122019-09-19 17:10:34 -070051 def ResolveAPPIDMismatchIfAny(self, _inst, *_args, **_kwargs):
52 """Mock out ResolveAPPIDMismatchIfAny."""
David Pursellf1d16a62015-03-25 13:31:04 -070053
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +020054
Mike Frysingerdda695b2019-11-23 20:58:59 -050055class RemoteAccessMock(remote_access_unittest.RemoteShMock):
56 """Mock out RemoteAccess."""
57
58 ATTRS = ('RemoteSh', 'Rsync', 'Scp')
59
60 def Rsync(self, *_args, **_kwargs):
61 return cros_build_lib.CommandResult(returncode=0)
62
63 def Scp(self, *_args, **_kwargs):
64 return cros_build_lib.CommandResult(returncode=0)
65
66
David Pursellf1d16a62015-03-25 13:31:04 -070067class RemoteDeviceUpdaterTest(cros_test_lib.MockTempDirTestCase):
68 """Test the flow of flash.Flash() with RemoteDeviceUpdater."""
69
70 IMAGE = '/path/to/image'
71 DEVICE = commandline.Device(scheme=commandline.DEVICE_SCHEME_SSH,
Mike Frysingerb5a297f2019-11-23 21:17:41 -050072 hostname=remote_access.TEST_IP)
David Pursellf1d16a62015-03-25 13:31:04 -070073
74 def setUp(self):
75 """Patches objects."""
76 self.updater_mock = self.StartPatcher(RemoteDeviceUpdaterMock())
77 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
78 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -070079 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070080 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
81 'remote/taco-paladin/R36/test'))
Amin Hassanic0f06fa2019-01-28 15:24:47 -080082 self.PatchObject(paygen_payload_lib, 'GenerateUpdatePayload')
83 self.PatchObject(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
David Pursellf1d16a62015-03-25 13:31:04 -070084 self.PatchObject(remote_access, 'CHECK_INTERVAL', new=0)
Mike Frysingerdda695b2019-11-23 20:58:59 -050085 self.PatchObject(remote_access.ChromiumOSDevice, 'Pingable',
86 return_value=True)
87 m = self.StartPatcher(RemoteAccessMock())
88 m.AddCmdResult(['cat', '/etc/lsb-release'],
89 stdout='CHROMEOS_RELEASE_BOARD=board')
90 m.SetDefaultCmdResult()
David Pursellf1d16a62015-03-25 13:31:04 -070091
92 def testUpdateAll(self):
93 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070094 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070095 flash.Flash(self.DEVICE, self.IMAGE)
96 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
97 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070098
99 def testUpdateStateful(self):
100 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -0700101 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -0700102 flash.Flash(self.DEVICE, self.IMAGE, rootfs_update=False)
103 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
104 self.assertFalse(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -0700105
106 def testUpdateRootfs(self):
107 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -0700108 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -0700109 flash.Flash(self.DEVICE, self.IMAGE, stateful_update=False)
110 self.assertFalse(self.updater_mock.patched['UpdateStateful'].called)
111 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -0700112
113 def testMissingPayloads(self):
114 """Tests we raise FlashError when payloads are missing."""
115 with mock.patch('os.path.exists', return_value=False):
Sanika Kulkarnia8c4e3a2019-09-20 16:47:25 -0700116 self.assertRaises(auto_updater_transfer.ChromiumOSTransferError,
117 flash.Flash, self.DEVICE, self.IMAGE)
David Pursellf1d16a62015-03-25 13:31:04 -0700118
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200119 def testFullPayload(self):
120 """Tests that we download full_payload and stateful using xBuddy."""
121 with mock.patch.object(
122 dev_server_wrapper,
123 'GetImagePathWithXbuddy',
124 return_value=('translated/xbuddy/path',
125 'resolved/xbuddy/path')) as mock_xbuddy:
126 with mock.patch('os.path.exists', return_value=True):
127 flash.Flash(self.DEVICE, self.IMAGE)
128
129 # Call to download full_payload and stateful. No other calls.
130 mock_xbuddy.assert_has_calls(
131 [mock.call('/path/to/image/full_payload', mock.ANY,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800132 static_dir=flash.DEVSERVER_STATIC_DIR, silent=True),
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200133 mock.call('/path/to/image/stateful', mock.ANY,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800134 static_dir=flash.DEVSERVER_STATIC_DIR, silent=True)])
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200135 self.assertEqual(mock_xbuddy.call_count, 2)
136
137 def testTestImage(self):
138 """Tests that we download the test image when the full payload fails."""
139 with mock.patch.object(
140 dev_server_wrapper,
141 'GetImagePathWithXbuddy',
142 side_effect=(dev_server_wrapper.ImagePathError,
143 ('translated/xbuddy/path',
144 'resolved/xbuddy/path'))) as mock_xbuddy:
145 with mock.patch('os.path.exists', return_value=True):
146 flash.Flash(self.DEVICE, self.IMAGE)
147
148 # Call to download full_payload and image. No other calls.
149 mock_xbuddy.assert_has_calls(
150 [mock.call('/path/to/image/full_payload', mock.ANY,
Achuith Bhandarkar12f43c72019-11-21 16:44:24 -0800151 static_dir=flash.DEVSERVER_STATIC_DIR, silent=True),
Achuith Bhandarkar4d4275f2019-10-01 17:07:23 +0200152 mock.call('/path/to/image', mock.ANY,
153 static_dir=flash.DEVSERVER_STATIC_DIR)])
154 self.assertEqual(mock_xbuddy.call_count, 2)
155
David Pursellf1d16a62015-03-25 13:31:04 -0700156
157class USBImagerMock(partial_mock.PartialCmdMock):
158 """Mock out USBImager."""
159 TARGET = 'chromite.cli.flash.USBImager'
160 ATTRS = ('CopyImageToDevice', 'InstallImageToDevice',
161 'ChooseRemovableDevice', 'ListAllRemovableDevices',
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700162 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -0700163 VALID_IMAGE = True
164
165 def __init__(self):
166 partial_mock.PartialCmdMock.__init__(self)
167
168 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
169 """Mock out CopyImageToDevice."""
170
171 def InstallImageToDevice(self, _inst, *_args, **_kwargs):
172 """Mock out InstallImageToDevice."""
173
174 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
175 """Mock out ChooseRemovableDevice."""
176
177 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
178 """Mock out ListAllRemovableDevices."""
179 return ['foo', 'taco', 'milk']
180
181 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
182 """Mock out GetRemovableDeviceDescription."""
183
David Pursellf1d16a62015-03-25 13:31:04 -0700184
185class USBImagerTest(cros_test_lib.MockTempDirTestCase):
186 """Test the flow of flash.Flash() with USBImager."""
187 IMAGE = '/path/to/image'
188
189 def Device(self, path):
190 """Create a USB device for passing to flash.Flash()."""
191 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
192 path=path)
193
194 def setUp(self):
195 """Patches objects."""
196 self.usb_mock = USBImagerMock()
197 self.imager_mock = self.StartPatcher(self.usb_mock)
198 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
199 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -0700200 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700201 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
202 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -0700203 self.PatchObject(os.path, 'exists', return_value=True)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700204 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
205 return_value=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700206
207 def testLocalImagePathCopy(self):
208 """Tests that imaging methods are called correctly."""
209 with mock.patch('os.path.isfile', return_value=True):
210 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
211 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
212
213 def testLocalImagePathInstall(self):
214 """Tests that imaging methods are called correctly."""
215 with mock.patch('os.path.isfile', return_value=True):
216 flash.Flash(self.Device('/dev/foo'), self.IMAGE, board='taco',
217 install=True)
218 self.assertTrue(self.imager_mock.patched['InstallImageToDevice'].called)
219
220 def testLocalBadImagePath(self):
221 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700222 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -0700223 with mock.patch('os.path.isfile', return_value=True):
224 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
225 mock_prompt.return_value = False
226 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
227 self.assertTrue(mock_prompt.called)
228
229 def testNonLocalImagePath(self):
230 """Tests that we try to get the image path using xbuddy."""
231 with mock.patch.object(
232 dev_server_wrapper,
233 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700234 return_value=('translated/xbuddy/path',
235 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -0700236 with mock.patch('os.path.isfile', return_value=False):
237 with mock.patch('os.path.isdir', return_value=False):
238 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
239 self.assertTrue(mock_xbuddy.called)
240
241 def testConfirmNonRemovableDevice(self):
242 """Tests that we ask user to confirm if the device is not removable."""
243 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
244 flash.Flash(self.Device('/dev/dummy'), self.IMAGE)
245 self.assertTrue(mock_prompt.called)
246
247 def testSkipPromptNonRemovableDevice(self):
248 """Tests that we skip the prompt for non-removable with --yes."""
249 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
250 flash.Flash(self.Device('/dev/dummy'), self.IMAGE, yes=True)
251 self.assertFalse(mock_prompt.called)
252
253 def testChooseRemovableDevice(self):
254 """Tests that we ask user to choose a device if none is given."""
255 flash.Flash(self.Device(''), self.IMAGE)
256 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700257
258
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600259class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700260 """Tests for flash.UsbImagerOperation."""
261 # pylint: disable=protected-access
262
263 def setUp(self):
264 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
265
266 def testUsbImagerOperationCalled(self):
267 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
268 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800269 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700270 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
271 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
272 self.PatchObject(logging.Logger, 'getEffectiveLevel',
273 return_value=logging.NOTICE)
274 usb_imager.CopyImageToDevice('foo', 'bar')
275
276 # Check that flash.UsbImagerOperation.Run() is called correctly.
Mike Frysinger45602c72019-09-22 02:15:11 -0400277 run_mock.assert_called_with(cros_build_lib.sudo_run, expected_cmd,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400278 debug_level=logging.NOTICE, encoding='utf-8',
279 update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700280
281 def testSudoRunCommandCalled(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400282 """Test that sudo_run is called when log level > NOTICE."""
Ralph Nathan9b997232015-05-15 13:13:12 -0700283 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800284 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700285 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
Mike Frysinger45602c72019-09-22 02:15:11 -0400286 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700287 self.PatchObject(logging.Logger, 'getEffectiveLevel',
288 return_value=logging.WARNING)
289 usb_imager.CopyImageToDevice('foo', 'bar')
290
Mike Frysinger45602c72019-09-22 02:15:11 -0400291 # Check that sudo_run() is called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700292 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
293 print_cmd=False)
294
295 def testPingDD(self):
296 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
297 expected_cmd = ['kill', '-USR1', '5']
Mike Frysinger45602c72019-09-22 02:15:11 -0400298 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700299 op = flash.UsbImagerOperation('foo')
300 op._PingDD(5)
301
Mike Frysinger45602c72019-09-22 02:15:11 -0400302 # Check that sudo_run was called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700303 run_mock.assert_called_with(expected_cmd, print_cmd=False)
304
305 def testGetDDPidFound(self):
306 """Check that the expected pid is returned for _GetDDPid()."""
307 expected_pid = 5
308 op = flash.UsbImagerOperation('foo')
309 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
310 self.rc.AddCmdResult(partial_mock.Ignore(),
311 output='%d\n10\n' % expected_pid)
312
313 pid = op._GetDDPid()
314
315 # Check that the correct pid was returned.
316 self.assertEqual(pid, expected_pid)
317
318 def testGetDDPidNotFound(self):
319 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
320 expected_pid = -1
321 op = flash.UsbImagerOperation('foo')
322 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
323 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
324
325 pid = op._GetDDPid()
326
327 # Check that the correct pid was returned.
328 self.assertEqual(pid, expected_pid)
329
330
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700331class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
332 """Tests the helpers from cli.flash."""
333
334 def testChooseImage(self):
335 """Tests that we can detect a GPT image."""
336 # pylint: disable=protected-access
337
338 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
339 # No images defined. Choosing the image should raise an error.
340 with self.assertRaises(ValueError):
341 flash._ChooseImageFromDirectory(self.tempdir)
342
343 file_a = os.path.join(self.tempdir, 'a')
344 osutils.Touch(file_a)
345 # Only one image available, it should be selected automatically.
346 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
347
348 osutils.Touch(os.path.join(self.tempdir, 'b'))
349 file_c = os.path.join(self.tempdir, 'c')
350 osutils.Touch(file_c)
351 osutils.Touch(os.path.join(self.tempdir, 'd'))
352
353 # Multiple images available, we should ask the user to select the right
354 # image.
355 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
356 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500357
358 def testIsFilePathGPTDiskImage(self):
359 """Tests the GPT image probing."""
360 # pylint: disable=protected-access
361
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400362 INVALID_PMBR = b' ' * 0x200
363 INVALID_GPT = b' ' * 0x200
364 VALID_PMBR = (b' ' * 0x1fe) + b'\x55\xaa'
365 VALID_GPT = b'EFI PART' + (b' ' * 0x1f8)
Mike Frysinger32759e42016-12-21 18:40:16 -0500366 TESTCASES = (
367 (False, False, INVALID_PMBR + INVALID_GPT),
368 (False, False, VALID_PMBR + INVALID_GPT),
369 (False, True, INVALID_PMBR + VALID_GPT),
370 (True, True, VALID_PMBR + VALID_GPT),
371 )
372
373 img = os.path.join(self.tempdir, 'img.bin')
374 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400375 osutils.WriteFile(img, data, mode='wb')
Mike Frysinger32759e42016-12-21 18:40:16 -0500376 self.assertEqual(
377 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
378 self.assertEqual(
379 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)