Rust · 6650 bytes Raw Blame History
1 //! Incremental compilation tests.
2 //!
3 //! Verifies that the .amod module file system handles incremental
4 //! rebuilds correctly:
5 //! - Recompiling a module produces the same .amod when the public
6 //! interface is unchanged.
7 //! - Changing a module's public interface produces a different .amod.
8 //! - Changing only private implementation does NOT change .amod.
9 //! - A consumer recompiled against the same .amod produces the
10 //! same .o (no unnecessary recompilation cascade).
11
12 use std::fs;
13 use std::path::{Path, PathBuf};
14 use std::process::Command;
15 use std::sync::atomic::{AtomicU64, Ordering};
16
17 static NEXT_ID: AtomicU64 = AtomicU64::new(0);
18
19 fn unique_dir() -> PathBuf {
20 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
21 let dir = std::env::temp_dir().join(format!("afs_incr_{}_{}", std::process::id(), id));
22 fs::create_dir_all(&dir).unwrap();
23 dir
24 }
25
26 fn find_compiler() -> PathBuf {
27 for c in &["target/release/armfortas", "target/debug/armfortas"] {
28 let p = PathBuf::from(c);
29 if p.exists() {
30 return fs::canonicalize(&p).unwrap();
31 }
32 }
33 panic!("armfortas binary not found");
34 }
35
36 fn compile(compiler: &Path, source: &Path, obj: &Path, search: &Path) {
37 let result = Command::new(compiler)
38 .current_dir(search)
39 .args([
40 source.to_str().unwrap(),
41 "-c",
42 "-O0",
43 "-o",
44 obj.to_str().unwrap(),
45 &format!("-I{}", search.display()),
46 ])
47 .output()
48 .expect("compiler launch failed");
49 assert!(
50 result.status.success(),
51 "compile {} failed:\n{}",
52 source.display(),
53 String::from_utf8_lossy(&result.stderr)
54 );
55 }
56
57 /// Extract the interface body from an .amod file (everything after the
58 /// first blank line, which separates the header from the interface).
59 fn extract_amod_body(amod: &[u8]) -> &[u8] {
60 let text = std::str::from_utf8(amod).unwrap_or("");
61 if let Some(idx) = text.find("\n\n") {
62 &amod[idx + 2..]
63 } else {
64 amod
65 }
66 }
67
68 /// Compile a module and return the .amod contents.
69 fn compile_module(compiler: &Path, dir: &Path, name: &str, source: &str) -> Vec<u8> {
70 let f90 = dir.join(format!("{}.f90", name));
71 let obj = dir.join(format!("{}.o", name));
72 fs::write(&f90, source).unwrap();
73 compile(compiler, &f90, &obj, dir);
74 let amod = dir.join(format!("{}.amod", name));
75 fs::read(&amod).unwrap_or_else(|e| panic!("{}.amod not found: {}", name, e))
76 }
77
78 #[test]
79 fn same_source_produces_identical_amod() {
80 let compiler = find_compiler();
81 let dir = unique_dir();
82 let src = "module m\n implicit none\n integer :: x = 42\nend module\n";
83
84 let amod1 = compile_module(&compiler, &dir, "m", src);
85 // Recompile with identical source.
86 let amod2 = compile_module(&compiler, &dir, "m", src);
87
88 assert_eq!(amod1, amod2, ".amod changed despite identical source");
89 let _ = fs::remove_dir_all(&dir);
90 }
91
92 #[test]
93 fn changed_public_interface_changes_amod() {
94 let compiler = find_compiler();
95 let dir = unique_dir();
96
97 let v1 = "module m\n implicit none\n integer :: x = 42\nend module\n";
98 let v2 = "module m\n implicit none\n integer :: x = 42\n integer :: y = 99\nend module\n";
99
100 let amod1 = compile_module(&compiler, &dir, "m", v1);
101 let amod2 = compile_module(&compiler, &dir, "m", v2);
102
103 assert_ne!(
104 amod1, amod2,
105 ".amod should differ when public interface changes"
106 );
107 let _ = fs::remove_dir_all(&dir);
108 }
109
110 #[test]
111 fn changed_private_impl_does_not_change_amod() {
112 let compiler = find_compiler();
113 let dir = unique_dir();
114
115 let v1 = "\
116 module m
117 implicit none
118 integer :: x = 42
119 contains
120 subroutine bump()
121 x = x + 1
122 end subroutine
123 end module
124 ";
125 let v2 = "\
126 module m
127 implicit none
128 integer :: x = 42
129 contains
130 subroutine bump()
131 x = x + 10
132 end subroutine
133 end module
134 ";
135
136 let amod1 = compile_module(&compiler, &dir, "m", v1);
137 let amod2 = compile_module(&compiler, &dir, "m", v2);
138
139 // The header includes a source checksum which will differ. But the
140 // interface section (everything after the blank line separating the
141 // header from the body) should be identical.
142 let body1 = extract_amod_body(&amod1);
143 let body2 = extract_amod_body(&amod2);
144 assert_eq!(
145 body1, body2,
146 ".amod interface body changed but only private impl differs"
147 );
148 let _ = fs::remove_dir_all(&dir);
149 }
150
151 #[test]
152 fn consumer_object_stable_when_amod_unchanged() {
153 let compiler = find_compiler();
154 let dir = unique_dir();
155
156 let mod_src = "module m\n implicit none\n integer :: x = 42\nend module\n";
157 let main_src = "program p\n use m\n implicit none\n print *, x\nend program\n";
158
159 // Compile module.
160 compile_module(&compiler, &dir, "m", mod_src);
161
162 // Compile consumer.
163 let main_f90 = dir.join("main.f90");
164 let main_o = dir.join("main.o");
165 fs::write(&main_f90, main_src).unwrap();
166 compile(&compiler, &main_f90, &main_o, &dir);
167 let obj1 = fs::read(&main_o).unwrap();
168
169 // Recompile consumer without changing anything.
170 compile(&compiler, &main_f90, &main_o, &dir);
171 let obj2 = fs::read(&main_o).unwrap();
172
173 assert_eq!(
174 obj1, obj2,
175 "consumer .o changed despite no source/amod change"
176 );
177 let _ = fs::remove_dir_all(&dir);
178 }
179
180 #[test]
181 fn consumer_object_changes_when_amod_changes() {
182 let compiler = find_compiler();
183 let dir = unique_dir();
184
185 let mod_v1 = "module m\n implicit none\n integer :: x = 42\nend module\n";
186 let mod_v2 =
187 "module m\n implicit none\n integer :: x = 42\n integer :: y = 99\nend module\n";
188 let main_src = "program p\n use m\n implicit none\n print *, x\nend program\n";
189
190 // Compile module v1 and consumer.
191 compile_module(&compiler, &dir, "m", mod_v1);
192 let main_f90 = dir.join("main.f90");
193 let main_o = dir.join("main.o");
194 fs::write(&main_f90, main_src).unwrap();
195 compile(&compiler, &main_f90, &main_o, &dir);
196 let _obj1 = fs::read(&main_o).unwrap();
197
198 // Recompile module with changed public interface.
199 compile_module(&compiler, &dir, "m", mod_v2);
200 // Recompile consumer against new .amod.
201 compile(&compiler, &main_f90, &main_o, &dir);
202 let _obj2 = fs::read(&main_o).unwrap();
203
204 // The consumer .o may or may not change (depends on whether the
205 // consumer references the new symbol). But the .amod definitely
206 // changed, so this test documents the observation either way.
207 // The key point: no crash, no stale symbol resolution.
208 let _ = fs::remove_dir_all(&dir);
209 }
210