text: # -*- coding: utf-8 -*- #
  # Copyright 2017 Google LLC. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  """A shared library for processing and validating Android test arguments."""
  
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import unicode_literals
  
  from googlecloudsdk.api_lib.firebase.test import arg_file
  from googlecloudsdk.api_lib.firebase.test import arg_util
  from googlecloudsdk.api_lib.firebase.test import arg_validate
  from googlecloudsdk.api_lib.firebase.test.android import catalog_manager
  from googlecloudsdk.calliope import exceptions
  
  
  def TypedArgRules():
    """Returns the rules for Android test args which depend on the test type.
  
    This dict is declared in a function rather than globally to avoid garbage
    collection issues during unit tests.
  
    Returns:
      A dict keyed by whether type-specific args are required or optional, and
      with a nested dict containing any default values for those args.
    """
    return {
        'instrumentation': {
            'required': ['test'],
            'optional': [
                'num_uniform_shards', 'test_targets_for_shard', 'test_package',
                'test_runner_class', 'test_targets', 'use_orchestrator'
            ],
            'defaults': {}
        },
        'robo': {
            'required': [],
            'optional': ['robo_directives', 'robo_script'],
            'defaults': {}
        },
        'game-loop': {
            'required': [],
            'optional': ['scenario_numbers', 'scenario_labels'],
            'defaults': {}
        },
    }
  
  
  def SharedArgRules():
    """Returns the rules for Android test args which are shared by all test types.
  
    This dict is declared in a function rather than globally to avoid garbage
    collection issues during unit tests.
  
    Returns:
      A dict keyed by whether shared args are required or optional, and with a
      nested dict containing any default values for those shared args.
    """
    return {
        'required': ['type', 'app'],
        'optional': [
            'additional_apks',
            'app_package',
            'async_',
            'auto_google_login',
            'client_details',
            'device',
            'device_ids',
            'directories_to_pull',
            'environment_variables',
            'locales',
            'network_profile',
            'num_flaky_test_attempts',
            'obb_files',
            'orientations',
            'os_version_ids',
            'other_files',
            'performance_metrics',
            'record_video',
            'results_bucket',
            'results_dir',
            'results_history_name',
            'timeout',
        ],
        'defaults': {
            'async_': False,
            'auto_google_login': True,
            'num_flaky_test_attempts': 0,
            'performance_metrics': True,
            'record_video': True,
            'timeout': 900,  # 15 minutes
        }
    }
  
  
  def AllArgsSet():
    """Returns a set containing the names of every Android test arg."""
    return arg_util.GetSetOfAllTestArgs(TypedArgRules(), SharedArgRules())
  
  
  class AndroidArgsManager(object):
    """Manages test arguments for Android devices."""
  
    def __init__(self,
                 catalog_mgr=None,
                 typed_arg_rules=None,
                 shared_arg_rules=None):
      """Constructs an AndroidArgsManager for a single Android test matrix.
  
      Args:
        catalog_mgr: an AndroidCatalogManager object.
        typed_arg_rules: a nested dict of dicts which are keyed first on the test
          type, then by whether args are required or optional, and what their
          default values are. If None, the default from TypedArgRules() is used.
        shared_arg_rules: a dict keyed by whether shared args are required or
          optional, and with a nested dict containing any default values for those
          shared args. If None, the default dict from SharedArgRules() is used.
      """
      self._catalog_mgr = catalog_mgr or catalog_manager.AndroidCatalogManager()
      self._typed_arg_rules = typed_arg_rules or TypedArgRules()
      self._shared_arg_rules = shared_arg_rules or SharedArgRules()
  
    def Prepare(self, args):
      """Load, apply defaults, and perform validation on test arguments.
  
      Args:
        args: an argparse namespace. All the arguments that were provided to this
          gcloud command invocation (i.e. group and command arguments combined).
          Arg values from an optional arg-file and/or arg default values may be
          added to this argparse namespace.
  
      Raises:
        InvalidArgumentException: If an argument name is unknown, an argument does
          not contain a valid value, or an argument is not valid when used with
          the given type of test.
        RequiredArgumentException: If a required arg is missing.
      """
      all_test_args_set = arg_util.GetSetOfAllTestArgs(self._typed_arg_rules,
                                                       self._shared_arg_rules)
      args_from_file = arg_file.GetArgsFromArgFile(args.argspec,
                                                   all_test_args_set)
      arg_util.ApplyLowerPriorityArgs(args, args_from_file, True)
  
      test_type = self.GetTestTypeOrRaise(args)
      self._CheckForConflictingArgs(args)
  
      typed_arg_defaults = self._typed_arg_rules[test_type]['defaults']
      shared_arg_defaults = self._shared_arg_rules['defaults']
      arg_util.ApplyLowerPriorityArgs(args, typed_arg_defaults)
      arg_util.ApplyLowerPriorityArgs(args, shared_arg_defaults)
      self._ApplyLegacyMatrixDimensionDefaults(args)
  
      arg_validate.ValidateArgsForTestType(args, test_type, self._typed_arg_rules,
                                           self._shared_arg_rules,
                                           all_test_args_set)
      arg_validate.ValidateOsVersions(args, self._catalog_mgr)
      arg_validate.ValidateDeviceList(args, self._catalog_mgr)
      arg_validate.ValidateResultsBucket(args)
      arg_validate.ValidateResultsDir(args)
      arg_validate.NormalizeAndValidateObbFileNames(args.obb_files)
      arg_validate.ValidateRoboDirectivesList(args)
      arg_validate.ValidateEnvironmentVariablesList(args)
      arg_validate.ValidateTestTargetsForShard(args)
      arg_validate.NormalizeAndValidateDirectoriesToPullList(
          args.directories_to_pull)
      arg_validate.ValidateScenarioNumbers(args)
  
    def GetTestTypeOrRaise(self, args):
      """If the test type is not user-specified, infer the most reasonable value.
  
      Args:
        args: an argparse namespace. All the arguments that were provided to this
          gcloud command invocation (i.e. group and command arguments combined).
  
      Returns:
        The type of the test to be run (e.g. 'robo' or 'instrumentation') and
        sets the 'type' arg if it was not user-specified.
  
      Raises:
        InvalidArgumentException if an explicit test type is invalid.
      """
      if not args.type:
        args.type = 'instrumentation' if args.test else 'robo'
      if args.type not in self._typed_arg_rules:
        raise exceptions.InvalidArgumentException(
            'type', "'{0}' is not a valid test type.".format(args.type))
      return args.type
  
    def _CheckForConflictingArgs(self, args):
      """Check for any args that cannot appear simultaneously."""
      if args.device:
        # If using sparse matrix syntax, can't also use legacy dimension flags.
        if args.device_ids:
          raise exceptions.ConflictingArgumentsException('--device-ids',
                                                         '--device')
        if args.os_version_ids:
          raise exceptions.ConflictingArgumentsException('--os-version-ids',
                                                         '--device')
        if args.locales:
          raise exceptions.ConflictingArgumentsException('--locales', '--device')
        if args.orientations:
          raise exceptions.ConflictingArgumentsException('--orientations',
                                                         '--device')
  
    def _ApplyLegacyMatrixDimensionDefaults(self, args):
      """Apply defaults to each dimension flag only if not using sparse matrix."""
      if args.device:
        return
      # If --device is unset and all of the legacy dimension flags are unset,
      # then we want to use --device by default. So don't apply any defaults to
      # the legacy dimension flags here, which would cause conflicting args.
      if not (args.device_ids or args.os_version_ids or args.locales or
              args.orientations):
        args.device = [{}]  # Default device dimensions will be filled in later.
        return
  
      if not args.device_ids:
        args.device_ids = [self._catalog_mgr.GetDefaultModel()]
      if not args.os_version_ids:
        args.os_version_ids = [self._catalog_mgr.GetDefaultVersion()]
      if not args.locales:
        args.locales = [self._catalog_mgr.GetDefaultLocale()]
      if not args.orientations:
        args.orientations = [self._catalog_mgr.GetDefaultOrientation()]
  