blob: 3aef0f3f1a0551876d930fb90bf4b87495c15c8e [file] [log] [blame]
David Pursellf1d16a62015-03-25 13:31:04 -07001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unit tests for the flash module."""
6
Chris McDonald14ac61d2021-07-21 11:49:56 -06007import logging
David Pursellf1d16a62015-03-25 13:31:04 -07008import os
Mike Frysinger166fea02021-02-12 05:30:33 -05009from unittest import mock
David Pursellf1d16a62015-03-25 13:31:04 -070010
11from chromite.cli import flash
David Pursellf1d16a62015-03-25 13:31:04 -070012from chromite.lib import commandline
13from chromite.lib import cros_build_lib
14from chromite.lib import cros_test_lib
15from chromite.lib import dev_server_wrapper
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070016from chromite.lib import osutils
David Pursellf1d16a62015-03-25 13:31:04 -070017from chromite.lib import partial_mock
Amin Hassanic0f06fa2019-01-28 15:24:47 -080018
David Pursellf1d16a62015-03-25 13:31:04 -070019
David Pursellf1d16a62015-03-25 13:31:04 -070020class USBImagerMock(partial_mock.PartialCmdMock):
21 """Mock out USBImager."""
22 TARGET = 'chromite.cli.flash.USBImager'
Amin Hassani04314b12020-12-15 15:59:54 -080023 ATTRS = ('CopyImageToDevice', 'ChooseRemovableDevice',
24 'ListAllRemovableDevices', 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -070025 VALID_IMAGE = True
26
27 def __init__(self):
28 partial_mock.PartialCmdMock.__init__(self)
29
30 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
31 """Mock out CopyImageToDevice."""
32
David Pursellf1d16a62015-03-25 13:31:04 -070033 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
34 """Mock out ChooseRemovableDevice."""
35
36 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
37 """Mock out ListAllRemovableDevices."""
38 return ['foo', 'taco', 'milk']
39
40 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
41 """Mock out GetRemovableDeviceDescription."""
42
David Pursellf1d16a62015-03-25 13:31:04 -070043
44class USBImagerTest(cros_test_lib.MockTempDirTestCase):
45 """Test the flow of flash.Flash() with USBImager."""
46 IMAGE = '/path/to/image'
47
48 def Device(self, path):
49 """Create a USB device for passing to flash.Flash()."""
50 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
51 path=path)
52
53 def setUp(self):
54 """Patches objects."""
55 self.usb_mock = USBImagerMock()
56 self.imager_mock = self.StartPatcher(self.usb_mock)
David Pursellf1d16a62015-03-25 13:31:04 -070057 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070058 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
59 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -070060 self.PatchObject(os.path, 'exists', return_value=True)
Jae Hoon Kim7f7bc232022-05-04 23:01:16 +000061 self.PatchObject(os.path, 'getsize', return_value=200)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070062 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
63 return_value=True)
Jae Hoon Kim7f7bc232022-05-04 23:01:16 +000064 self.PatchObject(osutils, 'GetDeviceSize', return_value=200)
David Pursellf1d16a62015-03-25 13:31:04 -070065
66 def testLocalImagePathCopy(self):
67 """Tests that imaging methods are called correctly."""
68 with mock.patch('os.path.isfile', return_value=True):
69 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
70 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
71
David Pursellf1d16a62015-03-25 13:31:04 -070072 def testLocalBadImagePath(self):
73 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070074 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -070075 with mock.patch('os.path.isfile', return_value=True):
76 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
77 mock_prompt.return_value = False
78 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
79 self.assertTrue(mock_prompt.called)
80
81 def testNonLocalImagePath(self):
82 """Tests that we try to get the image path using xbuddy."""
83 with mock.patch.object(
84 dev_server_wrapper,
85 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070086 return_value=('translated/xbuddy/path',
87 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -070088 with mock.patch('os.path.isfile', return_value=False):
89 with mock.patch('os.path.isdir', return_value=False):
90 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
91 self.assertTrue(mock_xbuddy.called)
92
93 def testConfirmNonRemovableDevice(self):
94 """Tests that we ask user to confirm if the device is not removable."""
95 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
Mike Frysingera5c6e792022-03-15 23:42:12 -040096 flash.Flash(self.Device('/dev/stub'), self.IMAGE)
David Pursellf1d16a62015-03-25 13:31:04 -070097 self.assertTrue(mock_prompt.called)
98
99 def testSkipPromptNonRemovableDevice(self):
100 """Tests that we skip the prompt for non-removable with --yes."""
101 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
Mike Frysingera5c6e792022-03-15 23:42:12 -0400102 flash.Flash(self.Device('/dev/stub'), self.IMAGE, yes=True)
David Pursellf1d16a62015-03-25 13:31:04 -0700103 self.assertFalse(mock_prompt.called)
104
105 def testChooseRemovableDevice(self):
106 """Tests that we ask user to choose a device if none is given."""
107 flash.Flash(self.Device(''), self.IMAGE)
108 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700109
Jae Hoon Kim7f7bc232022-05-04 23:01:16 +0000110 def testInsufficientRemovableDeviceStorage(self):
111 self.PatchObject(osutils, 'GetDeviceSize', return_value=100)
112 with self.assertRaises(flash.FlashError):
113 flash.Flash(self.Device(''), self.IMAGE)
114
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700115
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600116class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700117 """Tests for flash.UsbImagerOperation."""
118 # pylint: disable=protected-access
119
120 def setUp(self):
121 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
122
123 def testUsbImagerOperationCalled(self):
124 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
125 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800126 'oflag=direct', 'conv=fdatasync']
Mike Frysingera5c6e792022-03-15 23:42:12 -0400127 usb_imager = flash.USBImager('stub_device', 'board', 'foo', 'latest')
Ralph Nathan9b997232015-05-15 13:13:12 -0700128 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
129 self.PatchObject(logging.Logger, 'getEffectiveLevel',
130 return_value=logging.NOTICE)
131 usb_imager.CopyImageToDevice('foo', 'bar')
132
133 # Check that flash.UsbImagerOperation.Run() is called correctly.
Mike Frysinger45602c72019-09-22 02:15:11 -0400134 run_mock.assert_called_with(cros_build_lib.sudo_run, expected_cmd,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400135 debug_level=logging.NOTICE, encoding='utf-8',
136 update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700137
138 def testSudoRunCommandCalled(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400139 """Test that sudo_run is called when log level > NOTICE."""
Ralph Nathan9b997232015-05-15 13:13:12 -0700140 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800141 'oflag=direct', 'conv=fdatasync']
Mike Frysingera5c6e792022-03-15 23:42:12 -0400142 usb_imager = flash.USBImager('stub_device', 'board', 'foo', 'latest')
Mike Frysinger45602c72019-09-22 02:15:11 -0400143 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700144 self.PatchObject(logging.Logger, 'getEffectiveLevel',
145 return_value=logging.WARNING)
146 usb_imager.CopyImageToDevice('foo', 'bar')
147
Mike Frysinger45602c72019-09-22 02:15:11 -0400148 # Check that sudo_run() is called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700149 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
150 print_cmd=False)
151
152 def testPingDD(self):
153 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
154 expected_cmd = ['kill', '-USR1', '5']
Mike Frysinger45602c72019-09-22 02:15:11 -0400155 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700156 op = flash.UsbImagerOperation('foo')
157 op._PingDD(5)
158
Mike Frysinger45602c72019-09-22 02:15:11 -0400159 # Check that sudo_run was called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700160 run_mock.assert_called_with(expected_cmd, print_cmd=False)
161
162 def testGetDDPidFound(self):
163 """Check that the expected pid is returned for _GetDDPid()."""
164 expected_pid = 5
165 op = flash.UsbImagerOperation('foo')
Mike Nicholsa1414162021-04-22 20:07:22 +0000166 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
Ralph Nathan9b997232015-05-15 13:13:12 -0700167 self.rc.AddCmdResult(partial_mock.Ignore(),
168 output='%d\n10\n' % expected_pid)
169
170 pid = op._GetDDPid()
171
172 # Check that the correct pid was returned.
173 self.assertEqual(pid, expected_pid)
174
175 def testGetDDPidNotFound(self):
176 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
177 expected_pid = -1
178 op = flash.UsbImagerOperation('foo')
Mike Nicholsa1414162021-04-22 20:07:22 +0000179 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
Ralph Nathan9b997232015-05-15 13:13:12 -0700180 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
181
182 pid = op._GetDDPid()
183
184 # Check that the correct pid was returned.
185 self.assertEqual(pid, expected_pid)
186
187
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700188class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
189 """Tests the helpers from cli.flash."""
190
191 def testChooseImage(self):
192 """Tests that we can detect a GPT image."""
193 # pylint: disable=protected-access
194
195 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
196 # No images defined. Choosing the image should raise an error.
197 with self.assertRaises(ValueError):
198 flash._ChooseImageFromDirectory(self.tempdir)
199
200 file_a = os.path.join(self.tempdir, 'a')
201 osutils.Touch(file_a)
202 # Only one image available, it should be selected automatically.
203 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
204
205 osutils.Touch(os.path.join(self.tempdir, 'b'))
206 file_c = os.path.join(self.tempdir, 'c')
207 osutils.Touch(file_c)
208 osutils.Touch(os.path.join(self.tempdir, 'd'))
209
210 # Multiple images available, we should ask the user to select the right
211 # image.
212 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
213 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500214
215 def testIsFilePathGPTDiskImage(self):
216 """Tests the GPT image probing."""
217 # pylint: disable=protected-access
218
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400219 INVALID_PMBR = b' ' * 0x200
220 INVALID_GPT = b' ' * 0x200
221 VALID_PMBR = (b' ' * 0x1fe) + b'\x55\xaa'
222 VALID_GPT = b'EFI PART' + (b' ' * 0x1f8)
Mike Frysinger32759e42016-12-21 18:40:16 -0500223 TESTCASES = (
224 (False, False, INVALID_PMBR + INVALID_GPT),
225 (False, False, VALID_PMBR + INVALID_GPT),
226 (False, True, INVALID_PMBR + VALID_GPT),
227 (True, True, VALID_PMBR + VALID_GPT),
228 )
229
230 img = os.path.join(self.tempdir, 'img.bin')
231 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400232 osutils.WriteFile(img, data, mode='wb')
Mike Frysinger32759e42016-12-21 18:40:16 -0500233 self.assertEqual(
234 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
235 self.assertEqual(
236 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)