33import subprocess
44import sys
55import argparse
6+ import glob
7+
8+ # A constant for the base classpath needed by both test types
9+ # NOTE: You may need to update the kotlin version in the future
10+ RUNTIME_CLASSPATH_BASE = "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"
611
712def read_from_file (path : str ) -> str :
813 with open (path , "r" ) as f :
914 return f .read ()
1015
16+ def write_to_file (path : str , content : str ):
17+ with open (path , "w" ) as f :
18+ f .write (content )
19+
1120def normalize_name (test_name : str ) -> str :
1221 return test_name .replace ("_" , " " ).capitalize ()
1322
1423def run_command (cmd : list , cwd = None ):
1524 proc = subprocess .run (cmd , cwd = cwd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True )
1625 return proc
1726
18- def write_to_file (path : str , content : str ):
19- with open (path , "w" ) as f :
20- f .write (content )
21-
22- def process_test (test_dir : str , release_mode : bool ):
23- test_name = os .path .basename (test_dir )
24- normalized = normalize_name (test_name )
25- print (f"|-- Test '{ test_name } ' ({ normalized } )" )
26-
27- print ("|--- 🧼 Cleaning test folder..." )
28- proc = run_command (["cargo" , "clean" ], cwd = test_dir )
29- if proc .returncode != 0 :
30- fail_path = os .path .join (test_dir , "cargo-clean-fail.generated" )
31- output = f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } "
32- write_to_file (fail_path , output )
33- print (f"|---- ❌ cargo clean exited with code { proc .returncode } " )
34- return False
35-
27+ def build_rust_code (test_dir : str , release_mode : bool ) -> tuple [bool , str ]:
28+ """Builds the Rust code and returns (success, target_dir_name)."""
3629 print ("|--- ⚒️ Building with Cargo..." )
3730 build_cmd = ["cargo" , "build" , "--release" ] if release_mode else ["cargo" , "build" ]
38- no_jvm_target = os .path .join (test_dir , "no_jvm_target .flag" )
39- if not os .path .exists (no_jvm_target ):
40- print ("|---- 🛠️ Building with JVM target..." )
31+ use_target_json = os .path .join (test_dir , "use_target_json .flag" )
32+ if os .path .exists (use_target_json ):
33+ print ("|---- 🛠️ Building with JVM target JSON ..." )
4134 build_cmd .extend (["--target" , "../../../jvm-unknown-unknown.json" ])
35+
4236 proc = run_command (build_cmd , cwd = test_dir )
4337 if proc .returncode != 0 :
4438 fail_path = os .path .join (test_dir , "cargo-build-fail.generated" )
4539 output = f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } "
4640 write_to_file (fail_path , output )
4741 print (f"|---- ❌ cargo build exited with code { proc .returncode } " )
48- return False
42+ return False , ""
4943
50- print ("|--- 🤖 Running with Java..." )
5144 target_dir = "release" if release_mode else "debug"
52- if os .path .exists (no_jvm_target ):
53- jar_path = os .path .join (test_dir , "target" , target_dir , "deps" , f"{ test_name } -*.jar" )
45+ return True , target_dir
46+
47+ def find_and_prepare_jar (test_dir : str , test_name : str , target_dir : str ) -> tuple [bool , str ]:
48+ """Finds the generated JAR and moves it to a predictable location."""
49+ # If using a custom target, cargo places artifacts in a different folder structure.
50+ # If not, it's in target/{debug|release}/deps and needs to be moved.
51+ use_target_json = os .path .join (test_dir , "use_target_json.flag" )
52+ if not os .path .exists (use_target_json ):
53+ deps_dir = os .path .join (test_dir , "target" , target_dir , "deps" )
5454 jar_file = None
55- for file in os .listdir (os .path .join (test_dir , "target" , target_dir , "deps" )):
56- if file .startswith (test_name ) and file .endswith (".jar" ):
57- jar_file = file
58- break
55+ try :
56+ for file in os .listdir (deps_dir ):
57+ if file .startswith (test_name ) and file .endswith (".jar" ):
58+ jar_file = file
59+ break
60+ except FileNotFoundError :
61+ print (f"|---- ❌ Dependency directory not found: { deps_dir } " )
62+ return False , ""
63+
5964 if jar_file is None :
60- print ("|---- ❌ No jar file found in target/{target_dir}/deps" )
61- return False
62- os .makedirs (os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir ), exist_ok = True )
63- os .rename (os .path .join (test_dir , "target" , target_dir , "deps" , jar_file ),
64- os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir , f"{ test_name } .jar" ))
65+ print (f"|---- ❌ No jar file found for '{ test_name } ' in target/{ target_dir } /deps" )
66+ return False , ""
67+
68+ # Move jar to a predictable location
69+ dest_dir = os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir )
70+ os .makedirs (dest_dir , exist_ok = True )
71+ os .rename (os .path .join (deps_dir , jar_file ), os .path .join (dest_dir , f"{ test_name } .jar" ))
6572
6673 jar_path = os .path .join (test_dir , "target" , "jvm-unknown-unknown" , target_dir , f"{ test_name } .jar" )
67- proc = run_command (["java" , "-cp" , 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 } " , test_name ])
68-
74+ if not os .path .exists (jar_path ):
75+ print (f"|---- ❌ JAR file not found at expected path: { jar_path } " )
76+ return False , ""
77+ return True , jar_path
78+
79+ def check_results (proc , test_dir : str , release_mode : bool ) -> bool :
80+ """Checks the return code and output of a completed process."""
81+ # Check return code
6982 expected_returncode_file = os .path .join (test_dir , "java-returncode.expected" )
7083 if os .path .exists (expected_returncode_file ):
7184 expected_returncode = int (read_from_file (expected_returncode_file ).strip ())
@@ -75,39 +88,119 @@ def process_test(test_dir: str, release_mode: bool):
7588 write_to_file (fail_path , output )
7689 print (f"|---- ❌ java exited with code { proc .returncode } , expected { expected_returncode } " )
7790 return False
78- else :
79- if proc .returncode != 0 :
80- fail_path = os .path .join (test_dir , "java-fail.generated" )
81- output = f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } "
82- write_to_file (fail_path , output )
83- print (f"|---- ❌ java exited with code { proc .returncode } " )
84- return False
91+ elif proc .returncode != 0 :
92+ fail_path = os .path .join (test_dir , "java-fail.generated" )
93+ output = f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } "
94+ write_to_file (fail_path , output )
95+ print (f"|---- ❌ java exited with code { proc .returncode } " )
96+ return False
8597
98+ # Check output
8699 expected_file = os .path .join (test_dir , "java-output.release.expected" ) if release_mode else os .path .join (test_dir , "java-output.expected" )
87100 if not os .path .exists (expected_file ) and release_mode :
88101 expected_file = os .path .join (test_dir , "java-output.expected" )
89102
90103 if os .path .exists (expected_file ):
91- expected_output = read_from_file (expected_file )
92- if expected_output . strip () == "" :
93- expected_output = "STDOUT:STDERR:"
94- else :
95- expected_output = expected_output .replace ( " \n " , "" )
96- actual_output = f"STDOUT: { proc . stdout . strip () } STDERR: { proc . stderr . strip () } "
97- actual_output = actual_output . replace ( " \n " , "" )
98- if actual_output != expected_output . strip () :
104+ expected_output = read_from_file (expected_file ). strip ()
105+ actual_output = f"STDOUT: { proc . stdout . strip ()} STDERR: { proc . stderr . strip () } " . strip ()
106+
107+ # Normalize to handle different line endings and trailing whitespace
108+ expected_output = "" . join ( expected_output .split () )
109+ actual_output = "" . join ( actual_output . split ())
110+
111+ if actual_output != expected_output :
99112 diff_path = os .path .join (test_dir , "output-diff.generated" )
100- write_to_file (diff_path , actual_output )
113+ # Write a more human-readable diff file
114+ diff_content = f"--- EXPECTED ---\n { read_from_file (expected_file )} \n \n --- ACTUAL STDOUT ---\n { proc .stdout } \n \n --- ACTUAL STDERR ---\n { proc .stderr } \n "
115+ write_to_file (diff_path , diff_content )
101116 print ("|---- ❌ java output did not match expected output" )
102117 return False
103118 else :
104119 print ("|--- ✅ Output matches expected output!" )
105- else :
106- print ("|--- ⚠️ Expected output file not found. Skipping comparison." )
120+
121+ return True
122+
123+ def process_binary_test (test_dir : str , release_mode : bool ) -> bool :
124+ test_name = os .path .basename (test_dir )
125+ normalized = normalize_name (test_name )
126+ print (f"|-- Test '{ test_name } ' ({ normalized } )" )
127+
128+ print ("|--- 🧼 Cleaning test folder..." )
129+ proc = run_command (["cargo" , "clean" ], cwd = test_dir )
130+ if proc .returncode != 0 :
131+ fail_path = os .path .join (test_dir , "cargo-clean-fail.generated" )
132+ write_to_file (fail_path , f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } " )
133+ print (f"|---- ❌ cargo clean exited with code { proc .returncode } " )
134+ return False
135+
136+ build_ok , target_dir = build_rust_code (test_dir , release_mode )
137+ if not build_ok :
138+ return False
139+
140+ jar_ok , jar_path = find_and_prepare_jar (test_dir , test_name , target_dir )
141+ if not jar_ok :
142+ return False
143+
144+ print ("|--- 🤖 Running with Java..." )
145+ java_cp = f"{ RUNTIME_CLASSPATH_BASE } :{ jar_path } "
146+ proc = run_command (["java" , "-cp" , java_cp , test_name ])
147+
148+ if not check_results (proc , test_dir , release_mode ):
149+ return False
107150
108151 print ("|--- ✅ Binary test passed!" )
109152 return True
110153
154+ def process_integration_test (test_dir : str , release_mode : bool ) -> bool :
155+ test_name = os .path .basename (test_dir )
156+ normalized = normalize_name (test_name )
157+ print (f"|-- Test '{ test_name } ' ({ normalized } )" )
158+
159+ print ("|--- 🧼 Cleaning test folder..." )
160+ run_command (["cargo" , "clean" ], cwd = test_dir ) # Ignore clean failure for now
161+
162+ build_ok , target_dir = build_rust_code (test_dir , release_mode )
163+ if not build_ok :
164+ return False
165+
166+ jar_ok , jar_path = find_and_prepare_jar (test_dir , test_name , target_dir )
167+ if not jar_ok :
168+ return False
169+
170+ print ("|--- ☕ Compiling Java test source..." )
171+ abs_java_files = glob .glob (os .path .join (test_dir , "*.java" ))
172+ java_files = [os .path .basename (f ) for f in abs_java_files ]
173+ if not java_files :
174+ print ("|---- ❌ No .java files found in test directory." )
175+ return False
176+
177+ base_cp_components = [os .path .relpath (p , test_dir ) for p in RUNTIME_CLASSPATH_BASE .split (':' )]
178+ relative_jar_path = os .path .relpath (jar_path , test_dir )
179+
180+ javac_cp_list = ['.' ] + base_cp_components + [relative_jar_path ]
181+ javac_cp = os .pathsep .join (javac_cp_list )
182+
183+ javac_cmd = ["javac" , "-cp" , javac_cp ] + java_files
184+ proc = run_command (javac_cmd , cwd = test_dir )
185+ if proc .returncode != 0 :
186+ fail_path = os .path .join (test_dir , "javac-fail.generated" )
187+ write_to_file (fail_path , f"STDOUT:\n { proc .stdout } \n \n STDERR:\n { proc .stderr } " )
188+ print (f"|---- ❌ javac exited with code { proc .returncode } " )
189+ return False
190+
191+ print ("|--- 🤖 Running with Java..." )
192+ # Convention: The main class for integration tests is 'Main'
193+ main_class = "Main"
194+
195+ java_cp = javac_cp
196+ proc = run_command (["java" , "-cp" , java_cp , main_class ], cwd = test_dir )
197+
198+ if not check_results (proc , test_dir , release_mode ):
199+ return False
200+
201+ print ("|--- ✅ Integration test passed!" )
202+ return True
203+
111204def main ():
112205 parser = argparse .ArgumentParser (description = "Tester for Rustc's JVM Codegen Backend" )
113206 parser .add_argument ("--release" , action = "store_true" , help = "Run cargo in release mode" )
@@ -120,32 +213,43 @@ def main():
120213
121214 if args .release :
122215 print ("|- ⚒️ Running in release mode" )
123-
124216 print (" " )
125217
126- # Gather test directories
127- binary_dir = os .path .join ("tests" , "binary" )
128- if os .path .isdir (binary_dir ):
129- binary_tests = [os .path .join (binary_dir , d ) for d in os .listdir (binary_dir ) if os .path .isdir (os .path .join (binary_dir , d ))]
130- else :
131- binary_tests = []
218+ # --- Gather and filter tests ---
219+ only_run_set = set ([name .strip () for name in args .only_run .split ("," )]) if args .only_run else None
220+ dont_run_set = set ([name .strip () for name in args .dont_run .split ("," )]) if args .dont_run else set ()
132221
133- # Filter based on --only-run
134- if args .only_run :
135- requested_tests = set ([name .strip () for name in args .only_run .split ("," )])
136- binary_tests = [t for t in binary_tests if os .path .basename (t ) in requested_tests ]
222+ def discover_tests (test_type_dir ):
223+ if not os .path .isdir (test_type_dir ):
224+ return []
225+ all_tests = [os .path .join (test_type_dir , d ) for d in os .listdir (test_type_dir ) if os .path .isdir (os .path .join (test_type_dir , d ))]
226+
227+ # Filter tests
228+ if only_run_set :
229+ all_tests = [t for t in all_tests if os .path .basename (t ) in only_run_set ]
230+ all_tests = [t for t in all_tests if os .path .basename (t ) not in dont_run_set ]
231+ return all_tests
137232
138- # Exclude tests based on --dont-run
139- if args .dont_run :
140- excluded_tests = set ([name .strip () for name in args .dont_run .split ("," )])
141- binary_tests = [t for t in binary_tests if os .path .basename (t ) not in excluded_tests ]
142-
143- print (f"|- 📦 Running { len (binary_tests )} binary build test(s)..." )
144- for test_dir in binary_tests :
145- if not process_test (test_dir , args .release ):
146- overall_success = False
233+ binary_tests = discover_tests (os .path .join ("tests" , "binary" ))
234+ integration_tests = discover_tests (os .path .join ("tests" , "integration" ))
235+
236+ # --- Run Binary Tests ---
237+ if binary_tests :
238+ print (f"|- 📦 Running { len (binary_tests )} binary test(s)..." )
239+ for test_dir in sorted (binary_tests ):
240+ if not process_binary_test (test_dir , args .release ):
241+ overall_success = False
242+ print (" " )
243+
244+ # --- Run Integration Tests ---
245+ if integration_tests :
246+ print (f"|- 🔗 Running { len (integration_tests )} integration test(s)..." )
247+ for test_dir in sorted (integration_tests ):
248+ if not process_integration_test (test_dir , args .release ):
249+ overall_success = False
250+ print (" " )
147251
148- print ( "" )
252+ # --- Final Summary ---
149253 if overall_success :
150254 print ("|-✅ All tests passed!" )
151255 sys .exit (0 )
@@ -154,4 +258,4 @@ def main():
154258 sys .exit (1 )
155259
156260if __name__ == "__main__" :
157- main ()
261+ main ()
0 commit comments