blob: a724aef50ef63b66d81017dbc290a1bf5a9fa89c [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2021 The ChromiumOS Authors
Amin Hassani92f6c4a2021-02-20 17:36:09 -08002# 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 device_imager module."""
6
Amin Hassanid4b3ff82021-02-20 23:05:14 -08007import os
Amin Hassani92f6c4a2021-02-20 17:36:09 -08008import tempfile
Amin Hassani55970562021-02-22 20:49:13 -08009import time
Mike Frysinger166fea02021-02-12 05:30:33 -050010from unittest import mock
Amin Hassani92f6c4a2021-02-20 17:36:09 -080011
Amin Hassani92f6c4a2021-02-20 17:36:09 -080012from chromite.cli import device_imager
Amin Hassanid4b3ff82021-02-20 23:05:14 -080013from chromite.lib import constants
Amin Hassani92f6c4a2021-02-20 17:36:09 -080014from chromite.lib import cros_test_lib
Amin Hassani0fe49ae2021-02-21 23:41:58 -080015from chromite.lib import gs
Amin Hassanid4b3ff82021-02-20 23:05:14 -080016from chromite.lib import image_lib
17from chromite.lib import image_lib_unittest
Amin Hassani92f6c4a2021-02-20 17:36:09 -080018from chromite.lib import partial_mock
19from chromite.lib import remote_access
20from chromite.lib import remote_access_unittest
Amin Hassani74403082021-02-22 11:40:09 -080021from chromite.lib import stateful_updater
22from chromite.lib.paygen import paygen_stateful_payload_lib
Amin Hassani92f6c4a2021-02-20 17:36:09 -080023from chromite.lib.xbuddy import xbuddy
24
25
Amin Hassani92f6c4a2021-02-20 17:36:09 -080026# pylint: disable=protected-access
27
Amin Hassanid4b3ff82021-02-20 23:05:14 -080028
29def GetFdPath(fd):
Alex Klein1699fab2022-09-08 08:46:06 -060030 """Returns the fd path for the current process."""
31 return f"/proc/self/fd/{fd}"
Amin Hassanid4b3ff82021-02-20 23:05:14 -080032
33
Amin Hassani92f6c4a2021-02-20 17:36:09 -080034class DeviceImagerTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060035 """Tests DeviceImager class methods."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -080036
Alex Klein1699fab2022-09-08 08:46:06 -060037 def setUp(self):
38 """Sets up the class by creating proper mocks."""
39 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
40 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
41 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani92f6c4a2021-02-20 17:36:09 -080042
Alex Klein1699fab2022-09-08 08:46:06 -060043 def test_LocateImageLocalFile(self):
44 """Tests getting the path to local image."""
45 with tempfile.NamedTemporaryFile() as fp:
46 di = device_imager.DeviceImager(None, fp.name)
47 di._LocateImage()
48 self.assertEqual(di._image, fp.name)
49 self.assertEqual(di._image_type, device_imager.ImageType.FULL)
Amin Hassani92f6c4a2021-02-20 17:36:09 -080050
Alex Klein1699fab2022-09-08 08:46:06 -060051 def test_LocateImageDir(self):
52 """Tests failing on a given directory as a path."""
53 di = device_imager.DeviceImager(None, "/tmp")
54 with self.assertRaises(ValueError):
55 di._LocateImage()
Amin Hassani92f6c4a2021-02-20 17:36:09 -080056
Alex Klein1699fab2022-09-08 08:46:06 -060057 @mock.patch.object(
58 xbuddy.XBuddy, "Translate", return_value=("eve/R90", None)
59 )
60 @mock.patch.object(
61 remote_access.ChromiumOSDevice,
62 "board",
63 return_value="foo",
64 new_callable=mock.PropertyMock,
65 )
66 # pylint: disable=unused-argument
67 def test_LocateImageXBuddyRemote(self, _, board_mock):
68 """Tests getting remote xBuddy image path."""
69 with remote_access.ChromiumOSDeviceHandler(
70 remote_access.TEST_IP
71 ) as device:
72 di = device_imager.DeviceImager(
73 device, "xbuddy://remote/eve/latest"
74 )
75 di._LocateImage()
76 self.assertEqual(di._image, "gs://chromeos-image-archive/eve/R90")
77 self.assertEqual(
78 di._image_type, device_imager.ImageType.REMOTE_DIRECTORY
79 )
Amin Hassani92f6c4a2021-02-20 17:36:09 -080080
Alex Klein1699fab2022-09-08 08:46:06 -060081 @mock.patch.object(
82 xbuddy.XBuddy, "Translate", return_value=("eve/R90", "path/to/file")
83 )
84 @mock.patch.object(
85 remote_access.ChromiumOSDevice,
86 "board",
87 return_value="foo",
88 new_callable=mock.PropertyMock,
89 )
90 # pylint: disable=unused-argument
91 def test_LocateImageXBuddyLocal(self, _, board_mock):
92 """Tests getting local xBuddy image path."""
93 with remote_access.ChromiumOSDeviceHandler(
94 remote_access.TEST_IP
95 ) as device:
96 di = device_imager.DeviceImager(device, "xbuddy://local/eve/latest")
97 di._LocateImage()
98 self.assertEqual(di._image, "path/to/file")
99 self.assertEqual(di._image_type, device_imager.ImageType.FULL)
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 def test_SplitDevPath(self):
102 """Tests splitting a device path into prefix and partition number."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800103
Alex Klein1699fab2022-09-08 08:46:06 -0600104 di = device_imager.DeviceImager(None, None)
105 self.assertEqual(di._SplitDevPath("/dev/foop3"), ("/dev/foop", 3))
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800106
Alex Klein1699fab2022-09-08 08:46:06 -0600107 with self.assertRaises(device_imager.Error):
108 di._SplitDevPath("/foo")
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 with self.assertRaises(device_imager.Error):
111 di._SplitDevPath("/foo/p3p")
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 def test_GetKernelState(self):
114 """Tests getting the current active and inactive kernel states."""
115 di = device_imager.DeviceImager(None, None)
116 self.assertEqual(
117 di._GetKernelState(3),
118 (device_imager.DeviceImager.A, device_imager.DeviceImager.B),
119 )
120 self.assertEqual(
121 di._GetKernelState(5),
122 (device_imager.DeviceImager.B, device_imager.DeviceImager.A),
123 )
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800124
Alex Klein1699fab2022-09-08 08:46:06 -0600125 with self.assertRaises(device_imager.Error):
126 di._GetKernelState(1)
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 @mock.patch.object(
129 remote_access.ChromiumOSDevice,
130 "root_dev",
131 return_value="/dev/foop3",
132 new_callable=mock.PropertyMock,
133 )
134 def test_VerifyBootExpectations(self, _):
135 """Tests verifying the boot expectations after reboot."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800136
Alex Klein1699fab2022-09-08 08:46:06 -0600137 with remote_access.ChromiumOSDeviceHandler(
138 remote_access.TEST_IP
139 ) as device:
140 di = device_imager.DeviceImager(device, None)
141 di._inactive_state = device_imager.DeviceImager.A
142 di._VerifyBootExpectations()
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800143
Alex Klein1699fab2022-09-08 08:46:06 -0600144 @mock.patch.object(
145 remote_access.ChromiumOSDevice,
146 "root_dev",
147 return_value="/dev/foop3",
148 new_callable=mock.PropertyMock,
149 )
150 def test_VerifyBootExpectationsFails(self, _):
151 """Tests failure of boot expectations."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800152
Alex Klein1699fab2022-09-08 08:46:06 -0600153 with remote_access.ChromiumOSDeviceHandler(
154 remote_access.TEST_IP
155 ) as device:
156 di = device_imager.DeviceImager(device, None)
157 di._inactive_state = device_imager.DeviceImager.B
158 with self.assertRaises(device_imager.Error):
159 di._VerifyBootExpectations()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800160
161
162class TestReaderBase(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600163 """Test ReaderBase class"""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 def testNamedPipe(self):
166 """Tests initializing the class with named pipe."""
167 with device_imager.ReaderBase(use_named_pipes=True) as r:
168 self.assertIsInstance(r.Target(), str)
169 self.assertEqual(r._Source(), r.Target())
170 self.assertExists(r.Target())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 r._CloseSource() # Should not have any effect.
173 self.assertExists(r._Source())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800174
Alex Klein1699fab2022-09-08 08:46:06 -0600175 # Closing target should delete the named pipe.
176 r.CloseTarget()
177 self.assertNotExists(r.Target())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800178
Alex Klein1699fab2022-09-08 08:46:06 -0600179 def testFdPipe(self):
180 """Tests initializing the class with normal file descriptor pipes."""
181 with device_imager.ReaderBase() as r:
182 self.assertIsInstance(r.Target(), int)
183 self.assertIsInstance(r._Source(), int)
184 self.assertNotEqual(r._Source(), r.Target())
185 self.assertExists(GetFdPath(r.Target()))
186 self.assertExists(GetFdPath(r._Source()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 # Per crbug.com/1196702 it seems like some other process gets the file
189 # descriptor right after we close it and by the time we check its
190 # existence, it is still there and this can flake. So it might be better
191 # to make sure this is checked properly through real paths and not
192 # symlinks.
193 path = GetFdPath(r._Source())
194 old_path = os.path.realpath(path)
195 r._CloseSource()
196 with self.assertRaises(OSError):
197 new_path = os.path.realpath(path)
198 self.assertNotEqual(old_path, new_path)
199 raise OSError("Fake the context manager.")
Amin Hassanifa11c692021-04-07 09:17:31 -0700200
Alex Klein1699fab2022-09-08 08:46:06 -0600201 self.assertExists(GetFdPath(r.Target()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 path = GetFdPath(r.Target())
204 old_path = os.path.realpath(path)
205 r.CloseTarget()
206 with self.assertRaises(OSError):
207 new_path = os.path.realpath(path)
208 self.assertNotEqual(old_path, new_path)
209 raise OSError("Fake the context manager.")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800210
Alex Klein1699fab2022-09-08 08:46:06 -0600211 def testFdPipeCommunicate(self):
212 """Tests that file descriptors pipe can actually communicate."""
213 with device_imager.ReaderBase() as r:
214 with os.fdopen(r._Source(), "w") as fp:
215 fp.write("helloworld")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800216
Alex Klein1699fab2022-09-08 08:46:06 -0600217 with os.fdopen(r.Target(), "r") as fp:
218 self.assertEqual(fp.read(), "helloworld")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800219
220
221class PartialFileReaderTest(cros_test_lib.RunCommandTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600222 """Tests PartialFileReader class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800223
Alex Klein1699fab2022-09-08 08:46:06 -0600224 def testRun(self):
225 """Tests the main run() function."""
226 with device_imager.PartialFileReader(
227 "/foo", 512 * 2, 512, ["/usr/bin/pigz"]
228 ) as pfr:
229 pass
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800230
Alex Klein1699fab2022-09-08 08:46:06 -0600231 self.assertCommandCalled(
232 "dd status=none if=/foo ibs=512 skip=2 count=1 | /usr/bin/pigz",
233 stdout=pfr._Source(),
234 shell=True,
235 )
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800236
Alex Klein1699fab2022-09-08 08:46:06 -0600237 # Make sure the source has been close.
238 self.assertNotExists(GetFdPath(pfr._Source()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800239
240
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800241class GsFileCopierTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600242 """Tests GsFileCopier class."""
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 @mock.patch.object(gs.GSContext, "Copy")
245 def testRun(self, copy_mock):
246 """Tests the run() function."""
247 image = "gs://path/to/image"
248 with device_imager.GsFileCopier(image) as gfc:
249 self.assertTrue(gfc._use_named_pipes)
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800250
Alex Klein1699fab2022-09-08 08:46:06 -0600251 copy_mock.assert_called_with(image, gfc._Source())
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800252
253
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800254class PartitionUpdaterBaseTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600255 """Tests PartitionUpdaterBase class"""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 def testRunNotImplemented(self):
258 """Tests running the main Run() function is not implemented."""
259 # We just want to make sure the _Run() function is not implemented here.
260 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
261 with self.assertRaises(NotImplementedError):
262 pub.Run()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800263
Alex Klein1699fab2022-09-08 08:46:06 -0600264 def testRevertNotImplemented(self):
265 """Tests running the Revert() function is not implemented."""
266 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
267 with self.assertRaises(NotImplementedError):
268 pub.Revert()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 @mock.patch.object(device_imager.PartitionUpdaterBase, "_Run")
271 def testIsFinished(self, _):
272 """Tests IsFinished() function."""
273 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
274 self.assertFalse(pub.IsFinished())
275 pub.Run()
276 self.assertTrue(pub.IsFinished())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800277
278
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800279class RawPartitionUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600280 """Tests RawPartitionUpdater class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800281
Alex Klein1699fab2022-09-08 08:46:06 -0600282 def setUp(self):
283 """Sets up the class by creating proper mocks."""
284 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
285 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
286 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 @mock.patch.object(
289 device_imager.RawPartitionUpdater,
290 "_GetPartitionName",
291 return_value=constants.PART_KERN_A,
292 )
293 @mock.patch.object(
294 image_lib,
295 "GetImageDiskPartitionInfo",
296 return_value=image_lib_unittest.LOOP_PARTITION_INFO,
297 )
298 @mock.patch.object(device_imager.PartialFileReader, "CloseTarget")
299 @mock.patch.object(device_imager.PartialFileReader, "run")
300 def test_RunFullImage(self, run_mock, close_mock, _, name_mock):
301 """Test main Run() function for full image.
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 This function should parts of the source image and write it into the device
304 using proper compression programs.
305 """
306 with remote_access.ChromiumOSDeviceHandler(
307 remote_access.TEST_IP
308 ) as device:
309 self.rsh_mock.AddCmdResult(
310 [partial_mock.In("which"), "gzip"], returncode=0
311 )
312 self.rsh_mock.AddCmdResult(
313 self.path_env
314 + " gzip --decompress --stdout | "
315 + "dd bs=1M oflag=direct of=/dev/mmcblk0p2"
316 )
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 device_imager.RawPartitionUpdater(
319 device,
320 "foo-image",
321 device_imager.ImageType.FULL,
322 "/dev/mmcblk0p2",
323 ).Run()
324 run_mock.assert_called()
325 close_mock.assert_called()
326 name_mock.assert_called()
Amin Hassanid684e982021-02-26 11:10:58 -0800327
Alex Klein1699fab2022-09-08 08:46:06 -0600328 def test_RunRemoteImage(self):
329 """Test main Run() function for remote images."""
330 with remote_access.ChromiumOSDeviceHandler(
331 remote_access.TEST_IP
332 ) as device:
333 self.rsh_mock.AddCmdResult(
334 [partial_mock.In("which"), "gzip"], returncode=0
335 )
336 self.rsh_mock.AddCmdResult(
337 self.path_env
338 + " gzip --decompress --stdout | "
339 + "dd bs=1M oflag=direct of=/dev/mmcblk0p2"
340 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800341
Alex Klein1699fab2022-09-08 08:46:06 -0600342 path = os.path.join(
343 self.tempdir, constants.QUICK_PROVISION_PAYLOAD_KERNEL
344 )
345 with open(path, "w") as image:
346 image.write("helloworld")
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 device_imager.KernelUpdater(
349 device,
350 self.tempdir,
351 device_imager.ImageType.REMOTE_DIRECTORY,
352 "/dev/mmcblk0p2",
353 ).Run()
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800354
Amin Hassanid684e982021-02-26 11:10:58 -0800355
356class KernelUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600357 """Tests KernelUpdater class."""
Amin Hassanid684e982021-02-26 11:10:58 -0800358
Alex Klein1699fab2022-09-08 08:46:06 -0600359 def test_GetPartitionName(self):
360 """Tests the name of the partitions."""
361 ku = device_imager.KernelUpdater(None, None, None, None)
362 self.assertEqual(constants.PART_KERN_B, ku._GetPartitionName())
Amin Hassani75c5f942021-02-20 23:56:53 -0800363
Alex Klein1699fab2022-09-08 08:46:06 -0600364 def test_GetRemotePartitionName(self):
365 """Tests the name of the partitions."""
366 ku = device_imager.KernelUpdater(None, None, None, None)
367 self.assertEqual(
368 constants.QUICK_PROVISION_PAYLOAD_KERNEL,
369 ku._GetRemotePartitionName(),
370 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800371
Amin Hassani75c5f942021-02-20 23:56:53 -0800372
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000373class MiniOSUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600374 """Tests MiniOSUpdater class."""
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000375
Alex Klein1699fab2022-09-08 08:46:06 -0600376 def test_GetPartitionName(self):
377 """Tests the name of the partitions."""
378 u = device_imager.MiniOSUpdater(*([None] * 4))
379 self.assertEqual(constants.PART_MINIOS_A, u._GetPartitionName())
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000380
Alex Klein1699fab2022-09-08 08:46:06 -0600381 def test_GetRemotePartitionName(self):
382 """Tests the name of the partitions."""
383 u = device_imager.MiniOSUpdater(*([None] * 4))
384 self.assertEqual(
385 constants.QUICK_PROVISION_PAYLOAD_MINIOS,
386 u._GetRemotePartitionName(),
387 )
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000388
Alex Klein1699fab2022-09-08 08:46:06 -0600389 @mock.patch.object(device_imager.MiniOSUpdater, "_CopyPartitionFromImage")
390 @mock.patch.object(
391 device_imager.MiniOSUpdater,
392 "_MiniOSPartitionsExistInImage",
393 return_value=True,
394 )
395 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
396 def test_Run(self, postinstall_mock, partitions_exist_mock, copy_mock):
397 """Test main Run() function."""
398 with remote_access.ChromiumOSDeviceHandler(
399 remote_access.TEST_IP
400 ) as device:
401 device_imager.MiniOSUpdater(
402 device,
403 "foo-image",
404 device_imager.ImageType.FULL,
405 "/dev/mmcblk0p10",
406 ).Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000407
Alex Klein1699fab2022-09-08 08:46:06 -0600408 copy_mock.assert_called_with(constants.PART_MINIOS_A)
409 partitions_exist_mock.assert_called_with()
410 postinstall_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000411
Alex Klein1699fab2022-09-08 08:46:06 -0600412 @mock.patch.object(device_imager.MiniOSUpdater, "_CopyPartitionFromImage")
413 @mock.patch.object(
414 device_imager.MiniOSUpdater,
415 "_MiniOSPartitionsExistInImage",
416 return_value=False,
417 )
418 def test_RunMissingMiniOS(self, partitions_exist_mock, copy_mock):
419 """Test main Run() function with missing miniOS partitions on image."""
420 with remote_access.ChromiumOSDeviceHandler(
421 remote_access.TEST_IP
422 ) as device:
423 device_imager.MiniOSUpdater(
424 device,
425 "foo-image",
426 device_imager.ImageType.FULL,
427 "/dev/mmcblk0p10",
428 ).Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000429
Alex Klein1699fab2022-09-08 08:46:06 -0600430 copy_mock.assert_not_called()
431 partitions_exist_mock.assert_called_with()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700432
Alex Klein1699fab2022-09-08 08:46:06 -0600433 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
434 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
435 @mock.patch.object(device_imager.MiniOSUpdater, "_MiniOSPartitionsExist")
436 @mock.patch.object(gs.GSContext, "Exists", return_value=False)
437 def test_RunMissingMiniOSRemotePayload(
438 self,
439 gs_context_mock,
440 partitions_exist_mock,
441 redirect_mock,
442 post_install_mock,
443 ):
444 """Test main Run() function with missing miniOS remote payloads."""
445 with remote_access.ChromiumOSDeviceHandler(
446 remote_access.TEST_IP
447 ) as device:
448 device_imager.MiniOSUpdater(
449 device,
450 "foo-image",
451 device_imager.ImageType.REMOTE_DIRECTORY,
452 "/dev/mmcblk0p10",
453 ).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700454
Alex Klein1699fab2022-09-08 08:46:06 -0600455 post_install_mock.assert_not_called()
456 redirect_mock.assert_not_called()
457 partitions_exist_mock.assert_not_called()
458 gs_context_mock.assert_called()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700459
Alex Klein1699fab2022-09-08 08:46:06 -0600460 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
461 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
462 @mock.patch.object(
463 device_imager.MiniOSUpdater,
464 "_MiniOSPartitionsExist",
465 return_value=False,
466 )
467 @mock.patch.object(gs.GSContext, "Exists", return_value=True)
468 def test_RunMissingMiniOSPartitions(
469 self,
470 gs_context_mock,
471 partitions_exist_mock,
472 redirect_mock,
473 post_install_mock,
474 ):
475 """Test main Run() function with missing miniOS remote payloads."""
476 with remote_access.ChromiumOSDeviceHandler(
477 remote_access.TEST_IP
478 ) as device:
479 device_imager.MiniOSUpdater(
480 device,
481 "foo-image",
482 device_imager.ImageType.REMOTE_DIRECTORY,
483 "/dev/mmcblk0p10",
484 ).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700485
Alex Klein1699fab2022-09-08 08:46:06 -0600486 post_install_mock.assert_not_called()
487 redirect_mock.assert_not_called()
488 partitions_exist_mock.assert_called()
489 gs_context_mock.assert_called()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700490
Alex Klein1699fab2022-09-08 08:46:06 -0600491 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
492 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
493 @mock.patch.object(
494 device_imager.MiniOSUpdater, "_MiniOSPartitionsExist", return_value=True
495 )
496 @mock.patch.object(gs.GSContext, "Exists", return_value=True)
497 def test_RunMiniOSRemotePayload(
498 self,
499 gs_context_mock,
500 partitions_exist_mock,
501 redirect_mock,
502 post_install_mock,
503 ):
504 """Test main Run() function with missing miniOS remote payloads."""
505 with remote_access.ChromiumOSDeviceHandler(
506 remote_access.TEST_IP
507 ) as device:
508 device_imager.MiniOSUpdater(
509 device,
510 "foo-image",
511 device_imager.ImageType.REMOTE_DIRECTORY,
512 "/dev/mmcblk0p10",
513 ).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700514
Alex Klein1699fab2022-09-08 08:46:06 -0600515 post_install_mock.assert_called()
516 redirect_mock.assert_called()
517 partitions_exist_mock.assert_called()
518 gs_context_mock.assert_called()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000519
Alex Klein1699fab2022-09-08 08:46:06 -0600520 @mock.patch.object(device_imager.MiniOSUpdater, "_FlipMiniOSPriority")
521 def test_RunPostInstall(self, flip_mock):
522 """Test _RunPostInstall() function."""
523 with remote_access.ChromiumOSDeviceHandler(
524 remote_access.TEST_IP
525 ) as device:
526 device_imager.MiniOSUpdater(
527 device,
528 "foo-image",
529 device_imager.ImageType.FULL,
530 "/dev/mmcblk0p10",
531 )._RunPostInstall()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000532
Alex Klein1699fab2022-09-08 08:46:06 -0600533 flip_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000534
Alex Klein1699fab2022-09-08 08:46:06 -0600535 @mock.patch.object(device_imager.MiniOSUpdater, "_FlipMiniOSPriority")
536 def test_Revert(self, flip_mock):
537 """Test Revert() function."""
538 u = device_imager.MiniOSUpdater(
539 None, "foo-image", device_imager.ImageType.FULL, "/dev/mmcblk0p10"
540 )
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000541
Alex Klein1699fab2022-09-08 08:46:06 -0600542 # Before PostInstall runs.
543 u.Revert()
544 flip_mock.assert_not_called()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000545
Alex Klein1699fab2022-09-08 08:46:06 -0600546 u._ran_postinst = True
547 u.Revert()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 flip_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000550
Alex Klein1699fab2022-09-08 08:46:06 -0600551 @mock.patch.object(
552 device_imager.MiniOSUpdater, "_GetMiniOSPriority", return_value="A"
553 )
554 @mock.patch.object(device_imager.MiniOSUpdater, "_SetMiniOSPriority")
555 def test_FlipMiniOSPriority(self, set_mock, get_mock):
556 """Test _FlipMiniOSPriority() function."""
557 device_imager.MiniOSUpdater(
558 None, "foo-image", device_imager.ImageType.FULL, "/dev/mmcblk0p10"
559 )._FlipMiniOSPriority()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000560
Alex Klein1699fab2022-09-08 08:46:06 -0600561 get_mock.assert_called_with()
562 set_mock.assert_called_with("B")
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000563
564
Amin Hassani75c5f942021-02-20 23:56:53 -0800565class RootfsUpdaterTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600566 """Tests RootfsUpdater class."""
Amin Hassani75c5f942021-02-20 23:56:53 -0800567
Alex Klein1699fab2022-09-08 08:46:06 -0600568 def setUp(self):
569 """Sets up the class by creating proper mocks."""
570 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
571 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
572 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani75c5f942021-02-20 23:56:53 -0800573
Alex Klein1699fab2022-09-08 08:46:06 -0600574 def test_GetPartitionName(self):
575 """Tests the name of the partitions."""
576 ru = device_imager.RootfsUpdater(None, None, None, None, None)
577 self.assertEqual(constants.PART_ROOT_A, ru._GetPartitionName())
Amin Hassani75c5f942021-02-20 23:56:53 -0800578
Alex Klein1699fab2022-09-08 08:46:06 -0600579 def test_GetRemotePartitionName(self):
580 """Tests the name of the partitions."""
581 ru = device_imager.RootfsUpdater(None, None, None, None, None)
582 self.assertEqual(
583 constants.QUICK_PROVISION_PAYLOAD_ROOTFS,
584 ru._GetRemotePartitionName(),
585 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800586
Alex Klein1699fab2022-09-08 08:46:06 -0600587 @mock.patch.object(device_imager.ProgressWatcher, "run")
588 @mock.patch.object(device_imager.RootfsUpdater, "_RunPostInst")
589 @mock.patch.object(device_imager.RootfsUpdater, "_CopyPartitionFromImage")
590 def test_Run(self, copy_mock, postinst_mock, pw_mock):
591 """Test main Run() function.
Amin Hassani75c5f942021-02-20 23:56:53 -0800592
Alex Klein1699fab2022-09-08 08:46:06 -0600593 This function should parts of the source image and write it into the device
594 using proper compression programs.
595 """
596 with remote_access.ChromiumOSDeviceHandler(
597 remote_access.TEST_IP
598 ) as device:
599 device_imager.RootfsUpdater(
600 "/dev/mmcblk0p5",
601 device,
602 "foo-image",
603 device_imager.ImageType.FULL,
604 "/dev/mmcblk0p3",
605 ).Run()
Amin Hassani75c5f942021-02-20 23:56:53 -0800606
Alex Klein1699fab2022-09-08 08:46:06 -0600607 copy_mock.assert_called_with(constants.PART_ROOT_A)
608 postinst_mock.assert_called_with()
609 pw_mock.assert_called()
Amin Hassani75c5f942021-02-20 23:56:53 -0800610
Alex Klein1699fab2022-09-08 08:46:06 -0600611 def test_RunPostInstOnTarget(self):
612 """Test _RunPostInst() function."""
613 target = "/dev/mmcblk0p3"
614 with remote_access.ChromiumOSDeviceHandler(
615 remote_access.TEST_IP
616 ) as device:
617 device._work_dir = "/tmp/work_dir"
618 temp_dir = os.path.join(device.work_dir, "dir")
619 self.rsh_mock.AddCmdResult(
620 [self.path_env, "mktemp", "-d", "-p", device.work_dir],
621 stdout=temp_dir,
622 )
623 self.rsh_mock.AddCmdResult(
624 [self.path_env, "mount", "-o", "ro", target, temp_dir]
625 )
626 self.rsh_mock.AddCmdResult(
627 [self.path_env, os.path.join(temp_dir, "postinst"), target]
628 )
629 self.rsh_mock.AddCmdResult([self.path_env, "umount", temp_dir])
Amin Hassani75c5f942021-02-20 23:56:53 -0800630
Alex Klein1699fab2022-09-08 08:46:06 -0600631 device_imager.RootfsUpdater(
632 "/dev/mmcblk0p5",
633 device,
634 "foo-image",
635 device_imager.ImageType.FULL,
636 target,
637 )._RunPostInst()
Amin Hassani75c5f942021-02-20 23:56:53 -0800638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 def test_RunPostInstOnCurrentRoot(self):
640 """Test _RunPostInst() on current root (used for reverting an update)."""
641 root_dev = "/dev/mmcblk0p5"
642 self.rsh_mock.AddCmdResult([self.path_env, "/postinst", root_dev])
Amin Hassani75c5f942021-02-20 23:56:53 -0800643
Alex Klein1699fab2022-09-08 08:46:06 -0600644 with remote_access.ChromiumOSDeviceHandler(
645 remote_access.TEST_IP
646 ) as device:
647 device_imager.RootfsUpdater(
648 root_dev,
649 device,
650 "foo-image",
651 device_imager.ImageType.FULL,
652 "/dev/mmcblk0p3",
653 )._RunPostInst(on_target=False)
Amin Hassani75c5f942021-02-20 23:56:53 -0800654
Alex Klein1699fab2022-09-08 08:46:06 -0600655 @mock.patch.object(device_imager.RootfsUpdater, "_RunPostInst")
656 def testRevert(self, postinst_mock):
657 """Tests Revert() function."""
658 ru = device_imager.RootfsUpdater(None, None, None, None, None)
Amin Hassani75c5f942021-02-20 23:56:53 -0800659
Alex Klein1699fab2022-09-08 08:46:06 -0600660 ru.Revert()
661 postinst_mock.assert_not_called()
Amin Hassani75c5f942021-02-20 23:56:53 -0800662
Alex Klein1699fab2022-09-08 08:46:06 -0600663 ru._ran_postinst = True
664 ru.Revert()
665 postinst_mock.assert_called_with(on_target=False)
Amin Hassani74403082021-02-22 11:40:09 -0800666
667
668class StatefulPayloadGeneratorTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600669 """Tests stateful payload generator."""
Amin Hassani74403082021-02-22 11:40:09 -0800670
Alex Klein1699fab2022-09-08 08:46:06 -0600671 @mock.patch.object(paygen_stateful_payload_lib, "GenerateStatefulPayload")
672 def testRun(self, paygen_mock):
673 """Tests run() function."""
674 image = "/foo/image"
675 with device_imager.StatefulPayloadGenerator(image) as spg:
676 pass
677
678 paygen_mock.assert_called_with(image, spg._Source())
Amin Hassani74403082021-02-22 11:40:09 -0800679
680
681class StatefulUpdaterTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600682 """Tests StatefulUpdater."""
Amin Hassani74403082021-02-22 11:40:09 -0800683
Alex Klein1699fab2022-09-08 08:46:06 -0600684 @mock.patch.object(paygen_stateful_payload_lib, "GenerateStatefulPayload")
685 @mock.patch.object(stateful_updater.StatefulUpdater, "Update")
686 def test_RunFullImage(self, update_mock, paygen_mock):
687 """Test main Run() function for full image."""
688 with remote_access.ChromiumOSDeviceHandler(
689 remote_access.TEST_IP
690 ) as device:
691 device_imager.StatefulUpdater(
692 False, device, "foo-image", device_imager.ImageType.FULL, None
693 ).Run()
694 update_mock.assert_called_with(
695 mock.ANY, is_payload_on_device=False, update_type=None
696 )
697 paygen_mock.assert_called()
Amin Hassani74403082021-02-22 11:40:09 -0800698
Alex Klein1699fab2022-09-08 08:46:06 -0600699 @mock.patch.object(gs.GSContext, "Copy")
700 @mock.patch.object(stateful_updater.StatefulUpdater, "Update")
701 def test_RunRemoteImage(self, update_mock, copy_mock):
702 """Test main Run() function for remote images."""
703 with remote_access.ChromiumOSDeviceHandler(
704 remote_access.TEST_IP
705 ) as device:
706 device_imager.StatefulUpdater(
707 False,
708 device,
709 "gs://foo-image",
710 device_imager.ImageType.REMOTE_DIRECTORY,
711 None,
712 ).Run()
713 copy_mock.assert_called_with(
714 "gs://foo-image/stateful.tgz", mock.ANY
715 )
716 update_mock.assert_called_with(
717 mock.ANY, is_payload_on_device=False, update_type=None
718 )
Amin Hassani74403082021-02-22 11:40:09 -0800719
Alex Klein1699fab2022-09-08 08:46:06 -0600720 @mock.patch.object(stateful_updater.StatefulUpdater, "Reset")
721 def testRevert(self, reset_mock):
722 """Tests Revert() function."""
723 su = device_imager.StatefulUpdater(False, None, None, None, None)
724
725 su.Revert()
726 reset_mock.assert_called()
Amin Hassani55970562021-02-22 20:49:13 -0800727
728
729class ProgressWatcherTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600730 """Tests ProgressWatcher class"""
Amin Hassani55970562021-02-22 20:49:13 -0800731
Alex Klein1699fab2022-09-08 08:46:06 -0600732 def setUp(self):
733 """Sets up the class by creating proper mocks."""
734 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
735 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
736 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani55970562021-02-22 20:49:13 -0800737
Alex Klein1699fab2022-09-08 08:46:06 -0600738 @mock.patch.object(time, "sleep")
739 @mock.patch.object(
740 device_imager.ProgressWatcher,
741 "_ShouldExit",
742 side_effect=[False, False, True],
743 )
744 # pylint: disable=unused-argument
745 def testRun(self, exit_mock, _):
746 """Tests the run() function."""
747 with remote_access.ChromiumOSDeviceHandler(
748 remote_access.TEST_IP
749 ) as device:
750 target_root = "/foo/root"
751 self.rsh_mock.AddCmdResult(
752 [self.path_env, "blockdev", "--getsize64", target_root],
753 stdout="100",
754 )
755 self.rsh_mock.AddCmdResult(
756 [self.path_env, "lsof", "-t", target_root], stdout="999"
757 )
758 self.rsh_mock.AddCmdResult(
759 [self.path_env, "cat", "/proc/999/fdinfo/1"],
760 stdout="pos: 10\nflags: foo",
761 )
762 pw = device_imager.ProgressWatcher(device, target_root)
763 pw.run()