Exemple #1
0
def _ValidateSimulatorTypeWithOsVersion(device_type, os_version):
    """Checks if the simulator type with the given os version is valid.

  Args:
    device_type: string, device type of the new simulator. The value corresponds
      to the output of `xcrun simctl list devicetypes`. E.g., iPhone 6, iPad
      Air, etc.
    os_version: string, OS version of the new simulator. The format is
      {major}.{minor}, such as 9.3, 10.2.

  Raises:
    ios_errors.IllegalArgumentError: when the given simulator device type can
        not match the given OS version.
  """
    os_version_float = float(os_version)
    sim_profile = simtype_profile.SimTypeProfile(device_type)
    min_os_version_float = float(sim_profile.min_os_version)
    if min_os_version_float > os_version_float:
        raise ios_errors.IllegalArgumentError(
            'The min OS version of %s is %s. But current OS version is %s' %
            (device_type, min_os_version_float, os_version))
    max_os_version_float = float(sim_profile.max_os_version)
    if max_os_version_float < os_version_float:
        raise ios_errors.IllegalArgumentError(
            'The max OS version of %s is %s. But current OS version is %s' %
            (device_type, max_os_version_float, os_version))
def _FinalizeTestType(test_bundle_dir,
                      sdk,
                      app_under_test_dir=None,
                      original_test_type=None):
    """Finalizes the test type of the test session according to the args.

  If original_test_type is not given, will auto detect the test bundle.
  If

  Args:
    test_bundle_dir: string, the path of test bundle folder.
    sdk: ios_constants.SDK, the sdk of testing device.
    app_under_test_dir: string, the path of app under test folder.
    original_test_type: ios_constants.TestType, the original test type.

  Returns:
    a ios_constants.TestType object.

  Raises:
    ios_errors.IllegalArgumentError: The given arguments are unmatch.
  """
    if not original_test_type:
        test_type = _DetectTestType(test_bundle_dir)
        if (test_type == ios_constants.TestType.XCTEST
                and not app_under_test_dir
                and sdk == ios_constants.SDK.IPHONESIMULATOR):
            test_type = ios_constants.TestType.LOGIC_TEST
        logging.info('Will consider the test as test type %s to run.',
                     test_type)
    else:
        test_type = original_test_type
        if (test_type == ios_constants.TestType.LOGIC_TEST
                and sdk != ios_constants.SDK.IPHONESIMULATOR):
            if app_under_test_dir:
                test_type = ios_constants.TestType.XCTEST
                logging.info(
                    'Will consider the test as test type XCTest to run. Because '
                    'it is only support running Logic Test on iOS simulator and the '
                    'sdk of testing device is %s.', sdk)
            else:
                raise ios_errors.IllegalArgumentError(
                    'It is only support running Logic Test on iOS simulator.'
                    'The sdk of testing device is %s.' % sdk)
        elif (test_type == ios_constants.TestType.XCTEST
              and not app_under_test_dir
              and sdk == ios_constants.SDK.IPHONESIMULATOR):
            test_type = ios_constants.TestType.LOGIC_TEST
            logging.info(
                'Will consider the test as test type Logic Test to run. Because the '
                'app under test is not given.')
    if (not app_under_test_dir
            and test_type != ios_constants.TestType.LOGIC_TEST):
        raise ios_errors.IllegalArgumentError(
            'The app under test is required in test type %s.' % test_type)
    return test_type
Exemple #3
0
  def _ValidateArguments(self):
    """Checks whether the arguments of the dummy project is valid.

    Raises:
      IllegalArgumentError: when the sdk or test type is not supported.
    """
    if self._sdk not in ios_constants.SUPPORTED_SDKS:
      raise ios_errors.IllegalArgumentError(
          'The sdk %s is not supported. Supported sdks are %s.'
          % (self._sdk, ios_constants.SUPPORTED_SDKS))
    if self._test_type not in ios_constants.SUPPORTED_TEST_TYPES:
      raise ios_errors.IllegalArgumentError(
          'The test type %s is not supported. Supported test types are %s.'
          % (self._test_type, ios_constants.SUPPORTED_TEST_TYPES))
