use std::path::PathBuf;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(
name = "ps-qa",
about = "Drive a Blitz application through its control socket and judge what the renderer did.",
long_about = None,
version,
)]
pub struct Cli {
#[arg(long, global = true)]
pub descriptor: Option<PathBuf>,
#[arg(long, global = true)]
pub app: Option<PathBuf>,
#[arg(long, global = true, default_value_t = 1.0 / 60.0)]
pub pace: f64,
#[arg(long, global = true)]
pub trace: bool,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand)]
pub enum Command {
Nodes,
Panes,
Idle,
Blink {
#[arg(default_value_t = 0)]
allowed_missed: u64,
},
Ghost {
#[arg(default_value_t = 64.0)]
min_area: f64,
#[arg(default_value_t = 400)]
max: usize,
},
Drift {
#[arg(default_value_t = 20.0)]
seconds: f64,
},
Frames,
Metrics,
Tree,
Find {
#[arg(default_value = "*")]
pattern: String,
#[arg(long)]
role: Vec<String>,
#[arg(long)]
visible: bool,
#[arg(long)]
hidden: bool,
#[arg(long)]
painted: bool,
#[arg(long)]
offscreen: bool,
#[arg(long)]
disabled: bool,
#[arg(long)]
count: bool,
#[arg(long)]
limit: Option<usize>,
},
Layout {
#[arg(default_value = "")]
name: String,
},
Dom {
name: String,
#[arg(default_value_t = 6)]
depth: usize,
},
Transcript,
Paint {
#[arg(default_value = "")]
name: String,
#[arg(default_value_t = 10000.0)]
min_area: f64,
},
Spill {
#[arg(default_value = "h")]
axis: String,
#[arg(default_value_t = 1.0)]
tolerance: f64,
},
Watch {
#[arg(default_value_t = 20.0)]
seconds: f64,
},
Scroll {
#[arg(default_value_t = 120)]
ticks: u32,
#[arg(default_value_t = -80.0)]
delta: f64,
#[arg(default_value = "")]
over: String,
},
Drag {
#[arg(default_value = "")]
name: String,
#[arg(default_value_t = -400.0)]
dy: f64,
#[arg(default_value_t = 10)]
steps: u32,
},
Type {
#[arg(default_value_t = 20)]
count: u32,
#[arg(default_value = "")]
name: String,
},
Key {
name: String,
#[arg(default_value_t = 1)]
count: u32,
#[arg(default_value = "")]
over: String,
},
Reveal {
name: String,
},
Capture {
#[arg(default_value = "")]
name: String,
#[arg(default_value_t = 1.0)]
scale: f64,
},
Press {
name: String,
},
Click {
#[arg(required_unless_present = "id")]
name: Option<String>,
#[arg(long, conflicts_with = "name")]
id: Option<u64>,
},
Audit {
family: Option<String>,
},
Sweep {
family: Option<String>,
},
Cover {
surface: Option<String>,
},
Inventory {
surface: Option<String>,
},
Qa {
selector: Option<String>,
#[arg(long)]
checks: Option<PathBuf>,
},
List {
#[arg(long)]
checks: Option<PathBuf>,
},
}
static TRACE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static PACE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub fn set_trace(on: bool) {
TRACE.store(on, std::sync::atomic::Ordering::Relaxed);
}
pub fn set_pace(seconds: f64) {
PACE.store(seconds.to_bits(), std::sync::atomic::Ordering::Relaxed);
}
static APP: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
pub fn set_app_profile(path: Option<PathBuf>) {
let _ = APP.set(path);
}
pub fn app_profile() -> Option<PathBuf> {
APP.get().cloned().flatten()
}
pub fn pace() -> f64 {
f64::from_bits(PACE.load(std::sync::atomic::Ordering::Relaxed))
}
pub fn trace() -> bool {
TRACE.load(std::sync::atomic::Ordering::Relaxed)
}
impl Command {
pub fn is_dump(&self) -> bool {
matches!(
self,
Command::Metrics | Command::Watch { .. } | Command::Frames | Command::Tree
)
}
}