blob: 0f9414348966b6f5ef69f44ef883ed4bb9f672ba [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
David Pursellf1d16a62015-03-25 13:31:04 -07002# Copyright 2015 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"""Unit tests for the flash module."""
7
8from __future__ import print_function
9
David Pursellf1d16a62015-03-25 13:31:04 -070010import os
Mike Frysinger3f087aa2020-03-20 06:03:16 -040011import sys
David Pursellf1d16a62015-03-25 13:31:04 -070012
13from chromite.cli import flash
David Pursellf1d16a62015-03-25 13:31:04 -070014from chromite.lib import commandline
15from chromite.lib import cros_build_lib
Ralph Nathan9b997232015-05-15 13:13:12 -070016from chromite.lib import cros_logging as logging
David Pursellf1d16a62015-03-25 13:31:04 -070017from chromite.lib import cros_test_lib
18from chromite.lib import dev_server_wrapper
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070019from chromite.lib import osutils
David Pursellf1d16a62015-03-25 13:31:04 -070020from chromite.lib import partial_mock
Mike Frysinger40ffb532021-02-12 07:36:08 -050021from chromite.third_party import mock
Amin Hassanic0f06fa2019-01-28 15:24:47 -080022
David Pursellf1d16a62015-03-25 13:31:04 -070023
Mike Frysinger3f087aa2020-03-20 06:03:16 -040024assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
25
26
David Pursellf1d16a62015-03-25 13:31:04 -070027class USBImagerMock(partial_mock.PartialCmdMock):
28 """Mock out USBImager."""
29 TARGET = 'chromite.cli.flash.USBImager'
Amin Hassani04314b12020-12-15 15:59:54 -080030 ATTRS = ('CopyImageToDevice', 'ChooseRemovableDevice',
31 'ListAllRemovableDevices', 'GetRemovableDeviceDescription')
David Pursellf1d16a62015-03-25 13:31:04 -070032 VALID_IMAGE = True
33
34 def __init__(self):
35 partial_mock.PartialCmdMock.__init__(self)
36
37 def CopyImageToDevice(self, _inst, *_args, **_kwargs):
38 """Mock out CopyImageToDevice."""
39
David Pursellf1d16a62015-03-25 13:31:04 -070040 def ChooseRemovableDevice(self, _inst, *_args, **_kwargs):
41 """Mock out ChooseRemovableDevice."""
42
43 def ListAllRemovableDevices(self, _inst, *_args, **_kwargs):
44 """Mock out ListAllRemovableDevices."""
45 return ['foo', 'taco', 'milk']
46
47 def GetRemovableDeviceDescription(self, _inst, *_args, **_kwargs):
48 """Mock out GetRemovableDeviceDescription."""
49
David Pursellf1d16a62015-03-25 13:31:04 -070050
51class USBImagerTest(cros_test_lib.MockTempDirTestCase):
52 """Test the flow of flash.Flash() with USBImager."""
53 IMAGE = '/path/to/image'
54
55 def Device(self, path):
56 """Create a USB device for passing to flash.Flash()."""
57 return commandline.Device(scheme=commandline.DEVICE_SCHEME_USB,
58 path=path)
59
60 def setUp(self):
61 """Patches objects."""
62 self.usb_mock = USBImagerMock()
63 self.imager_mock = self.StartPatcher(self.usb_mock)
David Pursellf1d16a62015-03-25 13:31:04 -070064 self.PatchObject(dev_server_wrapper, 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070065 return_value=('taco-paladin/R36/chromiumos_test_image.bin',
66 'remote/taco-paladin/R36/test'))
David Pursellf1d16a62015-03-25 13:31:04 -070067 self.PatchObject(os.path, 'exists', return_value=True)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070068 self.isgpt_mock = self.PatchObject(flash, '_IsFilePathGPTDiskImage',
69 return_value=True)
David Pursellf1d16a62015-03-25 13:31:04 -070070
71 def testLocalImagePathCopy(self):
72 """Tests that imaging methods are called correctly."""
73 with mock.patch('os.path.isfile', return_value=True):
74 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
75 self.assertTrue(self.imager_mock.patched['CopyImageToDevice'].called)
76
David Pursellf1d16a62015-03-25 13:31:04 -070077 def testLocalBadImagePath(self):
78 """Tests that using an image not having the magic bytes has prompt."""
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -070079 self.isgpt_mock.return_value = False
David Pursellf1d16a62015-03-25 13:31:04 -070080 with mock.patch('os.path.isfile', return_value=True):
81 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
82 mock_prompt.return_value = False
83 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
84 self.assertTrue(mock_prompt.called)
85
86 def testNonLocalImagePath(self):
87 """Tests that we try to get the image path using xbuddy."""
88 with mock.patch.object(
89 dev_server_wrapper,
90 'GetImagePathWithXbuddy',
Gilad Arnolde62ec902015-04-24 14:41:02 -070091 return_value=('translated/xbuddy/path',
92 'resolved/xbuddy/path')) as mock_xbuddy:
David Pursellf1d16a62015-03-25 13:31:04 -070093 with mock.patch('os.path.isfile', return_value=False):
94 with mock.patch('os.path.isdir', return_value=False):
95 flash.Flash(self.Device('/dev/foo'), self.IMAGE)
96 self.assertTrue(mock_xbuddy.called)
97
98 def testConfirmNonRemovableDevice(self):
99 """Tests that we ask user to confirm if the device is not removable."""
100 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
101 flash.Flash(self.Device('/dev/dummy'), self.IMAGE)
102 self.assertTrue(mock_prompt.called)
103
104 def testSkipPromptNonRemovableDevice(self):
105 """Tests that we skip the prompt for non-removable with --yes."""
106 with mock.patch.object(cros_build_lib, 'BooleanPrompt') as mock_prompt:
107 flash.Flash(self.Device('/dev/dummy'), self.IMAGE, yes=True)
108 self.assertFalse(mock_prompt.called)
109
110 def testChooseRemovableDevice(self):
111 """Tests that we ask user to choose a device if none is given."""
112 flash.Flash(self.Device(''), self.IMAGE)
113 self.assertTrue(self.imager_mock.patched['ChooseRemovableDevice'].called)
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700114
115
Benjamin Gordon121a2aa2018-05-04 16:24:45 -0600116class UsbImagerOperationTest(cros_test_lib.RunCommandTestCase):
Ralph Nathan9b997232015-05-15 13:13:12 -0700117 """Tests for flash.UsbImagerOperation."""
118 # pylint: disable=protected-access
119
120 def setUp(self):
121 self.PatchObject(flash.UsbImagerOperation, '__init__', return_value=None)
122
123 def testUsbImagerOperationCalled(self):
124 """Test that flash.UsbImagerOperation is called when log level <= NOTICE."""
125 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800126 'oflag=direct', 'conv=fdatasync']
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000127 usb_imager = flash.USBImager('dummy_device', 'board', 'foo', 'latest')
Ralph Nathan9b997232015-05-15 13:13:12 -0700128 run_mock = self.PatchObject(flash.UsbImagerOperation, 'Run')
129 self.PatchObject(logging.Logger, 'getEffectiveLevel',
130 return_value=logging.NOTICE)
131 usb_imager.CopyImageToDevice('foo', 'bar')
132
133 # Check that flash.UsbImagerOperation.Run() is called correctly.
Mike Frysinger45602c72019-09-22 02:15:11 -0400134 run_mock.assert_called_with(cros_build_lib.sudo_run, expected_cmd,
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400135 debug_level=logging.NOTICE, encoding='utf-8',
136 update_period=0.5)
Ralph Nathan9b997232015-05-15 13:13:12 -0700137
138 def testSudoRunCommandCalled(self):
Mike Frysinger45602c72019-09-22 02:15:11 -0400139 """Test that sudo_run is called when log level > NOTICE."""
Ralph Nathan9b997232015-05-15 13:13:12 -0700140 expected_cmd = ['dd', 'if=foo', 'of=bar', 'bs=4M', 'iflag=fullblock',
Frank Huang8e626432019-06-24 19:51:08 +0800141 'oflag=direct', 'conv=fdatasync']
Achuith Bhandarkaree1336f2020-04-18 11:44:09 +0000142 usb_imager = flash.USBImager('dummy_device', 'board', 'foo', 'latest')
Mike Frysinger45602c72019-09-22 02:15:11 -0400143 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700144 self.PatchObject(logging.Logger, 'getEffectiveLevel',
145 return_value=logging.WARNING)
146 usb_imager.CopyImageToDevice('foo', 'bar')
147
Mike Frysinger45602c72019-09-22 02:15:11 -0400148 # Check that sudo_run() is called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700149 run_mock.assert_any_call(expected_cmd, debug_level=logging.NOTICE,
150 print_cmd=False)
151
152 def testPingDD(self):
153 """Test that UsbImagerOperation._PingDD() sends the correct signal."""
154 expected_cmd = ['kill', '-USR1', '5']
Mike Frysinger45602c72019-09-22 02:15:11 -0400155 run_mock = self.PatchObject(cros_build_lib, 'sudo_run')
Ralph Nathan9b997232015-05-15 13:13:12 -0700156 op = flash.UsbImagerOperation('foo')
157 op._PingDD(5)
158
Mike Frysinger45602c72019-09-22 02:15:11 -0400159 # Check that sudo_run was called correctly.
Ralph Nathan9b997232015-05-15 13:13:12 -0700160 run_mock.assert_called_with(expected_cmd, print_cmd=False)
161
162 def testGetDDPidFound(self):
163 """Check that the expected pid is returned for _GetDDPid()."""
164 expected_pid = 5
165 op = flash.UsbImagerOperation('foo')
Mike Nicholsa1414162021-04-22 20:07:22 +0000166 self.PatchObject(osutils, 'IsChildProcess', return_value=True)
Ralph Nathan9b997232015-05-15 13:13:12 -0700167 self.rc.AddCmdResult(partial_mock.Ignore(),
168 output='%d\n10\n' % expected_pid)
169
170 pid = op._GetDDPid()
171
172 # Check that the correct pid was returned.
173 self.assertEqual(pid, expected_pid)
174
175 def testGetDDPidNotFound(self):
176 """Check that -1 is returned for _GetDDPid() if the pids aren't valid."""
177 expected_pid = -1
178 op = flash.UsbImagerOperation('foo')
Mike Nicholsa1414162021-04-22 20:07:22 +0000179 self.PatchObject(osutils, 'IsChildProcess', return_value=False)
Ralph Nathan9b997232015-05-15 13:13:12 -0700180 self.rc.AddCmdResult(partial_mock.Ignore(), output='5\n10\n')
181
182 pid = op._GetDDPid()
183
184 # Check that the correct pid was returned.
185 self.assertEqual(pid, expected_pid)
186
187
Bertrand SIMONNET56f773d2015-05-04 14:02:39 -0700188class FlashUtilTest(cros_test_lib.MockTempDirTestCase):
189 """Tests the helpers from cli.flash."""
190
191 def testChooseImage(self):
192 """Tests that we can detect a GPT image."""
193 # pylint: disable=protected-access
194
195 with self.PatchObject(flash, '_IsFilePathGPTDiskImage', return_value=True):
196 # No images defined. Choosing the image should raise an error.
197 with self.assertRaises(ValueError):
198 flash._ChooseImageFromDirectory(self.tempdir)
199
200 file_a = os.path.join(self.tempdir, 'a')
201 osutils.Touch(file_a)
202 # Only one image available, it should be selected automatically.
203 self.assertEqual(file_a, flash._ChooseImageFromDirectory(self.tempdir))
204
205 osutils.Touch(os.path.join(self.tempdir, 'b'))
206 file_c = os.path.join(self.tempdir, 'c')
207 osutils.Touch(file_c)
208 osutils.Touch(os.path.join(self.tempdir, 'd'))
209
210 # Multiple images available, we should ask the user to select the right
211 # image.
212 with self.PatchObject(cros_build_lib, 'GetChoice', return_value=2):
213 self.assertEqual(file_c, flash._ChooseImageFromDirectory(self.tempdir))
Mike Frysinger32759e42016-12-21 18:40:16 -0500214
215 def testIsFilePathGPTDiskImage(self):
216 """Tests the GPT image probing."""
217 # pylint: disable=protected-access
218
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400219 INVALID_PMBR = b' ' * 0x200
220 INVALID_GPT = b' ' * 0x200
221 VALID_PMBR = (b' ' * 0x1fe) + b'\x55\xaa'
222 VALID_GPT = b'EFI PART' + (b' ' * 0x1f8)
Mike Frysinger32759e42016-12-21 18:40:16 -0500223 TESTCASES = (
224 (False, False, INVALID_PMBR + INVALID_GPT),
225 (False, False, VALID_PMBR + INVALID_GPT),
226 (False, True, INVALID_PMBR + VALID_GPT),
227 (True, True, VALID_PMBR + VALID_GPT),
228 )
229
230 img = os.path.join(self.tempdir, 'img.bin')
231 for exp_pmbr_t, exp_pmbr_f, data in TESTCASES:
Mike Frysinger3d5de8f2019-10-23 00:48:39 -0400232 osutils.WriteFile(img, data, mode='wb')
Mike Frysinger32759e42016-12-21 18:40:16 -0500233 self.assertEqual(
234 flash._IsFilePathGPTDiskImage(img, require_pmbr=True), exp_pmbr_t)
235 self.assertEqual(
236 flash._IsFilePathGPTDiskImage(img, require_pmbr=False), exp_pmbr_f)