Skip to content

Commit dc7398e

Browse files
committed
script for comparing compilers
1 parent e59744b commit dc7398e

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

compare_compilers.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
"""
3+
compare_compilers.py — for every .f90 file in a directory, compile with
4+
gfortran and ifort, then compare behaviour:
5+
6+
both fail to compile → consistent failure, skip
7+
one compiles, one not → discrepancy, report
8+
both compile → run both, diff stdout
9+
same output → SUCCESS
10+
diff output → report diff
11+
12+
Usage:
13+
python compare_compilers.py <directory> [options]
14+
"""
15+
16+
import argparse
17+
import difflib
18+
import os
19+
import subprocess
20+
import sys
21+
import tempfile
22+
from pathlib import Path
23+
24+
25+
# ---------------------------------------------------------------------------
26+
# Helpers
27+
# ---------------------------------------------------------------------------
28+
29+
def banner(text: str) -> str:
30+
bar = "=" * 64
31+
return f"\n{bar}\n{text}\n{bar}"
32+
33+
34+
def compile_file(compiler: str, src: Path, out: Path, timeout: int = 60):
35+
"""Returns (success: bool, stderr: str)."""
36+
try:
37+
r = subprocess.run(
38+
[compiler, str(src), "-o", str(out)],
39+
capture_output=True, text=True, timeout=timeout,
40+
)
41+
return r.returncode == 0, r.stderr
42+
except FileNotFoundError:
43+
return False, f"{compiler}: command not found"
44+
except subprocess.TimeoutExpired:
45+
return False, f"{compiler}: compilation timed out"
46+
47+
48+
def run_exe(exe: Path, timeout: int):
49+
"""Returns (stdout: str, stderr: str, returncode: int, timed_out: bool)."""
50+
try:
51+
r = subprocess.run(
52+
[str(exe)], capture_output=True, text=True,
53+
input="", timeout=timeout,
54+
)
55+
return r.stdout, r.stderr, r.returncode, False
56+
except subprocess.TimeoutExpired:
57+
return "", "", -1, True
58+
59+
60+
# ---------------------------------------------------------------------------
61+
# Main
62+
# ---------------------------------------------------------------------------
63+
64+
def main():
65+
parser = argparse.ArgumentParser(
66+
description="Compile .f90 files with gfortran and ifort and diff their output."
67+
)
68+
parser.add_argument("directory", help="Directory containing .f90 files")
69+
parser.add_argument(
70+
"--gfortran", default="gfortran",
71+
help="gfortran executable name/path (default: gfortran)"
72+
)
73+
parser.add_argument(
74+
"--ifort", default="ifort",
75+
help="ifort executable name/path (default: ifort)"
76+
)
77+
parser.add_argument(
78+
"--timeout", type=int, default=10,
79+
help="Per-program execution timeout in seconds (default: 10)"
80+
)
81+
parser.add_argument(
82+
"--compile-timeout", type=int, default=60,
83+
help="Per-file compilation timeout in seconds (default: 60)"
84+
)
85+
args = parser.parse_args()
86+
87+
src_dir = Path(args.directory)
88+
if not src_dir.is_dir():
89+
print(f"Error: '{src_dir}' is not a directory.", file=sys.stderr)
90+
sys.exit(1)
91+
92+
sources = sorted(src_dir.glob("*.f90"))
93+
if not sources:
94+
print(f"No .f90 files found in '{src_dir}'.")
95+
sys.exit(0)
96+
97+
n_both_fail = 0
98+
n_discrepancy = 0
99+
n_output_match = 0
100+
n_output_mismatch = 0
101+
n_runtime_issue = 0
102+
103+
with tempfile.TemporaryDirectory() as tmpdir:
104+
tmp = Path(tmpdir)
105+
106+
for src in sources:
107+
print(banner(src.name))
108+
109+
g_exe = tmp / f"{src.stem}_gfortran"
110+
i_exe = tmp / f"{src.stem}_ifort"
111+
112+
g_ok, g_err = compile_file(args.gfortran, src, g_exe, args.compile_timeout)
113+
i_ok, i_err = compile_file(args.ifort, src, i_exe, args.compile_timeout)
114+
115+
# ── both failed ──────────────────────────────────────────────
116+
if not g_ok and not i_ok:
117+
print("Both compilers FAILED — consistent failure, skipping.")
118+
n_both_fail += 1
119+
continue
120+
121+
# ── one succeeded, one failed ────────────────────────────────
122+
if g_ok != i_ok:
123+
winner = args.gfortran if g_ok else args.ifort
124+
loser = args.ifort if g_ok else args.gfortran
125+
loser_err = i_err if g_ok else g_err
126+
print(f"DISCREPANCY: {winner} compiled OK but {loser} failed.")
127+
if loser_err.strip():
128+
print(f"\n{loser} stderr:\n{loser_err.rstrip()}")
129+
n_discrepancy += 1
130+
continue
131+
132+
# ── both compiled: run and compare ───────────────────────────
133+
print(f"Both compiled. Running (timeout={args.timeout}s)...")
134+
135+
g_out, g_serr, g_rc, g_timeout = run_exe(g_exe, args.timeout)
136+
i_out, i_serr, i_rc, i_timeout = run_exe(i_exe, args.timeout)
137+
138+
if g_timeout or i_timeout:
139+
timed_out = []
140+
if g_timeout: timed_out.append(args.gfortran)
141+
if i_timeout: timed_out.append(args.ifort)
142+
print(f"RUNTIME TIMEOUT after {args.timeout}s: {', '.join(timed_out)}")
143+
n_runtime_issue += 1
144+
continue
145+
146+
# Report exit codes if they differ
147+
if g_rc != i_rc:
148+
print(f"Exit codes differ: {args.gfortran}={g_rc}, {args.ifort}={i_rc}")
149+
150+
if g_out == i_out:
151+
rc_note = "" if g_rc == i_rc else " (exit codes differ — see above)"
152+
print(f"Output MATCHES ({len(g_out)} chars). SUCCESS.{rc_note}")
153+
n_output_match += 1
154+
else:
155+
print("Output DIFFERS:")
156+
diff = list(difflib.unified_diff(
157+
g_out.splitlines(keepends=True),
158+
i_out.splitlines(keepends=True),
159+
fromfile=f"{args.gfortran} stdout",
160+
tofile=f"{args.ifort} stdout",
161+
))
162+
if diff:
163+
sys.stdout.writelines(diff)
164+
else:
165+
# Non-printable difference
166+
print(f" {args.gfortran}: {repr(g_out)}")
167+
print(f" {args.ifort}: {repr(i_out)}")
168+
n_output_mismatch += 1
169+
170+
# ── summary ─────────────────────────────────────────────────────────────
171+
total = len(sources)
172+
reported = n_discrepancy + n_output_match + n_output_mismatch + n_runtime_issue
173+
174+
print(banner("SUMMARY"))
175+
print(f" Files processed : {total}")
176+
print(f" Both failed (consistent) : {n_both_fail}")
177+
print(f" Compiler discrepancy : {n_discrepancy}")
178+
print(f" Both ran, output matches : {n_output_match} ← successes")
179+
print(f" Both ran, output differs : {n_output_mismatch}")
180+
print(f" Runtime issue / timeout : {n_runtime_issue}")
181+
print(f"\n SUCCESSES: {n_output_match} / {total} "
182+
f"({'%.0f' % (100*n_output_match/total)}%)" if total else "")
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)