compat.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import os
  2. import re
  3. import sys
  4. import types
  5. import functools
  6. def ensure_in_path(path):
  7. """
  8. Ensure that a given path is in the sys.path array
  9. """
  10. if not os.path.isdir(path):
  11. raise RuntimeError('Tried to add nonexisting path')
  12. def _samefile(x, y):
  13. try:
  14. return os.path.samefile(x, y)
  15. except OSError:
  16. return False
  17. except AttributeError:
  18. # Probably on Windows.
  19. path1 = os.path.abspath(x).lower()
  20. path2 = os.path.abspath(y).lower()
  21. return path1 == path2
  22. # Remove existing copies of it.
  23. for pth in sys.path:
  24. if _samefile(pth, path):
  25. sys.path.remove(pth)
  26. # Add it at the beginning.
  27. sys.path.insert(0, path)
  28. # Check if pytest is imported. If so, we use it to create marking decorators.
  29. # If not, we just create a function that does nothing.
  30. try:
  31. import pytest
  32. except ImportError:
  33. pytest = None
  34. if pytest is not None:
  35. slow_test = pytest.mark.slow_test
  36. xfail = pytest.mark.xfail
  37. else:
  38. slow_test = lambda x: x
  39. def xfail(*args, **kwargs):
  40. if len(args) > 0 and isinstance(args[0], types.FunctionType):
  41. return args[0]
  42. return lambda x: x
  43. # We don't use the pytest parametrizing function, since it seems to break
  44. # with unittest.TestCase subclasses.
  45. def parametrize(field_names, field_values):
  46. # If we're not given a list of field names, we make it.
  47. if not isinstance(field_names, (tuple, list)):
  48. field_names = (field_names,)
  49. field_values = [(val,) for val in field_values]
  50. # Create a decorator that saves this list of field names and values on the
  51. # function for later parametrizing.
  52. def decorator(func):
  53. func.__dict__['param_names'] = field_names
  54. func.__dict__['param_values'] = field_values
  55. return func
  56. return decorator
  57. # This is a metaclass that actually performs the parametrization.
  58. class ParametrizingMetaclass(type):
  59. IDENTIFIER_RE = re.compile('[^A-Za-z0-9]')
  60. def __new__(klass, name, bases, attrs):
  61. new_attrs = attrs.copy()
  62. for attr_name, attr in attrs.items():
  63. # We only care about functions
  64. if not isinstance(attr, types.FunctionType):
  65. continue
  66. param_names = attr.__dict__.pop('param_names', None)
  67. param_values = attr.__dict__.pop('param_values', None)
  68. if param_names is None or param_values is None:
  69. continue
  70. # Create multiple copies of the function.
  71. for i, values in enumerate(param_values):
  72. assert len(param_names) == len(values)
  73. # Get a repr of the values, and fix it to be a valid identifier
  74. human = '_'.join(
  75. [klass.IDENTIFIER_RE.sub('', repr(x)) for x in values]
  76. )
  77. # Create a new name.
  78. # new_name = attr.__name__ + "_%d" % i
  79. new_name = attr.__name__ + "__" + human
  80. # Create a replacement function.
  81. def create_new_func(func, names, values):
  82. # Create a kwargs dictionary.
  83. kwargs = dict(zip(names, values))
  84. @functools.wraps(func)
  85. def new_func(self):
  86. return func(self, **kwargs)
  87. # Manually set the name and return the new function.
  88. new_func.__name__ = new_name
  89. return new_func
  90. # Actually create the new function.
  91. new_func = create_new_func(attr, param_names, values)
  92. # Save this new function in our attrs dict.
  93. new_attrs[new_name] = new_func
  94. # Remove the old attribute from our new dictionary.
  95. del new_attrs[attr_name]
  96. # We create the class as normal, except we use our new attributes.
  97. return type.__new__(klass, name, bases, new_attrs)
  98. # This is a class decorator that actually applies the above metaclass.
  99. def parametrize_class(klass):
  100. return ParametrizingMetaclass(klass.__name__,
  101. klass.__bases__,
  102. klass.__dict__)