blob: 5894d79caa00556d1a77388abfe63400a79ab777 [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
10import mock
11import os
12
13from chromite.cli import flash
xixuane851dfb2016-05-02 18:02:37 -070014from chromite.lib import auto_updater
David Pursellf1d16a62015-03-25 13:31:04 -070015from chromite.lib import commandline
16from chromite.lib import cros_build_lib
Ralph Nathan9b997232015-05-15 13:13:12 -070017from chromite.lib import cros_logging as logging
David Pursellf1d16a62015-03-25 13:31:04 -070018from chromite.lib import cros_test_lib
19from chromite.lib import dev_server_wrapper
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070020from chromite.lib import osutils
David Pursellf1d16a62015-03-25 13:31:04 -070021from chromite.lib import partial_mock
22from chromite.lib import remote_access
23
Amin Hassanic0f06fa2019-01-28 15:24:47 -080024from chromite.lib.paygen import paygen_payload_lib
25from chromite.lib.paygen import paygen_stateful_payload_lib
26
David Pursellf1d16a62015-03-25 13:31:04 -070027
28class RemoteDeviceUpdaterMock(partial_mock.PartialCmdMock):
29 """Mock out RemoteDeviceUpdater."""
xixuan9a157272016-05-31 11:35:13 -070030 TARGET = 'chromite.lib.auto_updater.ChromiumOSFlashUpdater'
xixuane851dfb2016-05-02 18:02:37 -070031 ATTRS = ('UpdateStateful', 'UpdateRootfs', 'SetupRootfsUpdate',
32 'RebootAndVerify')
David Pursellf1d16a62015-03-25 13:31:04 -070033
34 def __init__(self):
35 partial_mock.PartialCmdMock.__init__(self)
36
37 def UpdateStateful(self, _inst, *_args, **_kwargs):
38 """Mock out UpdateStateful."""
39
40 def UpdateRootfs(self, _inst, *_args, **_kwargs):
41 """Mock out UpdateRootfs."""
42
43 def SetupRootfsUpdate(self, _inst, *_args, **_kwargs):
44 """Mock out SetupRootfsUpdate."""
45
xixuane851dfb2016-05-02 18:02:37 -070046 def RebootAndVerify(self, _inst, *_args, **_kwargs):
47 """Mock out RebootAndVerify."""
David Pursellf1d16a62015-03-25 13:31:04 -070048
49
David Pursellf1d16a62015-03-25 13:31:04 -070050class RemoteDeviceUpdaterTest(cros_test_lib.MockTempDirTestCase):
51 """Test the flow of flash.Flash() with RemoteDeviceUpdater."""
52
53 IMAGE = '/path/to/image'
54 DEVICE = commandline.Device(scheme=commandline.DEVICE_SCHEME_SSH,
55 hostname='1.1.1.1')
56
57 def setUp(self):
58 """Patches objects."""
59 self.updater_mock = self.StartPatcher(RemoteDeviceUpdaterMock())
60 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
61 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -070062 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070063 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
64 'remote/taco-paladin/R36/test'))
Amin Hassanic0f06fa2019-01-28 15:24:47 -080065 self.PatchObject(paygen_payload_lib, 'GenerateUpdatePayload')
66 self.PatchObject(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
David Pursellf1d16a62015-03-25 13:31:04 -070067 self.PatchObject(remote_access, 'CHECK_INTERVAL', new=0)
68 self.PatchObject(remote_access, 'ChromiumOSDevice')
69
70 def testUpdateAll(self):
71 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070072 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070073 flash.Flash(self.DEVICE, self.IMAGE)
74 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
75 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070076
77 def testUpdateStateful(self):
78 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070079 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070080 flash.Flash(self.DEVICE, self.IMAGE, rootfs_update=False)
81 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
82 self.assertFalse(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070083
84 def testUpdateRootfs(self):
85 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070086 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070087 flash.Flash(self.DEVICE, self.IMAGE, stateful_update=False)
88 self.assertFalse(self.updater_mock.patched['UpdateStateful'].called)
89 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070090
91 def testMissingPayloads(self):
92 """Tests we raise FlashError when payloads are missing."""
93 with mock.patch('os.path.exists', return_value=False):
xixuane851dfb2016-05-02 18:02:37 -070094 self.assertRaises(auto_updater.ChromiumOSUpdateError, flash.Flash,
95 self.DEVICE, self.IMAGE)
David Pursellf1d16a62015-03-25 13:31:04 -070096
David Pursellf1d16a62015-03-25 13:31:04 -070097
98class USBImagerMock(partial_mock.PartialCmdMock):
99 """Mock out USBImager."""
100 TARGET = 'chromite.cli.flash.USBImager'
101 ATTRS = ('CopyImageToDevice', 'InstallImageToDevice',
102 'ChooseRemovableDevice', 'ListAllRemovableDevices',
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700103 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -0700104 VALID_IMAGE = True
105
106 def __init__(self):
107 partial_mock.PartialCmdMock.__init__(self)
108
109 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
110 """Mock out CopyImageToDevice."""
111
112 def InstallImageToDevice(self, _inst, *_args, **_kwargs):
113 """Mock out InstallImageToDevice."""
114
115 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
116 """Mock out ChooseRemovableDevice."""
117
118 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
119 """Mock out ListAllRemovableDevices."""
120 return ['foo', 'taco', 'milk']
121
122 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
123 """Mock out GetRemovableDeviceDescription."""
124
David Pursellf1d16a62015-03-25 13:31:04 -0700125
126class USBImagerTest(cros_test_lib.MockTempDirTestCase):
127 """Test the flow of flash.Flash() with USBImager."""
128 IMAGE = '/path/to/image'
129
130 def Device(self, path):
131 """Create a USB device for passing to flash.Flash()."""
132 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
133 path=path)
134
135 def setUp(self):
136 """Patches objects."""
137 self.usb_mock = USBImagerMock()
138 self.imager_mock = self.StartPatcher(self.usb_mock)
139 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
140 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -0700141 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700142 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
143 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -0700144 self.PatchObject(os.path, 'exists', return_value=True)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700145 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
146 return_value=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700147
148 def testLocalImagePathCopy(self):
149 """Tests that imaging methods are called correctly."""
150 with mock.patch('os.path.isfile', return_value=True):
151 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
152 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
153
154 def testLocalImagePathInstall(self):
155 """Tests that imaging methods are called correctly."""
156 with mock.patch('os.path.isfile', return_value=True):
157 flash.Flash(self.Device('/dev/foo'), self.IMAGE, board='taco',
158 install=True)
159 self.assertTrue(self.imager_mock.patched['InstallImageToDevice'].called)
160
161 def testLocalBadImagePath(self):
162 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700163 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -0700164 with mock.patch('os.path.isfile', return_value=True):
165 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
166 mock_prompt.return_value = False
167 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
168 self.assertTrue(mock_prompt.called)
169
170 def testNonLocalImagePath(self):
171 """Tests that we try to get the image path using xbuddy."""
172 with mock.patch.object(
173 dev_server_wrapper,
174 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700175 return_value=('translated/xbuddy/path',
176 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -0700177 with mock.patch('os.path.isfile', return_value=False):
178 with mock.patch('os.path.isdir', return_value=False):
179 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
180 self.assertTrue(mock_xbuddy.called)
181
182 def testConfirmNonRemovableDevice(self):
183 """Tests that we ask user to confirm if the device is not removable."""
184 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
185 flash.Flash(self.Device('/dev/dummy'), self.IMAGE)
186 self.assertTrue(mock_prompt.called)
187
188 def testSkipPromptNonRemovableDevice(self):
189 """Tests that we skip the prompt for non-removable with --yes."""
190 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
191 flash.Flash(self.Device('/dev/dummy'), self.IMAGE, yes=True)
192 self.assertFalse(mock_prompt.called)
193
194 def testChooseRemovableDevice(self):
195 """Tests that we ask user to choose a device if none is given."""
196 flash.Flash(self.Device(''), self.IMAGE)
197 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700198
199
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600200class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700201 """Tests for flash.UsbImagerOperation."""
202 # pylint: disable=protected-access
203
204 def setUp(self):
205 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
206
207 def testUsbImagerOperationCalled(self):
208 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
209 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
210 'oflag=sync']
211 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
212 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
213 self.PatchObject(logging.Logger, 'getEffectiveLevel',
214 return_value=logging.NOTICE)
215 usb_imager.CopyImageToDevice('foo', 'bar')
216
217 # Check that flash.UsbImagerOperation.Run() is called correctly.
218 run_mock.assert_called_with(cros_build_lib.SudoRunCommand, expected_cmd,
219 debug_level=logging.NOTICE, update_period=0.5)
220
221 def testSudoRunCommandCalled(self):
222 """Test that SudoRunCommand is called when log level > NOTICE."""
223 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
224 'oflag=sync']
225 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
226 run_mock = self.PatchObject(cros_build_lib, 'SudoRunCommand')
227 self.PatchObject(logging.Logger, 'getEffectiveLevel',
228 return_value=logging.WARNING)
229 usb_imager.CopyImageToDevice('foo', 'bar')
230
231 # Check that SudoRunCommand() is called correctly.
232 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
233 print_cmd=False)
234
235 def testPingDD(self):
236 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
237 expected_cmd = ['kill', '-USR1', '5']
238 run_mock = self.PatchObject(cros_build_lib, 'SudoRunCommand')
239 op = flash.UsbImagerOperation('foo')
240 op._PingDD(5)
241
242 # Check that SudoRunCommand was called correctly.
243 run_mock.assert_called_with(expected_cmd, print_cmd=False)
244
245 def testGetDDPidFound(self):
246 """Check that the expected pid is returned for _GetDDPid()."""
247 expected_pid = 5
248 op = flash.UsbImagerOperation('foo')
249 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
250 self.rc.AddCmdResult(partial_mock.Ignore(),
251 output='%d\n10\n' % expected_pid)
252
253 pid = op._GetDDPid()
254
255 # Check that the correct pid was returned.
256 self.assertEqual(pid, expected_pid)
257
258 def testGetDDPidNotFound(self):
259 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
260 expected_pid = -1
261 op = flash.UsbImagerOperation('foo')
262 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
263 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
264
265 pid = op._GetDDPid()
266
267 # Check that the correct pid was returned.
268 self.assertEqual(pid, expected_pid)
269
270
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700271class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
272 """Tests the helpers from cli.flash."""
273
274 def testChooseImage(self):
275 """Tests that we can detect a GPT image."""
276 # pylint: disable=protected-access
277
278 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
279 # No images defined. Choosing the image should raise an error.
280 with self.assertRaises(ValueError):
281 flash._ChooseImageFromDirectory(self.tempdir)
282
283 file_a = os.path.join(self.tempdir, 'a')
284 osutils.Touch(file_a)
285 # Only one image available, it should be selected automatically.
286 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
287
288 osutils.Touch(os.path.join(self.tempdir, 'b'))
289 file_c = os.path.join(self.tempdir, 'c')
290 osutils.Touch(file_c)
291 osutils.Touch(os.path.join(self.tempdir, 'd'))
292
293 # Multiple images available, we should ask the user to select the right
294 # image.
295 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
296 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500297
298 def testIsFilePathGPTDiskImage(self):
299 """Tests the GPT image probing."""
300 # pylint: disable=protected-access
301
302 INVALID_PMBR = ' ' * 0x200
303 INVALID_GPT = ' ' * 0x200
304 VALID_PMBR = (' ' * 0x1fe) + '\x55\xaa'
305 VALID_GPT = 'EFI PART' + (' ' * 0x1f8)
306 TESTCASES = (
307 (False, False, INVALID_PMBR + INVALID_GPT),
308 (False, False, VALID_PMBR + INVALID_GPT),
309 (False, True, INVALID_PMBR + VALID_GPT),
310 (True, True, VALID_PMBR + VALID_GPT),
311 )
312
313 img = os.path.join(self.tempdir, 'img.bin')
314 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
315 osutils.WriteFile(img, data)
316 self.assertEqual(
317 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
318 self.assertEqual(
319 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)