File: //snap/google-cloud-cli/396/lib/googlecloudsdk/api_lib/firebase/test/util.py
# -*- 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 to support implementation of Firebase Test Lab commands."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.firebase.test import exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import exceptions as calliope_exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
OUTCOMES_FORMAT = """
table[box](
outcome.color(red=Fail, green=Pass, blue=Flaky, yellow=Inconclusive),
axis_value:label=TEST_AXIS_VALUE,
test_details:label=TEST_DETAILS
)
"""
def GetError(error):
"""Returns a ready-to-print string representation from the http response.
Args:
error: the Http error response, whose content is a JSON-format string for
most cases (e.g. invalid test dimension), but can be just a string other
times (e.g. invalid URI for CLOUDSDK_TEST_ENDPOINT).
Returns:
A ready-to-print string representation of the error.
"""
try:
data = json.loads(error.content)
except ValueError: # message is not JSON
return error.content
code = data['error']['code']
message = data['error']['message']
return 'ResponseError {0}: {1}'.format(code, message)
def GetErrorCodeAndMessage(error):
"""Returns the individual error code and message from a JSON http response.
Prefer using GetError(error) unless you need to examine the error code and
take specific action on it.
Args:
error: the Http error response, whose content is a JSON-format string.
Returns:
(code, msg) A tuple holding the error code and error message string.
Raises:
ValueError: if the error is not in JSON format.
"""
data = json.loads(error.content)
return data['error']['code'], data['error']['message']
def GetProject():
"""Get the user's project id from the core project properties.
Returns:
The id of the GCE project to use while running the test.
Raises:
MissingProjectError: if the user did not specify a project id via the
--project flag or via running "gcloud config set project PROJECT_ID".
"""
project = properties.VALUES.core.project.Get()
if not project:
raise exceptions.MissingProjectError(
'No project specified. Please add --project PROJECT_ID to the command'
' line or first run\n $ gcloud config set project PROJECT_ID')
return project
def GetDeviceIpBlocks(context=None):
"""Gets the device IP block catalog from the TestEnvironmentDiscoveryService.
Args:
context: {str:object}, The current context, which is a set of key-value
pairs that can be used for common initialization among commands.
Returns:
The device IP block catalog
Raises:
calliope_exceptions.HttpException: If it could not connect to the service.
"""
if context:
client = context['testing_client']
messages = context['testing_messages']
else:
client = apis.GetClientInstance('testing', 'v1')
messages = apis.GetMessagesModule('testing', 'v1')
env_type = (
messages.TestingTestEnvironmentCatalogGetRequest
.EnvironmentTypeValueValuesEnum.DEVICE_IP_BLOCKS)
return _GetCatalog(client, messages, env_type).deviceIpBlockCatalog
def GetAndroidCatalog(context=None):
"""Gets the Android catalog from the TestEnvironmentDiscoveryService.
Args:
context: {str:object}, The current context, which is a set of key-value
pairs that can be used for common initialization among commands.
Returns:
The android catalog.
Raises:
calliope_exceptions.HttpException: If it could not connect to the service.
"""
if context:
client = context['testing_client']
messages = context['testing_messages']
else:
client = apis.GetClientInstance('testing', 'v1')
messages = apis.GetMessagesModule('testing', 'v1')
env_type = (
messages.TestingTestEnvironmentCatalogGetRequest.
EnvironmentTypeValueValuesEnum.ANDROID)
return _GetCatalog(client, messages, env_type).androidDeviceCatalog
def GetIosCatalog(context=None):
"""Gets the iOS catalog from the TestEnvironmentDiscoveryService.
Args:
context: {str:object}, The current context, which is a set of key-value
pairs that can be used for common initialization among commands.
Returns:
The iOS catalog.
Raises:
calliope_exceptions.HttpException: If it could not connect to the service.
"""
if context:
client = context['testing_client']
messages = context['testing_messages']
else:
client = apis.GetClientInstance('testing', 'v1')
messages = apis.GetMessagesModule('testing', 'v1')
env_type = (
messages.TestingTestEnvironmentCatalogGetRequest.
EnvironmentTypeValueValuesEnum.IOS)
return _GetCatalog(client, messages, env_type).iosDeviceCatalog
def GetNetworkProfileCatalog(context=None):
"""Gets the network profile catalog from the TestEnvironmentDiscoveryService.
Args:
context: {str:object}, The current context, which is a set of key-value
pairs that can be used for common initialization among commands.
Returns:
The network profile catalog.
Raises:
calliope_exceptions.HttpException: If it could not connect to the service.
"""
if context:
client = context['testing_client']
messages = context['testing_messages']
else:
client = apis.GetClientInstance('testing', 'v1')
messages = apis.GetMessagesModule('testing', 'v1')
env_type = (
messages.TestingTestEnvironmentCatalogGetRequest.
EnvironmentTypeValueValuesEnum.NETWORK_CONFIGURATION)
return _GetCatalog(client, messages, env_type).networkConfigurationCatalog
def _GetCatalog(client, messages, environment_type):
"""Gets a test environment catalog from the TestEnvironmentDiscoveryService.
Args:
client: The Testing API client object.
messages: The Testing API messages object.
environment_type: {enum} which EnvironmentType catalog to get.
Returns:
The test environment catalog.
Raises:
calliope_exceptions.HttpException: If it could not connect to the service.
"""
project_id = properties.VALUES.core.project.Get()
request = messages.TestingTestEnvironmentCatalogGetRequest(
environmentType=environment_type,
projectId=project_id)
try:
return client.testEnvironmentCatalog.Get(request)
except apitools_exceptions.HttpError as error:
raise calliope_exceptions.HttpException(
'Unable to access the test environment catalog: ' + GetError(error))
except:
# Give the user some explanation in case we get a vague/unexpected error,
# such as a socket.error from httplib2.
log.error('Unable to access the Firebase Test Lab environment catalog.')
raise # Re-raise the error in case Calliope can do something with it.
def ParseRoboDirectiveKey(key):
"""Returns a tuple representing a directive's type and resource name.
Args:
key: the directive key, which can be "<type>:<resource>" or "<resource>"
Returns:
A tuple of the directive's parsed type and resource name. If no type is
specified, "text" will be returned as the default type.
Raises:
InvalidArgException: if the input format is incorrect or if the specified
type is unsupported.
"""
parts = key.split(':')
resource_name = parts[-1]
if len(parts) > 2:
# Invalid format: at most one ':' is allowed.
raise exceptions.InvalidArgException(
'robo_directives', 'Invalid format for key [{0}]. '
'Use a colon only to separate action type and resource name.'.format(
key))
if len(parts) == 1:
# Format: '<resource_name>=<input_text>' defaults to 'text'
action_type = 'text'
else:
# Format: '<type>:<resource_name>=<input_value>'
action_type = parts[0]
supported_action_types = ['text', 'click', 'ignore']
if action_type not in supported_action_types:
raise exceptions.InvalidArgException(
'robo_directives',
'Unsupported action type [{0}]. Please choose one of [{1}]'.format(
action_type, ', '.join(supported_action_types)))
return (action_type, resource_name)
def GetDeprecatedTagWarning(models, platform='android'):
"""Returns a warning string iff any device model is marked deprecated."""
for model in models:
for tag in model.tags:
if 'deprecated' in tag:
return ('Some devices are deprecated. Learn more at https://firebase.'
'google.com/docs/test-lab/%s/'
'available-testing-devices#deprecated' % platform)
return None
def GetRelativeDevicePath(device_path):
"""Returns the relative device path that can be joined with GCS bucket."""
return device_path[1:] if device_path.startswith('/') else device_path