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