1+ #!/usr/bin/env python3
2+
3+ import os
4+ import subprocess
5+ import sys
6+ import argparse
7+ import shutil
8+
9+ # --- Helper Functions ---
10+
11+ def run_command (cmd : list , cwd = None ):
12+ """Runs a command and captures its output, printing the command first."""
13+ print (f"|-----> Running: { ' ' .join (cmd )} " )
14+ proc = subprocess .run (
15+ cmd ,
16+ cwd = cwd ,
17+ stdout = subprocess .PIPE ,
18+ stderr = subprocess .PIPE ,
19+ text = True ,
20+ encoding = 'utf-8' ,
21+ errors = 'replace'
22+ )
23+ return proc
24+
25+ def write_to_file (path : str , content : str ):
26+ """Writes content to a file, creating parent directories if they don't exist."""
27+ os .makedirs (os .path .dirname (path ), exist_ok = True )
28+ with open (path , "w" , encoding = 'utf-8' ) as f :
29+ f .write (content )
30+
31+ # --- Main Investigation Logic ---
32+
33+ def investigate_test (test_name : str , release_mode : bool ):
34+ """Runs a single test and gathers detailed investigation artifacts."""
35+ mode = "release" if release_mode else "debug"
36+ print (f"🔬 Starting investigation for test '{ test_name } ' in '{ mode } ' mode." )
37+
38+ # 1. Set up paths
39+ test_dir = os .path .join ("tests" , "binary" , test_name )
40+ investigation_dir = os .path .join ("investigation" , f"{ test_name } _{ mode } " )
41+ javap_dir = os .path .join (investigation_dir , "javap_output" )
42+ extracted_jar_dir = os .path .join (investigation_dir , "extracted_jar" )
43+
44+ if not os .path .isdir (test_dir ):
45+ print (f"❌ Error: Test directory not found at '{ test_dir } '" )
46+ sys .exit (1 )
47+
48+ # 2. Create a clean investigation directory
49+ print (f"|-- 📁 Setting up investigation directory: '{ investigation_dir } '" )
50+ if os .path .exists (investigation_dir ):
51+ shutil .rmtree (investigation_dir )
52+ os .makedirs (javap_dir )
53+ os .makedirs (extracted_jar_dir )
54+
55+ # 3. Clean the project
56+ print ("|-- 🧼 Cleaning test folder..." )
57+ run_command (["cargo" , "clean" ], cwd = test_dir )
58+
59+ # 4. Build with Cargo and capture output
60+ print ("|-- ⚒️ Building with Cargo and capturing logs..." )
61+ build_cmd = ["cargo" , "build" , "--release" ] if release_mode else ["cargo" , "build" ]
62+ no_jvm_target = os .path .join (test_dir , "no_jvm_target.flag" )
63+ if not os .path .exists (no_jvm_target ):
64+ build_cmd .extend (["--target" , "../../../jvm-unknown-unknown.json" ])
65+
66+ proc = run_command (build_cmd , cwd = test_dir )
67+ build_log_content = f"--- COMMAND ---\n { ' ' .join (build_cmd )} \n \n --- RETURN CODE: { proc .returncode } ---\n \n --- STDOUT ---\n { proc .stdout } \n \n --- STDERR ---\n { proc .stderr } "
68+ write_to_file (os .path .join (investigation_dir , "cargo_build.log" ), build_log_content )
69+
70+ if proc .returncode != 0 :
71+ print (f"|---- ❌ cargo build failed with code { proc .returncode } . See cargo_build.log for details." )
72+ else :
73+ print ("|---- ✅ cargo build succeeded." )
74+
75+ # 5. Locate the JAR file, handling special cases
76+ print ("|-- 🔎 Locating generated JAR file..." )
77+ target_dir = "release" if release_mode else "debug"
78+
79+ # Handle the case where no custom target is used ('no_jvm_target.flag' exists)
80+ if os .path .exists (no_jvm_target ):
81+ print ("|---- Found 'no_jvm_target.flag', searching for JAR in standard deps folder..." )
82+ deps_dir = os .path .join (test_dir , "target" , target_dir , "deps" )
83+ jar_file_name = None
84+ if os .path .isdir (deps_dir ):
85+ for file in os .listdir (deps_dir ):
86+ if file .startswith (test_name ) and file .endswith (".jar" ):
87+ jar_file_name = file
88+ break
89+
90+ if jar_file_name is None :
91+ print (f"|---- ❌ No JAR file found in { deps_dir } . Cannot continue." )
92+ return
93+
94+ # Move the jar to the location expected by the rest of the script for consistency
95+ src_path = os .path .join (deps_dir , jar_file_name )
96+ dest_dir = os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir )
97+ os .makedirs (dest_dir , exist_ok = True )
98+ dest_path = os .path .join (dest_dir , f"{ test_name } .jar" )
99+ shutil .move (src_path , dest_path )
100+ print (f"|---- Moved JAR to { dest_path } " )
101+
102+ jar_path = os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir , f"{ test_name } .jar" )
103+
104+ if not os .path .exists (jar_path ):
105+ print (f"|---- ❌ JAR file not found at expected path: { jar_path } " )
106+ print ("|---- Investigation cannot proceed without a JAR file. Exiting." )
107+ return
108+
109+ print (f"|---- ✅ Found JAR: { jar_path } " )
110+
111+ # 6. Analyze the JAR file
112+ # 6a. `jar tf` - List JAR contents
113+ print ("|-- 🔍 Listing JAR contents (jar tf)..." )
114+ proc = run_command (["jar" , "tf" , jar_path ])
115+ write_to_file (os .path .join (investigation_dir , "jar_contents.txt" ), proc .stdout )
116+
117+ # 6b. `jar xf` - Extract JAR
118+ print ("|-- 📦 Extracting JAR contents for analysis..." )
119+ run_command (["jar" , "xf" , os .path .abspath (jar_path )], cwd = extracted_jar_dir )
120+
121+ # 6c. `javap -v -p` on all .class files
122+ print ("|-- 뜯 Decompiling .class files (javap -v -p)..." )
123+ class_count = 0
124+ for root , _ , files in os .walk (extracted_jar_dir ):
125+ for file in files :
126+ if file .endswith (".class" ):
127+ class_count += 1
128+ class_file_path = os .path .join (root , file )
129+ relative_path = os .path .relpath (class_file_path , extracted_jar_dir )
130+
131+ # Create a mirrored directory structure for the output
132+ output_path_dir = os .path .join (javap_dir , os .path .dirname (relative_path ))
133+ output_file_path = os .path .join (output_path_dir , os .path .basename (relative_path ).replace (".class" , ".javap.txt" ))
134+
135+ # Run javap on the .class file
136+ proc = run_command (["javap" , "-v" , "-p" , class_file_path ])
137+ output_content = f"--- COMMAND ---\n javap -v -p { class_file_path } \n \n --- STDOUT ---\n { proc .stdout } \n \n --- STDERR ---\n { proc .stderr } "
138+ write_to_file (output_file_path , output_content )
139+
140+ print (f"|---- ✅ Decompiled { class_count } class file(s)." )
141+
142+ # 7. Run with Java and capture output
143+ print ("|-- 🤖 Running with Java and capturing logs..." )
144+ # This classpath is copied from the original script. It assumes the script is run from the project root.
145+ classpath = f"library/build/distributions/library-0.1.0/lib/library-0.1.0.jar:library/build/distributions/library-0.1.0/lib/kotlin-stdlib-2.1.20.jar:{ jar_path } "
146+ java_cmd = ["java" , "-cp" , classpath , test_name ]
147+
148+ proc = run_command (java_cmd )
149+
150+ run_log_content = f"--- COMMAND ---\n { ' ' .join (java_cmd )} \n \n --- RETURN CODE: { proc .returncode } ---\n \n --- STDOUT ---\n { proc .stdout } \n \n --- STDERR ---\n { proc .stderr } "
151+ write_to_file (os .path .join (investigation_dir , "java_run.log" ), run_log_content )
152+
153+ if proc .returncode != 0 :
154+ print (f"|---- ❌ Java process exited with non-zero code: { proc .returncode } . See java_run.log for details." )
155+ else :
156+ print ("|---- ✅ Java process exited successfully (code 0)." )
157+
158+ print (f"\n ✨ Investigation complete! All artifacts are in '{ investigation_dir } '" )
159+
160+ def main ():
161+ parser = argparse .ArgumentParser (
162+ description = "Run a single Rustc JVM backend test and gather detailed artifacts for investigation." ,
163+ formatter_class = argparse .RawTextHelpFormatter
164+ )
165+ parser .add_argument ("test_name" , help = "The name of the test directory to investigate (e.g., 'hello_world')." )
166+ parser .add_argument ("--release" , action = "store_true" , help = "Run the test in release mode (cargo build --release)." )
167+ args = parser .parse_args ()
168+
169+ investigate_test (args .test_name , args .release )
170+
171+ if __name__ == "__main__" :
172+ main ()
0 commit comments