use std::io::{self, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "testty", about, version)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Run {
scenario: PathBuf,
#[arg(long)]
bin: Option<PathBuf>,
#[arg(long)]
proof: Option<PathBuf>,
},
Schema,
Proof {
#[command(subcommand)]
command: ProofCommand,
},
Update,
}
impl Command {
fn dispatch(self) -> ExitCode {
match self {
Command::Run { .. } => not_implemented("run"),
Command::Schema => not_implemented("schema"),
Command::Proof {
command: ProofCommand::Open { .. },
} => not_implemented("proof open"),
Command::Proof {
command: ProofCommand::Gallery { .. },
} => not_implemented("proof gallery"),
Command::Update => not_implemented("update"),
}
}
}
#[derive(Subcommand)]
enum ProofCommand {
Open {
html: PathBuf,
},
Gallery {
dir: PathBuf,
},
}
fn main() -> ExitCode {
let cli = Cli::parse();
cli.command.dispatch()
}
fn not_implemented(verb: &str) -> ExitCode {
let mut stderr = io::stderr().lock();
let _ = writeln!(stderr, "testty: `{verb}` is not yet implemented");
ExitCode::FAILURE
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
#[test]
fn parses_run_with_scenario_path() {
let argv = ["testty", "run", "scenario.yaml"];
let cli = Cli::parse_from(argv);
assert!(matches!(
cli.command,
Command::Run { scenario, bin: None, proof: None }
if scenario.as_path() == Path::new("scenario.yaml")
));
}
#[test]
fn parses_run_with_bin_and_proof_options() {
let argv = [
"testty",
"run",
"scenario.yaml",
"--bin",
"./app",
"--proof",
"out",
];
let cli = Cli::parse_from(argv);
assert!(matches!(
cli.command,
Command::Run { bin: Some(bin), proof: Some(proof), .. }
if bin.as_path() == Path::new("./app") && proof.as_path() == Path::new("out")
));
}
#[test]
fn parses_schema_verb() {
let argv = ["testty", "schema"];
let cli = Cli::parse_from(argv);
assert!(matches!(cli.command, Command::Schema));
}
#[test]
fn parses_proof_open_with_html_path() {
let argv = ["testty", "proof", "open", "report.html"];
let cli = Cli::parse_from(argv);
assert!(matches!(
cli.command,
Command::Proof { command: ProofCommand::Open { html } }
if html.as_path() == Path::new("report.html")
));
}
#[test]
fn parses_proof_gallery_with_dir_path() {
let argv = ["testty", "proof", "gallery", "proofs"];
let cli = Cli::parse_from(argv);
assert!(matches!(
cli.command,
Command::Proof { command: ProofCommand::Gallery { dir } }
if dir.as_path() == Path::new("proofs")
));
}
#[test]
fn parses_update_verb() {
let argv = ["testty", "update"];
let cli = Cli::parse_from(argv);
assert!(matches!(cli.command, Command::Update));
}
#[test]
fn run_without_scenario_is_rejected() {
let argv = ["testty", "run"];
let result = Cli::try_parse_from(argv);
assert!(result.is_err());
}
#[test]
fn every_stub_reports_unimplemented_and_fails() {
let verbs: [Command; 6] = [
Command::Run {
scenario: PathBuf::from("s.yaml"),
bin: None,
proof: None,
},
Command::Schema,
Command::Proof {
command: ProofCommand::Open {
html: PathBuf::from("r.html"),
},
},
Command::Proof {
command: ProofCommand::Gallery {
dir: PathBuf::from("d"),
},
},
Command::Update,
Command::Run {
scenario: PathBuf::from("s.yaml"),
bin: Some(PathBuf::from("b")),
proof: Some(PathBuf::from("p")),
},
];
for verb in verbs {
assert_eq!(verb.dispatch(), ExitCode::FAILURE);
}
}
}