blob: 02cf06aecac219b5bcb9bc852d47145b69847eb8 [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',
Amin Hassanif85be122019-09-19 17:10:34 -070033 'RebootAndVerify', 'ResolveAPPIDMismatchIfAny')
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
Amin Hassanif85be122019-09-19 17:10:34 -070050 def ResolveAPPIDMismatchIfAny(self, _inst, *_args, **_kwargs):
51 """Mock out ResolveAPPIDMismatchIfAny."""
David Pursellf1d16a62015-03-25 13:31:04 -070052
David Pursellf1d16a62015-03-25 13:31:04 -070053class RemoteDeviceUpdaterTest(cros_test_lib.MockTempDirTestCase):
54 """Test the flow of flash.Flash() with RemoteDeviceUpdater."""
55
56 IMAGE = '/path/to/image'
57 DEVICE = commandline.Device(scheme=commandline.DEVICE_SCHEME_SSH,
58 hostname='1.1.1.1')
59
60 def setUp(self):
61 """Patches objects."""
62 self.updater_mock = self.StartPatcher(RemoteDeviceUpdaterMock())
63 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
64 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -070065 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070066 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
67 'remote/taco-paladin/R36/test'))
Amin Hassanic0f06fa2019-01-28 15:24:47 -080068 self.PatchObject(paygen_payload_lib, 'GenerateUpdatePayload')
69 self.PatchObject(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
David Pursellf1d16a62015-03-25 13:31:04 -070070 self.PatchObject(remote_access, 'CHECK_INTERVAL', new=0)
71 self.PatchObject(remote_access, 'ChromiumOSDevice')
72
73 def testUpdateAll(self):
74 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070075 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070076 flash.Flash(self.DEVICE, self.IMAGE)
77 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
78 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070079
80 def testUpdateStateful(self):
81 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070082 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070083 flash.Flash(self.DEVICE, self.IMAGE, rootfs_update=False)
84 self.assertTrue(self.updater_mock.patched['UpdateStateful'].called)
85 self.assertFalse(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070086
87 def testUpdateRootfs(self):
88 """Tests that update methods are called correctly."""
David Pursellf1d16a62015-03-25 13:31:04 -070089 with mock.patch('os.path.exists', return_value=True):
Bertrand SIMONNETb34a98b2015-04-22 14:30:04 -070090 flash.Flash(self.DEVICE, self.IMAGE, stateful_update=False)
91 self.assertFalse(self.updater_mock.patched['UpdateStateful'].called)
92 self.assertTrue(self.updater_mock.patched['UpdateRootfs'].called)
David Pursellf1d16a62015-03-25 13:31:04 -070093
94 def testMissingPayloads(self):
95 """Tests we raise FlashError when payloads are missing."""
96 with mock.patch('os.path.exists', return_value=False):
xixuane851dfb2016-05-02 18:02:37 -070097 self.assertRaises(auto_updater.ChromiumOSUpdateError, flash.Flash,
98 self.DEVICE, self.IMAGE)
David Pursellf1d16a62015-03-25 13:31:04 -070099
David Pursellf1d16a62015-03-25 13:31:04 -0700100
101class USBImagerMock(partial_mock.PartialCmdMock):
102 """Mock out USBImager."""
103 TARGET = 'chromite.cli.flash.USBImager'
104 ATTRS = ('CopyImageToDevice', 'InstallImageToDevice',
105 'ChooseRemovableDevice', 'ListAllRemovableDevices',
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700106 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -0700107 VALID_IMAGE = True
108
109 def __init__(self):
110 partial_mock.PartialCmdMock.__init__(self)
111
112 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
113 """Mock out CopyImageToDevice."""
114
115 def InstallImageToDevice(self, _inst, *_args, **_kwargs):
116 """Mock out InstallImageToDevice."""
117
118 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
119 """Mock out ChooseRemovableDevice."""
120
121 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
122 """Mock out ListAllRemovableDevices."""
123 return ['foo', 'taco', 'milk']
124
125 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
126 """Mock out GetRemovableDeviceDescription."""
127
David Pursellf1d16a62015-03-25 13:31:04 -0700128
129class USBImagerTest(cros_test_lib.MockTempDirTestCase):
130 """Test the flow of flash.Flash() with USBImager."""
131 IMAGE = '/path/to/image'
132
133 def Device(self, path):
134 """Create a USB device for passing to flash.Flash()."""
135 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
136 path=path)
137
138 def setUp(self):
139 """Patches objects."""
140 self.usb_mock = USBImagerMock()
141 self.imager_mock = self.StartPatcher(self.usb_mock)
142 self.PatchObject(dev_server_wrapper, 'GenerateXbuddyRequest',
143 return_value='xbuddy/local/latest')
David Pursellf1d16a62015-03-25 13:31:04 -0700144 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700145 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
146 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -0700147 self.PatchObject(os.path, 'exists', return_value=True)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700148 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
149 return_value=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700150
151 def testLocalImagePathCopy(self):
152 """Tests that imaging methods are called correctly."""
153 with mock.patch('os.path.isfile', return_value=True):
154 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
155 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
156
157 def testLocalImagePathInstall(self):
158 """Tests that imaging methods are called correctly."""
159 with mock.patch('os.path.isfile', return_value=True):
160 flash.Flash(self.Device('/dev/foo'), self.IMAGE, board='taco',
161 install=True)
162 self.assertTrue(self.imager_mock.patched['InstallImageToDevice'].called)
163
164 def testLocalBadImagePath(self):
165 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700166 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -0700167 with mock.patch('os.path.isfile', return_value=True):
168 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
169 mock_prompt.return_value = False
170 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
171 self.assertTrue(mock_prompt.called)
172
173 def testNonLocalImagePath(self):
174 """Tests that we try to get the image path using xbuddy."""
175 with mock.patch.object(
176 dev_server_wrapper,
177 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -0700178 return_value=('translated/xbuddy/path',
179 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -0700180 with mock.patch('os.path.isfile', return_value=False):
181 with mock.patch('os.path.isdir', return_value=False):
182 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
183 self.assertTrue(mock_xbuddy.called)
184
185 def testConfirmNonRemovableDevice(self):
186 """Tests that we ask user to confirm if the device is not removable."""
187 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
188 flash.Flash(self.Device('/dev/dummy'), self.IMAGE)
189 self.assertTrue(mock_prompt.called)
190
191 def testSkipPromptNonRemovableDevice(self):
192 """Tests that we skip the prompt for non-removable with --yes."""
193 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
194 flash.Flash(self.Device('/dev/dummy'), self.IMAGE, yes=True)
195 self.assertFalse(mock_prompt.called)
196
197 def testChooseRemovableDevice(self):
198 """Tests that we ask user to choose a device if none is given."""
199 flash.Flash(self.Device(''), self.IMAGE)
200 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700201
202
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600203class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700204 """Tests for flash.UsbImagerOperation."""
205 # pylint: disable=protected-access
206
207 def setUp(self):
208 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
209
210 def testUsbImagerOperationCalled(self):
211 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
212 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800213 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700214 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
215 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
216 self.PatchObject(logging.Logger, 'getEffectiveLevel',
217 return_value=logging.NOTICE)
218 usb_imager.CopyImageToDevice('foo', 'bar')
219
220 # Check that flash.UsbImagerOperation.Run() is called correctly.
221 run_mock.assert_called_with(cros_build_lib.SudoRunCommand, expected_cmd,
222 debug_level=logging.NOTICE, update_period=0.5)
223
224 def testSudoRunCommandCalled(self):
225 """Test that SudoRunCommand is called when log level > NOTICE."""
226 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800227 'oflag=direct', 'conv=fdatasync']
Ralph Nathan9b997232015-05-15 13:13:12 -0700228 usb_imager = flash.USBImager('dummy_device', 'board', 'foo')
229 run_mock = self.PatchObject(cros_build_lib, 'SudoRunCommand')
230 self.PatchObject(logging.Logger, 'getEffectiveLevel',
231 return_value=logging.WARNING)
232 usb_imager.CopyImageToDevice('foo', 'bar')
233
234 # Check that SudoRunCommand() is called correctly.
235 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
236 print_cmd=False)
237
238 def testPingDD(self):
239 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
240 expected_cmd = ['kill', '-USR1', '5']
241 run_mock = self.PatchObject(cros_build_lib, 'SudoRunCommand')
242 op = flash.UsbImagerOperation('foo')
243 op._PingDD(5)
244
245 # Check that SudoRunCommand was called correctly.
246 run_mock.assert_called_with(expected_cmd, print_cmd=False)
247
248 def testGetDDPidFound(self):
249 """Check that the expected pid is returned for _GetDDPid()."""
250 expected_pid = 5
251 op = flash.UsbImagerOperation('foo')
252 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
253 self.rc.AddCmdResult(partial_mock.Ignore(),
254 output='%d\n10\n' % expected_pid)
255
256 pid = op._GetDDPid()
257
258 # Check that the correct pid was returned.
259 self.assertEqual(pid, expected_pid)
260
261 def testGetDDPidNotFound(self):
262 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
263 expected_pid = -1
264 op = flash.UsbImagerOperation('foo')
265 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
266 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
267
268 pid = op._GetDDPid()
269
270 # Check that the correct pid was returned.
271 self.assertEqual(pid, expected_pid)
272
273
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700274class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
275 """Tests the helpers from cli.flash."""
276
277 def testChooseImage(self):
278 """Tests that we can detect a GPT image."""
279 # pylint: disable=protected-access
280
281 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
282 # No images defined. Choosing the image should raise an error.
283 with self.assertRaises(ValueError):
284 flash._ChooseImageFromDirectory(self.tempdir)
285
286 file_a = os.path.join(self.tempdir, 'a')
287 osutils.Touch(file_a)
288 # Only one image available, it should be selected automatically.
289 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
290
291 osutils.Touch(os.path.join(self.tempdir, 'b'))
292 file_c = os.path.join(self.tempdir, 'c')
293 osutils.Touch(file_c)
294 osutils.Touch(os.path.join(self.tempdir, 'd'))
295
296 # Multiple images available, we should ask the user to select the right
297 # image.
298 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
299 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500300
301 def testIsFilePathGPTDiskImage(self):
302 """Tests the GPT image probing."""
303 # pylint: disable=protected-access
304
305 INVALID_PMBR = ' ' * 0x200
306 INVALID_GPT = ' ' * 0x200
307 VALID_PMBR = (' ' * 0x1fe) + '\x55\xaa'
308 VALID_GPT = 'EFI PART' + (' ' * 0x1f8)
309 TESTCASES = (
310 (False, False, INVALID_PMBR + INVALID_GPT),
311 (False, False, VALID_PMBR + INVALID_GPT),
312 (False, True, INVALID_PMBR + VALID_GPT),
313 (True, True, VALID_PMBR + VALID_GPT),
314 )
315
316 img = os.path.join(self.tempdir, 'img.bin')
317 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
318 osutils.WriteFile(img, data)
319 self.assertEqual(
320 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
321 self.assertEqual(
322 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)