Exemple #4
0
def GetOsType(device_type):
    """Gets the OS type of the given simulator.

  This method can not work fine if the device_type is invalid. Please calls
  simulator_util.ValidateSimulatorType(device_type, os_version) to validate
  it first.

  Args:
    device_type: string, device type of the new simulator. The value corresponds
      to the output of `xcrun simctl list devicetypes`. E.g., iPhone 6, iPad
      Air, etc.

  Returns:
    shared.ios_constants.OS.

  Raises:
    ios_errors.IllegalArgumentError: when the OS type of the given simulator
        device type can not be recognized.
  """
    if device_type.startswith('i'):
        return ios_constants.OS.IOS
    if 'TV' in device_type:
        return ios_constants.OS.TVOS
    if 'Watch' in device_type:
        return ios_constants.OS.WATCHOS
    raise ios_errors.IllegalArgumentError(
        'Failed to recognize the os type for simulator device type %s.' %
        device_type)
Exemple #5
0
  def RunXcTest(self, device_id, built_products_dir, derived_data_dir,
                startup_timeout_sec):
    """Runs `xcodebuild test` with the dummy project.

    If app under test or test bundle are not in built_products_dir, will copy
    the file into built_products_dir.

    Args:
      device_id: string, id of the device.
      built_products_dir: path of the built products dir in this build session.
      derived_data_dir: path of the derived data dir in this build session.
      startup_timeout_sec: Seconds until the xcodebuild command is deemed stuck.

    Returns:
      A value of type runner_exit_codes.EXITCODE.

    Raises:
      IllegalArgumentError: when test type is not xctest.
    """
    if self._test_type != ios_constants.TestType.XCTEST:
      raise ios_errors.IllegalArgumentError(
          'Only xctest dummy project is supported to run `xcodebuild test`. '
          'The test type %s is not supported.' % self._test_type)
    self.GenerateDummyProject()
    # In Xcode 7.3+, the folder structure of app under test is changed.
    if xcode_info_util.GetXcodeVersionNumber() >= 730:
      app_under_test_plugin_path = os.path.join(self._app_under_test_dir,
                                                'PlugIns')
      if not os.path.exists(app_under_test_plugin_path):
        os.mkdir(app_under_test_plugin_path)
      test_bundle_under_plugin_path = os.path.join(
          app_under_test_plugin_path, os.path.basename(self._test_bundle_dir))
      if not os.path.exists(test_bundle_under_plugin_path):
        shutil.copytree(self._test_bundle_dir, test_bundle_under_plugin_path)
    self._PrepareBuildProductsDir(built_products_dir)

    logging.info('Running `xcodebuild test` with dummy project.\n'
                 'device_id= %s\n'
                 'built_product_dir = %s\nderived_data_path = %s\n',
                 device_id,
                 built_products_dir,
                 derived_data_dir)
    command = ['xcodebuild', 'test',
               'BUILT_PRODUCTS_DIR=' + built_products_dir,
               '-project', self._xcodeproj_dir_path,
               '-scheme', self._test_scheme,
               '-destination', 'id=' + device_id,
               '-derivedDataPath', derived_data_dir]
    app_bundle_id = bundle_util.GetBundleId(self._app_under_test_dir)
    exit_code, _ = xcodebuild_test_executor.XcodebuildTestExecutor(
        command,
        succeeded_signal=_SIGNAL_XCODEBUILD_TEST_SUCCEEDED,
        failed_signal=_SIGNAL_XCODEBUILD_TEST_FAILED,
        sdk=self._sdk,
        test_type=self._test_type,
        device_id=device_id,
        app_bundle_id=app_bundle_id,
        startup_timeout_sec=startup_timeout_sec).Execute(return_output=False)
    return exit_code
