Skip to content

Make .trycmd more cram like #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Here's a trivial example:

```rust,no_run
#[test]
fn ui() {
let t = trycmd::TestCases::new();
t.pass("tests/cmd/*.toml");
fn cli_tests() {
trycmd::TestCases::new()
.case("tests/cmd/*.trycmd");
}
```

Expand Down
6 changes: 3 additions & 3 deletions src/cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ impl TestCases {
pub fn pass(&self, glob: impl AsRef<std::path::Path>) -> &Self {
self.runner
.borrow_mut()
.case(glob.as_ref(), Some(crate::CommandStatus::Pass));
.case(glob.as_ref(), Some(crate::CommandStatus::Success));
self
}

/// Overwrite expected status for a test
pub fn fail(&self, glob: impl AsRef<std::path::Path>) -> &Self {
self.runner
.borrow_mut()
.case(glob.as_ref(), Some(crate::CommandStatus::Fail));
.case(glob.as_ref(), Some(crate::CommandStatus::Failed));
self
}

Expand All @@ -49,7 +49,7 @@ impl TestCases {
pub fn skip(&self, glob: impl AsRef<std::path::Path>) -> &Self {
self.runner
.borrow_mut()
.case(glob.as_ref(), Some(crate::CommandStatus::Skip));
.case(glob.as_ref(), Some(crate::CommandStatus::Skipped));
self
}

Expand Down
15 changes: 12 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,18 @@
//!
//! #### `*.trycmd`
//!
//! A `trycmd` file is just a command with arguments, with the arguments split with [shlex](https://crates.io/crates/shlex).
//!
//! The command is interpreted as `bin.name` in a `toml` file.
//! `.trycmd` files provide a more visually familiar way of specifying test cases.
//!
//! The basic syntax is:
//! - "`$ `" line prefix starts a new command
//! - "`> `" line prefix appends to the prior command
//! - "`? <status>`" line indicates the exit code (like `echo "? $?"`) and `<status>` can be
//! - An exit code
//! - `success` *(default)*, `failed`, `interrupted`, `skipped`
//!
//! The command is then split with [shlex](https://crates.io/crates/shlex), allowing quoted content
//! to allow spaces. The first argument is the program to run which maps to `bin.name` in the
//! `.toml` file.
//!
//! #### `*.toml`
//!
Expand Down
8 changes: 4 additions & 4 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl Case {
pub(crate) fn run(&self, mode: &Mode, bins: &crate::BinRegistry) -> Result<Output, Output> {
let mut output = Output::default();

if self.expected == Some(crate::CommandStatus::Skip) {
if self.expected == Some(crate::CommandStatus::Skipped) {
assert_eq!(output.spawn.status, SpawnStatus::Skipped);
return Ok(output);
}
Expand Down Expand Up @@ -246,12 +246,12 @@ impl Case {
) -> Result<Output, Output> {
let status = output.spawn.exit.expect("bale out before now");
match expected {
crate::CommandStatus::Pass => {
crate::CommandStatus::Success => {
if !status.success() {
output.spawn.status = SpawnStatus::Expected("success".into());
}
}
crate::CommandStatus::Fail => {
crate::CommandStatus::Failed => {
if status.success() || status.code().is_none() {
output.spawn.status = SpawnStatus::Expected("failure".into());
}
Expand All @@ -261,7 +261,7 @@ impl Case {
output.spawn.status = SpawnStatus::Expected("interrupted".into());
}
}
crate::CommandStatus::Skip => unreachable!("handled earlier"),
crate::CommandStatus::Skipped => unreachable!("handled earlier"),
crate::CommandStatus::Code(expected_code) => {
if Some(expected_code) != status.code() {
output.spawn.status = SpawnStatus::Expected(expected_code.to_string());
Expand Down
94 changes: 84 additions & 10 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,26 @@ impl TryCmd {
}

fn parse_trycmd(s: &str) -> Result<Self, String> {
let mut cmdline = String::new();
let mut status = Some(CommandStatus::Success);
for line in s.lines() {
if let Some(raw) = line.strip_prefix("$ ") {
cmdline.clear();
cmdline.push_str(raw.trim());
cmdline.push(' ');
} else if let Some(raw) = line.strip_prefix("> ") {
cmdline.push_str(raw.trim());
cmdline.push(' ');
} else if let Some(raw) = line.strip_prefix("? ") {
status = Some(raw.trim().parse::<CommandStatus>()?);
} else {
return Err(format!("Invalid line: `{}`", line));
}
}

let mut env = Env::default();

let mut iter = shlex::Shlex::new(s.trim());
let mut iter = shlex::Shlex::new(cmdline.trim());
let bin = loop {
let next = iter
.next()
Expand All @@ -171,6 +188,7 @@ impl TryCmd {
bin: Some(Bin::Name(bin)),
args: Some(args),
env,
status,
..Default::default()
})
}
Expand Down Expand Up @@ -376,16 +394,33 @@ where
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub enum CommandStatus {
Pass,
Fail,
Success,
Failed,
Interrupted,
Skip,
Skipped,
Code(i32),
}

impl Default for CommandStatus {
fn default() -> Self {
CommandStatus::Pass
CommandStatus::Success
}
}

impl std::str::FromStr for CommandStatus {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"success" => Ok(Self::Success),
"failed" => Ok(Self::Failed),
"interrupted" => Ok(Self::Interrupted),
"skipped" => Ok(Self::Skipped),
_ => s
.parse::<i32>()
.map(Self::Code)
.map_err(|_| format!("Expected an exit code, got {}", s)),
}
}
}

Expand All @@ -398,9 +433,10 @@ mod test {
let expected = TryCmd {
bin: Some(Bin::Name("cmd".into())),
args: Some(Args::default()),
status: Some(CommandStatus::Success),
..Default::default()
};
let actual = TryCmd::parse_trycmd("cmd").unwrap();
let actual = TryCmd::parse_trycmd("$ cmd").unwrap();
assert_eq!(expected, actual);
}

Expand All @@ -409,9 +445,22 @@ mod test {
let expected = TryCmd {
bin: Some(Bin::Name("cmd".into())),
args: Some(Args::Split(vec!["arg1".into(), "arg with space".into()])),
status: Some(CommandStatus::Success),
..Default::default()
};
let actual = TryCmd::parse_trycmd("$ cmd arg1 'arg with space'").unwrap();
assert_eq!(expected, actual);
}

#[test]
fn parse_trycmd_multi_line() {
let expected = TryCmd {
bin: Some(Bin::Name("cmd".into())),
args: Some(Args::Split(vec!["arg1".into(), "arg with space".into()])),
status: Some(CommandStatus::Success),
..Default::default()
};
let actual = TryCmd::parse_trycmd("cmd arg1 'arg with space'").unwrap();
let actual = TryCmd::parse_trycmd("$ cmd arg1\n> 'arg with space'").unwrap();
assert_eq!(expected, actual);
}

Expand All @@ -428,9 +477,34 @@ mod test {
.collect(),
..Default::default()
},
status: Some(CommandStatus::Success),
..Default::default()
};
let actual = TryCmd::parse_trycmd("$ KEY1=VALUE1 KEY2='VALUE2 with space' cmd").unwrap();
assert_eq!(expected, actual);
}

#[test]
fn parse_trycmd_status() {
let expected = TryCmd {
bin: Some(Bin::Name("cmd".into())),
args: Some(Args::default()),
status: Some(CommandStatus::Skipped),
..Default::default()
};
let actual = TryCmd::parse_trycmd("$ cmd\n? skipped").unwrap();
assert_eq!(expected, actual);
}

#[test]
fn parse_trycmd_status_code() {
let expected = TryCmd {
bin: Some(Bin::Name("cmd".into())),
args: Some(Args::default()),
status: Some(CommandStatus::Code(-1)),
..Default::default()
};
let actual = TryCmd::parse_trycmd("KEY1=VALUE1 KEY2='VALUE2 with space' cmd").unwrap();
let actual = TryCmd::parse_trycmd("$ cmd\n? -1").unwrap();
assert_eq!(expected, actual);
}

Expand Down Expand Up @@ -498,10 +572,10 @@ mod test {
#[test]
fn parse_toml_status_success() {
let expected = TryCmd {
status: Some(CommandStatus::Pass),
status: Some(CommandStatus::Success),
..Default::default()
};
let actual = TryCmd::parse_toml("status = 'pass'").unwrap();
let actual = TryCmd::parse_toml("status = 'success'").unwrap();
assert_eq!(expected, actual);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/cmd/basic.trycmd
Original file line number Diff line number Diff line change
@@ -1 +1 @@
bin-fixture
$ bin-fixture
2 changes: 1 addition & 1 deletion tests/cmd/failure.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
bin.name = "bin-fixture"
status = "fail"
status = "failed"

[env.add]
exit = "1"
6 changes: 3 additions & 3 deletions tests/cmd/schema.stdout
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@
{
"type": "string",
"enum": [
"pass",
"fail",
"success",
"failed",
"interrupted",
"skip"
"skipped"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion tests/cmd/schema.trycmd
Original file line number Diff line number Diff line change
@@ -1 +1 @@
trycmd-schema
$ trycmd-schema