blob: 61e302fbf886abb9d67638a320e835d02df863af [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 Klein975e86c2023-01-23 16:49:10 -0700188 # Per crbug.com/1196702 it seems like some other process gets the
189 # file descriptor right after we close it and by the time we check
190 # its existence, it is still there and this can flake. So it might
191 # be better to make sure this is checked properly through real paths
192 # and not symlinks.
Alex Klein1699fab2022-09-08 08:46:06 -0600193 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
Mike Frysinger81b6f562022-12-27 18:04:15 -0500221class PartialFileReaderTest(cros_test_lib.TempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600222 """Tests PartialFileReader class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800223
Mike Frysinger81b6f562022-12-27 18:04:15 -0500224 def testRunCat(self):
225 """Tests the main run() function with cat."""
226 # Create a data file to read. Pick a stride that doesn't repeat at the
227 # same offsets that we're reading.
228 # pylint: disable=range-builtin-not-iterating
229 data = bytes(range(250)) * 512
230 path = self.tempdir / "foo.bin"
231 path.write_bytes(data)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800232
Mike Frysinger81b6f562022-12-27 18:04:15 -0500233 # Use cat so we get the same data back out that we passed in to verify
234 # the reading offsets work correctly.
235 with device_imager.PartialFileReader(
236 path, 512 * 2, 512, ["cat"]
237 ) as pfr:
238 # We read more data than should be available to make sure we don't
239 # write too many bytes.
240 out_data = os.read(pfr.Target(), 1024)
241 assert out_data == data[1024 : 1024 + 512]
242
243 # Make sure the source has been close.
244 self.assertNotExists(GetFdPath(pfr._Source()))
245
246 def testRunShrinker(self):
247 """Tests the main run() function with a "compressor"."""
248 # Create a data file to read. Pick a stride that doesn't repeat at the
249 # same offsets that we're reading.
250 # pylint: disable=range-builtin-not-iterating
251 data = bytes(range(250)) * 512
252 path = self.tempdir / "foo.bin"
253 path.write_bytes(data)
254
255 with device_imager.PartialFileReader(
256 path, 512 * 2, 512, ["tail", "-c289"]
257 ) as pfr:
258 # We read more data than should be available to make sure we don't
259 # write too many bytes.
260 out_data = os.read(pfr.Target(), 1024)
261 assert len(out_data) == 289
262 # The sum is a poor man's crc/hash, but should be good enough for our
263 # purposes in this test.
264 assert sum(out_data) == 32499
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800265
Alex Klein1699fab2022-09-08 08:46:06 -0600266 # Make sure the source has been close.
267 self.assertNotExists(GetFdPath(pfr._Source()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800268
269
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800270class GsFileCopierTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600271 """Tests GsFileCopier class."""
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 @mock.patch.object(gs.GSContext, "Copy")
274 def testRun(self, copy_mock):
275 """Tests the run() function."""
276 image = "gs://path/to/image"
277 with device_imager.GsFileCopier(image) as gfc:
278 self.assertTrue(gfc._use_named_pipes)
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800279
Alex Klein1699fab2022-09-08 08:46:06 -0600280 copy_mock.assert_called_with(image, gfc._Source())
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800281
282
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800283class PartitionUpdaterBaseTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600284 """Tests PartitionUpdaterBase class"""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800285
Alex Klein1699fab2022-09-08 08:46:06 -0600286 def testRunNotImplemented(self):
287 """Tests running the main Run() function is not implemented."""
288 # We just want to make sure the _Run() function is not implemented here.
289 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
290 with self.assertRaises(NotImplementedError):
291 pub.Run()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800292
Alex Klein1699fab2022-09-08 08:46:06 -0600293 def testRevertNotImplemented(self):
294 """Tests running the Revert() function is not implemented."""
295 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
296 with self.assertRaises(NotImplementedError):
297 pub.Revert()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 @mock.patch.object(device_imager.PartitionUpdaterBase, "_Run")
300 def testIsFinished(self, _):
301 """Tests IsFinished() function."""
302 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
303 self.assertFalse(pub.IsFinished())
304 pub.Run()
305 self.assertTrue(pub.IsFinished())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800306
307
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800308class RawPartitionUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600309 """Tests RawPartitionUpdater class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800310
Alex Klein1699fab2022-09-08 08:46:06 -0600311 def setUp(self):
312 """Sets up the class by creating proper mocks."""
313 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
314 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
315 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800316
Alex Klein1699fab2022-09-08 08:46:06 -0600317 @mock.patch.object(
318 device_imager.RawPartitionUpdater,
319 "_GetPartitionName",
320 return_value=constants.PART_KERN_A,
321 )
322 @mock.patch.object(
323 image_lib,
324 "GetImageDiskPartitionInfo",
325 return_value=image_lib_unittest.LOOP_PARTITION_INFO,
326 )
327 @mock.patch.object(device_imager.PartialFileReader, "CloseTarget")
328 @mock.patch.object(device_imager.PartialFileReader, "run")
329 def test_RunFullImage(self, run_mock, close_mock, _, name_mock):
330 """Test main Run() function for full image.
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800331
Alex Klein975e86c2023-01-23 16:49:10 -0700332 This function should parts of the source image and write it into the
333 device using proper compression programs.
Alex Klein1699fab2022-09-08 08:46:06 -0600334 """
335 with remote_access.ChromiumOSDeviceHandler(
336 remote_access.TEST_IP
337 ) as device:
338 self.rsh_mock.AddCmdResult(
339 [partial_mock.In("which"), "gzip"], returncode=0
340 )
341 self.rsh_mock.AddCmdResult(
342 self.path_env
343 + " gzip --decompress --stdout | "
Daichi Hironof36983f2023-01-27 10:44:52 +0900344 + "dd bs=1M of=/dev/mmcblk0p2"
Alex Klein1699fab2022-09-08 08:46:06 -0600345 )
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800346
Alex Klein1699fab2022-09-08 08:46:06 -0600347 device_imager.RawPartitionUpdater(
348 device,
349 "foo-image",
350 device_imager.ImageType.FULL,
351 "/dev/mmcblk0p2",
352 ).Run()
353 run_mock.assert_called()
354 close_mock.assert_called()
355 name_mock.assert_called()
Amin Hassanid684e982021-02-26 11:10:58 -0800356
Alex Klein1699fab2022-09-08 08:46:06 -0600357 def test_RunRemoteImage(self):
358 """Test main Run() function for remote images."""
359 with remote_access.ChromiumOSDeviceHandler(
360 remote_access.TEST_IP
361 ) as device:
362 self.rsh_mock.AddCmdResult(
363 [partial_mock.In("which"), "gzip"], returncode=0
364 )
365 self.rsh_mock.AddCmdResult(
366 self.path_env
367 + " gzip --decompress --stdout | "
Daichi Hironof36983f2023-01-27 10:44:52 +0900368 + "dd bs=1M of=/dev/mmcblk0p2"
Alex Klein1699fab2022-09-08 08:46:06 -0600369 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800370
Alex Klein1699fab2022-09-08 08:46:06 -0600371 path = os.path.join(
372 self.tempdir, constants.QUICK_PROVISION_PAYLOAD_KERNEL
373 )
374 with open(path, "w") as image:
375 image.write("helloworld")
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800376
Alex Klein1699fab2022-09-08 08:46:06 -0600377 device_imager.KernelUpdater(
378 device,
379 self.tempdir,
380 device_imager.ImageType.REMOTE_DIRECTORY,
381 "/dev/mmcblk0p2",
382 ).Run()
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800383
Amin Hassanid684e982021-02-26 11:10:58 -0800384
385class KernelUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600386 """Tests KernelUpdater class."""
Amin Hassanid684e982021-02-26 11:10:58 -0800387
Alex Klein1699fab2022-09-08 08:46:06 -0600388 def test_GetPartitionName(self):
389 """Tests the name of the partitions."""
390 ku = device_imager.KernelUpdater(None, None, None, None)
391 self.assertEqual(constants.PART_KERN_B, ku._GetPartitionName())
Amin Hassani75c5f942021-02-20 23:56:53 -0800392
Alex Klein1699fab2022-09-08 08:46:06 -0600393 def test_GetRemotePartitionName(self):
394 """Tests the name of the partitions."""
395 ku = device_imager.KernelUpdater(None, None, None, None)
396 self.assertEqual(
397 constants.QUICK_PROVISION_PAYLOAD_KERNEL,
398 ku._GetRemotePartitionName(),
399 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800400
Amin Hassani75c5f942021-02-20 23:56:53 -0800401
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000402class MiniOSUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600403 """Tests MiniOSUpdater class."""
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000404
Alex Klein1699fab2022-09-08 08:46:06 -0600405 def test_GetPartitionName(self):
406 """Tests the name of the partitions."""
407 u = device_imager.MiniOSUpdater(*([None] * 4))
408 self.assertEqual(constants.PART_MINIOS_A, u._GetPartitionName())
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000409
Alex Klein1699fab2022-09-08 08:46:06 -0600410 def test_GetRemotePartitionName(self):
411 """Tests the name of the partitions."""
412 u = device_imager.MiniOSUpdater(*([None] * 4))
413 self.assertEqual(
414 constants.QUICK_PROVISION_PAYLOAD_MINIOS,
415 u._GetRemotePartitionName(),
416 )
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 @mock.patch.object(device_imager.MiniOSUpdater, "_CopyPartitionFromImage")
419 @mock.patch.object(
420 device_imager.MiniOSUpdater,
421 "_MiniOSPartitionsExistInImage",
422 return_value=True,
423 )
424 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
425 def test_Run(self, postinstall_mock, partitions_exist_mock, copy_mock):
426 """Test main Run() function."""
427 with remote_access.ChromiumOSDeviceHandler(
428 remote_access.TEST_IP
429 ) as device:
430 device_imager.MiniOSUpdater(
431 device,
432 "foo-image",
433 device_imager.ImageType.FULL,
434 "/dev/mmcblk0p10",
435 ).Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000436
Alex Klein1699fab2022-09-08 08:46:06 -0600437 copy_mock.assert_called_with(constants.PART_MINIOS_A)
438 partitions_exist_mock.assert_called_with()
439 postinstall_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000440
Alex Klein1699fab2022-09-08 08:46:06 -0600441 @mock.patch.object(device_imager.MiniOSUpdater, "_CopyPartitionFromImage")
442 @mock.patch.object(
443 device_imager.MiniOSUpdater,
444 "_MiniOSPartitionsExistInImage",
445 return_value=False,
446 )
447 def test_RunMissingMiniOS(self, partitions_exist_mock, copy_mock):
448 """Test main Run() function with missing miniOS partitions on image."""
449 with remote_access.ChromiumOSDeviceHandler(
450 remote_access.TEST_IP
451 ) as device:
452 device_imager.MiniOSUpdater(
453 device,
454 "foo-image",
455 device_imager.ImageType.FULL,
456 "/dev/mmcblk0p10",
457 ).Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000458
Alex Klein1699fab2022-09-08 08:46:06 -0600459 copy_mock.assert_not_called()
460 partitions_exist_mock.assert_called_with()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700461
Alex Klein1699fab2022-09-08 08:46:06 -0600462 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
463 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
464 @mock.patch.object(device_imager.MiniOSUpdater, "_MiniOSPartitionsExist")
465 @mock.patch.object(gs.GSContext, "Exists", return_value=False)
466 def test_RunMissingMiniOSRemotePayload(
467 self,
468 gs_context_mock,
469 partitions_exist_mock,
470 redirect_mock,
471 post_install_mock,
472 ):
473 """Test main Run() function with missing miniOS remote payloads."""
474 with remote_access.ChromiumOSDeviceHandler(
475 remote_access.TEST_IP
476 ) as device:
477 device_imager.MiniOSUpdater(
478 device,
479 "foo-image",
480 device_imager.ImageType.REMOTE_DIRECTORY,
481 "/dev/mmcblk0p10",
482 ).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700483
Alex Klein1699fab2022-09-08 08:46:06 -0600484 post_install_mock.assert_not_called()
485 redirect_mock.assert_not_called()
486 partitions_exist_mock.assert_not_called()
487 gs_context_mock.assert_called()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700488
Alex Klein1699fab2022-09-08 08:46:06 -0600489 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
490 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
491 @mock.patch.object(
492 device_imager.MiniOSUpdater,
493 "_MiniOSPartitionsExist",
494 return_value=False,
495 )
496 @mock.patch.object(gs.GSContext, "Exists", return_value=True)
497 def test_RunMissingMiniOSPartitions(
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_not_called()
516 redirect_mock.assert_not_called()
517 partitions_exist_mock.assert_called()
518 gs_context_mock.assert_called()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700519
Alex Klein1699fab2022-09-08 08:46:06 -0600520 @mock.patch.object(device_imager.MiniOSUpdater, "_RunPostInstall")
521 @mock.patch.object(device_imager.MiniOSUpdater, "_RedirectPartition")
522 @mock.patch.object(
523 device_imager.MiniOSUpdater, "_MiniOSPartitionsExist", return_value=True
524 )
525 @mock.patch.object(gs.GSContext, "Exists", return_value=True)
526 def test_RunMiniOSRemotePayload(
527 self,
528 gs_context_mock,
529 partitions_exist_mock,
530 redirect_mock,
531 post_install_mock,
532 ):
533 """Test main Run() function with missing miniOS remote payloads."""
534 with remote_access.ChromiumOSDeviceHandler(
535 remote_access.TEST_IP
536 ) as device:
537 device_imager.MiniOSUpdater(
538 device,
539 "foo-image",
540 device_imager.ImageType.REMOTE_DIRECTORY,
541 "/dev/mmcblk0p10",
542 ).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700543
Alex Klein1699fab2022-09-08 08:46:06 -0600544 post_install_mock.assert_called()
545 redirect_mock.assert_called()
546 partitions_exist_mock.assert_called()
547 gs_context_mock.assert_called()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000548
Alex Klein1699fab2022-09-08 08:46:06 -0600549 @mock.patch.object(device_imager.MiniOSUpdater, "_FlipMiniOSPriority")
550 def test_RunPostInstall(self, flip_mock):
551 """Test _RunPostInstall() function."""
552 with remote_access.ChromiumOSDeviceHandler(
553 remote_access.TEST_IP
554 ) as device:
555 device_imager.MiniOSUpdater(
556 device,
557 "foo-image",
558 device_imager.ImageType.FULL,
559 "/dev/mmcblk0p10",
560 )._RunPostInstall()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000561
Alex Klein1699fab2022-09-08 08:46:06 -0600562 flip_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000563
Alex Klein1699fab2022-09-08 08:46:06 -0600564 @mock.patch.object(device_imager.MiniOSUpdater, "_FlipMiniOSPriority")
565 def test_Revert(self, flip_mock):
566 """Test Revert() function."""
567 u = device_imager.MiniOSUpdater(
568 None, "foo-image", device_imager.ImageType.FULL, "/dev/mmcblk0p10"
569 )
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000570
Alex Klein1699fab2022-09-08 08:46:06 -0600571 # Before PostInstall runs.
572 u.Revert()
573 flip_mock.assert_not_called()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000574
Alex Klein1699fab2022-09-08 08:46:06 -0600575 u._ran_postinst = True
576 u.Revert()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000577
Alex Klein1699fab2022-09-08 08:46:06 -0600578 flip_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000579
Alex Klein1699fab2022-09-08 08:46:06 -0600580 @mock.patch.object(
581 device_imager.MiniOSUpdater, "_GetMiniOSPriority", return_value="A"
582 )
583 @mock.patch.object(device_imager.MiniOSUpdater, "_SetMiniOSPriority")
584 def test_FlipMiniOSPriority(self, set_mock, get_mock):
585 """Test _FlipMiniOSPriority() function."""
586 device_imager.MiniOSUpdater(
587 None, "foo-image", device_imager.ImageType.FULL, "/dev/mmcblk0p10"
588 )._FlipMiniOSPriority()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000589
Alex Klein1699fab2022-09-08 08:46:06 -0600590 get_mock.assert_called_with()
591 set_mock.assert_called_with("B")
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000592
593
Amin Hassani75c5f942021-02-20 23:56:53 -0800594class RootfsUpdaterTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600595 """Tests RootfsUpdater class."""
Amin Hassani75c5f942021-02-20 23:56:53 -0800596
Alex Klein1699fab2022-09-08 08:46:06 -0600597 def setUp(self):
598 """Sets up the class by creating proper mocks."""
599 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
600 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
601 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani75c5f942021-02-20 23:56:53 -0800602
Alex Klein1699fab2022-09-08 08:46:06 -0600603 def test_GetPartitionName(self):
604 """Tests the name of the partitions."""
605 ru = device_imager.RootfsUpdater(None, None, None, None, None)
606 self.assertEqual(constants.PART_ROOT_A, ru._GetPartitionName())
Amin Hassani75c5f942021-02-20 23:56:53 -0800607
Alex Klein1699fab2022-09-08 08:46:06 -0600608 def test_GetRemotePartitionName(self):
609 """Tests the name of the partitions."""
610 ru = device_imager.RootfsUpdater(None, None, None, None, None)
611 self.assertEqual(
612 constants.QUICK_PROVISION_PAYLOAD_ROOTFS,
613 ru._GetRemotePartitionName(),
614 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800615
Alex Klein1699fab2022-09-08 08:46:06 -0600616 @mock.patch.object(device_imager.ProgressWatcher, "run")
617 @mock.patch.object(device_imager.RootfsUpdater, "_RunPostInst")
618 @mock.patch.object(device_imager.RootfsUpdater, "_CopyPartitionFromImage")
619 def test_Run(self, copy_mock, postinst_mock, pw_mock):
620 """Test main Run() function.
Amin Hassani75c5f942021-02-20 23:56:53 -0800621
Alex Klein975e86c2023-01-23 16:49:10 -0700622 This function should parts of the source image and write it into the
623 device using proper compression programs.
Alex Klein1699fab2022-09-08 08:46:06 -0600624 """
625 with remote_access.ChromiumOSDeviceHandler(
626 remote_access.TEST_IP
627 ) as device:
628 device_imager.RootfsUpdater(
629 "/dev/mmcblk0p5",
630 device,
631 "foo-image",
632 device_imager.ImageType.FULL,
633 "/dev/mmcblk0p3",
634 ).Run()
Amin Hassani75c5f942021-02-20 23:56:53 -0800635
Alex Klein1699fab2022-09-08 08:46:06 -0600636 copy_mock.assert_called_with(constants.PART_ROOT_A)
637 postinst_mock.assert_called_with()
638 pw_mock.assert_called()
Amin Hassani75c5f942021-02-20 23:56:53 -0800639
Alex Klein1699fab2022-09-08 08:46:06 -0600640 def test_RunPostInstOnTarget(self):
641 """Test _RunPostInst() function."""
642 target = "/dev/mmcblk0p3"
643 with remote_access.ChromiumOSDeviceHandler(
644 remote_access.TEST_IP
645 ) as device:
646 device._work_dir = "/tmp/work_dir"
647 temp_dir = os.path.join(device.work_dir, "dir")
648 self.rsh_mock.AddCmdResult(
649 [self.path_env, "mktemp", "-d", "-p", device.work_dir],
650 stdout=temp_dir,
651 )
652 self.rsh_mock.AddCmdResult(
653 [self.path_env, "mount", "-o", "ro", target, temp_dir]
654 )
655 self.rsh_mock.AddCmdResult(
656 [self.path_env, os.path.join(temp_dir, "postinst"), target]
657 )
658 self.rsh_mock.AddCmdResult([self.path_env, "umount", temp_dir])
Amin Hassani75c5f942021-02-20 23:56:53 -0800659
Alex Klein1699fab2022-09-08 08:46:06 -0600660 device_imager.RootfsUpdater(
661 "/dev/mmcblk0p5",
662 device,
663 "foo-image",
664 device_imager.ImageType.FULL,
665 target,
666 )._RunPostInst()
Amin Hassani75c5f942021-02-20 23:56:53 -0800667
Alex Klein1699fab2022-09-08 08:46:06 -0600668 def test_RunPostInstOnCurrentRoot(self):
Alex Klein975e86c2023-01-23 16:49:10 -0700669 """Test _RunPostInst() on current root; used for reverting an update."""
Alex Klein1699fab2022-09-08 08:46:06 -0600670 root_dev = "/dev/mmcblk0p5"
671 self.rsh_mock.AddCmdResult([self.path_env, "/postinst", root_dev])
Amin Hassani75c5f942021-02-20 23:56:53 -0800672
Alex Klein1699fab2022-09-08 08:46:06 -0600673 with remote_access.ChromiumOSDeviceHandler(
674 remote_access.TEST_IP
675 ) as device:
676 device_imager.RootfsUpdater(
677 root_dev,
678 device,
679 "foo-image",
680 device_imager.ImageType.FULL,
681 "/dev/mmcblk0p3",
682 )._RunPostInst(on_target=False)
Amin Hassani75c5f942021-02-20 23:56:53 -0800683
Alex Klein1699fab2022-09-08 08:46:06 -0600684 @mock.patch.object(device_imager.RootfsUpdater, "_RunPostInst")
685 def testRevert(self, postinst_mock):
686 """Tests Revert() function."""
687 ru = device_imager.RootfsUpdater(None, None, None, None, None)
Amin Hassani75c5f942021-02-20 23:56:53 -0800688
Alex Klein1699fab2022-09-08 08:46:06 -0600689 ru.Revert()
690 postinst_mock.assert_not_called()
Amin Hassani75c5f942021-02-20 23:56:53 -0800691
Alex Klein1699fab2022-09-08 08:46:06 -0600692 ru._ran_postinst = True
693 ru.Revert()
694 postinst_mock.assert_called_with(on_target=False)
Amin Hassani74403082021-02-22 11:40:09 -0800695
696
697class StatefulPayloadGeneratorTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600698 """Tests stateful payload generator."""
Amin Hassani74403082021-02-22 11:40:09 -0800699
Alex Klein1699fab2022-09-08 08:46:06 -0600700 @mock.patch.object(paygen_stateful_payload_lib, "GenerateStatefulPayload")
701 def testRun(self, paygen_mock):
702 """Tests run() function."""
703 image = "/foo/image"
704 with device_imager.StatefulPayloadGenerator(image) as spg:
705 pass
706
707 paygen_mock.assert_called_with(image, spg._Source())
Amin Hassani74403082021-02-22 11:40:09 -0800708
709
710class StatefulUpdaterTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600711 """Tests StatefulUpdater."""
Amin Hassani74403082021-02-22 11:40:09 -0800712
Alex Klein1699fab2022-09-08 08:46:06 -0600713 @mock.patch.object(paygen_stateful_payload_lib, "GenerateStatefulPayload")
714 @mock.patch.object(stateful_updater.StatefulUpdater, "Update")
715 def test_RunFullImage(self, update_mock, paygen_mock):
716 """Test main Run() function for full image."""
717 with remote_access.ChromiumOSDeviceHandler(
718 remote_access.TEST_IP
719 ) as device:
720 device_imager.StatefulUpdater(
721 False, device, "foo-image", device_imager.ImageType.FULL, None
722 ).Run()
723 update_mock.assert_called_with(
724 mock.ANY, is_payload_on_device=False, update_type=None
725 )
726 paygen_mock.assert_called()
Amin Hassani74403082021-02-22 11:40:09 -0800727
Alex Klein1699fab2022-09-08 08:46:06 -0600728 @mock.patch.object(gs.GSContext, "Copy")
729 @mock.patch.object(stateful_updater.StatefulUpdater, "Update")
730 def test_RunRemoteImage(self, update_mock, copy_mock):
731 """Test main Run() function for remote images."""
732 with remote_access.ChromiumOSDeviceHandler(
733 remote_access.TEST_IP
734 ) as device:
735 device_imager.StatefulUpdater(
736 False,
737 device,
738 "gs://foo-image",
739 device_imager.ImageType.REMOTE_DIRECTORY,
740 None,
741 ).Run()
742 copy_mock.assert_called_with(
743 "gs://foo-image/stateful.tgz", mock.ANY
744 )
745 update_mock.assert_called_with(
746 mock.ANY, is_payload_on_device=False, update_type=None
747 )
Amin Hassani74403082021-02-22 11:40:09 -0800748
Alex Klein1699fab2022-09-08 08:46:06 -0600749 @mock.patch.object(stateful_updater.StatefulUpdater, "Reset")
750 def testRevert(self, reset_mock):
751 """Tests Revert() function."""
752 su = device_imager.StatefulUpdater(False, None, None, None, None)
753
754 su.Revert()
755 reset_mock.assert_called()
Amin Hassani55970562021-02-22 20:49:13 -0800756
757
758class ProgressWatcherTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600759 """Tests ProgressWatcher class"""
Amin Hassani55970562021-02-22 20:49:13 -0800760
Alex Klein1699fab2022-09-08 08:46:06 -0600761 def setUp(self):
762 """Sets up the class by creating proper mocks."""
763 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
764 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
765 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani55970562021-02-22 20:49:13 -0800766
Alex Klein1699fab2022-09-08 08:46:06 -0600767 @mock.patch.object(time, "sleep")
768 @mock.patch.object(
769 device_imager.ProgressWatcher,
770 "_ShouldExit",
771 side_effect=[False, False, True],
772 )
773 # pylint: disable=unused-argument
774 def testRun(self, exit_mock, _):
775 """Tests the run() function."""
776 with remote_access.ChromiumOSDeviceHandler(
777 remote_access.TEST_IP
778 ) as device:
779 target_root = "/foo/root"
780 self.rsh_mock.AddCmdResult(
781 [self.path_env, "blockdev", "--getsize64", target_root],
782 stdout="100",
783 )
784 self.rsh_mock.AddCmdResult(
785 [self.path_env, "lsof", "-t", target_root], stdout="999"
786 )
787 self.rsh_mock.AddCmdResult(
788 [self.path_env, "cat", "/proc/999/fdinfo/1"],
789 stdout="pos: 10\nflags: foo",
790 )
791 pw = device_imager.ProgressWatcher(device, target_root)
792 pw.run()