blob: a801570a254c40b0bd4c67017c46f87f26c71bbb [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
25class InputInsnsTest(cros_test_lib.MockTestCase):
26 """Tests for InputInsns"""
27
Don Garrettac529772015-04-29 15:09:26 -070028 def setUp(self):
29 self.StartPatcher(gs_unittest.GSContextMock())
30
Mike Frysingerd13faeb2013-09-05 16:00:46 -040031 def testBasic(self):
32 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070033 insns = pushimage.InputInsns('test.board')
34 insns.GetInsnFile('recovery')
35 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
36 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040037
38 def testGetInsnFile(self):
39 """Verify various inputs result in right insns path"""
40 testdata = (
41 ('UPPER_CAPS', 'UPPER_CAPS'),
42 ('recovery', 'test.board'),
43 ('firmware', 'test.board.firmware'),
44 ('factory', 'test.board.factory'),
45 )
46 insns = pushimage.InputInsns('test.board')
47 for image_type, filename in testdata:
48 ret = insns.GetInsnFile(image_type)
49 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
50
51 def testSplitCfgField(self):
52 """Verify splitting behavior behaves"""
53 testdata = (
54 ('', []),
55 ('a b c', ['a', 'b', 'c']),
56 ('a, b', ['a', 'b']),
57 ('a,b', ['a', 'b']),
58 ('a,\tb', ['a', 'b']),
59 ('a\tb', ['a', 'b']),
60 )
61 for val, exp in testdata:
62 ret = pushimage.InputInsns.SplitCfgField(val)
63 self.assertEqual(ret, exp)
64
65 def testOutputInsnsBasic(self):
66 """Verify output instructions are sane"""
67 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040068channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050069keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040070chromeos_shell = false
71ensure_no_password = true
72firmware_update = true
73security_checks = true
74create_nplusone = true
75
76[general]
77"""
78
79 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050080 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040081 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050082 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040083 self.assertTrue(m.called)
84 content = m.call_args_list[0][0][1]
85 self.assertEqual(content.rstrip(), exp_content.rstrip())
86
87 def testOutputInsnsReplacements(self):
88 """Verify output instructions can be updated"""
89 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040090channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -050091keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -040092chromeos_shell = false
93ensure_no_password = true
94firmware_update = true
95security_checks = true
96create_nplusone = true
97
98[general]
99board = board
100config_board = test.board
101"""
102 sect_insns = {
103 'channel': 'dev',
104 'keyset': 'batman',
105 }
Mike Frysingerd7c93092019-10-14 00:12:50 -0400106 sect_general = collections.OrderedDict((
107 ('board', 'board'),
108 ('config_board', 'test.board'),
109 ))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400110
111 insns = pushimage.InputInsns('test.board')
112 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500113 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400114 self.assertTrue(m.called)
115 content = m.call_args_list[0][0][1]
116 self.assertEqual(content.rstrip(), exp_content.rstrip())
117
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500118 def testOutputInsnsMergeAlts(self):
119 """Verify handling of alternative insns.xxx sections"""
120 TEMPLATE_CONTENT = """[insns]
121channel = %(channel)s
122chromeos_shell = false
123ensure_no_password = true
124firmware_update = true
125security_checks = true
126create_nplusone = true
127override = sect_insns
128keyset = %(keyset)s
129%(extra)s
130[general]
131board = board
132config_board = test.board
133"""
134
135 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
136 exp_fields = {
137 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
138 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
139 'hotsoup': {
140 'channel': 'dev canary',
141 'keyset': 'ColdKeyset',
142 'extra': 'soup = cheddar\n',
143 },
144 }
145
146 # Make sure this overrides the insn sections.
147 sect_insns = {
148 'override': 'sect_insns',
149 }
150 sect_insns_copy = sect_insns.copy()
Mike Frysingerd7c93092019-10-14 00:12:50 -0400151 sect_general = collections.OrderedDict((
152 ('board', 'board'),
153 ('config_board', 'test.board'),
154 ))
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500155
156 insns = pushimage.InputInsns('test.multi')
157 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
158 m = self.PatchObject(osutils, 'WriteFile')
159
160 for alt in exp_alts:
161 m.reset_mock()
162 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
163 self.assertEqual(sect_insns, sect_insns_copy)
164 self.assertTrue(m.called)
165 content = m.call_args_list[0][0][1]
166 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
167 self.assertEqual(content.rstrip(), exp_content.rstrip())
168
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400169
Mike Frysinger4495b032014-03-05 17:24:03 -0500170class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400171 """Tests for MarkImageToBeSigned()"""
172
173 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400174 # Minor optimization -- we call this for logging purposes in the main
175 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400176 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400177
178 def testBasic(self):
179 """Simple smoke test"""
180 tbs_base = 'gs://some-bucket'
181 insns_path = 'chan/board/ver/file.instructions'
182 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
183 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
184 self.assertEqual(ret, tbs_file)
185
186 def testPriority(self):
187 """Verify diff priority values get used correctly"""
188 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
189 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
Mike Frysinger2d589a12019-08-25 14:15:12 -0400190 self.assertEqual(ret, '/tobesigned/%s,' % sprio)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400191
192 def testBadPriority(self):
193 """Verify we reject bad priority values"""
194 for prio in (-10, -1, 100, 91239):
195 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
196 '', '', prio)
197
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400198 def testTbsUpload(self):
199 """Make sure we actually try to upload the file"""
200 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
201 self.gs_mock.assertCommandContains(['cp', '--'])
202
203
Mike Frysinger4495b032014-03-05 17:24:03 -0500204class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400205 """Tests for PushImage()"""
206
207 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400208 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
209
210 def testBasic(self):
211 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800212 EXPECTED = {
213 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500214 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
215 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800216 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500217 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
218 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800219 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700220 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
221 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
222 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800223
224 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400225
Amey Deshpande3c487952015-09-03 12:11:13 -0700226 def testBasic_SignTypesEmptyList(self):
227 """Tests PushImage behavior when |sign_types| is empty instead of None.
228
229 As part of the buildbots, PushImage function always receives a tuple for
230 |sign_types| argument. This test checks the behavior for empty tuple.
231 """
232 EXPECTED = {
233 'canary': [
234 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
235 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
236 'dev': [
237 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
238 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
239 }
240 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
241 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
242 profile='hi', sign_types=())
243
244 self.assertEqual(urls, EXPECTED)
245
Amey Deshpandea936c622015-08-12 17:27:54 -0700246 def testBasic_RealBoardName(self):
247 """Runs a simple smoke test using a real board name."""
248 EXPECTED = {
249 'canary': [
250 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
251 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
252 'dev': [
253 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
254 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
255 }
256 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
257 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
258
259 self.assertEqual(urls, EXPECTED)
260
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400261 def testBasicMock(self):
262 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700263 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
264 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
265 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400266
267 def testBadVersion(self):
268 """Make sure we barf on bad version strings"""
269 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
270
271 def testNoInsns(self):
272 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800273 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400274 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800275 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400276
277 def testSignTypesRecovery(self):
278 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800279 EXPECTED = {
280 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500281 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
282 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800283 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500284 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
285 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800286 }
287
Amey Deshpandea936c622015-08-12 17:27:54 -0700288 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
289 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
290 sign_types=['recovery'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600291 self.assertEqual(self.gs_mock.call_count, 28)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400292 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800293 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400294
Amey Deshpandea936c622015-08-12 17:27:54 -0700295 def testSignTypesBase(self):
296 """Only sign the requested recovery type"""
297 EXPECTED = {
298 'canary': [
299 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
300 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
301 'dev': [
302 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
303 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
304 }
305
306 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
307 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
308 sign_types=['base'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600309 self.assertEqual(self.gs_mock.call_count, 30)
310 self.assertTrue(self.mark_mock.called)
311 self.assertEqual(urls, EXPECTED)
312
313 def testSignTypesCr50Firmware(self):
314 """Only sign the requested type"""
315 EXPECTED = {
316 'canary': [
317 ('gs://chromeos-releases/canary-channel/board2/5126.0.0/'
318 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
319 'dev': [
320 ('gs://chromeos-releases/dev-channel/board2/5126.0.0/'
321 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
322 }
323
324 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
325 urls = pushimage.PushImage('/src', 'board2', 'R34-5126.0.0',
326 sign_types=['cr50_firmware'])
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800327 self.assertEqual(self.gs_mock.call_count, 28)
Amey Deshpandea936c622015-08-12 17:27:54 -0700328 self.assertTrue(self.mark_mock.called)
329 self.assertEqual(urls, EXPECTED)
330
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400331 def testSignTypesNone(self):
332 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800333 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
334 sign_types=['nononononono'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600335 self.assertEqual(self.gs_mock.call_count, 26)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400336 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800337 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400338
Mike Frysinger4495b032014-03-05 17:24:03 -0500339 def testGsError(self):
340 """Verify random GS errors don't make us blow up entirely"""
341 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
342 output='gobblety gook\n')
343 with cros_test_lib.LoggingCapturer('chromite'):
344 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
345 'test.board', 'R34-5126.0.0')
346
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500347 def testMultipleKeysets(self):
348 """Verify behavior when processing an insn w/multiple keysets"""
349 EXPECTED = {
350 'canary': [
351 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
352 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
353 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
354 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
355 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
356 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
357 ],
358 'dev': [
359 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
360 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
361 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
362 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
363 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
364 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
365 ],
366 }
367 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
368 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
369 force_keysets=('key1', 'key2', 'key3'))
370 self.assertEqual(urls, EXPECTED)
371
Mike Frysinger77912102017-08-30 18:35:46 -0400372 def testForceChannel(self):
373 """Verify behavior when user has specified custom channel"""
374 EXPECTED = {
375 'meep': [
376 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
377 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
378 ],
379 }
380 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
381 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
382 force_channels=('meep',))
383 self.assertEqual(urls, EXPECTED)
384
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500385 def testMultipleAltInsns(self):
386 """Verify behavior when processing an insn w/multiple insn overlays"""
387 EXPECTED = {
388 'canary': [
389 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
390 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
391 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
392 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
393 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
394 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
395 ],
396 'dev': [
397 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
398 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
399 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
400 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
401 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
402 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
403 ],
404 }
405 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
406 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
407 self.assertEqual(urls, EXPECTED)
408
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400409
410class MainTests(cros_test_lib.MockTestCase):
411 """Tests for main()"""
412
413 def setUp(self):
414 self.PatchObject(pushimage, 'PushImage')
415
416 def testBasic(self):
417 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500418 pushimage.main(['--board', 'test.board', '/src', '--yes'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400419
420
Mike Frysingerea838d12014-12-08 11:55:32 -0500421def main(_argv):
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700422 # Use our local copy of insns for testing as the main one is not available in
423 # the public manifest. Even though _REL is a relative path, this works because
424 # os.join leaves absolute paths on the right hand side alone.
425 signing.INPUT_INSN_DIR_REL = signing.TEST_INPUT_INSN_DIR
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400426
427 # Run the tests.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500428 cros_test_lib.main(level='notice', module=__name__)