blob: 13a320f9a43f84c63674b1c44fd3d78b56d7f6f3 [file] [log] [blame]
Amin Hassani92f6c4a2021-02-20 17:36:09 -08001# Copyright 2021 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unit tests for the 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):
30 """Returns the fd path for the current process."""
31 return f'/proc/self/fd/{fd}'
32
33
Amin Hassani92f6c4a2021-02-20 17:36:09 -080034class DeviceImagerTest(cros_test_lib.MockTestCase):
35 """Tests DeviceImager class methods."""
36
37 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
42
Amin Hassanib1993eb2021-04-28 12:00:11 -070043 def test_LocateImageLocalFile(self):
Amin Hassani92f6c4a2021-02-20 17:36:09 -080044 """Tests getting the path to local image."""
45 with tempfile.NamedTemporaryFile() as fp:
46 di = device_imager.DeviceImager(None, fp.name)
Amin Hassanib1993eb2021-04-28 12:00:11 -070047 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
Amin Hassanib1993eb2021-04-28 12:00:11 -070051 def test_LocateImageDir(self):
Amin Hassani92f6c4a2021-02-20 17:36:09 -080052 """Tests failing on a given directory as a path."""
53 di = device_imager.DeviceImager(None, '/tmp')
54 with self.assertRaises(ValueError):
Amin Hassanib1993eb2021-04-28 12:00:11 -070055 di._LocateImage()
Amin Hassani92f6c4a2021-02-20 17:36:09 -080056
57 @mock.patch.object(xbuddy.XBuddy, 'Translate', return_value=('eve/R90', None))
Amin Hassani70c372a2021-03-31 20:18:51 -070058 @mock.patch.object(remote_access.ChromiumOSDevice, 'board',
59 return_value='foo', new_callable=mock.PropertyMock)
60 # pylint: disable=unused-argument
Amin Hassanib1993eb2021-04-28 12:00:11 -070061 def test_LocateImageXBuddyRemote(self, _, board_mock):
Amin Hassani92f6c4a2021-02-20 17:36:09 -080062 """Tests getting remote xBuddy image path."""
Amin Hassani70c372a2021-03-31 20:18:51 -070063 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
64 di = device_imager.DeviceImager(device, 'xbuddy://remote/eve/latest')
Amin Hassanib1993eb2021-04-28 12:00:11 -070065 di._LocateImage()
66 self.assertEqual(di._image, 'gs://chromeos-image-archive/eve/R90')
67 self.assertEqual(di._image_type, device_imager.ImageType.REMOTE_DIRECTORY)
Amin Hassani92f6c4a2021-02-20 17:36:09 -080068
69 @mock.patch.object(xbuddy.XBuddy, 'Translate',
70 return_value=('eve/R90', 'path/to/file'))
Amin Hassani70c372a2021-03-31 20:18:51 -070071 @mock.patch.object(remote_access.ChromiumOSDevice, 'board',
72 return_value='foo', new_callable=mock.PropertyMock)
73 # pylint: disable=unused-argument
Amin Hassanib1993eb2021-04-28 12:00:11 -070074 def test_LocateImageXBuddyLocal(self, _, board_mock):
Amin Hassani92f6c4a2021-02-20 17:36:09 -080075 """Tests getting local xBuddy image path."""
Amin Hassani70c372a2021-03-31 20:18:51 -070076 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
77 di = device_imager.DeviceImager(device, 'xbuddy://local/eve/latest')
Amin Hassanib1993eb2021-04-28 12:00:11 -070078 di._LocateImage()
79 self.assertEqual(di._image, 'path/to/file')
80 self.assertEqual(di._image_type, device_imager.ImageType.FULL)
Amin Hassani92f6c4a2021-02-20 17:36:09 -080081
82 def test_SplitDevPath(self):
83 """Tests splitting a device path into prefix and partition number."""
84
85 di = device_imager.DeviceImager(None, None)
86 self.assertEqual(di._SplitDevPath('/dev/foop3'), ('/dev/foop', 3))
87
88 with self.assertRaises(device_imager.Error):
89 di._SplitDevPath('/foo')
90
91 with self.assertRaises(device_imager.Error):
92 di._SplitDevPath('/foo/p3p')
93
94 def test_GetKernelState(self):
95 """Tests getting the current active and inactive kernel states."""
96 di = device_imager.DeviceImager(None, None)
97 self.assertEqual(di._GetKernelState(3), (device_imager.DeviceImager.A,
98 device_imager.DeviceImager.B))
99 self.assertEqual(di._GetKernelState(5), (device_imager.DeviceImager.B,
100 device_imager.DeviceImager.A))
101
102 with self.assertRaises(device_imager.Error):
103 di._GetKernelState(1)
104
105 @mock.patch.object(remote_access.ChromiumOSDevice, 'root_dev',
106 return_value='/dev/foop3', new_callable=mock.PropertyMock)
107 def test_VerifyBootExpectations(self, _):
108 """Tests verifying the boot expectations after reboot."""
109
110 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
111 di = device_imager.DeviceImager(device, None)
112 di._inactive_state = device_imager.DeviceImager.A
113 di._VerifyBootExpectations()
114
115 @mock.patch.object(remote_access.ChromiumOSDevice, 'root_dev',
116 return_value='/dev/foop3', new_callable=mock.PropertyMock)
117 def test_VerifyBootExpectationsFails(self, _):
118 """Tests failure of boot expectations."""
119
120 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
121 di = device_imager.DeviceImager(device, None)
122 di._inactive_state = device_imager.DeviceImager.B
123 with self.assertRaises(device_imager.Error):
124 di._VerifyBootExpectations()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800125
126
127class TestReaderBase(cros_test_lib.MockTestCase):
128 """Test ReaderBase class"""
129
130 def testNamedPipe(self):
131 """Tests initializing the class with named pipe."""
132 with device_imager.ReaderBase(use_named_pipes=True) as r:
133 self.assertIsInstance(r.Target(), str)
134 self.assertEqual(r._Source(), r.Target())
135 self.assertExists(r.Target())
136
137 r._CloseSource() # Should not have any effect.
138 self.assertExists(r._Source())
139
140 # Closing target should delete the named pipe.
141 r.CloseTarget()
142 self.assertNotExists(r.Target())
143
144 def testFdPipe(self):
145 """Tests initializing the class with normal file descriptor pipes."""
146 with device_imager.ReaderBase() as r:
147 self.assertIsInstance(r.Target(), int)
148 self.assertIsInstance(r._Source(), int)
149 self.assertNotEqual(r._Source(), r.Target())
150 self.assertExists(GetFdPath(r.Target()))
151 self.assertExists(GetFdPath(r._Source()))
152
Amin Hassanifa11c692021-04-07 09:17:31 -0700153 # Per crbug.com/1196702 it seems like some other process gets the file
154 # descriptor right after we close it and by the time we check its
155 # existence, it is still there and this can flake. So it might be better
156 # to make sure this is checked properly through real paths and not
157 # symlinks.
158 path = GetFdPath(r._Source())
159 old_path = os.path.realpath(path)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800160 r._CloseSource()
Amin Hassanifa11c692021-04-07 09:17:31 -0700161 with self.assertRaises(OSError):
162 new_path = os.path.realpath(path)
163 self.assertNotEqual(old_path, new_path)
164 raise OSError('Fake the context manager.')
165
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800166 self.assertExists(GetFdPath(r.Target()))
167
Amin Hassanifa11c692021-04-07 09:17:31 -0700168 path = GetFdPath(r.Target())
169 old_path = os.path.realpath(path)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800170 r.CloseTarget()
Amin Hassanifa11c692021-04-07 09:17:31 -0700171 with self.assertRaises(OSError):
172 new_path = os.path.realpath(path)
173 self.assertNotEqual(old_path, new_path)
174 raise OSError('Fake the context manager.')
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800175
176 def testFdPipeCommunicate(self):
177 """Tests that file descriptors pipe can actually communicate."""
178 with device_imager.ReaderBase() as r:
179 with os.fdopen(r._Source(), 'w') as fp:
180 fp.write('helloworld')
181
182 with os.fdopen(r.Target(), 'r') as fp:
183 self.assertEqual(fp.read(), 'helloworld')
184
185
186class PartialFileReaderTest(cros_test_lib.RunCommandTestCase):
187 """Tests PartialFileReader class."""
188
189 def testRun(self):
190 """Tests the main run() function."""
191 with device_imager.PartialFileReader(
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900192 '/foo', 512 * 2, 512, ['/usr/bin/pigz']) as pfr:
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800193 pass
194
195 self.assertCommandCalled(
196 'dd status=none if=/foo ibs=512 skip=2 count=1 | /usr/bin/pigz',
197 stdout=pfr._Source(), shell=True)
198
199 # Make sure the source has been close.
200 self.assertNotExists(GetFdPath(pfr._Source()))
201
202
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800203class GsFileCopierTest(cros_test_lib.TestCase):
204 """Tests GsFileCopier class."""
205
206 @mock.patch.object(gs.GSContext, 'Copy')
207 def testRun(self, copy_mock):
208 """Tests the run() function."""
209 image = 'gs://path/to/image'
210 with device_imager.GsFileCopier(image) as gfc:
211 self.assertTrue(gfc._use_named_pipes)
212
213 copy_mock.assert_called_with(image, gfc._Source())
214
215
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800216class PartitionUpdaterBaseTest(cros_test_lib.TestCase):
217 """Tests PartitionUpdaterBase class"""
218
219 def testRunNotImplemented(self):
220 """Tests running the main Run() function is not implemented."""
221 # We just want to make sure the _Run() function is not implemented here.
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900222 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800223 with self.assertRaises(NotImplementedError):
224 pub.Run()
225
226 def testRevertNotImplemented(self):
227 """Tests running the Revert() function is not implemented."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900228 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800229 with self.assertRaises(NotImplementedError):
230 pub.Revert()
231
232 @mock.patch.object(device_imager.PartitionUpdaterBase, '_Run')
233 def testIsFinished(self, _):
234 """Tests IsFinished() function."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900235 pub = device_imager.PartitionUpdaterBase(None, None, None, None)
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800236 self.assertFalse(pub.IsFinished())
237 pub.Run()
238 self.assertTrue(pub.IsFinished())
239
240
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800241class RawPartitionUpdaterTest(cros_test_lib.MockTempDirTestCase):
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800242 """Tests RawPartitionUpdater class."""
243
244 def setUp(self):
245 """Sets up the class by creating proper mocks."""
246 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
247 self.rsh_mock.AddCmdResult(partial_mock.In('${PATH}'), stdout='')
248 self.path_env = 'PATH=%s:' % remote_access.DEV_BIN_PATHS
249
250 @mock.patch.object(device_imager.RawPartitionUpdater, '_GetPartitionName',
251 return_value=constants.PART_KERN_A)
252 @mock.patch.object(image_lib, 'GetImageDiskPartitionInfo',
253 return_value=image_lib_unittest.LOOP_PARTITION_INFO)
254 @mock.patch.object(device_imager.PartialFileReader, 'CloseTarget')
255 @mock.patch.object(device_imager.PartialFileReader, 'run')
256 def test_RunFullImage(self, run_mock, close_mock, _, name_mock):
257 """Test main Run() function for full image.
258
259 This function should parts of the source image and write it into the device
260 using proper compression programs.
261 """
262 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
263 self.rsh_mock.AddCmdResult([partial_mock.In('which'), 'gzip'],
264 returncode=0)
265 self.rsh_mock.AddCmdResult(
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700266 self.path_env +
267 ' gzip --decompress --stdout | ' +
268 'dd bs=1M oflag=direct of=/dev/mmcblk0p2')
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800269
270 device_imager.RawPartitionUpdater(
271 device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900272 '/dev/mmcblk0p2').Run()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800273 run_mock.assert_called()
274 close_mock.assert_called()
275 name_mock.assert_called()
Amin Hassanid684e982021-02-26 11:10:58 -0800276
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800277 def test_RunRemoteImage(self):
278 """Test main Run() function for remote images."""
279 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
280 self.rsh_mock.AddCmdResult([partial_mock.In('which'), 'gzip'],
281 returncode=0)
282 self.rsh_mock.AddCmdResult(
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700283 self.path_env +
284 ' gzip --decompress --stdout | ' +
285 'dd bs=1M oflag=direct of=/dev/mmcblk0p2')
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800286
287 path = os.path.join(self.tempdir,
288 constants.QUICK_PROVISION_PAYLOAD_KERNEL)
289 with open(path, 'w') as image:
290 image.write('helloworld')
291
292 device_imager.KernelUpdater(
293 device, self.tempdir, device_imager.ImageType.REMOTE_DIRECTORY,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900294 '/dev/mmcblk0p2').Run()
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800295
Amin Hassanid684e982021-02-26 11:10:58 -0800296
297class KernelUpdaterTest(cros_test_lib.MockTempDirTestCase):
298 """Tests KernelUpdater class."""
299
300 def test_GetPartitionName(self):
301 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900302 ku = device_imager.KernelUpdater(None, None, None, None)
Amin Hassanid684e982021-02-26 11:10:58 -0800303 self.assertEqual(constants.PART_KERN_B, ku._GetPartitionName())
Amin Hassani75c5f942021-02-20 23:56:53 -0800304
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800305 def test_GetRemotePartitionName(self):
306 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900307 ku = device_imager.KernelUpdater(None, None, None, None)
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800308 self.assertEqual(constants.QUICK_PROVISION_PAYLOAD_KERNEL,
309 ku._GetRemotePartitionName())
310
Amin Hassani75c5f942021-02-20 23:56:53 -0800311
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000312class MiniOSUpdaterTest(cros_test_lib.MockTempDirTestCase):
313 """Tests MiniOSUpdater class."""
314
315 def test_GetPartitionName(self):
316 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900317 u = device_imager.MiniOSUpdater(*([None] * 4))
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000318 self.assertEqual(constants.PART_MINIOS_A, u._GetPartitionName())
319
320 def test_GetRemotePartitionName(self):
321 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900322 u = device_imager.MiniOSUpdater(*([None] * 4))
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700323 self.assertEqual(constants.QUICK_PROVISION_PAYLOAD_MINIOS,
324 u._GetRemotePartitionName())
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000325
326 @mock.patch.object(device_imager.MiniOSUpdater, '_CopyPartitionFromImage')
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700327 @mock.patch.object(device_imager.MiniOSUpdater,
328 '_MiniOSPartitionsExistInImage', return_value=True)
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000329 @mock.patch.object(device_imager.MiniOSUpdater, '_RunPostInstall')
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700330 def test_Run(self, postinstall_mock, partitions_exist_mock, copy_mock):
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000331 """Test main Run() function."""
332 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
333 device_imager.MiniOSUpdater(
334 device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900335 '/dev/mmcblk0p10').Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000336
337 copy_mock.assert_called_with(constants.PART_MINIOS_A)
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700338 partitions_exist_mock.assert_called_with()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000339 postinstall_mock.assert_called_with()
340
341 @mock.patch.object(device_imager.MiniOSUpdater, '_CopyPartitionFromImage')
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700342 @mock.patch.object(device_imager.MiniOSUpdater,
343 '_MiniOSPartitionsExistInImage', return_value=False)
344 def test_RunMissingMiniOS(self, partitions_exist_mock, copy_mock):
345 """Test main Run() function with missing miniOS partitions on image."""
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000346 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
347 device_imager.MiniOSUpdater(
348 device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900349 '/dev/mmcblk0p10').Run()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000350
351 copy_mock.assert_not_called()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700352 partitions_exist_mock.assert_called_with()
353
354 @mock.patch.object(device_imager.MiniOSUpdater, '_RunPostInstall')
355 @mock.patch.object(device_imager.MiniOSUpdater, '_RedirectPartition')
356 @mock.patch.object(device_imager.MiniOSUpdater, '_MiniOSPartitionsExist')
357 @mock.patch.object(gs.GSContext, 'Exists', return_value=False)
358 def test_RunMissingMiniOSRemotePayload(self, gs_context_mock,
359 partitions_exist_mock, redirect_mock,
360 post_install_mock):
361 """Test main Run() function with missing miniOS remote payloads."""
362 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
363 device_imager.MiniOSUpdater(
364 device, 'foo-image', device_imager.ImageType.REMOTE_DIRECTORY,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900365 '/dev/mmcblk0p10').Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700366
367 post_install_mock.assert_not_called()
368 redirect_mock.assert_not_called()
369 partitions_exist_mock.assert_not_called()
370 gs_context_mock.assert_called()
371
372 @mock.patch.object(device_imager.MiniOSUpdater, '_RunPostInstall')
373 @mock.patch.object(device_imager.MiniOSUpdater, '_RedirectPartition')
374 @mock.patch.object(device_imager.MiniOSUpdater, '_MiniOSPartitionsExist',
375 return_value=False)
376 @mock.patch.object(gs.GSContext, 'Exists', return_value=True)
377 def test_RunMissingMiniOSPartitions(self, gs_context_mock,
378 partitions_exist_mock, redirect_mock,
379 post_install_mock):
380 """Test main Run() function with missing miniOS remote payloads."""
381 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
382 device_imager.MiniOSUpdater(
383 device, 'foo-image', device_imager.ImageType.REMOTE_DIRECTORY,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900384 '/dev/mmcblk0p10').Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700385
386 post_install_mock.assert_not_called()
387 redirect_mock.assert_not_called()
388 partitions_exist_mock.assert_called()
389 gs_context_mock.assert_called()
390
391 @mock.patch.object(device_imager.MiniOSUpdater, '_RunPostInstall')
392 @mock.patch.object(device_imager.MiniOSUpdater, '_RedirectPartition')
393 @mock.patch.object(device_imager.MiniOSUpdater, '_MiniOSPartitionsExist',
394 return_value=True)
395 @mock.patch.object(gs.GSContext, 'Exists', return_value=True)
396 def test_RunMiniOSRemotePayload(self, gs_context_mock, partitions_exist_mock,
397 redirect_mock, post_install_mock):
398 """Test main Run() function with missing miniOS remote payloads."""
399 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
400 device_imager.MiniOSUpdater(
401 device, 'foo-image', device_imager.ImageType.REMOTE_DIRECTORY,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900402 '/dev/mmcblk0p10').Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700403
404 post_install_mock.assert_called()
405 redirect_mock.assert_called()
406 partitions_exist_mock.assert_called()
407 gs_context_mock.assert_called()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000408
409 @mock.patch.object(device_imager.MiniOSUpdater, '_FlipMiniOSPriority')
410 def test_RunPostInstall(self, flip_mock):
411 """Test _RunPostInstall() function."""
412 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
413 device_imager.MiniOSUpdater(
414 device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900415 '/dev/mmcblk0p10')._RunPostInstall()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000416
417 flip_mock.assert_called_with()
418
419 @mock.patch.object(device_imager.MiniOSUpdater, '_FlipMiniOSPriority')
420 def test_Revert(self, flip_mock):
421 """Test Revert() function."""
422 u = device_imager.MiniOSUpdater(
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700423 None, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900424 '/dev/mmcblk0p10')
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000425
426 # Before PostInstall runs.
427 u.Revert()
428 flip_mock.assert_not_called()
429
430 u._ran_postinst = True
431 u.Revert()
432
433 flip_mock.assert_called_with()
434
435 @mock.patch.object(device_imager.MiniOSUpdater, '_GetMiniOSPriority',
436 return_value='A')
437 @mock.patch.object(device_imager.MiniOSUpdater, '_SetMiniOSPriority')
438 def test_FlipMiniOSPriority(self, set_mock, get_mock):
439 """Test _FlipMiniOSPriority() function."""
440 device_imager.MiniOSUpdater(
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700441 None, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900442 '/dev/mmcblk0p10')._FlipMiniOSPriority()
Jae Hoon Kimcc723e02021-08-16 21:03:21 +0000443
444 get_mock.assert_called_with()
445 set_mock.assert_called_with('B')
446
447
Amin Hassani75c5f942021-02-20 23:56:53 -0800448class RootfsUpdaterTest(cros_test_lib.MockTestCase):
449 """Tests RootfsUpdater class."""
450
451 def setUp(self):
452 """Sets up the class by creating proper mocks."""
453 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
454 self.rsh_mock.AddCmdResult(partial_mock.In('${PATH}'), stdout='')
455 self.path_env = 'PATH=%s:' % remote_access.DEV_BIN_PATHS
456
457 def test_GetPartitionName(self):
458 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900459 ru = device_imager.RootfsUpdater(None, None, None, None, None)
Amin Hassani75c5f942021-02-20 23:56:53 -0800460 self.assertEqual(constants.PART_ROOT_A, ru._GetPartitionName())
461
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800462 def test_GetRemotePartitionName(self):
463 """Tests the name of the partitions."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900464 ru = device_imager.RootfsUpdater(None, None, None, None, None)
Amin Hassani0fe49ae2021-02-21 23:41:58 -0800465 self.assertEqual(constants.QUICK_PROVISION_PAYLOAD_ROOTFS,
466 ru._GetRemotePartitionName())
467
Amin Hassani55970562021-02-22 20:49:13 -0800468 @mock.patch.object(device_imager.ProgressWatcher, 'run')
Amin Hassani75c5f942021-02-20 23:56:53 -0800469 @mock.patch.object(device_imager.RootfsUpdater, '_RunPostInst')
470 @mock.patch.object(device_imager.RootfsUpdater, '_CopyPartitionFromImage')
Amin Hassani55970562021-02-22 20:49:13 -0800471 def test_Run(self, copy_mock, postinst_mock, pw_mock):
Amin Hassani75c5f942021-02-20 23:56:53 -0800472 """Test main Run() function.
473
474 This function should parts of the source image and write it into the device
475 using proper compression programs.
476 """
477 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
478 device_imager.RootfsUpdater(
479 '/dev/mmcblk0p5', device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900480 '/dev/mmcblk0p3').Run()
Amin Hassani75c5f942021-02-20 23:56:53 -0800481
482 copy_mock.assert_called_with(constants.PART_ROOT_A)
483 postinst_mock.assert_called_with()
Amin Hassani55970562021-02-22 20:49:13 -0800484 pw_mock.assert_called()
Amin Hassani75c5f942021-02-20 23:56:53 -0800485
486 def test_RunPostInstOnTarget(self):
487 """Test _RunPostInst() function."""
488 target = '/dev/mmcblk0p3'
489 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
490 device._work_dir = '/tmp/work_dir'
491 temp_dir = os.path.join(device.work_dir, 'dir')
492 self.rsh_mock.AddCmdResult(
493 [self.path_env, 'mktemp', '-d', '-p', device.work_dir],
494 stdout=temp_dir)
495 self.rsh_mock.AddCmdResult(
496 [self.path_env, 'mount', '-o', 'ro', target, temp_dir])
497 self.rsh_mock.AddCmdResult(
498 [self.path_env, os.path.join(temp_dir, 'postinst'), target])
499 self.rsh_mock.AddCmdResult([self.path_env, 'umount', temp_dir])
500
501 device_imager.RootfsUpdater(
502 '/dev/mmcblk0p5', device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900503 target)._RunPostInst()
Amin Hassani75c5f942021-02-20 23:56:53 -0800504
505 def test_RunPostInstOnCurrentRoot(self):
506 """Test _RunPostInst() on current root (used for reverting an update)."""
507 root_dev = '/dev/mmcblk0p5'
508 self.rsh_mock.AddCmdResult([self.path_env, '/postinst', root_dev])
509
510 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
511 device_imager.RootfsUpdater(
512 root_dev, device, 'foo-image', device_imager.ImageType.FULL,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900513 '/dev/mmcblk0p3')._RunPostInst(
Amin Hassani75c5f942021-02-20 23:56:53 -0800514 on_target=False)
515
516 @mock.patch.object(device_imager.RootfsUpdater, '_RunPostInst')
517 def testRevert(self, postinst_mock):
518 """Tests Revert() function."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900519 ru = device_imager.RootfsUpdater(None, None, None, None, None)
Amin Hassani75c5f942021-02-20 23:56:53 -0800520
521 ru.Revert()
522 postinst_mock.assert_not_called()
523
524 ru._ran_postinst = True
525 ru.Revert()
526 postinst_mock.assert_called_with(on_target=False)
Amin Hassani74403082021-02-22 11:40:09 -0800527
528
529class StatefulPayloadGeneratorTest(cros_test_lib.TestCase):
530 """Tests stateful payload generator."""
531 @mock.patch.object(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
532 def testRun(self, paygen_mock):
533 """Tests run() function."""
534 image = '/foo/image'
535 with device_imager.StatefulPayloadGenerator(image) as spg:
536 pass
537
538 paygen_mock.assert_called_with(image, spg._Source())
539
540
541class StatefulUpdaterTest(cros_test_lib.TestCase):
542 """Tests StatefulUpdater."""
543 @mock.patch.object(paygen_stateful_payload_lib, 'GenerateStatefulPayload')
544 @mock.patch.object(stateful_updater.StatefulUpdater, 'Update')
545 def test_RunFullImage(self, update_mock, paygen_mock):
546 """Test main Run() function for full image."""
547 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
548 device_imager.StatefulUpdater(False, device, 'foo-image',
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900549 device_imager.ImageType.FULL, None).Run()
Amin Hassani74403082021-02-22 11:40:09 -0800550 update_mock.assert_called_with(mock.ANY,
551 is_payload_on_device=False,
552 update_type=None)
553 paygen_mock.assert_called()
554
555 @mock.patch.object(gs.GSContext, 'Copy')
556 @mock.patch.object(stateful_updater.StatefulUpdater, 'Update')
557 def test_RunRemoteImage(self, update_mock, copy_mock):
558 """Test main Run() function for remote images."""
559 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
560 device_imager.StatefulUpdater(False, device, 'gs://foo-image',
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700561 device_imager.ImageType.REMOTE_DIRECTORY,
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900562 None).Run()
Jae Hoon Kimb88b7962021-10-18 11:08:38 -0700563 copy_mock.assert_called_with('gs://foo-image/stateful.tgz', mock.ANY)
Amin Hassani74403082021-02-22 11:40:09 -0800564 update_mock.assert_called_with(mock.ANY, is_payload_on_device=False,
565 update_type=None)
566
567 @mock.patch.object(stateful_updater.StatefulUpdater, 'Reset')
568 def testRevert(self, reset_mock):
569 """Tests Revert() function."""
Daichi Hironoc1a8fd32022-01-07 22:17:51 +0900570 su = device_imager.StatefulUpdater(False, None, None, None, None)
Amin Hassani74403082021-02-22 11:40:09 -0800571
572 su.Revert()
573 reset_mock.assert_called()
Amin Hassani55970562021-02-22 20:49:13 -0800574
575
576class ProgressWatcherTest(cros_test_lib.MockTestCase):
577 """Tests ProgressWatcher class"""
578
579 def setUp(self):
580 """Sets up the class by creating proper mocks."""
581 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
582 self.rsh_mock.AddCmdResult(partial_mock.In('${PATH}'), stdout='')
583 self.path_env = 'PATH=%s:' % remote_access.DEV_BIN_PATHS
584
585 @mock.patch.object(time, 'sleep')
586 @mock.patch.object(device_imager.ProgressWatcher, '_ShouldExit',
587 side_effect=[False, False, True])
588 # pylint: disable=unused-argument
589 def testRun(self, exit_mock, _):
590 """Tests the run() function."""
591 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
592 target_root = '/foo/root'
593 self.rsh_mock.AddCmdResult(
594 [self.path_env, 'blockdev', '--getsize64', target_root], stdout='100')
595 self.rsh_mock.AddCmdResult(
Benjamin Gordonfb2f1a72022-06-23 09:19:41 -0600596 [self.path_env, 'lsof', '-t', target_root],
597 stdout='999')
Amin Hassani55970562021-02-22 20:49:13 -0800598 self.rsh_mock.AddCmdResult([self.path_env, 'cat', '/proc/999/fdinfo/1'],
599 stdout='pos: 10\nflags: foo')
600 pw = device_imager.ProgressWatcher(device, target_root)
601 pw.run()