isolate.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. """The isolation plugin resets the contents of sys.modules after running
  2. each test module or package. Use it by setting ``--with-isolation`` or the
  3. NOSE_WITH_ISOLATION environment variable.
  4. The effects are similar to wrapping the following functions around the
  5. import and execution of each test module::
  6. def setup(module):
  7. module._mods = sys.modules.copy()
  8. def teardown(module):
  9. to_del = [ m for m in sys.modules.keys() if m not in
  10. module._mods ]
  11. for mod in to_del:
  12. del sys.modules[mod]
  13. sys.modules.update(module._mods)
  14. Isolation works only during lazy loading. In normal use, this is only
  15. during discovery of modules within a directory, where the process of
  16. importing, loading tests and running tests from each module is
  17. encapsulated in a single loadTestsFromName call. This plugin
  18. implements loadTestsFromNames to force the same lazy-loading there,
  19. which allows isolation to work in directed mode as well as discovery,
  20. at the cost of some efficiency: lazy-loading names forces full context
  21. setup and teardown to run for each name, defeating the grouping that
  22. is normally used to ensure that context setup and teardown are run the
  23. fewest possible times for a given set of names.
  24. .. warning ::
  25. This plugin should not be used in conjunction with other plugins
  26. that assume that modules, once imported, will stay imported; for
  27. instance, it may cause very odd results when used with the coverage
  28. plugin.
  29. """
  30. import logging
  31. import sys
  32. from nose.plugins import Plugin
  33. log = logging.getLogger('nose.plugins.isolation')
  34. class IsolationPlugin(Plugin):
  35. """
  36. Activate the isolation plugin to isolate changes to external
  37. modules to a single test module or package. The isolation plugin
  38. resets the contents of sys.modules after each test module or
  39. package runs to its state before the test. PLEASE NOTE that this
  40. plugin should not be used with the coverage plugin, or in any other case
  41. where module reloading may produce undesirable side-effects.
  42. """
  43. score = 10 # I want to be last
  44. name = 'isolation'
  45. def configure(self, options, conf):
  46. """Configure plugin.
  47. """
  48. Plugin.configure(self, options, conf)
  49. self._mod_stack = []
  50. def beforeContext(self):
  51. """Copy sys.modules onto my mod stack
  52. """
  53. mods = sys.modules.copy()
  54. self._mod_stack.append(mods)
  55. def afterContext(self):
  56. """Pop my mod stack and restore sys.modules to the state
  57. it was in when mod stack was pushed.
  58. """
  59. mods = self._mod_stack.pop()
  60. to_del = [ m for m in sys.modules.keys() if m not in mods ]
  61. if to_del:
  62. log.debug('removing sys modules entries: %s', to_del)
  63. for mod in to_del:
  64. del sys.modules[mod]
  65. sys.modules.update(mods)
  66. def loadTestsFromNames(self, names, module=None):
  67. """Create a lazy suite that calls beforeContext and afterContext
  68. around each name. The side-effect of this is that full context
  69. fixtures will be set up and torn down around each test named.
  70. """
  71. # Fast path for when we don't care
  72. if not names or len(names) == 1:
  73. return
  74. loader = self.loader
  75. plugins = self.conf.plugins
  76. def lazy():
  77. for name in names:
  78. plugins.beforeContext()
  79. yield loader.loadTestsFromName(name, module=module)
  80. plugins.afterContext()
  81. return (loader.suiteClass(lazy), [])
  82. def prepareTestLoader(self, loader):
  83. """Get handle on test loader so we can use it in loadTestsFromNames.
  84. """
  85. self.loader = loader