#!/usr/bin/env vpython3
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unittests for xcode_util.py."""

import logging
import mock
import os
import unittest

import test_runner_errors
import test_runner_test
import xcode_util


_XCODEBUILD_VERSION_OUTPUT_12 = b"""Xcode 12.4
Build version 12D4e
"""
_XCODEBUILD_VERSION_OUTPUT_13 = b"""Xcode 13.0
Build version 13A5155e
"""


class XcodeUtilTest(test_runner_test.TestCase):
  """Test class for xcode_util functions."""

  def setUp(self):
    super(XcodeUtilTest, self).setUp()

  @mock.patch(
      'subprocess.check_output', return_value=_XCODEBUILD_VERSION_OUTPUT_13)
  def test_version(self, _):
    """Tests xcode_util.version()"""
    version, build_version = xcode_util.version()
    self.assertEqual(version, '13.0')
    self.assertEqual(build_version, '13a5155e')

  @mock.patch(
      'subprocess.check_output', return_value=_XCODEBUILD_VERSION_OUTPUT_12)
  def test_using_xcode_12(self, _):
    """Tests xcode_util.using_xcode_11_or_higher"""
    self.assertTrue(xcode_util.using_xcode_11_or_higher())
    self.assertFalse(xcode_util.using_xcode_13_or_higher())

  @mock.patch(
      'subprocess.check_output', return_value=_XCODEBUILD_VERSION_OUTPUT_13)
  def test_using_xcode_13(self, _):
    """Tests xcode_util.using_xcode_13_or_higher"""
    self.assertTrue(xcode_util.using_xcode_11_or_higher())
    self.assertTrue(xcode_util.using_xcode_13_or_higher())


class InstallTest(XcodeUtilTest):
  """Test class for xcode_util.install function."""

  def setUp(self):
    super(InstallTest, self).setUp()
    self.mac_toolchain = 'mac_toolchain'
    self.xcode_build_version = 'TestXcodeVersion'
    self.xcode_app_path = 'test/path/Xcode.app'
    self.runtime_cache_folder = 'test/path/Runtime'
    self.ios_version = '14.4'

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime', autospec=True)
  @mock.patch('xcode_util._install_xcode', autospec=True)
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_legacy_mactoolchain_new_xcode(self, mock_macos_13_or_higher,
                                         mock_install_xcode,
                                         mock_install_runtime,
                                         mock_move_runtime):
    mock_macos_13_or_higher.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: False)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: False)

    with self.assertRaises(test_runner_errors.XcodeMacToolchainMismatchError):
      is_legacy_xcode = xcode_util.install(self.mac_toolchain,
                                           self.xcode_build_version,
                                           self.xcode_app_path)
      self.assertTrue(is_legacy_xcode, 'install should return true')

    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', False)
    self.assertFalse(mock_install_runtime.called,
                     '_install_runtime shouldn\'t be called')
    self.assertFalse(mock_move_runtime.called,
                     'move_runtime shouldn\'t be called')

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime', autospec=True)
  @mock.patch('xcode_util._install_xcode', autospec=True)
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_legacy_mactoolchain_legacy_xcode(self, mock_macos_13_or_higher,
                                            mock_install_xcode,
                                            mock_install_runtime,
                                            mock_move_runtime):
    mock_macos_13_or_higher.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: False)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: True)

    is_legacy_xcode = xcode_util.install(self.mac_toolchain,
                                         self.xcode_build_version,
                                         self.xcode_app_path)

    self.assertTrue(is_legacy_xcode, 'install_should return true')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', False)
    self.assertFalse(mock_install_runtime.called,
                     '_install_runtime shouldn\'t be called')
    self.assertFalse(mock_move_runtime.called,
                     'move_runtime shouldn\'t be called')

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime', autospec=True)
  @mock.patch('xcode_util._install_xcode', autospec=True)
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_new_mactoolchain_legacy_xcode(self, mock_macos_13_or_higher,
                                         mock_install_xcode,
                                         mock_install_runtime,
                                         mock_move_runtime):
    mock_macos_13_or_higher.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: True)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: True)

    is_legacy_xcode = xcode_util.install(self.mac_toolchain,
                                         self.xcode_build_version,
                                         self.xcode_app_path)

    self.assertTrue(is_legacy_xcode, 'install should return true')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', True)
    self.assertFalse(mock_install_runtime.called,
                     '_install_runtime shouldn\'t be called')
    self.assertFalse(mock_move_runtime.called,
                     'move_runtime shouldn\'t be called')

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime')
  @mock.patch('xcode_util._install_xcode')
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_new_mactoolchain_new_xcode(self, mock_macos_13_or_higher,
                                      mock_install_xcode, mock_install_runtime,
                                      mock_move_runtime):
    mock_macos_13_or_higher.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: True)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: False)

    is_legacy_xcode = xcode_util.install(
        self.mac_toolchain,
        self.xcode_build_version,
        self.xcode_app_path,
        runtime_cache_folder=self.runtime_cache_folder,
        ios_version=self.ios_version)

    self.assertFalse(is_legacy_xcode, 'install should return False')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', True)
    mock_install_runtime.assert_called_with('mac_toolchain',
                                            'test/path/Runtime',
                                            'TestXcodeVersion', '14.4')
    mock_move_runtime.assert_called_with('test/path/Runtime',
                                         'test/path/Xcode.app', True)

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime')
  @mock.patch('xcode_util._install_xcode')
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_new_mactoolchain_new_xcode_no_runtime(self, mock_macos_13_or_higher,
                                                 mock_install_xcode,
                                                 mock_install_runtime,
                                                 mock_move_runtime):
    mock_macos_13_or_higher.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: True)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: False)

    is_legacy_xcode = xcode_util.install(
        self.mac_toolchain,
        self.xcode_build_version,
        self.xcode_app_path,
        runtime_cache_folder=None,
        ios_version=None)

    self.assertFalse(is_legacy_xcode, 'install should return False')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', True)
    self.assertFalse(mock_install_runtime.called)
    self.assertFalse(mock_move_runtime.called)

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime')
  @mock.patch('xcode_util._install_xcode')
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  @mock.patch('os.path.exists', autospec=True)
  def test_new_mactoolchain_new_xcode_macos13_good_xcode_cache(
      self, mock_os_path_exists, mock_macos_13_or_higher, mock_install_xcode,
      mock_install_runtime, mock_move_runtime):
    mock_macos_13_or_higher.return_value = True
    mock_os_path_exists.return_value = False
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: True)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: False)

    is_legacy_xcode = xcode_util.install(
        self.mac_toolchain,
        self.xcode_build_version,
        self.xcode_app_path,
        runtime_cache_folder=None,
        ios_version=None)

    self.assertTrue(is_legacy_xcode, 'install should return True')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', True)
    self.assertFalse(mock_install_runtime.called)
    self.assertFalse(mock_move_runtime.called)

  @mock.patch('xcode_util.move_runtime', autospec=True)
  @mock.patch('xcode_util._install_runtime')
  @mock.patch('xcode_util._install_xcode')
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  @mock.patch('os.path.exists', autospec=True)
  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('os.mkdir', autospec=True)
  def test_new_mactoolchain_new_xcode_macos13_codesign_failed(
      self, mock_mkdir, mock_rmtree, mock_os_path_exists,
      mock_macos_13_or_higher, mock_install_xcode, mock_install_runtime,
      mock_move_runtime):
    mock_macos_13_or_higher.return_value = True
    mock_os_path_exists.return_value = True
    self.mock(xcode_util, '_using_new_mac_toolchain', lambda cmd: True)
    self.mock(xcode_util, '_is_legacy_xcode_package', lambda path: False)

    is_legacy_xcode = xcode_util.install(
        self.mac_toolchain,
        self.xcode_build_version,
        self.xcode_app_path,
        runtime_cache_folder=None,
        ios_version=None)

    mock_rmtree.assert_called_with(self.xcode_app_path)
    mock_mkdir.assert_called_with(self.xcode_app_path)
    self.assertTrue(is_legacy_xcode, 'install should return True')
    mock_install_xcode.assert_called_with('mac_toolchain', 'TestXcodeVersion',
                                          'test/path/Xcode.app', True)
    self.assertFalse(mock_install_runtime.called)
    self.assertFalse(mock_move_runtime.called)


class HelperFunctionTests(XcodeUtilTest):
  """Test class for xcode_util misc util functions."""

  def setUp(self):
    super(HelperFunctionTests, self).setUp()
    self.xcode_runtime_dir_rel_path = (
        'Contents/Developer/'
        'Platforms/iPhoneOS.platform/Library/Developer/'
        'CoreSimulator/Profiles/Runtimes')
    self.xcode_runtime_rel_path = (
        'Contents/Developer/'
        'Platforms/iPhoneOS.platform/Library/Developer/'
        'CoreSimulator/Profiles/Runtimes/iOS.simruntime')

  @mock.patch('subprocess.check_output', autospec=True)
  def test_using_new_mac_toolchain(self, mock_check_output):
    mock_check_output.return_value = b"""
