@@ -120,35 +120,6 @@ fn run_callgrind_full(bin: &Path) -> String {
120120 . unwrap_or_else ( |e| panic ! ( "read {}: {e}" , out_file. display( ) ) )
121121}
122122
123- /// Strip Callgrind's recursion-separation suffix (`fib'2` -> `fib`) so a
124- /// function and its deeper-recursion clones compare equal.
125- fn base ( name : & str ) -> & str {
126- name. split ( '\'' ) . next ( ) . unwrap_or ( name)
127- }
128-
129- /// Sum the `call_count` over every edge with these exact caller/callee
130- /// function names (recursion clones are distinct names, so pass them exactly).
131- fn sum_counts ( g : & CallGraph , caller : & str , callee : & str ) -> u64 {
132- g. edges ( )
133- . iter ( )
134- . filter ( |e| e. caller . function == caller && e. callee . function == callee)
135- . map ( |e| e. call_count . unwrap_or ( 0 ) )
136- . sum ( )
137- }
138-
139- /// True if any edge connects these two functions, regardless of count.
140- fn has_edge ( g : & CallGraph , caller : & str , callee : & str ) -> bool {
141- g. edges ( )
142- . iter ( )
143- . any ( |e| e. caller . function == caller && e. callee . function == callee)
144- }
145-
146- /// Full-program trace matrix: profile each fixture with `--instr-atstart=yes`
147- /// and assert version-stable invariants rather than a golden snapshot (the
148- /// startup frames carry libc/loader-specific names like
149- /// `__libc_start_main@@GLIBC_2.34` and raw loader addresses, which are not
150- /// portable). This is both an end-to-end test of full-program tracing and a
151- /// regression guard for the arm64 `B`-vs-`BL` jump-kind fix.
152123#[ rstest]
153124#[ case( "recursion" ) ]
154125#[ case( "chain" ) ]
@@ -159,82 +130,7 @@ fn fixture_full_trace(#[case] stem: &str) {
159130 let raw = run_callgrind_full ( & bin) ;
160131 let graph = CallGraph :: parse ( Cursor :: new ( raw. as_str ( ) ) , & ParseOptions :: default ( ) )
161132 . unwrap_or_else ( |e| panic ! ( "parse {stem} full callgrind output: {e:?}" ) ) ;
133+ let json = graph. to_json ( ) . expect ( "to_json" ) ;
162134
163- // The canonical JSON projection round-trips on a large, real-world graph.
164- graph. to_json ( ) . expect ( "to_json" ) ;
165-
166- // Full-program capture: `main` is reached as a callee. The scoped run never
167- // observes this (instrumentation starts inside `main`).
168- assert ! (
169- graph. edges( ) . iter( ) . any( |e| e. callee. function == "main" ) ,
170- "{stem}: expected `main` to appear as a callee under --instr-atstart=yes"
171- ) ;
172-
173- // The fixture's own source functions all appear in the binary's object
174- // (which also carries CRT glue like `_start`/`frame_dummy` — hence subset,
175- // not equality). This is the same self-contained sub-graph as the scoped
176- // snapshot, now surrounded by — but not merged with — the startup frames.
177- let own: Vec < & str > = graph
178- . nodes ( )
179- . iter ( )
180- . filter ( |n| n. object == stem)
181- . map ( |n| n. function . as_str ( ) )
182- . collect ( ) ;
183- let expected: & [ & str ] = match stem {
184- "recursion" => & [ "compute" , "fib" , "fib'2" , "main" , "square" ] ,
185- "chain" => & [ "a" , "b" , "c" , "main" ] ,
186- "diamond" => & [ "bottom" , "left" , "main" , "right" , "top" ] ,
187- "mutual" => & [ "is_even" , "is_even'2" , "is_odd" , "is_odd'2" , "main" ] ,
188- _ => unreachable ! ( "unknown fixture {stem}" ) ,
189- } ;
190- for f in expected {
191- assert ! (
192- own. contains( f) ,
193- "{stem}: expected fixture function `{f}` in object `{stem}`; got {own:?}"
194- ) ;
195- }
196-
197- // Per-fixture call-graph shape (matches the committed scoped snapshots; the
198- // ZERO_STATS-bounded compute region carries identical counts).
199- match stem {
200- "recursion" => {
201- // Direct recursion: fib(8) makes 2*fib(9)-1 = 67 fib invocations.
202- // compute->fib=1, fib->fib'2=2, leaving fib'2->fib'2=64. The pre-fix
203- // arm64 bug inflated this to 98 via phantom base-case "calls".
204- assert_eq ! ( sum_counts( & graph, "fib" , "fib'2" ) , 2 , "recursion fib->fib'2" ) ;
205- assert_eq ! (
206- sum_counts( & graph, "fib'2" , "fib'2" ) ,
207- 64 ,
208- "recursion fib'2->fib'2"
209- ) ;
210- }
211- "chain" => {
212- assert_eq ! ( sum_counts( & graph, "main" , "a" ) , 1 , "chain main->a" ) ;
213- assert_eq ! ( sum_counts( & graph, "a" , "b" ) , 1 , "chain a->b" ) ;
214- assert_eq ! ( sum_counts( & graph, "b" , "c" ) , 1 , "chain b->c" ) ;
215- }
216- "diamond" => {
217- assert ! ( has_edge( & graph, "top" , "left" ) , "diamond top->left" ) ;
218- assert ! ( has_edge( & graph, "top" , "right" ) , "diamond top->right" ) ;
219- assert ! ( has_edge( & graph, "left" , "bottom" ) , "diamond left->bottom" ) ;
220- assert ! ( has_edge( & graph, "right" , "bottom" ) , "diamond right->bottom" ) ;
221- }
222- "mutual" => {
223- // is_even/is_odd are *mutually* recursive: neither ever calls itself.
224- // The arm64 B-vs-BL bug invented self-edges (is_even->is_even'2 etc.);
225- // guard against their return.
226- for e in graph. edges ( ) {
227- let ( cb, kb) = ( base ( & e. caller . function ) , base ( & e. callee . function ) ) ;
228- if cb == "is_even" || cb == "is_odd" {
229- assert_ne ! ( cb, kb, "mutual: spurious self-edge {e:?}" ) ;
230- }
231- }
232- assert ! ( has_edge( & graph, "is_even" , "is_odd" ) , "mutual is_even->is_odd" ) ;
233- assert ! (
234- has_edge( & graph, "is_odd" , "is_even'2" ) ,
235- "mutual is_odd->is_even'2"
236- ) ;
237- }
238- _ => unreachable ! ( "unknown fixture {stem}" ) ,
239- }
135+ insta:: assert_snapshot!( format!( "{stem}_full__json" ) , json) ;
240136}
0 commit comments