1+ import io
2+ import logging
13import os
24import re
35import shutil
46import subprocess
57import tempfile
8+ import traceback
9+ from unittest .mock import patch
610
711import torch
812import torch ._dynamo
@@ -41,15 +45,80 @@ def tearDownClass(cls):
4145 print (f"test_minifier_common tmpdir kept at: { cls .DEBUG_DIR } " )
4246 cls ._exit_stack .close ()
4347
48+ def _gen_codegen_fn_patch_code (self , device , bug_type ):
49+ assert bug_type in ("compile_error" , "runtime_error" , "accuracy" )
50+ return f"""\
51+ { torch ._dynamo .config .codegen_config ()}
52+ { torch ._inductor .config .codegen_config ()}
53+ torch._inductor.config.{ "cpp" if device == "cpu" else "triton" } .inject_relu_bug_TESTING_ONLY = { bug_type !r}
54+ """
55+
56+ def _maybe_subprocess_run (self , args , * , isolate , cwd = None ):
57+ if not isolate :
58+ assert len (args ) >= 2 , args
59+ assert args [0 ] == "python3" , args
60+ if args [1 ] == "-c" :
61+ assert len (args ) == 3 , args
62+ code = args [2 ]
63+ args = ["-c" ]
64+ else :
65+ assert len (args ) >= 2 , args
66+ with open (args [1 ], "r" ) as f :
67+ code = f .read ()
68+ args = args [1 :]
69+
70+ # WARNING: This is not a perfect simulation of running
71+ # the program out of tree. We only interpose on things we KNOW we
72+ # need to handle for tests. If you need more stuff, you will
73+ # need to augment this appropriately.
74+
75+ # NB: Can't use save_config because that will omit some fields,
76+ # but we must save and reset ALL fields
77+ dynamo_config = torch ._dynamo .config ._config .copy ()
78+ inductor_config = torch ._inductor .config ._config .copy ()
79+ try :
80+ stderr = io .StringIO ()
81+ log_handler = logging .StreamHandler (stderr )
82+ log = logging .getLogger ("torch._dynamo" )
83+ log .addHandler (log_handler )
84+ try :
85+ prev_cwd = os .getcwd ()
86+ if cwd is not None :
87+ os .chdir (cwd )
88+ with patch ("sys.argv" , args ):
89+ exec (code , {"__name__" : "__main__" })
90+ rc = 0
91+ except Exception :
92+ rc = 1
93+ traceback .print_exc (file = stderr )
94+ finally :
95+ log .removeHandler (log_handler )
96+ if cwd is not None :
97+ os .chdir (prev_cwd )
98+ finally :
99+ object .__setattr__ (torch ._dynamo .config , "_config" , dynamo_config )
100+ object .__setattr__ (torch ._inductor .config , "_config" , inductor_config )
101+
102+ # TODO: return a more appropriate data structure here
103+ return subprocess .CompletedProcess (
104+ args ,
105+ rc ,
106+ b"" ,
107+ stderr .getvalue ().encode ("utf-8" ),
108+ )
109+ else :
110+ return subprocess .run (args , capture_output = True , cwd = cwd )
111+
44112 # Run `code` in a separate python process.
45113 # Returns the completed process state and the directory containing the
46114 # minifier launcher script, if `code` outputted it.
47- def _run_test_code (self , code ):
48- proc = subprocess . run (
49- ["python3" , "-c" , code ], capture_output = True , cwd = self .DEBUG_DIR
115+ def _run_test_code (self , code , * , isolate ):
116+ proc = self . _maybe_subprocess_run (
117+ ["python3" , "-c" , code ], isolate = isolate , cwd = self .DEBUG_DIR
50118 )
51- print ("stdout:" , proc .stdout .decode ("utf-8" ))
52- print ("stderr:" , proc .stderr .decode ("utf-8" ))
119+
120+ print ("test stdout:" , proc .stdout .decode ("utf-8" ))
121+ print ("test stderr:" , proc .stderr .decode ("utf-8" ))
53122 repro_dir_match = re .search (
54123 r"(\S+)minifier_launcher.py" , proc .stderr .decode ("utf-8" )
55124 )
@@ -58,34 +127,35 @@ def _run_test_code(self, code):
58127 return proc , None
59128
60129 # Runs the minifier launcher script in `repro_dir`
61- def _run_minifier_launcher (self , repro_dir ):
130+ def _run_minifier_launcher (self , repro_dir , isolate ):
62131 self .assertIsNotNone (repro_dir )
63132 launch_file = os .path .join (repro_dir , "minifier_launcher.py" )
64133 with open (launch_file , "r" ) as f :
65134 launch_code = f .read ()
66135 self .assertTrue (os .path .exists (launch_file ))
67136
68- launch_proc = subprocess .run (
69- ["python3" , launch_file ],
70- capture_output = True ,
71- cwd = repro_dir ,
72- )
137+ args = ["python3" , launch_file , "minify" ]
138+ if not isolate :
139+ args .append ("--no-isolate" )
140+ launch_proc = self ._maybe_subprocess_run (args , isolate = isolate , cwd = repro_dir )
73141 print ("minifier stdout:" , launch_proc .stdout .decode ("utf-8" ))
74142 print ("minifier stderr:" , launch_proc .stderr .decode ("utf-8" ))
75143
76144 return launch_proc , launch_code
77145
78146 # Runs the repro script in `repro_dir`
79- def _run_repro (self , repro_dir ):
147+ def _run_repro (self , repro_dir , * , isolate = True ):
80148 self .assertIsNotNone (repro_dir )
81149 repro_file = os .path .join (repro_dir , "repro.py" )
82150 with open (repro_file , "r" ) as f :
83151 repro_code = f .read ()
84152 self .assertTrue (os .path .exists (repro_file ))
85153
86- repro_proc = subprocess . run (
87- ["python3" , repro_file ], capture_output = True , cwd = repro_dir
154+ repro_proc = self . _maybe_subprocess_run (
155+ ["python3" , repro_file ], isolate = isolate , cwd = repro_dir
88156 )
157+ print ("repro stdout:" , repro_proc .stdout .decode ("utf-8" ))
158+ print ("repro stderr:" , repro_proc .stderr .decode ("utf-8" ))
89159 return repro_proc , repro_code
90160
91161 # Template for testing code.
@@ -108,16 +178,28 @@ def _gen_test_code(self, run_code, repro_after, repro_level, patch_code):
108178 # 1. Run the problematic code (in a separate process since it could segfault)
109179 # 2. Run the generated minifier launcher script
110180 # 3. Run the generated repro script
111- def _run_full_test (self , run_code , repro_after , repro_level , patch_code ):
181+ #
182+ # If possible, you should run the test with isolate=False; use
183+ # isolate=True only if the bug you're testing would otherwise
184+ # crash the process
185+ def _run_full_test (
186+ self , run_code , repro_after , repro_level , patch_code , * , isolate
187+ ):
112188 test_code = self ._gen_test_code (run_code , repro_after , repro_level , patch_code )
113- test_proc , repro_dir = self ._run_test_code (test_code )
189+ test_proc , repro_dir = self ._run_test_code (test_code , isolate = isolate )
114190 self .assertIsNotNone (repro_dir )
115191 print ("running minifier" )
116- launch_proc , launch_code = self ._run_minifier_launcher (repro_dir )
192+ launch_proc , launch_code = self ._run_minifier_launcher (
193+ repro_dir , isolate = isolate
194+ )
117195 print ("running repro" )
118- repro_proc , repro_code = self ._run_repro (repro_dir )
196+ repro_proc , repro_code = self ._run_repro (repro_dir , isolate = isolate )
119197 return (test_proc , launch_proc , repro_proc ), (launch_code , repro_code )
120198
121- def _run_full_test_nocode (self , run_code , repro_after , repro_level , patch_code ):
122- tbs , _ = self ._run_full_test (run_code , repro_after , repro_level , patch_code )
199+ def _run_full_test_nocode (
200+ self , run_code , repro_after , repro_level , patch_code , * , isolate
201+ ):
202+ tbs , _ = self ._run_full_test (
203+ run_code , repro_after , repro_level , patch_code , isolate = isolate
204+ )
123205 return tbs
0 commit comments