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.
 
 
 
 

176 lines
6.3 KiB

  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()()