1#![deny(missing_debug_implementations)]
2
3pub mod child_output;
231mod collected_output;
232pub mod config;
233mod context;
234pub mod error;
235pub mod input;
236mod macros;
237pub mod output;
238pub mod prelude;
239
240include!("common_re_exports.rs.snippet");
241
242#[cfg(test)]
243mod tests {
244 use crate::{
245 context::Context,
246 input::{run_result_with_context, run_result_with_context_unit},
247 prelude::*,
248 };
249 use lazy_static::lazy_static;
250 use std::{
251 collections::BTreeSet,
252 env::{current_dir, set_current_dir},
253 ffi::{OsStr, OsString},
254 fs,
255 io::Write,
256 path::PathBuf,
257 sync::{Arc, Mutex},
258 };
259 use tempfile::TempDir;
260
261 fn in_temporary_directory<F>(f: F)
262 where
263 F: FnOnce() + std::panic::UnwindSafe,
264 {
265 lazy_static! {
266 static ref CURRENT_DIR_LOCK: Mutex<()> = Mutex::new(());
267 }
268 let _lock = CURRENT_DIR_LOCK.lock();
269 let temp_dir = TempDir::new().unwrap();
270 let original_working_directory = current_dir().unwrap();
271 set_current_dir(&temp_dir).unwrap();
272 let result = std::panic::catch_unwind(|| {
273 f();
274 });
275 set_current_dir(original_working_directory).unwrap();
276 result.unwrap();
277 }
278
279 fn test_executable(name: &str) -> PathBuf {
280 lazy_static! {
281 static ref BUILT: Arc<Mutex<BTreeSet<String>>> = Arc::new(Mutex::new(BTreeSet::new()));
282 }
283 let mut set = match BUILT.lock() {
284 Ok(set) => set,
285 Err(error) => {
286 let _ = write!(
287 std::io::stderr(),
288 "test_executable: BUILT poisoned: {}",
289 error
290 );
291 let _ = std::io::stderr().flush();
292 std::process::exit(1)
293 }
294 };
295 if !set.contains(name) {
296 set.insert(name.to_owned());
297 run!(
298 LogCommand,
299 CurrentDir(std::env::var("CARGO_MANIFEST_DIR").unwrap()),
300 %"cargo build",
301 ("--bin", name),
302 %"--features test_executables",
303 );
304 }
305 executable_path::executable_path(name)
306 }
307
308 fn test_helper() -> PathBuf {
309 test_executable("test_executables_helper")
310 }
311
312 #[test]
313 fn allows_to_execute_a_command() {
314 in_temporary_directory(|| {
315 run!(%"touch foo");
316 assert!(PathBuf::from("foo").exists());
317 })
318 }
319
320 mod errors {
321 use super::*;
322
323 mod panics_by_default {
324 use super::*;
325
326 #[test]
327 #[should_panic(expected = "cradle error: false:\n exited with exit code: 1")]
328 fn non_zero_exit_codes() {
329 run!("false");
330 }
331
332 #[test]
333 #[should_panic(expected = "cradle error: false:\n exited with exit code: 1")]
334 fn combine_panics_with_other_outputs() {
335 let StdoutTrimmed(_) = run_output!("false");
336 }
337
338 #[test]
339 #[should_panic(expected = "cradle error: false foo bar:\n exited with exit code: 1")]
340 fn includes_full_command_on_non_zero_exit_codes() {
341 run!(%"false foo bar");
342 }
343
344 #[test]
345 #[should_panic(expected = "exited with exit code: 42")]
346 fn other_exit_codes() {
347 run!(test_helper(), "exit code 42");
348 }
349
350 #[test]
351 #[should_panic(
352 expected = "cradle error: File not found error when executing 'does-not-exist'"
353 )]
354 fn executable_cannot_be_found() {
355 run!("does-not-exist");
356 }
357
358 #[test]
359 #[cfg(unix)]
360 #[should_panic(expected = "/file foo bar:\n Permission denied (os error 13)")]
361 fn includes_full_command_on_io_errors() {
362 let temp_dir = TempDir::new().unwrap();
363 let without_executable_bit = temp_dir.path().join("file");
364 fs::write(&without_executable_bit, "").unwrap();
365 run!(without_executable_bit, %"foo bar");
366 }
367
368 #[rustversion::since(1.46)]
369 #[test]
370 fn includes_source_location_of_run_run_call() {
371 let (Status(_), Stderr(stderr)) =
372 run_output!(test_executable("test_executables_panic"));
373 let expected = "src/test_executables/panic.rs:4:5";
374 assert!(
375 stderr.contains(expected),
376 "{:?}\n does not contain\n{:?}",
377 stderr,
378 expected
379 );
380 }
381
382 #[test]
383 #[should_panic(expected = "cradle error: no arguments given")]
384 fn no_executable() {
385 let vector: Vec<String> = Vec::new();
386 run!(vector);
387 }
388
389 #[test]
390 #[should_panic(expected = "invalid utf-8 written to stdout")]
391 fn invalid_utf8_stdout() {
392 let StdoutTrimmed(_) = run_output!(test_helper(), "invalid utf-8 stdout");
393 }
394
395 #[test]
396 #[cfg(not(windows))]
397 fn invalid_utf8_to_stdout_is_allowed_when_not_captured() {
398 run!(test_helper(), "invalid utf-8 stdout");
399 }
400 }
401
402 mod result_types {
403 use super::*;
404 use pretty_assertions::assert_eq;
405
406 #[test]
407 fn non_zero_exit_codes() {
408 let result: Result<(), Error> = run_result!("false");
409 assert_eq!(
410 result.unwrap_err().to_string(),
411 "false:\n exited with exit code: 1"
412 );
413 }
414
415 #[test]
416 fn no_errors() {
417 let result: Result<(), Error> = run_result!("true");
418 result.unwrap();
419 }
420
421 #[test]
422 fn combine_ok_with_other_outputs() {
423 let StdoutTrimmed(output) = run_result!(%"echo foo").unwrap();
424 assert_eq!(output, "foo".to_string());
425 }
426
427 #[test]
428 fn combine_err_with_other_outputs() {
429 let result: Result<StdoutTrimmed, Error> = run_result!("false");
430 assert_eq!(
431 result.unwrap_err().to_string(),
432 "false:\n exited with exit code: 1"
433 );
434 }
435
436 #[test]
437 fn includes_full_command_on_non_zero_exit_codes() {
438 let result: Result<(), Error> = run_result!(%"false foo bar");
439 assert_eq!(
440 result.unwrap_err().to_string(),
441 "false foo bar:\n exited with exit code: 1"
442 );
443 }
444
445 #[test]
446 fn includes_full_command_on_io_errors() {
447 in_temporary_directory(|| {
448 fs::write("without-executable-bit", "").unwrap();
449 let result: Result<(), Error> =
450 run_result!(%"./without-executable-bit foo bar");
451 assert_eq!(
452 result.unwrap_err().to_string(),
453 if cfg!(windows) {
454 "./without-executable-bit foo bar:\n %1 is not a valid Win32 application. (os error 193)"
455 } else {
456 "./without-executable-bit foo bar:\n Permission denied (os error 13)"
457 }
458 );
459 });
460 }
461
462 #[test]
463 fn other_exit_codes() {
464 let result: Result<(), Error> = run_result!(test_helper(), "exit code 42");
465 assert!(result
466 .unwrap_err()
467 .to_string()
468 .contains("exited with exit code: 42"));
469 }
470
471 #[test]
472 fn missing_executable_file_error_message() {
473 let result: Result<(), Error> = run_result!("does-not-exist");
474 assert_eq!(
475 result.unwrap_err().to_string(),
476 "File not found error when executing 'does-not-exist'"
477 );
478 }
479
480 #[test]
481 fn missing_executable_file_error_can_be_matched_against() {
482 let result: Result<(), Error> = run_result!("does-not-exist");
483 match result {
484 Err(Error::FileNotFound { executable, .. }) => {
485 assert_eq!(executable, "does-not-exist");
486 }
487 _ => panic!("should match Error::FileNotFound"),
488 }
489 }
490
491 #[test]
492 fn missing_executable_file_error_can_be_caused_by_relative_paths() {
493 let result: Result<(), Error> = run_result!("./does-not-exist");
494 match result {
495 Err(Error::FileNotFound { executable, .. }) => {
496 assert_eq!(executable, "./does-not-exist");
497 }
498 _ => panic!("should match Error::FileNotFound"),
499 }
500 }
501
502 #[test]
503 fn no_executable() {
504 let vector: Vec<String> = Vec::new();
505 let result: Result<(), Error> = run_result!(vector);
506 assert_eq!(result.unwrap_err().to_string(), "no arguments given");
507 }
508
509 #[test]
510 fn invalid_utf8_stdout() {
511 let test_helper = test_helper();
512 let result: Result<StdoutTrimmed, Error> =
513 run_result!(&test_helper, "invalid utf-8 stdout");
514 assert_eq!(
515 result.unwrap_err().to_string(),
516 format!(
517 "{} 'invalid utf-8 stdout':\n invalid utf-8 written to stdout",
518 test_helper.display()
519 )
520 );
521 }
522 }
523
524 mod whitespace_in_executable_note {
525 use super::*;
526 use pretty_assertions::assert_eq;
527 use unindent::Unindent;
528
529 #[test]
530 fn missing_executable_file_with_whitespace_includes_note() {
531 let result: Result<(), Error> = run_result!("does not exist");
532 let expected = "
533 File not found error when executing 'does not exist'
534 note: Given executable name 'does not exist' contains whitespace.
535 Did you mean to run 'does', with 'not' and 'exist' as arguments?
536 Consider using Split: https://docs.rs/cradle/latest/cradle/input/struct.Split.html
537 "
538 .unindent()
539 .trim()
540 .to_string();
541 assert_eq!(result.unwrap_err().to_string(), expected);
542 }
543
544 #[test]
545 fn single_argument() {
546 let result: Result<(), Error> = run_result!("foo bar");
547 let expected = "
548 File not found error when executing 'foo bar'
549 note: Given executable name 'foo bar' contains whitespace.
550 Did you mean to run 'foo', with 'bar' as the argument?
551 Consider using Split: https://docs.rs/cradle/latest/cradle/input/struct.Split.html
552 "
553 .unindent()
554 .trim()
555 .to_string();
556 assert_eq!(result.unwrap_err().to_string(), expected);
557 }
558 }
559 }
560
561 #[test]
562 fn allows_to_retrieve_stdout() {
563 let StdoutTrimmed(stdout) = run_output!(%"echo foo");
564 assert_eq!(stdout, "foo");
565 }
566
567 #[test]
568 fn command_and_argument_as_separate_ref_str() {
569 let StdoutTrimmed(stdout) = run_output!("echo", "foo");
570 assert_eq!(stdout, "foo");
571 }
572
573 #[test]
574 fn multiple_arguments_as_ref_str() {
575 let StdoutTrimmed(stdout) = run_output!("echo", "foo", "bar");
576 assert_eq!(stdout, "foo bar");
577 }
578
579 #[test]
580 fn arguments_can_be_given_as_references() {
581 let reference: &LogCommand = &LogCommand;
582 let executable: &String = &"echo".to_string();
583 let argument: &String = &"foo".to_string();
584 let StdoutTrimmed(stdout) = run_output!(reference, executable, argument);
585 assert_eq!(stdout, "foo");
586 }
587
588 mod sequences {
589 use super::*;
590
591 #[test]
592 fn allows_to_pass_in_arguments_as_a_vec_of_ref_str() {
593 let args: Vec<&str> = vec!["foo"];
594 let StdoutTrimmed(stdout) = run_output!("echo", args);
595 assert_eq!(stdout, "foo");
596 }
597
598 #[test]
599 fn vector_of_non_strings() {
600 let context = Context::test();
601 let log_commands: Vec<LogCommand> = vec![LogCommand];
602 let StdoutTrimmed(stdout) =
603 run_result_with_context(context.clone(), (log_commands, Split("echo foo")))
604 .unwrap();
605 assert_eq!(stdout, "foo");
606 assert_eq!(context.stderr(), "+ echo foo\n");
607 }
608
609 #[rustversion::since(1.51)]
610 #[test]
611 fn arrays_as_arguments() {
612 let args: [&str; 2] = ["echo", "foo"];
613 let StdoutTrimmed(stdout) = run_output!(args);
614 assert_eq!(stdout, "foo");
615 }
616
617 #[rustversion::since(1.51)]
618 #[test]
619 fn arrays_of_non_strings() {
620 let context = Context::test();
621 let log_commands: [LogCommand; 1] = [LogCommand];
622 let StdoutTrimmed(stdout) =
623 run_result_with_context(context.clone(), (log_commands, Split("echo foo")))
624 .unwrap();
625 assert_eq!(stdout, "foo");
626 assert_eq!(context.stderr(), "+ echo foo\n");
627 }
628
629 #[rustversion::since(1.51)]
630 #[test]
631 fn elements_in_arrays_are_not_split_by_whitespace() {
632 in_temporary_directory(|| {
633 let args: [&str; 1] = ["foo bar"];
634 run!("touch", args);
635 assert!(PathBuf::from("foo bar").exists());
636 });
637 }
638
639 #[rustversion::since(1.51)]
640 #[test]
641 fn array_refs_as_arguments() {
642 let args: &[&str; 2] = &["echo", "foo"];
643 let StdoutTrimmed(stdout) = run_output!(args);
644 assert_eq!(stdout, "foo");
645 }
646
647 #[rustversion::since(1.51)]
648 #[test]
649 fn elements_in_array_refs_are_not_split_by_whitespace() {
650 in_temporary_directory(|| {
651 let args: &[&str; 1] = &["foo bar"];
652 run!("touch", args);
653 assert!(PathBuf::from("foo bar").exists());
654 });
655 }
656
657 #[test]
658 fn slices_as_arguments() {
659 let args: &[&str] = &["echo", "foo"];
660 let StdoutTrimmed(stdout) = run_output!(args);
661 assert_eq!(stdout, "foo");
662 }
663
664 #[test]
665 fn slices_of_non_strings() {
666 let context = Context::test();
667 let log_commands: &[LogCommand] = &[LogCommand];
668 let StdoutTrimmed(stdout) =
669 run_result_with_context(context.clone(), (log_commands, Split("echo foo")))
670 .unwrap();
671 assert_eq!(stdout, "foo");
672 assert_eq!(context.stderr(), "+ echo foo\n");
673 }
674
675 #[test]
676 fn elements_in_slices_are_not_split_by_whitespace() {
677 in_temporary_directory(|| {
678 let args: &[&str] = &["foo bar"];
679 run!("touch", args);
680 assert!(PathBuf::from("foo bar").exists());
681 });
682 }
683
684 #[test]
685 fn vector_of_vectors() {
686 let StdoutTrimmed(output) = run_output!(vec![vec!["echo"], vec!["foo", "bar"]]);
687 assert_eq!(output, "foo bar");
688 }
689 }
690
691 mod strings {
692 use super::*;
693
694 #[test]
695 fn works_for_string() {
696 let command: String = "true".to_string();
697 run!(command);
698 }
699
700 #[test]
701 fn multiple_strings() {
702 let command: String = "echo".to_string();
703 let argument: String = "foo".to_string();
704 let StdoutTrimmed(output) = run_output!(command, argument);
705 assert_eq!(output, "foo");
706 }
707
708 #[test]
709 fn mix_ref_str_and_string() {
710 let argument: String = "foo".to_string();
711 let StdoutTrimmed(output) = run_output!("echo", argument);
712 assert_eq!(output, "foo");
713 }
714
715 #[test]
716 fn does_not_split_strings_in_vectors() {
717 in_temporary_directory(|| {
718 let argument: Vec<String> = vec!["filename with spaces".to_string()];
719 run!("touch", argument);
720 assert!(PathBuf::from("filename with spaces").exists());
721 });
722 }
723 }
724
725 mod os_strings {
726 use super::*;
727
728 #[test]
729 fn works_for_os_string() {
730 run!(OsString::from("true"));
731 }
732
733 #[test]
734 fn works_for_os_str() {
735 run!(OsStr::new("true"));
736 }
737 }
738
739 mod stdout {
740 use super::*;
741 use std::{thread, time::Duration};
742
743 #[test]
744 fn relays_stdout_by_default() {
745 let context = Context::test();
746 run_result_with_context_unit(context.clone(), Split("echo foo")).unwrap();
747 assert_eq!(context.stdout(), "foo\n");
748 }
749
750 #[test]
751 fn relays_stdout_for_non_zero_exit_codes() {
752 let context = Context::test();
753 let _ = run_result_with_context_unit(
754 context.clone(),
755 (test_helper(), "output foo and exit with 42"),
756 );
757 assert_eq!(context.stdout(), "foo\n");
758 }
759
760 #[test]
761 fn streams_stdout() {
762 in_temporary_directory(|| {
763 let context = Context::test();
764 let context_clone = context.clone();
765 let thread = thread::spawn(|| {
766 run_result_with_context_unit(
767 context_clone,
768 (test_helper(), "stream chunk then wait for file"),
769 )
770 .unwrap();
771 });
772 while (context.stdout()) != "foo\n" {
773 thread::sleep(Duration::from_secs_f32(0.05));
774 }
775 run!(%"touch file");
776 thread.join().unwrap();
777 });
778 }
779
780 #[test]
781 fn does_not_relay_stdout_when_collecting_into_string() {
782 let context = Context::test();
783 let StdoutTrimmed(_) =
784 run_result_with_context(context.clone(), Split("echo foo")).unwrap();
785 assert_eq!(context.stdout(), "");
786 }
787
788 #[test]
789 fn does_not_relay_stdout_when_collecting_into_result_of_string() {
790 let context = Context::test();
791 let _: Result<StdoutTrimmed, Error> =
792 run_result_with_context(context.clone(), Split("echo foo"));
793 assert_eq!(context.stdout(), "");
794 }
795 }
796
797 mod stderr {
798 use super::*;
799 use pretty_assertions::assert_eq;
800 use std::{thread, time::Duration};
801
802 #[test]
803 fn relays_stderr_by_default() {
804 let context = Context::test();
805 run_result_with_context_unit(context.clone(), (test_helper(), "write to stderr"))
806 .unwrap();
807 assert_eq!(context.stderr(), "foo\n");
808 }
809
810 #[test]
811 fn relays_stderr_for_non_zero_exit_codes() {
812 let context = Context::test();
813 let _: Result<(), Error> = run_result_with_context(
814 context.clone(),
815 (test_helper(), "write to stderr and exit with 42"),
816 );
817 assert_eq!(context.stderr(), "foo\n");
818 }
819
820 #[test]
821 fn streams_stderr() {
822 in_temporary_directory(|| {
823 let context = Context::test();
824 let context_clone = context.clone();
825 let thread = thread::spawn(|| {
826 run_result_with_context_unit(
827 context_clone,
828 (test_helper(), "stream chunk to stderr then wait for file"),
829 )
830 .unwrap();
831 });
832 loop {
833 let expected = "foo\n";
834 let stderr = context.stderr();
835 if stderr == expected {
836 break;
837 }
838 assert!(
839 stderr.len() <= expected.len(),
840 "expected: {}, got: {}",
841 expected,
842 stderr
843 );
844 thread::sleep(Duration::from_secs_f32(0.05));
845 }
846 run!(%"touch file");
847 thread.join().unwrap();
848 });
849 }
850
851 #[test]
852 fn capture_stderr() {
853 let Stderr(stderr) = run_output!(test_helper(), "write to stderr");
854 assert_eq!(stderr, "foo\n");
855 }
856
857 #[test]
858 fn assumes_stderr_is_utf_8() {
859 let result: Result<Stderr, Error> = run_result!(test_helper(), "invalid utf-8 stderr");
860 assert_eq!(
861 result.unwrap_err().to_string(),
862 format!(
863 "{} 'invalid utf-8 stderr':\n invalid utf-8 written to stderr",
864 test_helper().display(),
865 )
866 );
867 }
868
869 #[test]
870 #[cfg(not(windows))]
871 fn does_allow_invalid_utf_8_to_stderr_when_not_captured() {
872 run!(test_helper(), "invalid utf-8 stderr");
873 }
874
875 #[test]
876 fn does_not_relay_stderr_when_catpuring() {
877 let context = Context::test();
878 let Stderr(_) =
879 run_result_with_context(context.clone(), (test_helper(), "write to stderr"))
880 .unwrap();
881 assert_eq!(context.stderr(), "");
882 }
883 }
884
885 mod log_commands {
886 use super::*;
887
888 #[test]
889 fn logs_simple_commands() {
890 let context = Context::test();
891 run_result_with_context_unit(context.clone(), (LogCommand, "true")).unwrap();
892 assert_eq!(context.stderr(), "+ true\n");
893 }
894
895 #[test]
896 fn logs_commands_with_arguments() {
897 let context = Context::test();
898 run_result_with_context_unit(context.clone(), (LogCommand, Split("echo foo"))).unwrap();
899 assert_eq!(context.stderr(), "+ echo foo\n");
900 }
901
902 #[test]
903 fn quotes_arguments_with_spaces() {
904 let context = Context::test();
905 run_result_with_context_unit(context.clone(), (LogCommand, "echo", "foo bar")).unwrap();
906 assert_eq!(context.stderr(), "+ echo 'foo bar'\n");
907 }
908
909 #[test]
910 fn quotes_empty_arguments() {
911 let context = Context::test();
912 run_result_with_context_unit(context.clone(), (LogCommand, "echo", "")).unwrap();
913 assert_eq!(context.stderr(), "+ echo ''\n");
914 }
915
916 #[test]
917 #[cfg(unix)]
918 fn arguments_with_invalid_utf8_will_be_logged_with_lossy_conversion() {
919 use std::{ffi::OsStr, os::unix::prelude::OsStrExt, path::Path};
920 let context = Context::test();
921 let argument_with_invalid_utf8: &OsStr =
922 OsStrExt::from_bytes(&[102, 111, 111, 0x80, 98, 97, 114]);
923 let argument_with_invalid_utf8: &Path = argument_with_invalid_utf8.as_ref();
924 run_result_with_context_unit(
925 context.clone(),
926 (LogCommand, "echo", argument_with_invalid_utf8),
927 )
928 .unwrap();
929 assert_eq!(context.stderr(), "+ echo foo�bar\n");
930 }
931 }
932
933 mod exit_status {
934 use super::*;
935
936 #[test]
937 fn zero() {
938 let Status(exit_status) = run_output!("true");
939 assert!(exit_status.success());
940 }
941
942 #[test]
943 fn one() {
944 let Status(exit_status) = run_output!("false");
945 assert!(!exit_status.success());
946 }
947
948 #[test]
949 fn forty_two() {
950 let Status(exit_status) = run_output!(test_helper(), "exit code 42");
951 assert!(!exit_status.success());
952 assert_eq!(exit_status.code(), Some(42));
953 }
954
955 #[test]
956 fn failing_commands_return_oks_when_exit_status_is_captured() {
957 let Status(exit_status) = run_result!("false").unwrap();
958 assert!(!exit_status.success());
959 }
960 }
961
962 mod bool_output {
963 use super::*;
964
965 #[test]
966 fn success_exit_status_is_true() {
967 assert!(run_output!("true"));
968 }
969
970 #[test]
971 fn failure_exit_status_is_false() {
972 assert!(!run_output!("false"));
973 }
974
975 #[test]
976 #[should_panic]
977 fn io_error_panics() {
978 assert!(run_output!("/"));
979 }
980 }
981
982 mod tuple_inputs {
983 use super::*;
984 use pretty_assertions::assert_eq;
985
986 #[test]
987 fn two_tuple() {
988 let StdoutTrimmed(output) = run_output!(("echo", "foo"));
989 assert_eq!(output, "foo");
990 }
991
992 #[test]
993 fn three_tuples() {
994 let StdoutTrimmed(output) = run_output!(("echo", "foo", "bar"));
995 assert_eq!(output, "foo bar");
996 }
997
998 #[test]
999 fn nested_tuples() {
1000 let StdoutTrimmed(output) = run_output!(("echo", ("foo", "bar")));
1001 assert_eq!(output, "foo bar");
1002 }
1003
1004 #[test]
1005 fn unit_input() {
1006 let StdoutTrimmed(output) = run_output!(("echo", ()));
1007 assert_eq!(output, "");
1008 }
1009 }
1010
1011 mod tuple_outputs {
1012 use super::*;
1013
1014 #[test]
1015 fn two_tuple_1() {
1016 let (StdoutTrimmed(output), Status(exit_status)) =
1017 run_output!(test_helper(), "output foo and exit with 42");
1018 assert_eq!(output, "foo");
1019 assert_eq!(exit_status.code(), Some(42));
1020 }
1021
1022 #[test]
1023 fn two_tuple_2() {
1024 let (Status(exit_status), StdoutTrimmed(output)) =
1025 run_output!(test_helper(), "output foo and exit with 42");
1026 assert_eq!(output, "foo");
1027 assert_eq!(exit_status.code(), Some(42));
1028 }
1029
1030 #[test]
1031 fn result_of_tuple() {
1032 let (StdoutTrimmed(output), Status(exit_status)) = run_result!(%"echo foo").unwrap();
1033 assert_eq!(output, "foo");
1034 assert!(exit_status.success());
1035 }
1036
1037 #[test]
1038 fn result_of_tuple_when_erroring() {
1039 let (StdoutTrimmed(output), Status(exit_status)) = run_result!("false").unwrap();
1040 assert_eq!(output, "");
1041 assert_eq!(exit_status.code(), Some(1));
1042 }
1043
1044 #[test]
1045 fn three_tuples() {
1046 let (Stderr(stderr), StdoutTrimmed(stdout), Status(exit_status)) =
1047 run_output!(%"echo foo");
1048 assert_eq!(stderr, "");
1049 assert_eq!(stdout, "foo");
1050 assert_eq!(exit_status.code(), Some(0));
1051 }
1052
1053 #[test]
1054 fn capturing_stdout_on_errors() {
1055 let (StdoutTrimmed(output), Status(exit_status)) =
1056 run_output!(test_helper(), "output foo and exit with 42");
1057 assert!(!exit_status.success());
1058 assert_eq!(output, "foo");
1059 }
1060
1061 #[test]
1062 fn capturing_stderr_on_errors() {
1063 let (Stderr(output), Status(exit_status)) =
1064 run_output!(test_helper(), "write to stderr and exit with 42");
1065 assert!(!exit_status.success());
1066 assert_eq!(output, "foo\n");
1067 }
1068 }
1069
1070 mod current_dir {
1071 use super::*;
1072 use std::path::Path;
1073
1074 #[test]
1075 fn sets_the_working_directory() {
1076 in_temporary_directory(|| {
1077 fs::create_dir("dir").unwrap();
1078 fs::write("dir/file", "foo").unwrap();
1079 fs::write("file", "wrong file").unwrap();
1080 let StdoutUntrimmed(output) = run_output!(%"cat file", CurrentDir("dir"));
1081 assert_eq!(output, "foo");
1082 });
1083 }
1084
1085 #[test]
1086 fn works_for_other_types() {
1087 in_temporary_directory(|| {
1088 fs::create_dir("dir").unwrap();
1089 let dir: String = "dir".to_string();
1090 run!("true", CurrentDir(dir));
1091 let dir: PathBuf = PathBuf::from("dir");
1092 run!("true", CurrentDir(dir));
1093 let dir: &Path = Path::new("dir");
1094 run!("true", CurrentDir(dir));
1095 });
1096 }
1097 }
1098
1099 mod capturing_stdout {
1100 use super::*;
1101
1102 mod trimmed {
1103 use super::*;
1104
1105 #[test]
1106 fn trims_trailing_whitespace() {
1107 let StdoutTrimmed(output) = run_output!(%"echo foo");
1108 assert_eq!(output, "foo");
1109 }
1110
1111 #[test]
1112 fn trims_leading_whitespace() {
1113 let StdoutTrimmed(output) = run_output!(%"echo -n", " foo");
1114 assert_eq!(output, "foo");
1115 }
1116
1117 #[test]
1118 fn does_not_remove_whitespace_within_output() {
1119 let StdoutTrimmed(output) = run_output!(%"echo -n", "foo bar");
1120 assert_eq!(output, "foo bar");
1121 }
1122
1123 #[test]
1124 fn does_not_modify_output_without_whitespace() {
1125 let StdoutTrimmed(output) = run_output!(%"echo -n", "foo");
1126 assert_eq!(output, "foo");
1127 }
1128
1129 #[test]
1130 fn does_not_relay_stdout() {
1131 let context = Context::test();
1132 let StdoutTrimmed(_) =
1133 run_result_with_context(context.clone(), Split("echo foo")).unwrap();
1134 assert_eq!(context.stdout(), "");
1135 }
1136 }
1137
1138 mod untrimmed {
1139 use super::*;
1140
1141 #[test]
1142 fn does_not_trim_trailing_newline() {
1143 let StdoutUntrimmed(output) = run_output!(%"echo foo");
1144 assert_eq!(output, "foo\n");
1145 }
1146
1147 #[test]
1148 fn does_not_trim_leading_whitespace() {
1149 let StdoutUntrimmed(output) = run_output!(%"echo -n", " foo");
1150 assert_eq!(output, " foo");
1151 }
1152
1153 #[test]
1154 fn does_not_relay_stdout() {
1155 let context = Context::test();
1156 let StdoutUntrimmed(_) =
1157 run_result_with_context(context.clone(), Split("echo foo")).unwrap();
1158 assert_eq!(context.stdout(), "");
1159 }
1160 }
1161 }
1162
1163 mod split {
1164 use super::*;
1165
1166 #[test]
1167 fn splits_words_by_whitespace() {
1168 let StdoutTrimmed(output) = run_output!(Split("echo foo"));
1169 assert_eq!(output, "foo");
1170 }
1171
1172 #[test]
1173 fn skips_multiple_whitespace_characters() {
1174 let StdoutUntrimmed(output) = run_output!("echo", Split("foo bar"));
1175 assert_eq!(output, "foo bar\n");
1176 }
1177
1178 #[test]
1179 fn trims_leading_whitespace() {
1180 let StdoutTrimmed(output) = run_output!(Split(" echo foo"));
1181 assert_eq!(output, "foo");
1182 }
1183
1184 #[test]
1185 fn trims_trailing_whitespace() {
1186 let StdoutUntrimmed(output) = run_output!("echo", Split("foo "));
1187 assert_eq!(output, "foo\n");
1188 }
1189
1190 mod percent_sign {
1191 use super::*;
1192
1193 #[test]
1194 fn splits_words() {
1195 let StdoutUntrimmed(output) = run_output!(%"echo foo");
1196 assert_eq!(output, "foo\n");
1197 }
1198
1199 #[test]
1200 fn works_for_later_arguments() {
1201 let StdoutUntrimmed(output) = run_output!("echo", %"foo\tbar");
1202 assert_eq!(output, "foo bar\n");
1203 }
1204
1205 #[test]
1206 fn for_first_of_multiple_arguments() {
1207 let StdoutUntrimmed(output) = run_output!(%"echo foo", "bar");
1208 assert_eq!(output, "foo bar\n");
1209 }
1210
1211 #[test]
1212 fn non_literals() {
1213 let command = "echo foo";
1214 let StdoutUntrimmed(output) = run_output!(%command);
1215 assert_eq!(output, "foo\n");
1216 }
1217
1218 #[test]
1219 fn in_run() {
1220 run!(%"echo foo");
1221 }
1222
1223 #[test]
1224 fn in_run_result() {
1225 let StdoutTrimmed(_) = run_result!(%"echo foo").unwrap();
1226 }
1227 }
1228 }
1229
1230 mod splitting_with_library_functions {
1231 use super::*;
1232
1233 #[test]
1234 fn allow_to_use_split() {
1235 let StdoutTrimmed(output) = run_output!("echo foo".split(' '));
1236 assert_eq!(output, "foo");
1237 }
1238
1239 #[test]
1240 fn split_whitespace() {
1241 let StdoutTrimmed(output) = run_output!("echo foo".split_whitespace());
1242 assert_eq!(output, "foo");
1243 }
1244
1245 #[test]
1246 fn split_ascii_whitespace() {
1247 let StdoutTrimmed(output) = run_output!("echo foo".split_ascii_whitespace());
1248 assert_eq!(output, "foo");
1249 }
1250 }
1251
1252 mod paths {
1253 use super::*;
1254 use pretty_assertions::assert_eq;
1255 use std::path::Path;
1256
1257 fn write_test_script() -> PathBuf {
1258 if cfg!(unix) {
1259 let file = PathBuf::from("./test-script");
1260 let script = "#!/usr/bin/env bash\necho test-output\n";
1261 fs::write(&file, script).unwrap();
1262 run!(%"chmod +x test-script");
1263 file
1264 } else {
1265 let file = PathBuf::from("./test-script.bat");
1266 let script = "@echo test-output\n";
1267 fs::write(&file, script).unwrap();
1268 file
1269 }
1270 }
1271
1272 #[test]
1273 fn ref_path_as_argument() {
1274 in_temporary_directory(|| {
1275 let file: &Path = Path::new("file");
1276 fs::write(file, "test-contents").unwrap();
1277 let StdoutUntrimmed(output) = run_output!("cat", file);
1278 assert_eq!(output, "test-contents");
1279 })
1280 }
1281
1282 #[test]
1283 fn ref_path_as_executable() {
1284 in_temporary_directory(|| {
1285 let file: &Path = &write_test_script();
1286 let StdoutTrimmed(output) = run_output!(file);
1287 assert_eq!(output, "test-output");
1288 })
1289 }
1290
1291 #[test]
1292 fn path_buf_as_argument() {
1293 in_temporary_directory(|| {
1294 let file: PathBuf = PathBuf::from("file");
1295 fs::write(&file, "test-contents").unwrap();
1296 let StdoutUntrimmed(output) = run_output!("cat", file);
1297 assert_eq!(output, "test-contents");
1298 })
1299 }
1300
1301 #[test]
1302 fn path_buf_as_executable() {
1303 in_temporary_directory(|| {
1304 let file: PathBuf = write_test_script();
1305 let StdoutTrimmed(output) = run_output!(file);
1306 assert_eq!(output, "test-output");
1307 })
1308 }
1309 }
1310
1311 mod stdin {
1312 use super::*;
1313
1314 #[test]
1315 fn allows_to_pass_in_strings_as_stdin() {
1316 let StdoutUntrimmed(output) = run_output!(test_helper(), "reverse", Stdin("foo"));
1317 assert_eq!(output, "oof");
1318 }
1319
1320 #[test]
1321 fn allows_passing_in_u8_slices_as_stdin() {
1322 let StdoutUntrimmed(output) = run_output!(test_helper(), "reverse", Stdin(&[0, 1, 2]));
1323 assert_eq!(output, "\x02\x01\x00");
1324 }
1325
1326 #[test]
1327 #[cfg(unix)]
1328 fn stdin_is_closed_by_default() {
1329 let StdoutTrimmed(output) = run_output!(test_helper(), "wait until stdin is closed");
1330 assert_eq!(output, "stdin is closed");
1331 }
1332
1333 #[test]
1334 fn writing_too_many_bytes_into_a_non_reading_child_may_error() {
1335 let big_string = String::from_utf8(vec![b'a'; 2_usize.pow(16) + 1]).unwrap();
1336 let result: Result<(), crate::Error> = run_result!("true", Stdin(big_string));
1337 let message = result.unwrap_err().to_string();
1338 assert!(if cfg!(unix) {
1339 message == "true:\n Broken pipe (os error 32)"
1340 } else {
1341 [
1342 "true:\n The pipe is being closed. (os error 232)",
1343 "true:\n The pipe has been ended. (os error 109)",
1344 ]
1345 .contains(&message.as_str())
1346 });
1347 }
1348
1349 #[test]
1350 fn multiple_stdin_arguments_are_all_passed_into_the_child_process() {
1351 let StdoutUntrimmed(output) =
1352 run_output!(test_helper(), "reverse", Stdin("foo"), Stdin("bar"));
1353 assert_eq!(output, "raboof");
1354 }
1355
1356 #[test]
1357 fn works_for_owned_strings() {
1358 let argument: String = "foo".to_string();
1359 let StdoutUntrimmed(output) = run_output!(test_helper(), "reverse", Stdin(argument));
1360 assert_eq!(output, "oof");
1361 }
1362 }
1363
1364 mod invocation_syntax {
1365 use super::*;
1366
1367 #[test]
1368 fn trailing_comma_is_accepted_after_normal_argument() {
1369 run!("echo", "foo",);
1370 let StdoutUntrimmed(_) = run_output!("echo", "foo",);
1371 let _result: Result<(), Error> = run_result!("echo", "foo",);
1372 }
1373
1374 #[test]
1375 fn trailing_comma_is_accepted_after_split_argument() {
1376 run!("echo", %"foo",);
1377 let StdoutUntrimmed(_) = run_output!("echo", %"foo",);
1378 let _result: Result<(), Error> = run_result!("echo", %"foo",);
1379 }
1380 }
1381
1382 mod environment_variables {
1383 use super::*;
1384 use pretty_assertions::assert_eq;
1385 use std::env;
1386
1387 #[test]
1388 fn allows_to_add_variables() {
1389 let StdoutTrimmed(output) = run_output!(
1390 test_helper(),
1391 %"echo FOO",
1392 Env("FOO", "bar")
1393 );
1394 assert_eq!(output, "bar");
1395 }
1396
1397 #[test]
1398 fn works_for_multiple_variables() {
1399 let StdoutUntrimmed(output) = run_output!(
1400 test_helper(),
1401 %"echo FOO BAR",
1402 Env("FOO", "a"),
1403 Env("BAR", "b")
1404 );
1405 assert_eq!(output, "a\nb\n");
1406 }
1407
1408 fn find_unused_environment_variable() -> String {
1409 let mut i = 0;
1410 loop {
1411 let key = format!("CRADLE_TEST_VARIABLE_{}", i);
1412 if env::var_os(&key).is_none() {
1413 break key;
1414 }
1415 i += 1;
1416 }
1417 }
1418
1419 #[test]
1420 fn child_processes_inherit_the_environment() {
1421 let unused_key = find_unused_environment_variable();
1422 env::set_var(&unused_key, "foo");
1423 let StdoutTrimmed(output) = run_output!(test_helper(), "echo", unused_key);
1424 assert_eq!(output, "foo");
1425 }
1426
1427 #[test]
1428 fn overwrites_existing_parent_variables() {
1429 let unused_key = find_unused_environment_variable();
1430 env::set_var(&unused_key, "foo");
1431 let StdoutTrimmed(output) =
1432 run_output!(test_helper(), "echo", &unused_key, Env(&unused_key, "bar"));
1433 assert_eq!(output, "bar");
1434 }
1435
1436 #[test]
1437 fn variables_are_overwritten_by_subsequent_variables_with_the_same_name() {
1438 let StdoutTrimmed(output) = run_output!(
1439 test_helper(),
1440 "echo",
1441 "FOO",
1442 Env("FOO", "a"),
1443 Env("FOO", "b"),
1444 );
1445 assert_eq!(output, "b");
1446 }
1447
1448 #[test]
1449 fn variables_can_be_set_to_the_empty_string() {
1450 let StdoutUntrimmed(output) =
1451 run_output!(test_helper(), "echo", "FOO", Env("FOO", ""),);
1452 assert_eq!(output, "empty variable: FOO\n");
1453 }
1454 }
1455
1456 mod run_interface {
1457 use super::*;
1458 use std::path::Path;
1459
1460 #[test]
1461 fn allows_to_run_commands_with_dot_run() {
1462 let StdoutTrimmed(output) = Split("echo foo").run_output();
1463 assert_eq!(output, "foo");
1464 }
1465
1466 #[test]
1467 fn allows_to_bundle_arguments_up_in_tuples() {
1468 let StdoutTrimmed(output) = ("echo", "foo").run_output();
1469 assert_eq!(output, "foo");
1470 }
1471
1472 #[test]
1473 fn works_for_different_output_types() {
1474 let Status(status) = "false".run_output();
1475 assert!(!status.success());
1476 }
1477
1478 #[test]
1479 fn run() {
1480 in_temporary_directory(|| {
1481 ("touch", "foo").run();
1482 assert!(Path::new("foo").exists());
1483 });
1484 }
1485
1486 #[test]
1487 fn run_result() {
1488 let StdoutTrimmed(output) = ("echo", "foo").run_result().unwrap();
1489 assert_eq!(output, "foo");
1490 let result: Result<(), Error> = "does-not-exist".run_result();
1491 match result {
1492 Err(Error::FileNotFound { .. }) => {}
1493 _ => panic!("should match Error::FileNotFound"),
1494 }
1495 }
1496 }
1497}