blob: c620e6fd6fa88e7735f6676776ee9ca1aa7b1074 [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 Frysingerd13faeb2013-09-05 16:00:46 -040010import os
Mike Frysingerd13faeb2013-09-05 16:00:46 -040011
Mike Frysinger6db648e2018-07-24 19:57:58 -040012import mock
13
Mike Frysinger6791b1d2014-03-04 20:52:22 -050014from chromite.lib import cros_build_lib
Mike Frysingerd13faeb2013-09-05 16:00:46 -040015from chromite.lib import cros_test_lib
Don Garrett9fd20a82014-09-04 11:37:22 -070016from chromite.lib import gs
Mike Frysingerd13faeb2013-09-05 16:00:46 -040017from chromite.lib import gs_unittest
18from chromite.lib import osutils
Mike Frysinger4495b032014-03-05 17:24:03 -050019from chromite.lib import partial_mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040020from chromite.lib import signing
21from chromite.scripts import pushimage
22
23
24class InputInsnsTest(cros_test_lib.MockTestCase):
25 """Tests for InputInsns"""
26
Don Garrettac529772015-04-29 15:09:26 -070027 def setUp(self):
28 self.StartPatcher(gs_unittest.GSContextMock())
29
Mike Frysingerd13faeb2013-09-05 16:00:46 -040030 def testBasic(self):
31 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070032 insns = pushimage.InputInsns('test.board')
33 insns.GetInsnFile('recovery')
34 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
35 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040036
37 def testGetInsnFile(self):
38 """Verify various inputs result in right insns path"""
39 testdata = (
40 ('UPPER_CAPS', 'UPPER_CAPS'),
41 ('recovery', 'test.board'),
42 ('firmware', 'test.board.firmware'),
43 ('factory', 'test.board.factory'),
44 )
45 insns = pushimage.InputInsns('test.board')
46 for image_type, filename in testdata:
47 ret = insns.GetInsnFile(image_type)
48 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
49
50 def testSplitCfgField(self):
51 """Verify splitting behavior behaves"""
52 testdata = (
53 ('', []),
54 ('a b c', ['a', 'b', 'c']),
55 ('a, b', ['a', 'b']),
56 ('a,b', ['a', 'b']),
57 ('a,\tb', ['a', 'b']),
58 ('a\tb', ['a', 'b']),
59 )
60 for val, exp in testdata:
61 ret = pushimage.InputInsns.SplitCfgField(val)
62 self.assertEqual(ret, exp)
63
64 def testOutputInsnsBasic(self):
65 """Verify output instructions are sane"""
66 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040067channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050068keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040069chromeos_shell = false
70ensure_no_password = true
71firmware_update = true
72security_checks = true
73create_nplusone = true
74
75[general]
76"""
77
78 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050079 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040080 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050081 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040082 self.assertTrue(m.called)
83 content = m.call_args_list[0][0][1]
84 self.assertEqual(content.rstrip(), exp_content.rstrip())
85
86 def testOutputInsnsReplacements(self):
87 """Verify output instructions can be updated"""
88 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040089channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -050090keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -040091chromeos_shell = false
92ensure_no_password = true
93firmware_update = true
94security_checks = true
95create_nplusone = true
96
97[general]
98board = board
99config_board = test.board
100"""
101 sect_insns = {
102 'channel': 'dev',
103 'keyset': 'batman',
104 }
105 sect_general = {
106 'config_board': 'test.board',
107 'board': 'board',
108 }
109
110 insns = pushimage.InputInsns('test.board')
111 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500112 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400113 self.assertTrue(m.called)
114 content = m.call_args_list[0][0][1]
115 self.assertEqual(content.rstrip(), exp_content.rstrip())
116
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500117 def testOutputInsnsMergeAlts(self):
118 """Verify handling of alternative insns.xxx sections"""
119 TEMPLATE_CONTENT = """[insns]
120channel = %(channel)s
121chromeos_shell = false
122ensure_no_password = true
123firmware_update = true
124security_checks = true
125create_nplusone = true
126override = sect_insns
127keyset = %(keyset)s
128%(extra)s
129[general]
130board = board
131config_board = test.board
132"""
133
134 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
135 exp_fields = {
136 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
137 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
138 'hotsoup': {
139 'channel': 'dev canary',
140 'keyset': 'ColdKeyset',
141 'extra': 'soup = cheddar\n',
142 },
143 }
144
145 # Make sure this overrides the insn sections.
146 sect_insns = {
147 'override': 'sect_insns',
148 }
149 sect_insns_copy = sect_insns.copy()
150 sect_general = {
151 'config_board': 'test.board',
152 'board': 'board',
153 }
154
155 insns = pushimage.InputInsns('test.multi')
156 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
157 m = self.PatchObject(osutils, 'WriteFile')
158
159 for alt in exp_alts:
160 m.reset_mock()
161 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
162 self.assertEqual(sect_insns, sect_insns_copy)
163 self.assertTrue(m.called)
164 content = m.call_args_list[0][0][1]
165 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
166 self.assertEqual(content.rstrip(), exp_content.rstrip())
167
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400168
Mike Frysinger4495b032014-03-05 17:24:03 -0500169class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400170 """Tests for MarkImageToBeSigned()"""
171
172 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400173 # Minor optimization -- we call this for logging purposes in the main
174 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400175 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400176
177 def testBasic(self):
178 """Simple smoke test"""
179 tbs_base = 'gs://some-bucket'
180 insns_path = 'chan/board/ver/file.instructions'
181 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
182 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
183 self.assertEqual(ret, tbs_file)
184
185 def testPriority(self):
186 """Verify diff priority values get used correctly"""
187 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
188 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
Mike Frysinger2d589a12019-08-25 14:15:12 -0400189 self.assertEqual(ret, '/tobesigned/%s,' % sprio)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400190
191 def testBadPriority(self):
192 """Verify we reject bad priority values"""
193 for prio in (-10, -1, 100, 91239):
194 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
195 '', '', prio)
196
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400197 def testTbsUpload(self):
198 """Make sure we actually try to upload the file"""
199 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
200 self.gs_mock.assertCommandContains(['cp', '--'])
201
202
Mike Frysinger4495b032014-03-05 17:24:03 -0500203class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400204 """Tests for PushImage()"""
205
206 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400207 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
208
209 def testBasic(self):
210 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800211 EXPECTED = {
212 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500213 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
214 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800215 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500216 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
217 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800218 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700219 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
220 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
221 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800222
223 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400224
Amey Deshpande3c487952015-09-03 12:11:13 -0700225 def testBasic_SignTypesEmptyList(self):
226 """Tests PushImage behavior when |sign_types| is empty instead of None.
227
228 As part of the buildbots, PushImage function always receives a tuple for
229 |sign_types| argument. This test checks the behavior for empty tuple.
230 """
231 EXPECTED = {
232 'canary': [
233 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
234 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
235 'dev': [
236 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
237 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
238 }
239 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
240 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
241 profile='hi', sign_types=())
242
243 self.assertEqual(urls, EXPECTED)
244
Amey Deshpandea936c622015-08-12 17:27:54 -0700245 def testBasic_RealBoardName(self):
246 """Runs a simple smoke test using a real board name."""
247 EXPECTED = {
248 'canary': [
249 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
250 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
251 'dev': [
252 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
253 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
254 }
255 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
256 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
257
258 self.assertEqual(urls, EXPECTED)
259
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400260 def testBasicMock(self):
261 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700262 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
263 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
264 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400265
266 def testBadVersion(self):
267 """Make sure we barf on bad version strings"""
268 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
269
270 def testNoInsns(self):
271 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800272 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400273 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800274 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400275
276 def testSignTypesRecovery(self):
277 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800278 EXPECTED = {
279 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500280 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
281 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800282 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500283 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
284 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800285 }
286
Amey Deshpandea936c622015-08-12 17:27:54 -0700287 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
288 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
289 sign_types=['recovery'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600290 self.assertEqual(self.gs_mock.call_count, 28)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400291 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800292 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400293
Amey Deshpandea936c622015-08-12 17:27:54 -0700294 def testSignTypesBase(self):
295 """Only sign the requested recovery type"""
296 EXPECTED = {
297 'canary': [
298 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
299 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
300 'dev': [
301 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
302 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
303 }
304
305 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
306 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
307 sign_types=['base'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600308 self.assertEqual(self.gs_mock.call_count, 30)
309 self.assertTrue(self.mark_mock.called)
310 self.assertEqual(urls, EXPECTED)
311
312 def testSignTypesCr50Firmware(self):
313 """Only sign the requested type"""
314 EXPECTED = {
315 'canary': [
316 ('gs://chromeos-releases/canary-channel/board2/5126.0.0/'
317 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
318 'dev': [
319 ('gs://chromeos-releases/dev-channel/board2/5126.0.0/'
320 'ChromeOS-cr50_firmware-R34-5126.0.0-board2.instructions')],
321 }
322
323 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
324 urls = pushimage.PushImage('/src', 'board2', 'R34-5126.0.0',
325 sign_types=['cr50_firmware'])
Xiaochu Liu254e0dd2019-03-08 16:10:57 -0800326 self.assertEqual(self.gs_mock.call_count, 28)
Amey Deshpandea936c622015-08-12 17:27:54 -0700327 self.assertTrue(self.mark_mock.called)
328 self.assertEqual(urls, EXPECTED)
329
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400330 def testSignTypesNone(self):
331 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800332 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
333 sign_types=['nononononono'])
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600334 self.assertEqual(self.gs_mock.call_count, 26)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400335 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800336 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400337
Mike Frysinger4495b032014-03-05 17:24:03 -0500338 def testGsError(self):
339 """Verify random GS errors don't make us blow up entirely"""
340 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
341 output='gobblety gook\n')
342 with cros_test_lib.LoggingCapturer('chromite'):
343 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
344 'test.board', 'R34-5126.0.0')
345
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500346 def testMultipleKeysets(self):
347 """Verify behavior when processing an insn w/multiple keysets"""
348 EXPECTED = {
349 'canary': [
350 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
351 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
352 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
353 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
354 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
355 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
356 ],
357 'dev': [
358 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
359 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
360 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
361 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
362 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
363 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
364 ],
365 }
366 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
367 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
368 force_keysets=('key1', 'key2', 'key3'))
369 self.assertEqual(urls, EXPECTED)
370
Mike Frysinger77912102017-08-30 18:35:46 -0400371 def testForceChannel(self):
372 """Verify behavior when user has specified custom channel"""
373 EXPECTED = {
374 'meep': [
375 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
376 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
377 ],
378 }
379 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
380 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
381 force_channels=('meep',))
382 self.assertEqual(urls, EXPECTED)
383
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500384 def testMultipleAltInsns(self):
385 """Verify behavior when processing an insn w/multiple insn overlays"""
386 EXPECTED = {
387 'canary': [
388 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
389 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
390 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
391 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
392 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
393 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
394 ],
395 'dev': [
396 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
397 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
398 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
399 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
400 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
401 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
402 ],
403 }
404 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
405 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
406 self.assertEqual(urls, EXPECTED)
407
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400408
409class MainTests(cros_test_lib.MockTestCase):
410 """Tests for main()"""
411
412 def setUp(self):
413 self.PatchObject(pushimage, 'PushImage')
414
415 def testBasic(self):
416 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500417 pushimage.main(['--board', 'test.board', '/src', '--yes'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400418
419
Mike Frysingerea838d12014-12-08 11:55:32 -0500420def main(_argv):
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700421 # Use our local copy of insns for testing as the main one is not available in
422 # the public manifest. Even though _REL is a relative path, this works because
423 # os.join leaves absolute paths on the right hand side alone.
424 signing.INPUT_INSN_DIR_REL = signing.TEST_INPUT_INSN_DIR
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400425
426 # Run the tests.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500427 cros_test_lib.main(level='notice', module=__name__)