Exemple #6
0
def _PlatformToSdk(platform):
    """Gets the SDK of the given platform."""
    if platform == ios_constants.PLATFORM.IOS_DEVICE:
        return ios_constants.SDK.IPHONEOS
    if platform == ios_constants.PLATFORM.IOS_SIMULATOR:
        return ios_constants.SDK.IPHONESIMULATOR
    raise ios_errors.IllegalArgumentError(
        'The platform %s is not supported. The supported values are %s.' %
        (platform, ios_constants.SUPPORTED_PLATFORMS))
Exemple #7
0
def _GetJson(json_path):
    """Gets the json dict from the file."""
    if json_path:
        with open(json_path) as input_file:
            try:
                return json.load(input_file)
            except ValueError as e:
                raise ios_errors.IllegalArgumentError(e)
    return None
Exemple #8
0
    def _ValidateArguments(self):
        """Checks whether the arguments of this class are valid.

    Raises:
      IllegalArgumentError: when the sdk or test type is not supported.
    """
        if self._sdk not in ios_constants.SUPPORTED_SDKS:
            raise ios_errors.IllegalArgumentError(
                'The sdk %s is not supported. Supported sdks are %s.' %
                (self._sdk, ios_constants.SUPPORTED_SDKS))
        if self._test_type not in ios_constants.SUPPORTED_TEST_TYPES:
            raise ios_errors.IllegalArgumentError(
                'The test type %s is not supported. Supported test types are %s.'
                % (self._test_type, ios_constants.SUPPORTED_TEST_TYPES))
        if (self._test_type == ios_constants.TestType.LOGIC_TEST
                and self._on_device):
            raise ios_errors.IllegalArgumentError(
                'Only support running logic test on sdk iphonesimulator. '
                'Current sdk is %s' % self._sdk)
Exemple #9
0
def _GetSdk(device_id):
    """Gets the sdk of the target device with the given device_id."""
    devices_list_output = subprocess.check_output(
        ['xcrun', 'xcdevice', 'list']).decode('utf-8')
    for device_info in json.loads(devices_list_output):
        if device_info['identifier'] == device_id:
            return ios_constants.SDK.IPHONESIMULATOR if device_info[
                'simulator'] else ios_constants.SDK.IPHONEOS

    raise ios_errors.IllegalArgumentError(
        'The device with id %s can not be found. The known devices are %s.' %
        (device_id, devices_list_output))
Exemple #10
0
def _ValidateSimulatorType(device_type):
    """Checks if the simulator type is valid.

  Args:
    device_type: string, device type of the new simulator. The value corresponds
      to the output of `xcrun simctl list devicetypes`. E.g., iPhone 6, iPad
      Air, etc.

  Raises:
    ios_errors.IllegalArgumentError: when the given simulator device type is
    invalid.
  """
    supported_sim_device_types = GetSupportedSimDeviceTypes()
    if device_type not in supported_sim_device_types:
        raise ios_errors.IllegalArgumentError(
            'The simulator device type %s is not supported. Supported simulator '
            'device types are %s.' % (device_type, supported_sim_device_types))
def GetLastSupportedSimOsVersion(os_type=ios_constants.OS.IOS,
                                 device_type=None):
    """Gets the last supported version of given arguments.

  If device_type is given, will return the last supported OS version of the
  device type. Otherwise, will return the last supported OS version of the
  OS type.

  Args:
    os_type: shared.ios_constants.OS, OS type of simulator, such as iOS,
      watchOS, tvOS.
    device_type: string, device type of the new simulator. The value corresponds
      to the output of `xcrun simctl list devicetypes`. E.g., iPhone 6, iPad
      Air, etc.

  Returns:
    a string, the last supported version.

  Raises:
    ios_errors.SimError: when there is no supported OS version of the given OS.
    ios_errors.IllegalArgumentError: when the supported OS version can not match
        the given simulator type.
  """
    supported_os_versions = GetSupportedSimOsVersions(os_type)
    if not supported_os_versions:
        raise ios_errors.SimError('Can not find supported OS version of %s.' %
                                  os_type)
    if not device_type:
        return supported_os_versions[-1]

    max_os_version = simtype_profile.SimTypeProfile(device_type).max_os_version
    # The supported os versions will be from latest to older after reverse().
    supported_os_versions.reverse()
    if not max_os_version:
        return supported_os_versions[0]

    for os_version in supported_os_versions:
        if float(os_version) <= max_os_version:
            return os_version
    raise ios_errors.IllegalArgumentError(
        'The supported OS version %s can not match simulator type %s. Because '
        'its max OS version is %s' %
        (supported_os_versions, device_type, max_os_version))
