fortrangoingonforty/armfortas / 0ebb1f5

Browse files

Add incremental compilation tests and fix coverage script PATH

Five tests: identical-source amod stability, public interface change
detection, private-impl-only amod body stability, consumer .o
stability, and consumer rebuild after interface change.
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
0ebb1f51d356f952ac4d50bf59d640845653c4a0
Parents
261c316
Tree
cd7b94d

2 changed files

StatusFile+-
M scripts/coverage.sh 3 0
A tests/incremental.rs 195 0
scripts/coverage.shmodified
@@ -13,6 +13,9 @@
1313
 set -euo pipefail
1414
 cd "$(git rev-parse --show-toplevel)"
1515
 
16
+# Ensure cargo bin dir is in PATH
17
+export PATH="$HOME/.cargo/bin:$PATH"
18
+
1619
 MODE="${1:-}"
1720
 
1821
 if command -v cargo-llvm-cov &>/dev/null; then
tests/incremental.rsadded
@@ -0,0 +1,195 @@
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
+}