assert_cli/assert.rs
1use std::default;
2use std::ffi::{OsStr, OsString};
3use std::io::Write;
4use std::path::PathBuf;
5use std::process::{Command, Stdio};
6use std::vec::Vec;
7
8use environment::Environment;
9use failure;
10use failure::Fail;
11
12use errors::*;
13use output::{Content, Output, OutputKind, OutputPredicate};
14
15/// Assertions for a specific command.
16#[derive(Debug)]
17#[must_use]
18pub struct Assert {
19 cmd: Vec<OsString>,
20 env: Environment,
21 current_dir: Option<PathBuf>,
22 expect_success: Option<bool>,
23 expect_exit_code: Option<i32>,
24 expect_output: Vec<OutputPredicate>,
25 stdin_contents: Option<Vec<u8>>,
26}
27
28impl default::Default for Assert {
29 /// Construct an assert using `cargo run --` as command.
30 ///
31 /// Defaults to asserting _successful_ execution.
32 fn default() -> Self {
33 Assert {
34 cmd: vec![
35 "cargo",
36 "run",
37 #[cfg(not(debug_assertions))]
38 "--release",
39 "--quiet",
40 "--",
41 ].into_iter()
42 .map(OsString::from)
43 .collect(),
44 env: Environment::inherit(),
45 current_dir: None,
46 expect_success: Some(true),
47 expect_exit_code: None,
48 expect_output: vec![],
49 stdin_contents: None,
50 }
51 }
52}
53
54impl Assert {
55 /// Run the crate's main binary.
56 ///
57 /// Defaults to asserting _successful_ execution.
58 pub fn main_binary() -> Self {
59 Assert::default()
60 }
61
62 /// Run a specific binary of the current crate.
63 ///
64 /// Defaults to asserting _successful_ execution.
65 pub fn cargo_binary<S: AsRef<OsStr>>(name: S) -> Self {
66 Assert {
67 cmd: vec![
68 OsStr::new("cargo"),
69 OsStr::new("run"),
70 #[cfg(not(debug_assertions))]
71 OsStr::new("--release"),
72 OsStr::new("--quiet"),
73 OsStr::new("--bin"),
74 name.as_ref(),
75 OsStr::new("--"),
76 ].into_iter()
77 .map(OsString::from)
78 .collect(),
79 ..Self::default()
80 }
81 }
82
83 /// Run a specific example of the current crate.
84 ///
85 /// Defaults to asserting _successful_ execution.
86 pub fn example<S: AsRef<OsStr>>(name: S) -> Self {
87 Assert {
88 cmd: vec![
89 OsStr::new("cargo"),
90 OsStr::new("run"),
91 #[cfg(not(debug_assertions))]
92 OsStr::new("--release"),
93 OsStr::new("--quiet"),
94 OsStr::new("--example"),
95 name.as_ref(),
96 OsStr::new("--"),
97 ].into_iter()
98 .map(OsString::from)
99 .collect(),
100 ..Self::default()
101 }
102 }
103
104 /// Run a custom command.
105 ///
106 /// Defaults to asserting _successful_ execution.
107 ///
108 /// # Examples
109 ///
110 /// ```rust
111 /// extern crate assert_cli;
112 ///
113 /// assert_cli::Assert::command(&["echo", "1337"])
114 /// .unwrap();
115 /// ```
116 pub fn command<S: AsRef<OsStr>>(cmd: &[S]) -> Self {
117 Assert {
118 cmd: cmd.into_iter().map(OsString::from).collect(),
119 ..Self::default()
120 }
121 }
122
123 /// Add arguments to the command.
124 ///
125 /// # Examples
126 ///
127 /// ```rust
128 /// extern crate assert_cli;
129 ///
130 /// assert_cli::Assert::command(&["echo"])
131 /// .with_args(&["42"])
132 /// .stdout().contains("42")
133 /// .unwrap();
134 ///
135 /// ```
136 pub fn with_args<S: AsRef<OsStr>>(mut self, args: &[S]) -> Self {
137 self.cmd.extend(args.into_iter().map(OsString::from));
138 self
139 }
140
141 /// Add stdin to the command.
142 ///
143 /// # Examples
144 ///
145 /// ```rust
146 /// extern crate assert_cli;
147 ///
148 /// assert_cli::Assert::command(&["cat"])
149 /// .stdin("42")
150 /// .stdout().contains("42")
151 /// .unwrap();
152 /// ```
153 pub fn stdin<S: Into<Vec<u8>>>(mut self, contents: S) -> Self {
154 self.stdin_contents = Some(contents.into());
155 self
156 }
157
158 /// Sets the working directory for the command.
159 ///
160 /// # Examples
161 ///
162 /// ```rust
163 /// extern crate assert_cli;
164 ///
165 /// assert_cli::Assert::command(&["wc", "lib.rs"])
166 /// .current_dir(std::path::Path::new("src"))
167 /// .stdout().contains("lib.rs")
168 /// .execute()
169 /// .unwrap();
170 /// ```
171 pub fn current_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
172 self.current_dir = Some(dir.into());
173 self
174 }
175
176 /// Sets environments variables for the command.
177 ///
178 /// # Examples
179 ///
180 /// ```rust
181 /// extern crate assert_cli;
182 ///
183 /// assert_cli::Assert::command(&["printenv"])
184 /// .with_env(&[("TEST_ENV", "OK")])
185 /// .stdout().contains("TEST_ENV=OK")
186 /// .execute()
187 /// .unwrap();
188 ///
189 /// let env = assert_cli::Environment::empty()
190 /// .insert("FOO", "BAR");
191 ///
192 /// assert_cli::Assert::command(&["printenv"])
193 /// .with_env(&env)
194 /// .stdout().is("FOO=BAR")
195 /// .execute()
196 /// .unwrap();
197 ///
198 /// ::std::env::set_var("BAZ", "BAR");
199 ///
200 /// assert_cli::Assert::command(&["printenv"])
201 /// .stdout().contains("BAZ=BAR")
202 /// .execute()
203 /// .unwrap();
204 /// ```
205 pub fn with_env<E: Into<Environment>>(mut self, env: E) -> Self {
206 self.env = env.into();
207
208 self
209 }
210
211 /// Small helper to make chains more readable.
212 ///
213 /// # Examples
214 ///
215 /// ```rust
216 /// extern crate assert_cli;
217 ///
218 /// assert_cli::Assert::command(&["cat", "non-existing-file"])
219 /// .fails()
220 /// .and()
221 /// .stderr().contains("non-existing-file")
222 /// .unwrap();
223 /// ```
224 pub fn and(self) -> Self {
225 self
226 }
227
228 /// Expect the command to be executed successfully.
229 ///
230 /// # Examples
231 ///
232 /// ```rust
233 /// extern crate assert_cli;
234 ///
235 /// assert_cli::Assert::command(&["echo", "42"])
236 /// .succeeds()
237 /// .unwrap();
238 /// ```
239 pub fn succeeds(mut self) -> Self {
240 self.expect_exit_code = None;
241 self.expect_success = Some(true);
242 self
243 }
244
245 /// Expect the command to fail.
246 ///
247 /// Note: This does not include shell failures like `command not found`. I.e. the
248 /// command must _run_ and fail for this assertion to pass.
249 ///
250 /// # Examples
251 ///
252 /// ```rust
253 /// extern crate assert_cli;
254 ///
255 /// assert_cli::Assert::command(&["cat", "non-existing-file"])
256 /// .fails()
257 /// .and()
258 /// .stderr().contains("non-existing-file")
259 /// .unwrap();
260 /// ```
261 pub fn fails(mut self) -> Self {
262 self.expect_success = Some(false);
263 self
264 }
265
266 /// Expect the command to fail and return a specific error code.
267 ///
268 /// # Examples
269 ///
270 /// ```rust
271 /// extern crate assert_cli;
272 ///
273 /// assert_cli::Assert::command(&["cat", "non-existing-file"])
274 /// .fails_with(1)
275 /// .and()
276 /// .stderr().is("cat: non-existing-file: No such file or directory")
277 /// .unwrap();
278 /// ```
279 pub fn fails_with(mut self, expect_exit_code: i32) -> Self {
280 self.expect_success = Some(false);
281 self.expect_exit_code = Some(expect_exit_code);
282 self
283 }
284
285 /// Do not care whether the command exits successfully or if it fails.
286 ///
287 /// This function removes any assertions that were already set, including
288 /// any expected exit code that was set with [`fails_with`].
289 ///
290 /// # Examples
291 ///
292 /// ```rust
293 /// extern crate assert_cli;
294 ///
295 /// assert_cli::Assert::command(&["cat", "non-existing-file"])
296 /// .ignore_status()
297 /// .and()
298 /// .stderr().is("cat: non-existing-file: No such file or directory")
299 /// .unwrap();
300 /// ```
301 ///
302 /// [`fails_with`]: #method.fails_with
303 pub fn ignore_status(mut self) -> Self {
304 self.expect_exit_code = None;
305 self.expect_success = None;
306 self
307 }
308
309 /// Create an assertion for stdout's contents
310 ///
311 /// # Examples
312 ///
313 /// ```rust
314 /// extern crate assert_cli;
315 ///
316 /// assert_cli::Assert::command(&["echo", "42"])
317 /// .stdout().contains("42")
318 /// .unwrap();
319 /// ```
320 pub fn stdout(self) -> OutputAssertionBuilder {
321 OutputAssertionBuilder {
322 assertion: self,
323 kind: OutputKind::StdOut,
324 }
325 }
326
327 /// Create an assertion for stdout's contents
328 ///
329 /// # Examples
330 ///
331 /// ```rust
332 /// extern crate assert_cli;
333 ///
334 /// assert_cli::Assert::command(&["cat", "non-existing-file"])
335 /// .fails_with(1)
336 /// .and()
337 /// .stderr().is("cat: non-existing-file: No such file or directory")
338 /// .unwrap();
339 /// ```
340 pub fn stderr(self) -> OutputAssertionBuilder {
341 OutputAssertionBuilder {
342 assertion: self,
343 kind: OutputKind::StdErr,
344 }
345 }
346
347 /// Execute the command and check the assertions.
348 ///
349 /// # Examples
350 ///
351 /// ```rust
352 /// extern crate assert_cli;
353 ///
354 /// let test = assert_cli::Assert::command(&["echo", "42"])
355 /// .stdout().contains("42")
356 /// .execute();
357 /// assert!(test.is_ok());
358 /// ```
359 pub fn execute(self) -> Result<(), AssertionError> {
360 let bin = &self.cmd[0];
361
362 let args: Vec<_> = self.cmd.iter().skip(1).collect();
363 let mut command = Command::new(bin);
364 let command = command
365 .stdin(Stdio::piped())
366 .stdout(Stdio::piped())
367 .stderr(Stdio::piped())
368 .env_clear()
369 .envs(self.env.clone().compile())
370 .args(&args);
371
372 let command = match self.current_dir {
373 Some(ref dir) => command.current_dir(dir),
374 None => command,
375 };
376
377 let mut spawned = command
378 .spawn()
379 .chain_with(|| AssertionError::new(self.cmd.clone()))?;
380
381 if let Some(ref contents) = self.stdin_contents {
382 spawned
383 .stdin
384 .as_mut()
385 .expect("Couldn't get mut ref to command stdin")
386 .write_all(contents)
387 .chain_with(|| AssertionError::new(self.cmd.clone()))?;
388 }
389 let output = spawned
390 .wait_with_output()
391 .chain_with(|| AssertionError::new(self.cmd.clone()))?;
392
393 if let Some(expect_success) = self.expect_success {
394 let actual_success = output.status.success();
395 if expect_success != actual_success {
396 return Err(
397 AssertionError::new(self.cmd.clone()).chain(StatusError::new(
398 actual_success,
399 output.stdout.clone(),
400 output.stderr.clone(),
401 )),
402 )?;
403 }
404 }
405
406 if self.expect_exit_code.is_some() && self.expect_exit_code != output.status.code() {
407 return Err(
408 AssertionError::new(self.cmd.clone()).chain(ExitCodeError::new(
409 self.expect_exit_code,
410 output.status.code(),
411 output.stdout.clone(),
412 output.stderr.clone(),
413 )),
414 );
415 }
416
417 self.expect_output
418 .iter()
419 .map(|a| {
420 a.verify(&output)
421 .chain_with(|| AssertionError::new(self.cmd.clone()))
422 })
423 .collect::<Result<Vec<()>, AssertionError>>()?;
424
425 Ok(())
426 }
427
428 /// Execute the command, check the assertions, and panic when they fail.
429 ///
430 /// # Examples
431 ///
432 /// ```rust,should_panic="Assert CLI failure"
433 /// extern crate assert_cli;
434 ///
435 /// assert_cli::Assert::command(&["echo", "42"])
436 /// .fails()
437 /// .unwrap(); // panics
438 /// ```
439 pub fn unwrap(self) {
440 if let Err(err) = self.execute() {
441 panic!(Self::format_causes(err.causes()));
442 }
443 }
444
445 fn format_causes(mut causes: failure::Causes) -> String {
446 let mut result = causes.next().expect("an error should exist").to_string();
447 for cause in causes {
448 result.push_str(&format!("\nwith: {}", cause));
449 }
450 result
451 }
452}
453
454/// Assertions for command output.
455#[derive(Debug)]
456#[must_use]
457pub struct OutputAssertionBuilder {
458 assertion: Assert,
459 kind: OutputKind,
460}
461
462impl OutputAssertionBuilder {
463 /// Expect the command's output to **contain** `output`.
464 ///
465 /// # Examples
466 ///
467 /// ```rust
468 /// extern crate assert_cli;
469 ///
470 /// assert_cli::Assert::command(&["echo", "42"])
471 /// .stdout().contains("42")
472 /// .unwrap();
473 /// ```
474 pub fn contains<O: Into<Content>>(mut self, output: O) -> Assert {
475 let pred = OutputPredicate::new(self.kind, Output::contains(output));
476 self.assertion.expect_output.push(pred);
477 self.assertion
478 }
479
480 /// Expect the command to output **exactly** this `output`.
481 ///
482 /// # Examples
483 ///
484 /// ```rust
485 /// extern crate assert_cli;
486 ///
487 /// assert_cli::Assert::command(&["echo", "42"])
488 /// .stdout().is("42")
489 /// .unwrap();
490 /// ```
491 pub fn is<O: Into<Content>>(mut self, output: O) -> Assert {
492 let pred = OutputPredicate::new(self.kind, Output::is(output));
493 self.assertion.expect_output.push(pred);
494 self.assertion
495 }
496
497 /// Expect the command's output to not **contain** `output`.
498 ///
499 /// # Examples
500 ///
501 /// ```rust
502 /// extern crate assert_cli;
503 ///
504 /// assert_cli::Assert::command(&["echo", "42"])
505 /// .stdout().doesnt_contain("73")
506 /// .unwrap();
507 /// ```
508 pub fn doesnt_contain<O: Into<Content>>(mut self, output: O) -> Assert {
509 let pred = OutputPredicate::new(self.kind, Output::doesnt_contain(output));
510 self.assertion.expect_output.push(pred);
511 self.assertion
512 }
513
514 /// Expect the command to output to not be **exactly** this `output`.
515 ///
516 /// # Examples
517 ///
518 /// ```rust
519 /// extern crate assert_cli;
520 ///
521 /// assert_cli::Assert::command(&["echo", "42"])
522 /// .stdout().isnt("73")
523 /// .unwrap();
524 /// ```
525 pub fn isnt<O: Into<Content>>(mut self, output: O) -> Assert {
526 let pred = OutputPredicate::new(self.kind, Output::isnt(output));
527 self.assertion.expect_output.push(pred);
528 self.assertion
529 }
530
531 /// Expect the command output to satisfy the given predicate.
532 ///
533 /// # Examples
534 ///
535 /// ```rust
536 /// extern crate assert_cli;
537 ///
538 /// assert_cli::Assert::command(&["echo", "-n", "42"])
539 /// .stdout().satisfies(|x| x.len() == 2, "bad length")
540 /// .unwrap();
541 /// ```
542 pub fn satisfies<F, M>(mut self, pred: F, msg: M) -> Assert
543 where
544 F: 'static + Fn(&str) -> bool,
545 M: Into<String>,
546 {
547 let pred = OutputPredicate::new(self.kind, Output::satisfies(pred, msg));
548 self.assertion.expect_output.push(pred);
549 self.assertion
550 }
551}
552
553#[cfg(test)]
554mod test {
555 use super::*;
556 use std::ffi::OsString;
557
558 fn command() -> Assert {
559 Assert::command(&["printenv"])
560 }
561
562 #[test]
563 fn main_binary_default_uses_active_profile() {
564 let assert = Assert::main_binary();
565
566 let expected = if cfg!(debug_assertions) {
567 OsString::from("cargo run --quiet -- ")
568 } else {
569 OsString::from("cargo run --release --quiet -- ")
570 };
571
572 assert_eq!(
573 expected,
574 assert
575 .cmd
576 .into_iter()
577 .fold(OsString::from(""), |mut cmd, token| {
578 cmd.push(token);
579 cmd.push(" ");
580 cmd
581 })
582 );
583 }
584
585 #[test]
586 fn cargo_binary_default_uses_active_profile() {
587 let assert = Assert::cargo_binary("hello");
588
589 let expected = if cfg!(debug_assertions) {
590 OsString::from("cargo run --quiet --bin hello -- ")
591 } else {
592 OsString::from("cargo run --release --quiet --bin hello -- ")
593 };
594
595 assert_eq!(
596 expected,
597 assert
598 .cmd
599 .into_iter()
600 .fold(OsString::from(""), |mut cmd, token| {
601 cmd.push(token);
602 cmd.push(" ");
603 cmd
604 })
605 );
606 }
607
608 #[test]
609 fn take_ownership() {
610 let x = Environment::inherit();
611
612 command()
613 .with_env(x.clone())
614 .with_env(&x)
615 .with_env(x)
616 .unwrap();
617 }
618
619 #[test]
620 fn in_place_mod() {
621 let y = Environment::empty();
622
623 let y = y.insert("key", "value");
624
625 assert_eq!(
626 y.compile(),
627 vec![(OsString::from("key"), OsString::from("value"))]
628 );
629 }
630
631 #[test]
632 fn in_place_mod2() {
633 let x = Environment::inherit();
634
635 command()
636 .with_env(&x.insert("key", "value").insert("key", "vv"))
637 .stdout()
638 .contains("key=vv")
639 .execute()
640 .unwrap();
641 // Granted, `insert` moved `x`, so we can no longer reference it, even
642 // though only a reference was passed to `with_env`
643 }
644
645 #[test]
646 fn in_place_mod3() {
647 // In-place modification while allowing later accesses to the `Environment`
648 let y = Environment::empty();
649
650 assert_eq!(
651 y.clone().insert("key", "value").compile(),
652 vec![(OsString::from("key"), OsString::from("value"))]
653 );
654
655 command()
656 .with_env(y)
657 .stdout()
658 .doesnt_contain("key=value")
659 .execute()
660 .unwrap();
661 }
662
663 #[test]
664 fn empty_env() {
665 // In-place modification while allowing later accesses to the `Environment`
666 let y = Environment::empty();
667
668 assert!(command().with_env(y).stdout().is("").execute().is_ok());
669 }
670 #[test]
671 fn take_vec() {
672 let v = vec![("bar".to_string(), "baz".to_string())];
673
674 command()
675 .with_env(&vec![("bar", "baz")])
676 .stdout()
677 .contains("bar=baz")
678 .execute()
679 .unwrap();
680
681 command()
682 .with_env(&v)
683 .stdout()
684 .contains("bar=baz")
685 .execute()
686 .unwrap();
687
688 command()
689 .with_env(&vec![("bar", "baz")])
690 .stdout()
691 .isnt("")
692 .execute()
693 .unwrap();
694 }
695
696 #[test]
697 fn take_slice_of_strs() {
698 command()
699 .with_env(&[("bar", "BAZ")])
700 .stdout()
701 .contains("bar=BAZ")
702 .execute()
703 .unwrap();
704
705 command()
706 .with_env(&[("bar", "BAZ")][..])
707 .stdout()
708 .contains("bar=BAZ")
709 .execute()
710 .unwrap();
711
712 command()
713 .with_env([("bar", "BAZ")].as_ref())
714 .stdout()
715 .contains("bar=BAZ")
716 .execute()
717 .unwrap();
718 }
719
720 #[test]
721 fn take_slice_of_strings() {
722 // same deal as above
723
724 command()
725 .with_env(&[("bar".to_string(), "BAZ".to_string())])
726 .stdout()
727 .contains("bar=BAZ")
728 .execute()
729 .unwrap();
730
731 command()
732 .with_env(&[("bar".to_string(), "BAZ".to_string())][..])
733 .stdout()
734 .contains("bar=BAZ")
735 .execute()
736 .unwrap();
737 }
738
739 #[test]
740 fn take_slice() {
741 command()
742 .with_env(&[("hey", "ho")])
743 .stdout()
744 .contains("hey=ho")
745 .execute()
746 .unwrap();
747
748 command()
749 .with_env(&[("hey", "ho".to_string())])
750 .stdout()
751 .contains("hey=ho")
752 .execute()
753 .unwrap();
754 }
755
756 #[test]
757 fn take_string_i32() {
758 command()
759 .with_env(&[("bar", 3 as i32)])
760 .stdout()
761 .contains("bar=3")
762 .execute()
763 .unwrap();
764 }
765}