1#!/usr/bin/env python 2 3# Copyright (c) 2016 The Chromium Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7import functools 8import inspect 9import os 10import sys 11import time 12import platform 13 14 15def GetCatapultDir(): 16 return os.path.normpath( 17 os.path.join(os.path.dirname(__file__), '..', '..', '..')) 18 19 20def IsRunningOnCrosDevice(): 21 """Returns True if we're on a ChromeOS device.""" 22 lsb_release = '/etc/lsb-release' 23 if sys.platform.startswith('linux') and os.path.exists(lsb_release): 24 with open(lsb_release, 'r') as f: 25 res = f.read() 26 if res.count('CHROMEOS_RELEASE_NAME'): 27 return True 28 return False 29 30 31def GetHostOsName(): 32 if IsRunningOnCrosDevice(): 33 return 'chromeos' 34 elif sys.platform.startswith('linux'): 35 return 'linux' 36 elif sys.platform == 'darwin': 37 return 'mac' 38 elif sys.platform == 'win32': 39 return 'win' 40 41 42def GetHostArchName(): 43 return platform.machine() 44 45 46def _ExecutableExtensions(): 47 # pathext is, e.g. '.com;.exe;.bat;.cmd' 48 exts = os.getenv('PATHEXT').split(';') #e.g. ['.com','.exe','.bat','.cmd'] 49 return [x[1:].upper() for x in exts] #e.g. ['COM','EXE','BAT','CMD'] 50 51 52def IsExecutable(path): 53 if os.path.isfile(path): 54 if hasattr(os, 'name') and os.name == 'nt': 55 return path.split('.')[-1].upper() in _ExecutableExtensions() 56 else: 57 return os.access(path, os.X_OK) 58 else: 59 return False 60 61 62def _AddDirToPythonPath(*path_parts): 63 path = os.path.abspath(os.path.join(*path_parts)) 64 if os.path.isdir(path) and path not in sys.path: 65 # Some callsite that use telemetry assumes that sys.path[0] is the directory 66 # containing the script, so we add these extra paths to right after it. 67 sys.path.insert(1, path) 68 69_AddDirToPythonPath(os.path.join(GetCatapultDir(), 'devil')) 70_AddDirToPythonPath(os.path.join(GetCatapultDir(), 'dependency_manager')) 71_AddDirToPythonPath(os.path.join(GetCatapultDir(), 'third_party', 'mock')) 72# mox3 is needed for pyfakefs usage, but not for pylint. 73_AddDirToPythonPath(os.path.join(GetCatapultDir(), 'third_party', 'mox3')) 74_AddDirToPythonPath( 75 os.path.join(GetCatapultDir(), 'third_party', 'pyfakefs')) 76 77from devil.utils import timeout_retry # pylint: disable=wrong-import-position 78from devil.utils import reraiser_thread # pylint: disable=wrong-import-position 79 80 81# Decorator that adds timeout functionality to a function. 82def Timeout(default_timeout): 83 return lambda func: TimeoutDeco(func, default_timeout) 84 85# Note: Even though the "timeout" keyword argument is the only 86# keyword argument that will need to be given to the decorated function, 87# we still have to use the **kwargs syntax, because we have to use 88# the *args syntax here before (since the decorator decorates functions 89# with different numbers of positional arguments) and Python doesn't allow 90# a single named keyword argument after *args. 91# (e.g., 'def foo(*args, bar=42):' is a syntax error) 92 93def TimeoutDeco(func, default_timeout): 94 @functools.wraps(func) 95 def RunWithTimeout(*args, **kwargs): 96 if 'timeout' in kwargs: 97 timeout = kwargs['timeout'] 98 else: 99 timeout = default_timeout 100 try: 101 return timeout_retry.Run(func, timeout, 0, args=args) 102 except reraiser_thread.TimeoutError: 103 print '%s timed out.' % func.__name__ 104 return False 105 return RunWithTimeout 106 107 108MIN_POLL_INTERVAL_IN_SECONDS = 0.1 109MAX_POLL_INTERVAL_IN_SECONDS = 5 110OUTPUT_INTERVAL_IN_SECONDS = 300 111 112def WaitFor(condition, timeout): 113 """Waits for up to |timeout| secs for the function |condition| to return True. 114 115 Polling frequency is (elapsed_time / 10), with a min of .1s and max of 5s. 116 117 Returns: 118 Result of |condition| function (if present). 119 """ 120 def GetConditionString(): 121 if condition.__name__ == '<lambda>': 122 try: 123 return inspect.getsource(condition).strip() 124 except IOError: 125 pass 126 return condition.__name__ 127 128 # Do an initial check to see if its true. 129 res = condition() 130 if res: 131 return res 132 start_time = time.time() 133 last_output_time = start_time 134 elapsed_time = time.time() - start_time 135 while elapsed_time < timeout: 136 res = condition() 137 if res: 138 return res 139 now = time.time() 140 elapsed_time = now - start_time 141 last_output_elapsed_time = now - last_output_time 142 if last_output_elapsed_time > OUTPUT_INTERVAL_IN_SECONDS: 143 last_output_time = time.time() 144 poll_interval = min(max(elapsed_time / 10., MIN_POLL_INTERVAL_IN_SECONDS), 145 MAX_POLL_INTERVAL_IN_SECONDS) 146 time.sleep(poll_interval) 147 raise TimeoutException('Timed out while waiting %ds for %s.' % 148 (timeout, GetConditionString())) 149 150class TimeoutException(Exception): 151 """The operation failed to complete because of a timeout. 152 153 It is possible that waiting for a longer period of time would result in a 154 successful operation. 155 """ 156 pass 157