blob: 754969004d993188cf70edf03ee7cf2066cb2db3 [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
Mike Frysinger506db6b2023-02-24 16:10:59 -050018from chromite.lib import osutils
Amin Hassani92f6c4a2021-02-20 17:36:09 -080019from chromite.lib import partial_mock
20from chromite.lib import remote_access
21from chromite.lib import remote_access_unittest
Amin Hassani74403082021-02-22 11:40:09 -080022from chromite.lib import stateful_updater
23from chromite.lib.paygen import paygen_stateful_payload_lib
Amin Hassani92f6c4a2021-02-20 17:36:09 -080024from chromite.lib.xbuddy import xbuddy
25
26
Amin Hassani92f6c4a2021-02-20 17:36:09 -080027# pylint: disable=protected-access
28
Amin Hassanid4b3ff82021-02-20 23:05:14 -080029
30def GetFdPath(fd):
Alex Klein1699fab2022-09-08 08:46:06 -060031 """Returns the fd path for the current process."""
32 return f"/proc/self/fd/{fd}"
Amin Hassanid4b3ff82021-02-20 23:05:14 -080033
34
Amin Hassani92f6c4a2021-02-20 17:36:09 -080035class DeviceImagerTest(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -060036 """Tests DeviceImager class methods."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -080037
Alex Klein1699fab2022-09-08 08:46:06 -060038 def setUp(self):
39 """Sets up the class by creating proper mocks."""
40 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
41 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
42 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassani92f6c4a2021-02-20 17:36:09 -080043
Alex Klein1699fab2022-09-08 08:46:06 -060044 def test_LocateImageLocalFile(self):
45 """Tests getting the path to local image."""
46 with tempfile.NamedTemporaryFile() as fp:
47 di = device_imager.DeviceImager(None, fp.name)
48 di._LocateImage()
49 self.assertEqual(di._image, fp.name)
50 self.assertEqual(di._image_type, device_imager.ImageType.FULL)
Amin Hassani92f6c4a2021-02-20 17:36:09 -080051
Alex Klein1699fab2022-09-08 08:46:06 -060052 def test_LocateImageDir(self):
53 """Tests failing on a given directory as a path."""
54 di = device_imager.DeviceImager(None, "/tmp")
55 with self.assertRaises(ValueError):
56 di._LocateImage()
Amin Hassani92f6c4a2021-02-20 17:36:09 -080057
Alex Klein1699fab2022-09-08 08:46:06 -060058 @mock.patch.object(
59 xbuddy.XBuddy, "Translate", return_value=("eve/R90", None)
60 )
61 @mock.patch.object(
62 remote_access.ChromiumOSDevice,
63 "board",
64 return_value="foo",
65 new_callable=mock.PropertyMock,
66 )
67 # pylint: disable=unused-argument
68 def test_LocateImageXBuddyRemote(self, _, board_mock):
69 """Tests getting remote xBuddy image path."""
70 with remote_access.ChromiumOSDeviceHandler(
71 remote_access.TEST_IP
72 ) as device:
73 di = device_imager.DeviceImager(
74 device, "xbuddy://remote/eve/latest"
75 )
76 di._LocateImage()
77 self.assertEqual(di._image, "gs://chromeos-image-archive/eve/R90")
78 self.assertEqual(
79 di._image_type, device_imager.ImageType.REMOTE_DIRECTORY
80 )
Amin Hassani92f6c4a2021-02-20 17:36:09 -080081
Alex Klein1699fab2022-09-08 08:46:06 -060082 @mock.patch.object(
83 xbuddy.XBuddy, "Translate", return_value=("eve/R90", "path/to/file")
84 )
85 @mock.patch.object(
86 remote_access.ChromiumOSDevice,
87 "board",
88 return_value="foo",
89 new_callable=mock.PropertyMock,
90 )
91 # pylint: disable=unused-argument
92 def test_LocateImageXBuddyLocal(self, _, board_mock):
93 """Tests getting local xBuddy image path."""
94 with remote_access.ChromiumOSDeviceHandler(
95 remote_access.TEST_IP
96 ) as device:
97 di = device_imager.DeviceImager(device, "xbuddy://local/eve/latest")
98 di._LocateImage()
99 self.assertEqual(di._image, "path/to/file")
100 self.assertEqual(di._image_type, device_imager.ImageType.FULL)
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 def test_SplitDevPath(self):
103 """Tests splitting a device path into prefix and partition number."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800104
Alex Klein1699fab2022-09-08 08:46:06 -0600105 di = device_imager.DeviceImager(None, None)
106 self.assertEqual(di._SplitDevPath("/dev/foop3"), ("/dev/foop", 3))
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800107
Alex Klein1699fab2022-09-08 08:46:06 -0600108 with self.assertRaises(device_imager.Error):
109 di._SplitDevPath("/foo")
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 with self.assertRaises(device_imager.Error):
112 di._SplitDevPath("/foo/p3p")
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800113
Alex Klein1699fab2022-09-08 08:46:06 -0600114 def test_GetKernelState(self):
115 """Tests getting the current active and inactive kernel states."""
116 di = device_imager.DeviceImager(None, None)
117 self.assertEqual(
118 di._GetKernelState(3),
119 (device_imager.DeviceImager.A, device_imager.DeviceImager.B),
120 )
121 self.assertEqual(
122 di._GetKernelState(5),
123 (device_imager.DeviceImager.B, device_imager.DeviceImager.A),
124 )
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800125
Alex Klein1699fab2022-09-08 08:46:06 -0600126 with self.assertRaises(device_imager.Error):
127 di._GetKernelState(1)
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 @mock.patch.object(
130 remote_access.ChromiumOSDevice,
131 "root_dev",
132 return_value="/dev/foop3",
133 new_callable=mock.PropertyMock,
134 )
135 def test_VerifyBootExpectations(self, _):
136 """Tests verifying the boot expectations after reboot."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800137
Alex Klein1699fab2022-09-08 08:46:06 -0600138 with remote_access.ChromiumOSDeviceHandler(
139 remote_access.TEST_IP
140 ) as device:
141 di = device_imager.DeviceImager(device, None)
142 di._inactive_state = device_imager.DeviceImager.A
143 di._VerifyBootExpectations()
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800144
Alex Klein1699fab2022-09-08 08:46:06 -0600145 @mock.patch.object(
146 remote_access.ChromiumOSDevice,
147 "root_dev",
148 return_value="/dev/foop3",
149 new_callable=mock.PropertyMock,
150 )
151 def test_VerifyBootExpectationsFails(self, _):
152 """Tests failure of boot expectations."""
Amin Hassani92f6c4a2021-02-20 17:36:09 -0800153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 with remote_access.ChromiumOSDeviceHandler(
155 remote_access.TEST_IP
156 ) as device:
157 di = device_imager.DeviceImager(device, None)
158 di._inactive_state = device_imager.DeviceImager.B
159 with self.assertRaises(device_imager.Error):
160 di._VerifyBootExpectations()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800161
162
163class TestReaderBase(cros_test_lib.MockTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600164 """Test ReaderBase class"""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 def testNamedPipe(self):
167 """Tests initializing the class with named pipe."""
168 with device_imager.ReaderBase(use_named_pipes=True) as r:
169 self.assertIsInstance(r.Target(), str)
170 self.assertEqual(r._Source(), r.Target())
171 self.assertExists(r.Target())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 r._CloseSource() # Should not have any effect.
174 self.assertExists(r._Source())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800175
Alex Klein1699fab2022-09-08 08:46:06 -0600176 # Closing target should delete the named pipe.
177 r.CloseTarget()
178 self.assertNotExists(r.Target())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 def testFdPipe(self):
181 """Tests initializing the class with normal file descriptor pipes."""
182 with device_imager.ReaderBase() as r:
183 self.assertIsInstance(r.Target(), int)
184 self.assertIsInstance(r._Source(), int)
185 self.assertNotEqual(r._Source(), r.Target())
186 self.assertExists(GetFdPath(r.Target()))
187 self.assertExists(GetFdPath(r._Source()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800188
Alex Klein975e86c2023-01-23 16:49:10 -0700189 # Per crbug.com/1196702 it seems like some other process gets the
190 # file descriptor right after we close it and by the time we check
191 # its existence, it is still there and this can flake. So it might
192 # be better to make sure this is checked properly through real paths
193 # and not symlinks.
Alex Klein1699fab2022-09-08 08:46:06 -0600194 path = GetFdPath(r._Source())
195 old_path = os.path.realpath(path)
196 r._CloseSource()
197 with self.assertRaises(OSError):
198 new_path = os.path.realpath(path)
199 self.assertNotEqual(old_path, new_path)
200 raise OSError("Fake the context manager.")
Amin Hassanifa11c692021-04-07 09:17:31 -0700201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 self.assertExists(GetFdPath(r.Target()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800203
Alex Klein1699fab2022-09-08 08:46:06 -0600204 path = GetFdPath(r.Target())
205 old_path = os.path.realpath(path)
206 r.CloseTarget()
207 with self.assertRaises(OSError):
208 new_path = os.path.realpath(path)
209 self.assertNotEqual(old_path, new_path)
210 raise OSError("Fake the context manager.")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800211
Alex Klein1699fab2022-09-08 08:46:06 -0600212 def testFdPipeCommunicate(self):
213 """Tests that file descriptors pipe can actually communicate."""
214 with device_imager.ReaderBase() as r:
215 with os.fdopen(r._Source(), "w") as fp:
216 fp.write("helloworld")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800217
Alex Klein1699fab2022-09-08 08:46:06 -0600218 with os.fdopen(r.Target(), "r") as fp:
219 self.assertEqual(fp.read(), "helloworld")
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800220
221
Mike Frysinger81b6f562022-12-27 18:04:15 -0500222class PartialFileReaderTest(cros_test_lib.TempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600223 """Tests PartialFileReader class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800224
Mike Frysinger81b6f562022-12-27 18:04:15 -0500225 def testRunCat(self):
226 """Tests the main run() function with cat."""
227 # Create a data file to read. Pick a stride that doesn't repeat at the
228 # same offsets that we're reading.
229 # pylint: disable=range-builtin-not-iterating
230 data = bytes(range(250)) * 512
231 path = self.tempdir / "foo.bin"
232 path.write_bytes(data)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800233
Mike Frysinger81b6f562022-12-27 18:04:15 -0500234 # Use cat so we get the same data back out that we passed in to verify
235 # the reading offsets work correctly.
236 with device_imager.PartialFileReader(
237 path, 512 * 2, 512, ["cat"]
238 ) as pfr:
239 # We read more data than should be available to make sure we don't
240 # write too many bytes.
241 out_data = os.read(pfr.Target(), 1024)
242 assert out_data == data[1024 : 1024 + 512]
243
244 # Make sure the source has been close.
245 self.assertNotExists(GetFdPath(pfr._Source()))
246
247 def testRunShrinker(self):
248 """Tests the main run() function with a "compressor"."""
249 # Create a data file to read. Pick a stride that doesn't repeat at the
250 # same offsets that we're reading.
251 # pylint: disable=range-builtin-not-iterating
252 data = bytes(range(250)) * 512
253 path = self.tempdir / "foo.bin"
254 path.write_bytes(data)
255
256 with device_imager.PartialFileReader(
257 path, 512 * 2, 512, ["tail", "-c289"]
258 ) as pfr:
259 # We read more data than should be available to make sure we don't
260 # write too many bytes.
261 out_data = os.read(pfr.Target(), 1024)
262 assert len(out_data) == 289
263 # The sum is a poor man's crc/hash, but should be good enough for our
264 # purposes in this test.
265 assert sum(out_data) == 32499
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 # Make sure the source has been close.
268 self.assertNotExists(GetFdPath(pfr._Source()))
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800269
270
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800271class GsFileCopierTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600272 """Tests GsFileCopier class."""
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800273
Alex Klein1699fab2022-09-08 08:46:06 -0600274 @mock.patch.object(gs.GSContext, "Copy")
275 def testRun(self, copy_mock):
276 """Tests the run() function."""
277 image = "gs://path/to/image"
278 with device_imager.GsFileCopier(image) as gfc:
279 self.assertTrue(gfc._use_named_pipes)
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800280
Alex Klein1699fab2022-09-08 08:46:06 -0600281 copy_mock.assert_called_with(image, gfc._Source())
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800282
283
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800284class PartitionUpdaterBaseTest(cros_test_lib.TestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600285 """Tests PartitionUpdaterBase class"""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800286
Alex Klein1699fab2022-09-08 08:46:06 -0600287 def testRunNotImplemented(self):
288 """Tests running the main Run() function is not implemented."""
289 # We just want to make sure the _Run() function is not implemented here.
290 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
291 with self.assertRaises(NotImplementedError):
292 pub.Run()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 def testRevertNotImplemented(self):
295 """Tests running the Revert() function is not implemented."""
296 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
297 with self.assertRaises(NotImplementedError):
298 pub.Revert()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800299
Alex Klein1699fab2022-09-08 08:46:06 -0600300 @mock.patch.object(device_imager.PartitionUpdaterBase, "_Run")
301 def testIsFinished(self, _):
302 """Tests IsFinished() function."""
303 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
304 self.assertFalse(pub.IsFinished())
305 pub.Run()
306 self.assertTrue(pub.IsFinished())
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800307
308
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800309class RawPartitionUpdaterTest(cros_test_lib.MockTempDirTestCase):
Alex Klein1699fab2022-09-08 08:46:06 -0600310 """Tests RawPartitionUpdater class."""
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800311
Alex Klein1699fab2022-09-08 08:46:06 -0600312 def setUp(self):
313 """Sets up the class by creating proper mocks."""
314 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
315 self.rsh_mock.AddCmdResult(partial_mock.In("${PATH}"), stdout="")
316 self.path_env = "PATH=%s:" % remote_access.DEV_BIN_PATHS
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800317
Alex Klein1699fab2022-09-08 08:46:06 -0600318 @mock.patch.object(
319 device_imager.RawPartitionUpdater,
320 "_GetPartitionName",
321 return_value=constants.PART_KERN_A,
322 )
323 @mock.patch.object(
324 image_lib,
325 "GetImageDiskPartitionInfo",
326 return_value=image_lib_unittest.LOOP_PARTITION_INFO,
327 )
328 @mock.patch.object(device_imager.PartialFileReader, "CloseTarget")
329 @mock.patch.object(device_imager.PartialFileReader, "run")
330 def test_RunFullImage(self, run_mock, close_mock, _, name_mock):
331 """Test main Run() function for full image.
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800332
Alex Klein975e86c2023-01-23 16:49:10 -0700333 This function should parts of the source image and write it into the
334 device using proper compression programs.
Alex Klein1699fab2022-09-08 08:46:06 -0600335 """
336 with remote_access.ChromiumOSDeviceHandler(
337 remote_access.TEST_IP
338 ) as device:
339 self.rsh_mock.AddCmdResult(
340 [partial_mock.In("which"), "gzip"], returncode=0
341 )
342 self.rsh_mock.AddCmdResult(
343 self.path_env
344 + " gzip --decompress --stdout | "
Daichi Hironof36983f2023-01-27 10:44:52 +0900345 + "dd bs=1M of=/dev/mmcblk0p2"
Alex Klein1699fab2022-09-08 08:46:06 -0600346 )
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800347
Alex Klein1699fab2022-09-08 08:46:06 -0600348 device_imager.RawPartitionUpdater(
349 device,
350 "foo-image",
351 device_imager.ImageType.FULL,
352 "/dev/mmcblk0p2",
353 ).Run()
354 run_mock.assert_called()
355 close_mock.assert_called()
356 name_mock.assert_called()
Amin Hassanid684e982021-02-26 11:10:58 -0800357
Alex Klein1699fab2022-09-08 08:46:06 -0600358 def test_RunRemoteImage(self):
359 """Test main Run() function for remote images."""
360 with remote_access.ChromiumOSDeviceHandler(
361 remote_access.TEST_IP
362 ) as device:
363 self.rsh_mock.AddCmdResult(
364 [partial_mock.In("which"), "gzip"], returncode=0
365 )
366 self.rsh_mock.AddCmdResult(
367 self.path_env
368 + " gzip --decompress --stdout | "
Daichi Hironof36983f2023-01-27 10:44:52 +0900369 + "dd bs=1M of=/dev/mmcblk0p2"
Alex Klein1699fab2022-09-08 08:46:06 -0600370 )
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800371
Alex Klein1699fab2022-09-08 08:46:06 -0600372 path = os.path.join(
373 self.tempdir, constants.QUICK_PROVISION_PAYLOAD_KERNEL
374 )
Mike Frysinger506db6b2023-02-24 16:10:59 -0500375 osutils.WriteFile(path, "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()