Skip to content

Commit fad7262

Browse files
authored
Merge pull request #19 from epage/cram
Make `.trycmd` more cram like
2 parents ddae7d7 + d2a3bf5 commit fad7262

File tree

9 files changed

+112
-29
lines changed

9 files changed

+112
-29
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ Here's a trivial example:
1616

1717
```rust,no_run
1818
#[test]
19-
fn ui() {
20-
let t = trycmd::TestCases::new();
21-
t.pass("tests/cmd/*.toml");
19+
fn cli_tests() {
20+
trycmd::TestCases::new()
21+
.case("tests/cmd/*.trycmd");
2222
}
2323
```
2424

src/cases.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ impl TestCases {
2525
pub fn pass(&self, glob: impl AsRef<std::path::Path>) -> &Self {
2626
self.runner
2727
.borrow_mut()
28-
.case(glob.as_ref(), Some(crate::CommandStatus::Pass));
28+
.case(glob.as_ref(), Some(crate::CommandStatus::Success));
2929
self
3030
}
3131

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

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

src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,18 @@
4747
//!
4848
//! #### `*.trycmd`
4949
//!
50-
//! A `trycmd` file is just a command with arguments, with the arguments split with [shlex](https://crates.io/crates/shlex).
51-
//!
52-
//! The command is interpreted as `bin.name` in a `toml` file.
50+
//! `.trycmd` files provide a more visually familiar way of specifying test cases.
51+
//!
52+
//! The basic syntax is:
53+
//! - "`$ `" line prefix starts a new command
54+
//! - "`> `" line prefix appends to the prior command
55+
//! - "`? <status>`" line indicates the exit code (like `echo "? $?"`) and `<status>` can be
56+
//! - An exit code
57+
//! - `success` *(default)*, `failed`, `interrupted`, `skipped`
58+
//!
59+
//! The command is then split with [shlex](https://crates.io/crates/shlex), allowing quoted content
60+
//! to allow spaces. The first argument is the program to run which maps to `bin.name` in the
61+
//! `.toml` file.
5362
//!
5463
//! #### `*.toml`
5564
//!

src/runner.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl Case {
116116
pub(crate) fn run(&self, mode: &Mode, bins: &crate::BinRegistry) -> Result<Output, Output> {
117117
let mut output = Output::default();
118118

119-
if self.expected == Some(crate::CommandStatus::Skip) {
119+
if self.expected == Some(crate::CommandStatus::Skipped) {
120120
assert_eq!(output.spawn.status, SpawnStatus::Skipped);
121121
return Ok(output);
122122
}
@@ -246,12 +246,12 @@ impl Case {
246246
) -> Result<Output, Output> {
247247
let status = output.spawn.exit.expect("bale out before now");
248248
match expected {
249-
crate::CommandStatus::Pass => {
249+
crate::CommandStatus::Success => {
250250
if !status.success() {
251251
output.spawn.status = SpawnStatus::Expected("success".into());
252252
}
253253
}
254-
crate::CommandStatus::Fail => {
254+
crate::CommandStatus::Failed => {
255255
if status.success() || status.code().is_none() {
256256
output.spawn.status = SpawnStatus::Expected("failure".into());
257257
}
@@ -261,7 +261,7 @@ impl Case {
261261
output.spawn.status = SpawnStatus::Expected("interrupted".into());
262262
}
263263
}
264-
crate::CommandStatus::Skip => unreachable!("handled earlier"),
264+
crate::CommandStatus::Skipped => unreachable!("handled earlier"),
265265
crate::CommandStatus::Code(expected_code) => {
266266
if Some(expected_code) != status.code() {
267267
output.spawn.status = SpawnStatus::Expected(expected_code.to_string());

src/schema.rs

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,26 @@ impl TryCmd {
153153
}
154154

155155
fn parse_trycmd(s: &str) -> Result<Self, String> {
156+
let mut cmdline = String::new();
157+
let mut status = Some(CommandStatus::Success);
158+
for line in s.lines() {
159+
if let Some(raw) = line.strip_prefix("$ ") {
160+
cmdline.clear();
161+
cmdline.push_str(raw.trim());
162+
cmdline.push(' ');
163+
} else if let Some(raw) = line.strip_prefix("> ") {
164+
cmdline.push_str(raw.trim());
165+
cmdline.push(' ');
166+
} else if let Some(raw) = line.strip_prefix("? ") {
167+
status = Some(raw.trim().parse::<CommandStatus>()?);
168+
} else {
169+
return Err(format!("Invalid line: `{}`", line));
170+
}
171+
}
172+
156173
let mut env = Env::default();
157174

158-
let mut iter = shlex::Shlex::new(s.trim());
175+
let mut iter = shlex::Shlex::new(cmdline.trim());
159176
let bin = loop {
160177
let next = iter
161178
.next()
@@ -171,6 +188,7 @@ impl TryCmd {
171188
bin: Some(Bin::Name(bin)),
172189
args: Some(args),
173190
env,
191+
status,
174192
..Default::default()
175193
})
176194
}
@@ -376,16 +394,33 @@ where
376394
#[serde(rename_all = "kebab-case")]
377395
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
378396
pub enum CommandStatus {
379-
Pass,
380-
Fail,
397+
Success,
398+
Failed,
381399
Interrupted,
382-
Skip,
400+
Skipped,
383401
Code(i32),
384402
}
385403

386404
impl Default for CommandStatus {
387405
fn default() -> Self {
388-
CommandStatus::Pass
406+
CommandStatus::Success
407+
}
408+
}
409+
410+
impl std::str::FromStr for CommandStatus {
411+
type Err = String;
412+
413+
fn from_str(s: &str) -> Result<Self, Self::Err> {
414+
match s {
415+
"success" => Ok(Self::Success),
416+
"failed" => Ok(Self::Failed),
417+
"interrupted" => Ok(Self::Interrupted),
418+
"skipped" => Ok(Self::Skipped),
419+
_ => s
420+
.parse::<i32>()
421+
.map(Self::Code)
422+
.map_err(|_| format!("Expected an exit code, got {}", s)),
423+
}
389424
}
390425
}
391426

@@ -398,9 +433,10 @@ mod test {
398433
let expected = TryCmd {
399434
bin: Some(Bin::Name("cmd".into())),
400435
args: Some(Args::default()),
436+
status: Some(CommandStatus::Success),
401437
..Default::default()
402438
};
403-
let actual = TryCmd::parse_trycmd("cmd").unwrap();
439+
let actual = TryCmd::parse_trycmd("$ cmd").unwrap();
404440
assert_eq!(expected, actual);
405441
}
406442

@@ -409,9 +445,22 @@ mod test {
409445
let expected = TryCmd {
410446
bin: Some(Bin::Name("cmd".into())),
411447
args: Some(Args::Split(vec!["arg1".into(), "arg with space".into()])),
448+
status: Some(CommandStatus::Success),
449+
..Default::default()
450+
};
451+
let actual = TryCmd::parse_trycmd("$ cmd arg1 'arg with space'").unwrap();
452+
assert_eq!(expected, actual);
453+
}
454+
455+
#[test]
456+
fn parse_trycmd_multi_line() {
457+
let expected = TryCmd {
458+
bin: Some(Bin::Name("cmd".into())),
459+
args: Some(Args::Split(vec!["arg1".into(), "arg with space".into()])),
460+
status: Some(CommandStatus::Success),
412461
..Default::default()
413462
};
414-
let actual = TryCmd::parse_trycmd("cmd arg1 'arg with space'").unwrap();
463+
let actual = TryCmd::parse_trycmd("$ cmd arg1\n> 'arg with space'").unwrap();
415464
assert_eq!(expected, actual);
416465
}
417466

@@ -428,9 +477,34 @@ mod test {
428477
.collect(),
429478
..Default::default()
430479
},
480+
status: Some(CommandStatus::Success),
481+
..Default::default()
482+
};
483+
let actual = TryCmd::parse_trycmd("$ KEY1=VALUE1 KEY2='VALUE2 with space' cmd").unwrap();
484+
assert_eq!(expected, actual);
485+
}
486+
487+
#[test]
488+
fn parse_trycmd_status() {
489+
let expected = TryCmd {
490+
bin: Some(Bin::Name("cmd".into())),
491+
args: Some(Args::default()),
492+
status: Some(CommandStatus::Skipped),
493+
..Default::default()
494+
};
495+
let actual = TryCmd::parse_trycmd("$ cmd\n? skipped").unwrap();
496+
assert_eq!(expected, actual);
497+
}
498+
499+
#[test]
500+
fn parse_trycmd_status_code() {
501+
let expected = TryCmd {
502+
bin: Some(Bin::Name("cmd".into())),
503+
args: Some(Args::default()),
504+
status: Some(CommandStatus::Code(-1)),
431505
..Default::default()
432506
};
433-
let actual = TryCmd::parse_trycmd("KEY1=VALUE1 KEY2='VALUE2 with space' cmd").unwrap();
507+
let actual = TryCmd::parse_trycmd("$ cmd\n? -1").unwrap();
434508
assert_eq!(expected, actual);
435509
}
436510

@@ -498,10 +572,10 @@ mod test {
498572
#[test]
499573
fn parse_toml_status_success() {
500574
let expected = TryCmd {
501-
status: Some(CommandStatus::Pass),
575+
status: Some(CommandStatus::Success),
502576
..Default::default()
503577
};
504-
let actual = TryCmd::parse_toml("status = 'pass'").unwrap();
578+
let actual = TryCmd::parse_toml("status = 'success'").unwrap();
505579
assert_eq!(expected, actual);
506580
}
507581

tests/cmd/basic.trycmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
bin-fixture
1+
$ bin-fixture

tests/cmd/failure.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
bin.name = "bin-fixture"
2-
status = "fail"
2+
status = "failed"
33

44
[env.add]
55
exit = "1"

tests/cmd/schema.stdout

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,10 @@
193193
{
194194
"type": "string",
195195
"enum": [
196-
"pass",
197-
"fail",
196+
"success",
197+
"failed",
198198
"interrupted",
199-
"skip"
199+
"skipped"
200200
]
201201
},
202202
{

tests/cmd/schema.trycmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
trycmd-schema
1+
$ trycmd-schema

0 commit comments

Comments
 (0)