1#!/usr/bin/env vpython3 2# Copyright 2022 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import io 7import json 8import os 9import sys 10import unittest 11from unittest import mock 12 13import update_product_bundles 14 15sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 16 'test'))) 17 18import common 19import compatible_utils 20 21 22class TestUpdateProductBundles(unittest.TestCase): 23 def setUp(self): 24 # By default, test in attended mode. 25 compatible_utils.force_running_attended() 26 ffx_mock = mock.Mock() 27 ffx_mock.returncode = 0 28 self._ffx_patcher = mock.patch('common.run_ffx_command', 29 return_value=ffx_mock) 30 self._ffx_mock = self._ffx_patcher.start() 31 self.addCleanup(self._ffx_mock.stop) 32 33 def testConvertToProductBundleDefaultsUnknownImage(self): 34 self.assertEqual( 35 update_product_bundles.convert_to_products(['unknown-image']), 36 ['unknown-image']) 37 38 def testConvertToProductBundleRemovesReleaseSuffix(self): 39 self.assertEqual( 40 update_product_bundles.convert_to_products( 41 ['smart_display_eng.astro-release']), ['smart_display_eng.astro']) 42 43 def testConvertToProductBundleWarnsDeprecated(self): 44 with self.assertLogs(level='WARNING') as logs: 45 deprecated_images = [ 46 'qemu.arm64', 'qemu.x64', 'core.x64-dfv2-release', 47 'workstation_eng.chromebook-x64-release' 48 ] 49 self.assertEqual( 50 update_product_bundles.convert_to_products(deprecated_images), [ 51 'terminal.qemu-arm64', 'terminal.x64', 'core.x64-dfv2', 52 'workstation_eng.chromebook-x64' 53 ]) 54 for i, deprecated_image in enumerate(deprecated_images): 55 self.assertIn(f'Image name {deprecated_image} has been deprecated', 56 logs.output[i]) 57 58 59 @mock.patch('common.run_ffx_command') 60 def testRemoveRepositoriesRunsRemoveOnGivenRepos(self, ffx_mock): 61 update_product_bundles.remove_repositories(['foo', 'bar', 'fizz', 'buzz']) 62 63 ffx_mock.assert_has_calls([ 64 mock.call(cmd=('repository', 'remove', 'foo')), 65 mock.call(cmd=('repository', 'remove', 'bar')), 66 mock.call(cmd=('repository', 'remove', 'fizz')), 67 mock.call(cmd=('repository', 'remove', 'buzz')), 68 ]) 69 70 @mock.patch('os.path.exists') 71 @mock.patch('os.path.abspath') 72 def testGetRepositoriesPrunesReposThatDoNotExist(self, mock_abspath, 73 mock_exists): 74 with mock.patch('common.SDK_ROOT', 'some/path'): 75 self._ffx_mock.return_value.stdout = json.dumps([{ 76 "name": "terminal.x64", 77 "spec": { 78 "type": "pm", 79 "path": "some/path/that/exists" 80 } 81 }, { 82 "name": "workstation-eng.chromebook-x64", 83 "spec": { 84 "type": "pm", 85 "path": "some/path/that/does/not/exist" 86 } 87 }]) 88 mock_exists.side_effect = [True, False] 89 mock_abspath.side_effect = lambda x: x 90 91 self.assertEqual(update_product_bundles.get_repositories(), [{ 92 "name": "terminal.x64", 93 "spec": { 94 "type": "pm", 95 "path": "some/path/that/exists" 96 } 97 }]) 98 99 self._ffx_mock.assert_has_calls([ 100 mock.call(cmd=('--machine', 'json', 'repository', 'list'), 101 capture_output=True), 102 mock.call(cmd=('repository', 'remove', 103 'workstation-eng.chromebook-x64')) 104 ]) 105 106 @mock.patch('common.make_clean_directory') 107 @mock.patch('update_product_bundles.running_unattended', return_value=True) 108 # Disallow reading sdk_override. 109 @mock.patch('os.path.isfile', return_value=False) 110 @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') 111 def testLookupAndDownloadWithAuth(self, *_): 112 try: 113 common.get_host_os() 114 except: 115 # Ignore unsupported platforms. common.get_host_os used in 116 # update_product_bundles.main throws an unsupported exception. 117 return 118 auth_file = os.path.abspath( 119 os.path.join(os.path.dirname(__file__), 'get_auth_token.py')) 120 self._ffx_mock.return_value.stdout = json.dumps({ 121 "name": "core.x64", 122 "product_version": "17.20240106.2.1", 123 "transfer_manifest_url": "http://download-url" 124 }) 125 126 with mock.patch( 127 'sys.argv', 128 ['update_product_bundles.py', 'terminal.x64', '--internal']): 129 update_product_bundles.main() 130 self._ffx_mock.assert_has_calls([ 131 mock.call(cmd=[ 132 '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', 133 '--base-url', f'gs://fuchsia-sdk/development/1.1.1', '--auth', 134 auth_file 135 ], 136 capture_output=True), 137 mock.call(cmd=[ 138 'product', 'download', 'http://download-url', 139 os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64'), 140 '--auth', auth_file 141 ]) 142 ]) 143 144 @mock.patch('common.make_clean_directory') 145 @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') 146 @mock.patch('update_product_bundles.get_current_signature', 147 return_value='2.2.2') 148 @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value=None) 149 def testIgnoreDownloadImagesWithSameHash(self, *_): 150 try: 151 common.get_host_os() 152 except: 153 # Ignore unsupported platforms. common.get_host_os used in 154 # update_product_bundles.main throws an unsupported exception. 155 return 156 with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): 157 update_product_bundles.main() 158 self.assertFalse(self._ffx_mock.called) 159 160 @mock.patch('common.make_clean_directory') 161 @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') 162 @mock.patch('update_product_bundles.get_current_signature', 163 return_value='0.0') 164 @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value=None) 165 def testDownloadImagesWithDifferentHash(self, *_): 166 try: 167 common.get_host_os() 168 except: 169 # Ignore unsupported platforms. common.get_host_os used in 170 # update_product_bundles.main throws an unsupported exception. 171 return 172 self._ffx_mock.return_value.stdout = json.dumps({ 173 "name": 174 "core.x64", 175 "product_version": 176 "17.20240106.2.1", 177 "transfer_manifest_url": 178 "http://download-url" 179 }) 180 with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): 181 update_product_bundles.main() 182 self._ffx_mock.assert_has_calls([ 183 mock.call(cmd=[ 184 '--machine', 'json', 'product', 'lookup', 'terminal.x64', '2.2.2', 185 '--base-url', 'gs://fuchsia/development/2.2.2' 186 ], 187 capture_output=True), 188 mock.call(cmd=[ 189 'product', 'download', 'http://download-url', 190 os.path.join(common.IMAGES_ROOT, 'terminal', 'x64') 191 ]) 192 ]) 193 194 @mock.patch('common.make_clean_directory') 195 @mock.patch('update_sdk.GetSDKOverrideGCSPath', 196 return_value='gs://my-bucket/sdk') 197 @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') 198 def testSDKOverrideForSDKImages(self, *_): 199 try: 200 common.get_host_os() 201 except: 202 # Ignore unsupported platforms. common.get_host_os used in 203 # update_product_bundles.main throws an unsupported exception. 204 return 205 self._ffx_mock.return_value.stdout = json.dumps({ 206 "name": 207 "core.x64", 208 "product_version": 209 "17.20240106.2.1", 210 "transfer_manifest_url": 211 "http://download-url" 212 }) 213 with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): 214 update_product_bundles.main() 215 self._ffx_mock.assert_has_calls([ 216 mock.call(cmd=[ 217 '--machine', 'json', 'product', 'lookup', 'terminal.x64', '2.2.2', 218 '--base-url', 'gs://my-bucket' 219 ], 220 capture_output=True), 221 mock.call(cmd=[ 222 'product', 'download', 'http://download-url', 223 os.path.join(common.IMAGES_ROOT, 'terminal', 'x64') 224 ]) 225 ]) 226 227 @mock.patch('common.make_clean_directory') 228 @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') 229 @mock.patch('update_product_bundles.get_current_signature', 230 return_value='1.1.1') 231 def testIgnoreDownloadInternalImagesWithSameHash(self, *_): 232 try: 233 common.get_host_os() 234 except: 235 # Ignore unsupported platforms. common.get_host_os used in 236 # update_product_bundles.main throws an unsupported exception. 237 return 238 with mock.patch( 239 'sys.argv', 240 ['update_product_bundles.py', 'terminal.x64', '--internal']): 241 update_product_bundles.main() 242 self.assertFalse(self._ffx_mock.called) 243 244 @mock.patch('common.make_clean_directory') 245 @mock.patch('update_product_bundles.get_current_signature', 246 return_value='0.0') 247 @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') 248 def testDownloadInternalImagesWithDifferentHash(self, *_): 249 try: 250 common.get_host_os() 251 except: 252 # Ignore unsupported platforms. common.get_host_os used in 253 # update_product_bundles.main throws an unsupported exception. 254 return 255 self._ffx_mock.return_value.stdout = json.dumps({ 256 "name": 257 "core.x64", 258 "product_version": 259 "17.20240106.2.1", 260 "transfer_manifest_url": 261 "http://download-url" 262 }) 263 with mock.patch( 264 'sys.argv', 265 ['update_product_bundles.py', 'terminal.x64', '--internal']): 266 update_product_bundles.main() 267 self._ffx_mock.assert_has_calls([ 268 mock.call(cmd=[ 269 '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', 270 '--base-url', 'gs://fuchsia-sdk/development/1.1.1' 271 ], 272 capture_output=True), 273 mock.call(cmd=[ 274 'product', 'download', 'http://download-url', 275 os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64') 276 ]) 277 ]) 278 279 @mock.patch('common.make_clean_directory') 280 @mock.patch('update_sdk.GetSDKOverrideGCSPath', 281 return_value='gs://my-bucket/sdk') 282 @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') 283 def testIgnoreSDKOverrideForInternalImages(self, *_): 284 try: 285 common.get_host_os() 286 except: 287 # Ignore unsupported platforms. common.get_host_os used in 288 # update_product_bundles.main throws an unsupported exception. 289 return 290 self._ffx_mock.return_value.stdout = json.dumps({ 291 "name": 292 "core.x64", 293 "product_version": 294 "17.20240106.2.1", 295 "transfer_manifest_url": 296 "http://download-url" 297 }) 298 with mock.patch( 299 'sys.argv', 300 ['update_product_bundles.py', 'terminal.x64', '--internal']): 301 update_product_bundles.main() 302 self._ffx_mock.assert_has_calls([ 303 mock.call(cmd=[ 304 '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', 305 '--base-url', 'gs://fuchsia-sdk/development/1.1.1' 306 ], 307 capture_output=True), 308 mock.call(cmd=[ 309 'product', 'download', 'http://download-url', 310 os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64') 311 ]) 312 ]) 313 314 315if __name__ == '__main__': 316 unittest.main() 317