blob: 0f8fe647be1c92e8775744f6a1b6cb5e70a70599 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Mike Frysingerd13faeb2013-09-05 16:00:46 -04002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Unittests for pushimage.py"""
7
Mike Frysinger6791b1d2014-03-04 20:52:22 -05008from __future__ import print_function
9
Mike Frysingerd7c93092019-10-14 00:12:50 -040010import collections
Mike Frysingerd13faeb2013-09-05 16:00:46 -040011import os
Mike Frysingerd13faeb2013-09-05 16:00:46 -040012
Mike Frysinger6db648e2018-07-24 19:57:58 -040013import mock
14
Mike Frysinger6791b1d2014-03-04 20:52:22 -050015from chromite.lib import cros_build_lib
Mike Frysingerd13faeb2013-09-05 16:00:46 -040016from chromite.lib import cros_test_lib
Don Garrett9fd20a82014-09-04 11:37:22 -070017from chromite.lib import gs
Mike Frysingerd13faeb2013-09-05 16:00:46 -040018from chromite.lib import gs_unittest
19from chromite.lib import osutils
Mike Frysinger4495b032014-03-05 17:24:03 -050020from chromite.lib import partial_mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040021from chromite.lib import signing
22from chromite.scripts import pushimage
23
24
Greg Edelstona4c9b3b2020-01-07 17:51:13 -070025# Use our local copy of insns for testing as the main one is not available in
26# the public manifest. Even though _REL is a relative path, this works because
27# os.join leaves absolute paths on the right hand side alone.
28signing.INPUT_INSN_DIR_REL = signing.TEST_INPUT_INSN_DIR
29
30
Mike Frysingerd13faeb2013-09-05 16:00:46 -040031class InputInsnsTest(cros_test_lib.MockTestCase):
32 """Tests for InputInsns"""
33
Don Garrettac529772015-04-29 15:09:26 -070034 def setUp(self):
35 self.StartPatcher(gs_unittest.GSContextMock())
36
Mike Frysingerd13faeb2013-09-05 16:00:46 -040037 def testBasic(self):
38 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070039 insns = pushimage.InputInsns('test.board')
40 insns.GetInsnFile('recovery')
41 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
42 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040043
44 def testGetInsnFile(self):
45 """Verify various inputs result in right insns path"""
46 testdata = (
47 ('UPPER_CAPS', 'UPPER_CAPS'),
48 ('recovery', 'test.board'),
49 ('firmware', 'test.board.firmware'),
50 ('factory', 'test.board.factory'),
51 )
52 insns = pushimage.InputInsns('test.board')
53 for image_type, filename in testdata:
54 ret = insns.GetInsnFile(image_type)
55 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
56
57 def testSplitCfgField(self):
58 """Verify splitting behavior behaves"""
59 testdata = (
60 ('', []),
61 ('a b c', ['a', 'b', 'c']),
62 ('a, b', ['a', 'b']),
63 ('a,b', ['a', 'b']),
64 ('a,\tb', ['a', 'b']),
65 ('a\tb', ['a', 'b']),
66 )
67 for val, exp in testdata:
68 ret = pushimage.InputInsns.SplitCfgField(val)
69 self.assertEqual(ret, exp)
70
71 def testOutputInsnsBasic(self):
72 """Verify output instructions are sane"""
73 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040074channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050075keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040076chromeos_shell = false
77ensure_no_password = true
78firmware_update = true
79security_checks = true
80create_nplusone = true
81
82[general]
83"""
84
85 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050086 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040087 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050088 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040089 self.assertTrue(m.called)
90 content = m.call_args_list[0][0][1]
91 self.assertEqual(content.rstrip(), exp_content.rstrip())
92
93 def testOutputInsnsReplacements(self):
94 """Verify output instructions can be updated"""
95 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040096channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -050097keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -040098chromeos_shell = false
99ensure_no_password = true
100firmware_update = true
101security_checks = true
102create_nplusone = true
103
104[general]
105board = board
106config_board = test.board
107"""
108 sect_insns = {
109 'channel': 'dev',
110 'keyset': 'batman',
111 }
Mike Frysingerd7c93092019-10-14 00:12:50 -0400112 sect_general = collections.OrderedDict((
113 ('board', 'board'),
114 ('config_board', 'test.board'),
115 ))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400116
117 insns = pushimage.InputInsns('test.board')
118 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500119 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400120 self.assertTrue(m.called)
121 content = m.call_args_list[0][0][1]
122 self.assertEqual(content.rstrip(), exp_content.rstrip())
123
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500124 def testOutputInsnsMergeAlts(self):
125 """Verify handling of alternative insns.xxx sections"""
126 TEMPLATE_CONTENT = """[insns]
127channel = %(channel)s
128chromeos_shell = false
129ensure_no_password = true
130firmware_update = true
131security_checks = true
132create_nplusone = true
133override = sect_insns
134keyset = %(keyset)s
135%(extra)s
136[general]
137board = board
138config_board = test.board
139"""
140
141 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
142 exp_fields = {
143 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
144 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
145 'hotsoup': {
146 'channel': 'dev canary',
147 'keyset': 'ColdKeyset',
148 'extra': 'soup = cheddar\n',
149 },
150 }
151
152 # Make sure this overrides the insn sections.
153 sect_insns = {
154 'override': 'sect_insns',
155 }
156 sect_insns_copy = sect_insns.copy()
Mike Frysingerd7c93092019-10-14 00:12:50 -0400157 sect_general = collections.OrderedDict((
158 ('board', 'board'),
159 ('config_board', 'test.board'),
160 ))
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500161
162 insns = pushimage.InputInsns('test.multi')
163 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
164 m = self.PatchObject(osutils, 'WriteFile')
165
166 for alt in exp_alts:
167 m.reset_mock()
168 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
169 self.assertEqual(sect_insns, sect_insns_copy)
170 self.assertTrue(m.called)
171 content = m.call_args_list[0][0][1]
172 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
173 self.assertEqual(content.rstrip(), exp_content.rstrip())
174
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400175
Mike Frysinger4495b032014-03-05 17:24:03 -0500176class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400177 """Tests for MarkImageToBeSigned()"""
178
179 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400180 # Minor optimization -- we call this for logging purposes in the main
181 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400182 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400183
184 def testBasic(self):
185 """Simple smoke test"""
186 tbs_base = 'gs://some-bucket'
187 insns_path = 'chan/board/ver/file.instructions'
188 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
189 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
190 self.assertEqual(ret, tbs_file)
191
192 def testPriority(self):
193 """Verify diff priority values get used correctly"""
194 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
195 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
Mike Frysinger2d589a12019-08-25 14:15:12 -0400196 self.assertEqual(ret, '/tobesigned/%s,' % sprio)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400197
198 def testBadPriority(self):
199 """Verify we reject bad priority values"""
200 for prio in (-10, -1, 100, 91239):
201 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
202 '', '', prio)
203
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400204 def testTbsUpload(self):
205 """Make sure we actually try to upload the file"""
206 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
207 self.gs_mock.assertCommandContains(['cp', '--'])
208
209
Mike Frysinger4495b032014-03-05 17:24:03 -0500210class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400211 """Tests for PushImage()"""
212
213 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400214 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
215
216 def testBasic(self):
217 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800218 EXPECTED = {
219 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500220 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
221 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800222 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500223 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
224 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800225 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700226 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
227 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
228 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800229
230 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400231
Amey Deshpande3c487952015-09-03 12:11:13 -0700232 def testBasic_SignTypesEmptyList(self):
233 """Tests PushImage behavior when |sign_types| is empty instead of None.
234
235 As part of the buildbots, PushImage function always receives a tuple for
236 |sign_types| argument. This test checks the behavior for empty tuple.
237 """
238 EXPECTED = {
239 'canary': [
240 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
241 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
242 'dev': [
243 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
244 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
245 }
246 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
247 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
248 profile='hi', sign_types=())
249
250 self.assertEqual(urls, EXPECTED)
251
Amey Deshpandea936c622015-08-12 17:27:54 -0700252 def testBasic_RealBoardName(self):
253 """Runs a simple smoke test using a real board name."""
254 EXPECTED = {
255 'canary': [
256 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
257 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
258 'dev': [
259 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
260 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
261 }
262 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
263 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
264
265 self.assertEqual(urls, EXPECTED)
266
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400267 def testBasicMock(self):
268 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700269 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
270 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
271 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400272
273 def testBadVersion(self):
274 """Make sure we barf on bad version strings"""
275 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
276
277 def testNoInsns(self):
278 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800279 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400280 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800281 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400282
283 def testSignTypesRecovery(self):
284 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800285 EXPECTED = {
286 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500287 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
288 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800289 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500290 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
291 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800292 }
293
Amey Deshpandea936c622015-08-12 17:27:54 -0700294 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
295 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
296 sign_types=['recovery'])
Amin Hassani21e0ed12019-11-04 14:24:36 -0800297 self.assertEqual(self.gs_mock.call_count, 32)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400298 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800299 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400300
Amey Deshpandea936c622015-08-12 17:27:54 -0700301 def testSignTypesBase(self):
302 """Only sign the requested recovery type"""
303 EXPECTED = {
304 'canary': [
305 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
306 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
307 'dev': [
308 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
309 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
310 }
311
312 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
313 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
314 sign_types=['base'])
Amin Hassani21e0ed12019-11-04 14:24:36 -0800315 self.assertEqual(self.gs_mock.call_count, 34)
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600316 self.assertTrue(self.mark_mock.called)
317 self.assertEqual(urls, EXPECTED)
318
319 def testSignTypesCr50Firmware(self):
320 """Only sign the requested type"""
321 EXPECTED = {
322 'canary': [
323 ('gs://chromeos-releases/canary-channel/board2/5126.0.0/'
324 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
325 'dev': [
326 ('gs://chromeos-releases/dev-channel/board2/5126.0.0/'
327 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
328 }
329
330 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
331 urls = pushimage.PushImage('/src', 'board2', 'R34-5126.0.0',
332 sign_types=['cr50_firmware'])
Amin Hassani21e0ed12019-11-04 14:24:36 -0800333 self.assertEqual(self.gs_mock.call_count, 32)
Amey Deshpandea936c622015-08-12 17:27:54 -0700334 self.assertTrue(self.mark_mock.called)
335 self.assertEqual(urls, EXPECTED)
336
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400337 def testSignTypesNone(self):
338 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800339 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
340 sign_types=['nononononono'])
Amin Hassani21e0ed12019-11-04 14:24:36 -0800341 self.assertEqual(self.gs_mock.call_count, 30)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400342 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800343 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400344
Mike Frysinger4495b032014-03-05 17:24:03 -0500345 def testGsError(self):
346 """Verify random GS errors don't make us blow up entirely"""
347 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
348 output='gobblety gook\n')
349 with cros_test_lib.LoggingCapturer('chromite'):
350 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
351 'test.board', 'R34-5126.0.0')
352
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500353 def testMultipleKeysets(self):
354 """Verify behavior when processing an insn w/multiple keysets"""
355 EXPECTED = {
356 'canary': [
357 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
358 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
359 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
360 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
361 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
362 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
363 ],
364 'dev': [
365 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
366 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
367 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
368 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
369 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
370 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
371 ],
372 }
373 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
374 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
375 force_keysets=('key1', 'key2', 'key3'))
376 self.assertEqual(urls, EXPECTED)
377
Mike Frysinger77912102017-08-30 18:35:46 -0400378 def testForceChannel(self):
379 """Verify behavior when user has specified custom channel"""
380 EXPECTED = {
381 'meep': [
382 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
383 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
384 ],
385 }
386 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
387 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
388 force_channels=('meep',))
389 self.assertEqual(urls, EXPECTED)
390
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500391 def testMultipleAltInsns(self):
392 """Verify behavior when processing an insn w/multiple insn overlays"""
393 EXPECTED = {
394 'canary': [
395 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
396 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
397 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
398 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
399 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
400 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
401 ],
402 'dev': [
403 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
404 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
405 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
406 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
407 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
408 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
409 ],
410 }
411 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
412 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
413 self.assertEqual(urls, EXPECTED)
414
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400415
416class MainTests(cros_test_lib.MockTestCase):
417 """Tests for main()"""
418
419 def setUp(self):
420 self.PatchObject(pushimage, 'PushImage')
421
422 def testBasic(self):
423 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500424 pushimage.main(['--board', 'test.board', '/src', '--yes'])