blob: 2d091c9ec434f6d474dbdd7671089fd5206536ba [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 Frysinger99a7f8a2020-04-19 06:23:18 -040012import sys
Mike Frysingerd13faeb2013-09-05 16:00:46 -040013
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
Mike Frysinger40ffb532021-02-12 07:36:08 -050022from chromite.third_party import mock
Mike Frysingerd13faeb2013-09-05 16:00:46 -040023
24
Mike Frysinger99a7f8a2020-04-19 06:23:18 -040025assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
26
27
Greg Edelstona4c9b3b2020-01-07 17:51:13 -070028# Use our local copy of insns for testing as the main one is not available in
29# the public manifest. Even though _REL is a relative path, this works because
30# os.join leaves absolute paths on the right hand side alone.
31signing.INPUT_INSN_DIR_REL = signing.TEST_INPUT_INSN_DIR
32
33
Mike Frysingerd13faeb2013-09-05 16:00:46 -040034class InputInsnsTest(cros_test_lib.MockTestCase):
35 """Tests for InputInsns"""
36
Don Garrettac529772015-04-29 15:09:26 -070037 def setUp(self):
38 self.StartPatcher(gs_unittest.GSContextMock())
39
Mike Frysingerd13faeb2013-09-05 16:00:46 -040040 def testBasic(self):
41 """Simple smoke test"""
Don Garrettac529772015-04-29 15:09:26 -070042 insns = pushimage.InputInsns('test.board')
43 insns.GetInsnFile('recovery')
44 self.assertEqual(insns.GetChannels(), ['dev', 'canary'])
45 self.assertEqual(insns.GetKeysets(), ['stumpy-mp-v3'])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040046
47 def testGetInsnFile(self):
48 """Verify various inputs result in right insns path"""
49 testdata = (
50 ('UPPER_CAPS', 'UPPER_CAPS'),
51 ('recovery', 'test.board'),
52 ('firmware', 'test.board.firmware'),
53 ('factory', 'test.board.factory'),
54 )
55 insns = pushimage.InputInsns('test.board')
56 for image_type, filename in testdata:
57 ret = insns.GetInsnFile(image_type)
58 self.assertEqual(os.path.basename(ret), '%s.instructions' % (filename))
59
60 def testSplitCfgField(self):
61 """Verify splitting behavior behaves"""
62 testdata = (
63 ('', []),
64 ('a b c', ['a', 'b', 'c']),
65 ('a, b', ['a', 'b']),
66 ('a,b', ['a', 'b']),
67 ('a,\tb', ['a', 'b']),
68 ('a\tb', ['a', 'b']),
69 )
70 for val, exp in testdata:
71 ret = pushimage.InputInsns.SplitCfgField(val)
72 self.assertEqual(ret, exp)
73
74 def testOutputInsnsBasic(self):
75 """Verify output instructions are sane"""
76 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040077channel = dev canary
Mike Frysingerd84d91e2015-11-05 18:02:24 -050078keyset = stumpy-mp-v3
Mike Frysingerd13faeb2013-09-05 16:00:46 -040079chromeos_shell = false
80ensure_no_password = true
81firmware_update = true
82security_checks = true
83create_nplusone = true
84
85[general]
86"""
87
88 insns = pushimage.InputInsns('test.board')
Mike Frysinger37ccc2b2015-11-11 17:16:51 -050089 self.assertEqual(insns.GetAltInsnSets(), [None])
Mike Frysingerd13faeb2013-09-05 16:00:46 -040090 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -050091 insns.OutputInsns('/bogus', {}, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -040092 self.assertTrue(m.called)
93 content = m.call_args_list[0][0][1]
94 self.assertEqual(content.rstrip(), exp_content.rstrip())
95
96 def testOutputInsnsReplacements(self):
97 """Verify output instructions can be updated"""
98 exp_content = """[insns]
Mike Frysingerd13faeb2013-09-05 16:00:46 -040099channel = dev
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500100keyset = batman
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400101chromeos_shell = false
102ensure_no_password = true
103firmware_update = true
104security_checks = true
105create_nplusone = true
106
107[general]
108board = board
109config_board = test.board
110"""
111 sect_insns = {
112 'channel': 'dev',
113 'keyset': 'batman',
114 }
Mike Frysingerd7c93092019-10-14 00:12:50 -0400115 sect_general = collections.OrderedDict((
116 ('board', 'board'),
117 ('config_board', 'test.board'),
118 ))
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400119
120 insns = pushimage.InputInsns('test.board')
121 m = self.PatchObject(osutils, 'WriteFile')
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500122 insns.OutputInsns('/a/file', sect_insns, sect_general)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400123 self.assertTrue(m.called)
124 content = m.call_args_list[0][0][1]
125 self.assertEqual(content.rstrip(), exp_content.rstrip())
126
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500127 def testOutputInsnsMergeAlts(self):
128 """Verify handling of alternative insns.xxx sections"""
129 TEMPLATE_CONTENT = """[insns]
130channel = %(channel)s
131chromeos_shell = false
132ensure_no_password = true
133firmware_update = true
134security_checks = true
135create_nplusone = true
136override = sect_insns
137keyset = %(keyset)s
138%(extra)s
139[general]
140board = board
141config_board = test.board
142"""
143
144 exp_alts = ['insns.one', 'insns.two', 'insns.hotsoup']
145 exp_fields = {
146 'one': {'channel': 'dev canary', 'keyset': 'OneKeyset', 'extra': ''},
147 'two': {'channel': 'best', 'keyset': 'TwoKeyset', 'extra': ''},
148 'hotsoup': {
149 'channel': 'dev canary',
150 'keyset': 'ColdKeyset',
151 'extra': 'soup = cheddar\n',
152 },
153 }
154
155 # Make sure this overrides the insn sections.
156 sect_insns = {
157 'override': 'sect_insns',
158 }
159 sect_insns_copy = sect_insns.copy()
Mike Frysingerd7c93092019-10-14 00:12:50 -0400160 sect_general = collections.OrderedDict((
161 ('board', 'board'),
162 ('config_board', 'test.board'),
163 ))
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500164
165 insns = pushimage.InputInsns('test.multi')
166 self.assertEqual(insns.GetAltInsnSets(), exp_alts)
167 m = self.PatchObject(osutils, 'WriteFile')
168
169 for alt in exp_alts:
170 m.reset_mock()
171 insns.OutputInsns('/a/file', sect_insns, sect_general, insns_merge=alt)
172 self.assertEqual(sect_insns, sect_insns_copy)
173 self.assertTrue(m.called)
174 content = m.call_args_list[0][0][1]
175 exp_content = TEMPLATE_CONTENT % exp_fields[alt[6:]]
176 self.assertEqual(content.rstrip(), exp_content.rstrip())
177
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400178
Mike Frysinger4495b032014-03-05 17:24:03 -0500179class MarkImageToBeSignedTest(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400180 """Tests for MarkImageToBeSigned()"""
181
182 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400183 # Minor optimization -- we call this for logging purposes in the main
184 # code, but don't really care about it for testing. It just slows us.
Mike Frysinger6430d132014-10-27 23:43:30 -0400185 self.PatchObject(cros_build_lib, 'MachineDetails', return_value='1234\n')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400186
187 def testBasic(self):
188 """Simple smoke test"""
189 tbs_base = 'gs://some-bucket'
190 insns_path = 'chan/board/ver/file.instructions'
191 tbs_file = '%s/tobesigned/90,chan,board,ver,file.instructions' % tbs_base
192 ret = pushimage.MarkImageToBeSigned(self.ctx, tbs_base, insns_path, 90)
193 self.assertEqual(ret, tbs_file)
194
195 def testPriority(self):
196 """Verify diff priority values get used correctly"""
197 for prio, sprio in ((0, '00'), (9, '09'), (35, '35'), (99, '99')):
198 ret = pushimage.MarkImageToBeSigned(self.ctx, '', '', prio)
Mike Frysinger2d589a12019-08-25 14:15:12 -0400199 self.assertEqual(ret, '/tobesigned/%s,' % sprio)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400200
201 def testBadPriority(self):
202 """Verify we reject bad priority values"""
203 for prio in (-10, -1, 100, 91239):
204 self.assertRaises(ValueError, pushimage.MarkImageToBeSigned, self.ctx,
205 '', '', prio)
206
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400207 def testTbsUpload(self):
208 """Make sure we actually try to upload the file"""
209 pushimage.MarkImageToBeSigned(self.ctx, '', '', 50)
210 self.gs_mock.assertCommandContains(['cp', '--'])
211
212
Mike Frysinger4495b032014-03-05 17:24:03 -0500213class PushImageTests(gs_unittest.AbstractGSContextTest):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400214 """Tests for PushImage()"""
215
216 def setUp(self):
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400217 self.mark_mock = self.PatchObject(pushimage, 'MarkImageToBeSigned')
218
219 def testBasic(self):
220 """Simple smoke test"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800221 EXPECTED = {
222 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500223 ('gs://chromeos-releases/canary-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 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500226 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
227 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800228 }
Don Garrett9fd20a82014-09-04 11:37:22 -0700229 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
230 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
231 profile='hi')
Don Garrett9459c2f2014-01-22 18:20:24 -0800232
233 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400234
Amey Deshpande3c487952015-09-03 12:11:13 -0700235 def testBasic_SignTypesEmptyList(self):
236 """Tests PushImage behavior when |sign_types| is empty instead of None.
237
238 As part of the buildbots, PushImage function always receives a tuple for
239 |sign_types| argument. This test checks the behavior for empty tuple.
240 """
241 EXPECTED = {
242 'canary': [
243 ('gs://chromeos-releases/canary-channel/test.board-hi/5126.0.0/'
244 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
245 'dev': [
246 ('gs://chromeos-releases/dev-channel/test.board-hi/5126.0.0/'
247 'ChromeOS-recovery-R34-5126.0.0-test.board-hi.instructions')],
248 }
249 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
250 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
251 profile='hi', sign_types=())
252
253 self.assertEqual(urls, EXPECTED)
254
Amey Deshpandea936c622015-08-12 17:27:54 -0700255 def testBasic_RealBoardName(self):
256 """Runs a simple smoke test using a real board name."""
257 EXPECTED = {
258 'canary': [
259 ('gs://chromeos-releases/canary-channel/x86-alex/5126.0.0/'
260 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
261 'dev': [
262 ('gs://chromeos-releases/dev-channel/x86-alex/5126.0.0/'
263 'ChromeOS-recovery-R34-5126.0.0-x86-alex.instructions')],
264 }
265 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
266 urls = pushimage.PushImage('/src', 'x86-alex', 'R34-5126.0.0')
267
268 self.assertEqual(urls, EXPECTED)
269
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400270 def testBasicMock(self):
271 """Simple smoke test in mock mode"""
Don Garrett9fd20a82014-09-04 11:37:22 -0700272 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
273 pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
274 dry_run=True, mock=True)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400275
276 def testBadVersion(self):
277 """Make sure we barf on bad version strings"""
278 self.assertRaises(ValueError, pushimage.PushImage, '', '', 'asdf')
279
280 def testNoInsns(self):
281 """Boards w/out insn files should get skipped"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800282 urls = pushimage.PushImage('/src', 'a bad bad board', 'R34-5126.0.0')
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400283 self.assertEqual(self.gs_mock.call_count, 0)
Don Garrett9459c2f2014-01-22 18:20:24 -0800284 self.assertEqual(urls, None)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400285
286 def testSignTypesRecovery(self):
287 """Only sign the requested recovery type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800288 EXPECTED = {
289 'canary': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500290 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
291 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800292 'dev': [
Mike Frysingerd6e2df02014-11-26 02:55:04 -0500293 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
294 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions')],
Don Garrett9459c2f2014-01-22 18:20:24 -0800295 }
296
Amey Deshpandea936c622015-08-12 17:27:54 -0700297 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
298 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
299 sign_types=['recovery'])
Mike Frysingerba0609c2020-07-21 05:19:06 -0400300 self.assertEqual(self.gs_mock.call_count, 30)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400301 self.assertTrue(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800302 self.assertEqual(urls, EXPECTED)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400303
Amey Deshpandea936c622015-08-12 17:27:54 -0700304 def testSignTypesBase(self):
305 """Only sign the requested recovery type"""
306 EXPECTED = {
307 'canary': [
308 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
309 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
310 'dev': [
311 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
312 'ChromeOS-base-R34-5126.0.0-test.board.instructions')],
313 }
314
315 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
316 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
317 sign_types=['base'])
Mike Frysingerba0609c2020-07-21 05:19:06 -0400318 self.assertEqual(self.gs_mock.call_count, 32)
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600319 self.assertTrue(self.mark_mock.called)
320 self.assertEqual(urls, EXPECTED)
321
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800322 def testSignTypesGscFirmware(self):
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600323 """Only sign the requested type"""
324 EXPECTED = {
325 'canary': [
326 ('gs://chromeos-releases/canary-channel/board2/5126.0.0/'
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800327 'ChromeOS-gsc_firmware-R34-5126.0.0-board2.instructions')],
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600328 'dev': [
329 ('gs://chromeos-releases/dev-channel/board2/5126.0.0/'
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800330 'ChromeOS-gsc_firmware-R34-5126.0.0-board2.instructions')],
LaMont Jones7d6c98f2019-09-27 12:37:33 -0600331 }
332
333 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
334 urls = pushimage.PushImage('/src', 'board2', 'R34-5126.0.0',
Vadim Bendeburyfe37f282020-11-04 19:05:49 -0800335 sign_types=['gsc_firmware'])
Mike Frysingerba0609c2020-07-21 05:19:06 -0400336 self.assertEqual(self.gs_mock.call_count, 30)
Amey Deshpandea936c622015-08-12 17:27:54 -0700337 self.assertTrue(self.mark_mock.called)
338 self.assertEqual(urls, EXPECTED)
339
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400340 def testSignTypesNone(self):
341 """Verify nothing is signed when we request an unavailable type"""
Don Garrett9459c2f2014-01-22 18:20:24 -0800342 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
343 sign_types=['nononononono'])
Mike Frysingerba0609c2020-07-21 05:19:06 -0400344 self.assertEqual(self.gs_mock.call_count, 28)
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400345 self.assertFalse(self.mark_mock.called)
Don Garrett9459c2f2014-01-22 18:20:24 -0800346 self.assertEqual(urls, {})
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400347
Mike Frysinger4495b032014-03-05 17:24:03 -0500348 def testGsError(self):
349 """Verify random GS errors don't make us blow up entirely"""
350 self.gs_mock.AddCmdResult(partial_mock.In('stat'), returncode=1,
351 output='gobblety gook\n')
352 with cros_test_lib.LoggingCapturer('chromite'):
353 self.assertRaises(pushimage.PushError, pushimage.PushImage, '/src',
354 'test.board', 'R34-5126.0.0')
355
Mike Frysingerd84d91e2015-11-05 18:02:24 -0500356 def testMultipleKeysets(self):
357 """Verify behavior when processing an insn w/multiple keysets"""
358 EXPECTED = {
359 'canary': [
360 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
361 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
362 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
363 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
364 ('gs://chromeos-releases/canary-channel/test.board/5126.0.0/'
365 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
366 ],
367 'dev': [
368 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
369 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
370 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
371 'ChromeOS-recovery-R34-5126.0.0-test.board-key2.instructions'),
372 ('gs://chromeos-releases/dev-channel/test.board/5126.0.0/'
373 'ChromeOS-recovery-R34-5126.0.0-test.board-key3.instructions'),
374 ],
375 }
376 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
377 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
378 force_keysets=('key1', 'key2', 'key3'))
379 self.assertEqual(urls, EXPECTED)
380
Mike Frysinger77912102017-08-30 18:35:46 -0400381 def testForceChannel(self):
382 """Verify behavior when user has specified custom channel"""
383 EXPECTED = {
384 'meep': [
385 ('gs://chromeos-releases/meep-channel/test.board/5126.0.0/'
386 'ChromeOS-recovery-R34-5126.0.0-test.board.instructions'),
387 ],
388 }
389 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
390 urls = pushimage.PushImage('/src', 'test.board', 'R34-5126.0.0',
391 force_channels=('meep',))
392 self.assertEqual(urls, EXPECTED)
393
Mike Frysinger37ccc2b2015-11-11 17:16:51 -0500394 def testMultipleAltInsns(self):
395 """Verify behavior when processing an insn w/multiple insn overlays"""
396 EXPECTED = {
397 'canary': [
398 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
399 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
400 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
401 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
402 ('gs://chromeos-releases/canary-channel/test.multi/1.0.0/'
403 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
404 ],
405 'dev': [
406 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
407 'ChromeOS-recovery-R1-1.0.0-test.multi.instructions'),
408 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
409 'ChromeOS-recovery-R1-1.0.0-test.multi-TwoKeyset.instructions'),
410 ('gs://chromeos-releases/dev-channel/test.multi/1.0.0/'
411 'ChromeOS-recovery-R1-1.0.0-test.multi-ColdKeyset.instructions'),
412 ],
413 }
414 with mock.patch.object(gs.GSContext, 'Exists', return_value=True):
415 urls = pushimage.PushImage('/src', 'test.multi', 'R1-1.0.0')
416 self.assertEqual(urls, EXPECTED)
417
Mike Frysingerd13faeb2013-09-05 16:00:46 -0400418
419class MainTests(cros_test_lib.MockTestCase):
420 """Tests for main()"""
421
422 def setUp(self):
423 self.PatchObject(pushimage, 'PushImage')
424
425 def testBasic(self):
426 """Simple smoke test"""
Mike Frysinger09fe0122014-02-09 02:44:05 -0500427 pushimage.main(['--board', 'test.board', '/src', '--yes'])