1use crate::{color::Palette, fixture};
29#[cfg(feature = "assert-color")]
30use anstream::panic;
31use camino::Utf8Path;
32use camino_tempfile::{NamedUtf8TempFile, Utf8TempDir};
33use predicates::{
34 path::PredicateFileContentExt, reflection::PredicateReflection, str::PredicateStrExt,
35};
36use predicates_core::Predicate;
37use predicates_tree::CaseTreeExt;
38use std::{fmt, path::Path};
39
40pub trait PathAssert {
72 #[track_caller]
105 fn assert<I, P>(&self, pred: I) -> &Self
106 where
107 I: IntoUtf8PathPredicate<P>,
108 P: Predicate<Utf8Path>;
109}
110
111impl PathAssert for Utf8TempDir {
112 #[track_caller]
113 fn assert<I, P>(&self, pred: I) -> &Self
114 where
115 I: IntoUtf8PathPredicate<P>,
116 P: Predicate<Utf8Path>,
117 {
118 assert(self.path(), pred);
119 self
120 }
121}
122
123impl PathAssert for NamedUtf8TempFile {
124 #[track_caller]
125 fn assert<I, P>(&self, pred: I) -> &Self
126 where
127 I: IntoUtf8PathPredicate<P>,
128 P: Predicate<Utf8Path>,
129 {
130 assert(self.path(), pred);
131 self
132 }
133}
134
135impl PathAssert for fixture::ChildPath {
136 #[track_caller]
137 fn assert<I, P>(&self, pred: I) -> &Self
138 where
139 I: IntoUtf8PathPredicate<P>,
140 P: Predicate<Utf8Path>,
141 {
142 assert(self.as_path(), pred);
143 self
144 }
145}
146
147#[track_caller]
148fn assert<I, P>(path: &Utf8Path, pred: I)
149where
150 I: IntoUtf8PathPredicate<P>,
151 P: Predicate<Utf8Path>,
152{
153 let pred = pred.into_path();
154 if let Some(case) = pred.find_case(false, path) {
155 let palette = Palette::color();
156 panic!(
157 "Unexpected file, failed {:#}\n{:#}={:#}",
158 case.tree(),
159 palette.key("path"),
160 palette.value(path)
161 );
162 }
163}
164
165pub trait IntoUtf8PathPredicate<P>
182where
183 P: Predicate<Utf8Path>,
184{
185 type Predicate;
187
188 fn into_path(self) -> P;
190}
191
192impl<P> IntoUtf8PathPredicate<P> for P
193where
194 P: Predicate<Utf8Path>,
195{
196 type Predicate = P;
197
198 fn into_path(self) -> Self::Predicate {
199 self
200 }
201}
202
203#[derive(Debug)]
221pub struct BytesContentPathPredicate(
222 predicates::path::FileContentPredicate<predicates::ord::EqPredicate<&'static [u8]>>,
223);
224
225impl BytesContentPathPredicate {
226 pub(crate) fn new(value: &'static [u8]) -> Self {
227 let pred = predicates::ord::eq(value).from_file_path();
228 BytesContentPathPredicate(pred)
229 }
230}
231
232impl PredicateReflection for BytesContentPathPredicate {
233 fn parameters<'a>(
234 &'a self,
235 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
236 self.0.parameters()
237 }
238
239 fn children<'a>(
241 &'a self,
242 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
243 self.0.children()
244 }
245}
246
247impl Predicate<Utf8Path> for BytesContentPathPredicate {
248 fn eval(&self, item: &Utf8Path) -> bool {
249 self.0.eval(item.as_std_path())
250 }
251
252 fn find_case<'a>(
253 &'a self,
254 expected: bool,
255 variable: &Utf8Path,
256 ) -> Option<predicates_core::reflection::Case<'a>> {
257 self.0.find_case(expected, variable.as_std_path())
258 }
259}
260
261impl fmt::Display for BytesContentPathPredicate {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 self.0.fmt(f)
264 }
265}
266
267impl IntoUtf8PathPredicate<BytesContentPathPredicate> for &'static [u8] {
268 type Predicate = BytesContentPathPredicate;
269
270 fn into_path(self) -> Self::Predicate {
271 Self::Predicate::new(self)
272 }
273}
274
275impl<const N: usize> IntoUtf8PathPredicate<BytesContentPathPredicate> for &'static [u8; N] {
276 type Predicate = BytesContentPathPredicate;
277
278 fn into_path(self) -> Self::Predicate {
279 Self::Predicate::new(self)
280 }
281}
282
283#[derive(Debug, Clone)]
301pub struct StrContentPathPredicate(
302 predicates::path::FileContentPredicate<
303 predicates::str::Utf8Predicate<predicates::str::DifferencePredicate>,
304 >,
305);
306
307impl StrContentPathPredicate {
308 pub(crate) fn new(value: String) -> Self {
309 let pred = predicates::str::diff(value).from_utf8().from_file_path();
310 StrContentPathPredicate(pred)
311 }
312}
313
314impl predicates_core::reflection::PredicateReflection for StrContentPathPredicate {
315 fn parameters<'a>(
316 &'a self,
317 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
318 self.0.parameters()
319 }
320
321 fn children<'a>(
323 &'a self,
324 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
325 self.0.children()
326 }
327}
328
329impl Predicate<Utf8Path> for StrContentPathPredicate {
330 fn eval(&self, item: &Utf8Path) -> bool {
331 self.0.eval(item.as_std_path())
332 }
333
334 fn find_case<'a>(
335 &'a self,
336 expected: bool,
337 variable: &Utf8Path,
338 ) -> Option<predicates_core::reflection::Case<'a>> {
339 self.0.find_case(expected, variable.as_std_path())
340 }
341}
342
343impl fmt::Display for StrContentPathPredicate {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 self.0.fmt(f)
346 }
347}
348
349impl IntoUtf8PathPredicate<StrContentPathPredicate> for String {
350 type Predicate = StrContentPathPredicate;
351
352 fn into_path(self) -> Self::Predicate {
353 Self::Predicate::new(self)
354 }
355}
356
357impl IntoUtf8PathPredicate<StrContentPathPredicate> for &str {
358 type Predicate = StrContentPathPredicate;
359
360 fn into_path(self) -> Self::Predicate {
361 Self::Predicate::new(self.to_owned())
362 }
363}
364
365impl IntoUtf8PathPredicate<StrContentPathPredicate> for &String {
366 type Predicate = StrContentPathPredicate;
367
368 fn into_path(self) -> Self::Predicate {
369 Self::Predicate::new(self.to_owned())
370 }
371}
372
373#[derive(Debug, Clone)]
393pub struct StrPathPredicate<P: Predicate<str>>(
394 predicates::path::FileContentPredicate<predicates::str::Utf8Predicate<P>>,
395);
396
397impl<P> StrPathPredicate<P>
398where
399 P: Predicate<str>,
400{
401 pub(crate) fn new(value: P) -> Self {
402 let pred = value.from_utf8().from_file_path();
403 StrPathPredicate(pred)
404 }
405}
406
407impl<P> PredicateReflection for StrPathPredicate<P>
408where
409 P: Predicate<str>,
410{
411 fn parameters<'a>(
412 &'a self,
413 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
414 self.0.parameters()
415 }
416
417 fn children<'a>(
419 &'a self,
420 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
421 self.0.children()
422 }
423}
424
425impl<P> Predicate<Utf8Path> for StrPathPredicate<P>
426where
427 P: Predicate<str>,
428{
429 fn eval(&self, item: &Utf8Path) -> bool {
430 self.0.eval(item.as_std_path())
431 }
432
433 fn find_case<'a>(
434 &'a self,
435 expected: bool,
436 variable: &Utf8Path,
437 ) -> Option<predicates_core::reflection::Case<'a>> {
438 self.0.find_case(expected, variable.as_std_path())
439 }
440}
441
442impl<P> fmt::Display for StrPathPredicate<P>
443where
444 P: Predicate<str>,
445{
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 self.0.fmt(f)
448 }
449}
450
451impl<P> IntoUtf8PathPredicate<StrPathPredicate<P>> for P
452where
453 P: Predicate<str>,
454{
455 type Predicate = StrPathPredicate<P>;
456
457 fn into_path(self) -> Self::Predicate {
458 Self::Predicate::new(self)
459 }
460}
461
462pub struct PathPredicate<P: Predicate<Path>>(P);
482
483impl<P> PathPredicate<P>
484where
485 P: Predicate<Path>,
486{
487 pub(crate) fn new(predicate: P) -> Self {
488 Self(predicate)
489 }
490}
491
492impl<P> PredicateReflection for PathPredicate<P>
493where
494 P: Predicate<Path>,
495{
496 fn parameters<'a>(
497 &'a self,
498 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Parameter<'a>> + 'a> {
499 self.0.parameters()
500 }
501
502 fn children<'a>(
504 &'a self,
505 ) -> Box<dyn Iterator<Item = predicates_core::reflection::Child<'a>> + 'a> {
506 self.0.children()
507 }
508}
509
510impl<P> Predicate<Utf8Path> for PathPredicate<P>
511where
512 P: Predicate<Path>,
513{
514 fn eval(&self, item: &Utf8Path) -> bool {
515 self.0.eval(item.as_std_path())
516 }
517
518 fn find_case<'a>(
519 &'a self,
520 expected: bool,
521 variable: &Utf8Path,
522 ) -> Option<predicates_core::reflection::Case<'a>> {
523 self.0.find_case(expected, variable.as_std_path())
524 }
525}
526
527impl<P> fmt::Display for PathPredicate<P>
528where
529 P: Predicate<Path>,
530{
531 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
532 self.0.fmt(f)
533 }
534}
535
536impl<P> IntoUtf8PathPredicate<PathPredicate<P>> for P
537where
538 P: Predicate<Path>,
539{
540 type Predicate = PathPredicate<P>;
541
542 fn into_path(self) -> Self::Predicate {
543 Self::Predicate::new(self)
544 }
545}
546
547#[cfg(test)]
548mod test {
549 use super::*;
550 use predicates::prelude::*;
551
552 fn convert_path<I, P>(pred: I) -> P
555 where
556 I: IntoUtf8PathPredicate<P>,
557 P: Predicate<Utf8Path>,
558 {
559 pred.into_path()
560 }
561
562 #[test]
563 fn into_utf8_path_from_pred() {
564 let pred = convert_path(predicate::eq(Utf8Path::new("hello.md")));
565 let case = pred.find_case(false, Utf8Path::new("hello.md"));
566 println!("Failing case: {case:?}");
567 assert!(case.is_none());
568 }
569
570 #[test]
571 fn into_utf8_path_from_bytes() {
572 let pred = convert_path(b"hello\n" as &[u8]);
573 let case = pred.find_case(false, Utf8Path::new("tests/fixture/hello.txt"));
574 println!("Failing case: {case:?}");
575 assert!(case.is_none());
576 }
577
578 #[test]
579 fn into_utf8_path_from_str() {
580 let pred = convert_path("hello\n");
581 let case = pred.find_case(false, Utf8Path::new("tests/fixture/hello.txt"));
582 println!("Failing case: {case:?}");
583 assert!(case.is_none());
584 }
585
586 #[test]
587 fn into_utf8_path_from_path() {
588 let pred = convert_path(predicate::path::missing());
589 let case = pred.find_case(false, Utf8Path::new("tests/fixture/missing.txt"));
590 println!("Failing case: {case:?}");
591 assert!(case.is_none());
592 }
593}