Exemple #12
0
def _GetSdk(device_id):
    """Gets the sdk of the target device with the given device_id."""
    # The command `instruments -s devices` is much slower than
    # `xcrun simctl list devices`. So use `xcrun simctl list devices` to check
    # IPHONESIMULATOR SDK first.
    simlist_devices_output = simulator_util.RunSimctlCommand(
        ['xcrun', 'simctl', 'list', 'devices'])
    if device_id in simlist_devices_output:
        return ios_constants.SDK.IPHONESIMULATOR

    known_devices_output = subprocess.check_output(
        ['instruments', '-s', 'devices'])
    for line in known_devices_output.split('\n'):
        if device_id in line and '(Simulator)' not in line:
            return ios_constants.SDK.IPHONEOS

    raise ios_errors.IllegalArgumentError(
        'The device with id %s can not be found. The known devices are %s.' %
        (device_id, known_devices_output))
def _PrepareBundles(working_dir, app_under_test_path, test_bundle_path):
    """Prepares the bundles in work directory.

  If the original bundle is .ipa, the .ipa file will be unzipped under
  working_dir. If the original bundle is .app/.xctest and the bundle file is not
  in working_dir, the bundle file will be copied to working_dir.

  Args:
    working_dir: string, the working directory.
    app_under_test_path: string, the path of the application to be tested.
        It can be .ipa or .app. It can be None.
    test_bundle_path: string, the path of the test bundle to be tested. It can
        be .ipa or .xctest.

  Returns:
    a tuple with two items:
      a path of app under test directory (.app) under work directory.
      a path of test bundle directory (.xctest) under work directory.

  Raises:
    ios_errors.IllegalArgumentError: if the app under test/test bundle does not
      exist or its extension is invaild.
  """
    working_dir = os.path.abspath(working_dir)
    app_under_test_dir = None
    if app_under_test_path:
        if not os.path.exists(app_under_test_path):
            raise ios_errors.IllegalArgumentError(
                'The app under test does not exists: %s' % app_under_test_path)
        if not (app_under_test_path.endswith('.app')
                or app_under_test_path.endswith('.ipa')):
            raise ios_errors.IllegalArgumentError(
                'The app under test %s should be with .app or .ipa extension.'
                % app_under_test_path)

        app_under_test_dir = os.path.join(
            working_dir,
            os.path.splitext(os.path.basename(app_under_test_path))[0] +
            '.app')
        if not os.path.exists(app_under_test_dir):
            if app_under_test_path.endswith('.ipa'):
                extract_app_under_test_dir = bundle_util.ExtractApp(
                    app_under_test_path, working_dir)
                shutil.move(extract_app_under_test_dir, app_under_test_dir)
            elif not os.path.abspath(app_under_test_path).startswith(
                    working_dir):
                # Only copies the app under test if it is not in working directory.
                shutil.copytree(app_under_test_path, app_under_test_dir)
            else:
                app_under_test_dir = app_under_test_path

    if not os.path.exists(test_bundle_path):
        raise ios_errors.IllegalArgumentError(
            'The test bundle does not exists: %s' % test_bundle_path)
    if not (test_bundle_path.endswith('.xctest')
            or test_bundle_path.endswith('.ipa')
            or test_bundle_path.endswith('.zip')):
        raise ios_errors.IllegalArgumentError(
            'The test bundle %s should be with .xctest, .ipa or .zip extension.'
            % test_bundle_path)

    test_bundle_dir = os.path.join(
        working_dir,
        os.path.splitext(os.path.basename(test_bundle_path))[0] + '.xctest')
    if not os.path.exists(test_bundle_dir):
        if test_bundle_path.endswith('.ipa') or test_bundle_path.endswith(
                '.zip'):
            extract_test_bundle_dir = bundle_util.ExtractTestBundle(
                test_bundle_path, working_dir)
            shutil.move(extract_test_bundle_dir, test_bundle_dir)
        elif not os.path.abspath(test_bundle_path).startswith(working_dir):
            # Only copies the test bundle if it is not in working directory.
            shutil.copytree(test_bundle_path, test_bundle_dir)
        else:
            test_bundle_dir = test_bundle_path

    return app_under_test_dir, test_bundle_dir
  def Prepare(self, app_under_test=None, test_bundle=None,
              xctestrun_file_path=None, test_type=None, signing_options=None):
    """Prepares the test session.

    If xctestrun_file is not provided, will use app under test and test bundle
    path to generate a new xctest file or dummy project.

    Args:
      app_under_test: string, the path of the application to be tested. It can
          be .ipa or .app.
      test_bundle: string, the path of the test bundle to be tested. It can
          be .ipa or .xctest.
      xctestrun_file_path: string, the path of the xctestrun file. It is the
          configure file to launch test in Xcode 8+.
      test_type: ios_constants.TestType. The type of test bundle.
      signing_options: dict, the signing app options. See
          ios_constants.SIGNING_OPTIONS_JSON_HELP for details.

    Raises:
      ios_errors.IllegalArgumentError:
          1) the app under test/test bundle does not exist;
          2) the app under test/test bundle's extension is invaild.
    """
    if not signing_options:
      signing_options = {}

    if self._work_dir:
      if not os.path.exists(self._work_dir):
        os.mkdir(self._work_dir)
      self._work_dir = os.path.abspath(self._work_dir)
      self._delete_work_dir = False
    else:
      self._work_dir = tempfile.mkdtemp()
      self._delete_work_dir = True

    if self._output_dir:
      if not os.path.exists(self._output_dir):
        os.mkdir(self._output_dir)
      self._delete_output_dir = False
    else:
      self._output_dir = tempfile.mkdtemp()
      self._delete_output_dir = True

    if xctestrun_file_path:
      xcode_version_num = xcode_info_util.GetXcodeVersionNumber()
      if xcode_version_num < 800:
        raise ios_errors.IllegalArgumentError(
            'The xctestrun file is only supported in Xcode 8+. But current '
            'Xcode version number is %s' % xcode_version_num)
      self._xctestrun_obj = xctestrun.XctestRun(
          xctestrun_file_path, test_type)
    else:
      if not test_bundle:
        raise ios_errors.IllegalArgumentError(
            'Without providing xctestrun file, test bundle is required.')
      app_under_test_dir, test_bundle_dir = _PrepareBundles(
          self._work_dir, app_under_test, test_bundle)
      test_type = _FinalizeTestType(
          test_bundle_dir, self._sdk, app_under_test_dir=app_under_test_dir,
          original_test_type=test_type)

      # xctestrun can only support in Xcode 8+.
      # Since xctestrun approach is more flexiable to local debug and is easy to
      # support tests_to_run feature. So in Xcode 8+, use xctestrun approach to
      # run XCTest and Logic Test.
      if (test_type in ios_constants.SUPPORTED_TEST_TYPES and
          test_type != ios_constants.TestType.LOGIC_TEST and
          xcode_info_util.GetXcodeVersionNumber() >= 800):
        xctestrun_factory = xctestrun.XctestRunFactory(
            app_under_test_dir, test_bundle_dir, self._sdk, test_type,
            signing_options, self._work_dir)
        self._xctestrun_obj = xctestrun_factory.GenerateXctestrun()
      elif test_type == ios_constants.TestType.XCUITEST:
        raise ios_errors.IllegalArgumentError(
            'Only supports running XCUITest under Xcode 8+. '
            'Current xcode version is %s' %
            xcode_info_util.GetXcodeVersionNumber())
      elif test_type == ios_constants.TestType.XCTEST:
        self._dummy_project_obj = dummy_project.DummyProject(
            app_under_test_dir,
            test_bundle_dir,
            self._sdk,
            ios_constants.TestType.XCTEST,
            self._work_dir,
            keychain_path=signing_options.get('keychain_path') or None)
        self._dummy_project_obj.GenerateDummyProject()
      elif test_type == ios_constants.TestType.LOGIC_TEST:
        self._logic_test_bundle = test_bundle_dir
      else:
        raise ios_errors.IllegalArgumentError(
            'The test type %s is not supported. Supported test types are %s'
            % (test_type, ios_constants.SUPPORTED_TEST_TYPES))
    self._prepared = True
