python (3.12.0)
1 """This module includes tests of the code object representation.
2
3 >>> def f(x):
4 ... def g(y):
5 ... return x + y
6 ... return g
7 ...
8
9 >>> dump(f.__code__)
10 name: f
11 argcount: 1
12 posonlyargcount: 0
13 kwonlyargcount: 0
14 names: ()
15 varnames: ('x', 'g')
16 cellvars: ('x',)
17 freevars: ()
18 nlocals: 2
19 flags: 3
20 consts: ('None', '<code object g>')
21
22 >>> dump(f(4).__code__)
23 name: g
24 argcount: 1
25 posonlyargcount: 0
26 kwonlyargcount: 0
27 names: ()
28 varnames: ('y',)
29 cellvars: ()
30 freevars: ('x',)
31 nlocals: 1
32 flags: 19
33 consts: ('None',)
34
35 >>> def h(x, y):
36 ... a = x + y
37 ... b = x - y
38 ... c = a * b
39 ... return c
40 ...
41
42 >>> dump(h.__code__)
43 name: h
44 argcount: 2
45 posonlyargcount: 0
46 kwonlyargcount: 0
47 names: ()
48 varnames: ('x', 'y', 'a', 'b', 'c')
49 cellvars: ()
50 freevars: ()
51 nlocals: 5
52 flags: 3
53 consts: ('None',)
54
55 >>> def attrs(obj):
56 ... print(obj.attr1)
57 ... print(obj.attr2)
58 ... print(obj.attr3)
59
60 >>> dump(attrs.__code__)
61 name: attrs
62 argcount: 1
63 posonlyargcount: 0
64 kwonlyargcount: 0
65 names: ('print', 'attr1', 'attr2', 'attr3')
66 varnames: ('obj',)
67 cellvars: ()
68 freevars: ()
69 nlocals: 1
70 flags: 3
71 consts: ('None',)
72
73 >>> def optimize_away():
74 ... 'doc string'
75 ... 'not a docstring'
76 ... 53
77 ... 0x53
78
79 >>> dump(optimize_away.__code__)
80 name: optimize_away
81 argcount: 0
82 posonlyargcount: 0
83 kwonlyargcount: 0
84 names: ()
85 varnames: ()
86 cellvars: ()
87 freevars: ()
88 nlocals: 0
89 flags: 3
90 consts: ("'doc string'", 'None')
91
92 >>> def keywordonly_args(a,b,*,k1):
93 ... return a,b,k1
94 ...
95
96 >>> dump(keywordonly_args.__code__)
97 name: keywordonly_args
98 argcount: 2
99 posonlyargcount: 0
100 kwonlyargcount: 1
101 names: ()
102 varnames: ('a', 'b', 'k1')
103 cellvars: ()
104 freevars: ()
105 nlocals: 3
106 flags: 3
107 consts: ('None',)
108
109 >>> def posonly_args(a,b,/,c):
110 ... return a,b,c
111 ...
112
113 >>> dump(posonly_args.__code__)
114 name: posonly_args
115 argcount: 3
116 posonlyargcount: 2
117 kwonlyargcount: 0
118 names: ()
119 varnames: ('a', 'b', 'c')
120 cellvars: ()
121 freevars: ()
122 nlocals: 3
123 flags: 3
124 consts: ('None',)
125
126 """
127
128 import inspect
129 import sys
130 import threading
131 import doctest
132 import unittest
133 import textwrap
134 import weakref
135 import dis
136
137 try:
138 import ctypes
139 except ImportError:
140 ctypes = None
141 from test.support import (cpython_only,
142 check_impl_detail, requires_debug_ranges,
143 gc_collect)
144 from test.support.script_helper import assert_python_ok
145 from test.support import threading_helper
146 from opcode import opmap, opname
147 COPY_FREE_VARS = opmap['COPY_FREE_VARS']
148
149
150 def consts(t):
151 """Yield a doctest-safe sequence of object reprs."""
152 for elt in t:
153 r = repr(elt)
154 if r.startswith("<code object"):
155 yield "<code object %s>" % elt.co_name
156 else:
157 yield r
158
159 def dump(co):
160 """Print out a text representation of a code object."""
161 for attr in ["name", "argcount", "posonlyargcount",
162 "kwonlyargcount", "names", "varnames",
163 "cellvars", "freevars", "nlocals", "flags"]:
164 print("%s: %s" % (attr, getattr(co, "co_" + attr)))
165 print("consts:", tuple(consts(co.co_consts)))
166
167 # Needed for test_closure_injection below
168 # Defined at global scope to avoid implicitly closing over __class__
169 def external_getitem(self, i):
170 return f"Foreign getitem: {super().__getitem__(i)}"
171
172 class ESC[4;38;5;81mCodeTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
173
174 @cpython_only
175 def test_newempty(self):
176 import _testcapi
177 co = _testcapi.code_newempty("filename", "funcname", 15)
178 self.assertEqual(co.co_filename, "filename")
179 self.assertEqual(co.co_name, "funcname")
180 self.assertEqual(co.co_firstlineno, 15)
181 #Empty code object should raise, but not crash the VM
182 with self.assertRaises(Exception):
183 exec(co)
184
185 @cpython_only
186 def test_closure_injection(self):
187 # From https://bugs.python.org/issue32176
188 from types import FunctionType
189
190 def create_closure(__class__):
191 return (lambda: __class__).__closure__
192
193 def new_code(c):
194 '''A new code object with a __class__ cell added to freevars'''
195 return c.replace(co_freevars=c.co_freevars + ('__class__',), co_code=bytes([COPY_FREE_VARS, 1])+c.co_code)
196
197 def add_foreign_method(cls, name, f):
198 code = new_code(f.__code__)
199 assert not f.__closure__
200 closure = create_closure(cls)
201 defaults = f.__defaults__
202 setattr(cls, name, FunctionType(code, globals(), name, defaults, closure))
203
204 class ESC[4;38;5;81mList(ESC[4;38;5;149mlist):
205 pass
206
207 add_foreign_method(List, "__getitem__", external_getitem)
208
209 # Ensure the closure injection actually worked
210 function = List.__getitem__
211 class_ref = function.__closure__[0].cell_contents
212 self.assertIs(class_ref, List)
213
214 # Ensure the zero-arg super() call in the injected method works
215 obj = List([1, 2, 3])
216 self.assertEqual(obj[0], "Foreign getitem: 1")
217
218 def test_constructor(self):
219 def func(): pass
220 co = func.__code__
221 CodeType = type(co)
222
223 # test code constructor
224 CodeType(co.co_argcount,
225 co.co_posonlyargcount,
226 co.co_kwonlyargcount,
227 co.co_nlocals,
228 co.co_stacksize,
229 co.co_flags,
230 co.co_code,
231 co.co_consts,
232 co.co_names,
233 co.co_varnames,
234 co.co_filename,
235 co.co_name,
236 co.co_qualname,
237 co.co_firstlineno,
238 co.co_linetable,
239 co.co_exceptiontable,
240 co.co_freevars,
241 co.co_cellvars)
242
243 def test_qualname(self):
244 self.assertEqual(
245 CodeTest.test_qualname.__code__.co_qualname,
246 CodeTest.test_qualname.__qualname__
247 )
248
249 def test_replace(self):
250 def func():
251 x = 1
252 return x
253 code = func.__code__
254
255 # different co_name, co_varnames, co_consts
256 def func2():
257 y = 2
258 z = 3
259 return y
260 code2 = func2.__code__
261
262 for attr, value in (
263 ("co_argcount", 0),
264 ("co_posonlyargcount", 0),
265 ("co_kwonlyargcount", 0),
266 ("co_nlocals", 1),
267 ("co_stacksize", 0),
268 ("co_flags", code.co_flags | inspect.CO_COROUTINE),
269 ("co_firstlineno", 100),
270 ("co_code", code2.co_code),
271 ("co_consts", code2.co_consts),
272 ("co_names", ("myname",)),
273 ("co_varnames", ('spam',)),
274 ("co_freevars", ("freevar",)),
275 ("co_cellvars", ("cellvar",)),
276 ("co_filename", "newfilename"),
277 ("co_name", "newname"),
278 ("co_linetable", code2.co_linetable),
279 ):
280 with self.subTest(attr=attr, value=value):
281 new_code = code.replace(**{attr: value})
282 self.assertEqual(getattr(new_code, attr), value)
283
284 new_code = code.replace(co_varnames=code2.co_varnames,
285 co_nlocals=code2.co_nlocals)
286 self.assertEqual(new_code.co_varnames, code2.co_varnames)
287 self.assertEqual(new_code.co_nlocals, code2.co_nlocals)
288
289 def test_nlocals_mismatch(self):
290 def func():
291 x = 1
292 return x
293 co = func.__code__
294 assert co.co_nlocals > 0;
295
296 # First we try the constructor.
297 CodeType = type(co)
298 for diff in (-1, 1):
299 with self.assertRaises(ValueError):
300 CodeType(co.co_argcount,
301 co.co_posonlyargcount,
302 co.co_kwonlyargcount,
303 # This is the only change.
304 co.co_nlocals + diff,
305 co.co_stacksize,
306 co.co_flags,
307 co.co_code,
308 co.co_consts,
309 co.co_names,
310 co.co_varnames,
311 co.co_filename,
312 co.co_name,
313 co.co_qualname,
314 co.co_firstlineno,
315 co.co_linetable,
316 co.co_exceptiontable,
317 co.co_freevars,
318 co.co_cellvars,
319 )
320 # Then we try the replace method.
321 with self.assertRaises(ValueError):
322 co.replace(co_nlocals=co.co_nlocals - 1)
323 with self.assertRaises(ValueError):
324 co.replace(co_nlocals=co.co_nlocals + 1)
325
326 def test_shrinking_localsplus(self):
327 # Check that PyCode_NewWithPosOnlyArgs resizes both
328 # localsplusnames and localspluskinds, if an argument is a cell.
329 def func(arg):
330 return lambda: arg
331 code = func.__code__
332 newcode = code.replace(co_name="func") # Should not raise SystemError
333 self.assertEqual(code, newcode)
334
335 def test_empty_linetable(self):
336 def func():
337 pass
338 new_code = code = func.__code__.replace(co_linetable=b'')
339 self.assertEqual(list(new_code.co_lines()), [])
340
341 def test_co_lnotab_is_deprecated(self): # TODO: remove in 3.14
342 def func():
343 pass
344
345 with self.assertWarns(DeprecationWarning):
346 func.__code__.co_lnotab
347
348 def test_invalid_bytecode(self):
349 def foo():
350 pass
351
352 # assert that opcode 229 is invalid
353 self.assertEqual(opname[229], '<229>')
354
355 # change first opcode to 0xeb (=229)
356 foo.__code__ = foo.__code__.replace(
357 co_code=b'\xe5' + foo.__code__.co_code[1:])
358
359 msg = "unknown opcode 229"
360 with self.assertRaisesRegex(SystemError, msg):
361 foo()
362
363 @requires_debug_ranges()
364 def test_co_positions_artificial_instructions(self):
365 import dis
366
367 namespace = {}
368 exec(textwrap.dedent("""\
369 try:
370 1/0
371 except Exception as e:
372 exc = e
373 """), namespace)
374
375 exc = namespace['exc']
376 traceback = exc.__traceback__
377 code = traceback.tb_frame.f_code
378
379 artificial_instructions = []
380 for instr, positions in zip(
381 dis.get_instructions(code, show_caches=True),
382 code.co_positions(),
383 strict=True
384 ):
385 # If any of the positions is None, then all have to
386 # be None as well for the case above. There are still
387 # some places in the compiler, where the artificial instructions
388 # get assigned the first_lineno but they don't have other positions.
389 # There is no easy way of inferring them at that stage, so for now
390 # we don't support it.
391 self.assertIn(positions.count(None), [0, 3, 4])
392
393 if not any(positions):
394 artificial_instructions.append(instr)
395
396 self.assertEqual(
397 [
398 (instruction.opname, instruction.argval)
399 for instruction in artificial_instructions
400 ],
401 [
402 ("PUSH_EXC_INFO", None),
403 ("LOAD_CONST", None), # artificial 'None'
404 ("STORE_NAME", "e"), # XX: we know the location for this
405 ("DELETE_NAME", "e"),
406 ("RERAISE", 1),
407 ("COPY", 3),
408 ("POP_EXCEPT", None),
409 ("RERAISE", 1)
410 ]
411 )
412
413 def test_endline_and_columntable_none_when_no_debug_ranges(self):
414 # Make sure that if `-X no_debug_ranges` is used, there is
415 # minimal debug info
416 code = textwrap.dedent("""
417 def f():
418 pass
419
420 positions = f.__code__.co_positions()
421 for line, end_line, column, end_column in positions:
422 assert line == end_line
423 assert column is None
424 assert end_column is None
425 """)
426 assert_python_ok('-X', 'no_debug_ranges', '-c', code)
427
428 def test_endline_and_columntable_none_when_no_debug_ranges_env(self):
429 # Same as above but using the environment variable opt out.
430 code = textwrap.dedent("""
431 def f():
432 pass
433
434 positions = f.__code__.co_positions()
435 for line, end_line, column, end_column in positions:
436 assert line == end_line
437 assert column is None
438 assert end_column is None
439 """)
440 assert_python_ok('-c', code, PYTHONNODEBUGRANGES='1')
441
442 # co_positions behavior when info is missing.
443
444 @requires_debug_ranges()
445 def test_co_positions_empty_linetable(self):
446 def func():
447 x = 1
448 new_code = func.__code__.replace(co_linetable=b'')
449 positions = new_code.co_positions()
450 for line, end_line, column, end_column in positions:
451 self.assertIsNone(line)
452 self.assertEqual(end_line, new_code.co_firstlineno + 1)
453
454 def test_code_equality(self):
455 def f():
456 try:
457 a()
458 except:
459 b()
460 else:
461 c()
462 finally:
463 d()
464 code_a = f.__code__
465 code_b = code_a.replace(co_linetable=b"")
466 code_c = code_a.replace(co_exceptiontable=b"")
467 code_d = code_b.replace(co_exceptiontable=b"")
468 self.assertNotEqual(code_a, code_b)
469 self.assertNotEqual(code_a, code_c)
470 self.assertNotEqual(code_a, code_d)
471 self.assertNotEqual(code_b, code_c)
472 self.assertNotEqual(code_b, code_d)
473 self.assertNotEqual(code_c, code_d)
474
475 def test_code_hash_uses_firstlineno(self):
476 c1 = (lambda: 1).__code__
477 c2 = (lambda: 1).__code__
478 self.assertNotEqual(c1, c2)
479 self.assertNotEqual(hash(c1), hash(c2))
480 c3 = c1.replace(co_firstlineno=17)
481 self.assertNotEqual(c1, c3)
482 self.assertNotEqual(hash(c1), hash(c3))
483
484 def test_code_hash_uses_order(self):
485 # Swapping posonlyargcount and kwonlyargcount should change the hash.
486 c = (lambda x, y, *, z=1, w=1: 1).__code__
487 self.assertEqual(c.co_argcount, 2)
488 self.assertEqual(c.co_posonlyargcount, 0)
489 self.assertEqual(c.co_kwonlyargcount, 2)
490 swapped = c.replace(co_posonlyargcount=2, co_kwonlyargcount=0)
491 self.assertNotEqual(c, swapped)
492 self.assertNotEqual(hash(c), hash(swapped))
493
494 def test_code_hash_uses_bytecode(self):
495 c = (lambda x, y: x + y).__code__
496 d = (lambda x, y: x * y).__code__
497 c1 = c.replace(co_code=d.co_code)
498 self.assertNotEqual(c, c1)
499 self.assertNotEqual(hash(c), hash(c1))
500
501
502 def isinterned(s):
503 return s is sys.intern(('_' + s + '_')[1:-1])
504
505 class ESC[4;38;5;81mCodeConstsTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
506
507 def find_const(self, consts, value):
508 for v in consts:
509 if v == value:
510 return v
511 self.assertIn(value, consts) # raises an exception
512 self.fail('Should never be reached')
513
514 def assertIsInterned(self, s):
515 if not isinterned(s):
516 self.fail('String %r is not interned' % (s,))
517
518 def assertIsNotInterned(self, s):
519 if isinterned(s):
520 self.fail('String %r is interned' % (s,))
521
522 @cpython_only
523 def test_interned_string(self):
524 co = compile('res = "str_value"', '?', 'exec')
525 v = self.find_const(co.co_consts, 'str_value')
526 self.assertIsInterned(v)
527
528 @cpython_only
529 def test_interned_string_in_tuple(self):
530 co = compile('res = ("str_value",)', '?', 'exec')
531 v = self.find_const(co.co_consts, ('str_value',))
532 self.assertIsInterned(v[0])
533
534 @cpython_only
535 def test_interned_string_in_frozenset(self):
536 co = compile('res = a in {"str_value"}', '?', 'exec')
537 v = self.find_const(co.co_consts, frozenset(('str_value',)))
538 self.assertIsInterned(tuple(v)[0])
539
540 @cpython_only
541 def test_interned_string_default(self):
542 def f(a='str_value'):
543 return a
544 self.assertIsInterned(f())
545
546 @cpython_only
547 def test_interned_string_with_null(self):
548 co = compile(r'res = "str\0value!"', '?', 'exec')
549 v = self.find_const(co.co_consts, 'str\0value!')
550 self.assertIsNotInterned(v)
551
552
553 class ESC[4;38;5;81mCodeWeakRefTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
554
555 def test_basic(self):
556 # Create a code object in a clean environment so that we know we have
557 # the only reference to it left.
558 namespace = {}
559 exec("def f(): pass", globals(), namespace)
560 f = namespace["f"]
561 del namespace
562
563 self.called = False
564 def callback(code):
565 self.called = True
566
567 # f is now the last reference to the function, and through it, the code
568 # object. While we hold it, check that we can create a weakref and
569 # deref it. Then delete it, and check that the callback gets called and
570 # the reference dies.
571 coderef = weakref.ref(f.__code__, callback)
572 self.assertTrue(bool(coderef()))
573 del f
574 gc_collect() # For PyPy or other GCs.
575 self.assertFalse(bool(coderef()))
576 self.assertTrue(self.called)
577
578 # Python implementation of location table parsing algorithm
579 def read(it):
580 return next(it)
581
582 def read_varint(it):
583 b = read(it)
584 val = b & 63;
585 shift = 0;
586 while b & 64:
587 b = read(it)
588 shift += 6
589 val |= (b&63) << shift
590 return val
591
592 def read_signed_varint(it):
593 uval = read_varint(it)
594 if uval & 1:
595 return -(uval >> 1)
596 else:
597 return uval >> 1
598
599 def parse_location_table(code):
600 line = code.co_firstlineno
601 it = iter(code.co_linetable)
602 while True:
603 try:
604 first_byte = read(it)
605 except StopIteration:
606 return
607 code = (first_byte >> 3) & 15
608 length = (first_byte & 7) + 1
609 if code == 15:
610 yield (code, length, None, None, None, None)
611 elif code == 14:
612 line_delta = read_signed_varint(it)
613 line += line_delta
614 end_line = line + read_varint(it)
615 col = read_varint(it)
616 if col == 0:
617 col = None
618 else:
619 col -= 1
620 end_col = read_varint(it)
621 if end_col == 0:
622 end_col = None
623 else:
624 end_col -= 1
625 yield (code, length, line, end_line, col, end_col)
626 elif code == 13: # No column
627 line_delta = read_signed_varint(it)
628 line += line_delta
629 yield (code, length, line, line, None, None)
630 elif code in (10, 11, 12): # new line
631 line_delta = code - 10
632 line += line_delta
633 column = read(it)
634 end_column = read(it)
635 yield (code, length, line, line, column, end_column)
636 else:
637 assert (0 <= code < 10)
638 second_byte = read(it)
639 column = code << 3 | (second_byte >> 4)
640 yield (code, length, line, line, column, column + (second_byte & 15))
641
642 def positions_from_location_table(code):
643 for _, length, line, end_line, col, end_col in parse_location_table(code):
644 for _ in range(length):
645 yield (line, end_line, col, end_col)
646
647 def dedup(lst, prev=object()):
648 for item in lst:
649 if item != prev:
650 yield item
651 prev = item
652
653 def lines_from_postions(positions):
654 return dedup(l for (l, _, _, _) in positions)
655
656 def misshappen():
657 """
658
659
660
661
662
663 """
664 x = (
665
666
667 4
668
669 +
670
671 y
672
673 )
674 y = (
675 a
676 +
677 b
678 +
679
680 d
681 )
682 return q if (
683
684 x
685
686 ) else p
687
688 def bug93662():
689 example_report_generation_message= (
690 """
691 """
692 ).strip()
693 raise ValueError()
694
695
696 class ESC[4;38;5;81mCodeLocationTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
697
698 def check_positions(self, func):
699 pos1 = list(func.__code__.co_positions())
700 pos2 = list(positions_from_location_table(func.__code__))
701 for l1, l2 in zip(pos1, pos2):
702 self.assertEqual(l1, l2)
703 self.assertEqual(len(pos1), len(pos2))
704
705 def test_positions(self):
706 self.check_positions(parse_location_table)
707 self.check_positions(misshappen)
708 self.check_positions(bug93662)
709
710 def check_lines(self, func):
711 co = func.__code__
712 lines1 = [line for _, _, line in co.co_lines()]
713 self.assertEqual(lines1, list(dedup(lines1)))
714 lines2 = list(lines_from_postions(positions_from_location_table(co)))
715 for l1, l2 in zip(lines1, lines2):
716 self.assertEqual(l1, l2)
717 self.assertEqual(len(lines1), len(lines2))
718
719 def test_lines(self):
720 self.check_lines(parse_location_table)
721 self.check_lines(misshappen)
722 self.check_lines(bug93662)
723
724 @cpython_only
725 def test_code_new_empty(self):
726 # If this test fails, it means that the construction of PyCode_NewEmpty
727 # needs to be modified! Please update this test *and* PyCode_NewEmpty,
728 # so that they both stay in sync.
729 def f():
730 pass
731 PY_CODE_LOCATION_INFO_NO_COLUMNS = 13
732 f.__code__ = f.__code__.replace(
733 co_stacksize=1,
734 co_firstlineno=42,
735 co_code=bytes(
736 [
737 dis.opmap["RESUME"], 0,
738 dis.opmap["LOAD_ASSERTION_ERROR"], 0,
739 dis.opmap["RAISE_VARARGS"], 1,
740 ]
741 ),
742 co_linetable=bytes(
743 [
744 (1 << 7)
745 | (PY_CODE_LOCATION_INFO_NO_COLUMNS << 3)
746 | (3 - 1),
747 0,
748 ]
749 ),
750 )
751 self.assertRaises(AssertionError, f)
752 self.assertEqual(
753 list(f.__code__.co_positions()),
754 3 * [(42, 42, None, None)],
755 )
756
757
758 if check_impl_detail(cpython=True) and ctypes is not None:
759 py = ctypes.pythonapi
760 freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
761
762 RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
763 RequestCodeExtraIndex.argtypes = (freefunc,)
764 RequestCodeExtraIndex.restype = ctypes.c_ssize_t
765
766 SetExtra = py.PyUnstable_Code_SetExtra
767 SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
768 SetExtra.restype = ctypes.c_int
769
770 GetExtra = py.PyUnstable_Code_GetExtra
771 GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
772 ctypes.POINTER(ctypes.c_voidp))
773 GetExtra.restype = ctypes.c_int
774
775 LAST_FREED = None
776 def myfree(ptr):
777 global LAST_FREED
778 LAST_FREED = ptr
779
780 FREE_FUNC = freefunc(myfree)
781 FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
782
783 class ESC[4;38;5;81mCoExtra(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
784 def get_func(self):
785 # Defining a function causes the containing function to have a
786 # reference to the code object. We need the code objects to go
787 # away, so we eval a lambda.
788 return eval('lambda:42')
789
790 def test_get_non_code(self):
791 f = self.get_func()
792
793 self.assertRaises(SystemError, SetExtra, 42, FREE_INDEX,
794 ctypes.c_voidp(100))
795 self.assertRaises(SystemError, GetExtra, 42, FREE_INDEX,
796 ctypes.c_voidp(100))
797
798 def test_bad_index(self):
799 f = self.get_func()
800 self.assertRaises(SystemError, SetExtra, f.__code__,
801 FREE_INDEX+100, ctypes.c_voidp(100))
802 self.assertEqual(GetExtra(f.__code__, FREE_INDEX+100,
803 ctypes.c_voidp(100)), 0)
804
805 def test_free_called(self):
806 # Verify that the provided free function gets invoked
807 # when the code object is cleaned up.
808 f = self.get_func()
809
810 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(100))
811 del f
812 self.assertEqual(LAST_FREED, 100)
813
814 def test_get_set(self):
815 # Test basic get/set round tripping.
816 f = self.get_func()
817
818 extra = ctypes.c_voidp()
819
820 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(200))
821 # reset should free...
822 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(300))
823 self.assertEqual(LAST_FREED, 200)
824
825 extra = ctypes.c_voidp()
826 GetExtra(f.__code__, FREE_INDEX, extra)
827 self.assertEqual(extra.value, 300)
828 del f
829
830 @threading_helper.requires_working_threading()
831 def test_free_different_thread(self):
832 # Freeing a code object on a different thread then
833 # where the co_extra was set should be safe.
834 f = self.get_func()
835 class ESC[4;38;5;81mThreadTest(ESC[4;38;5;149mthreadingESC[4;38;5;149m.ESC[4;38;5;149mThread):
836 def __init__(self, f, test):
837 super().__init__()
838 self.f = f
839 self.test = test
840 def run(self):
841 del self.f
842 self.test.assertEqual(LAST_FREED, 500)
843
844 SetExtra(f.__code__, FREE_INDEX, ctypes.c_voidp(500))
845 tt = ThreadTest(f, self)
846 del f
847 tt.start()
848 tt.join()
849 self.assertEqual(LAST_FREED, 500)
850
851
852 def load_tests(loader, tests, pattern):
853 tests.addTest(doctest.DocTestSuite())
854 return tests
855
856
857 if __name__ == "__main__":
858 unittest.main()