blob: 8388551c14b536183815cd0a1d4fa85af7ebf9d9 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2018 - The Android Open Source Project
#
# 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.
import os
import shutil
import tempfile
from acts import logger
from acts.libs.proc import job
_UICD_JAR_CMD = 'java -jar %s/uicd-commandline.jar'
_UNZIP_CMD = 'tar -xzf %s -C %s'
class UicdError(Exception):
"""Raised for exceptions that occur in UIConductor-related tasks"""
class UicdCli(object):
"""Provides an interface for running UIConductor (Uicd) workflows under its
CLI.
This class does not handle workflow creation, which requires the Uicd
frontend.
"""
def __init__(self, uicd_zip, workflow_paths, log_path=None):
"""Creates a UicdCli object. Extracts the required uicd-cli binaries.
Args:
uicd_zip: The path to uicd_cli.tar.gz
workflow_paths: List of paths to uicd workflows and/or directories
containing them.
log_path: Directory for storing logs generated by Uicd.
"""
self._uicd_zip = uicd_zip[0] if isinstance(uicd_zip, list) else uicd_zip
self._uicd_path = tempfile.mkdtemp(prefix='uicd')
self._log_path = log_path
if self._log_path:
os.makedirs(self._log_path, exist_ok=True)
self._log = logger.create_tagged_trace_logger(tag='Uicd')
self._set_workflows(workflow_paths)
self._setup_cli()
def _set_workflows(self, workflow_paths):
"""Set up a dictionary that maps workflow name to its file location.
This allows the user to specify workflows to run without having to
provide the full path.
Args:
workflow_paths: List of paths to uicd workflows and/or directories
containing them.
Raises:
UicdError if two or more Uicd workflows share the same file name
"""
if isinstance(workflow_paths, str):
workflow_paths = [workflow_paths]
# get a list of workflow files from specified paths
def _raise(e):
raise e
workflow_files = []
for path in workflow_paths:
if os.path.isfile(path):
workflow_files.append(path)
else:
for (root, _, files) in os.walk(path, onerror=_raise):
for file in files:
workflow_files.append(os.path.join(root, file))
# populate the dictionary
self._workflows = {}
for path in workflow_files:
workflow_name = os.path.basename(path)
if workflow_name in self._workflows.keys():
raise UicdError('Uicd workflows may not share the same name.')
self._workflows[workflow_name] = path
def _setup_cli(self):
"""Extract tar from uicd_zip and place unzipped files in uicd_path.
Raises:
Exception if the extraction fails.
"""
self._log.debug('Extracting uicd-cli binaries from %s' % self._uicd_zip)
unzip_cmd = _UNZIP_CMD % (self._uicd_zip, self._uicd_path)
try:
job.run(unzip_cmd.split())
except job.Error:
self._log.exception('Failed to extract uicd-cli binaries.')
raise
def run(self, serial, workflows, timeout=120):
"""Run specified workflows on the UIConductor CLI.
Args:
serial: Device serial
workflows: List or str of workflows to run.
timeout: Number seconds to wait for command to finish.
"""
base_cmd = _UICD_JAR_CMD % self._uicd_path
if isinstance(workflows, str):
workflows = [workflows]
for workflow_name in workflows:
self._log.info('Running workflow "%s"' % workflow_name)
if workflow_name in self._workflows:
args = '-d %s -i %s' % (serial, self._workflows[workflow_name])
else:
self._log.error(
'The workflow "%s" does not exist.' % workflow_name)
continue
if self._log_path:
args = '%s -o %s' % (args, self._log_path)
cmd = '%s %s' % (base_cmd, args)
try:
result = job.run(cmd.split(), timeout=timeout)
except job.Error:
self._log.exception(
'Failed to run workflow "%s"' % workflow_name)
continue
if result.stdout:
stdout_split = result.stdout.splitlines()
if len(stdout_split) > 2:
self._log.debug('Uicd logs stored at %s' % stdout_split[2])
def __del__(self):
"""Delete the temp directory to Uicd CLI binaries upon ACTS exit."""
shutil.rmtree(self._uicd_path)