Exemple #15
0
def CreateNewSimulator(device_type=None, os_version=None, name_prefix=None):
    """Creates a new simulator according to arguments.

  If neither device_type nor os_version is given, will use the latest iOS
  version and latest iPhone type.
  If os_version is given but device_type is not, will use latest iPhone type
  according to the OS version limitation. E.g., if the given os_version is 9.3,
  the latest simulator type is iPhone 6s Plus. Because the min OS version of
  iPhone 7 is 10.0.
  If device_type is given but os_version is not, will use the min value
  between max OS version of the simulator type and current latest OS version.
  E.g., if the given device_type is iPhone 5 and latest OS version is 10.3,
  will use 10.2. Because the max OS version of iPhone 5 is 10.2.

  Args:
    device_type: string, device type of the new simulator. The value corresponds
      to the output of `xcrun simctl list devicetypes`. E.g., iPhone 6, iPad
      Air, etc.
    os_version: string, OS version of the new simulator. The format is
      {major}.{minor}, such as 9.3, 10.2.
    name_prefix: string, name prefix of the new simulator. By default, it is
      "New".

  Returns:
     a tuple with four items:
        string, id of the new simulator.
        string, simulator device type of the new simulator.
        string, OS version of the new simulator.
        string, name of the new simulator.

  Raises:
    ios_errors.SimError: when failed to create new simulator.
    ios_errors.IllegalArgumentError: when the given argument is invalid.
  """
    if not device_type:
        os_type = ios_constants.OS.IOS
    else:
        _ValidateSimulatorType(device_type)
        os_type = GetOsType(device_type)
    if not os_version:
        os_version = GetLastSupportedSimOsVersion(os_type,
                                                  device_type=device_type)
    else:
        supported_sim_os_versions = GetSupportedSimOsVersions(os_type)
        if os_version not in supported_sim_os_versions:
            raise ios_errors.IllegalArgumentError(
                'The simulator os version %s is not supported. Supported simulator '
                'os versions are %s.' %
                (os_version, supported_sim_os_versions))
    if not device_type:
        device_type = GetLastSupportedIphoneSimType(os_version)
    else:
        _ValidateSimulatorTypeWithOsVersion(device_type, os_version)
    if not name_prefix:
        name_prefix = 'New'
    name = '%s-%s-%s' % (name_prefix, device_type, os_version)

    # Example
    # Runtime ID of iOS 10.2: com.apple.CoreSimulator.SimRuntime.iOS-10-2
    runtime_id = _PREFIX_RUNTIME_ID + os_type + '-' + os_version.replace(
        '.', '-')
    logging.info('Creating a new simulator:\nName: %s\nOS: %s %s\nType: %s',
                 name, os_type, os_version, device_type)
    for i in range(0, _SIM_OPERATION_MAX_ATTEMPTS):
        try:
            new_simulator_id = RunSimctlCommand(
                ['xcrun', 'simctl', 'create', name, device_type, runtime_id])
        except ios_errors.SimError as e:
            raise ios_errors.SimError('Failed to create simulator: %s' %
                                      str(e))
        new_simulator_obj = Simulator(new_simulator_id)
        # After creating a new simulator, its state is CREATING. When the
        # simulator's state becomes SHUTDOWN, the simulator is created.
        try:
            new_simulator_obj.WaitUntilStateShutdown(
                _SIMULATOR_CREATING_TO_SHUTDOWN_TIMEOUT_SEC)
            logging.info('Created new simulator %s.', new_simulator_id)
            return new_simulator_id, device_type, os_version, name
        except ios_errors.SimError as error:
            logging.debug('Failed to create simulator %s: %s.',
                          new_simulator_id, error)
            logging.debug('Deleted half-created simulator %s.',
                          new_simulator_id)
            new_simulator_obj.Delete()
            if i != _SIM_OPERATION_MAX_ATTEMPTS - 1:
                logging.debug('Will sleep %ss and retry again.',
                              _SIM_ERROR_RETRY_INTERVAL_SEC)
                # If the simulator's state becomes SHUTDOWN, there may be something
                # wrong in CoreSimulatorService. Sleeps a short interval(2s) can help
                # reduce flakiness.
                time.sleep(_SIM_ERROR_RETRY_INTERVAL_SEC)
    raise ios_errors.SimError('Failed to create simulator in %d attempts.' %
                              _SIM_OPERATION_MAX_ATTEMPTS)
    def Prepare(self,
                app_under_test=None,
                test_bundle=None,
                xctestrun_file_path=None,
                test_type=None,
                signing_options=None):
        """Prepares the test session.

    If xctestrun_file is not provided, will use app under test and test bundle
    path to generate a new xctest file.

    Args:
      app_under_test: string, the path of the application to be tested. It can
          be .ipa or .app.
      test_bundle: string, the path of the test bundle to be tested. It can
          be .ipa or .xctest.
      xctestrun_file_path: string, the path of the xctestrun file. It is the
          configure file to launch test in Xcode 8+.
      test_type: ios_constants.TestType. The type of test bundle.
      signing_options: dict, the signing app options. See
          ios_constants.SIGNING_OPTIONS_JSON_HELP for details.

    Raises:
      ios_errors.IllegalArgumentError:
          1) the app under test/test bundle does not exist;
          2) the app under test/test bundle's extension is invaild.
    """
        if not signing_options:
            signing_options = {}

        if self._work_dir:
            if not os.path.exists(self._work_dir):
                os.mkdir(self._work_dir)
            self._work_dir = os.path.abspath(self._work_dir)
            self._delete_work_dir = False
        else:
            self._work_dir = tempfile.mkdtemp()
            self._delete_work_dir = True

        if self._output_dir:
            if not os.path.exists(self._output_dir):
                os.mkdir(self._output_dir)
            self._delete_output_dir = False
        else:
            self._output_dir = tempfile.mkdtemp()
            self._delete_output_dir = True

        if xctestrun_file_path:
            self._xctestrun_obj = xctestrun.XctestRun(xctestrun_file_path,
                                                      test_type)
        else:
            if not test_bundle:
                raise ios_errors.IllegalArgumentError(
                    'Without providing xctestrun file, test bundle is required.'
                )
            app_under_test_dir, test_bundle_dir = _PrepareBundles(
                self._work_dir, app_under_test, test_bundle)
            test_type = _FinalizeTestType(
                test_bundle_dir,
                self._sdk,
                app_under_test_dir=app_under_test_dir,
                original_test_type=test_type)

            if test_type not in ios_constants.SUPPORTED_TEST_TYPES:
                raise ios_errors.IllegalArgumentError(
                    'The test type %s is not supported. Supported test types are %s'
                    % (test_type, ios_constants.SUPPORTED_TEST_TYPES))

            if test_type != ios_constants.TestType.LOGIC_TEST:
                xctestrun_factory = xctestrun.XctestRunFactory(
                    app_under_test_dir, test_bundle_dir, self._sdk,
                    self._device_arch, test_type, signing_options,
                    self._work_dir)
                self._xctestrun_obj = xctestrun_factory.GenerateXctestrun()
            else:
                self._logic_test_bundle = test_bundle_dir
        self._prepared = True
