blob: 93663707f75cd76292ffb2c5995066a4b14cdfa4 [file] [log] [blame]
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06001# Copyright 2017 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"""Tests for cros_sdk."""
6
Chris McDonald59650c32021-07-20 15:29:28 -06007import logging
Benjamin Gordon2d7bf582017-07-12 10:11:26 -06008import os
Mike Frysinger5f5c70b2022-07-19 12:55:21 -04009import re
Mike Frysinger66d32cd2019-12-17 14:55:29 -050010import subprocess
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040011import sys
Mike Frysingeredc04912019-11-16 01:30:43 -050012import unittest
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060013
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040014from chromite.lib import constants
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060015from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060016from chromite.lib import cros_sdk_lib
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060017from chromite.lib import cros_test_lib
18from chromite.lib import osutils
Mike Frysingerdaf57b82019-11-23 17:26:51 -050019from chromite.lib import retry_util
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060020from chromite.lib import sudo
Mike Frysingerdaf57b82019-11-23 17:26:51 -050021from chromite.scripts import cros_sdk
Benjamin Gordon2d7bf582017-07-12 10:11:26 -060022
23
Mike Frysingeredc04912019-11-16 01:30:43 -050024# This long decorator triggers a false positive in the docstring test.
25# https://github.com/PyCQA/pylint/issues/3077
26# pylint: disable=bad-docstring-quotes
27@unittest.skipIf(cros_build_lib.IsInsideChroot(),
28 'Tests only make sense outside the chroot')
Benjamin Gordon03c56e42017-09-11 15:48:39 -040029class CrosSdkPrerequisitesTest(cros_test_lib.TempDirTestCase):
30 """Tests for required packages on the host machine.
31
32 These are not real unit tests. If these tests fail, it means your machine is
33 missing necessary commands to support image-backed chroots. Run cros_sdk in
34 a terminal to get info about what you need to install.
35 """
36
37 def testLvmCommandsPresent(self):
38 """Check for commands from the lvm2 package."""
39 with sudo.SudoKeepAlive():
40 cmd = ['lvs', '--version']
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -050041 result = cros_build_lib.run(cmd, check=False)
Benjamin Gordon03c56e42017-09-11 15:48:39 -040042 self.assertEqual(result.returncode, 0)
43
44 def testThinProvisioningToolsPresent(self):
45 """Check for commands from the thin-provisioning-tools package."""
46 with sudo.SudoKeepAlive():
47 cmd = ['thin_check', '-V']
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -050048 result = cros_build_lib.run(cmd, check=False)
Benjamin Gordon03c56e42017-09-11 15:48:39 -040049 self.assertEqual(result.returncode, 0)
50
51 def testLosetupCommandPresent(self):
52 """Check for commands from the mount package."""
53 with sudo.SudoKeepAlive():
54 cmd = ['losetup', '--help']
Mike Frysingerf5a3b2d2019-12-12 14:36:17 -050055 result = cros_build_lib.run(cmd, check=False)
Benjamin Gordon03c56e42017-09-11 15:48:39 -040056 self.assertEqual(result.returncode, 0)
57
58
Mike Frysingerdaf57b82019-11-23 17:26:51 -050059class CrosSdkUtilsTest(cros_test_lib.MockTempDirTestCase):
60 """Tests for misc util funcs."""
61
62 def testGetArchStageTarballs(self):
63 """Basic test of GetArchStageTarballs."""
64 self.assertCountEqual([
65 'https://storage.googleapis.com/chromiumos-sdk/cros-sdk-123.tar.xz',
Mike Frysingerdaf57b82019-11-23 17:26:51 -050066 ], cros_sdk.GetArchStageTarballs('123'))
67
68 def testFetchRemoteTarballsEmpty(self):
69 """Test FetchRemoteTarballs with no results."""
70 m = self.PatchObject(retry_util, 'RunCurl')
71 with self.assertRaises(ValueError):
Mike Frysingerf744d032022-05-07 20:38:39 -040072 cros_sdk.FetchRemoteTarballs(self.tempdir, [])
Mike Frysinger112b67c2022-08-08 00:52:10 -040073 m.return_value = cros_build_lib.CompletedProcess(stdout=b'Foo: bar\n')
Mike Frysingerdaf57b82019-11-23 17:26:51 -050074 with self.assertRaises(ValueError):
Mike Frysingerf744d032022-05-07 20:38:39 -040075 cros_sdk.FetchRemoteTarballs(self.tempdir, ['gs://x.tar'])
Mike Frysingerdaf57b82019-11-23 17:26:51 -050076
77 def testFetchRemoteTarballsSuccess(self):
78 """Test FetchRemoteTarballs with a successful download."""
Mike Frysinger112b67c2022-08-08 00:52:10 -040079 curl = cros_build_lib.CompletedProcess(stdout=(
Mike Frysingerdaf57b82019-11-23 17:26:51 -050080 b'HTTP/1.0 200\n'
81 b'Foo: bar\n'
82 b'Content-Length: 100\n'
83 ))
84 self.PatchObject(retry_util, 'RunCurl', return_value=curl)
85 self.assertEqual(
86 os.path.join(self.tempdir, 'tar'),
Mike Frysingerf744d032022-05-07 20:38:39 -040087 cros_sdk.FetchRemoteTarballs(self.tempdir, ['gs://x/tar']))
Mike Frysingerdaf57b82019-11-23 17:26:51 -050088
89
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040090class CrosSdkParserCommandLineTest(cros_test_lib.MockTestCase):
91 """Tests involving the CLI."""
92
93 # pylint: disable=protected-access
94
95 # A typical sys.argv[0] that cros_sdk sees.
96 ARGV0 = '/home/chronos/chromiumos/chromite/bin/cros_sdk'
97
98 def setUp(self):
99 self.parser, _ = cros_sdk._CreateParser('1', '2')
100
101 def testSudoCommand(self):
102 """Verify basic sudo command building works."""
103 # Stabilize the env for testing.
104 for v in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
105 os.environ[v] = 'value'
106 os.environ['PATH'] = 'path'
107
108 cmd = cros_sdk._SudoCommand()
109 assert cmd[0] == 'sudo'
110 assert 'CHROMEOS_SUDO_PATH=path' in cmd
111 rlimits = [x for x in cmd if x.startswith('CHROMEOS_SUDO_RLIMITS=')]
112 assert len(rlimits) == 1
113
114 # Spot check some pass thru vars.
115 assert 'GIT_AUTHOR_EMAIL=value' in cmd
116 assert 'https_proxy=value' in cmd
117
118 # Make sure we only pass vars after `sudo`.
119 for i in range(1, len(cmd)):
120 assert '=' in cmd[i]
121 v = cmd[i].split('=', 1)[0]
122 assert re.match(r'^[A-Za-z0-9_]+$', v) is not None
123
124 def testReexecCommand(self):
125 """Verify reexec command line building."""
126 # Stub sudo logic since we tested it above already.
127 self.PatchObject(cros_sdk, '_SudoCommand', return_value=['sudo'])
128 opts = self.parser.parse_args([])
129 new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
130 assert new_cmd == ['sudo', '--', sys.executable, self.ARGV0]
131
132 def testReexecCommandStrace(self):
133 """Verify reexec command line building w/strace."""
134 # Stub sudo logic since we tested it above already.
135 self.PatchObject(cros_sdk, '_SudoCommand', return_value=['sudo'])
136
137 # Strace args passed, but not enabled.
138 opts = self.parser.parse_args(['--strace-arguments=-s4096 -v'])
139 new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
140 assert new_cmd == ['sudo', '--', sys.executable, self.ARGV0]
141
142 # Strace enabled.
143 opts = self.parser.parse_args(['--strace'])
144 new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
145 assert new_cmd == ['sudo', '--', 'strace', '--', sys.executable, self.ARGV0]
146
147 # Strace enabled w/args.
148 opts = self.parser.parse_args(['--strace', '--strace-arguments=-s4096 -v'])
149 new_cmd = cros_sdk._BuildReExecCommand([self.ARGV0], opts)
150 assert new_cmd == [
151 'sudo', '--', 'strace', '-s4096', '-v', '--', sys.executable,
152 self.ARGV0,
153 ]
154
155
Mike Frysingeredc04912019-11-16 01:30:43 -0500156@unittest.skipIf(cros_build_lib.IsInsideChroot(),
157 'Tests only make sense outside the chroot')
158@unittest.skip(
159 'These are triggering hangs inside lvm when run through run_tests. '
160 'https://crbug.com/764335')
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600161class CrosSdkSnapshotTest(cros_test_lib.TempDirTestCase):
162 """Tests for the snapshot functionality in cros_sdk."""
163
164 def setUp(self):
165 with sudo.SudoKeepAlive():
166 # Create just enough of a chroot to fool cros_sdk into accepting it.
167 self.chroot = os.path.join(self.tempdir, 'chroot')
Benjamin Gordon74645232018-05-04 17:40:42 -0600168 cros_sdk_lib.MountChroot(self.chroot, create=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600169 logging.debug('Chroot mounted on %s', self.chroot)
170
171 chroot_etc = os.path.join(self.chroot, 'etc')
172 osutils.SafeMakedirsNonRoot(chroot_etc)
173
174 self.chroot_version_file = os.path.join(chroot_etc, 'cros_chroot_version')
175 osutils.Touch(self.chroot_version_file, makedirs=True)
176
177 def tearDown(self):
178 with sudo.SudoKeepAlive():
Don Garrett36650112018-06-28 15:54:34 -0700179 cros_sdk_lib.CleanupChrootMount(self.chroot, delete=True)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600180
181 def _crosSdk(self, args):
182 cmd = ['cros_sdk', '--chroot', self.chroot]
183 cmd.extend(args)
184
185 try:
Mike Frysinger45602c72019-09-22 02:15:11 -0400186 result = cros_build_lib.run(
Mike Frysingerc9885932020-02-26 01:02:49 -0500187 cmd, print_cmd=False, stdout=True, check=False,
Mike Frysinger66d32cd2019-12-17 14:55:29 -0500188 stderr=subprocess.STDOUT)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600189 except cros_build_lib.RunCommandError as e:
190 raise SystemExit('Running %r failed!: %s' % (cmd, e))
191
Mike Frysinger876a8e52022-06-23 18:07:30 -0400192 return result.returncode, result.stdout
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600193
194 def testSnapshotsRequireImage(self):
195 code, output = self._crosSdk(['--snapshot-list', '--nouse-image'])
196 self.assertNotEqual(code, 0)
197 self.assertIn('Snapshot operations are not compatible with', output)
198
199 code, output = self._crosSdk(['--snapshot-delete', 'test', '--nouse-image'])
200 self.assertNotEqual(code, 0)
201 self.assertIn('Snapshot operations are not compatible with', output)
202
203 code, output = self._crosSdk(['--snapshot-create', 'test', '--nouse-image'])
204 self.assertNotEqual(code, 0)
205 self.assertIn('Snapshot operations are not compatible with', output)
206
207 code, output = self._crosSdk(['--snapshot-restore', 'test',
208 '--nouse-image'])
209 self.assertNotEqual(code, 0)
210 self.assertIn('Snapshot operations are not compatible with', output)
211
212 def testSnapshotWithDeleteChroot(self):
213 code, output = self._crosSdk(['--delete', '--snapshot-list'])
214 self.assertNotEqual(code, 0)
215 self.assertIn('Trying to enter or snapshot the chroot', output)
216
217 code, output = self._crosSdk(['--delete', '--snapshot-delete', 'test'])
218 self.assertNotEqual(code, 0)
219 self.assertIn('Trying to enter or snapshot the chroot', output)
220
221 code, output = self._crosSdk(['--delete', '--snapshot-create', 'test'])
222 self.assertNotEqual(code, 0)
223 self.assertIn('Trying to enter or snapshot the chroot', output)
224
225 code, output = self._crosSdk(['--delete', '--snapshot-restore', 'test'])
226 self.assertNotEqual(code, 0)
227 self.assertIn('Trying to enter or snapshot the chroot', output)
228
229 def testEmptySnapshotList(self):
230 code, output = self._crosSdk(['--snapshot-list'])
231 self.assertEqual(code, 0)
232 self.assertEqual(output, '')
233
234 def testOneSnapshot(self):
235 code, _ = self._crosSdk(['--snapshot-create', 'test'])
236 self.assertEqual(code, 0)
237
238 code, output = self._crosSdk(['--snapshot-list'])
239 self.assertEqual(code, 0)
240 self.assertEqual(output.strip(), 'test')
241
242 def testMultipleSnapshots(self):
243 code, _ = self._crosSdk(['--snapshot-create', 'test'])
244 self.assertEqual(code, 0)
245 code, _ = self._crosSdk(['--snapshot-create', 'test2'])
246 self.assertEqual(code, 0)
247
248 code, output = self._crosSdk(['--snapshot-list'])
249 self.assertEqual(code, 0)
250 self.assertSetEqual(set(output.strip().split('\n')), {'test', 'test2'})
251
252 def testCantCreateSameSnapshotTwice(self):
253 code, _ = self._crosSdk(['--snapshot-create', 'test'])
254 self.assertEqual(code, 0)
255 code, _ = self._crosSdk(['--snapshot-create', 'test'])
256 self.assertNotEqual(code, 0)
257
258 code, output = self._crosSdk(['--snapshot-list'])
259 self.assertEqual(code, 0)
260 self.assertEqual(output.strip(), 'test')
261
262 def testCreateSnapshotMountsAsNeeded(self):
263 with sudo.SudoKeepAlive():
Benjamin Gordon74645232018-05-04 17:40:42 -0600264 cros_sdk_lib.CleanupChrootMount(self.chroot)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600265
266 code, _ = self._crosSdk(['--snapshot-create', 'test'])
267 self.assertEqual(code, 0)
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500268 self.assertExists(self.chroot_version_file)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600269
270 code, output = self._crosSdk(['--snapshot-list'])
271 self.assertEqual(code, 0)
272 self.assertEqual(output.strip(), 'test')
273
274 def testDeleteGoodSnapshot(self):
275 code, _ = self._crosSdk(['--snapshot-create', 'test'])
276 self.assertEqual(code, 0)
277 code, _ = self._crosSdk(['--snapshot-create', 'test2'])
278 self.assertEqual(code, 0)
279
280 code, _ = self._crosSdk(['--snapshot-delete', 'test'])
281 self.assertEqual(code, 0)
282
283 code, output = self._crosSdk(['--snapshot-list'])
284 self.assertEqual(code, 0)
285 self.assertEqual(output.strip(), 'test2')
286
287 def testDeleteMissingSnapshot(self):
288 code, _ = self._crosSdk(['--snapshot-create', 'test'])
289 self.assertEqual(code, 0)
290 code, _ = self._crosSdk(['--snapshot-create', 'test2'])
291 self.assertEqual(code, 0)
292
293 code, _ = self._crosSdk(['--snapshot-delete', 'test3'])
294 self.assertEqual(code, 0)
295
296 code, output = self._crosSdk(['--snapshot-list'])
297 self.assertEqual(code, 0)
298 self.assertSetEqual(set(output.strip().split('\n')), {'test', 'test2'})
299
300 def testDeleteAndCreateSnapshot(self):
301 code, _ = self._crosSdk(['--snapshot-create', 'test'])
302 self.assertEqual(code, 0)
303 code, _ = self._crosSdk(['--snapshot-create', 'test2'])
304 self.assertEqual(code, 0)
305
306 code, _ = self._crosSdk(['--snapshot-create', 'test',
307 '--snapshot-delete', 'test'])
308 self.assertEqual(code, 0)
309
310 code, output = self._crosSdk(['--snapshot-list'])
311 self.assertEqual(code, 0)
312 self.assertSetEqual(set(output.strip().split('\n')), {'test', 'test2'})
313
314 def testRestoreSnapshot(self):
315 with sudo.SudoKeepAlive():
316 test_file = os.path.join(self.chroot, 'etc', 'test_file')
317 osutils.Touch(test_file)
318
319 code, _ = self._crosSdk(['--snapshot-create', 'test'])
320 self.assertEqual(code, 0)
321
322 osutils.SafeUnlink(test_file)
323
324 code, _ = self._crosSdk(['--snapshot-restore', 'test'])
325 self.assertEqual(code, 0)
Benjamin Gordon74645232018-05-04 17:40:42 -0600326 self.assertTrue(cros_sdk_lib.MountChroot(self.chroot, create=False))
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500327 self.assertExists(test_file)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600328
329 code, output = self._crosSdk(['--snapshot-list'])
330 self.assertEqual(code, 0)
331 self.assertEqual(output, '')
332
333 def testRestoreAndCreateSnapshot(self):
334 with sudo.SudoKeepAlive():
335 test_file = os.path.join(self.chroot, 'etc', 'test_file')
336 osutils.Touch(test_file)
337
338 code, _ = self._crosSdk(['--snapshot-create', 'test'])
339 self.assertEqual(code, 0)
340
341 osutils.SafeUnlink(test_file)
342
343 code, _ = self._crosSdk(['--snapshot-restore', 'test',
344 '--snapshot-create', 'test'])
345 self.assertEqual(code, 0)
Benjamin Gordon74645232018-05-04 17:40:42 -0600346 self.assertTrue(cros_sdk_lib.MountChroot(self.chroot, create=False))
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500347 self.assertExists(test_file)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600348
349 code, output = self._crosSdk(['--snapshot-list'])
350 self.assertEqual(code, 0)
351 self.assertEqual(output.strip(), 'test')
352
353 def testDeleteCantRestoreSameSnapshot(self):
354 code, _ = self._crosSdk(['--snapshot-create', 'test'])
355 self.assertEqual(code, 0)
356
357 code, _ = self._crosSdk(['--snapshot-delete', 'test',
358 '--snapshot-restore', 'test'])
359 self.assertNotEqual(code, 0)
360
361 code, output = self._crosSdk(['--snapshot-list'])
362 self.assertEqual(code, 0)
363 self.assertEqual(output.strip(), 'test')
364
365 def testCantRestoreInvalidSnapshot(self):
366 with sudo.SudoKeepAlive():
367 test_file = os.path.join(self.chroot, 'etc', 'test_file')
368 osutils.Touch(test_file)
369
370 code, _ = self._crosSdk(['--snapshot-restore', 'test'])
371 self.assertNotEqual(code, 0)
372 # Failed restore leaves the existing snapshot in place.
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500373 self.assertExists(test_file)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600374
375 def testRestoreSnapshotMountsAsNeeded(self):
376 with sudo.SudoKeepAlive():
377 test_file = os.path.join(self.chroot, 'etc', 'test_file')
378 osutils.Touch(test_file)
379
380 code, _ = self._crosSdk(['--snapshot-create', 'test'])
381 self.assertEqual(code, 0)
382
383 osutils.SafeUnlink(test_file)
384
Benjamin Gordon74645232018-05-04 17:40:42 -0600385 cros_sdk_lib.CleanupChrootMount(self.chroot)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600386
387 code, _ = self._crosSdk(['--snapshot-restore', 'test'])
388 self.assertEqual(code, 0)
Mike Frysingerf2fa7d62017-12-14 18:33:59 -0500389 self.assertExists(test_file)
Benjamin Gordon2d7bf582017-07-12 10:11:26 -0600390
391 code, output = self._crosSdk(['--snapshot-list'])
392 self.assertEqual(code, 0)
393 self.assertEqual(output, '')