|  | # Copyright (c) 2016 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | import contextlib | 
|  | import json | 
|  | import logging | 
|  | import os | 
|  | import re | 
|  | import shutil | 
|  | import signal | 
|  | import subprocess | 
|  | import sys | 
|  | import tempfile | 
|  | import time | 
|  |  | 
|  |  | 
|  | def VerboseCompileRegexOrAbort(regex): | 
|  | """Compiles a user-provided regular expression, exits the program on error.""" | 
|  | try: | 
|  | return re.compile(regex) | 
|  | except re.error as e: | 
|  | sys.stderr.write('invalid regex: {}\n{}\n'.format(regex, e)) | 
|  | sys.exit(2) | 
|  |  | 
|  |  | 
|  | def PollFor(condition, condition_name, interval=5): | 
|  | """Polls for a function to return true. | 
|  |  | 
|  | Args: | 
|  | condition: Function to wait its return to be True. | 
|  | condition_name: The condition's name used for logging. | 
|  | interval: Periods to wait between tries in seconds. | 
|  |  | 
|  | Returns: | 
|  | What condition has returned to stop waiting. | 
|  | """ | 
|  | while True: | 
|  | result = condition() | 
|  | logging.info('Polling condition %s is %s' % ( | 
|  | condition_name, 'met' if result else 'not met')) | 
|  | if result: | 
|  | return result | 
|  | time.sleep(interval) | 
|  |  | 
|  |  | 
|  | def SerializeAttributesToJsonDict(json_dict, instance, attributes): | 
|  | """Adds the |attributes| from |instance| to a |json_dict|. | 
|  |  | 
|  | Args: | 
|  | json_dict: (dict) Dict to update. | 
|  | instance: (object) instance to take the values from. | 
|  | attributes: ([str]) List of attributes to serialize. | 
|  |  | 
|  | Returns: | 
|  | json_dict | 
|  | """ | 
|  | json_dict.update({attr: getattr(instance, attr) for attr in attributes}) | 
|  | return json_dict | 
|  |  | 
|  |  | 
|  | def DeserializeAttributesFromJsonDict(json_dict, instance, attributes): | 
|  | """Sets a list of |attributes| in |instance| according to their value in | 
|  | |json_dict|. | 
|  |  | 
|  | Args: | 
|  | json_dict: (dict) Dict containing values dumped by | 
|  | SerializeAttributesToJsonDict. | 
|  | instance: (object) instance to modify. | 
|  | attributes: ([str]) List of attributes to set. | 
|  |  | 
|  | Raises: | 
|  | AttributeError if one of the attribute doesn't exist in |instance|. | 
|  |  | 
|  | Returns: | 
|  | instance | 
|  | """ | 
|  | for attr in attributes: | 
|  | getattr(instance, attr) # To raise AttributeError if attr doesn't exist. | 
|  | setattr(instance, attr, json_dict[attr]) | 
|  | return instance | 
|  |  | 
|  |  | 
|  | @contextlib.contextmanager | 
|  | def TemporaryDirectory(suffix='', prefix='tmp'): | 
|  | """Returns a freshly-created directory that gets automatically deleted after | 
|  | usage. | 
|  | """ | 
|  | name = tempfile.mkdtemp(suffix=suffix, prefix=prefix) | 
|  | try: | 
|  | yield name | 
|  | finally: | 
|  | shutil.rmtree(name) | 
|  |  | 
|  |  | 
|  | def EnsureParentDirectoryExists(path): | 
|  | """Verifies that the parent directory exists or creates it if missing.""" | 
|  | parent_directory_path = os.path.abspath(os.path.dirname(path)) | 
|  | if not os.path.isdir(parent_directory_path): | 
|  | os.makedirs(parent_directory_path) | 
|  |  | 
|  |  | 
|  | def GetCommandLineForLogging(cmd, env_diff=None): | 
|  | """Get command line string. | 
|  |  | 
|  | Args: | 
|  | cmd: Command line argument | 
|  | env_diff: Environment modification for the command line. | 
|  |  | 
|  | Returns: | 
|  | Command line string. | 
|  | """ | 
|  | cmd_str = '' | 
|  | if env_diff: | 
|  | for key, value in env_diff.iteritems(): | 
|  | cmd_str += '{}={} '.format(key, value) | 
|  | return cmd_str + subprocess.list2cmdline(cmd) | 
|  |  | 
|  |  | 
|  | # TimeoutError inherit from BaseException to pass through DevUtils' retries | 
|  | # decorator that catches only exceptions inheriting from Exception. | 
|  | class TimeoutError(BaseException): | 
|  | pass | 
|  |  | 
|  |  | 
|  | # If this exception is ever raised, then might be better to replace this | 
|  | # implementation with Thread.join(timeout=XXX). | 
|  | class TimeoutCollisionError(Exception): | 
|  | pass | 
|  |  | 
|  |  | 
|  | @contextlib.contextmanager | 
|  | def TimeoutScope(seconds, error_name): | 
|  | """Raises TimeoutError if the with statement is finished within |seconds|.""" | 
|  | assert seconds > 0 | 
|  | def _signal_callback(signum, frame): | 
|  | del signum, frame # unused. | 
|  | raise TimeoutError(error_name) | 
|  |  | 
|  | try: | 
|  | signal.signal(signal.SIGALRM, _signal_callback) | 
|  | if signal.alarm(seconds) != 0: | 
|  | raise TimeoutCollisionError( | 
|  | 'Discarding an alarm that was scheduled before.') | 
|  | yield | 
|  | finally: | 
|  | signal.alarm(0) | 
|  | if signal.getsignal(signal.SIGALRM) != _signal_callback: | 
|  | raise TimeoutCollisionError('Looks like there is a signal.signal(signal.' | 
|  | 'SIGALRM) made within the with statement.') |