def _PrepareBundles(working_dir, app_under_test_path, test_bundle_path):
    """Prepares the bundles in work directory.

  If the original bundle is .ipa, the .ipa file will be unzipped under
  working_dir. If the original bundle is .app/.xctest and the bundle file is not
  in working_dir, the bundle file will be copied to working_dir.

  Args:
    working_dir: string, the working directory.
    app_under_test_path: string, the path of the application to be tested.
        It can be .ipa or .app. It can be None.
    test_bundle_path: string, the path of the test bundle to be tested. It can
        be .ipa or .xctest.

  Returns:
    a tuple with two items:
      a path of app under test directory (.app) under work directory.
      a path of test bundle directory (.xctest) under work directory.

  Raises:
    ios_errors.IllegalArgumentError: if the app under test/test bundle does not
      exist or its extension is invaild.
  """
    working_dir = os.path.abspath(working_dir)
    app_under_test_dir = None
    if app_under_test_path:
        if not os.path.exists(app_under_test_path):
            raise ios_errors.IllegalArgumentError(
                'The app under test does not exists: %s' % app_under_test_path)
        if not (app_under_test_path.endswith('.app')
                or app_under_test_path.endswith('.ipa')):
            raise ios_errors.IllegalArgumentError(
                'The app under test %s should be with .app or .ipa extension.'
                % app_under_test_path)

        app_under_test_dir = os.path.join(
            working_dir,
            os.path.splitext(os.path.basename(app_under_test_path))[0] +
            '.app')
        if not os.path.exists(app_under_test_dir):
            if app_under_test_path.endswith('.ipa'):
                extract_app_under_test_dir = bundle_util.ExtractApp(
                    app_under_test_path, working_dir)
                shutil.move(extract_app_under_test_dir, app_under_test_dir)
            elif not os.path.abspath(app_under_test_path).startswith(
                    working_dir):
                # Only copies the app under test if it is not in working directory.
                shutil.copytree(app_under_test_path, app_under_test_dir)
            else:
                app_under_test_dir = app_under_test_path

    if not os.path.exists(test_bundle_path):
        raise ios_errors.IllegalArgumentError(
            'The test bundle does not exists: %s' % test_bundle_path)
    if not (test_bundle_path.endswith('.xctest')
            or test_bundle_path.endswith('.ipa')
            or test_bundle_path.endswith('.zip')):
        raise ios_errors.IllegalArgumentError(
            'The test bundle %s should be with .xctest, .ipa or .zip extension.'
            % test_bundle_path)

    bundle_copied = False
    test_bundle_dir = os.path.join(
        working_dir,
        os.path.splitext(os.path.basename(test_bundle_path))[0] + '.xctest')
    if not os.path.exists(test_bundle_dir):
        if test_bundle_path.endswith('.ipa') or test_bundle_path.endswith(
                '.zip'):
            extract_test_bundle_dir = bundle_util.ExtractTestBundle(
                test_bundle_path, working_dir)
            shutil.move(extract_test_bundle_dir, test_bundle_dir)
            bundle_copied = True
        elif not os.path.abspath(test_bundle_path).startswith(working_dir):
            # Only copies the test bundle if it is not in working directory.
            shutil.copytree(test_bundle_path, test_bundle_dir)
            bundle_copied = True
        else:
            test_bundle_dir = test_bundle_path
    plugins_path = os.path.join(test_bundle_dir, "PlugIns")
    if not app_under_test_dir and os.path.isdir(plugins_path):
        try:
            app = next(
                filter(lambda x: x.endswith(".app"), os.listdir(plugins_path)))
            app = os.path.join(plugins_path, app)
            app_dest_dir = os.path.join(
                working_dir,
                os.path.splitext(os.path.basename(app))[0] + '.app')
            if bundle_copied:
                shutil.move(app, app_dest_dir)
            else:
                shutil.copy(app, app_dest_dir)
            # Only set this after the copy/move has successfully completed
            app_under_test_dir = app_dest_dir
        except StopIteration:
            pass

    return app_under_test_dir, test_bundle_dir