Organize the public CLI with clap

This commit is contained in:
ms2k 2026-07-18 23:25:40 +09:00
commit d08d931b5f

View file

@ -2,11 +2,11 @@ use std::collections::VecDeque;
use std::convert::Infallible;
use std::ffi::OsString;
use std::io::Write;
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use lazaret_client::{
BootstrapAcquisition, ClientBackend, ClientCommand, ClientError, DomainStatus,
parse_command_line, run,
BootstrapAcquisition, ClientBackend, ClientCommand, ClientError, DomainStatus, run,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -234,8 +234,9 @@ fn unsupported_host_and_configuration_errors_keep_their_cause() {
#[test]
fn command_line_requires_an_explicit_profile_and_preserves_command_bytes() {
let parsed = parse_command_line(
let parsed = lazaret_client::cli::parse(
[
"run",
"--profile",
"agent",
"--workspace",
@ -250,6 +251,9 @@ fn command_line_requires_an_explicit_profile_and_preserves_command_bytes() {
PathBuf::from("/current"),
)
.unwrap();
let lazaret_client::cli::Command::Run(parsed) = parsed else {
panic!("expected run command");
};
assert_eq!(parsed.profile, "agent");
assert_eq!(parsed.workspace, PathBuf::from("/project"));
assert_eq!(
@ -259,8 +263,8 @@ fn command_line_requires_an_explicit_profile_and_preserves_command_bytes() {
assert_eq!(parsed.argv, [b"tool".to_vec(), b"argument".to_vec()]);
assert!(
parse_command_line(
["--", "tool"].map(OsString::from),
lazaret_client::cli::parse(
["run", "--", "tool"].map(OsString::from),
PathBuf::from("/current")
)
.unwrap_err()
@ -268,3 +272,102 @@ fn command_line_requires_an_explicit_profile_and_preserves_command_bytes() {
.contains("profile")
);
}
#[test]
fn command_line_preserves_non_utf8_paths_and_command_arguments() {
let workspace = OsString::from_vec(b"/project-\xff".to_vec());
let argument = OsString::from_vec(b"argument-\xfe".to_vec());
let parsed = lazaret_client::cli::parse(
[
OsString::from("run"),
OsString::from("--profile"),
OsString::from("agent"),
OsString::from("--workspace"),
workspace.clone(),
OsString::from("--"),
OsString::from("tool"),
argument,
],
PathBuf::from("/current"),
)
.unwrap();
let lazaret_client::cli::Command::Run(parsed) = parsed else {
panic!("expected run command");
};
assert_eq!(parsed.workspace, PathBuf::from(workspace));
assert_eq!(parsed.argv, [b"tool".to_vec(), b"argument-\xfe".to_vec()]);
}
#[test]
fn public_help_explains_commands_and_options() {
for (arguments, expected) in [
(
&["--help"][..],
&[
"Run development tools in an isolated gVisor sandbox",
"run",
"state",
][..],
),
(
&["run", "--help"],
&[
"Agent profile to use",
"Project directory to share",
"Command and arguments to run",
],
),
(
&["state", "--help"],
&["list", "inspect", "verify", "reset"],
),
(
&["state", "reset", "--help"],
&[
"Reset every configured persistent sandbox path",
"Reset one exact configured container path",
"interactive confirmation",
],
),
] {
let error = lazaret_client::cli::parse(
arguments.iter().map(OsString::from),
PathBuf::from("/current"),
)
.unwrap_err();
assert_eq!(error.kind(), clap::error::ErrorKind::DisplayHelp);
let help = error.to_string();
for text in expected {
assert!(
help.contains(text),
"{arguments:?} did not explain {text:?}"
);
}
}
}
#[test]
fn clap_rejects_missing_profile_command_and_ambiguous_reset_scope() {
let workspace = PathBuf::from("/current");
for arguments in [
vec!["run", "--", "tool"],
vec!["run", "--profile", "agent"],
vec!["state", "reset", "--profile", "agent"],
vec![
"state",
"reset",
"--profile",
"agent",
"--all",
"--path",
"/usr",
],
] {
let error = lazaret_client::cli::parse(
arguments.into_iter().map(OsString::from),
workspace.clone(),
)
.unwrap_err();
assert_eq!(error.exit_code(), 2);
}
}