• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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