Skip to main content

csusage_test_support/
dsh.rs

1use std::path::Path;
2
3/// Creates the session log used by dsh report tests. The layout mirrors the
4/// dsh home: `sessions/<escaped-cwd>/session-<id>/session.v3.jsonl.zstd`,
5/// where each log is a zstd-compressed JSONL event stream.
6pub fn create_fixture(sessions_root: impl AsRef<Path>) {
7    write_session(
8        sessions_root.as_ref().join("--home-user-workspace--"),
9        "session-11111111-1111-4111-8111-111111111111",
10        &[
11            header(1_000, "session-11111111-1111-4111-8111-111111111111"),
12            request_header(1_100, "deepseek-flash"),
13            assistant_step(1_200, 1, 1, 100, 10),
14            assistant_step(1_300, 1, 2, 200, 20),
15            request_header(1_400, "glm-5.3-flash"),
16            assistant_step(1_500, 2, 1, 50, 5),
17        ],
18    );
19    write_session(
20        sessions_root.as_ref().join("--home-user-workspace--"),
21        "session-22222222-2222-4222-8222-222222222222",
22        &[
23            header(2_000, "session-22222222-2222-4222-8222-222222222222"),
24            request_header(2_100, "deepseek-flash"),
25            assistant_step(2_200, 1, 1, 300, 30),
26        ],
27    );
28}
29
30fn write_session(session: std::path::PathBuf, _id: &str, events: &[serde_json::Value]) {
31    let session = session.join(_id);
32    let mut text = String::new();
33    for event in events {
34        text.push_str(&serde_json::to_string(event).unwrap());
35        text.push('\n');
36    }
37    // dsh writes zstd-compressed logs; tests emit a single raw-block frame,
38    // which any zstd decoder accepts without a compressor.
39    let bytes = text.as_bytes();
40    // Magic, then a frame header descriptor with an 8-byte content size and
41    // the single-segment flag set, then the content size itself.
42    let mut zstd = vec![0x28, 0xB5, 0x2F, 0xFD, 0xE0];
43    zstd.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
44    zstd.extend_from_slice(&((bytes.len() as u32) << 3 | 1).to_le_bytes()[..3]);
45    zstd.extend_from_slice(bytes);
46
47    let log = session.join("session.v3.jsonl.zstd");
48    std::fs::create_dir_all(log.parent().unwrap()).unwrap();
49    std::fs::write(log, zstd).unwrap();
50}
51
52fn header(time: i64, id: &str) -> serde_json::Value {
53    serde_json::json!({
54        "type": "session",
55        "id": id,
56        "time": time,
57        "cwd": "/home/user/workspace",
58    })
59}
60
61fn request_header(time: i64, model: &str) -> serde_json::Value {
62    serde_json::json!({
63        "type": "request/header",
64        "time": time,
65        "data": {"header": {"config": {"model": model}}},
66    })
67}
68
69fn assistant_step(time: i64, turn: u64, step: u64, input: u64, output: u64) -> serde_json::Value {
70    serde_json::json!({
71        "type": "assistant/message",
72        "time": time,
73        "data": {
74            "turn": turn,
75            "step": step,
76            "usage": {
77                "inputTokens": input,
78                "outputTokens": output,
79                "totalTokens": input + output,
80            },
81        },
82    })
83}