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