blob: 47bfe0321931788f6cc96ede7a6817ed89cc8d91 [file] [log] [blame]
Mike Frysingerd13faeb2013-09-05 16:00:46 -04001# Copyright (c) 2013 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"""Unittests for pushimage.py"""
6
Mike Frysinger6791b1d2014-03-04 20:52:22 -05007from __future__ import print_function
8
Don Garrett9fd20a82014-09-04 11:37:22 -07009import mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040010import os
Mike Frysingerd13faeb2013-09-05 16:00:46 -040011
Mike Frysinger6791b1d2014-03-04 20:52:22 -050012from chromite.lib import cros_build_lib
Mike Frysingerd13faeb2013-09-05 16:00:46 -040013from chromite.lib import cros_test_lib
Don Garrett9fd20a82014-09-04 11:37:22 -070014from chromite.lib import gs
Mike Frysingerd13faeb2013-09-05 16:00:46 -040015from chromite.lib import gs_unittest
16from chromite.lib import osutils
Mike Frysinger4495b032014-03-05 17:24:03 -050017from chromite.lib import partial_mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040018from chromite.lib import signing
19from chromite.scripts import pushimage
20
21
22class InputInsnsTest(cros_test_lib.MockTestCase):
23 """Tests for InputInsns"""
24
Don Garrettac529772015-04-29 15:09:26 -070025 def setUp(self):
26 self.StartPatcher(gs_unittest.GSContextMock())
27
Mike Frysingerd13faeb2013-09-05 16:00:46 -040028 def testBasic(self):
29 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070030 insns = pushimage.InputInsns('test.board')
31 insns.GetInsnFile('recovery')
32 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
33 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040034
35 def testGetInsnFile(self):
36 """Verify various inputs result in right insns path"""
37 testdata = (
38 ('UPPER_CAPS', 'UPPER_CAPS'),
39 ('recovery', 'test.board'),
40 ('firmware', 'test.board.firmware'),
41 ('factory', 'test.board.factory'),
42 )
43 insns = pushimage.InputInsns('test.board')
44 for image_type, filename in testdata:
45 ret = insns.GetInsnFile(image_type)
46 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
47
48 def testSplitCfgField(self):
49 """Verify splitting behavior behaves"""
50 testdata = (
51 ('', []),
52 ('a b c', ['a', 'b', 'c']),
53 ('a, b', ['a', 'b']),
54 ('a,b', ['a', 'b']),
55 ('a,\tb', ['a', 'b']),
56 ('a\tb', ['a', 'b']),
57 )
58 for val, exp in testdata:
59 ret = pushimage.InputInsns.SplitCfgField(val)
60 self.assertEqual(ret, exp)
61
62 def testOutputInsnsBasic(self):
63 """Verify output instructions are sane"""
64 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040065channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050066keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040067chromeos_shell = false
68ensure_no_password = true
69firmware_update = true
70security_checks = true
71create_nplusone = true
72
73[general]
74"""
75
76 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050077 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040078 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050079 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040080 self.assertTrue(m.called)
81 content = m.call_args_list[0][0][1]
82 self.assertEqual(content.rstrip(), exp_content.rstrip())
83
84 def testOutputInsnsReplacements(self):
85 """Verify output instructions can be updated"""
86 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040087channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -050088keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -040089chromeos_shell = false
90ensure_no_password = true
91firmware_update = true
92security_checks = true
93create_nplusone = true
94
95[general]
96board = board
97config_board = test.board
98"""
99 sect_insns = {
100 'channel': 'dev',
101 'keyset': 'batman',
102 }
103 sect_general = {
104 'config_board': 'test.board',
105 'board': 'board',
106 }
107
108 insns = pushimage.InputInsns('test.board')
109 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500110 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400111 self.assertTrue(m.called)
112 content = m.call_args_list[0][0][1]
113 self.assertEqual(content.rstrip(), exp_content.rstrip())
114
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500115 def testOutputInsnsMergeAlts(self):
116 """Verify handling of alternative insns.xxx sections"""
117 TEMPLATE_CONTENT = """[insns]
118channel = %(channel)s
119chromeos_shell = false
120ensure_no_password = true
121firmware_update = true
122security_checks = true
123create_nplusone = true
124override = sect_insns
125keyset = %(keyset)s
126%(extra)s
127[general]
128board = board
129config_board = test.board
130"""
131
132 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
133 exp_fields = {
134 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
135 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
136 'hotsoup': {
137 'channel': 'dev canary',
138 'keyset': 'ColdKeyset',
139 'extra': 'soup = cheddar\n',
140 },
141 }
142
143 # Make sure this overrides the insn sections.
144 sect_insns = {
145 'override': 'sect_insns',
146 }
147 sect_insns_copy = sect_insns.copy()
148 sect_general = {
149 'config_board': 'test.board',
150 'board': 'board',
151 }
152
153 insns = pushimage.InputInsns('test.multi')
154 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
155 m = self.PatchObject(osutils, 'WriteFile')
156
157 for alt in exp_alts:
158 m.reset_mock()
159 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
160 self.assertEqual(sect_insns, sect_insns_copy)
161 self.assertTrue(m.called)
162 content = m.call_args_list[0][0][1]
163 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
164 self.assertEqual(content.rstrip(), exp_content.rstrip())
165
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400166
Mike Frysinger4495b032014-03-05 17:24:03 -0500167class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400168 """Tests for MarkImageToBeSigned()"""
169
170 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400171 # Minor optimization -- we call this for logging purposes in the main
172 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400173 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400174
175 def testBasic(self):
176 """Simple smoke test"""
177 tbs_base = 'gs://some-bucket'
178 insns_path = 'chan/board/ver/file.instructions'
179 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
180 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
181 self.assertEqual(ret, tbs_file)
182
183 def testPriority(self):
184 """Verify diff priority values get used correctly"""
185 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
186 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
187 self.assertEquals(ret, '/tobesigned/%s,' % sprio)
188
189 def testBadPriority(self):
190 """Verify we reject bad priority values"""
191 for prio in (-10, -1, 100, 91239):
192 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
193 '', '', prio)
194
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400195 def testTbsUpload(self):
196 """Make sure we actually try to upload the file"""
197 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
198 self.gs_mock.assertCommandContains(['cp', '--'])
199
200
Mike Frysinger4495b032014-03-05 17:24:03 -0500201class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400202 """Tests for PushImage()"""
203
204 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400205 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
206
207 def testBasic(self):
208 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800209 EXPECTED = {
210 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500211 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
212 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800213 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500214 ('gs://chromeos-releases/dev-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 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700217 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
218 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
219 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800220
221 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400222
Amey Deshpande3c487952015-09-03 12:11:13 -0700223 def testBasic_SignTypesEmptyList(self):
224 """Tests PushImage behavior when |sign_types| is empty instead of None.
225
226 As part of the buildbots, PushImage function always receives a tuple for
227 |sign_types| argument. This test checks the behavior for empty tuple.
228 """
229 EXPECTED = {
230 'canary': [
231 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
232 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
233 'dev': [
234 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
235 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
236 }
237 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
238 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
239 profile='hi', sign_types=())
240
241 self.assertEqual(urls, EXPECTED)
242
Amey Deshpandea936c622015-08-12 17:27:54 -0700243 def testBasic_RealBoardName(self):
244 """Runs a simple smoke test using a real board name."""
245 EXPECTED = {
246 'canary': [
247 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
248 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
249 'dev': [
250 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
251 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
252 }
253 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
254 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
255
256 self.assertEqual(urls, EXPECTED)
257
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400258 def testBasicMock(self):
259 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700260 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
261 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
262 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400263
264 def testBadVersion(self):
265 """Make sure we barf on bad version strings"""
266 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
267
268 def testNoInsns(self):
269 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800270 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400271 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800272 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400273
274 def testSignTypesRecovery(self):
275 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800276 EXPECTED = {
277 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500278 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
279 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800280 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500281 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
282 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800283 }
284
Amey Deshpandea936c622015-08-12 17:27:54 -0700285 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
286 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
287 sign_types=['recovery'])
Vincent Palatind599c662015-10-26 09:51:41 -0700288 self.assertEqual(self.gs_mock.call_count, 26)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400289 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800290 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400291
Amey Deshpandea936c622015-08-12 17:27:54 -0700292 def testSignTypesBase(self):
293 """Only sign the requested recovery type"""
294 EXPECTED = {
295 'canary': [
296 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
297 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
298 'dev': [
299 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
300 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
301 }
302
303 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
304 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
305 sign_types=['base'])
Vincent Palatind599c662015-10-26 09:51:41 -0700306 self.assertEqual(self.gs_mock.call_count, 28)
Amey Deshpandea936c622015-08-12 17:27:54 -0700307 self.assertTrue(self.mark_mock.called)
308 self.assertEqual(urls, EXPECTED)
309
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400310 def testSignTypesNone(self):
311 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800312 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
313 sign_types=['nononononono'])
Vincent Palatind599c662015-10-26 09:51:41 -0700314 self.assertEqual(self.gs_mock.call_count, 24)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400315 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800316 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400317
Mike Frysinger4495b032014-03-05 17:24:03 -0500318 def testGsError(self):
319 """Verify random GS errors don't make us blow up entirely"""
320 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
321 output='gobblety gook\n')
322 with cros_test_lib.LoggingCapturer('chromite'):
323 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
324 'test.board', 'R34-5126.0.0')
325
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500326 def testMultipleKeysets(self):
327 """Verify behavior when processing an insn w/multiple keysets"""
328 EXPECTED = {
329 'canary': [
330 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
331 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
332 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
333 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
334 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
335 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
336 ],
337 'dev': [
338 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
339 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
340 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
341 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
342 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
343 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
344 ],
345 }
346 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
347 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
348 force_keysets=('key1', 'key2', 'key3'))
349 self.assertEqual(urls, EXPECTED)
350
Mike Frysinger77912102017-08-30 18:35:46 -0400351 def testForceChannel(self):
352 """Verify behavior when user has specified custom channel"""
353 EXPECTED = {
354 'meep': [
355 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
356 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
357 ],
358 }
359 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
360 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
361 force_channels=('meep',))
362 self.assertEqual(urls, EXPECTED)
363
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500364 def testMultipleAltInsns(self):
365 """Verify behavior when processing an insn w/multiple insn overlays"""
366 EXPECTED = {
367 'canary': [
368 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
369 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
370 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
371 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
372 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
373 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
374 ],
375 'dev': [
376 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
377 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
378 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
379 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
380 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
381 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
382 ],
383 }
384 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
385 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
386 self.assertEqual(urls, EXPECTED)
387
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400388
389class MainTests(cros_test_lib.MockTestCase):
390 """Tests for main()"""
391
392 def setUp(self):
393 self.PatchObject(pushimage, 'PushImage')
394
395 def testBasic(self):
396 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500397 pushimage.main(['--board', 'test.board', '/src', '--yes'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400398
399
Mike Frysingerea838d12014-12-08 11:55:32 -0500400def main(_argv):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400401 # Use our local copy of insns for testing as the main one is not
402 # available in the public manifest.
403 signing.INPUT_INSN_DIR = signing.TEST_INPUT_INSN_DIR
404
405 # Run the tests.
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500406 cros_test_lib.main(level='notice', module=__name__)