OLD | NEW |
| (Empty) |
1 # Copyright (c) 2006-2007 Twisted Matrix Laboratories. | |
2 # See LICENSE for details. | |
3 | |
4 """ | |
5 Tests for twisted.python.modules, abstract access to imported or importable | |
6 objects. | |
7 """ | |
8 | |
9 import os | |
10 import sys | |
11 import itertools | |
12 import zipfile | |
13 import compileall | |
14 | |
15 from twisted.trial.unittest import TestCase | |
16 | |
17 from twisted.python import modules | |
18 from twisted.python.filepath import FilePath | |
19 from twisted.python.reflect import namedAny | |
20 | |
21 from twisted.test.test_paths import zipit | |
22 | |
23 | |
24 | |
25 class PySpaceTestCase(TestCase): | |
26 | |
27 def findByIteration(self, modname, where=modules, importPackages=False): | |
28 """ | |
29 You don't ever actually want to do this, so it's not in the public API,
but | |
30 sometimes we want to compare the result of an iterative call with a | |
31 lookup call and make sure they're the same for test purposes. | |
32 """ | |
33 for modinfo in where.walkModules(importPackages=importPackages): | |
34 if modinfo.name == modname: | |
35 return modinfo | |
36 self.fail("Unable to find module %r through iteration." % (modname,)) | |
37 | |
38 | |
39 | |
40 class BasicTests(PySpaceTestCase): | |
41 def test_nonexistentPaths(self): | |
42 """ | |
43 Verify that L{modules.walkModules} ignores entries in sys.path which | |
44 do not exist in the filesystem. | |
45 """ | |
46 existentPath = FilePath(self.mktemp()) | |
47 os.makedirs(existentPath.child("test_package").path) | |
48 existentPath.child("test_package").child("__init__.py").setContent("") | |
49 | |
50 nonexistentPath = FilePath(self.mktemp()) | |
51 self.failIf(nonexistentPath.exists()) | |
52 | |
53 originalSearchPaths = sys.path[:] | |
54 sys.path[:] = [existentPath.path] | |
55 try: | |
56 expected = [modules.getModule("test_package")] | |
57 | |
58 beforeModules = list(modules.walkModules()) | |
59 sys.path.append(nonexistentPath.path) | |
60 afterModules = list(modules.walkModules()) | |
61 finally: | |
62 sys.path[:] = originalSearchPaths | |
63 | |
64 self.assertEqual(beforeModules, expected) | |
65 self.assertEqual(afterModules, expected) | |
66 | |
67 | |
68 def test_nonDirectoryPaths(self): | |
69 """ | |
70 Verify that L{modules.walkModules} ignores entries in sys.path which | |
71 refer to regular files in the filesystem. | |
72 """ | |
73 existentPath = FilePath(self.mktemp()) | |
74 os.makedirs(existentPath.child("test_package").path) | |
75 existentPath.child("test_package").child("__init__.py").setContent("") | |
76 | |
77 nonDirectoryPath = FilePath(self.mktemp()) | |
78 self.failIf(nonDirectoryPath.exists()) | |
79 nonDirectoryPath.setContent("zip file or whatever\n") | |
80 | |
81 originalSearchPaths = sys.path[:] | |
82 sys.path[:] = [existentPath.path] | |
83 try: | |
84 beforeModules = list(modules.walkModules()) | |
85 sys.path.append(nonDirectoryPath.path) | |
86 afterModules = list(modules.walkModules()) | |
87 finally: | |
88 sys.path[:] = originalSearchPaths | |
89 | |
90 self.assertEqual(beforeModules, afterModules) | |
91 | |
92 | |
93 def test_twistedShowsUp(self): | |
94 """ | |
95 Scrounge around in the top-level module namespace and make sure that | |
96 Twisted shows up, and that the module thusly obtained is the same as | |
97 the module that we find when we look for it explicitly by name. | |
98 """ | |
99 self.assertEquals(modules.getModule('twisted'), | |
100 self.findByIteration("twisted")) | |
101 | |
102 | |
103 def test_dottedNames(self): | |
104 """ | |
105 Verify that the walkModules APIs will give us back subpackages, not just | |
106 subpackages. | |
107 """ | |
108 self.assertEquals( | |
109 modules.getModule('twisted.python'), | |
110 self.findByIteration("twisted.python", | |
111 where=modules.getModule('twisted'))) | |
112 | |
113 | |
114 def test_onlyTopModules(self): | |
115 """ | |
116 Verify that the iterModules API will only return top-level modules and | |
117 packages, not submodules or subpackages. | |
118 """ | |
119 for module in modules.iterModules(): | |
120 self.failIf( | |
121 '.' in module.name, | |
122 "no nested modules should be returned from iterModules: %r" | |
123 % (module.filePath)) | |
124 | |
125 | |
126 def test_loadPackagesAndModules(self): | |
127 """ | |
128 Verify that we can locate and load packages, modules, submodules, and | |
129 subpackages. | |
130 """ | |
131 for n in ['os', | |
132 'twisted', | |
133 'twisted.python', | |
134 'twisted.python.reflect']: | |
135 m = namedAny(n) | |
136 self.failUnlessIdentical( | |
137 modules.getModule(n).load(), | |
138 m) | |
139 self.failUnlessIdentical( | |
140 self.findByIteration(n).load(), | |
141 m) | |
142 | |
143 | |
144 def test_pathEntriesOnPath(self): | |
145 """ | |
146 Verify that path entries discovered via module loading are, in fact, on | |
147 sys.path somewhere. | |
148 """ | |
149 for n in ['os', | |
150 'twisted', | |
151 'twisted.python', | |
152 'twisted.python.reflect']: | |
153 self.failUnlessIn( | |
154 modules.getModule(n).pathEntry.filePath.path, | |
155 sys.path) | |
156 | |
157 | |
158 def test_alwaysPreferPy(self): | |
159 """ | |
160 Verify that .py files will always be preferred to .pyc files, regardless
of | |
161 directory listing order. | |
162 """ | |
163 mypath = FilePath(self.mktemp()) | |
164 mypath.createDirectory() | |
165 pp = modules.PythonPath(sysPath=[mypath.path]) | |
166 originalSmartPath = pp._smartPath | |
167 def _evilSmartPath(pathName): | |
168 o = originalSmartPath(pathName) | |
169 originalChildren = o.children | |
170 def evilChildren(): | |
171 # normally this order is random; let's make sure it always | |
172 # comes up .pyc-first. | |
173 x = originalChildren() | |
174 x.sort() | |
175 x.reverse() | |
176 return x | |
177 o.children = evilChildren | |
178 return o | |
179 mypath.child("abcd.py").setContent('\n') | |
180 compileall.compile_dir(mypath.path, quiet=True) | |
181 # sanity check | |
182 self.assertEquals(len(mypath.children()), 2) | |
183 pp._smartPath = _evilSmartPath | |
184 self.assertEquals(pp['abcd'].filePath, | |
185 mypath.child('abcd.py')) | |
186 | |
187 | |
188 def test_packageMissingPath(self): | |
189 """ | |
190 A package can delete its __path__ for some reasons, | |
191 C{modules.PythonPath} should be able to deal with it. | |
192 """ | |
193 mypath = FilePath(self.mktemp()) | |
194 mypath.createDirectory() | |
195 pp = modules.PythonPath(sysPath=[mypath.path]) | |
196 subpath = mypath.child("abcd") | |
197 subpath.createDirectory() | |
198 subpath.child("__init__.py").setContent('del __path__\n') | |
199 sys.path.append(mypath.path) | |
200 import abcd | |
201 try: | |
202 l = list(pp.walkModules()) | |
203 self.assertEquals(len(l), 1) | |
204 self.assertEquals(l[0].name, 'abcd') | |
205 finally: | |
206 del abcd | |
207 del sys.modules['abcd'] | |
208 sys.path.remove(mypath.path) | |
209 | |
210 | |
211 | |
212 class PathModificationTest(PySpaceTestCase): | |
213 """ | |
214 These tests share setup/cleanup behavior of creating a dummy package and | |
215 stuffing some code in it. | |
216 """ | |
217 | |
218 _serialnum = itertools.count().next # used to generate serial numbers for | |
219 # package names. | |
220 | |
221 def setUp(self): | |
222 self.pathExtensionName = self.mktemp() | |
223 self.pathExtension = FilePath(self.pathExtensionName) | |
224 self.pathExtension.createDirectory() | |
225 self.packageName = "pyspacetests%d" % (self._serialnum(),) | |
226 self.packagePath = self.pathExtension.child(self.packageName) | |
227 self.packagePath.createDirectory() | |
228 self.packagePath.child("__init__.py").setContent("") | |
229 self.packagePath.child("a.py").setContent("") | |
230 self.packagePath.child("b.py").setContent("") | |
231 self.packagePath.child("c__init__.py").setContent("") | |
232 self.pathSetUp = False | |
233 | |
234 | |
235 def _setupSysPath(self): | |
236 assert not self.pathSetUp | |
237 self.pathSetUp = True | |
238 sys.path.append(self.pathExtensionName) | |
239 | |
240 | |
241 def _underUnderPathTest(self, doImport=True): | |
242 moddir2 = self.mktemp() | |
243 fpmd = FilePath(moddir2) | |
244 fpmd.createDirectory() | |
245 fpmd.child("foozle.py").setContent("x = 123\n") | |
246 self.packagePath.child("__init__.py").setContent( | |
247 "__path__.append(%r)\n" % (moddir2,)) | |
248 # Cut here | |
249 self._setupSysPath() | |
250 modinfo = modules.getModule(self.packageName) | |
251 self.assertEquals( | |
252 self.findByIteration(self.packageName+".foozle", modinfo, | |
253 importPackages=doImport), | |
254 modinfo['foozle']) | |
255 self.assertEquals(modinfo['foozle'].load().x, 123) | |
256 | |
257 | |
258 def test_underUnderPathAlreadyImported(self): | |
259 """ | |
260 Verify that iterModules will honor the __path__ of already-loaded packag
es. | |
261 """ | |
262 self._underUnderPathTest() | |
263 | |
264 | |
265 def test_underUnderPathNotAlreadyImported(self): | |
266 """ | |
267 Verify that iterModules will honor the __path__ of already-loaded packag
es. | |
268 """ | |
269 self._underUnderPathTest(False) | |
270 | |
271 | |
272 test_underUnderPathNotAlreadyImported.todo = ( | |
273 "This may be impossible but it sure would be nice.") | |
274 | |
275 | |
276 def _listModules(self): | |
277 pkginfo = modules.getModule(self.packageName) | |
278 nfni = [modinfo.name.split(".")[-1] for modinfo in | |
279 pkginfo.iterModules()] | |
280 nfni.sort() | |
281 self.failUnlessEqual(nfni, ['a', 'b', 'c__init__']) | |
282 | |
283 | |
284 def test_listingModules(self): | |
285 """ | |
286 Make sure the module list comes back as we expect from iterModules on a | |
287 package, whether zipped or not. | |
288 """ | |
289 self._setupSysPath() | |
290 self._listModules() | |
291 | |
292 | |
293 def test_listingModulesAlreadyImported(self): | |
294 """ | |
295 Make sure the module list comes back as we expect from iterModules on a | |
296 package, whether zipped or not, even if the package has already been | |
297 imported. | |
298 """ | |
299 self._setupSysPath() | |
300 namedAny(self.packageName) | |
301 self._listModules() | |
302 | |
303 | |
304 def tearDown(self): | |
305 # Intentionally using 'assert' here, this is not a test assertion, this | |
306 # is just an "oh fuck what is going ON" assertion. -glyph | |
307 if self.pathSetUp: | |
308 HORK = "path cleanup failed: don't be surprised if other tests break
" | |
309 assert sys.path.pop() is self.pathExtensionName, HORK+", 1" | |
310 assert self.pathExtensionName not in sys.path, HORK+", 2" | |
311 | |
312 | |
313 | |
314 class RebindingTest(PathModificationTest): | |
315 """ | |
316 These tests verify that the default path interrogation API works properly | |
317 even when sys.path has been rebound to a different object. | |
318 """ | |
319 def _setupSysPath(self): | |
320 assert not self.pathSetUp | |
321 self.pathSetUp = True | |
322 self.savedSysPath = sys.path | |
323 sys.path = sys.path[:] | |
324 sys.path.append(self.pathExtensionName) | |
325 | |
326 | |
327 def tearDown(self): | |
328 """ | |
329 Clean up sys.path by re-binding our original object. | |
330 """ | |
331 if self.pathSetUp: | |
332 sys.path = self.savedSysPath | |
333 | |
334 | |
335 | |
336 class ZipPathModificationTest(PathModificationTest): | |
337 def _setupSysPath(self): | |
338 assert not self.pathSetUp | |
339 zipit(self.pathExtensionName, self.pathExtensionName+'.zip') | |
340 self.pathExtensionName += '.zip' | |
341 assert zipfile.is_zipfile(self.pathExtensionName) | |
342 PathModificationTest._setupSysPath(self) | |
343 | |
344 | |
345 class PythonPathTestCase(TestCase): | |
346 """ | |
347 Tests for the class which provides the implementation for all of the | |
348 public API of L{twisted.python.modules}. | |
349 """ | |
350 def test_unhandledImporter(self): | |
351 """ | |
352 Make sure that the behavior when encountering an unknown importer | |
353 type is not catastrophic failure. | |
354 """ | |
355 class SecretImporter(object): | |
356 pass | |
357 | |
358 def hook(name): | |
359 return SecretImporter() | |
360 | |
361 syspath = ['example/path'] | |
362 sysmodules = {} | |
363 syshooks = [hook] | |
364 syscache = {} | |
365 def sysloader(name): | |
366 return None | |
367 space = modules.PythonPath( | |
368 syspath, sysmodules, syshooks, syscache, sysloader) | |
369 entries = list(space.iterEntries()) | |
370 self.assertEquals(len(entries), 1) | |
371 self.assertRaises(KeyError, lambda: entries[0]['module']) | |
OLD | NEW |