src/code_room/reader/wired/trace_tests.rs
//! **A traced run lands on the map.** A filed chunk of a run over the fixture
//! room's `shop` (functions named by symbol and source location, the way a Runa
//! `Trace` step files them) is mapped to the room's functions, and the replay's
//! `Viz.CodeMap` lods answer from it: the runs with their mapped share, a window
//! of events on heads at the map's own positions, paged, with each thread's open
//! heads at a scrub point and the run's heat.
use nornir_trace::wire::{ChunkFiling, FiledFn};
use nornir_trace::{EventKind, TraceEvent};
use serde_json::{Value, json};
use crate::mimir_lens::room_feed::fixture::{self, SHOP};
use crate::warehouse::step_feed::{Measured, StepFeed, TraceChunk, file_mapped};
use super::trace::RoomTraceMap;
fn f(symbol: &str, repo: &str, file: &str, line: u32) -> FiledFn {
FiledFn { symbol: symbol.into(), repo: repo.into(), file: file.into(), line }
}
fn map(ws: &fixture::Workspace, p: Value) -> Value {
super::codemap::code_map(&ws.reach(), &p).unwrap_or_else(|r| panic!("{p}: [{}] {}", r.state, r.note))
}
/// The map position of the card `find` names `path` (the top middle of its rect).
fn head_of(ws: &fixture::Workspace, q: &str, path: &str) -> [f64; 2] {
let v = map(ws, json!({ "lod": "find", "q": q }));
let hit = v["hits"].as_array().or(v["results"].as_array()).into_iter().flatten().find(|h| h["what"] == "fn" && h["path"] == path).unwrap_or_else(|| panic!("no {path}: {v}")).clone();
let r = hit["rect"].as_array().unwrap().iter().map(|x| x.as_f64().unwrap()).collect::<Vec<_>>();
[(r[0] + r[2]) * 0.5, r[1]]
}
#[test]
fn a_filed_trace_maps_to_the_rooms_functions_and_replays_on_the_maps_heads() {
let _g = fixture::serial();
let ws = fixture::workspace_without_runs();
let functions = vec![
f("shop::pay::charge", SHOP, "src/pay.rs", 5), // 0: by span
f("shop::cart::Cart::add::{closure#0}", SHOP, "src/cart.rs", 11), // 1: a closure → add
f("<shop::cart::Cart>::total", "", "", 0), // 2: by path only
f("std::io::stdio::_print", "", "", 0), // 3: not in the room
f("shop::pay::audit", SHOP, "src/pay.rs", 23), // 4: by span
];
use EventKind::*;
let evs = [(7, 0, 0, Enter), (9, 5, 1, Enter), (9, 6, 2, Enter), (9, 8, 2, Exit), (7, 10, 4, Enter), (9, 12, 1, Exit), (7, 15, 3, Enter), (7, 16, 3, Exit), (7, 20, 4, Exit), (7, 30, 0, Exit)];
let events: Vec<TraceEvent> = evs.iter().enumerate().map(|(i, &(thread, t_ns, func, kind))| TraceEvent { thread, seq_no: i as u64, t_ns, func, kind }).collect();
let filing = ChunkFiling { chunk: 0, chunks: 1, base_seq: 0, target: "test shop".into(), functions, total_events: 10, threads: 2, duration_ns: 30, ..Default::default() };
let room = super::open(&ws.reach(), "").unwrap();
let feed = StepFeed {
repo: SHOP.into(),
commit: "c1".into(),
run_id: "run-trace-1".into(),
ts_micros: chrono::Utc::now().timestamp_micros(),
measured: Measured::Trace(Box::new(TraceChunk { filing, events, source: "runa ci run-1 #0 Trace".into() })),
selection: None,
};
let rm = RoomTraceMap { room };
assert_eq!(ws.warehouse.block_on(file_mapped(&ws.warehouse, &feed, &rm)).unwrap(), 10);
assert_eq!(ws.warehouse.block_on(file_mapped(&ws.warehouse, &feed, &rm)).unwrap(), 0, "a chunk is filed once");
// the runs: 4 of 5 functions are the room's, all 3 that live in a checkout
let runs = map(&ws, json!({ "lod": "trace_runs", "repo": SHOP }));
let r = &runs["runs"][0];
assert_eq!((r["run_id"].as_str(), r["events"].as_u64(), r["threads"].as_u64()), (Some("run-trace-1"), Some(10), Some(2)), "{runs}");
assert_eq!((r["functions"].as_u64(), r["mapped_functions"].as_u64(), r["mapped_share"].as_f64()), (Some(5), Some(4), Some(0.8)), "{r}");
assert_eq!((r["local_functions"].as_u64(), r["mapped_local_functions"].as_u64()), (Some(3), Some(3)), "{r}");
assert_eq!(r["mapped_events"].as_u64(), Some(8), "{r}");
// a window: placed events on heads at the map's positions, std counted unplaced
let w = map(&ws, json!({ "lod": "trace", "run_id": "run-trace-1", "from_ns": 0, "to_ns": 16 }));
let events = w["events"].as_array().unwrap();
let heads = w["heads"].as_array().unwrap();
let path_of = |e: &Value| heads[e[4].as_u64().unwrap() as usize]["path"].as_str().unwrap().to_string();
let got: Vec<(u64, u64, String, u64)> = events.iter().map(|e| (e[1].as_u64().unwrap(), e[2].as_u64().unwrap(), path_of(e), e[3].as_u64().unwrap())).collect();
assert_eq!(
got,
vec![
(0, 7, "shop::pay::charge".into(), 0),
(5, 9, "shop::cart::Cart::add".into(), 0),
(6, 9, "shop::cart::Cart::total".into(), 0),
(8, 9, "shop::cart::Cart::total".into(), 1),
(10, 7, "shop::pay::audit".into(), 0),
(12, 9, "shop::cart::Cart::add".into(), 1),
],
"{w}"
);
assert_eq!(w["unplaced"], 2, "{w}");
// an entry names its caller: the innermost placed function open on its thread
let caller_of = |t: u64| events.iter().find(|e| e[1] == t && e[3] == 0).and_then(|e| e[5].as_u64()).map(|h| heads[h as usize]["path"].as_str().unwrap().to_string());
assert_eq!(caller_of(0), None, "the outermost entry has no caller");
assert_eq!(caller_of(6).as_deref(), Some("shop::cart::Cart::add"), "total is called by add on thread 9");
assert_eq!(caller_of(10).as_deref(), Some("shop::pay::charge"), "audit is called by charge on thread 7");
for h in heads {
let at = [h["at"][0].as_f64().unwrap(), h["at"][1].as_f64().unwrap()];
let name = h["path"].as_str().unwrap().rsplit("::").next().unwrap();
assert_eq!(at, head_of(&ws, name, h["path"].as_str().unwrap()), "{h}");
}
// paged
let p1 = map(&ws, json!({ "lod": "trace", "run_id": "run-trace-1", "limit": 2 }));
assert_eq!((p1["events"].as_array().unwrap().len(), p1["more"].as_bool()), (2, Some(true)), "{p1}");
let p2 = map(&ws, json!({ "lod": "trace", "run_id": "run-trace-1", "limit": 2, "after_seq": p1["next_after_seq"] }));
assert_eq!(p2["events"][0][1], 6, "the page after seq 1 starts at total's entry: {p2}");
let p2h = |e: &Value| p2["heads"][e.as_u64().unwrap() as usize]["path"].as_str().unwrap().to_string();
assert_eq!(p2h(&p2["events"][0][5]), "shop::cart::Cart::add", "a later page knows the caller from the run before it: {p2}");
// a scrub to t=7: who is open on which thread; and the run's heat
let s = map(&ws, json!({ "lod": "trace", "run_id": "run-trace-1", "from_ns": 7, "to_ns": 7, "stacks": true, "heat": true }));
let hp = |i: &Value| s["heads"][i.as_u64().unwrap() as usize]["path"].as_str().unwrap().to_string();
let stacks: Vec<(u64, Vec<String>)> = s["stacks"].as_array().unwrap().iter().map(|t| (t["thread"].as_u64().unwrap(), t["heads"].as_array().unwrap().iter().map(hp).collect())).collect();
assert_eq!(stacks, vec![(7, vec!["shop::pay::charge".to_string()]), (9, vec!["shop::cart::Cart::add".into(), "shop::cart::Cart::total".into()])], "{s}");
let heat: Vec<(String, u64)> = s["heat"].as_array().unwrap().iter().map(|h| (hp(&h[0]), h[1].as_u64().unwrap())).collect();
assert_eq!(heat, vec![("shop::pay::charge".into(), 30), ("shop::pay::audit".into(), 10), ("shop::cart::Cart::add".into(), 7), ("shop::cart::Cart::total".into(), 2)], "{s}");
}