Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test.py 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import os
  2. import operator
  3. import sys
  4. import contextlib
  5. import itertools
  6. import unittest
  7. from distutils.errors import DistutilsError, DistutilsOptionError
  8. from distutils import log
  9. from unittest import TestLoader
  10. from setuptools.extern import six
  11. from setuptools.extern.six.moves import map, filter
  12. from pkg_resources import (resource_listdir, resource_exists, normalize_path,
  13. working_set, _namespace_packages, evaluate_marker,
  14. add_activation_listener, require, EntryPoint)
  15. from setuptools import Command
  16. __metaclass__ = type
  17. class ScanningLoader(TestLoader):
  18. def __init__(self):
  19. TestLoader.__init__(self)
  20. self._visited = set()
  21. def loadTestsFromModule(self, module, pattern=None):
  22. """Return a suite of all tests cases contained in the given module
  23. If the module is a package, load tests from all the modules in it.
  24. If the module has an ``additional_tests`` function, call it and add
  25. the return value to the tests.
  26. """
  27. if module in self._visited:
  28. return None
  29. self._visited.add(module)
  30. tests = []
  31. tests.append(TestLoader.loadTestsFromModule(self, module))
  32. if hasattr(module, "additional_tests"):
  33. tests.append(module.additional_tests())
  34. if hasattr(module, '__path__'):
  35. for file in resource_listdir(module.__name__, ''):
  36. if file.endswith('.py') and file != '__init__.py':
  37. submodule = module.__name__ + '.' + file[:-3]
  38. else:
  39. if resource_exists(module.__name__, file + '/__init__.py'):
  40. submodule = module.__name__ + '.' + file
  41. else:
  42. continue
  43. tests.append(self.loadTestsFromName(submodule))
  44. if len(tests) != 1:
  45. return self.suiteClass(tests)
  46. else:
  47. return tests[0] # don't create a nested suite for only one return
  48. # adapted from jaraco.classes.properties:NonDataProperty
  49. class NonDataProperty:
  50. def __init__(self, fget):
  51. self.fget = fget
  52. def __get__(self, obj, objtype=None):
  53. if obj is None:
  54. return self
  55. return self.fget(obj)
  56. class test(Command):
  57. """Command to run unit tests after in-place build"""
  58. description = "run unit tests after in-place build"
  59. user_options = [
  60. ('test-module=', 'm', "Run 'test_suite' in specified module"),
  61. ('test-suite=', 's',
  62. "Run single test, case or suite (e.g. 'module.test_suite')"),
  63. ('test-runner=', 'r', "Test runner to use"),
  64. ]
  65. def initialize_options(self):
  66. self.test_suite = None
  67. self.test_module = None
  68. self.test_loader = None
  69. self.test_runner = None
  70. def finalize_options(self):
  71. if self.test_suite and self.test_module:
  72. msg = "You may specify a module or a suite, but not both"
  73. raise DistutilsOptionError(msg)
  74. if self.test_suite is None:
  75. if self.test_module is None:
  76. self.test_suite = self.distribution.test_suite
  77. else:
  78. self.test_suite = self.test_module + ".test_suite"
  79. if self.test_loader is None:
  80. self.test_loader = getattr(self.distribution, 'test_loader', None)
  81. if self.test_loader is None:
  82. self.test_loader = "setuptools.command.test:ScanningLoader"
  83. if self.test_runner is None:
  84. self.test_runner = getattr(self.distribution, 'test_runner', None)
  85. @NonDataProperty
  86. def test_args(self):
  87. return list(self._test_args())
  88. def _test_args(self):
  89. if not self.test_suite and sys.version_info >= (2, 7):
  90. yield 'discover'
  91. if self.verbose:
  92. yield '--verbose'
  93. if self.test_suite:
  94. yield self.test_suite
  95. def with_project_on_sys_path(self, func):
  96. """
  97. Backward compatibility for project_on_sys_path context.
  98. """
  99. with self.project_on_sys_path():
  100. func()
  101. @contextlib.contextmanager
  102. def project_on_sys_path(self, include_dists=[]):
  103. with_2to3 = six.PY3 and getattr(self.distribution, 'use_2to3', False)
  104. if with_2to3:
  105. # If we run 2to3 we can not do this inplace:
  106. # Ensure metadata is up-to-date
  107. self.reinitialize_command('build_py', inplace=0)
  108. self.run_command('build_py')
  109. bpy_cmd = self.get_finalized_command("build_py")
  110. build_path = normalize_path(bpy_cmd.build_lib)
  111. # Build extensions
  112. self.reinitialize_command('egg_info', egg_base=build_path)
  113. self.run_command('egg_info')
  114. self.reinitialize_command('build_ext', inplace=0)
  115. self.run_command('build_ext')
  116. else:
  117. # Without 2to3 inplace works fine:
  118. self.run_command('egg_info')
  119. # Build extensions in-place
  120. self.reinitialize_command('build_ext', inplace=1)
  121. self.run_command('build_ext')
  122. ei_cmd = self.get_finalized_command("egg_info")
  123. old_path = sys.path[:]
  124. old_modules = sys.modules.copy()
  125. try:
  126. project_path = normalize_path(ei_cmd.egg_base)
  127. sys.path.insert(0, project_path)
  128. working_set.__init__()
  129. add_activation_listener(lambda dist: dist.activate())
  130. require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
  131. with self.paths_on_pythonpath([project_path]):
  132. yield
  133. finally:
  134. sys.path[:] = old_path
  135. sys.modules.clear()
  136. sys.modules.update(old_modules)
  137. working_set.__init__()
  138. @staticmethod
  139. @contextlib.contextmanager
  140. def paths_on_pythonpath(paths):
  141. """
  142. Add the indicated paths to the head of the PYTHONPATH environment
  143. variable so that subprocesses will also see the packages at
  144. these paths.
  145. Do this in a context that restores the value on exit.
  146. """
  147. nothing = object()
  148. orig_pythonpath = os.environ.get('PYTHONPATH', nothing)
  149. current_pythonpath = os.environ.get('PYTHONPATH', '')
  150. try:
  151. prefix = os.pathsep.join(paths)
  152. to_join = filter(None, [prefix, current_pythonpath])
  153. new_path = os.pathsep.join(to_join)
  154. if new_path:
  155. os.environ['PYTHONPATH'] = new_path
  156. yield
  157. finally:
  158. if orig_pythonpath is nothing:
  159. os.environ.pop('PYTHONPATH', None)
  160. else:
  161. os.environ['PYTHONPATH'] = orig_pythonpath
  162. @staticmethod
  163. def install_dists(dist):
  164. """
  165. Install the requirements indicated by self.distribution and
  166. return an iterable of the dists that were built.
  167. """
  168. ir_d = dist.fetch_build_eggs(dist.install_requires)
  169. tr_d = dist.fetch_build_eggs(dist.tests_require or [])
  170. er_d = dist.fetch_build_eggs(
  171. v for k, v in dist.extras_require.items()
  172. if k.startswith(':') and evaluate_marker(k[1:])
  173. )
  174. return itertools.chain(ir_d, tr_d, er_d)
  175. def run(self):
  176. installed_dists = self.install_dists(self.distribution)
  177. cmd = ' '.join(self._argv)
  178. if self.dry_run:
  179. self.announce('skipping "%s" (dry run)' % cmd)
  180. return
  181. self.announce('running "%s"' % cmd)
  182. paths = map(operator.attrgetter('location'), installed_dists)
  183. with self.paths_on_pythonpath(paths):
  184. with self.project_on_sys_path():
  185. self.run_tests()
  186. def run_tests(self):
  187. # Purge modules under test from sys.modules. The test loader will
  188. # re-import them from the build location. Required when 2to3 is used
  189. # with namespace packages.
  190. if six.PY3 and getattr(self.distribution, 'use_2to3', False):
  191. module = self.test_suite.split('.')[0]
  192. if module in _namespace_packages:
  193. del_modules = []
  194. if module in sys.modules:
  195. del_modules.append(module)
  196. module += '.'
  197. for name in sys.modules:
  198. if name.startswith(module):
  199. del_modules.append(name)
  200. list(map(sys.modules.__delitem__, del_modules))
  201. test = unittest.main(
  202. None, None, self._argv,
  203. testLoader=self._resolve_as_ep(self.test_loader),
  204. testRunner=self._resolve_as_ep(self.test_runner),
  205. exit=False,
  206. )
  207. if not test.result.wasSuccessful():
  208. msg = 'Test failed: %s' % test.result
  209. self.announce(msg, log.ERROR)
  210. raise DistutilsError(msg)
  211. @property
  212. def _argv(self):
  213. return ['unittest'] + self.test_args
  214. @staticmethod
  215. def _resolve_as_ep(val):
  216. """
  217. Load the indicated attribute value, called, as a as if it were
  218. specified as an entry point.
  219. """
  220. if val is None:
  221. return
  222. parsed = EntryPoint.parse("x=" + val)
  223. return parsed.resolve()()