Skip to content

Commit 3bf29db

Browse files
committed
feat: add Rust parity_check.rs script for Windows
High-performance replacement for parity_check.ps1: - Runs C++ (uffs.com) and Rust (uffs.exe) scans - Sorts outputs and computes SHA256 hashes - Shows diff samples on mismatch - Uses rust-script with sha2 dependency Usage: rust-script scripts/windows/parity_check.rs D [E F] [--sample N]
1 parent 513a36a commit 3bf29db

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

scripts/windows/parity_check.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env rust-script
2+
//! UFFS Live Parity Check — C++ vs Rust comparison.
3+
//! Usage: rust-script scripts/windows/parity_check.rs D [E F] [--bin-dir DIR] [--sample N]
4+
//! ```cargo
5+
//! [dependencies]
6+
//! sha2 = "0.10"
7+
//! ```
8+
9+
use sha2::{Digest, Sha256};
10+
use std::collections::HashSet;
11+
use std::env;
12+
use std::fs::{self, File};
13+
use std::io::{BufRead, BufReader, Write};
14+
use std::path::PathBuf;
15+
use std::process::Command;
16+
use std::time::Instant;
17+
18+
struct Config { drives: Vec<String>, cpp: PathBuf, rust: PathBuf, sample: usize, out: PathBuf }
19+
struct Scan { ok: bool, ms: u128, err: Option<String> }
20+
21+
fn main() {
22+
let cfg = parse_args();
23+
println!("\n╔════════════════════════════════════════════╗");
24+
println!("║ UFFS Live Parity Check — C++ vs Rust ║");
25+
println!("╚════════════════════════════════════════════╝\n");
26+
println!(" C++ : {}\n Rust: {}\n Drives: {}\n", cfg.cpp.display(), cfg.rust.display(), cfg.drives.join(", "));
27+
let ok = cfg.drives.iter().all(|d| check(&cfg, d));
28+
std::process::exit(if ok { 0 } else { 1 });
29+
}
30+
31+
fn parse_args() -> Config {
32+
let args: Vec<String> = env::args().collect();
33+
let mut drives = vec![]; let mut bin = home().join("bin"); let mut sample = 30usize;
34+
let mut i = 1;
35+
while i < args.len() {
36+
match args[i].as_str() {
37+
"--bin-dir" => { i += 1; bin = PathBuf::from(&args[i]); }
38+
"--sample" => { i += 1; sample = args[i].parse().unwrap_or(30); }
39+
a if !a.starts_with('-') => drives.push(a.to_uppercase()),
40+
_ => {}
41+
}
42+
i += 1;
43+
}
44+
if drives.is_empty() { eprintln!("Usage: parity_check.rs <DRIVE> [--bin-dir DIR]"); std::process::exit(1); }
45+
Config { drives, cpp: bin.join("uffs.com"), rust: bin.join("uffs.exe"), sample, out: env::current_dir().unwrap() }
46+
}
47+
48+
fn home() -> PathBuf { env::var_os("USERPROFILE").or(env::var_os("HOME")).map(PathBuf::from).unwrap_or(".".into()) }
49+
fn ts() -> u64 { std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() }
50+
fn flush() { std::io::stdout().flush().ok(); }
51+
52+
fn check(cfg: &Config, drv: &str) -> bool {
53+
let dl = drv.to_lowercase(); let t = ts();
54+
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n Drive {}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━", drv);
55+
let cpp_f = cfg.out.join(format!("parity_cpp_{dl}_{t}.txt"));
56+
let rust_f = cfg.out.join(format!("parity_rust_{dl}_{t}.txt"));
57+
58+
print!(" [1/4] C++ scan..."); flush();
59+
let c = scan(&cfg.cpp, &["*", &format!("--drives={}", drv)], &cpp_f); pres(&c);
60+
print!(" [2/4] Rust scan..."); flush();
61+
let r = scan(&cfg.rust, &["*", "--drive", drv, "--no-cache"], &rust_f); pres(&r);
62+
63+
if !c.ok || !r.ok { println!(" ❌ Scan failed"); return false; }
64+
65+
print!(" [3/4] Sorting..."); flush();
66+
let t0 = Instant::now();
67+
let cl = rsort(&cpp_f); let rl = rsort(&rust_f);
68+
println!(" ✅ ({} ms)\n C++: {} lines, Rust: {} lines", t0.elapsed().as_millis(), cl.len(), rl.len());
69+
70+
print!(" [4/4] SHA256..."); flush();
71+
let ch = sha(&cl); let rh = sha(&rl);
72+
if ch == rh {
73+
println!(" ✅ MATCH\n\n ╔══════════════════════════════════════════╗\n ║ PARITY: PASS ║\n ╚══════════════════════════════════════════╝\n SHA256: {}\n", ch);
74+
fs::remove_file(&cpp_f).ok(); fs::remove_file(&rust_f).ok(); true
75+
} else {
76+
println!(" ❌ MISMATCH"); diff(cfg, drv, &cl, &rl, &ch, &rh, t); false
77+
}
78+
}
79+
80+
fn scan(bin: &PathBuf, args: &[&str], out: &PathBuf) -> Scan {
81+
let t0 = Instant::now();
82+
match Command::new(bin).args(args).output() {
83+
Ok(o) if o.status.success() => { fs::write(out, &o.stdout).ok(); Scan { ok: true, ms: t0.elapsed().as_millis(), err: None } }
84+
Ok(o) => Scan { ok: false, ms: t0.elapsed().as_millis(), err: Some(format!("exit {}: {}", o.status.code().unwrap_or(-1), String::from_utf8_lossy(&o.stderr))) },
85+
Err(e) => Scan { ok: false, ms: t0.elapsed().as_millis(), err: Some(e.to_string()) },
86+
}
87+
}
88+
89+
fn pres(s: &Scan) {
90+
if s.ok { println!(" ✅ ({} ms)", s.ms); }
91+
else { println!(" ❌ ({} ms)", s.ms); if let Some(e) = &s.err { for l in e.lines().take(3) { println!(" {}", l); } } }
92+
}
93+
94+
fn rsort(p: &PathBuf) -> Vec<String> {
95+
let mut v: Vec<String> = BufReader::new(File::open(p).unwrap()).lines().map_while(Result::ok).filter(|l| !l.trim().is_empty()).collect();
96+
v.sort_unstable(); v
97+
}
98+
99+
fn sha(lines: &[String]) -> String {
100+
let mut h = Sha256::new(); for l in lines { h.update(l.as_bytes()); h.update(b"\n"); } format!("{:x}", h.finalize())
101+
}
102+
103+
fn diff(cfg: &Config, drv: &str, cpp: &[String], rust: &[String], ch: &str, rh: &str, t: u64) {
104+
println!("\n ╔══════════════════════════════════════════╗\n ║ PARITY: FAIL ║\n ╚══════════════════════════════════════════╝");
105+
println!(" C++ SHA256 : {}\n Rust SHA256: {}", ch, rh);
106+
let cs: HashSet<&str> = cpp.iter().map(|s| s.as_str()).collect();
107+
let rs: HashSet<&str> = rust.iter().map(|s| s.as_str()).collect();
108+
let oc: Vec<_> = cpp.iter().filter(|l| !rs.contains(l.as_str())).collect();
109+
let or: Vec<_> = rust.iter().filter(|l| !cs.contains(l.as_str())).collect();
110+
println!("\n Only C++: {} | Only Rust: {}", oc.len(), or.len());
111+
if !oc.is_empty() { println!("\n C++ only (first 5):"); for l in oc.iter().take(5) { println!(" < {}", &l[..l.len().min(100)]); } }
112+
if !or.is_empty() { println!(" Rust only (first 5):"); for l in or.iter().take(5) { println!(" > {}", &l[..l.len().min(100)]); } }
113+
let dp = cfg.out.join(format!("parity_diff_{}_{}.txt", drv.to_lowercase(), t));
114+
if let Ok(mut f) = File::create(&dp) {
115+
writeln!(f, "# Drive {} | C++ SHA256: {} | Rust SHA256: {}", drv, ch, rh).ok();
116+
writeln!(f, "# C++ lines: {} | Rust lines: {} | Only C++: {} | Only Rust: {}\n", cpp.len(), rust.len(), oc.len(), or.len()).ok();
117+
writeln!(f, "=== C++ ONLY ({}) ===", oc.len()).ok(); for l in oc.iter().take(cfg.sample) { writeln!(f, "< {}", l).ok(); }
118+
writeln!(f, "\n=== RUST ONLY ({}) ===", or.len()).ok(); for l in or.iter().take(cfg.sample) { writeln!(f, "> {}", l).ok(); }
119+
println!("\n Diff: {}", dp.display());
120+
}
121+
println!();
122+
}
123+

0 commit comments

Comments
 (0)