1
0

loader.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.support.pytest.loader
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. Salt's Loader PyTest Mock Support
  6. """
  7. import functools
  8. import logging
  9. import sys
  10. import types
  11. import attr # pylint: disable=3rd-party-module-not-gated
  12. from tests.support.mock import patch
  13. log = logging.getLogger(__name__)
  14. @attr.s(init=True, slots=True, frozen=True)
  15. class LoaderModuleMock:
  16. request = attr.ib(init=True)
  17. setup_loader_modules = attr.ib(init=True)
  18. salt_dunders = attr.ib(
  19. init=True,
  20. repr=False,
  21. kw_only=True,
  22. default=(
  23. "__opts__",
  24. "__salt__",
  25. "__runner__",
  26. "__context__",
  27. "__utils__",
  28. "__ext_pillar__",
  29. "__thorium__",
  30. "__states__",
  31. "__serializers__",
  32. "__ret__",
  33. "__grains__",
  34. "__pillar__",
  35. "__sdb__",
  36. # Proxy is commented out on purpose since some code in salt expects a NameError
  37. # and is most of the time not a required dunder
  38. # '__proxy__'
  39. ),
  40. )
  41. def __enter__(self):
  42. module_globals = {dunder: {} for dunder in self.salt_dunders}
  43. for module, globals_to_mock in self.setup_loader_modules.items():
  44. log.trace(
  45. "Setting up loader globals for %s; globals: %s", module, globals_to_mock
  46. )
  47. if not isinstance(module, types.ModuleType):
  48. raise RuntimeError(
  49. "The dictionary keys returned by setup_loader_modules() "
  50. "must be an imported module, not {}".format(type(module))
  51. )
  52. if not isinstance(globals_to_mock, dict):
  53. raise RuntimeError(
  54. "The dictionary values returned by setup_loader_modules() "
  55. "must be a dictionary, not {}".format(type(globals_to_mock))
  56. )
  57. for dunder in module_globals:
  58. if not hasattr(module, dunder):
  59. # Set the dunder name as an attribute on the module if not present
  60. setattr(module, dunder, {})
  61. # Remove the added attribute after the test finishes
  62. self.addfinalizer(delattr, module, dunder)
  63. for key in globals_to_mock:
  64. if key == "sys.modules":
  65. sys_modules = globals_to_mock[key]
  66. if not isinstance(sys_modules, dict):
  67. raise RuntimeError(
  68. "'sys.modules' must be a dictionary not: {}".format(
  69. type(sys_modules)
  70. )
  71. )
  72. patcher = patch.dict(sys.modules, sys_modules)
  73. patcher.start()
  74. def cleanup_sys_modules(patcher, sys_modules):
  75. patcher.stop()
  76. del patcher
  77. del sys_modules
  78. self.addfinalizer(cleanup_sys_modules, patcher, sys_modules)
  79. continue
  80. mocked_details = globals_to_mock[key]
  81. if isinstance(mocked_details, dict) and key.startswith("__"):
  82. # A salt dunder
  83. if not hasattr(module, key):
  84. # Set the dunder name as an attribute on the module if not present
  85. setattr(module, key, {})
  86. # Remove the added attribute after the test finishes
  87. self.addfinalizer(delattr, module, key)
  88. for mock_key, mock_data in mocked_details.items():
  89. module_globals.setdefault(key, {})[mock_key] = mock_data
  90. else:
  91. if not hasattr(module, key):
  92. # Set the key name as an attribute on the module if not present
  93. setattr(module, key, None)
  94. # Remove the added attribute after the test finishes
  95. self.addfinalizer(delattr, module, key)
  96. module_globals[key] = mocked_details
  97. # Patch the module!
  98. log.trace(
  99. "Patching loader globals for %s; globals: %s", module, module_globals
  100. )
  101. patcher = patch.multiple(module, **module_globals)
  102. patcher.start()
  103. def cleanup_module_globals(patcher, module_globals):
  104. patcher.stop()
  105. del patcher
  106. module_globals.clear()
  107. del module_globals
  108. # Be sure to unpatch the module once the test finishes
  109. self.addfinalizer(cleanup_module_globals, patcher, module_globals)
  110. return self
  111. def __exit__(self, *args):
  112. pass
  113. def addfinalizer(self, func, *args, **kwargs):
  114. # Compat layer while we still support running the test suite under unittest
  115. try:
  116. self.request.addfinalizer(functools.partial(func, *args, **kwargs))
  117. except AttributeError:
  118. self.request.addCleanup(func, *args, **kwargs)