Mac OS / iOS toolchain management

Usage:  mac_toolchain [command] [arguments]

Commands:
  help             prints help about a command
  install          Installs Xcode.
  upload           Uploads Xcode CIPD packages.
  package          Create CIPD packages locally.
  install-runtime  Installs Runtime.


Use "mac_toolchain help [command]" for more information about a command."""
    self.assertTrue(xcode_util._using_new_mac_toolchain('mac_toolchain'))

  @mock.patch('subprocess.check_output', autospec=True)
  def test_using_new_legacy_toolchain(self, mock_check_output):
    mock_check_output.return_value = b"""
Mac OS / iOS toolchain management

Usage:  mac_toolchain [command] [arguments]

Commands:
  help             prints help about a command
  install          Installs Xcode.
  upload           Uploads Xcode CIPD packages.
  package          Create CIPD packages locally.


Use "mac_toolchain help [command]" for more information about a command."""
    self.assertFalse(xcode_util._using_new_mac_toolchain('mac_toolchain'))

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_is_legacy_xcode_package_legacy(self, mock_glob, mock_rmtree):
    test_xcode_path = 'test/path/Xcode.app/'
    runtime_names = ['iOS.simruntime', 'iOS 12.4.simruntime']
    xcode_runtime_paths = [
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     runtime_name) for runtime_name in runtime_names
    ]
    mock_glob.return_value = xcode_runtime_paths
    self.assertTrue(xcode_util._is_legacy_xcode_package(test_xcode_path))
    mock_glob.assert_called_with(
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     '*.simruntime'))
    self.assertFalse(mock_rmtree.called)

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_is_legacy_xcode_package_no_runtime(self, mock_macos_13_or_higher,
                                              mock_glob, mock_rmtree):
    mock_macos_13_or_higher.return_value = False
    test_xcode_path = 'test/path/Xcode.app/'
    xcode_runtime_paths = []
    mock_glob.return_value = xcode_runtime_paths
    self.assertFalse(xcode_util._is_legacy_xcode_package(test_xcode_path))
    mock_glob.assert_called_with(
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     '*.simruntime'))
    self.assertFalse(mock_rmtree.called)

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  @mock.patch('mac_util.is_macos_13_or_higher', autospec=True)
  def test_is_legacy_xcode_package_single_runtime(self, mock_macos_13_or_higher,
                                                  mock_glob, mock_rmtree):
    mock_macos_13_or_higher.return_value = False
    test_xcode_path = 'test/path/Xcode.app/'
    runtime_names = ['iOS.simruntime']
    xcode_runtime_paths = [
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     runtime_name) for runtime_name in runtime_names
    ]
    mock_glob.return_value = xcode_runtime_paths
    self.assertFalse(xcode_util._is_legacy_xcode_package(test_xcode_path))
    mock_glob.assert_called_with(
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     '*.simruntime'))
    mock_rmtree.assert_called_with(
        os.path.join(test_xcode_path, self.xcode_runtime_dir_rel_path,
                     'iOS.simruntime'))

class MoveRuntimeTests(XcodeUtilTest):
  """Test class for xcode_util.move_runtime function."""

  def setUp(self):
    super(MoveRuntimeTests, self).setUp()
    self.runtime_cache_folder = 'test/path/Runtime'
    self.xcode_app_path = 'test/path/Xcode.app'

  @mock.patch('shutil.move', autospec=True)
  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_move_runtime_into_xcode(self, mock_glob, mock_rmtree, mock_move):

    mock_glob.side_effect = [['test/path/Runtime/iOS.simruntime'], []]

    xcode_util.move_runtime(self.runtime_cache_folder, self.xcode_app_path,
                            True)

    xcode_runtime_path = ('test/path/Xcode.app/Contents/Developer/'
                          'Platforms/iPhoneOS.platform/Library/Developer/'
                          'CoreSimulator/Profiles/Runtimes/iOS.simruntime')
    calls = [
        mock.call('test/path/Runtime/*.simruntime'),
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/*.simruntime'))
    ]
    mock_glob.assert_has_calls(calls)
    self.assertFalse(mock_rmtree.called)
    mock_move.assert_called_with('test/path/Runtime/iOS.simruntime',
                                 xcode_runtime_path)

  @mock.patch('shutil.move', autospec=True)
  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_move_runtime_outside_xcode(self, mock_glob, mock_rmtree, mock_move):
    xcode_runtime_folder = ('test/path/Xcode.app/Contents/Developer/'
                            'Platforms/iPhoneOS.platform/Library/Developer/'
                            'CoreSimulator/Profiles/Runtimes')
    mock_glob.side_effect = [[xcode_runtime_folder + '/iOS.simruntime'], []]

    xcode_util.move_runtime(self.runtime_cache_folder, self.xcode_app_path,
                            False)

    calls = [
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/*.simruntime')),
        mock.call('test/path/Runtime/*.simruntime')
    ]
    mock_glob.assert_has_calls(calls)
    self.assertFalse(mock_rmtree.called)
    mock_move.assert_called_with(xcode_runtime_folder + '/iOS.simruntime',
                                 'test/path/Runtime/iOS.simruntime')

  @mock.patch('shutil.move', autospec=True)
  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_move_runtime_multiple_in_src(self, mock_glob, mock_rmtree,
                                        mock_move):
    mock_glob.side_effect = [[
        'test/path/Runtime/iOS.simruntime',
        'test/path/Runtime/iOS 13.4.simruntime'
    ], []]

    with self.assertRaises(test_runner_errors.IOSRuntimeHandlingError):
      xcode_util.move_runtime(self.runtime_cache_folder, self.xcode_app_path,
                              True)
    mock_glob.assert_called_with('test/path/Runtime/*.simruntime')
    self.assertFalse(mock_rmtree.called)
    self.assertFalse(mock_move.called)

  @mock.patch('shutil.move', autospec=True)
  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_move_runtime_remove_from_dst(self, mock_glob, mock_rmtree,
                                        mock_move):

    mock_glob.side_effect = [['test/path/Runtime/iOS.simruntime'],
                             [('test/path/Xcode.app/Contents/Developer/'
                               'Platforms/iPhoneOS.platform/Library/Developer/'
                               'CoreSimulator/Profiles/Runtimes/iOS.simruntime')
                             ]]

    xcode_util.move_runtime(self.runtime_cache_folder, self.xcode_app_path,
                            True)

    xcode_runtime_path = ('test/path/Xcode.app/Contents/Developer/'
                          'Platforms/iPhoneOS.platform/Library/Developer/'
                          'CoreSimulator/Profiles/Runtimes/iOS.simruntime')
    calls = [
        mock.call('test/path/Runtime/*.simruntime'),
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/*.simruntime'))
    ]
    mock_glob.assert_has_calls(calls)
    mock_rmtree.assert_called_with(xcode_runtime_path)
    mock_move.assert_called_with('test/path/Runtime/iOS.simruntime',
                                 xcode_runtime_path)


  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  def test_remove_runtimes(self, mock_glob, mock_rmtree):

    mock_glob.return_value = [
        ('test/path/Xcode.app/Contents/Developer/'
         'Platforms/iPhoneOS.platform/Library/Developer/'
         'CoreSimulator/Profiles/Runtimes/iOS.simruntime'),
        ('test/path/Xcode.app/Contents/Developer/'
         'Platforms/iPhoneOS.platform/Library/Developer/'
         'CoreSimulator/Profiles/Runtimes/iOS 15.0.simruntime')
    ]

    xcode_util.remove_runtimes(self.xcode_app_path)

    calls = [
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/*.simruntime'))
    ]
    mock_glob.assert_has_calls(calls)
    calls = [
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/iOS.simruntime')),
        mock.call(('test/path/Xcode.app/Contents/Developer/'
                   'Platforms/iPhoneOS.platform/Library/Developer/'
                   'CoreSimulator/Profiles/Runtimes/iOS 15.0.simruntime'))
    ]
    mock_rmtree.assert_has_calls(calls)


class MacToolchainInvocationTests(XcodeUtilTest):
  """Test class for xcode_util functions invoking mac_toolchain."""

  def setUp(self):
    super(MacToolchainInvocationTests, self).setUp()
    self.mac_toolchain = 'mac_toolchain'
    self.xcode_build_version = 'TestXcodeVersion'
    self.xcode_app_path = 'test/path/Xcode.app'
    self.runtime_cache_folder = 'test/path/Runtime'
    self.ios_version = '14.4'

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('os.path.exists', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  @mock.patch('subprocess.check_call', autospec=True)
  def test_install_runtime_no_cache(self, mock_check_output, mock_glob,
                                    mock_exists, mock_rmtree):
    mock_glob.return_value = []
    mock_exists.return_value = False

    xcode_util._install_runtime(self.mac_toolchain, self.runtime_cache_folder,
                                self.xcode_build_version, self.ios_version)

    mock_glob.assert_called_with('test/path/Runtime/*.simruntime')
    calls = [
        mock.call('test/path/Runtime/.cipd'),
        mock.call('test/path/Runtime/.xcode_versions'),
    ]
    mock_exists.assert_has_calls(calls)
    self.assertFalse(mock_rmtree.called)
    mock_check_output.assert_called_with([
        'mac_toolchain', 'install-runtime', '-xcode-version',
        'testxcodeversion', '-runtime-version', 'ios-14-4', '-output-dir',
        'test/path/Runtime'
    ],
                                         stderr=-2)

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('os.path.exists', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  @mock.patch('subprocess.check_call', autospec=True)
  def test_install_runtime_has_cache(self, mock_check_output, mock_glob,
                                     mock_exists, mock_rmtree):
    mock_glob.return_value = ['test/path/Runtime/iOS.simruntime']
    xcode_util._install_runtime(self.mac_toolchain, self.runtime_cache_folder,
                                self.xcode_build_version, self.ios_version)

    mock_glob.assert_called_with('test/path/Runtime/*.simruntime')
    self.assertFalse(mock_exists.called)
    self.assertFalse(mock_rmtree.called)
    mock_check_output.assert_called_with([
        'mac_toolchain', 'install-runtime', '-xcode-version',
        'testxcodeversion', '-runtime-version', 'ios-14-4', '-output-dir',
        'test/path/Runtime'
    ],
                                         stderr=-2)

  @mock.patch('shutil.rmtree', autospec=True)
  @mock.patch('os.path.exists', autospec=True)
  @mock.patch('glob.glob', autospec=True)
  @mock.patch('subprocess.check_call', autospec=True)
  def test_install_runtime_incorrect_cache(self, mock_check_output, mock_glob,
                                           mock_exists, mock_rmtree):
    mock_glob.return_value = []
    mock_exists.return_value = True

    xcode_util._install_runtime(self.mac_toolchain, self.runtime_cache_folder,
                                self.xcode_build_version, self.ios_version)

    mock_glob.assert_called_with('test/path/Runtime/*.simruntime')
    calls = [
        mock.call('test/path/Runtime/.cipd'),
        mock.call('test/path/Runtime/.xcode_versions'),
    ]
    mock_exists.assert_has_calls(calls)
    mock_rmtree.assert_has_calls(calls)

    mock_check_output.assert_called_with([
        'mac_toolchain', 'install-runtime', '-xcode-version',
        'testxcodeversion', '-runtime-version', 'ios-14-4', '-output-dir',
        'test/path/Runtime'
    ],
                                         stderr=-2)

  @mock.patch('subprocess.check_call', autospec=True)
  def test_install_xcode_legacy_mac_toolchain(self, mock_check_output):
    using_new_mac_toolchain = False
    xcode_util._install_xcode(self.mac_toolchain, self.xcode_build_version,
                              self.xcode_app_path, using_new_mac_toolchain)
    mock_check_output.assert_called_with([
        'mac_toolchain', 'install', '-kind', 'ios', '-xcode-version',
        'testxcodeversion', '-output-dir', 'test/path/Xcode.app'
    ],
                                         stderr=-2)

  @mock.patch('subprocess.check_call', autospec=True)
  def test_install_xcode_new_mac_toolchain(self, mock_check_output):
    using_new_mac_toolchain = True
    xcode_util._install_xcode(self.mac_toolchain, self.xcode_build_version,
                              self.xcode_app_path, using_new_mac_toolchain)
    mock_check_output.assert_called_with([
        'mac_toolchain', 'install', '-kind', 'ios', '-xcode-version',
        'testxcodeversion', '-output-dir', 'test/path/Xcode.app',
        '-with-runtime=False'
    ],
                                         stderr=-2)


if __name__ == '__main__':
  logging.basicConfig(
      format='[%(asctime)s:%(levelname)s] %(message)s',
      level=logging.DEBUG,
      datefmt='%I:%M:%S')
  unittest.main()