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