Skip to main content

turbopack/module_options/
rule_condition.rs

1use std::{
2    iter,
3    mem::{replace, take},
4};
5
6use anyhow::Result;
7use bincode::{Decode, Encode};
8use either::Either;
9use smallvec::SmallVec;
10use turbo_esregex::EsRegex;
11use turbo_tasks::{NonLocalValue, ReadRef, ResolvedVc, trace::TraceRawVcs};
12use turbo_tasks_fs::{FileContent, FileSystemPath, glob::Glob};
13use turbopack_core::{
14    asset::Asset,
15    reference_type::{ReferenceType, ReferenceTypeCondition},
16    source::Source,
17    virtual_source::VirtualSource,
18};
19
20#[derive(Debug, Clone, TraceRawVcs, PartialEq, Eq, NonLocalValue, Encode, Decode)]
21pub enum RuleCondition {
22    All(Vec<RuleCondition>),
23    Any(Vec<RuleCondition>),
24    Not(Box<RuleCondition>),
25    True,
26    False,
27    ReferenceType(ReferenceTypeCondition),
28    ResourceIsVirtualSource,
29    ResourcePathEquals(FileSystemPath),
30    ResourcePathHasNoExtension,
31    ResourcePathEndsWith(String),
32    ResourcePathInDirectory(String),
33    ResourcePathInExactDirectory(FileSystemPath),
34    ContentTypeStartsWith(String),
35    ContentTypeEmpty,
36    ResourcePathEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
37    ResourceContentEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
38    /// For paths that are within the same filesystem as the `base`, it need to
39    /// match the relative path from base to resource. This includes `./` or
40    /// `../` prefix. For paths in a different filesystem, it need to match
41    /// the resource path in that filesystem without any prefix. This means
42    /// any glob starting with `./` or `../` will only match paths in the
43    /// project. Globs starting with `**` can match any path.
44    ResourcePathGlob {
45        base: FileSystemPath,
46        #[turbo_tasks(trace_ignore)]
47        glob: ReadRef<Glob>,
48    },
49    ResourceBasePathGlob(#[turbo_tasks(trace_ignore)] ReadRef<Glob>),
50    ResourceQueryContains(String),
51    ResourceQueryEquals(String),
52    ResourceQueryEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
53    ContentTypeGlob(#[turbo_tasks(trace_ignore)] ReadRef<Glob>),
54    ContentTypeEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
55}
56
57impl RuleCondition {
58    pub fn all(conditions: Vec<RuleCondition>) -> RuleCondition {
59        RuleCondition::All(conditions)
60    }
61
62    pub fn any(conditions: Vec<RuleCondition>) -> RuleCondition {
63        RuleCondition::Any(conditions)
64    }
65
66    #[allow(clippy::should_implement_trait)]
67    pub fn not(condition: RuleCondition) -> RuleCondition {
68        RuleCondition::Not(Box::new(condition))
69    }
70
71    /// Slightly optimize a `RuleCondition` by flattening nested `Any`, `All`, or `Not` variants.
72    ///
73    /// Does not apply general re-ordering of rules (which may also be a valid optimization using a
74    /// cost heuristic), but does flatten constant `True` and `False` conditions, potentially
75    /// skipping other rules.
76    pub fn flatten(&mut self) {
77        match self {
78            RuleCondition::Any(conds) => {
79                // fast path: flatten children in-place and avoid constructing an additional vec
80                let mut needs_flattening = false;
81                for c in conds.iter_mut() {
82                    c.flatten();
83                    if *c == RuleCondition::True {
84                        // short-circuit: all conditions are side-effect free
85                        *self = RuleCondition::True;
86                        return;
87                    }
88                    needs_flattening = needs_flattening
89                        || matches!(c, RuleCondition::Any(_) | RuleCondition::False);
90                }
91
92                if needs_flattening {
93                    *conds = take(conds)
94                        .into_iter()
95                        .flat_map(|c| match c {
96                            RuleCondition::Any(nested) => {
97                                debug_assert!(!nested.is_empty(), "empty Any should be False");
98                                Either::Left(nested.into_iter())
99                            }
100                            RuleCondition::False => Either::Right(Either::Left(iter::empty())),
101                            c => Either::Right(Either::Right(iter::once(c))),
102                        })
103                        .collect();
104                }
105
106                match conds.len() {
107                    0 => *self = RuleCondition::False,
108                    1 => *self = take(conds).into_iter().next().unwrap(),
109                    _ => {}
110                }
111            }
112            RuleCondition::All(conds) => {
113                // fast path: flatten children in-place and avoid constructing an additional vec
114                let mut needs_flattening = false;
115                for c in conds.iter_mut() {
116                    c.flatten();
117                    if *c == RuleCondition::False {
118                        // short-circuit: all conditions are side-effect free
119                        *self = RuleCondition::False;
120                        return;
121                    }
122                    needs_flattening = needs_flattening
123                        || matches!(c, RuleCondition::All(_) | RuleCondition::True);
124                }
125
126                if needs_flattening {
127                    *conds = take(conds)
128                        .into_iter()
129                        .flat_map(|c| match c {
130                            RuleCondition::All(nested) => {
131                                debug_assert!(!nested.is_empty(), "empty All should be True");
132                                Either::Left(nested.into_iter())
133                            }
134                            RuleCondition::True => Either::Right(Either::Left(iter::empty())),
135                            c => Either::Right(Either::Right(iter::once(c))),
136                        })
137                        .collect();
138                }
139
140                match conds.len() {
141                    0 => *self = RuleCondition::True,
142                    1 => *self = take(conds).into_iter().next().unwrap(),
143                    _ => {}
144                }
145            }
146            RuleCondition::Not(cond) => {
147                match &mut **cond {
148                    // nested `Not`s negate each other
149                    RuleCondition::Not(inner) => {
150                        let inner = &mut **inner;
151                        inner.flatten();
152                        // Use `replace` with a dummy condition instead of `take` since
153                        // `RuleCondition` doesn't implement `Default`.
154                        *self = replace(inner, RuleCondition::False)
155                    }
156                    RuleCondition::True => *self = RuleCondition::False,
157                    RuleCondition::False => *self = RuleCondition::True,
158                    other => other.flatten(),
159                }
160            }
161            _ => {}
162        }
163    }
164
165    pub async fn matches(
166        &self,
167        source: ResolvedVc<Box<dyn Source>>,
168        path: &FileSystemPath,
169        reference_type: &ReferenceType,
170    ) -> Result<bool> {
171        enum Op<'a> {
172            All(&'a [RuleCondition]), // Remaining conditions in an All
173            Any(&'a [RuleCondition]), // Remaining conditions in an Any
174            Not,                      // Inverts the previous condition
175        }
176
177        // Evaluates the condition returning the result and possibly pushing additional operations
178        // onto the stack as a kind of continuation.
179        async fn process_condition<'a, const SZ: usize>(
180            source: ResolvedVc<Box<dyn Source + 'static>>,
181            path: &FileSystemPath,
182            reference_type: &ReferenceType,
183            stack: &mut SmallVec<[Op<'a>; SZ]>,
184            mut cond: &'a RuleCondition,
185        ) -> Result<bool, anyhow::Error> {
186            // Use a loop to avoid recursion and unnecessary stack operations.
187            loop {
188                match cond {
189                    RuleCondition::All(conditions) => {
190                        if conditions.is_empty() {
191                            return Ok(true);
192                        } else {
193                            if conditions.len() > 1 {
194                                stack.push(Op::All(&conditions[1..]));
195                            }
196                            cond = &conditions[0];
197                            // jump directly to the next condition, no need to deal with
198                            // the stack.
199                            continue;
200                        }
201                    }
202                    RuleCondition::Any(conditions) => {
203                        if conditions.is_empty() {
204                            return Ok(false);
205                        } else {
206                            if conditions.len() > 1 {
207                                stack.push(Op::Any(&conditions[1..]));
208                            }
209                            cond = &conditions[0];
210                            continue;
211                        }
212                    }
213                    RuleCondition::Not(inner) => {
214                        stack.push(Op::Not);
215                        cond = inner.as_ref();
216                        continue;
217                    }
218                    RuleCondition::True => {
219                        return Ok(true);
220                    }
221                    RuleCondition::False => {
222                        return Ok(false);
223                    }
224                    RuleCondition::ReferenceType(condition_ty) => {
225                        return Ok(condition_ty.includes(reference_type));
226                    }
227                    RuleCondition::ResourceIsVirtualSource => {
228                        return Ok(ResolvedVc::try_downcast_type::<VirtualSource>(source).is_some());
229                    }
230                    RuleCondition::ResourcePathEquals(other) => {
231                        return Ok(path == other);
232                    }
233                    RuleCondition::ResourcePathEndsWith(end) => {
234                        return Ok(path.path.ends_with(end));
235                    }
236                    RuleCondition::ResourcePathHasNoExtension => {
237                        return Ok(if let Some(i) = path.path.rfind('.') {
238                            if let Some(j) = path.path.rfind('/') {
239                                j > i
240                            } else {
241                                false
242                            }
243                        } else {
244                            true
245                        });
246                    }
247                    RuleCondition::ResourcePathInDirectory(dir) => {
248                        return Ok(path.path.starts_with(&format!("{dir}/"))
249                            || path.path.contains(&format!("/{dir}/")));
250                    }
251                    RuleCondition::ResourcePathInExactDirectory(parent_path) => {
252                        return Ok(path.is_inside_ref(parent_path));
253                    }
254                    RuleCondition::ContentTypeStartsWith(start) => {
255                        let content_type = &source.ident().await?.content_type;
256                        return Ok(content_type
257                            .as_ref()
258                            .is_some_and(|ct| ct.starts_with(start.as_str())));
259                    }
260                    RuleCondition::ContentTypeEmpty => {
261                        return Ok(source.ident().await?.content_type.is_none());
262                    }
263                    RuleCondition::ResourcePathGlob { glob, base } => {
264                        return Ok(if let Some(rel_path) = base.get_relative_path_to(path) {
265                            glob.matches(&rel_path)
266                        } else {
267                            glob.matches(&path.path)
268                        });
269                    }
270                    RuleCondition::ResourceBasePathGlob(glob) => {
271                        let basename = path
272                            .path
273                            .rsplit_once('/')
274                            .map_or(path.path.as_str(), |(_, b)| b);
275                        return Ok(glob.matches(basename));
276                    }
277                    RuleCondition::ResourcePathEsRegex(regex) => {
278                        return Ok(regex.is_match(&path.path));
279                    }
280                    RuleCondition::ResourceContentEsRegex(regex) => {
281                        let content = source.content().file_content().await?;
282                        match &*content {
283                            FileContent::Content(file_content) => {
284                                return Ok(regex.is_match(&file_content.content().to_str()?));
285                            }
286                            FileContent::NotFound => return Ok(false),
287                        }
288                    }
289                    RuleCondition::ResourceQueryContains(query) => {
290                        let ident = source.ident().await?;
291                        return Ok(ident.query.contains(query));
292                    }
293                    RuleCondition::ResourceQueryEquals(query) => {
294                        let ident = source.ident().await?;
295                        return Ok(ident.query == *query);
296                    }
297                    RuleCondition::ResourceQueryEsRegex(regex) => {
298                        let ident = source.ident().await?;
299                        return Ok(regex.is_match(&ident.query));
300                    }
301                    RuleCondition::ContentTypeGlob(glob) => {
302                        let ident = source.ident().await?;
303                        return Ok(ident
304                            .content_type
305                            .as_ref()
306                            .is_some_and(|ct| glob.matches(ct)));
307                    }
308                    RuleCondition::ContentTypeEsRegex(regex) => {
309                        let ident = source.ident().await?;
310                        return Ok(ident
311                            .content_type
312                            .as_ref()
313                            .is_some_and(|ct| regex.is_match(ct)));
314                    }
315                }
316            }
317        }
318        // Allocate a small inline stack to avoid heap allocations in the common case where
319        // conditions are not deeply stacked.  Additionally we take care to avoid stack
320        // operations unless strictly necessary.
321        const EXPECTED_SIZE: usize = 8;
322        let mut stack = SmallVec::<[Op; EXPECTED_SIZE]>::with_capacity(EXPECTED_SIZE);
323        let mut result = process_condition(source, path, reference_type, &mut stack, self).await?;
324        while let Some(op) = stack.pop() {
325            match op {
326                Op::All(remaining) => {
327                    // Previous was true, keep going
328                    if result {
329                        if remaining.len() > 1 {
330                            stack.push(Op::All(&remaining[1..]));
331                        }
332                        result = process_condition(
333                            source,
334                            path,
335                            reference_type,
336                            &mut stack,
337                            &remaining[0],
338                        )
339                        .await?;
340                    }
341                }
342                Op::Any(remaining) => {
343                    // Previous was false, keep going
344                    if !result {
345                        if remaining.len() > 1 {
346                            stack.push(Op::Any(&remaining[1..]));
347                        }
348                        // If the stack didn't change, we can loop inline, but we would still need
349                        // to pop the item.  This might be faster since we would avoid the `match`
350                        // but overall, that is quite minor for an enum with 3 cases.
351                        result = process_condition(
352                            source,
353                            path,
354                            reference_type,
355                            &mut stack,
356                            &remaining[0],
357                        )
358                        .await?;
359                    }
360                }
361                Op::Not => {
362                    result = !result;
363                }
364            }
365        }
366        Ok(result)
367    }
368}
369
370#[cfg(test)]
371pub mod tests {
372    use turbo_tasks::Vc;
373    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
374    use turbo_tasks_fs::{FileContent, FileSystem, VirtualFileSystem};
375    use turbopack_core::{asset::AssetContent, file_source::FileSource};
376
377    use super::*;
378
379    #[test]
380    fn flatten_any_with_single_child_collapses() {
381        let mut rc = RuleCondition::Any(vec![RuleCondition::True]);
382        rc.flatten();
383        assert_eq!(rc, RuleCondition::True);
384
385        let mut rc = RuleCondition::Any(vec![RuleCondition::ContentTypeEmpty]);
386        rc.flatten();
387        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
388    }
389
390    #[test]
391    fn flatten_any_nested_and_false() {
392        let mut rc = RuleCondition::Any(vec![
393            RuleCondition::False,
394            RuleCondition::Any(vec![RuleCondition::ContentTypeEmpty, RuleCondition::False]),
395        ]);
396        rc.flatten();
397        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
398    }
399
400    #[test]
401    fn flatten_any_short_circuits_on_true() {
402        let mut rc = RuleCondition::Any(vec![
403            RuleCondition::False,
404            RuleCondition::True,
405            RuleCondition::ContentTypeEmpty,
406        ]);
407        rc.flatten();
408        assert_eq!(rc, RuleCondition::True);
409    }
410
411    #[test]
412    fn flatten_any_empty_becomes_false() {
413        let mut rc = RuleCondition::Any(vec![]);
414        rc.flatten();
415        assert_eq!(rc, RuleCondition::False);
416    }
417
418    #[test]
419    fn flatten_all_with_single_child_collapses() {
420        let mut rc = RuleCondition::All(vec![RuleCondition::ContentTypeEmpty]);
421        rc.flatten();
422        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
423
424        let mut rc = RuleCondition::All(vec![RuleCondition::True]);
425        rc.flatten();
426        assert_eq!(rc, RuleCondition::True);
427    }
428
429    #[test]
430    fn flatten_all_nested_and_true() {
431        let mut rc = RuleCondition::All(vec![
432            RuleCondition::True,
433            RuleCondition::All(vec![RuleCondition::ContentTypeEmpty, RuleCondition::True]),
434        ]);
435        rc.flatten();
436        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
437    }
438
439    #[test]
440    fn flatten_all_short_circuits_on_false() {
441        let mut rc = RuleCondition::All(vec![
442            RuleCondition::True,
443            RuleCondition::False,
444            RuleCondition::ContentTypeEmpty,
445        ]);
446        rc.flatten();
447        assert_eq!(rc, RuleCondition::False);
448    }
449
450    #[test]
451    fn flatten_all_empty_becomes_true() {
452        let mut rc = RuleCondition::All(vec![]);
453        rc.flatten();
454        assert_eq!(rc, RuleCondition::True);
455    }
456
457    #[test]
458    fn flatten_not_of_not() {
459        let mut rc = RuleCondition::Not(Box::new(RuleCondition::Not(Box::new(
460            RuleCondition::All(vec![RuleCondition::ContentTypeEmpty]),
461        ))));
462        rc.flatten();
463        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
464    }
465
466    #[test]
467    fn flatten_not_constants() {
468        let mut rc = RuleCondition::Not(Box::new(RuleCondition::True));
469        rc.flatten();
470        assert_eq!(rc, RuleCondition::False);
471
472        let mut rc = RuleCondition::Not(Box::new(RuleCondition::False));
473        rc.flatten();
474        assert_eq!(rc, RuleCondition::True);
475    }
476
477    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
478    async fn test_rule_condition_leaves() {
479        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
480            BackendOptions::default(),
481            noop_backing_storage(),
482        ));
483        tt.run_once(async { run_leaves_test_operation().read_strongly_consistent().await })
484            .await
485            .unwrap();
486    }
487
488    #[turbo_tasks::function(operation)]
489    pub async fn run_leaves_test_operation() -> Result<()> {
490        let fs = VirtualFileSystem::new();
491        let virtual_path = fs.root().await?.join("foo.js")?;
492        let virtual_source = Vc::upcast::<Box<dyn Source>>(VirtualSource::new(
493            virtual_path.clone(),
494            AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(),
495        ))
496        .to_resolved()
497        .await?;
498
499        let non_virtual_path = fs.root().await?.join("bar.js")?;
500        let non_virtual_source =
501            Vc::upcast::<Box<dyn Source>>(FileSource::new(non_virtual_path.clone()))
502                .to_resolved()
503                .await?;
504
505        {
506            let condition = RuleCondition::ReferenceType(ReferenceTypeCondition::Runtime);
507            assert!(
508                condition
509                    .matches(virtual_source, &virtual_path, &ReferenceType::Runtime)
510                    .await
511                    .unwrap()
512            );
513            assert!(
514                !condition
515                    .matches(
516                        non_virtual_source,
517                        &non_virtual_path,
518                        &ReferenceType::Css(
519                            turbopack_core::reference_type::CssReferenceSubType::Compose
520                        )
521                    )
522                    .await
523                    .unwrap()
524            );
525        }
526
527        {
528            let condition = RuleCondition::ResourceIsVirtualSource;
529            assert!(
530                condition
531                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
532                    .await
533                    .unwrap()
534            );
535            assert!(
536                !condition
537                    .matches(
538                        non_virtual_source,
539                        &non_virtual_path,
540                        &ReferenceType::Undefined
541                    )
542                    .await
543                    .unwrap()
544            );
545        }
546        {
547            let condition = RuleCondition::ResourcePathEquals(virtual_path.clone());
548            assert!(
549                condition
550                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
551                    .await
552                    .unwrap()
553            );
554            assert!(
555                !condition
556                    .matches(
557                        non_virtual_source,
558                        &non_virtual_path,
559                        &ReferenceType::Undefined
560                    )
561                    .await
562                    .unwrap()
563            );
564        }
565        {
566            let condition = RuleCondition::ResourcePathHasNoExtension;
567            assert!(
568                condition
569                    .matches(
570                        virtual_source,
571                        &fs.root().await?.join("foo")?,
572                        &ReferenceType::Undefined
573                    )
574                    .await
575                    .unwrap()
576            );
577            assert!(
578                !condition
579                    .matches(
580                        non_virtual_source,
581                        &non_virtual_path,
582                        &ReferenceType::Undefined
583                    )
584                    .await
585                    .unwrap()
586            );
587        }
588        {
589            let condition = RuleCondition::ResourcePathEndsWith("foo.js".to_string());
590            assert!(
591                condition
592                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
593                    .await
594                    .unwrap()
595            );
596            assert!(
597                !condition
598                    .matches(
599                        non_virtual_source,
600                        &non_virtual_path,
601                        &ReferenceType::Undefined
602                    )
603                    .await
604                    .unwrap()
605            );
606        }
607        anyhow::Ok(())
608    }
609
610    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
611    async fn test_rule_condition_tree() {
612        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
613            BackendOptions::default(),
614            noop_backing_storage(),
615        ));
616        tt.run_once(async {
617            run_rule_condition_tree_test_operation()
618                .read_strongly_consistent()
619                .await
620        })
621        .await
622        .unwrap();
623    }
624
625    #[turbo_tasks::function(operation)]
626    pub async fn run_rule_condition_tree_test_operation() -> Result<()> {
627        let fs = VirtualFileSystem::new();
628        let virtual_path = fs.root().await?.join("foo.js")?;
629        let virtual_source = Vc::upcast::<Box<dyn Source>>(VirtualSource::new(
630            virtual_path.clone(),
631            AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(),
632        ))
633        .to_resolved()
634        .await?;
635
636        let non_virtual_path = fs.root().await?.join("bar.js")?;
637        let non_virtual_source =
638            Vc::upcast::<Box<dyn Source>>(FileSource::new(non_virtual_path.clone()))
639                .to_resolved()
640                .await?;
641
642        {
643            // not
644            let condition = RuleCondition::not(RuleCondition::ResourceIsVirtualSource);
645            assert!(
646                !condition
647                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
648                    .await
649                    .unwrap()
650            );
651            assert!(
652                condition
653                    .matches(
654                        non_virtual_source,
655                        &non_virtual_path,
656                        &ReferenceType::Undefined
657                    )
658                    .await
659                    .unwrap()
660            );
661        }
662        {
663            // any
664            // Only one of the conditions matches our virtual source
665            let condition = RuleCondition::any(vec![
666                RuleCondition::ResourcePathInDirectory("doesnt/exist".to_string()),
667                RuleCondition::ResourceIsVirtualSource,
668                RuleCondition::ResourcePathHasNoExtension,
669            ]);
670            assert!(
671                condition
672                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
673                    .await
674                    .unwrap()
675            );
676            assert!(
677                !condition
678                    .matches(
679                        non_virtual_source,
680                        &non_virtual_path,
681                        &ReferenceType::Undefined
682                    )
683                    .await
684                    .unwrap()
685            );
686        }
687        {
688            // all
689            // Only one of the conditions matches our virtual source
690            let condition = RuleCondition::all(vec![
691                RuleCondition::ResourcePathEndsWith("foo.js".to_string()),
692                RuleCondition::ResourceIsVirtualSource,
693                RuleCondition::ResourcePathEquals(virtual_path.clone()),
694            ]);
695            assert!(
696                condition
697                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
698                    .await
699                    .unwrap()
700            );
701            assert!(
702                !condition
703                    .matches(
704                        non_virtual_source,
705                        &non_virtual_path,
706                        &ReferenceType::Undefined
707                    )
708                    .await
709                    .unwrap()
710            );
711        }
712        {
713            // bigger tree
714
715            // Build a simple tree to cover our various composite conditions
716            let condition = RuleCondition::all(vec![
717                RuleCondition::ResourceIsVirtualSource,
718                RuleCondition::ResourcePathEquals(virtual_path.clone()),
719                RuleCondition::Not(Box::new(RuleCondition::ResourcePathHasNoExtension)),
720                RuleCondition::Any(vec![
721                    RuleCondition::ResourcePathEndsWith("foo.js".to_string()),
722                    RuleCondition::ContentTypeEmpty,
723                ]),
724            ]);
725            assert!(
726                condition
727                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
728                    .await
729                    .unwrap()
730            );
731            assert!(
732                !condition
733                    .matches(
734                        non_virtual_source,
735                        &non_virtual_path,
736                        &ReferenceType::Undefined
737                    )
738                    .await
739                    .unwrap()
740            );
741        }
742        anyhow::Ok(())
743    }
744}