Skip to content

Commit 5e6c390

Browse files
committed
Resolve issue #16 by no longer normalising all names to lowercase, add integration test with Java, minor refactor of testing workflow
1 parent 5d3ca8f commit 5e6c390

38 files changed

+274
-132
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,7 @@ investigation/
2424
jvm-unknown-unknown.json
2525

2626
# Vendored files
27-
**/vendor/*
27+
**/vendor/*
28+
29+
# Generated by Javac in the integration tests
30+
**/*.class

CompareSize.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,10 @@ def collect_tests(binary_dir, only_run, dont_run):
9898
candidate_tests = [t for t in candidate_tests if os.path.basename(t) not in skip]
9999

100100
final_tests = []
101-
skipped_no_jvm = []
102-
skip_flag_name = "no_jvm_target.flag"
101+
skip_flag_name = "use_target_json.flag"
103102
for test_dir in candidate_tests:
104103
flag_path = os.path.join(test_dir, skip_flag_name)
105-
if os.path.exists(flag_path):
104+
if not os.path.exists(flag_path):
106105
final_tests.append(test_dir)
107106

108107
if not final_tests:

Investigate.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def investigate_test(test_name: str, release_mode: bool):
5959
# 4. Build with Cargo and capture output
6060
print("|-- ⚒️ Building with Cargo and capturing logs...")
6161
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):
62+
use_target_json = os.path.join(test_dir, "use_target_json.flag")
63+
if os.path.exists(use_target_json):
6464
build_cmd.extend(["--target", "../../../jvm-unknown-unknown.json"])
6565

6666
proc = run_command(build_cmd, cwd=test_dir)
@@ -76,9 +76,9 @@ def investigate_test(test_name: str, release_mode: bool):
7676
print("|-- 🔎 Locating generated JAR file...")
7777
target_dir = "release" if release_mode else "debug"
7878

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...")
79+
# Handle the case where no custom target JSON is used ('use_target_json.flag' doesn't exist)
80+
if not os.path.exists(use_target_json):
81+
print("|---- Found 'use_target_json.flag', searching for JAR in standard deps folder...")
8282
deps_dir = os.path.join(test_dir, "target", target_dir, "deps")
8383
jar_file_name = None
8484
if os.path.isdir(deps_dir):

Tester.py

Lines changed: 180 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,69 +3,82 @@
33
import subprocess
44
import sys
55
import 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

712
def 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+
1120
def normalize_name(test_name: str) -> str:
1221
return test_name.replace("_", " ").capitalize()
1322

1423
def 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\nSTDERR:\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\nSTDERR:\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\nSTDERR:\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\nSTDERR:\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\nSTDERR:\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\nSTDERR:\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+
111204
def 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

156260
if __name__ == "__main__":
157-
main()
261+
main()

0 commit comments

Comments
 (0)