File: //snap/google-cloud-cli/current/lib/googlecloudsdk/api_lib/firebase/test/ios/matrix_creator.py
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Create iOS test matrices in Firebase Test Lab."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
import uuid
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.firebase.test import matrix_creator_common
from googlecloudsdk.api_lib.firebase.test import matrix_ops
from googlecloudsdk.api_lib.firebase.test import util
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.core import log
def CreateMatrix(args, context, history_id, gcs_results_root, release_track):
"""Creates a new iOS matrix test in Firebase Test Lab from the user's params.
Args:
args: an argparse namespace. All the arguments that were provided to this
gcloud command invocation (i.e. group and command arguments combined).
context: {str:obj} dict containing the gcloud command context, which
includes the Testing API client+messages libs generated by Apitools.
history_id: {str} A history ID to publish Tool Results to.
gcs_results_root: the root dir for a matrix within the GCS results bucket.
release_track: the release track that the command is invoked from.
Returns:
A TestMatrix object created from the supplied matrix configuration values.
"""
creator = MatrixCreator(args, context, history_id, gcs_results_root,
release_track)
return creator.CreateTestMatrix(uuid.uuid4().hex)
class MatrixCreator(object):
"""Creates a single iOS test matrix based on user-supplied test arguments."""
def __init__(self, args, context, history_id, gcs_results_root,
release_track):
"""Construct an MatrixCreator to be used to create a single test matrix.
Args:
args: an argparse namespace. All the arguments that were provided to this
gcloud command invocation (i.e. group and command arguments combined).
context: {str:obj} dict containing the gcloud command context, which
includes the Testing API client+messages libs generated by Apitools.
history_id: {str} A history ID to publish Tool Results to.
gcs_results_root: the root dir for a matrix within the GCS results bucket.
release_track: the release track that the command is invoked from.
"""
self._project = util.GetProject()
self._args = args
self._history_id = history_id
self._gcs_results_root = gcs_results_root
self._client = context['testing_client']
self._messages = context['testing_messages']
self._release_track = release_track
def _BuildFileReference(self, filename, use_basename=True):
"""Build a FileReference pointing to a file in GCS."""
if not filename:
return None
if use_basename:
filename = os.path.basename(filename)
path = os.path.join(self._gcs_results_root, filename)
return self._messages.FileReference(gcsPath=path)
def _BuildGenericTestSetup(self):
"""Build an IosTestSetup for an iOS test."""
additional_ipas = [
self._BuildFileReference(os.path.basename(additional_ipa))
for additional_ipa in getattr(self._args, 'additional_ipas', []) or []
]
directories_to_pull = []
for directory in getattr(self._args, 'directories_to_pull', []) or []:
if ':' in directory:
bundle, path = directory.split(':')
directories_to_pull.append(
self._messages.IosDeviceFile(bundleId=bundle, devicePath=path))
else:
directories_to_pull.append(
self._messages.IosDeviceFile(devicePath=directory))
device_files = []
other_files = getattr(self._args, 'other_files', None) or {}
for device_path in other_files.keys():
# Device paths are be prefixed by the bundle ID if they refer to an app's
# sandboxed filesystem, separated with the device path by ':'
idx = device_path.find(':')
bundle_id = device_path[:idx] if idx != -1 else None
path = device_path[idx + 1:] if idx != -1 else device_path
device_files.append(
self._messages.IosDeviceFile(
content=self._BuildFileReference(
util.GetRelativeDevicePath(path), use_basename=False),
bundleId=bundle_id,
devicePath=path))
return self._messages.IosTestSetup(
networkProfile=getattr(self._args, 'network_profile', None),
additionalIpas=additional_ipas,
pushFiles=device_files,
pullDirectories=directories_to_pull)
def _BuildIosXcTestSpec(self):
"""Build a TestSpecification for an IosXcTest."""
spec = self._messages.TestSpecification(
disableVideoRecording=not self._args.record_video,
iosTestSetup=self._BuildGenericTestSetup(),
testTimeout=matrix_ops.ReformatDuration(self._args.timeout),
iosXcTest=self._messages.IosXcTest(
testsZip=self._BuildFileReference(self._args.test),
xctestrun=self._BuildFileReference(self._args.xctestrun_file),
xcodeVersion=self._args.xcode_version,
testSpecialEntitlements=getattr(self._args,
'test_special_entitlements',
False)))
return spec
def _BuildIosTestLoopTestSpec(self):
"""Build a TestSpecification for an IosXcTest."""
spec = self._messages.TestSpecification(
disableVideoRecording=not self._args.record_video,
iosTestSetup=self._BuildGenericTestSetup(),
testTimeout=matrix_ops.ReformatDuration(self._args.timeout),
iosTestLoop=self._messages.IosTestLoop(
appIpa=self._BuildFileReference(self._args.app),
scenarios=self._args.scenario_numbers))
return spec
def _BuildIosRoboTestSpec(self):
"""Build a TestSpecification for an iOS Robo test."""
spec = self._messages.TestSpecification(
disableVideoRecording=not self._args.record_video,
iosTestSetup=self._BuildGenericTestSetup(),
testTimeout=matrix_ops.ReformatDuration(self._args.timeout),
iosRoboTest=self._messages.IosRoboTest(
appIpa=self._BuildFileReference(self._args.app)))
if getattr(self._args, 'robo_script', None):
spec.iosRoboTest.roboScript = self._BuildFileReference(
os.path.basename(self._args.robo_script))
return spec
def _TestSpecFromType(self, test_type):
"""Map a test type into its corresponding TestSpecification message ."""
if test_type == 'xctest':
return self._BuildIosXcTestSpec()
elif test_type == 'game-loop':
return self._BuildIosTestLoopTestSpec()
elif test_type == 'robo':
return self._BuildIosRoboTestSpec()
else: # It's a bug in our arg validation if we ever get here.
raise exceptions.InvalidArgumentException(
'type', 'Unknown test type "{}".'.format(test_type))
def _BuildTestMatrix(self, spec):
"""Build just the user-specified parts of an iOS TestMatrix message.
Args:
spec: a TestSpecification message corresponding to the test type.
Returns:
A TestMatrix message.
"""
devices = [self._BuildIosDevice(d) for d in self._args.device]
environment_matrix = self._messages.EnvironmentMatrix(
iosDeviceList=self._messages.IosDeviceList(iosDevices=devices))
gcs = self._messages.GoogleCloudStorage(gcsPath=self._gcs_results_root)
hist = self._messages.ToolResultsHistory(
projectId=self._project, historyId=self._history_id)
results = self._messages.ResultStorage(
googleCloudStorage=gcs, toolResultsHistory=hist)
client_info = matrix_creator_common.BuildClientInfo(
self._messages,
getattr(self._args, 'client_details', {}) or {}, self._release_track)
return self._messages.TestMatrix(
testSpecification=spec,
environmentMatrix=environment_matrix,
clientInfo=client_info,
resultStorage=results,
flakyTestAttempts=self._args.num_flaky_test_attempts or 0)
def _BuildIosDevice(self, device_map):
return self._messages.IosDevice(
iosModelId=device_map['model'],
iosVersionId=device_map['version'],
locale=device_map['locale'],
orientation=device_map['orientation'])
def _BuildTestMatrixRequest(self, request_id):
"""Build a TestingProjectsTestMatricesCreateRequest for a test matrix.
Args:
request_id: {str} a unique ID for the CreateTestMatrixRequest.
Returns:
A TestingProjectsTestMatricesCreateRequest message.
"""
spec = self._TestSpecFromType(self._args.type)
return self._messages.TestingProjectsTestMatricesCreateRequest(
projectId=self._project,
testMatrix=self._BuildTestMatrix(spec),
requestId=request_id)
def CreateTestMatrix(self, request_id):
"""Invoke the Testing service to create a test matrix from the user's args.
Args:
request_id: {str} a unique ID for the CreateTestMatrixRequest.
Returns:
The TestMatrix response message from the TestMatrices.Create rpc.
Raises:
HttpException if the test service reports an HttpError.
"""
request = self._BuildTestMatrixRequest(request_id)
log.debug('TestMatrices.Create request:\n{0}\n'.format(request))
try:
response = self._client.projects_testMatrices.Create(request)
log.debug('TestMatrices.Create response:\n{0}\n'.format(response))
except apitools_exceptions.HttpError as error:
msg = 'Http error while creating test matrix: ' + util.GetError(error)
raise exceptions.HttpException(msg)
log.status.Print('Test [{id}] has been created in the Google Cloud.'.format(
id=response.testMatrixId))
return response