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