Skip to main content

dsfb_gpu_debug_demo/cli/
generate_fixture.rs

1//! `dsfb-gpu-debug generate-fixture` — synthesize the canonical 10 000-event
2//! trace fixture and write it to disk in canonical-JSON form.
3//!
4//! Flags:
5//!
6//! * `--out PATH` (required): destination JSON path.
7//! * `--seed HEX_OR_DEC` (optional, default `0xD5FBD5FBD5FBD5FB`): the LCG
8//!   seed. Same seed always produces the same fixture bytes.
9//!
10//! Exit codes:
11//! * 0 — wrote file successfully.
12//! * 1 — usage error.
13//! * 5 — I/O failure writing the output.
14
15use std::fs;
16use std::path::Path;
17use std::process::ExitCode;
18
19use dsfb_gpu_debug_core::fixture::{synthesize, DEFAULT_SEED};
20use dsfb_gpu_debug_core::serialize::write_fixture;
21
22use super::{parse_flags, require_flag, usage_error};
23
24/// Parse argv tail and run the generate-fixture command.
25pub fn parse_and_run(args: &[String]) -> ExitCode {
26    let flags = match parse_flags(args) {
27        Ok(f) => f,
28        Err(message) => return usage_error(&message),
29    };
30
31    let out_path = match require_flag(&flags, "out") {
32        Ok(s) => s.to_string(),
33        Err(message) => return usage_error(&message),
34    };
35
36    let seed = match flags.get("seed") {
37        None => DEFAULT_SEED,
38        Some(raw) => match parse_seed(raw) {
39            Ok(s) => s,
40            Err(message) => return usage_error(&message),
41        },
42    };
43
44    let events = synthesize(seed);
45    let bytes = write_fixture(&events);
46
47    if let Some(parent) = Path::new(&out_path).parent() {
48        if !parent.as_os_str().is_empty() {
49            if let Err(error) = fs::create_dir_all(parent) {
50                eprintln!(
51                    "dsfb-gpu-debug: could not create {}: {error}",
52                    parent.display()
53                );
54                return ExitCode::from(5);
55            }
56        }
57    }
58
59    if let Err(error) = fs::write(&out_path, &bytes) {
60        eprintln!("dsfb-gpu-debug: failed to write {out_path}: {error}");
61        return ExitCode::from(5);
62    }
63
64    eprintln!(
65        "dsfb-gpu-debug: wrote {n} events ({bytes} canonical bytes) to {out_path}",
66        n = events.len(),
67        bytes = bytes.len()
68    );
69    ExitCode::SUCCESS
70}
71
72/// Parse a seed literal. Accepts plain decimal (e.g. `12345`) and the
73/// `0x`/`0X` hexadecimal prefix (e.g. `0xD5FBD5FBD5FBD5FB`).
74fn parse_seed(raw: &str) -> Result<u64, String> {
75    if let Some(hex) = raw.strip_prefix("0x").or_else(|| raw.strip_prefix("0X")) {
76        u64::from_str_radix(hex, 16).map_err(|e| format!("invalid hex seed {raw}: {e}"))
77    } else {
78        raw.parse::<u64>()
79            .map_err(|e| format!("invalid decimal seed {raw}: {e}"))
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn parse_seed_accepts_decimal_and_hex() {
89        assert_eq!(parse_seed("0").unwrap(), 0);
90        assert_eq!(parse_seed("42").unwrap(), 42);
91        assert_eq!(parse_seed("0xff").unwrap(), 0xff);
92        assert_eq!(parse_seed("0XFF").unwrap(), 0xff);
93        assert_eq!(
94            parse_seed("0xD5FBD5FBD5FBD5FB").unwrap(),
95            0xD5FB_D5FB_D5FB_D5FB
96        );
97    }
98
99    #[test]
100    fn parse_seed_rejects_garbage() {
101        assert!(parse_seed("not a number").is_err());
102        assert!(parse_seed("0xZZZZ").is_err());
103    }
104}