#!/usr/bin/env vpython3 # Copyright 2022 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import io import json import os import sys import unittest from unittest import mock import update_product_bundles sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'test'))) import common import compatible_utils class TestUpdateProductBundles(unittest.TestCase): def setUp(self): # By default, test in attended mode. compatible_utils.force_running_attended() ffx_mock = mock.Mock() ffx_mock.returncode = 0 self._ffx_patcher = mock.patch('common.run_ffx_command', return_value=ffx_mock) self._ffx_mock = self._ffx_patcher.start() self.addCleanup(self._ffx_mock.stop) def testConvertToProductBundleDefaultsUnknownImage(self): self.assertEqual( update_product_bundles.convert_to_products(['unknown-image']), ['unknown-image']) def testConvertToProductBundleRemovesReleaseSuffix(self): self.assertEqual( update_product_bundles.convert_to_products( ['smart_display_eng.astro-release']), ['smart_display_eng.astro']) def testConvertToProductBundleWarnsDeprecated(self): with self.assertLogs(level='WARNING') as logs: deprecated_images = [ 'qemu.arm64', 'qemu.x64', 'core.x64-dfv2-release', 'workstation_eng.chromebook-x64-release' ] self.assertEqual( update_product_bundles.convert_to_products(deprecated_images), [ 'terminal.qemu-arm64', 'terminal.x64', 'core.x64-dfv2', 'workstation_eng.chromebook-x64' ]) for i, deprecated_image in enumerate(deprecated_images): self.assertIn(f'Image name {deprecated_image} has been deprecated', logs.output[i]) @mock.patch('common.run_ffx_command') def testRemoveRepositoriesRunsRemoveOnGivenRepos(self, ffx_mock): update_product_bundles.remove_repositories(['foo', 'bar', 'fizz', 'buzz']) ffx_mock.assert_has_calls([ mock.call(cmd=('repository', 'remove', 'foo')), mock.call(cmd=('repository', 'remove', 'bar')), mock.call(cmd=('repository', 'remove', 'fizz')), mock.call(cmd=('repository', 'remove', 'buzz')), ]) @mock.patch('os.path.exists') @mock.patch('os.path.abspath') def testGetRepositoriesPrunesReposThatDoNotExist(self, mock_abspath, mock_exists): with mock.patch('common.SDK_ROOT', 'some/path'): self._ffx_mock.return_value.stdout = json.dumps([{ "name": "terminal.x64", "spec": { "type": "pm", "path": "some/path/that/exists" } }, { "name": "workstation-eng.chromebook-x64", "spec": { "type": "pm", "path": "some/path/that/does/not/exist" } }]) mock_exists.side_effect = [True, False] mock_abspath.side_effect = lambda x: x self.assertEqual(update_product_bundles.get_repositories(), [{ "name": "terminal.x64", "spec": { "type": "pm", "path": "some/path/that/exists" } }]) self._ffx_mock.assert_has_calls([ mock.call(cmd=('--machine', 'json', 'repository', 'list'), capture_output=True), mock.call(cmd=('repository', 'remove', 'workstation-eng.chromebook-x64')) ]) @mock.patch('common.make_clean_directory') @mock.patch('update_product_bundles.running_unattended', return_value=True) # Disallow reading sdk_override. @mock.patch('os.path.isfile', return_value=False) @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') def testLookupAndDownloadWithAuth(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return auth_file = os.path.abspath( os.path.join(os.path.dirname(__file__), 'get_auth_token.py')) self._ffx_mock.return_value.stdout = json.dumps({ "name": "core.x64", "product_version": "17.20240106.2.1", "transfer_manifest_url": "http://download-url" }) with mock.patch( 'sys.argv', ['update_product_bundles.py', 'terminal.x64', '--internal']): update_product_bundles.main() self._ffx_mock.assert_has_calls([ mock.call(cmd=[ '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', '--base-url', f'gs://fuchsia-sdk/development/1.1.1', '--auth', auth_file ], capture_output=True), mock.call(cmd=[ 'product', 'download', 'http://download-url', os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64'), '--auth', auth_file ]) ]) @mock.patch('common.make_clean_directory') @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') @mock.patch('update_product_bundles.get_current_signature', return_value='2.2.2') @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value=None) def testIgnoreDownloadImagesWithSameHash(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): update_product_bundles.main() self.assertFalse(self._ffx_mock.called) @mock.patch('common.make_clean_directory') @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') @mock.patch('update_product_bundles.get_current_signature', return_value='0.0') @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value=None) def testDownloadImagesWithDifferentHash(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return self._ffx_mock.return_value.stdout = json.dumps({ "name": "core.x64", "product_version": "17.20240106.2.1", "transfer_manifest_url": "http://download-url" }) with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): update_product_bundles.main() self._ffx_mock.assert_has_calls([ mock.call(cmd=[ '--machine', 'json', 'product', 'lookup', 'terminal.x64', '2.2.2', '--base-url', 'gs://fuchsia/development/2.2.2' ], capture_output=True), mock.call(cmd=[ 'product', 'download', 'http://download-url', os.path.join(common.IMAGES_ROOT, 'terminal', 'x64') ]) ]) @mock.patch('common.make_clean_directory') @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value='gs://my-bucket/sdk') @mock.patch('common.get_hash_from_sdk', return_value='2.2.2') def testSDKOverrideForSDKImages(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return self._ffx_mock.return_value.stdout = json.dumps({ "name": "core.x64", "product_version": "17.20240106.2.1", "transfer_manifest_url": "http://download-url" }) with mock.patch('sys.argv', ['update_product_bundles.py', 'terminal.x64']): update_product_bundles.main() self._ffx_mock.assert_has_calls([ mock.call(cmd=[ '--machine', 'json', 'product', 'lookup', 'terminal.x64', '2.2.2', '--base-url', 'gs://my-bucket' ], capture_output=True), mock.call(cmd=[ 'product', 'download', 'http://download-url', os.path.join(common.IMAGES_ROOT, 'terminal', 'x64') ]) ]) @mock.patch('common.make_clean_directory') @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') @mock.patch('update_product_bundles.get_current_signature', return_value='1.1.1') def testIgnoreDownloadInternalImagesWithSameHash(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return with mock.patch( 'sys.argv', ['update_product_bundles.py', 'terminal.x64', '--internal']): update_product_bundles.main() self.assertFalse(self._ffx_mock.called) @mock.patch('common.make_clean_directory') @mock.patch('update_product_bundles.get_current_signature', return_value='0.0') @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') def testDownloadInternalImagesWithDifferentHash(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return self._ffx_mock.return_value.stdout = json.dumps({ "name": "core.x64", "product_version": "17.20240106.2.1", "transfer_manifest_url": "http://download-url" }) with mock.patch( 'sys.argv', ['update_product_bundles.py', 'terminal.x64', '--internal']): update_product_bundles.main() self._ffx_mock.assert_has_calls([ mock.call(cmd=[ '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', '--base-url', 'gs://fuchsia-sdk/development/1.1.1' ], capture_output=True), mock.call(cmd=[ 'product', 'download', 'http://download-url', os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64') ]) ]) @mock.patch('common.make_clean_directory') @mock.patch('update_sdk.GetSDKOverrideGCSPath', return_value='gs://my-bucket/sdk') @mock.patch('update_product_bundles.internal_hash', return_value='1.1.1') def testIgnoreSDKOverrideForInternalImages(self, *_): try: common.get_host_os() except: # Ignore unsupported platforms. common.get_host_os used in # update_product_bundles.main throws an unsupported exception. return self._ffx_mock.return_value.stdout = json.dumps({ "name": "core.x64", "product_version": "17.20240106.2.1", "transfer_manifest_url": "http://download-url" }) with mock.patch( 'sys.argv', ['update_product_bundles.py', 'terminal.x64', '--internal']): update_product_bundles.main() self._ffx_mock.assert_has_calls([ mock.call(cmd=[ '--machine', 'json', 'product', 'lookup', 'terminal.x64', '1.1.1', '--base-url', 'gs://fuchsia-sdk/development/1.1.1' ], capture_output=True), mock.call(cmd=[ 'product', 'download', 'http://download-url', os.path.join(common.INTERNAL_IMAGES_ROOT, 'terminal', 'x64') ]) ]) if __name__ == '__main__': unittest.main()