| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  | import dis | 
					
						
							|  |  |  | import io | 
					
						
							|  |  |  | import textwrap | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  | import types | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from test.support.bytecode_helper import AssemblerTestCase | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Tests for the code-object creation stage of the compiler. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class IsolatedAssembleTests(AssemblerTestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def complete_metadata(self, metadata, filename="myfile.py"): | 
					
						
							|  |  |  |         if metadata is None: | 
					
						
							|  |  |  |             metadata = {} | 
					
						
							|  |  |  |         for key in ['name', 'qualname']: | 
					
						
							|  |  |  |             metadata.setdefault(key, key) | 
					
						
							|  |  |  |         for key in ['consts']: | 
					
						
							|  |  |  |             metadata.setdefault(key, []) | 
					
						
							| 
									
										
										
										
											2023-05-09 11:02:14 -06:00
										 |  |  |         for key in ['names', 'varnames', 'cellvars', 'freevars', 'fasthidden']: | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |             metadata.setdefault(key, {}) | 
					
						
							|  |  |  |         for key in ['argcount', 'posonlyargcount', 'kwonlyargcount']: | 
					
						
							|  |  |  |             metadata.setdefault(key, 0) | 
					
						
							|  |  |  |         metadata.setdefault('firstlineno', 1) | 
					
						
							|  |  |  |         metadata.setdefault('filename', filename) | 
					
						
							|  |  |  |         return metadata | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  |     def insts_to_code_object(self, insts, metadata): | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |         metadata = self.complete_metadata(metadata) | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |         seq = self.seq_from_insts(insts) | 
					
						
							|  |  |  |         return self.get_code_object(metadata['filename'], seq, metadata) | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  |     def assemble_test(self, insts, metadata, expected): | 
					
						
							|  |  |  |         co = self.insts_to_code_object(insts, metadata) | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |         self.assertIsInstance(co, types.CodeType) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expected_metadata = {} | 
					
						
							|  |  |  |         for key, value in metadata.items(): | 
					
						
							| 
									
										
										
										
											2023-05-09 11:02:14 -06:00
										 |  |  |             if key == "fasthidden": | 
					
						
							|  |  |  |                 # not exposed on code object | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |             if isinstance(value, list): | 
					
						
							|  |  |  |                 expected_metadata[key] = tuple(value) | 
					
						
							|  |  |  |             elif isinstance(value, dict): | 
					
						
							|  |  |  |                 expected_metadata[key] = tuple(value.keys()) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 expected_metadata[key] = value | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for key, value in expected_metadata.items(): | 
					
						
							|  |  |  |             self.assertEqual(getattr(co, "co_" + key), value) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         f = types.FunctionType(co, {}) | 
					
						
							|  |  |  |         for args, res in expected.items(): | 
					
						
							|  |  |  |             self.assertEqual(f(*args), res) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_simple_expr(self): | 
					
						
							|  |  |  |         metadata = { | 
					
						
							|  |  |  |             'filename' : 'avg.py', | 
					
						
							|  |  |  |             'name'     : 'avg', | 
					
						
							|  |  |  |             'qualname' : 'stats.avg', | 
					
						
							| 
									
										
										
										
											2023-05-09 14:33:40 +01:00
										 |  |  |             'consts'   : {2 : 0}, | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |             'argcount' : 2, | 
					
						
							|  |  |  |             'varnames' : {'x' : 0, 'y' : 1}, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # code for "return (x+y)/2" | 
					
						
							|  |  |  |         insts = [ | 
					
						
							|  |  |  |             ('RESUME', 0), | 
					
						
							|  |  |  |             ('LOAD_FAST', 0, 1),   # 'x' | 
					
						
							|  |  |  |             ('LOAD_FAST', 1, 1),   # 'y' | 
					
						
							|  |  |  |             ('BINARY_OP', 0, 1),   # '+' | 
					
						
							|  |  |  |             ('LOAD_CONST', 0, 1),  # 2 | 
					
						
							|  |  |  |             ('BINARY_OP', 11, 1),   # '/' | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('RETURN_VALUE', None, 1), | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |         ] | 
					
						
							|  |  |  |         expected = {(3, 4) : 3.5, (-100, 200) : 50, (10, 18) : 14} | 
					
						
							|  |  |  |         self.assemble_test(insts, metadata, expected) | 
					
						
							| 
									
										
										
										
											2023-06-29 10:34:00 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_expression_with_pseudo_instruction_load_closure(self): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def mod_two(x): | 
					
						
							|  |  |  |             def inner(): | 
					
						
							|  |  |  |                 return x | 
					
						
							|  |  |  |             return inner() % 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         inner_code = mod_two.__code__.co_consts[1] | 
					
						
							|  |  |  |         assert isinstance(inner_code, types.CodeType) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         metadata = { | 
					
						
							|  |  |  |             'filename' : 'mod_two.py', | 
					
						
							|  |  |  |             'name'     : 'mod_two', | 
					
						
							|  |  |  |             'qualname' : 'nested.mod_two', | 
					
						
							|  |  |  |             'cellvars' : {'x' : 0}, | 
					
						
							|  |  |  |             'consts': {None: 0, inner_code: 1, 2: 2}, | 
					
						
							|  |  |  |             'argcount' : 1, | 
					
						
							|  |  |  |             'varnames' : {'x' : 0}, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         instructions = [ | 
					
						
							|  |  |  |             ('RESUME', 0,), | 
					
						
							|  |  |  |             ('LOAD_CLOSURE', 0, 1), | 
					
						
							|  |  |  |             ('BUILD_TUPLE', 1, 1), | 
					
						
							|  |  |  |             ('LOAD_CONST', 1, 1), | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('MAKE_FUNCTION', None, 2), | 
					
						
							| 
									
										
										
										
											2023-06-29 10:34:00 -06:00
										 |  |  |             ('SET_FUNCTION_ATTRIBUTE', 8, 2), | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('PUSH_NULL', None, 1), | 
					
						
							| 
									
										
										
										
											2023-06-29 10:34:00 -06:00
										 |  |  |             ('CALL', 0, 2),                     # (lambda: x)() | 
					
						
							|  |  |  |             ('LOAD_CONST', 2, 2),               # 2 | 
					
						
							|  |  |  |             ('BINARY_OP', 6, 2),                # % | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('RETURN_VALUE', None, 2) | 
					
						
							| 
									
										
										
										
											2023-06-29 10:34:00 -06:00
										 |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         expected = {(0,): 0, (1,): 1, (2,): 0, (120,): 0, (121,): 1} | 
					
						
							|  |  |  |         self.assemble_test(instructions, metadata, expected) | 
					
						
							| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_exception_table(self): | 
					
						
							|  |  |  |         metadata = { | 
					
						
							|  |  |  |             'filename' : 'exc.py', | 
					
						
							|  |  |  |             'name'     : 'exc', | 
					
						
							|  |  |  |             'consts'   : {2 : 0}, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # code for "try: pass\n except: pass" | 
					
						
							|  |  |  |         insts = [ | 
					
						
							|  |  |  |             ('RESUME', 0), | 
					
						
							|  |  |  |             ('SETUP_FINALLY', 3), | 
					
						
							|  |  |  |             ('RETURN_CONST', 0), | 
					
						
							|  |  |  |             ('SETUP_CLEANUP', 8), | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('PUSH_EXC_INFO', None), | 
					
						
							|  |  |  |             ('POP_TOP', None), | 
					
						
							|  |  |  |             ('POP_EXCEPT', None), | 
					
						
							| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  |             ('RETURN_CONST', 0), | 
					
						
							|  |  |  |             ('COPY', 3), | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             ('POP_EXCEPT', None), | 
					
						
							| 
									
										
										
										
											2024-02-22 12:36:44 +00:00
										 |  |  |             ('RERAISE', 1), | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         co = self.insts_to_code_object(insts, metadata) | 
					
						
							|  |  |  |         output = io.StringIO() | 
					
						
							|  |  |  |         dis.dis(co, file=output) | 
					
						
							|  |  |  |         exc_table = textwrap.dedent("""
 | 
					
						
							|  |  |  |                                        ExceptionTable: | 
					
						
							|  |  |  |                                          L1 to L2 -> L2 [0] | 
					
						
							|  |  |  |                                          L2 to L3 -> L3 [1] lasti | 
					
						
							|  |  |  |                                     """)
 | 
					
						
							|  |  |  |         self.assertTrue(output.getvalue().endswith(exc_table)) |