1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4# pylint: disable=W0212
5
6import functools
7import inspect
8import types
9
10
11def Cache(obj):
12  """Decorator for caching read-only properties.
13
14  Example usage (always returns the same Foo instance):
15    @Cache
16    def CreateFoo():
17      return Foo()
18
19  If CreateFoo() accepts parameters, a separate cached value is maintained
20  for each unique parameter combination.
21
22  Cached methods maintain their cache for the lifetime of the /instance/, while
23  cached functions maintain their cache for the lifetime of the /module/.
24  """
25  @functools.wraps(obj)
26  def Cacher(*args, **kwargs):
27    cacher = args[0] if inspect.getargspec(obj).args[:1] == ['self'] else obj
28    cacher.__cache = cacher.__cache if hasattr(cacher, '__cache') else {}
29    key = str(obj) + str(args) + str(kwargs)
30    if key not in cacher.__cache:
31      cacher.__cache[key] = obj(*args, **kwargs)
32    return cacher.__cache[key]
33  return Cacher
34
35
36def Disabled(*args):
37  """Decorator for disabling tests/benchmarks.
38
39  May be used without args to unconditionally disable:
40    @Disabled  # Unconditionally disabled.
41
42  If args are given, the test will be disabled if ANY of the args match the
43  browser type, OS name or OS version:
44    @Disabled('canary')        # Disabled for canary browsers
45    @Disabled('win')           # Disabled on Windows.
46    @Disabled('win', 'linux')  # Disabled on both Windows and Linux.
47    @Disabled('mavericks')     # Disabled on Mac Mavericks (10.9) only.
48  """
49  def _Disabled(func):
50    if not isinstance(func, types.FunctionType):
51      func._disabled_strings = disabled_strings
52      return func
53    @functools.wraps(func)
54    def wrapper(*args, **kwargs):
55      func(*args, **kwargs)
56    wrapper._disabled_strings = disabled_strings
57    return wrapper
58  if len(args) == 1 and callable(args[0]):
59    disabled_strings = []
60    return _Disabled(args[0])
61  disabled_strings = list(args)
62  for disabled_string in disabled_strings:
63    # TODO(tonyg): Validate that these strings are recognized.
64    assert isinstance(disabled_string, str), '@Disabled accepts a list of strs'
65  return _Disabled
66
67
68def Enabled(*args):
69  """Decorator for enabling tests/benchmarks.
70
71  The test will be enabled if ANY of the args match the browser type, OS name
72  or OS version:
73    @Enabled('canary')        # Enabled only for canary browsers
74    @Enabled('win')           # Enabled only on Windows.
75    @Enabled('win', 'linux')  # Enabled only on Windows or Linux.
76    @Enabled('mavericks')     # Enabled only on Mac Mavericks (10.9).
77  """
78  def _Enabled(func):
79    if not isinstance(func, types.FunctionType):
80      func._enabled_strings = enabled_strings
81      return func
82    @functools.wraps(func)
83    def wrapper(*args, **kwargs):
84      func(*args, **kwargs)
85    wrapper._enabled_strings = enabled_strings
86    return wrapper
87  assert args and not callable(args[0]), '@Enabled requires argumentas'
88  enabled_strings = list(args)
89  for enabled_string in enabled_strings:
90    # TODO(tonyg): Validate that these strings are recognized.
91    assert isinstance(enabled_string, str), '@Enabled accepts a list of strs'
92  return _Enabled
93
94
95def IsEnabled(test, possible_browser):
96  """Returns True iff |test| is enabled given the |possible_browser|.
97
98  Use to respect the @Enabled / @Disabled decorators.
99
100  Args:
101    test: A function or class that may contain _disabled_strings and/or
102          _enabled_strings attributes.
103    possible_browser: A PossibleBrowser to check whether |test| may run against.
104  """
105  platform_attributes = [a.lower() for a in [
106      possible_browser.browser_type,
107      possible_browser.platform.GetOSName(),
108      possible_browser.platform.GetOSVersionName(),
109      ]]
110  if possible_browser.supports_tab_control:
111    platform_attributes.append('has tabs')
112
113  if hasattr(test, '__name__'):
114    name = test.__name__
115  elif hasattr(test, '__class__'):
116    name = test.__class__.__name__
117  else:
118    name = str(test)
119
120  if hasattr(test, '_disabled_strings'):
121    disabled_strings = test._disabled_strings
122    if not disabled_strings:
123      return False  # No arguments to @Disabled means always disable.
124    for disabled_string in disabled_strings:
125      if disabled_string in platform_attributes:
126        print (
127            'Skipping %s because it is disabled for %s. '
128            'You are running %s.' % (name,
129                                     ' and '.join(disabled_strings),
130                                     ' '.join(platform_attributes)))
131        return False
132
133  if hasattr(test, '_enabled_strings'):
134    enabled_strings = test._enabled_strings
135    if not enabled_strings:
136      return True  # No arguments to @Enabled means always enable.
137    for enabled_string in enabled_strings:
138      if enabled_string in platform_attributes:
139        return True
140    print (
141        'Skipping %s because it is only enabled for %s. '
142        'You are running %s.' % (name,
143                                 ' or '.join(enabled_strings),
144                                 ' '.join(platform_attributes)))
145    return False
146
147  return True
148