Rust · 3490 bytes Raw Blame History
1 //! Sprint 4 real-world gate: parse `libarmfortas_rt.a` (if the parent
2 //! workspace has built it), walk its BSD symbol index, fetch a member, and
3 //! confirm the fetched member parses as an `ObjectFile` that defines the
4 //! expected symbol.
5 //!
6 //! If the runtime archive isn't present (a clean clone that has never been
7 //! built) the test is skipped with a clear message rather than failing.
8
9 use std::path::{Path, PathBuf};
10
11 use afs_ld::archive::{Archive, FetchError, SymbolIndex};
12 use afs_ld::symbol::SymKind;
13
14 fn find_runtime_archive() -> Option<PathBuf> {
15 // afs-ld crate manifest sits at .../armfortas/afs-ld/; target/ lives at
16 // .../armfortas/target/.
17 let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
18 for profile in ["debug", "release"] {
19 let candidate = workspace
20 .join("target")
21 .join(profile)
22 .join("libarmfortas_rt.a");
23 if candidate.is_file() {
24 return Some(candidate);
25 }
26 }
27 None
28 }
29
30 #[test]
31 fn libarmfortas_rt_archive_walks_cleanly() {
32 let Some(path) = find_runtime_archive() else {
33 eprintln!("skipping: libarmfortas_rt.a not built; run `cargo build -p armfortas-rt` first");
34 return;
35 };
36
37 let bytes = std::fs::read(&path).expect("read runtime archive");
38 let ar = Archive::open(&path, &bytes).expect("parse runtime archive");
39
40 // Archive should have members and a symbol index.
41 let members: Vec<_> = ar.object_members().collect();
42 assert!(!members.is_empty(), "runtime archive has no object members");
43
44 let idx = ar
45 .symbol_index()
46 .expect("runtime archive has a BSD symbol index");
47 assert!(!idx.is_empty(), "runtime archive symbol index is empty");
48
49 // Pick a symbol we're confident the runtime exports. `_afs_program_init`
50 // is the entry-point the driver-synthesized _main calls.
51 let target = "_afs_program_init";
52 let Some(mem) = ar.first_member_defining(target) else {
53 panic_with_index("runtime archive missing _afs_program_init", idx);
54 };
55 assert!(!mem.name.is_empty());
56
57 // Fetch and parse that member. The member must expose the symbol as a
58 // defined, external, section symbol.
59 let obj = match ar.fetch_object_defining(target) {
60 Some(Ok(o)) => o,
61 Some(Err(FetchError::Read(e))) => panic!("parse error on {}: {e}", mem.name),
62 Some(Err(FetchError::Io(e))) => panic!("i/o error on {}: {e}", mem.name),
63 None => panic!("fetch returned None for {}", target),
64 };
65
66 // Find the symbol we asked for and confirm its shape.
67 let defining = obj
68 .symbols
69 .iter()
70 .find(|s| {
71 obj.symbol_name(s).map(|n| n == target).unwrap_or(false)
72 && s.kind() == SymKind::Sect
73 && s.is_ext()
74 })
75 .unwrap_or_else(|| {
76 let names: Vec<_> = obj
77 .symbols
78 .iter()
79 .filter_map(|s| obj.symbol_name(s).ok().map(|n| n.to_string()))
80 .collect();
81 panic!(
82 "member {:?} does not define {} as a SECT+EXT symbol (saw: {:?})",
83 mem.name, target, names
84 )
85 });
86 let _ = defining;
87 }
88
89 fn panic_with_index(msg: &str, idx: &SymbolIndex) -> ! {
90 let preview: Vec<&str> = idx
91 .entries
92 .iter()
93 .take(10)
94 .map(|e| e.name.as_str())
95 .collect();
96 panic!("{msg}\nfirst 10 symbols: {preview:?}");
97 }
98