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 6.3 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. from distutils.errors import DistutilsOptionError
  2. from unittest import TestLoader
  3. import unittest
  4. import sys
  5. from pkg_resources import (resource_listdir, resource_exists, normalize_path,
  6. working_set, _namespace_packages,
  7. add_activation_listener, require, EntryPoint)
  8. from setuptools import Command
  9. from setuptools.compat import PY3
  10. from setuptools.py31compat import unittest_main
  11. class ScanningLoader(TestLoader):
  12. def loadTestsFromModule(self, module):
  13. """Return a suite of all tests cases contained in the given module
  14. If the module is a package, load tests from all the modules in it.
  15. If the module has an ``additional_tests`` function, call it and add
  16. the return value to the tests.
  17. """
  18. tests = []
  19. tests.append(TestLoader.loadTestsFromModule(self, module))
  20. if hasattr(module, "additional_tests"):
  21. tests.append(module.additional_tests())
  22. if hasattr(module, '__path__'):
  23. for file in resource_listdir(module.__name__, ''):
  24. if file.endswith('.py') and file != '__init__.py':
  25. submodule = module.__name__ + '.' + file[:-3]
  26. else:
  27. if resource_exists(module.__name__, file + '/__init__.py'):
  28. submodule = module.__name__ + '.' + file
  29. else:
  30. continue
  31. tests.append(self.loadTestsFromName(submodule))
  32. if len(tests) != 1:
  33. return self.suiteClass(tests)
  34. else:
  35. return tests[0] # don't create a nested suite for only one return
  36. class test(Command):
  37. """Command to run unit tests after in-place build"""
  38. description = "run unit tests after in-place build"
  39. user_options = [
  40. ('test-module=', 'm', "Run 'test_suite' in specified module"),
  41. ('test-suite=', 's',
  42. "Test suite to run (e.g. 'some_module.test_suite')"),
  43. ('test-runner=', 'r', "Test runner to use"),
  44. ]
  45. def initialize_options(self):
  46. self.test_suite = None
  47. self.test_module = None
  48. self.test_loader = None
  49. self.test_runner = None
  50. def finalize_options(self):
  51. if self.test_suite is None:
  52. if self.test_module is None:
  53. self.test_suite = self.distribution.test_suite
  54. else:
  55. self.test_suite = self.test_module + ".test_suite"
  56. elif self.test_module:
  57. raise DistutilsOptionError(
  58. "You may specify a module or a suite, but not both"
  59. )
  60. self.test_args = [self.test_suite]
  61. if self.verbose:
  62. self.test_args.insert(0, '--verbose')
  63. if self.test_loader is None:
  64. self.test_loader = getattr(self.distribution, 'test_loader', None)
  65. if self.test_loader is None:
  66. self.test_loader = "setuptools.command.test:ScanningLoader"
  67. if self.test_runner is None:
  68. self.test_runner = getattr(self.distribution, 'test_runner', None)
  69. def with_project_on_sys_path(self, func):
  70. with_2to3 = PY3 and getattr(self.distribution, 'use_2to3', False)
  71. if with_2to3:
  72. # If we run 2to3 we can not do this inplace:
  73. # Ensure metadata is up-to-date
  74. self.reinitialize_command('build_py', inplace=0)
  75. self.run_command('build_py')
  76. bpy_cmd = self.get_finalized_command("build_py")
  77. build_path = normalize_path(bpy_cmd.build_lib)
  78. # Build extensions
  79. self.reinitialize_command('egg_info', egg_base=build_path)
  80. self.run_command('egg_info')
  81. self.reinitialize_command('build_ext', inplace=0)
  82. self.run_command('build_ext')
  83. else:
  84. # Without 2to3 inplace works fine:
  85. self.run_command('egg_info')
  86. # Build extensions in-place
  87. self.reinitialize_command('build_ext', inplace=1)
  88. self.run_command('build_ext')
  89. ei_cmd = self.get_finalized_command("egg_info")
  90. old_path = sys.path[:]
  91. old_modules = sys.modules.copy()
  92. try:
  93. sys.path.insert(0, normalize_path(ei_cmd.egg_base))
  94. working_set.__init__()
  95. add_activation_listener(lambda dist: dist.activate())
  96. require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
  97. func()
  98. finally:
  99. sys.path[:] = old_path
  100. sys.modules.clear()
  101. sys.modules.update(old_modules)
  102. working_set.__init__()
  103. def run(self):
  104. if self.distribution.install_requires:
  105. self.distribution.fetch_build_eggs(
  106. self.distribution.install_requires)
  107. if self.distribution.tests_require:
  108. self.distribution.fetch_build_eggs(self.distribution.tests_require)
  109. if self.test_suite:
  110. cmd = ' '.join(self.test_args)
  111. if self.dry_run:
  112. self.announce('skipping "unittest %s" (dry run)' % cmd)
  113. else:
  114. self.announce('running "unittest %s"' % cmd)
  115. self.with_project_on_sys_path(self.run_tests)
  116. def run_tests(self):
  117. # Purge modules under test from sys.modules. The test loader will
  118. # re-import them from the build location. Required when 2to3 is used
  119. # with namespace packages.
  120. if PY3 and getattr(self.distribution, 'use_2to3', False):
  121. module = self.test_args[-1].split('.')[0]
  122. if module in _namespace_packages:
  123. del_modules = []
  124. if module in sys.modules:
  125. del_modules.append(module)
  126. module += '.'
  127. for name in sys.modules:
  128. if name.startswith(module):
  129. del_modules.append(name)
  130. list(map(sys.modules.__delitem__, del_modules))
  131. unittest_main(
  132. None, None, [unittest.__file__] + self.test_args,
  133. testLoader=self._resolve_as_ep(self.test_loader),
  134. testRunner=self._resolve_as_ep(self.test_runner),
  135. )
  136. @staticmethod
  137. def _resolve_as_ep(val):
  138. """
  139. Load the indicated attribute value, called, as a as if it were
  140. specified as an entry point.
  141. """
  142. if val is None:
  143. return
  144. parsed = EntryPoint.parse("x=" + val)
  145. return parsed.resolve()()