blob: ca03ce813ee681dacae0d08cf6eb0719ba0137ff [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 sys
9import tempfile
10
11import mock
12
13from chromite.cli import device_imager
Amin Hassanid4b3ff82021-02-20 23:05:14 -080014from chromite.lib import constants
15from chromite.lib import cros_build_lib
Amin Hassani92f6c4a2021-02-20 17:36:09 -080016from chromite.lib import cros_test_lib
Amin Hassanid4b3ff82021-02-20 23:05:14 -080017from chromite.lib import image_lib
18from chromite.lib import image_lib_unittest
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
22from chromite.lib.xbuddy import xbuddy
23
24
25assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
26
27
28# pylint: disable=protected-access
29
Amin Hassanid4b3ff82021-02-20 23:05:14 -080030
31def GetFdPath(fd):
32 """Returns the fd path for the current process."""
33 return f'/proc/self/fd/{fd}'
34
35
Amin Hassani92f6c4a2021-02-20 17:36:09 -080036class DeviceImagerTest(cros_test_lib.MockTestCase):
37 """Tests DeviceImager class methods."""
38
39 def setUp(self):
40 """Sets up the class by creating proper mocks."""
41 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
42 self.rsh_mock.AddCmdResult(partial_mock.In('${PATH}'), stdout='')
43 self.path_env = 'PATH=%s:' % remote_access.DEV_BIN_PATHS
44
45 def test_GetImageLocalFile(self):
46 """Tests getting the path to local image."""
47 with tempfile.NamedTemporaryFile() as fp:
48 di = device_imager.DeviceImager(None, fp.name)
49 self.assertEqual(di._GetImage(), (fp.name, device_imager.ImageType.FULL))
50
51 def test_GetImageDir(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._GetImage()
56
57 @mock.patch.object(xbuddy.XBuddy, 'Translate', return_value=('eve/R90', None))
58 def test_GetImageXBuddyRemote(self, _):
59 """Tests getting remote xBuddy image path."""
60 di = device_imager.DeviceImager(None, 'xbuddy://remote/eve/latest')
61 self.assertEqual(di._GetImage(),
62 ('gs://chromeos-image-archive/eve/R90',
63 device_imager.ImageType.REMOTE_DIRECTORY))
64
65 @mock.patch.object(xbuddy.XBuddy, 'Translate',
66 return_value=('eve/R90', 'path/to/file'))
67 def test_GetImageXBuddyLocal(self, _):
68 """Tests getting local xBuddy image path."""
69 di = device_imager.DeviceImager(None, 'xbuddy://local/eve/latest')
70 self.assertEqual(di._GetImage(),
71 ('path/to/file', device_imager.ImageType.FULL))
72
73 def test_SplitDevPath(self):
74 """Tests splitting a device path into prefix and partition number."""
75
76 di = device_imager.DeviceImager(None, None)
77 self.assertEqual(di._SplitDevPath('/dev/foop3'), ('/dev/foop', 3))
78
79 with self.assertRaises(device_imager.Error):
80 di._SplitDevPath('/foo')
81
82 with self.assertRaises(device_imager.Error):
83 di._SplitDevPath('/foo/p3p')
84
85 def test_GetKernelState(self):
86 """Tests getting the current active and inactive kernel states."""
87 di = device_imager.DeviceImager(None, None)
88 self.assertEqual(di._GetKernelState(3), (device_imager.DeviceImager.A,
89 device_imager.DeviceImager.B))
90 self.assertEqual(di._GetKernelState(5), (device_imager.DeviceImager.B,
91 device_imager.DeviceImager.A))
92
93 with self.assertRaises(device_imager.Error):
94 di._GetKernelState(1)
95
96 @mock.patch.object(remote_access.ChromiumOSDevice, 'root_dev',
97 return_value='/dev/foop3', new_callable=mock.PropertyMock)
98 def test_VerifyBootExpectations(self, _):
99 """Tests verifying the boot expectations after reboot."""
100
101 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
102 di = device_imager.DeviceImager(device, None)
103 di._inactive_state = device_imager.DeviceImager.A
104 di._VerifyBootExpectations()
105
106 @mock.patch.object(remote_access.ChromiumOSDevice, 'root_dev',
107 return_value='/dev/foop3', new_callable=mock.PropertyMock)
108 def test_VerifyBootExpectationsFails(self, _):
109 """Tests failure of boot expectations."""
110
111 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
112 di = device_imager.DeviceImager(device, None)
113 di._inactive_state = device_imager.DeviceImager.B
114 with self.assertRaises(device_imager.Error):
115 di._VerifyBootExpectations()
Amin Hassanid4b3ff82021-02-20 23:05:14 -0800116
117
118class TestReaderBase(cros_test_lib.MockTestCase):
119 """Test ReaderBase class"""
120
121 def testNamedPipe(self):
122 """Tests initializing the class with named pipe."""
123 with device_imager.ReaderBase(use_named_pipes=True) as r:
124 self.assertIsInstance(r.Target(), str)
125 self.assertEqual(r._Source(), r.Target())
126 self.assertExists(r.Target())
127
128 r._CloseSource() # Should not have any effect.
129 self.assertExists(r._Source())
130
131 # Closing target should delete the named pipe.
132 r.CloseTarget()
133 self.assertNotExists(r.Target())
134
135 def testFdPipe(self):
136 """Tests initializing the class with normal file descriptor pipes."""
137 with device_imager.ReaderBase() as r:
138 self.assertIsInstance(r.Target(), int)
139 self.assertIsInstance(r._Source(), int)
140 self.assertNotEqual(r._Source(), r.Target())
141 self.assertExists(GetFdPath(r.Target()))
142 self.assertExists(GetFdPath(r._Source()))
143
144 r._CloseSource()
145 self.assertNotExists(GetFdPath(r._Source()))
146 self.assertExists(GetFdPath(r.Target()))
147
148 r.CloseTarget()
149 self.assertNotExists(GetFdPath(r.Target()))
150
151 def testFdPipeCommunicate(self):
152 """Tests that file descriptors pipe can actually communicate."""
153 with device_imager.ReaderBase() as r:
154 with os.fdopen(r._Source(), 'w') as fp:
155 fp.write('helloworld')
156
157 with os.fdopen(r.Target(), 'r') as fp:
158 self.assertEqual(fp.read(), 'helloworld')
159
160
161class PartialFileReaderTest(cros_test_lib.RunCommandTestCase):
162 """Tests PartialFileReader class."""
163
164 def testRun(self):
165 """Tests the main run() function."""
166 with device_imager.PartialFileReader(
167 '/foo', 512 * 2, 512, cros_build_lib.COMP_GZIP) as pfr:
168 pass
169
170 self.assertCommandCalled(
171 'dd status=none if=/foo ibs=512 skip=2 count=1 | /usr/bin/pigz',
172 stdout=pfr._Source(), shell=True)
173
174 # Make sure the source has been close.
175 self.assertNotExists(GetFdPath(pfr._Source()))
176
177
178class PartitionUpdaterBaseTest(cros_test_lib.TestCase):
179 """Tests PartitionUpdaterBase class"""
180
181 def testRunNotImplemented(self):
182 """Tests running the main Run() function is not implemented."""
183 # We just want to make sure the _Run() function is not implemented here.
184 pub = device_imager.PartitionUpdaterBase(None, None, None, None, None)
185 with self.assertRaises(NotImplementedError):
186 pub.Run()
187
188 def testRevertNotImplemented(self):
189 """Tests running the Revert() function is not implemented."""
190 pub = device_imager.PartitionUpdaterBase(None, None, None, None, None)
191 with self.assertRaises(NotImplementedError):
192 pub.Revert()
193
194 @mock.patch.object(device_imager.PartitionUpdaterBase, '_Run')
195 def testIsFinished(self, _):
196 """Tests IsFinished() function."""
197 pub = device_imager.PartitionUpdaterBase(None, None, None, None, None)
198 self.assertFalse(pub.IsFinished())
199 pub.Run()
200 self.assertTrue(pub.IsFinished())
201
202
203class RawPartitionUpdaterTest(cros_test_lib.MockTestCase):
204 """Tests RawPartitionUpdater class."""
205
206 def setUp(self):
207 """Sets up the class by creating proper mocks."""
208 self.rsh_mock = self.StartPatcher(remote_access_unittest.RemoteShMock())
209 self.rsh_mock.AddCmdResult(partial_mock.In('${PATH}'), stdout='')
210 self.path_env = 'PATH=%s:' % remote_access.DEV_BIN_PATHS
211
212 @mock.patch.object(device_imager.RawPartitionUpdater, '_GetPartitionName',
213 return_value=constants.PART_KERN_A)
214 @mock.patch.object(image_lib, 'GetImageDiskPartitionInfo',
215 return_value=image_lib_unittest.LOOP_PARTITION_INFO)
216 @mock.patch.object(device_imager.PartialFileReader, 'CloseTarget')
217 @mock.patch.object(device_imager.PartialFileReader, 'run')
218 def test_RunFullImage(self, run_mock, close_mock, _, name_mock):
219 """Test main Run() function for full image.
220
221 This function should parts of the source image and write it into the device
222 using proper compression programs.
223 """
224 with remote_access.ChromiumOSDeviceHandler(remote_access.TEST_IP) as device:
225 self.rsh_mock.AddCmdResult([partial_mock.In('which'), 'gzip'],
226 returncode=0)
227 self.rsh_mock.AddCmdResult(
228 self.path_env +
229 ' gzip --decompress --stdout | dd bs=1M oflag=direct of=/dev/mmcblk0p2')
230
231 device_imager.RawPartitionUpdater(
232 device, 'foo-image', device_imager.ImageType.FULL,
233 '/dev/mmcblk0p2', cros_build_lib.COMP_GZIP).Run()
234 run_mock.assert_called()
235 close_mock.assert_called()
236 name_mock.assert_called()
Amin Hassanid684e982021-02-26 11:10:58 -0800237
238
239class KernelUpdaterTest(cros_test_lib.MockTempDirTestCase):
240 """Tests KernelUpdater class."""
241
242 def test_GetPartitionName(self):
243 """Tests the name of the partitions."""
244 ku = device_imager.KernelUpdater(None, None, None, None, None)
245 self.assertEqual(constants.PART_KERN_B, ku._GetPartitionName())