1use std::borrow::Cow;
4use std::error::Error;
5use std::fmt;
6use std::process;
7use std::str;
8
9#[cfg(feature = "color")]
10use anstream::panic;
11use predicates::str::PredicateStrExt;
12use predicates_tree::CaseTreeExt;
13
14use crate::output::DebugBytes;
15use crate::output::output_fmt;
16
17pub trait OutputAssertExt {
34 #[must_use]
51 fn assert(self) -> Assert;
52}
53
54impl OutputAssertExt for process::Output {
55 fn assert(self) -> Assert {
56 Assert::new(self)
57 }
58}
59
60impl OutputAssertExt for &mut process::Command {
61 fn assert(self) -> Assert {
62 let output = match self.output() {
63 Ok(output) => output,
64 Err(err) => {
65 panic!("Failed to spawn {self:?}: {err}");
66 }
67 };
68 Assert::new(output).append_context("command", format!("{self:?}"))
69 }
70}
71
72pub struct Assert {
91 output: process::Output,
92 context: Vec<(&'static str, Box<dyn fmt::Display + Send + Sync>)>,
93}
94
95impl Assert {
96 #[must_use]
100 pub fn new(output: process::Output) -> Self {
101 Self {
102 output,
103 context: vec![],
104 }
105 }
106
107 fn into_error(self, reason: AssertReason) -> AssertError {
108 AssertError {
109 assert: self,
110 reason,
111 }
112 }
113
114 #[must_use]
130 pub fn append_context<D>(mut self, name: &'static str, context: D) -> Self
131 where
132 D: fmt::Display + Send + Sync + 'static,
133 {
134 self.context.push((name, Box::new(context)));
135 self
136 }
137
138 pub fn get_output(&self) -> &process::Output {
142 &self.output
143 }
144
145 #[track_caller]
160 pub fn success(self) -> Self {
161 self.try_success().unwrap_or_else(AssertError::panic)
162 }
163
164 pub fn try_success(self) -> AssertResult {
166 if !self.output.status.success() {
167 let actual_code = self.output.status.code();
168 return Err(self.into_error(AssertReason::UnexpectedFailure { actual_code }));
169 }
170 Ok(self)
171 }
172
173 #[track_caller]
189 pub fn failure(self) -> Self {
190 self.try_failure().unwrap_or_else(AssertError::panic)
191 }
192
193 pub fn try_failure(self) -> AssertResult {
195 if self.output.status.success() {
196 return Err(self.into_error(AssertReason::UnexpectedSuccess));
197 }
198 Ok(self)
199 }
200
201 #[track_caller]
203 pub fn interrupted(self) -> Self {
204 self.try_interrupted().unwrap_or_else(AssertError::panic)
205 }
206
207 pub fn try_interrupted(self) -> AssertResult {
209 if self.output.status.code().is_some() {
210 return Err(self.into_error(AssertReason::UnexpectedCompletion));
211 }
212 Ok(self)
213 }
214
215 #[track_caller]
264 pub fn code<I, P>(self, pred: I) -> Self
265 where
266 I: IntoCodePredicate<P>,
267 P: predicates_core::Predicate<i32>,
268 {
269 self.try_code(pred).unwrap_or_else(AssertError::panic)
270 }
271
272 pub fn try_code<I, P>(self, pred: I) -> AssertResult
274 where
275 I: IntoCodePredicate<P>,
276 P: predicates_core::Predicate<i32>,
277 {
278 self.code_impl(&pred.into_code())
279 }
280
281 fn code_impl(self, pred: &dyn predicates_core::Predicate<i32>) -> AssertResult {
282 let actual_code = if let Some(actual_code) = self.output.status.code() {
283 actual_code
284 } else {
285 return Err(self.into_error(AssertReason::CommandInterrupted));
286 };
287 if let Some(case) = pred.find_case(false, &actual_code) {
288 return Err(self.into_error(AssertReason::UnexpectedReturnCode {
289 case_tree: CaseTree(case.tree()),
290 }));
291 }
292 Ok(self)
293 }
294
295 #[track_caller]
362 pub fn stdout<I, P>(self, pred: I) -> Self
363 where
364 I: IntoOutputPredicate<P>,
365 P: predicates_core::Predicate<[u8]>,
366 {
367 self.try_stdout(pred).unwrap_or_else(AssertError::panic)
368 }
369
370 pub fn try_stdout<I, P>(self, pred: I) -> AssertResult
372 where
373 I: IntoOutputPredicate<P>,
374 P: predicates_core::Predicate<[u8]>,
375 {
376 self.stdout_impl(&pred.into_output())
377 }
378
379 fn stdout_impl(self, pred: &dyn predicates_core::Predicate<[u8]>) -> AssertResult {
380 {
381 let actual = &self.output.stdout;
382 if let Some(case) = pred.find_case(false, actual) {
383 return Err(self.into_error(AssertReason::UnexpectedStdout {
384 case_tree: CaseTree(case.tree()),
385 }));
386 }
387 }
388 Ok(self)
389 }
390
391 #[track_caller]
458 pub fn stderr<I, P>(self, pred: I) -> Self
459 where
460 I: IntoOutputPredicate<P>,
461 P: predicates_core::Predicate<[u8]>,
462 {
463 self.try_stderr(pred).unwrap_or_else(AssertError::panic)
464 }
465
466 pub fn try_stderr<I, P>(self, pred: I) -> AssertResult
468 where
469 I: IntoOutputPredicate<P>,
470 P: predicates_core::Predicate<[u8]>,
471 {
472 self.stderr_impl(&pred.into_output())
473 }
474
475 fn stderr_impl(self, pred: &dyn predicates_core::Predicate<[u8]>) -> AssertResult {
476 {
477 let actual = &self.output.stderr;
478 if let Some(case) = pred.find_case(false, actual) {
479 return Err(self.into_error(AssertReason::UnexpectedStderr {
480 case_tree: CaseTree(case.tree()),
481 }));
482 }
483 }
484 Ok(self)
485 }
486}
487
488impl fmt::Display for Assert {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 let palette = crate::Palette::color();
491 for (name, context) in &self.context {
492 writeln!(f, "{:#}=`{:#}`", palette.key(name), palette.value(context))?;
493 }
494 output_fmt(&self.output, f)
495 }
496}
497
498impl fmt::Debug for Assert {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 f.debug_struct("Assert")
501 .field("output", &self.output)
502 .finish()
503 }
504}
505
506pub trait IntoCodePredicate<P>
531where
532 P: predicates_core::Predicate<i32>,
533{
534 type Predicate;
536
537 fn into_code(self) -> P;
539}
540
541impl<P> IntoCodePredicate<P> for P
542where
543 P: predicates_core::Predicate<i32>,
544{
545 type Predicate = P;
546
547 fn into_code(self) -> Self::Predicate {
548 self
549 }
550}
551
552#[derive(Debug)]
569pub struct EqCodePredicate(predicates::ord::EqPredicate<i32>);
570
571impl EqCodePredicate {
572 pub(crate) fn new(value: i32) -> Self {
573 let pred = predicates::ord::eq(value);
574 EqCodePredicate(pred)
575 }
576}
577
578impl predicates_core::reflection::PredicateReflection for EqCodePredicate {
579 fn parameters<'a>(
580 &'a self,
581 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
582 self.0.parameters()
583 }
584
585 fn children<'a>(
587 &'a self,
588 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
589 self.0.children()
590 }
591}
592
593impl predicates_core::Predicate<i32> for EqCodePredicate {
594 fn eval(&self, item: &i32) -> bool {
595 self.0.eval(item)
596 }
597
598 fn find_case<'a>(
599 &'a self,
600 expected: bool,
601 variable: &i32,
602 ) -> Option<predicates_core::reflection::Case<'a>> {
603 self.0.find_case(expected, variable)
604 }
605}
606
607impl fmt::Display for EqCodePredicate {
608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609 self.0.fmt(f)
610 }
611}
612
613impl IntoCodePredicate<EqCodePredicate> for i32 {
614 type Predicate = EqCodePredicate;
615
616 fn into_code(self) -> Self::Predicate {
617 Self::Predicate::new(self)
618 }
619}
620
621#[derive(Debug)]
638pub struct InCodePredicate(predicates::iter::InPredicate<i32>);
639
640impl InCodePredicate {
641 pub(crate) fn new<I: IntoIterator<Item = i32>>(value: I) -> Self {
642 let pred = predicates::iter::in_iter(value);
643 InCodePredicate(pred)
644 }
645}
646
647impl predicates_core::reflection::PredicateReflection for InCodePredicate {
648 fn parameters<'a>(
649 &'a self,
650 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
651 self.0.parameters()
652 }
653
654 fn children<'a>(
656 &'a self,
657 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
658 self.0.children()
659 }
660}
661
662impl predicates_core::Predicate<i32> for InCodePredicate {
663 fn eval(&self, item: &i32) -> bool {
664 self.0.eval(item)
665 }
666
667 fn find_case<'a>(
668 &'a self,
669 expected: bool,
670 variable: &i32,
671 ) -> Option<predicates_core::reflection::Case<'a>> {
672 self.0.find_case(expected, variable)
673 }
674}
675
676impl fmt::Display for InCodePredicate {
677 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678 self.0.fmt(f)
679 }
680}
681
682impl IntoCodePredicate<InCodePredicate> for Vec<i32> {
683 type Predicate = InCodePredicate;
684
685 fn into_code(self) -> Self::Predicate {
686 Self::Predicate::new(self)
687 }
688}
689
690impl IntoCodePredicate<InCodePredicate> for &'static [i32] {
691 type Predicate = InCodePredicate;
692
693 fn into_code(self) -> Self::Predicate {
694 Self::Predicate::new(self.iter().cloned())
695 }
696}
697
698pub trait IntoOutputPredicate<P>
725where
726 P: predicates_core::Predicate<[u8]>,
727{
728 type Predicate;
730
731 fn into_output(self) -> P;
733}
734
735impl<P> IntoOutputPredicate<P> for P
736where
737 P: predicates_core::Predicate<[u8]>,
738{
739 type Predicate = P;
740
741 fn into_output(self) -> Self::Predicate {
742 self
743 }
744}
745
746#[derive(Debug)]
764pub struct BytesContentOutputPredicate(Cow<'static, [u8]>);
765
766impl BytesContentOutputPredicate {
767 pub(crate) fn new(value: &'static [u8]) -> Self {
768 BytesContentOutputPredicate(Cow::from(value))
769 }
770
771 pub(crate) fn from_vec(value: Vec<u8>) -> Self {
772 BytesContentOutputPredicate(Cow::from(value))
773 }
774}
775
776impl predicates_core::reflection::PredicateReflection for BytesContentOutputPredicate {}
777
778impl predicates_core::Predicate<[u8]> for BytesContentOutputPredicate {
779 fn eval(&self, item: &[u8]) -> bool {
780 self.0.as_ref() == item
781 }
782
783 fn find_case(
784 &self,
785 expected: bool,
786 variable: &[u8],
787 ) -> Option<predicates_core::reflection::Case<'_>> {
788 let actual = self.eval(variable);
789 if expected == actual {
790 Some(predicates_core::reflection::Case::new(Some(self), actual))
791 } else {
792 None
793 }
794 }
795}
796
797impl fmt::Display for BytesContentOutputPredicate {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 predicates::ord::eq(self.0.as_ref()).fmt(f)
800 }
801}
802
803impl IntoOutputPredicate<BytesContentOutputPredicate> for Vec<u8> {
804 type Predicate = BytesContentOutputPredicate;
805
806 fn into_output(self) -> Self::Predicate {
807 Self::Predicate::from_vec(self)
808 }
809}
810
811impl IntoOutputPredicate<BytesContentOutputPredicate> for &'static [u8] {
812 type Predicate = BytesContentOutputPredicate;
813
814 fn into_output(self) -> Self::Predicate {
815 Self::Predicate::new(self)
816 }
817}
818
819#[derive(Debug, Clone)]
839pub struct StrContentOutputPredicate(
840 predicates::str::Utf8Predicate<predicates::str::DifferencePredicate>,
841);
842
843impl StrContentOutputPredicate {
844 pub(crate) fn from_str(value: &'static str) -> Self {
845 let pred = predicates::str::diff(value).from_utf8();
846 StrContentOutputPredicate(pred)
847 }
848
849 pub(crate) fn from_string(value: String) -> Self {
850 let pred = predicates::str::diff(value).from_utf8();
851 StrContentOutputPredicate(pred)
852 }
853}
854
855impl predicates_core::reflection::PredicateReflection for StrContentOutputPredicate {
856 fn parameters<'a>(
857 &'a self,
858 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
859 self.0.parameters()
860 }
861
862 fn children<'a>(
864 &'a self,
865 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
866 self.0.children()
867 }
868}
869
870impl predicates_core::Predicate<[u8]> for StrContentOutputPredicate {
871 fn eval(&self, item: &[u8]) -> bool {
872 self.0.eval(item)
873 }
874
875 fn find_case<'a>(
876 &'a self,
877 expected: bool,
878 variable: &[u8],
879 ) -> Option<predicates_core::reflection::Case<'a>> {
880 self.0.find_case(expected, variable)
881 }
882}
883
884impl fmt::Display for StrContentOutputPredicate {
885 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
886 self.0.fmt(f)
887 }
888}
889
890impl IntoOutputPredicate<StrContentOutputPredicate> for String {
891 type Predicate = StrContentOutputPredicate;
892
893 fn into_output(self) -> Self::Predicate {
894 Self::Predicate::from_string(self)
895 }
896}
897
898impl IntoOutputPredicate<StrContentOutputPredicate> for &'static str {
899 type Predicate = StrContentOutputPredicate;
900
901 fn into_output(self) -> Self::Predicate {
902 Self::Predicate::from_str(self)
903 }
904}
905
906#[derive(Debug, Clone)]
926pub struct StrOutputPredicate<P: predicates_core::Predicate<str>>(
927 predicates::str::Utf8Predicate<P>,
928);
929
930impl<P> StrOutputPredicate<P>
931where
932 P: predicates_core::Predicate<str>,
933{
934 pub(crate) fn new(pred: P) -> Self {
935 let pred = pred.from_utf8();
936 StrOutputPredicate(pred)
937 }
938}
939
940impl<P> predicates_core::reflection::PredicateReflection for StrOutputPredicate<P>
941where
942 P: predicates_core::Predicate<str>,
943{
944 fn parameters<'a>(
945 &'a self,
946 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
947 self.0.parameters()
948 }
949
950 fn children<'a>(
952 &'a self,
953 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
954 self.0.children()
955 }
956}
957
958impl<P> predicates_core::Predicate<[u8]> for StrOutputPredicate<P>
959where
960 P: predicates_core::Predicate<str>,
961{
962 fn eval(&self, item: &[u8]) -> bool {
963 self.0.eval(item)
964 }
965
966 fn find_case<'a>(
967 &'a self,
968 expected: bool,
969 variable: &[u8],
970 ) -> Option<predicates_core::reflection::Case<'a>> {
971 self.0.find_case(expected, variable)
972 }
973}
974
975impl<P> fmt::Display for StrOutputPredicate<P>
976where
977 P: predicates_core::Predicate<str>,
978{
979 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
980 self.0.fmt(f)
981 }
982}
983
984impl<P> IntoOutputPredicate<StrOutputPredicate<P>> for P
985where
986 P: predicates_core::Predicate<str>,
987{
988 type Predicate = StrOutputPredicate<P>;
989
990 fn into_output(self) -> Self::Predicate {
991 Self::Predicate::new(self)
992 }
993}
994
995pub type AssertResult = Result<Assert, AssertError>;
1014
1015#[derive(Debug)]
1017pub struct AssertError {
1018 assert: Assert,
1019 reason: AssertReason,
1020}
1021
1022#[derive(Debug)]
1023enum AssertReason {
1024 UnexpectedFailure { actual_code: Option<i32> },
1025 UnexpectedSuccess,
1026 UnexpectedCompletion,
1027 CommandInterrupted,
1028 UnexpectedReturnCode { case_tree: CaseTree },
1029 UnexpectedStdout { case_tree: CaseTree },
1030 UnexpectedStderr { case_tree: CaseTree },
1031}
1032
1033impl AssertError {
1034 #[track_caller]
1035 fn panic<T>(self) -> T {
1036 panic!("{}", self)
1037 }
1038
1039 pub fn assert(self) -> Assert {
1063 self.assert
1064 }
1065}
1066
1067impl Error for AssertError {}
1068
1069impl fmt::Display for AssertError {
1070 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1071 match &self.reason {
1072 AssertReason::UnexpectedFailure { actual_code } => writeln!(
1073 f,
1074 "Unexpected failure.\ncode={}\nstderr=```{}```",
1075 actual_code
1076 .map(|actual_code| actual_code.to_string())
1077 .unwrap_or_else(|| "<interrupted>".to_owned()),
1078 DebugBytes::new(&self.assert.output.stderr),
1079 ),
1080 AssertReason::UnexpectedSuccess => {
1081 writeln!(f, "Unexpected success")
1082 }
1083 AssertReason::UnexpectedCompletion => {
1084 writeln!(f, "Unexpected completion")
1085 }
1086 AssertReason::CommandInterrupted => {
1087 writeln!(f, "Command interrupted")
1088 }
1089 AssertReason::UnexpectedReturnCode { case_tree } => {
1090 writeln!(f, "Unexpected return code, failed {case_tree}")
1091 }
1092 AssertReason::UnexpectedStdout { case_tree } => {
1093 writeln!(f, "Unexpected stdout, failed {case_tree}")
1094 }
1095 AssertReason::UnexpectedStderr { case_tree } => {
1096 writeln!(f, "Unexpected stderr, failed {case_tree}")
1097 }
1098 }?;
1099 write!(f, "{}", self.assert)
1100 }
1101}
1102
1103struct CaseTree(predicates_tree::CaseTree);
1104
1105impl fmt::Display for CaseTree {
1106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1107 <predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
1108 }
1109}
1110
1111impl fmt::Debug for CaseTree {
1113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1114 <predicates_tree::CaseTree as fmt::Display>::fmt(&self.0, f)
1115 }
1116}
1117
1118#[cfg(test)]
1119mod test {
1120 use super::*;
1121
1122 use predicates::prelude::*;
1123
1124 fn convert_code<I, P>(pred: I) -> P
1127 where
1128 I: IntoCodePredicate<P>,
1129 P: Predicate<i32>,
1130 {
1131 pred.into_code()
1132 }
1133
1134 #[test]
1135 fn into_code_from_pred() {
1136 let pred = convert_code(predicate::eq(10));
1137 assert!(pred.eval(&10));
1138 }
1139
1140 #[test]
1141 fn into_code_from_i32() {
1142 let pred = convert_code(10);
1143 assert!(pred.eval(&10));
1144 }
1145
1146 #[test]
1147 fn into_code_from_vec() {
1148 let pred = convert_code(vec![3, 10]);
1149 assert!(pred.eval(&10));
1150 }
1151
1152 #[test]
1153 fn into_code_from_array() {
1154 let pred = convert_code(&[3, 10] as &[i32]);
1155 assert!(pred.eval(&10));
1156 }
1157
1158 fn convert_output<I, P>(pred: I) -> P
1161 where
1162 I: IntoOutputPredicate<P>,
1163 P: Predicate<[u8]>,
1164 {
1165 pred.into_output()
1166 }
1167
1168 #[test]
1169 fn into_output_from_pred() {
1170 let pred = convert_output(predicate::eq(b"Hello" as &[u8]));
1171 assert!(pred.eval(b"Hello" as &[u8]));
1172 }
1173
1174 #[test]
1175 fn into_output_from_bytes() {
1176 let pred = convert_output(b"Hello" as &[u8]);
1177 assert!(pred.eval(b"Hello" as &[u8]));
1178 }
1179
1180 #[test]
1181 fn into_output_from_vec() {
1182 let pred = convert_output(vec![b'H', b'e', b'l', b'l', b'o']);
1183 assert!(pred.eval(b"Hello" as &[u8]));
1184 }
1185
1186 #[test]
1187 fn into_output_from_str() {
1188 let pred = convert_output("Hello");
1189 assert!(pred.eval(b"Hello" as &[u8]));
1190 }
1191}