Mike Frysinger | f1ba7ad | 2022-09-12 05:42:57 -0400 | [diff] [blame] | 1 | # Copyright 2015 The ChromiumOS Authors |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 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 McDonald | 14ac61d | 2021-07-21 11:49:56 -0600 | [diff] [blame] | 7 | import logging |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 8 | import os |
Mike Frysinger | 166fea0 | 2021-02-12 05:30:33 -0500 | [diff] [blame] | 9 | from unittest import mock |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 10 | |
| 11 | from chromite.cli import flash |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 12 | from chromite.lib import commandline |
| 13 | from chromite.lib import cros_build_lib |
| 14 | from chromite.lib import cros_test_lib |
| 15 | from chromite.lib import dev_server_wrapper |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 16 | from chromite.lib import osutils |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 17 | from chromite.lib import partial_mock |
Amin Hassani | c0f06fa | 2019-01-28 15:24:47 -0800 | [diff] [blame] | 18 | |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 19 | |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 20 | class USBImagerMock(partial_mock.PartialCmdMock): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 21 | """Mock out USBImager.""" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 22 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 23 | TARGET = "chromite.cli.flash.USBImager" |
| 24 | ATTRS = ( |
| 25 | "CopyImageToDevice", |
| 26 | "ChooseRemovableDevice", |
| 27 | "ListAllRemovableDevices", |
| 28 | "GetRemovableDeviceDescription", |
| 29 | ) |
| 30 | VALID_IMAGE = True |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 31 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 32 | def __init__(self): |
| 33 | partial_mock.PartialCmdMock.__init__(self) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 34 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 35 | def CopyImageToDevice(self, _inst, *_args, **_kwargs): |
| 36 | """Mock out CopyImageToDevice.""" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 37 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 38 | def ChooseRemovableDevice(self, _inst, *_args, **_kwargs): |
| 39 | """Mock out ChooseRemovableDevice.""" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 40 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 41 | def ListAllRemovableDevices(self, _inst, *_args, **_kwargs): |
| 42 | """Mock out ListAllRemovableDevices.""" |
| 43 | return ["foo", "taco", "milk"] |
| 44 | |
| 45 | def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs): |
| 46 | """Mock out GetRemovableDeviceDescription.""" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 47 | |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 48 | |
| 49 | class USBImagerTest(cros_test_lib.MockTempDirTestCase): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 50 | """Test the flow of flash.Flash() with USBImager.""" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 51 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 52 | IMAGE = "/path/to/image" |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 53 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 54 | def Device(self, path): |
| 55 | """Create a USB device for passing to flash.Flash().""" |
| 56 | return commandline.Device( |
| 57 | scheme=commandline.DEVICE_SCHEME_USB, path=path |
| 58 | ) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 59 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 60 | def setUp(self): |
| 61 | """Patches objects.""" |
| 62 | self.usb_mock = USBImagerMock() |
| 63 | self.imager_mock = self.StartPatcher(self.usb_mock) |
| 64 | self.PatchObject( |
| 65 | dev_server_wrapper, |
| 66 | "GetImagePathWithXbuddy", |
| 67 | return_value=( |
| 68 | "taco-paladin/R36/chromiumos_test_image.bin", |
| 69 | "remote/taco-paladin/R36/test", |
| 70 | ), |
| 71 | ) |
| 72 | self.PatchObject(os.path, "exists", return_value=True) |
| 73 | self.PatchObject(os.path, "getsize", return_value=200) |
| 74 | self.isgpt_mock = self.PatchObject( |
| 75 | flash, "_IsFilePathGPTDiskImage", return_value=True |
| 76 | ) |
| 77 | self.PatchObject(osutils, "GetDeviceSize", return_value=200) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 78 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 79 | def testLocalImagePathCopy(self): |
| 80 | """Tests that imaging methods are called correctly.""" |
| 81 | with mock.patch("os.path.isfile", return_value=True): |
| 82 | flash.Flash(self.Device("/dev/foo"), self.IMAGE) |
| 83 | self.assertTrue( |
| 84 | self.imager_mock.patched["CopyImageToDevice"].called |
| 85 | ) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 86 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 87 | def testLocalBadImagePath(self): |
| 88 | """Tests that using an image not having the magic bytes has prompt.""" |
| 89 | self.isgpt_mock.return_value = False |
| 90 | with mock.patch("os.path.isfile", return_value=True): |
| 91 | with mock.patch.object( |
| 92 | cros_build_lib, "BooleanPrompt" |
| 93 | ) as mock_prompt: |
| 94 | mock_prompt.return_value = False |
| 95 | flash.Flash(self.Device("/dev/foo"), self.IMAGE) |
| 96 | self.assertTrue(mock_prompt.called) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 97 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 98 | def testNonLocalImagePath(self): |
| 99 | """Tests that we try to get the image path using xbuddy.""" |
| 100 | with mock.patch.object( |
| 101 | dev_server_wrapper, |
| 102 | "GetImagePathWithXbuddy", |
| 103 | return_value=("translated/xbuddy/path", "resolved/xbuddy/path"), |
| 104 | ) as mock_xbuddy: |
| 105 | with mock.patch("os.path.isfile", return_value=False): |
| 106 | with mock.patch("os.path.isdir", return_value=False): |
| 107 | flash.Flash(self.Device("/dev/foo"), self.IMAGE) |
| 108 | self.assertTrue(mock_xbuddy.called) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 109 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 110 | def testConfirmNonRemovableDevice(self): |
| 111 | """Tests that we ask user to confirm if the device is not removable.""" |
| 112 | with mock.patch.object(cros_build_lib, "BooleanPrompt") as mock_prompt: |
| 113 | flash.Flash(self.Device("/dev/stub"), self.IMAGE) |
| 114 | self.assertTrue(mock_prompt.called) |
David Pursell | f1d16a6 | 2015-03-25 13:31:04 -0700 | [diff] [blame] | 115 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 116 | def testSkipPromptNonRemovableDevice(self): |
| 117 | """Tests that we skip the prompt for non-removable with --yes.""" |
| 118 | with mock.patch.object(cros_build_lib, "BooleanPrompt") as mock_prompt: |
| 119 | flash.Flash(self.Device("/dev/stub"), self.IMAGE, yes=True) |
| 120 | self.assertFalse(mock_prompt.called) |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 121 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 122 | def testChooseRemovableDevice(self): |
| 123 | """Tests that we ask user to choose a device if none is given.""" |
| 124 | flash.Flash(self.Device(""), self.IMAGE) |
| 125 | self.assertTrue( |
| 126 | self.imager_mock.patched["ChooseRemovableDevice"].called |
| 127 | ) |
| 128 | |
| 129 | def testInsufficientRemovableDeviceStorage(self): |
| 130 | self.PatchObject(osutils, "GetDeviceSize", return_value=100) |
| 131 | with self.assertRaises(flash.FlashError): |
| 132 | flash.Flash(self.Device(""), self.IMAGE) |
Jae Hoon Kim | 7f7bc23 | 2022-05-04 23:01:16 +0000 | [diff] [blame] | 133 | |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 134 | |
Benjamin Gordon | 121a2aa | 2018-05-04 16:24:45 -0600 | [diff] [blame] | 135 | class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 136 | """Tests for flash.UsbImagerOperation.""" |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 137 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 138 | # pylint: disable=protected-access |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 139 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 140 | def setUp(self): |
| 141 | self.PatchObject( |
| 142 | flash.UsbImagerOperation, "__init__", return_value=None |
| 143 | ) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 144 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 145 | def testUsbImagerOperationCalled(self): |
| 146 | """Test that flash.UsbImagerOperation is called when log level <= NOTICE.""" |
| 147 | expected_cmd = [ |
| 148 | "dd", |
| 149 | "if=foo", |
| 150 | "of=bar", |
| 151 | "bs=4M", |
| 152 | "iflag=fullblock", |
| 153 | "oflag=direct", |
| 154 | "conv=fdatasync", |
| 155 | ] |
| 156 | usb_imager = flash.USBImager("stub_device", "board", "foo", "latest") |
| 157 | run_mock = self.PatchObject(flash.UsbImagerOperation, "Run") |
| 158 | self.PatchObject( |
| 159 | logging.Logger, "getEffectiveLevel", return_value=logging.NOTICE |
| 160 | ) |
| 161 | usb_imager.CopyImageToDevice("foo", "bar") |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 162 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 163 | # Check that flash.UsbImagerOperation.Run() is called correctly. |
| 164 | run_mock.assert_called_with( |
| 165 | cros_build_lib.sudo_run, |
| 166 | expected_cmd, |
| 167 | debug_level=logging.NOTICE, |
| 168 | encoding="utf-8", |
| 169 | update_period=0.5, |
| 170 | ) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 171 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 172 | def testSudoRunCommandCalled(self): |
| 173 | """Test that sudo_run is called when log level > NOTICE.""" |
| 174 | expected_cmd = [ |
| 175 | "dd", |
| 176 | "if=foo", |
| 177 | "of=bar", |
| 178 | "bs=4M", |
| 179 | "iflag=fullblock", |
| 180 | "oflag=direct", |
| 181 | "conv=fdatasync", |
| 182 | ] |
| 183 | usb_imager = flash.USBImager("stub_device", "board", "foo", "latest") |
| 184 | run_mock = self.PatchObject(cros_build_lib, "sudo_run") |
| 185 | self.PatchObject( |
| 186 | logging.Logger, "getEffectiveLevel", return_value=logging.WARNING |
| 187 | ) |
| 188 | usb_imager.CopyImageToDevice("foo", "bar") |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 189 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 190 | # Check that sudo_run() is called correctly. |
| 191 | run_mock.assert_any_call( |
| 192 | expected_cmd, debug_level=logging.NOTICE, print_cmd=False |
| 193 | ) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 194 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 195 | def testPingDD(self): |
| 196 | """Test that UsbImagerOperation._PingDD() sends the correct signal.""" |
| 197 | expected_cmd = ["kill", "-USR1", "5"] |
| 198 | run_mock = self.PatchObject(cros_build_lib, "sudo_run") |
| 199 | op = flash.UsbImagerOperation("foo") |
| 200 | op._PingDD(5) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 201 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 202 | # Check that sudo_run was called correctly. |
| 203 | run_mock.assert_called_with(expected_cmd, print_cmd=False) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 204 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 205 | def testGetDDPidFound(self): |
| 206 | """Check that the expected pid is returned for _GetDDPid().""" |
| 207 | expected_pid = 5 |
| 208 | op = flash.UsbImagerOperation("foo") |
| 209 | self.PatchObject(osutils, "IsChildProcess", return_value=True) |
| 210 | self.rc.AddCmdResult( |
| 211 | partial_mock.Ignore(), stdout=f"{expected_pid}\n10\n" |
| 212 | ) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 213 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 214 | pid = op._GetDDPid() |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 215 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 216 | # Check that the correct pid was returned. |
| 217 | self.assertEqual(pid, expected_pid) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 218 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 219 | def testGetDDPidNotFound(self): |
| 220 | """Check that -1 is returned for _GetDDPid() if the pids aren't valid.""" |
| 221 | expected_pid = -1 |
| 222 | op = flash.UsbImagerOperation("foo") |
| 223 | self.PatchObject(osutils, "IsChildProcess", return_value=False) |
| 224 | self.rc.AddCmdResult(partial_mock.Ignore(), stdout="5\n10\n") |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 225 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 226 | pid = op._GetDDPid() |
| 227 | |
| 228 | # Check that the correct pid was returned. |
| 229 | self.assertEqual(pid, expected_pid) |
Ralph Nathan | 9b99723 | 2015-05-15 13:13:12 -0700 | [diff] [blame] | 230 | |
| 231 | |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 232 | class FlashUtilTest(cros_test_lib.MockTempDirTestCase): |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 233 | """Tests the helpers from cli.flash.""" |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 234 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 235 | def testChooseImage(self): |
| 236 | """Tests that we can detect a GPT image.""" |
| 237 | # pylint: disable=protected-access |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 238 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 239 | with self.PatchObject( |
| 240 | flash, "_IsFilePathGPTDiskImage", return_value=True |
| 241 | ): |
| 242 | # No images defined. Choosing the image should raise an error. |
| 243 | with self.assertRaises(ValueError): |
| 244 | flash._ChooseImageFromDirectory(self.tempdir) |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 245 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 246 | file_a = os.path.join(self.tempdir, "a") |
| 247 | osutils.Touch(file_a) |
| 248 | # Only one image available, it should be selected automatically. |
| 249 | self.assertEqual( |
| 250 | file_a, flash._ChooseImageFromDirectory(self.tempdir) |
| 251 | ) |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 252 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 253 | osutils.Touch(os.path.join(self.tempdir, "b")) |
| 254 | file_c = os.path.join(self.tempdir, "c") |
| 255 | osutils.Touch(file_c) |
| 256 | osutils.Touch(os.path.join(self.tempdir, "d")) |
Bertrand SIMONNET | 56f773d | 2015-05-04 14:02:39 -0700 | [diff] [blame] | 257 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 258 | # Multiple images available, we should ask the user to select the right |
| 259 | # image. |
| 260 | with self.PatchObject(cros_build_lib, "GetChoice", return_value=2): |
| 261 | self.assertEqual( |
| 262 | file_c, flash._ChooseImageFromDirectory(self.tempdir) |
| 263 | ) |
Mike Frysinger | 32759e4 | 2016-12-21 18:40:16 -0500 | [diff] [blame] | 264 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 265 | def testIsFilePathGPTDiskImage(self): |
| 266 | """Tests the GPT image probing.""" |
| 267 | # pylint: disable=protected-access |
Mike Frysinger | 32759e4 | 2016-12-21 18:40:16 -0500 | [diff] [blame] | 268 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 269 | INVALID_PMBR = b" " * 0x200 |
| 270 | INVALID_GPT = b" " * 0x200 |
| 271 | VALID_PMBR = (b" " * 0x1FE) + b"\x55\xaa" |
| 272 | VALID_GPT = b"EFI PART" + (b" " * 0x1F8) |
| 273 | TESTCASES = ( |
| 274 | (False, False, INVALID_PMBR + INVALID_GPT), |
| 275 | (False, False, VALID_PMBR + INVALID_GPT), |
| 276 | (False, True, INVALID_PMBR + VALID_GPT), |
| 277 | (True, True, VALID_PMBR + VALID_GPT), |
| 278 | ) |
Mike Frysinger | 32759e4 | 2016-12-21 18:40:16 -0500 | [diff] [blame] | 279 | |
Alex Klein | 1699fab | 2022-09-08 08:46:06 -0600 | [diff] [blame] | 280 | img = os.path.join(self.tempdir, "img.bin") |
| 281 | for exp_pmbr_t, exp_pmbr_f, data in TESTCASES: |
| 282 | osutils.WriteFile(img, data, mode="wb") |
| 283 | self.assertEqual( |
| 284 | flash._IsFilePathGPTDiskImage(img, require_pmbr=True), |
| 285 | exp_pmbr_t, |
| 286 | ) |
| 287 | self.assertEqual( |
| 288 | flash._IsFilePathGPTDiskImage(img, require_pmbr=False), |
| 289 | exp_pmbr_f, |
| 290 | ) |