1 | import re
|
---|
2 | import sys
|
---|
3 | import types
|
---|
4 | import unittest
|
---|
5 | import inspect
|
---|
6 | import linecache
|
---|
7 | import datetime
|
---|
8 | from UserList import UserList
|
---|
9 | from UserDict import UserDict
|
---|
10 |
|
---|
11 | from test.test_support import run_unittest, check_py3k_warnings
|
---|
12 |
|
---|
13 | with check_py3k_warnings(
|
---|
14 | ("tuple parameter unpacking has been removed", SyntaxWarning),
|
---|
15 | quiet=True):
|
---|
16 | from test import inspect_fodder as mod
|
---|
17 | from test import inspect_fodder2 as mod2
|
---|
18 |
|
---|
19 | # C module for test_findsource_binary
|
---|
20 | import unicodedata
|
---|
21 |
|
---|
22 | # Functions tested in this suite:
|
---|
23 | # ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
|
---|
24 | # isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers,
|
---|
25 | # getdoc, getfile, getmodule, getsourcefile, getcomments, getsource,
|
---|
26 | # getclasstree, getargspec, getargvalues, formatargspec, formatargvalues,
|
---|
27 | # currentframe, stack, trace, isdatadescriptor
|
---|
28 |
|
---|
29 | # NOTE: There are some additional tests relating to interaction with
|
---|
30 | # zipimport in the test_zipimport_support test module.
|
---|
31 |
|
---|
32 | modfile = mod.__file__
|
---|
33 | if modfile.endswith(('c', 'o')):
|
---|
34 | modfile = modfile[:-1]
|
---|
35 |
|
---|
36 | import __builtin__
|
---|
37 |
|
---|
38 | try:
|
---|
39 | 1 // 0
|
---|
40 | except:
|
---|
41 | tb = sys.exc_traceback
|
---|
42 |
|
---|
43 | git = mod.StupidGit()
|
---|
44 |
|
---|
45 | class IsTestBase(unittest.TestCase):
|
---|
46 | predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode,
|
---|
47 | inspect.isframe, inspect.isfunction, inspect.ismethod,
|
---|
48 | inspect.ismodule, inspect.istraceback,
|
---|
49 | inspect.isgenerator, inspect.isgeneratorfunction])
|
---|
50 |
|
---|
51 | def istest(self, predicate, exp):
|
---|
52 | obj = eval(exp)
|
---|
53 | self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp))
|
---|
54 |
|
---|
55 | for other in self.predicates - set([predicate]):
|
---|
56 | if predicate == inspect.isgeneratorfunction and\
|
---|
57 | other == inspect.isfunction:
|
---|
58 | continue
|
---|
59 | self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp))
|
---|
60 |
|
---|
61 | def generator_function_example(self):
|
---|
62 | for i in xrange(2):
|
---|
63 | yield i
|
---|
64 |
|
---|
65 | class TestPredicates(IsTestBase):
|
---|
66 | def test_sixteen(self):
|
---|
67 | count = len(filter(lambda x:x.startswith('is'), dir(inspect)))
|
---|
68 | # This test is here for remember you to update Doc/library/inspect.rst
|
---|
69 | # which claims there are 16 such functions
|
---|
70 | expected = 16
|
---|
71 | err_msg = "There are %d (not %d) is* functions" % (count, expected)
|
---|
72 | self.assertEqual(count, expected, err_msg)
|
---|
73 |
|
---|
74 |
|
---|
75 | def test_excluding_predicates(self):
|
---|
76 | self.istest(inspect.isbuiltin, 'sys.exit')
|
---|
77 | self.istest(inspect.isbuiltin, '[].append')
|
---|
78 | self.istest(inspect.iscode, 'mod.spam.func_code')
|
---|
79 | self.istest(inspect.isframe, 'tb.tb_frame')
|
---|
80 | self.istest(inspect.isfunction, 'mod.spam')
|
---|
81 | self.istest(inspect.ismethod, 'mod.StupidGit.abuse')
|
---|
82 | self.istest(inspect.ismethod, 'git.argue')
|
---|
83 | self.istest(inspect.ismodule, 'mod')
|
---|
84 | self.istest(inspect.istraceback, 'tb')
|
---|
85 | self.istest(inspect.isdatadescriptor, '__builtin__.file.closed')
|
---|
86 | self.istest(inspect.isdatadescriptor, '__builtin__.file.softspace')
|
---|
87 | self.istest(inspect.isgenerator, '(x for x in xrange(2))')
|
---|
88 | self.istest(inspect.isgeneratorfunction, 'generator_function_example')
|
---|
89 | if hasattr(types, 'GetSetDescriptorType'):
|
---|
90 | self.istest(inspect.isgetsetdescriptor,
|
---|
91 | 'type(tb.tb_frame).f_locals')
|
---|
92 | else:
|
---|
93 | self.assertFalse(inspect.isgetsetdescriptor(type(tb.tb_frame).f_locals))
|
---|
94 | if hasattr(types, 'MemberDescriptorType'):
|
---|
95 | self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days')
|
---|
96 | else:
|
---|
97 | self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days))
|
---|
98 |
|
---|
99 | def test_isroutine(self):
|
---|
100 | self.assertTrue(inspect.isroutine(mod.spam))
|
---|
101 | self.assertTrue(inspect.isroutine([].count))
|
---|
102 |
|
---|
103 | def test_isclass(self):
|
---|
104 | self.istest(inspect.isclass, 'mod.StupidGit')
|
---|
105 | self.assertTrue(inspect.isclass(list))
|
---|
106 |
|
---|
107 | class newstyle(object): pass
|
---|
108 | self.assertTrue(inspect.isclass(newstyle))
|
---|
109 |
|
---|
110 | class CustomGetattr(object):
|
---|
111 | def __getattr__(self, attr):
|
---|
112 | return None
|
---|
113 | self.assertFalse(inspect.isclass(CustomGetattr()))
|
---|
114 |
|
---|
115 | def test_get_slot_members(self):
|
---|
116 | class C(object):
|
---|
117 | __slots__ = ("a", "b")
|
---|
118 |
|
---|
119 | x = C()
|
---|
120 | x.a = 42
|
---|
121 | members = dict(inspect.getmembers(x))
|
---|
122 | self.assertIn('a', members)
|
---|
123 | self.assertNotIn('b', members)
|
---|
124 |
|
---|
125 | def test_isabstract(self):
|
---|
126 | from abc import ABCMeta, abstractmethod
|
---|
127 |
|
---|
128 | class AbstractClassExample(object):
|
---|
129 | __metaclass__ = ABCMeta
|
---|
130 |
|
---|
131 | @abstractmethod
|
---|
132 | def foo(self):
|
---|
133 | pass
|
---|
134 |
|
---|
135 | class ClassExample(AbstractClassExample):
|
---|
136 | def foo(self):
|
---|
137 | pass
|
---|
138 |
|
---|
139 | a = ClassExample()
|
---|
140 |
|
---|
141 | # Test general behaviour.
|
---|
142 | self.assertTrue(inspect.isabstract(AbstractClassExample))
|
---|
143 | self.assertFalse(inspect.isabstract(ClassExample))
|
---|
144 | self.assertFalse(inspect.isabstract(a))
|
---|
145 | self.assertFalse(inspect.isabstract(int))
|
---|
146 | self.assertFalse(inspect.isabstract(5))
|
---|
147 |
|
---|
148 |
|
---|
149 | class TestInterpreterStack(IsTestBase):
|
---|
150 | def __init__(self, *args, **kwargs):
|
---|
151 | unittest.TestCase.__init__(self, *args, **kwargs)
|
---|
152 |
|
---|
153 | git.abuse(7, 8, 9)
|
---|
154 |
|
---|
155 | def test_abuse_done(self):
|
---|
156 | self.istest(inspect.istraceback, 'git.ex[2]')
|
---|
157 | self.istest(inspect.isframe, 'mod.fr')
|
---|
158 |
|
---|
159 | def test_stack(self):
|
---|
160 | self.assertTrue(len(mod.st) >= 5)
|
---|
161 | self.assertEqual(mod.st[0][1:],
|
---|
162 | (modfile, 16, 'eggs', [' st = inspect.stack()\n'], 0))
|
---|
163 | self.assertEqual(mod.st[1][1:],
|
---|
164 | (modfile, 9, 'spam', [' eggs(b + d, c + f)\n'], 0))
|
---|
165 | self.assertEqual(mod.st[2][1:],
|
---|
166 | (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0))
|
---|
167 | self.assertEqual(mod.st[3][1:],
|
---|
168 | (modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0))
|
---|
169 |
|
---|
170 | def test_trace(self):
|
---|
171 | self.assertEqual(len(git.tr), 3)
|
---|
172 | self.assertEqual(git.tr[0][1:], (modfile, 43, 'argue',
|
---|
173 | [' spam(a, b, c)\n'], 0))
|
---|
174 | self.assertEqual(git.tr[1][1:], (modfile, 9, 'spam',
|
---|
175 | [' eggs(b + d, c + f)\n'], 0))
|
---|
176 | self.assertEqual(git.tr[2][1:], (modfile, 18, 'eggs',
|
---|
177 | [' q = y // 0\n'], 0))
|
---|
178 |
|
---|
179 | def test_frame(self):
|
---|
180 | args, varargs, varkw, locals = inspect.getargvalues(mod.fr)
|
---|
181 | self.assertEqual(args, ['x', 'y'])
|
---|
182 | self.assertEqual(varargs, None)
|
---|
183 | self.assertEqual(varkw, None)
|
---|
184 | self.assertEqual(locals, {'x': 11, 'p': 11, 'y': 14})
|
---|
185 | self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
|
---|
186 | '(x=11, y=14)')
|
---|
187 |
|
---|
188 | def test_previous_frame(self):
|
---|
189 | args, varargs, varkw, locals = inspect.getargvalues(mod.fr.f_back)
|
---|
190 | self.assertEqual(args, ['a', 'b', 'c', 'd', ['e', ['f']]])
|
---|
191 | self.assertEqual(varargs, 'g')
|
---|
192 | self.assertEqual(varkw, 'h')
|
---|
193 | self.assertEqual(inspect.formatargvalues(args, varargs, varkw, locals),
|
---|
194 | '(a=7, b=8, c=9, d=3, (e=4, (f=5,)), *g=(), **h={})')
|
---|
195 |
|
---|
196 | class GetSourceBase(unittest.TestCase):
|
---|
197 | # Subclasses must override.
|
---|
198 | fodderFile = None
|
---|
199 |
|
---|
200 | def __init__(self, *args, **kwargs):
|
---|
201 | unittest.TestCase.__init__(self, *args, **kwargs)
|
---|
202 |
|
---|
203 | with open(inspect.getsourcefile(self.fodderFile)) as fp:
|
---|
204 | self.source = fp.read()
|
---|
205 |
|
---|
206 | def sourcerange(self, top, bottom):
|
---|
207 | lines = self.source.split("\n")
|
---|
208 | return "\n".join(lines[top-1:bottom]) + "\n"
|
---|
209 |
|
---|
210 | def assertSourceEqual(self, obj, top, bottom):
|
---|
211 | self.assertEqual(inspect.getsource(obj),
|
---|
212 | self.sourcerange(top, bottom))
|
---|
213 |
|
---|
214 | class TestRetrievingSourceCode(GetSourceBase):
|
---|
215 | fodderFile = mod
|
---|
216 |
|
---|
217 | def test_getclasses(self):
|
---|
218 | classes = inspect.getmembers(mod, inspect.isclass)
|
---|
219 | self.assertEqual(classes,
|
---|
220 | [('FesteringGob', mod.FesteringGob),
|
---|
221 | ('MalodorousPervert', mod.MalodorousPervert),
|
---|
222 | ('ParrotDroppings', mod.ParrotDroppings),
|
---|
223 | ('StupidGit', mod.StupidGit),
|
---|
224 | ('Tit', mod.MalodorousPervert),
|
---|
225 | ])
|
---|
226 | tree = inspect.getclasstree([cls[1] for cls in classes])
|
---|
227 | self.assertEqual(tree,
|
---|
228 | [(mod.ParrotDroppings, ()),
|
---|
229 | [(mod.FesteringGob, (mod.MalodorousPervert,
|
---|
230 | mod.ParrotDroppings))
|
---|
231 | ],
|
---|
232 | (mod.StupidGit, ()),
|
---|
233 | [(mod.MalodorousPervert, (mod.StupidGit,)),
|
---|
234 | [(mod.FesteringGob, (mod.MalodorousPervert,
|
---|
235 | mod.ParrotDroppings))
|
---|
236 | ]
|
---|
237 | ]
|
---|
238 | ])
|
---|
239 | tree = inspect.getclasstree([cls[1] for cls in classes], True)
|
---|
240 | self.assertEqual(tree,
|
---|
241 | [(mod.ParrotDroppings, ()),
|
---|
242 | (mod.StupidGit, ()),
|
---|
243 | [(mod.MalodorousPervert, (mod.StupidGit,)),
|
---|
244 | [(mod.FesteringGob, (mod.MalodorousPervert,
|
---|
245 | mod.ParrotDroppings))
|
---|
246 | ]
|
---|
247 | ]
|
---|
248 | ])
|
---|
249 |
|
---|
250 | def test_getfunctions(self):
|
---|
251 | functions = inspect.getmembers(mod, inspect.isfunction)
|
---|
252 | self.assertEqual(functions, [('eggs', mod.eggs),
|
---|
253 | ('spam', mod.spam)])
|
---|
254 |
|
---|
255 | @unittest.skipIf(sys.flags.optimize >= 2,
|
---|
256 | "Docstrings are omitted with -O2 and above")
|
---|
257 | def test_getdoc(self):
|
---|
258 | self.assertEqual(inspect.getdoc(mod), 'A module docstring.')
|
---|
259 | self.assertEqual(inspect.getdoc(mod.StupidGit),
|
---|
260 | 'A longer,\n\nindented\n\ndocstring.')
|
---|
261 | self.assertEqual(inspect.getdoc(git.abuse),
|
---|
262 | 'Another\n\ndocstring\n\ncontaining\n\ntabs')
|
---|
263 |
|
---|
264 | def test_cleandoc(self):
|
---|
265 | self.assertEqual(inspect.cleandoc('An\n indented\n docstring.'),
|
---|
266 | 'An\nindented\ndocstring.')
|
---|
267 |
|
---|
268 | def test_getcomments(self):
|
---|
269 | self.assertEqual(inspect.getcomments(mod), '# line 1\n')
|
---|
270 | self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n')
|
---|
271 |
|
---|
272 | def test_getmodule(self):
|
---|
273 | # Check actual module
|
---|
274 | self.assertEqual(inspect.getmodule(mod), mod)
|
---|
275 | # Check class (uses __module__ attribute)
|
---|
276 | self.assertEqual(inspect.getmodule(mod.StupidGit), mod)
|
---|
277 | # Check a method (no __module__ attribute, falls back to filename)
|
---|
278 | self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod)
|
---|
279 | # Do it again (check the caching isn't broken)
|
---|
280 | self.assertEqual(inspect.getmodule(mod.StupidGit.abuse), mod)
|
---|
281 | # Check a builtin
|
---|
282 | self.assertEqual(inspect.getmodule(str), sys.modules["__builtin__"])
|
---|
283 | # Check filename override
|
---|
284 | self.assertEqual(inspect.getmodule(None, modfile), mod)
|
---|
285 |
|
---|
286 | def test_getsource(self):
|
---|
287 | self.assertSourceEqual(git.abuse, 29, 39)
|
---|
288 | self.assertSourceEqual(mod.StupidGit, 21, 46)
|
---|
289 |
|
---|
290 | def test_getsourcefile(self):
|
---|
291 | self.assertEqual(inspect.getsourcefile(mod.spam), modfile)
|
---|
292 | self.assertEqual(inspect.getsourcefile(git.abuse), modfile)
|
---|
293 | fn = "_non_existing_filename_used_for_sourcefile_test.py"
|
---|
294 | co = compile("None", fn, "exec")
|
---|
295 | self.assertEqual(inspect.getsourcefile(co), None)
|
---|
296 | linecache.cache[co.co_filename] = (1, None, "None", co.co_filename)
|
---|
297 | self.assertEqual(inspect.getsourcefile(co), fn)
|
---|
298 |
|
---|
299 | def test_getfile(self):
|
---|
300 | self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__)
|
---|
301 |
|
---|
302 | def test_getmodule_recursion(self):
|
---|
303 | from types import ModuleType
|
---|
304 | name = '__inspect_dummy'
|
---|
305 | m = sys.modules[name] = ModuleType(name)
|
---|
306 | m.__file__ = "<string>" # hopefully not a real filename...
|
---|
307 | m.__loader__ = "dummy" # pretend the filename is understood by a loader
|
---|
308 | exec "def x(): pass" in m.__dict__
|
---|
309 | self.assertEqual(inspect.getsourcefile(m.x.func_code), '<string>')
|
---|
310 | del sys.modules[name]
|
---|
311 | inspect.getmodule(compile('a=10','','single'))
|
---|
312 |
|
---|
313 | def test_proceed_with_fake_filename(self):
|
---|
314 | '''doctest monkeypatches linecache to enable inspection'''
|
---|
315 | fn, source = '<test>', 'def x(): pass\n'
|
---|
316 | getlines = linecache.getlines
|
---|
317 | def monkey(filename, module_globals=None):
|
---|
318 | if filename == fn:
|
---|
319 | return source.splitlines(True)
|
---|
320 | else:
|
---|
321 | return getlines(filename, module_globals)
|
---|
322 | linecache.getlines = monkey
|
---|
323 | try:
|
---|
324 | ns = {}
|
---|
325 | exec compile(source, fn, 'single') in ns
|
---|
326 | inspect.getsource(ns["x"])
|
---|
327 | finally:
|
---|
328 | linecache.getlines = getlines
|
---|
329 |
|
---|
330 | class TestDecorators(GetSourceBase):
|
---|
331 | fodderFile = mod2
|
---|
332 |
|
---|
333 | def test_wrapped_decorator(self):
|
---|
334 | self.assertSourceEqual(mod2.wrapped, 14, 17)
|
---|
335 |
|
---|
336 | def test_replacing_decorator(self):
|
---|
337 | self.assertSourceEqual(mod2.gone, 9, 10)
|
---|
338 |
|
---|
339 | class TestOneliners(GetSourceBase):
|
---|
340 | fodderFile = mod2
|
---|
341 | def test_oneline_lambda(self):
|
---|
342 | # Test inspect.getsource with a one-line lambda function.
|
---|
343 | self.assertSourceEqual(mod2.oll, 25, 25)
|
---|
344 |
|
---|
345 | def test_threeline_lambda(self):
|
---|
346 | # Test inspect.getsource with a three-line lambda function,
|
---|
347 | # where the second and third lines are _not_ indented.
|
---|
348 | self.assertSourceEqual(mod2.tll, 28, 30)
|
---|
349 |
|
---|
350 | def test_twoline_indented_lambda(self):
|
---|
351 | # Test inspect.getsource with a two-line lambda function,
|
---|
352 | # where the second line _is_ indented.
|
---|
353 | self.assertSourceEqual(mod2.tlli, 33, 34)
|
---|
354 |
|
---|
355 | def test_onelinefunc(self):
|
---|
356 | # Test inspect.getsource with a regular one-line function.
|
---|
357 | self.assertSourceEqual(mod2.onelinefunc, 37, 37)
|
---|
358 |
|
---|
359 | def test_manyargs(self):
|
---|
360 | # Test inspect.getsource with a regular function where
|
---|
361 | # the arguments are on two lines and _not_ indented and
|
---|
362 | # the body on the second line with the last arguments.
|
---|
363 | self.assertSourceEqual(mod2.manyargs, 40, 41)
|
---|
364 |
|
---|
365 | def test_twolinefunc(self):
|
---|
366 | # Test inspect.getsource with a regular function where
|
---|
367 | # the body is on two lines, following the argument list and
|
---|
368 | # continued on the next line by a \\.
|
---|
369 | self.assertSourceEqual(mod2.twolinefunc, 44, 45)
|
---|
370 |
|
---|
371 | def test_lambda_in_list(self):
|
---|
372 | # Test inspect.getsource with a one-line lambda function
|
---|
373 | # defined in a list, indented.
|
---|
374 | self.assertSourceEqual(mod2.a[1], 49, 49)
|
---|
375 |
|
---|
376 | def test_anonymous(self):
|
---|
377 | # Test inspect.getsource with a lambda function defined
|
---|
378 | # as argument to another function.
|
---|
379 | self.assertSourceEqual(mod2.anonymous, 55, 55)
|
---|
380 |
|
---|
381 | class TestBuggyCases(GetSourceBase):
|
---|
382 | fodderFile = mod2
|
---|
383 |
|
---|
384 | def test_with_comment(self):
|
---|
385 | self.assertSourceEqual(mod2.with_comment, 58, 59)
|
---|
386 |
|
---|
387 | def test_multiline_sig(self):
|
---|
388 | self.assertSourceEqual(mod2.multiline_sig[0], 63, 64)
|
---|
389 |
|
---|
390 | def test_nested_class(self):
|
---|
391 | self.assertSourceEqual(mod2.func69().func71, 71, 72)
|
---|
392 |
|
---|
393 | def test_one_liner_followed_by_non_name(self):
|
---|
394 | self.assertSourceEqual(mod2.func77, 77, 77)
|
---|
395 |
|
---|
396 | def test_one_liner_dedent_non_name(self):
|
---|
397 | self.assertSourceEqual(mod2.cls82.func83, 83, 83)
|
---|
398 |
|
---|
399 | def test_with_comment_instead_of_docstring(self):
|
---|
400 | self.assertSourceEqual(mod2.func88, 88, 90)
|
---|
401 |
|
---|
402 | def test_method_in_dynamic_class(self):
|
---|
403 | self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97)
|
---|
404 |
|
---|
405 | @unittest.skipIf(
|
---|
406 | not hasattr(unicodedata, '__file__') or
|
---|
407 | unicodedata.__file__[-4:] in (".pyc", ".pyo"),
|
---|
408 | "unicodedata is not an external binary module")
|
---|
409 | def test_findsource_binary(self):
|
---|
410 | self.assertRaises(IOError, inspect.getsource, unicodedata)
|
---|
411 | self.assertRaises(IOError, inspect.findsource, unicodedata)
|
---|
412 |
|
---|
413 | def test_findsource_code_in_linecache(self):
|
---|
414 | lines = ["x=1"]
|
---|
415 | co = compile(lines[0], "_dynamically_created_file", "exec")
|
---|
416 | self.assertRaises(IOError, inspect.findsource, co)
|
---|
417 | self.assertRaises(IOError, inspect.getsource, co)
|
---|
418 | linecache.cache[co.co_filename] = (1, None, lines, co.co_filename)
|
---|
419 | self.assertEqual(inspect.findsource(co), (lines,0))
|
---|
420 | self.assertEqual(inspect.getsource(co), lines[0])
|
---|
421 |
|
---|
422 | def test_findsource_without_filename(self):
|
---|
423 | for fname in ['', '<string>']:
|
---|
424 | co = compile('x=1', fname, "exec")
|
---|
425 | self.assertRaises(IOError, inspect.findsource, co)
|
---|
426 | self.assertRaises(IOError, inspect.getsource, co)
|
---|
427 |
|
---|
428 |
|
---|
429 | class _BrokenDataDescriptor(object):
|
---|
430 | """
|
---|
431 | A broken data descriptor. See bug #1785.
|
---|
432 | """
|
---|
433 | def __get__(*args):
|
---|
434 | raise AssertionError("should not __get__ data descriptors")
|
---|
435 |
|
---|
436 | def __set__(*args):
|
---|
437 | raise RuntimeError
|
---|
438 |
|
---|
439 | def __getattr__(*args):
|
---|
440 | raise AssertionError("should not __getattr__ data descriptors")
|
---|
441 |
|
---|
442 |
|
---|
443 | class _BrokenMethodDescriptor(object):
|
---|
444 | """
|
---|
445 | A broken method descriptor. See bug #1785.
|
---|
446 | """
|
---|
447 | def __get__(*args):
|
---|
448 | raise AssertionError("should not __get__ method descriptors")
|
---|
449 |
|
---|
450 | def __getattr__(*args):
|
---|
451 | raise AssertionError("should not __getattr__ method descriptors")
|
---|
452 |
|
---|
453 |
|
---|
454 | # Helper for testing classify_class_attrs.
|
---|
455 | def attrs_wo_objs(cls):
|
---|
456 | return [t[:3] for t in inspect.classify_class_attrs(cls)]
|
---|
457 |
|
---|
458 |
|
---|
459 | class TestClassesAndFunctions(unittest.TestCase):
|
---|
460 | def test_classic_mro(self):
|
---|
461 | # Test classic-class method resolution order.
|
---|
462 | class A: pass
|
---|
463 | class B(A): pass
|
---|
464 | class C(A): pass
|
---|
465 | class D(B, C): pass
|
---|
466 |
|
---|
467 | expected = (D, B, A, C)
|
---|
468 | got = inspect.getmro(D)
|
---|
469 | self.assertEqual(expected, got)
|
---|
470 |
|
---|
471 | def test_newstyle_mro(self):
|
---|
472 | # The same w/ new-class MRO.
|
---|
473 | class A(object): pass
|
---|
474 | class B(A): pass
|
---|
475 | class C(A): pass
|
---|
476 | class D(B, C): pass
|
---|
477 |
|
---|
478 | expected = (D, B, C, A, object)
|
---|
479 | got = inspect.getmro(D)
|
---|
480 | self.assertEqual(expected, got)
|
---|
481 |
|
---|
482 | def assertArgSpecEquals(self, routine, args_e, varargs_e = None,
|
---|
483 | varkw_e = None, defaults_e = None,
|
---|
484 | formatted = None):
|
---|
485 | args, varargs, varkw, defaults = inspect.getargspec(routine)
|
---|
486 | self.assertEqual(args, args_e)
|
---|
487 | self.assertEqual(varargs, varargs_e)
|
---|
488 | self.assertEqual(varkw, varkw_e)
|
---|
489 | self.assertEqual(defaults, defaults_e)
|
---|
490 | if formatted is not None:
|
---|
491 | self.assertEqual(inspect.formatargspec(args, varargs, varkw, defaults),
|
---|
492 | formatted)
|
---|
493 |
|
---|
494 | def test_getargspec(self):
|
---|
495 | self.assertArgSpecEquals(mod.eggs, ['x', 'y'], formatted = '(x, y)')
|
---|
496 |
|
---|
497 | self.assertArgSpecEquals(mod.spam,
|
---|
498 | ['a', 'b', 'c', 'd', ['e', ['f']]],
|
---|
499 | 'g', 'h', (3, (4, (5,))),
|
---|
500 | '(a, b, c, d=3, (e, (f,))=(4, (5,)), *g, **h)')
|
---|
501 |
|
---|
502 | def test_getargspec_method(self):
|
---|
503 | class A(object):
|
---|
504 | def m(self):
|
---|
505 | pass
|
---|
506 | self.assertArgSpecEquals(A.m, ['self'])
|
---|
507 |
|
---|
508 | def test_getargspec_sublistofone(self):
|
---|
509 | with check_py3k_warnings(
|
---|
510 | ("tuple parameter unpacking has been removed", SyntaxWarning),
|
---|
511 | ("parenthesized argument names are invalid", SyntaxWarning)):
|
---|
512 | exec 'def sublistOfOne((foo,)): return 1'
|
---|
513 | self.assertArgSpecEquals(sublistOfOne, [['foo']])
|
---|
514 |
|
---|
515 | exec 'def fakeSublistOfOne((foo)): return 1'
|
---|
516 | self.assertArgSpecEquals(fakeSublistOfOne, ['foo'])
|
---|
517 |
|
---|
518 |
|
---|
519 | def _classify_test(self, newstyle):
|
---|
520 | """Helper for testing that classify_class_attrs finds a bunch of
|
---|
521 | different kinds of attributes on a given class.
|
---|
522 | """
|
---|
523 | if newstyle:
|
---|
524 | base = object
|
---|
525 | else:
|
---|
526 | class base:
|
---|
527 | pass
|
---|
528 |
|
---|
529 | class A(base):
|
---|
530 | def s(): pass
|
---|
531 | s = staticmethod(s)
|
---|
532 |
|
---|
533 | def c(cls): pass
|
---|
534 | c = classmethod(c)
|
---|
535 |
|
---|
536 | def getp(self): pass
|
---|
537 | p = property(getp)
|
---|
538 |
|
---|
539 | def m(self): pass
|
---|
540 |
|
---|
541 | def m1(self): pass
|
---|
542 |
|
---|
543 | datablob = '1'
|
---|
544 |
|
---|
545 | dd = _BrokenDataDescriptor()
|
---|
546 | md = _BrokenMethodDescriptor()
|
---|
547 |
|
---|
548 | attrs = attrs_wo_objs(A)
|
---|
549 | self.assertIn(('s', 'static method', A), attrs, 'missing static method')
|
---|
550 | self.assertIn(('c', 'class method', A), attrs, 'missing class method')
|
---|
551 | self.assertIn(('p', 'property', A), attrs, 'missing property')
|
---|
552 | self.assertIn(('m', 'method', A), attrs, 'missing plain method')
|
---|
553 | self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
|
---|
554 | self.assertIn(('datablob', 'data', A), attrs, 'missing data')
|
---|
555 | self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')
|
---|
556 | self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')
|
---|
557 |
|
---|
558 | class B(A):
|
---|
559 | def m(self): pass
|
---|
560 |
|
---|
561 | attrs = attrs_wo_objs(B)
|
---|
562 | self.assertIn(('s', 'static method', A), attrs, 'missing static method')
|
---|
563 | self.assertIn(('c', 'class method', A), attrs, 'missing class method')
|
---|
564 | self.assertIn(('p', 'property', A), attrs, 'missing property')
|
---|
565 | self.assertIn(('m', 'method', B), attrs, 'missing plain method')
|
---|
566 | self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
|
---|
567 | self.assertIn(('datablob', 'data', A), attrs, 'missing data')
|
---|
568 | self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')
|
---|
569 | self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')
|
---|
570 |
|
---|
571 |
|
---|
572 | class C(A):
|
---|
573 | def m(self): pass
|
---|
574 | def c(self): pass
|
---|
575 |
|
---|
576 | attrs = attrs_wo_objs(C)
|
---|
577 | self.assertIn(('s', 'static method', A), attrs, 'missing static method')
|
---|
578 | self.assertIn(('c', 'method', C), attrs, 'missing plain method')
|
---|
579 | self.assertIn(('p', 'property', A), attrs, 'missing property')
|
---|
580 | self.assertIn(('m', 'method', C), attrs, 'missing plain method')
|
---|
581 | self.assertIn(('m1', 'method', A), attrs, 'missing plain method')
|
---|
582 | self.assertIn(('datablob', 'data', A), attrs, 'missing data')
|
---|
583 | self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')
|
---|
584 | self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')
|
---|
585 |
|
---|
586 | class D(B, C):
|
---|
587 | def m1(self): pass
|
---|
588 |
|
---|
589 | attrs = attrs_wo_objs(D)
|
---|
590 | self.assertIn(('s', 'static method', A), attrs, 'missing static method')
|
---|
591 | if newstyle:
|
---|
592 | self.assertIn(('c', 'method', C), attrs, 'missing plain method')
|
---|
593 | else:
|
---|
594 | self.assertIn(('c', 'class method', A), attrs, 'missing class method')
|
---|
595 | self.assertIn(('p', 'property', A), attrs, 'missing property')
|
---|
596 | self.assertIn(('m', 'method', B), attrs, 'missing plain method')
|
---|
597 | self.assertIn(('m1', 'method', D), attrs, 'missing plain method')
|
---|
598 | self.assertIn(('datablob', 'data', A), attrs, 'missing data')
|
---|
599 | self.assertIn(('md', 'method', A), attrs, 'missing method descriptor')
|
---|
600 | self.assertIn(('dd', 'data', A), attrs, 'missing data descriptor')
|
---|
601 |
|
---|
602 |
|
---|
603 | def test_classify_oldstyle(self):
|
---|
604 | """classify_class_attrs finds static methods, class methods,
|
---|
605 | properties, normal methods, and data attributes on an old-style
|
---|
606 | class.
|
---|
607 | """
|
---|
608 | self._classify_test(False)
|
---|
609 |
|
---|
610 |
|
---|
611 | def test_classify_newstyle(self):
|
---|
612 | """Just like test_classify_oldstyle, but for a new-style class.
|
---|
613 | """
|
---|
614 | self._classify_test(True)
|
---|
615 |
|
---|
616 | def test_classify_builtin_types(self):
|
---|
617 | # Simple sanity check that all built-in types can have their
|
---|
618 | # attributes classified.
|
---|
619 | for name in dir(__builtin__):
|
---|
620 | builtin = getattr(__builtin__, name)
|
---|
621 | if isinstance(builtin, type):
|
---|
622 | inspect.classify_class_attrs(builtin)
|
---|
623 |
|
---|
624 | def test_getmembers_method(self):
|
---|
625 | # Old-style classes
|
---|
626 | class B:
|
---|
627 | def f(self):
|
---|
628 | pass
|
---|
629 |
|
---|
630 | self.assertIn(('f', B.f), inspect.getmembers(B))
|
---|
631 | # contrary to spec, ismethod() is also True for unbound methods
|
---|
632 | # (see #1785)
|
---|
633 | self.assertIn(('f', B.f), inspect.getmembers(B, inspect.ismethod))
|
---|
634 | b = B()
|
---|
635 | self.assertIn(('f', b.f), inspect.getmembers(b))
|
---|
636 | self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod))
|
---|
637 |
|
---|
638 | # New-style classes
|
---|
639 | class B(object):
|
---|
640 | def f(self):
|
---|
641 | pass
|
---|
642 |
|
---|
643 | self.assertIn(('f', B.f), inspect.getmembers(B))
|
---|
644 | self.assertIn(('f', B.f), inspect.getmembers(B, inspect.ismethod))
|
---|
645 | b = B()
|
---|
646 | self.assertIn(('f', b.f), inspect.getmembers(b))
|
---|
647 | self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod))
|
---|
648 |
|
---|
649 |
|
---|
650 | class TestGetcallargsFunctions(unittest.TestCase):
|
---|
651 |
|
---|
652 | # tuple parameters are named '.1', '.2', etc.
|
---|
653 | is_tuplename = re.compile(r'^\.\d+$').match
|
---|
654 |
|
---|
655 | def assertEqualCallArgs(self, func, call_params_string, locs=None):
|
---|
656 | locs = dict(locs or {}, func=func)
|
---|
657 | r1 = eval('func(%s)' % call_params_string, None, locs)
|
---|
658 | r2 = eval('inspect.getcallargs(func, %s)' % call_params_string, None,
|
---|
659 | locs)
|
---|
660 | self.assertEqual(r1, r2)
|
---|
661 |
|
---|
662 | def assertEqualException(self, func, call_param_string, locs=None):
|
---|
663 | locs = dict(locs or {}, func=func)
|
---|
664 | try:
|
---|
665 | eval('func(%s)' % call_param_string, None, locs)
|
---|
666 | except Exception, ex1:
|
---|
667 | pass
|
---|
668 | else:
|
---|
669 | self.fail('Exception not raised')
|
---|
670 | try:
|
---|
671 | eval('inspect.getcallargs(func, %s)' % call_param_string, None,
|
---|
672 | locs)
|
---|
673 | except Exception, ex2:
|
---|
674 | pass
|
---|
675 | else:
|
---|
676 | self.fail('Exception not raised')
|
---|
677 | self.assertIs(type(ex1), type(ex2))
|
---|
678 | self.assertEqual(str(ex1), str(ex2))
|
---|
679 |
|
---|
680 | def makeCallable(self, signature):
|
---|
681 | """Create a function that returns its locals(), excluding the
|
---|
682 | autogenerated '.1', '.2', etc. tuple param names (if any)."""
|
---|
683 | with check_py3k_warnings(
|
---|
684 | ("tuple parameter unpacking has been removed", SyntaxWarning),
|
---|
685 | quiet=True):
|
---|
686 | code = ("lambda %s: dict(i for i in locals().items() "
|
---|
687 | "if not is_tuplename(i[0]))")
|
---|
688 | return eval(code % signature, {'is_tuplename' : self.is_tuplename})
|
---|
689 |
|
---|
690 | def test_plain(self):
|
---|
691 | f = self.makeCallable('a, b=1')
|
---|
692 | self.assertEqualCallArgs(f, '2')
|
---|
693 | self.assertEqualCallArgs(f, '2, 3')
|
---|
694 | self.assertEqualCallArgs(f, 'a=2')
|
---|
695 | self.assertEqualCallArgs(f, 'b=3, a=2')
|
---|
696 | self.assertEqualCallArgs(f, '2, b=3')
|
---|
697 | # expand *iterable / **mapping
|
---|
698 | self.assertEqualCallArgs(f, '*(2,)')
|
---|
699 | self.assertEqualCallArgs(f, '*[2]')
|
---|
700 | self.assertEqualCallArgs(f, '*(2, 3)')
|
---|
701 | self.assertEqualCallArgs(f, '*[2, 3]')
|
---|
702 | self.assertEqualCallArgs(f, '**{"a":2}')
|
---|
703 | self.assertEqualCallArgs(f, 'b=3, **{"a":2}')
|
---|
704 | self.assertEqualCallArgs(f, '2, **{"b":3}')
|
---|
705 | self.assertEqualCallArgs(f, '**{"b":3, "a":2}')
|
---|
706 | # expand UserList / UserDict
|
---|
707 | self.assertEqualCallArgs(f, '*UserList([2])')
|
---|
708 | self.assertEqualCallArgs(f, '*UserList([2, 3])')
|
---|
709 | self.assertEqualCallArgs(f, '**UserDict(a=2)')
|
---|
710 | self.assertEqualCallArgs(f, '2, **UserDict(b=3)')
|
---|
711 | self.assertEqualCallArgs(f, 'b=2, **UserDict(a=3)')
|
---|
712 | # unicode keyword args
|
---|
713 | self.assertEqualCallArgs(f, '**{u"a":2}')
|
---|
714 | self.assertEqualCallArgs(f, 'b=3, **{u"a":2}')
|
---|
715 | self.assertEqualCallArgs(f, '2, **{u"b":3}')
|
---|
716 | self.assertEqualCallArgs(f, '**{u"b":3, u"a":2}')
|
---|
717 |
|
---|
718 | def test_varargs(self):
|
---|
719 | f = self.makeCallable('a, b=1, *c')
|
---|
720 | self.assertEqualCallArgs(f, '2')
|
---|
721 | self.assertEqualCallArgs(f, '2, 3')
|
---|
722 | self.assertEqualCallArgs(f, '2, 3, 4')
|
---|
723 | self.assertEqualCallArgs(f, '*(2,3,4)')
|
---|
724 | self.assertEqualCallArgs(f, '2, *[3,4]')
|
---|
725 | self.assertEqualCallArgs(f, '2, 3, *UserList([4])')
|
---|
726 |
|
---|
727 | def test_varkw(self):
|
---|
728 | f = self.makeCallable('a, b=1, **c')
|
---|
729 | self.assertEqualCallArgs(f, 'a=2')
|
---|
730 | self.assertEqualCallArgs(f, '2, b=3, c=4')
|
---|
731 | self.assertEqualCallArgs(f, 'b=3, a=2, c=4')
|
---|
732 | self.assertEqualCallArgs(f, 'c=4, **{"a":2, "b":3}')
|
---|
733 | self.assertEqualCallArgs(f, '2, c=4, **{"b":3}')
|
---|
734 | self.assertEqualCallArgs(f, 'b=2, **{"a":3, "c":4}')
|
---|
735 | self.assertEqualCallArgs(f, '**UserDict(a=2, b=3, c=4)')
|
---|
736 | self.assertEqualCallArgs(f, '2, c=4, **UserDict(b=3)')
|
---|
737 | self.assertEqualCallArgs(f, 'b=2, **UserDict(a=3, c=4)')
|
---|
738 | # unicode keyword args
|
---|
739 | self.assertEqualCallArgs(f, 'c=4, **{u"a":2, u"b":3}')
|
---|
740 | self.assertEqualCallArgs(f, '2, c=4, **{u"b":3}')
|
---|
741 | self.assertEqualCallArgs(f, 'b=2, **{u"a":3, u"c":4}')
|
---|
742 |
|
---|
743 | def test_varkw_only(self):
|
---|
744 | # issue11256:
|
---|
745 | f = self.makeCallable('**c')
|
---|
746 | self.assertEqualCallArgs(f, '')
|
---|
747 | self.assertEqualCallArgs(f, 'a=1')
|
---|
748 | self.assertEqualCallArgs(f, 'a=1, b=2')
|
---|
749 | self.assertEqualCallArgs(f, 'c=3, **{"a": 1, "b": 2}')
|
---|
750 | self.assertEqualCallArgs(f, '**UserDict(a=1, b=2)')
|
---|
751 | self.assertEqualCallArgs(f, 'c=3, **UserDict(a=1, b=2)')
|
---|
752 |
|
---|
753 | def test_tupleargs(self):
|
---|
754 | f = self.makeCallable('(b,c), (d,(e,f))=(0,[1,2])')
|
---|
755 | self.assertEqualCallArgs(f, '(2,3)')
|
---|
756 | self.assertEqualCallArgs(f, '[2,3]')
|
---|
757 | self.assertEqualCallArgs(f, 'UserList([2,3])')
|
---|
758 | self.assertEqualCallArgs(f, '(2,3), (4,(5,6))')
|
---|
759 | self.assertEqualCallArgs(f, '(2,3), (4,[5,6])')
|
---|
760 | self.assertEqualCallArgs(f, '(2,3), [4,UserList([5,6])]')
|
---|
761 |
|
---|
762 | def test_multiple_features(self):
|
---|
763 | f = self.makeCallable('a, b=2, (c,(d,e))=(3,[4,5]), *f, **g')
|
---|
764 | self.assertEqualCallArgs(f, '2, 3, (4,[5,6]), 7')
|
---|
765 | self.assertEqualCallArgs(f, '2, 3, *[(4,[5,6]), 7], x=8')
|
---|
766 | self.assertEqualCallArgs(f, '2, 3, x=8, *[(4,[5,6]), 7]')
|
---|
767 | self.assertEqualCallArgs(f, '2, x=8, *[3, (4,[5,6]), 7], y=9')
|
---|
768 | self.assertEqualCallArgs(f, 'x=8, *[2, 3, (4,[5,6])], y=9')
|
---|
769 | self.assertEqualCallArgs(f, 'x=8, *UserList([2, 3, (4,[5,6])]), '
|
---|
770 | '**{"y":9, "z":10}')
|
---|
771 | self.assertEqualCallArgs(f, '2, x=8, *UserList([3, (4,[5,6])]), '
|
---|
772 | '**UserDict(y=9, z=10)')
|
---|
773 |
|
---|
774 | def test_errors(self):
|
---|
775 | f0 = self.makeCallable('')
|
---|
776 | f1 = self.makeCallable('a, b')
|
---|
777 | f2 = self.makeCallable('a, b=1')
|
---|
778 | # f0 takes no arguments
|
---|
779 | self.assertEqualException(f0, '1')
|
---|
780 | self.assertEqualException(f0, 'x=1')
|
---|
781 | self.assertEqualException(f0, '1,x=1')
|
---|
782 | # f1 takes exactly 2 arguments
|
---|
783 | self.assertEqualException(f1, '')
|
---|
784 | self.assertEqualException(f1, '1')
|
---|
785 | self.assertEqualException(f1, 'a=2')
|
---|
786 | self.assertEqualException(f1, 'b=3')
|
---|
787 | # f2 takes at least 1 argument
|
---|
788 | self.assertEqualException(f2, '')
|
---|
789 | self.assertEqualException(f2, 'b=3')
|
---|
790 | for f in f1, f2:
|
---|
791 | # f1/f2 takes exactly/at most 2 arguments
|
---|
792 | self.assertEqualException(f, '2, 3, 4')
|
---|
793 | self.assertEqualException(f, '1, 2, 3, a=1')
|
---|
794 | self.assertEqualException(f, '2, 3, 4, c=5')
|
---|
795 | self.assertEqualException(f, '2, 3, 4, a=1, c=5')
|
---|
796 | # f got an unexpected keyword argument
|
---|
797 | self.assertEqualException(f, 'c=2')
|
---|
798 | self.assertEqualException(f, '2, c=3')
|
---|
799 | self.assertEqualException(f, '2, 3, c=4')
|
---|
800 | self.assertEqualException(f, '2, c=4, b=3')
|
---|
801 | self.assertEqualException(f, '**{u"\u03c0\u03b9": 4}')
|
---|
802 | # f got multiple values for keyword argument
|
---|
803 | self.assertEqualException(f, '1, a=2')
|
---|
804 | self.assertEqualException(f, '1, **{"a":2}')
|
---|
805 | self.assertEqualException(f, '1, 2, b=3')
|
---|
806 | # XXX: Python inconsistency
|
---|
807 | # - for functions and bound methods: unexpected keyword 'c'
|
---|
808 | # - for unbound methods: multiple values for keyword 'a'
|
---|
809 | #self.assertEqualException(f, '1, c=3, a=2')
|
---|
810 | f = self.makeCallable('(a,b)=(0,1)')
|
---|
811 | self.assertEqualException(f, '1')
|
---|
812 | self.assertEqualException(f, '[1]')
|
---|
813 | self.assertEqualException(f, '(1,2,3)')
|
---|
814 | # issue11256:
|
---|
815 | f3 = self.makeCallable('**c')
|
---|
816 | self.assertEqualException(f3, '1, 2')
|
---|
817 | self.assertEqualException(f3, '1, 2, a=1, b=2')
|
---|
818 |
|
---|
819 | class TestGetcallargsMethods(TestGetcallargsFunctions):
|
---|
820 |
|
---|
821 | def setUp(self):
|
---|
822 | class Foo(object):
|
---|
823 | pass
|
---|
824 | self.cls = Foo
|
---|
825 | self.inst = Foo()
|
---|
826 |
|
---|
827 | def makeCallable(self, signature):
|
---|
828 | assert 'self' not in signature
|
---|
829 | mk = super(TestGetcallargsMethods, self).makeCallable
|
---|
830 | self.cls.method = mk('self, ' + signature)
|
---|
831 | return self.inst.method
|
---|
832 |
|
---|
833 | class TestGetcallargsUnboundMethods(TestGetcallargsMethods):
|
---|
834 |
|
---|
835 | def makeCallable(self, signature):
|
---|
836 | super(TestGetcallargsUnboundMethods, self).makeCallable(signature)
|
---|
837 | return self.cls.method
|
---|
838 |
|
---|
839 | def assertEqualCallArgs(self, func, call_params_string, locs=None):
|
---|
840 | return super(TestGetcallargsUnboundMethods, self).assertEqualCallArgs(
|
---|
841 | *self._getAssertEqualParams(func, call_params_string, locs))
|
---|
842 |
|
---|
843 | def assertEqualException(self, func, call_params_string, locs=None):
|
---|
844 | return super(TestGetcallargsUnboundMethods, self).assertEqualException(
|
---|
845 | *self._getAssertEqualParams(func, call_params_string, locs))
|
---|
846 |
|
---|
847 | def _getAssertEqualParams(self, func, call_params_string, locs=None):
|
---|
848 | assert 'inst' not in call_params_string
|
---|
849 | locs = dict(locs or {}, inst=self.inst)
|
---|
850 | return (func, 'inst,' + call_params_string, locs)
|
---|
851 |
|
---|
852 | def test_main():
|
---|
853 | run_unittest(
|
---|
854 | TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
|
---|
855 | TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
|
---|
856 | TestGetcallargsFunctions, TestGetcallargsMethods,
|
---|
857 | TestGetcallargsUnboundMethods)
|
---|
858 |
|
---|
859 | if __name__ == "__main__":
|
---|
860 | test_main()
|
---|