blob: 4fdcf7e22df6f1071f1d698fda751bd590c0944f [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
Don Garrett9fd20a82014-09-04 11:37:22 -070010import mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040011import os
Mike Frysingerd13faeb2013-09-05 16:00:46 -040012
Mike Frysinger6791b1d2014-03-04 20:52:22 -050013from chromite.lib import cros_build_lib
Mike Frysingerd13faeb2013-09-05 16:00:46 -040014from chromite.lib import cros_test_lib
Don Garrett9fd20a82014-09-04 11:37:22 -070015from chromite.lib import gs
Mike Frysingerd13faeb2013-09-05 16:00:46 -040016from chromite.lib import gs_unittest
17from chromite.lib import osutils
Mike Frysinger4495b032014-03-05 17:24:03 -050018from chromite.lib import partial_mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040019from chromite.lib import signing
20from chromite.scripts import pushimage
21
22
23class InputInsnsTest(cros_test_lib.MockTestCase):
24 """Tests for InputInsns"""
25
Don Garrettac529772015-04-29 15:09:26 -070026 def setUp(self):
27 self.StartPatcher(gs_unittest.GSContextMock())
28
Mike Frysingerd13faeb2013-09-05 16:00:46 -040029 def testBasic(self):
30 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070031 insns = pushimage.InputInsns('test.board')
32 insns.GetInsnFile('recovery')
33 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
34 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040035
36 def testGetInsnFile(self):
37 """Verify various inputs result in right insns path"""
38 testdata = (
39 ('UPPER_CAPS', 'UPPER_CAPS'),
40 ('recovery', 'test.board'),
41 ('firmware', 'test.board.firmware'),
42 ('factory', 'test.board.factory'),
43 )
44 insns = pushimage.InputInsns('test.board')
45 for image_type, filename in testdata:
46 ret = insns.GetInsnFile(image_type)
47 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
48
49 def testSplitCfgField(self):
50 """Verify splitting behavior behaves"""
51 testdata = (
52 ('', []),
53 ('a b c', ['a', 'b', 'c']),
54 ('a, b', ['a', 'b']),
55 ('a,b', ['a', 'b']),
56 ('a,\tb', ['a', 'b']),
57 ('a\tb', ['a', 'b']),
58 )
59 for val, exp in testdata:
60 ret = pushimage.InputInsns.SplitCfgField(val)
61 self.assertEqual(ret, exp)
62
63 def testOutputInsnsBasic(self):
64 """Verify output instructions are sane"""
65 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040066channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050067keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040068chromeos_shell = false
69ensure_no_password = true
70firmware_update = true
71security_checks = true
72create_nplusone = true
73
74[general]
75"""
76
77 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050078 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040079 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050080 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040081 self.assertTrue(m.called)
82 content = m.call_args_list[0][0][1]
83 self.assertEqual(content.rstrip(), exp_content.rstrip())
84
85 def testOutputInsnsReplacements(self):
86 """Verify output instructions can be updated"""
87 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040088channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -050089keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -040090chromeos_shell = false
91ensure_no_password = true
92firmware_update = true
93security_checks = true
94create_nplusone = true
95
96[general]
97board = board
98config_board = test.board
99"""
100 sect_insns = {
101 'channel': 'dev',
102 'keyset': 'batman',
103 }
104 sect_general = {
105 'config_board': 'test.board',
106 'board': 'board',
107 }
108
109 insns = pushimage.InputInsns('test.board')
110 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500111 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400112 self.assertTrue(m.called)
113 content = m.call_args_list[0][0][1]
114 self.assertEqual(content.rstrip(), exp_content.rstrip())
115
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500116 def testOutputInsnsMergeAlts(self):
117 """Verify handling of alternative insns.xxx sections"""
118 TEMPLATE_CONTENT = """[insns]
119channel = %(channel)s
120chromeos_shell = false
121ensure_no_password = true
122firmware_update = true
123security_checks = true
124create_nplusone = true
125override = sect_insns
126keyset = %(keyset)s
127%(extra)s
128[general]
129board = board
130config_board = test.board
131"""
132
133 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
134 exp_fields = {
135 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
136 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
137 'hotsoup': {
138 'channel': 'dev canary',
139 'keyset': 'ColdKeyset',
140 'extra': 'soup = cheddar\n',
141 },
142 }
143
144 # Make sure this overrides the insn sections.
145 sect_insns = {
146 'override': 'sect_insns',
147 }
148 sect_insns_copy = sect_insns.copy()
149 sect_general = {
150 'config_board': 'test.board',
151 'board': 'board',
152 }
153
154 insns = pushimage.InputInsns('test.multi')
155 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
156 m = self.PatchObject(osutils, 'WriteFile')
157
158 for alt in exp_alts:
159 m.reset_mock()
160 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
161 self.assertEqual(sect_insns, sect_insns_copy)
162 self.assertTrue(m.called)
163 content = m.call_args_list[0][0][1]
164 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
165 self.assertEqual(content.rstrip(), exp_content.rstrip())
166
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400167
Mike Frysinger4495b032014-03-05 17:24:03 -0500168class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400169 """Tests for MarkImageToBeSigned()"""
170
171 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400172 # Minor optimization -- we call this for logging purposes in the main
173 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400174 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400175
176 def testBasic(self):
177 """Simple smoke test"""
178 tbs_base = 'gs://some-bucket'
179 insns_path = 'chan/board/ver/file.instructions'
180 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
181 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
182 self.assertEqual(ret, tbs_file)
183
184 def testPriority(self):
185 """Verify diff priority values get used correctly"""
186 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
187 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
188 self.assertEquals(ret, '/tobesigned/%s,' % sprio)
189
190 def testBadPriority(self):
191 """Verify we reject bad priority values"""
192 for prio in (-10, -1, 100, 91239):
193 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
194 '', '', prio)
195
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400196 def testTbsUpload(self):
197 """Make sure we actually try to upload the file"""
198 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
199 self.gs_mock.assertCommandContains(['cp', '--'])
200
201
Mike Frysinger4495b032014-03-05 17:24:03 -0500202class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400203 """Tests for PushImage()"""
204
205 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400206 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
207
208 def testBasic(self):
209 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800210 EXPECTED = {
211 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500212 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
213 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800214 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500215 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
216 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800217 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700218 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
219 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
220 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800221
222 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400223
Amey Deshpande3c487952015-09-03 12:11:13 -0700224 def testBasic_SignTypesEmptyList(self):
225 """Tests PushImage behavior when |sign_types| is empty instead of None.
226
227 As part of the buildbots, PushImage function always receives a tuple for
228 |sign_types| argument. This test checks the behavior for empty tuple.
229 """
230 EXPECTED = {
231 'canary': [
232 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
233 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
234 'dev': [
235 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
236 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
237 }
238 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
239 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
240 profile='hi', sign_types=())
241
242 self.assertEqual(urls, EXPECTED)
243
Amey Deshpandea936c622015-08-12 17:27:54 -0700244 def testBasic_RealBoardName(self):
245 """Runs a simple smoke test using a real board name."""
246 EXPECTED = {
247 'canary': [
248 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
249 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
250 'dev': [
251 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
252 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
253 }
254 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
255 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
256
257 self.assertEqual(urls, EXPECTED)
258
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400259 def testBasicMock(self):
260 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700261 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
262 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
263 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400264
265 def testBadVersion(self):
266 """Make sure we barf on bad version strings"""
267 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
268
269 def testNoInsns(self):
270 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800271 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400272 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800273 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400274
275 def testSignTypesRecovery(self):
276 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800277 EXPECTED = {
278 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500279 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
280 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800281 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500282 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
283 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800284 }
285
Amey Deshpandea936c622015-08-12 17:27:54 -0700286 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
287 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
288 sign_types=['recovery'])
Amin Hassanic27a6612018-06-04 10:27:12 -0700289 self.assertEqual(self.gs_mock.call_count, 24)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400290 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800291 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400292
Amey Deshpandea936c622015-08-12 17:27:54 -0700293 def testSignTypesBase(self):
294 """Only sign the requested recovery type"""
295 EXPECTED = {
296 'canary': [
297 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
298 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
299 'dev': [
300 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
301 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
302 }
303
304 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
305 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
306 sign_types=['base'])
Amin Hassanic27a6612018-06-04 10:27:12 -0700307 self.assertEqual(self.gs_mock.call_count, 26)
Amey Deshpandea936c622015-08-12 17:27:54 -0700308 self.assertTrue(self.mark_mock.called)
309 self.assertEqual(urls, EXPECTED)
310
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400311 def testSignTypesNone(self):
312 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800313 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
314 sign_types=['nononononono'])
Amin Hassanic27a6612018-06-04 10:27:12 -0700315 self.assertEqual(self.gs_mock.call_count, 22)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400316 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800317 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400318
Mike Frysinger4495b032014-03-05 17:24:03 -0500319 def testGsError(self):
320 """Verify random GS errors don't make us blow up entirely"""
321 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
322 output='gobblety gook\n')
323 with cros_test_lib.LoggingCapturer('chromite'):
324 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
325 'test.board', 'R34-5126.0.0')
326
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500327 def testMultipleKeysets(self):
328 """Verify behavior when processing an insn w/multiple keysets"""
329 EXPECTED = {
330 'canary': [
331 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
332 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
333 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
334 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
335 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
336 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
337 ],
338 'dev': [
339 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
340 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
341 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
342 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
343 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
344 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
345 ],
346 }
347 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
348 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
349 force_keysets=('key1', 'key2', 'key3'))
350 self.assertEqual(urls, EXPECTED)
351
Mike Frysinger77912102017-08-30 18:35:46 -0400352 def testForceChannel(self):
353 """Verify behavior when user has specified custom channel"""
354 EXPECTED = {
355 'meep': [
356 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
357 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
358 ],
359 }
360 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
361 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
362 force_channels=('meep',))
363 self.assertEqual(urls, EXPECTED)
364
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500365 def testMultipleAltInsns(self):
366 """Verify behavior when processing an insn w/multiple insn overlays"""
367 EXPECTED = {
368 'canary': [
369 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
370 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
371 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
372 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
373 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
374 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
375 ],
376 'dev': [
377 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
378 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
379 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
380 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
381 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
382 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
383 ],
384 }
385 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
386 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
387 self.assertEqual(urls, EXPECTED)
388
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400389
390class MainTests(cros_test_lib.MockTestCase):
391 """Tests for main()"""
392
393 def setUp(self):
394 self.PatchObject(pushimage, 'PushImage')
395
396 def testBasic(self):
397 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500398 pushimage.main(['--board', 'test.board', '/src', '--yes'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400399
400
Mike Frysingerea838d12014-12-08 11:55:32 -0500401def main(_argv):
Don Garrett3cf5f9a2018-08-14 13:14:47 -0700402 # Use our local copy of insns for testing as the main one is not available in
403 # the public manifest. Even though _REL is a relative path, this works because
404 # os.join leaves absolute paths on the right hand side alone.
405 signing.INPUT_INSN_DIR_REL = signing.TEST_INPUT_INSN_DIR
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400406
407 # Run the tests.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500408 cros_test_lib.main(level='notice', module=__name__)