1use std::{cmp::Reverse, collections::hash_map::Entry};
2
3use anyhow::{Context, Result, bail};
4use auto_hash_map::AutoSet;
5use rustc_hash::{FxHashMap, FxHashSet};
6use tracing::Instrument;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{OperationVc, ResolvedVc, Vc};
9
10use crate::{
11 chunk::chunking_context::UnusedReferences,
12 module::Module,
13 module_graph::{
14 GraphEdgeIndex, GraphTraversalAction, ModuleGraph,
15 side_effect_module_info::compute_side_effect_free_module_info,
16 },
17 resolve::{ExportUsage, ImportUsage},
18};
19
20#[turbo_tasks::value(transparent, cell = "keyed")]
21pub struct UsedExportsMap(FxHashMap<ResolvedVc<Box<dyn Module>>, ModuleExportUsageInfo>);
22
23#[turbo_tasks::value(transparent, cell = "keyed")]
24pub struct ExportCircuitBreakers(FxHashSet<ResolvedVc<Box<dyn Module>>>);
25
26#[turbo_tasks::value(transparent, cell = "keyed")]
36pub struct PartialNamespaceModules(FxHashSet<ResolvedVc<Box<dyn Module>>>);
37
38#[turbo_tasks::value(transparent, cell = "keyed")]
40#[derive(Debug)]
41pub struct UnusedReferenceEdges(FxHashSet<GraphEdgeIndex>);
42
43#[turbo_tasks::value]
44#[derive(Clone, Default, Debug)]
45pub struct BindingUsageInfo {
46 unused_references: ResolvedVc<UnusedReferences>,
47 unused_references_edges: ResolvedVc<UnusedReferenceEdges>,
48
49 used_exports: ResolvedVc<UsedExportsMap>,
50 export_circuit_breakers: ResolvedVc<ExportCircuitBreakers>,
51 partial_namespace_modules: ResolvedVc<PartialNamespaceModules>,
52}
53
54#[turbo_tasks::value(transparent)]
55pub struct OptionBindingUsageInfo(Option<ResolvedVc<BindingUsageInfo>>);
56
57#[turbo_tasks::value]
58pub struct ModuleExportUsage {
59 pub export_usage: ResolvedVc<ModuleExportUsageInfo>,
60 pub is_circuit_breaker: bool,
63 pub namespace_object_may_escape: bool,
66}
67#[turbo_tasks::value_impl]
68impl ModuleExportUsage {
69 #[turbo_tasks::function]
70 pub async fn unknown() -> Result<Vc<Self>> {
71 Ok(Self {
72 export_usage: ModuleExportUsageInfo::all().to_resolved().await?,
73 is_circuit_breaker: true,
74 namespace_object_may_escape: true,
75 }
76 .cell())
77 }
78}
79
80impl BindingUsageInfo {
81 pub async fn used_exports(
82 &self,
83 module: ResolvedVc<Box<dyn Module>>,
84 ) -> Result<Vc<ModuleExportUsage>> {
85 let is_circuit_breaker = self.export_circuit_breakers.contains_key(&module).await?;
86 let Some(exports) = self.used_exports.get(&module).await? else {
87 bail!(
88 "export usage not found for module: {:?}",
89 module.ident_string().await?
90 );
91 };
92 let namespace_object_may_escape =
93 self.partial_namespace_modules.contains_key(&module).await?;
94 Ok(ModuleExportUsage {
95 export_usage: (*exports).clone().resolved_cell(),
96 is_circuit_breaker,
97 namespace_object_may_escape,
98 }
99 .cell())
100 }
101}
102
103#[turbo_tasks::value_impl]
104impl BindingUsageInfo {
105 #[turbo_tasks::function]
106 pub fn unused_references(&self) -> Vc<UnusedReferences> {
107 *self.unused_references
108 }
109
110 #[turbo_tasks::function]
111 pub fn unused_references_edges(&self) -> Vc<UnusedReferenceEdges> {
112 *self.unused_references_edges
113 }
114}
115
116#[turbo_tasks::function(operation)]
117pub async fn compute_binding_usage_info(
118 graph: OperationVc<ModuleGraph>,
119 remove_unused_imports: bool,
120) -> Result<Vc<BindingUsageInfo>> {
121 let span_outer = tracing::info_span!(
122 "compute binding usage info",
123 visit_count = tracing::field::Empty,
124 unused_reference_count = tracing::field::Empty
125 );
126 let span = span_outer.clone();
127
128 async move {
129 let mut used_exports = FxHashMap::<_, ModuleExportUsageInfo>::default();
130 let mut partial_namespace_modules = FxHashSet::default();
131 #[cfg(debug_assertions)]
132 let mut debug_unused_references_name = FxHashSet::<(
133 ResolvedVc<Box<dyn Module>>,
134 ExportUsage,
135 ResolvedVc<Box<dyn Module>>,
136 )>::default();
137 let mut unused_references_edges = FxHashSet::default();
138 let mut unused_references =
139 FxHashMap::<_, FxHashSet<ResolvedVc<Box<dyn Module>>>>::default();
140
141 let graph = graph.connect();
142 let graph_ref = graph.await?;
143 if graph_ref.unused_references_edges.is_some() {
144 panic!(
156 "don't run compute_binding_usage_info on a graph after calling \
157 without_unused_references"
158 );
159 }
160 let side_effect_free_modules = if remove_unused_imports {
161 let side_effect_free_modules = compute_side_effect_free_module_info(graph).await?;
162 span.record("side_effect_free_modules", side_effect_free_modules.len());
163 Some(side_effect_free_modules)
164 } else {
165 None
166 };
167
168 let module_rank =
173 graph_ref.reverse_postorder_ranks(graph_ref.all_chunk_group_entry_modules())?;
174
175 let visit_count = graph_ref.traverse_edges_fixed_point_with_priority(
176 graph_ref
177 .all_chunk_group_entry_modules()
178 .map(|m| {
179 Ok((
180 m,
181 Reverse(*module_rank.get(&m).context("Module rank not found")?),
182 ))
183 })
184 .collect::<Result<Vec<_>>>()?,
185 &mut (),
186 |parent, target, _, _| {
187 let Some((parent, ref_data, edge)) = parent else {
189 used_exports.insert(target, ModuleExportUsageInfo::All);
190 return Ok(GraphTraversalAction::Continue);
191 };
192
193 if remove_unused_imports {
194 if matches!(&ref_data.binding_usage.export, ExportUsage::Evaluation)
198 && side_effect_free_modules
199 .as_ref()
200 .expect("this must be present if `remove_unused_imports` is true")
201 .contains(&target)
202 {
203 #[cfg(debug_assertions)]
204 debug_unused_references_name.insert((
205 parent,
206 ref_data.binding_usage.export.clone(),
207 target,
208 ));
209 unused_references_edges.insert(edge);
210 unused_references
211 .entry(ref_data.reference)
212 .or_default()
213 .insert(target);
214 return Ok(GraphTraversalAction::Skip);
215 }
216 match &ref_data.binding_usage.import {
218 ImportUsage::Exports(exports) => {
219 let source_used_exports = used_exports
220 .get(&parent)
221 .context("parent module must have usage info")?;
222 if exports
223 .iter()
224 .all(|e| !source_used_exports.is_export_used(e))
225 {
226 #[cfg(debug_assertions)]
228 debug_unused_references_name.insert((
229 parent,
230 ref_data.binding_usage.export.clone(),
231 target,
232 ));
233 unused_references_edges.insert(edge);
234 unused_references
235 .entry(ref_data.reference)
236 .or_default()
237 .insert(target);
238
239 return Ok(GraphTraversalAction::Skip);
240 } else {
241 #[cfg(debug_assertions)]
242 debug_unused_references_name.remove(&(
243 parent,
244 ref_data.binding_usage.export.clone(),
245 target,
246 ));
247 unused_references_edges.remove(&edge);
248 if let Entry::Occupied(mut e) =
249 unused_references.entry(ref_data.reference)
250 {
251 e.get_mut().remove(&target);
252 if e.get().is_empty() {
253 e.remove();
254 }
255 }
256 }
258 }
259 ImportUsage::TopLevel => {
260 #[cfg(debug_assertions)]
261 debug_unused_references_name.remove(&(
262 parent,
263 ref_data.binding_usage.export.clone(),
264 target,
265 ));
266 unused_references_edges.remove(&edge);
267 if let Entry::Occupied(mut e) =
268 unused_references.entry(ref_data.reference)
269 {
270 e.get_mut().remove(&target);
271 if e.get().is_empty() {
272 e.remove();
273 }
274 }
275 }
277 }
278 }
279
280 let is_first_visit = !used_exports.contains_key(&target);
281 let changed = match &ref_data.binding_usage.export {
282 ExportUsage::Passthrough {
283 namespace_object_may_escape,
284 } => {
285 let namespace_changed = if *namespace_object_may_escape
289 || partial_namespace_modules.contains(&parent)
290 {
291 partial_namespace_modules.insert(target)
292 } else {
293 false
294 };
295 let usage_changed = if parent == target {
296 false
299 } else {
300 used_exports.entry(target).or_default();
301 let [Some(parent_usage), Some(target_usage)] =
302 used_exports.get_disjoint_mut([&parent, &target])
303 else {
304 bail!("parent module must have usage info");
305 };
306 target_usage.add_usage_info(parent_usage)
307 };
308 namespace_changed || usage_changed
309 }
310 export_usage => {
311 let namespace_changed =
319 matches!(export_usage, ExportUsage::PartialNamespaceObject(_))
320 && partial_namespace_modules.insert(target);
321 let usage_changed =
322 used_exports.entry(target).or_default().add(export_usage);
323 namespace_changed || usage_changed
324 }
325 };
326 if changed || is_first_visit {
327 Ok(GraphTraversalAction::Continue)
330 } else {
331 Ok(GraphTraversalAction::Skip)
332 }
333 },
334 |successor, _| {
335 Ok(Reverse(
336 *module_rank
337 .get(&successor)
338 .context("Module rank not found")?,
339 ))
340 },
341 )?;
342
343 let mut export_circuit_breakers = FxHashSet::default();
356
357 graph_ref.traverse_cycles(
358 |e| e.chunking_type.is_parallel() && !unused_references.contains_key(&e.reference),
360 |cycle| {
361 export_circuit_breakers.extend(cycle.iter().map(|n| **n));
370 Ok(())
371 },
372 )?;
373
374 span.record("visit_count", visit_count);
375 span.record("unused_reference_count", unused_references.len());
376
377 #[cfg(debug_assertions)]
378 {
379 use std::sync::LazyLock;
380 static PRINT_UNUSED_REFERENCES: LazyLock<bool> = LazyLock::new(|| {
381 std::env::var_os("TURBOPACK_PRINT_UNUSED_REFERENCES")
382 .is_some_and(|v| v == "1" || v == "true")
383 });
384 if *PRINT_UNUSED_REFERENCES {
385 use turbo_tasks::TryJoinIterExt;
386 println!(
387 "unused references: {:#?}",
388 debug_unused_references_name
389 .iter()
390 .map(async |(s, e, t)| Ok((
391 s.ident_string().await?,
392 e,
393 t.ident_string().await?,
394 )))
395 .try_join()
396 .await?
397 );
398 }
399
400 static PRINT_USED_EXPORTS: LazyLock<bool> = LazyLock::new(|| {
401 std::env::var_os("TURBOPACK_PRINT_USED_EXPORTS")
402 .is_some_and(|v| v == "1" || v == "true")
403 });
404 if *PRINT_USED_EXPORTS {
405 use turbo_tasks::TryJoinIterExt;
406 println!(
407 "used exports: {:#?}",
408 used_exports
409 .iter()
410 .map(async |(m, v)| Ok((m.ident_string().await?, v,)))
411 .try_join()
412 .await?
413 );
414 }
415 }
416
417 Ok(BindingUsageInfo {
418 unused_references: ResolvedVc::cell(unused_references),
419 unused_references_edges: ResolvedVc::cell(unused_references_edges),
420 used_exports: ResolvedVc::cell(used_exports),
421 export_circuit_breakers: ResolvedVc::cell(export_circuit_breakers),
422 partial_namespace_modules: ResolvedVc::cell(partial_namespace_modules),
423 }
424 .cell())
425 }
426 .instrument(span_outer)
427 .await
428}
429
430#[turbo_tasks::value]
431#[derive(Default, Clone, Debug)]
432pub enum ModuleExportUsageInfo {
433 #[default]
435 Evaluation,
436 Exports(AutoSet<RcStr>),
437 All,
438}
439
440#[turbo_tasks::value_impl]
441impl ModuleExportUsageInfo {
442 #[turbo_tasks::function]
443 pub fn all() -> Vc<Self> {
444 ModuleExportUsageInfo::All.cell()
445 }
446}
447
448impl ModuleExportUsageInfo {
449 pub fn add(&mut self, usage: &ExportUsage) -> bool {
451 match (&mut *self, usage) {
452 (Self::All, _) => false,
453 (_, ExportUsage::All) => {
454 *self = Self::All;
455 true
456 }
457 (Self::Evaluation, ExportUsage::Named(name)) => {
458 *self = Self::Exports(AutoSet::from_iter([name.clone()]));
460 true
461 }
462 (Self::Evaluation, ExportUsage::PartialNamespaceObject(names)) => {
463 *self = Self::Exports(AutoSet::from_iter(names.iter().cloned()));
464 true
465 }
466 (Self::Exports(l), ExportUsage::Named(r)) => {
467 l.insert(r.clone())
469 }
470 (Self::Exports(l), ExportUsage::PartialNamespaceObject(names)) => {
471 let mut changed = false;
472 for name in names {
473 changed |= l.insert(name.clone());
474 }
475 changed
476 }
477 (_, ExportUsage::Evaluation) => false,
478 (_, ExportUsage::Passthrough { .. }) => {
479 *self = Self::All;
482 true
483 }
484 }
485 }
486
487 fn add_usage_info(&mut self, usage: &Self) -> bool {
489 match (&mut *self, usage) {
490 (Self::All, _) | (_, Self::Evaluation) => false,
491 (_, Self::All) => {
492 *self = Self::All;
493 true
494 }
495 (Self::Evaluation, Self::Exports(exports)) => {
496 *self = Self::Exports(exports.clone());
497 true
498 }
499 (Self::Exports(left), Self::Exports(right)) => {
500 let mut changed = false;
501 for export in right {
502 changed |= left.insert(export.clone());
503 }
504 changed
505 }
506 }
507 }
508
509 pub fn is_export_used(&self, export: &RcStr) -> bool {
510 match self {
511 Self::All => true,
512 Self::Evaluation => false,
513 Self::Exports(exports) => exports.contains(export),
514 }
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use turbo_rcstr::rcstr;
521
522 use super::ModuleExportUsageInfo;
523 use crate::resolve::ExportUsage;
524
525 #[test]
526 fn resolved_usage_join_is_monotonic() {
527 let mut usage = ModuleExportUsageInfo::Evaluation;
528 let first = ModuleExportUsageInfo::Exports([rcstr!("first")].into_iter().collect());
529 let second = ModuleExportUsageInfo::Exports([rcstr!("second")].into_iter().collect());
530
531 assert!(!usage.add_usage_info(&ModuleExportUsageInfo::Evaluation));
532 assert!(usage.add_usage_info(&first));
533 assert!(usage.is_export_used(&rcstr!("first")));
534 assert!(!usage.add_usage_info(&first));
535 assert!(usage.add_usage_info(&second));
536 assert!(usage.is_export_used(&rcstr!("second")));
537 assert!(!usage.add_usage_info(&ModuleExportUsageInfo::Evaluation));
538 assert!(usage.add_usage_info(&ModuleExportUsageInfo::All));
539 assert!(!usage.add_usage_info(&second));
540 assert!(matches!(usage, ModuleExportUsageInfo::All));
541 }
542
543 #[test]
544 fn unresolved_passthrough_falls_back_to_all() {
545 let mut usage = ModuleExportUsageInfo::Evaluation;
546
547 assert!(usage.add(&ExportUsage::Passthrough {
548 namespace_object_may_escape: false,
549 }));
550 assert!(matches!(usage, ModuleExportUsageInfo::All));
551 }
552}
553
554#[cfg(test)]
555mod graph_tests {
556 use anyhow::Result;
557 use rustc_hash::FxHashMap;
558 use turbo_rcstr::{RcStr, rcstr};
559 use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
560 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
561 use turbo_tasks_fs::{FileSystem, FileSystemPath, VirtualFileSystem};
562
563 use super::compute_binding_usage_info;
564 use crate::{
565 asset::{Asset, AssetContent},
566 ident::AssetIdent,
567 module::{Module, ModuleSideEffects},
568 module_graph::{
569 GraphEntries, ModuleGraph, SingleModuleGraph,
570 chunk_group_info::{ChunkGroupEntry, EntryHeuristics},
571 },
572 reference::{ModuleReference, ModuleReferences, SingleChunkableModuleReference},
573 resolve::ExportUsage,
574 };
575
576 type Edges = Vec<(RcStr, Vec<(RcStr, ExportUsage)>)>;
578
579 #[turbo_tasks::value(shared)]
580 struct UsageTestRepo {
581 edges: FxHashMap<RcStr, Vec<(RcStr, ExportUsage)>>,
582 }
583
584 #[turbo_tasks::value]
585 struct UsageTestModule {
586 root: FileSystemPath,
587 name: RcStr,
588 repo: ResolvedVc<UsageTestRepo>,
589 }
590
591 #[turbo_tasks::value_impl]
592 impl UsageTestModule {
593 #[turbo_tasks::function]
595 fn new(root: FileSystemPath, name: RcStr, repo: ResolvedVc<UsageTestRepo>) -> Vc<Self> {
596 Self { root, name, repo }.cell()
597 }
598 }
599
600 #[turbo_tasks::value_impl]
601 impl Asset for UsageTestModule {
602 #[turbo_tasks::function]
603 fn content(&self) -> Vc<AssetContent> {
604 panic!("UsageTestModule::content shouldn't be called")
605 }
606 }
607
608 #[turbo_tasks::value_impl]
609 impl Module for UsageTestModule {
610 #[turbo_tasks::function]
611 fn ident(&self) -> Result<Vc<AssetIdent>> {
612 Ok(AssetIdent::from_path(self.root.join(&self.name)?).into_vc())
613 }
614
615 #[turbo_tasks::function]
616 fn source(&self) -> Vc<crate::source::OptionSource> {
617 Vc::cell(None)
618 }
619
620 #[turbo_tasks::function]
621 async fn references(&self) -> Result<Vc<ModuleReferences>> {
622 let repo = self.repo.await?;
623 let references = repo
624 .edges
625 .get(&self.name)
626 .into_iter()
627 .flatten()
628 .map(async |(target, export)| {
629 let target =
630 UsageTestModule::new(self.root.clone(), target.clone(), *self.repo);
631 Ok(ResolvedVc::upcast::<Box<dyn ModuleReference>>(
632 SingleChunkableModuleReference::new(
633 Vc::upcast(target),
634 rcstr!("test-dep"),
635 export_usage_vc(export),
636 )
637 .to_resolved()
638 .await?,
639 ))
640 })
641 .try_join()
642 .await?;
643 Ok(Vc::cell(references))
644 }
645
646 #[turbo_tasks::function]
647 fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
648 ModuleSideEffects::SideEffectful.cell()
649 }
650 }
651
652 fn export_usage_vc(export: &ExportUsage) -> Vc<ExportUsage> {
654 match export {
655 ExportUsage::All => ExportUsage::all(),
656 ExportUsage::Named(name) => ExportUsage::named(name.clone()),
657 ExportUsage::PartialNamespaceObject(names) => {
658 ExportUsage::partial_namespace_object(names.to_vec())
659 }
660 ExportUsage::Evaluation => ExportUsage::evaluation(),
661 ExportUsage::Passthrough {
662 namespace_object_may_escape,
663 } => ExportUsage::passthrough(*namespace_object_may_escape),
664 }
665 }
666
667 #[turbo_tasks::value(transparent)]
669 struct NamespaceEscapes(Vec<(RcStr, bool)>);
670
671 #[turbo_tasks::function(operation, root)]
672 async fn namespace_escapes_operation(
673 namespace_path_is_longer: bool,
674 plain_usage_is_all: bool,
675 ) -> Result<Vc<NamespaceEscapes>> {
676 let plain_usage = if plain_usage_is_all {
677 ExportUsage::All
678 } else {
679 ExportUsage::Named(rcstr!("a"))
680 };
681 let edges = namespace_graph(namespace_path_is_longer, plain_usage);
682 let root = VirtualFileSystem::new_with_name(rcstr!("test"))
683 .root()
684 .owned()
685 .await?;
686 let names = edges
687 .iter()
688 .flat_map(|(from, to)| {
689 std::iter::once(from.clone()).chain(to.iter().map(|(t, _)| t.clone()))
690 })
691 .collect::<std::collections::BTreeSet<_>>();
692 let repo = UsageTestRepo {
693 edges: edges.into_iter().collect(),
694 }
695 .resolved_cell();
696 let module = async |name: &RcStr| -> Result<ResolvedVc<Box<dyn Module>>> {
697 Ok(ResolvedVc::upcast(
698 UsageTestModule::new(root.clone(), name.clone(), *repo)
699 .to_resolved()
700 .await?,
701 ))
702 };
703
704 let single_graph = SingleModuleGraph::new_with_entries(
705 GraphEntries::resolved_cell(GraphEntries::new(
706 vec![ChunkGroupEntry::Entry {
707 modules: vec![module(&rcstr!("entry")).await?],
708 heuristics: EntryHeuristics::default(),
709 }],
710 vec![],
711 )),
712 false,
713 true,
714 );
715 let graph = ModuleGraph::from_graphs(vec![single_graph], None);
716 let binding_usage = compute_binding_usage_info(graph, false).connect().await?;
717
718 let escapes = names
719 .iter()
720 .map(async |name| {
721 let usage = binding_usage
722 .used_exports(module(name).await?)
723 .await?
724 .await?;
725 Ok((name.clone(), usage.namespace_object_may_escape))
726 })
727 .try_join()
728 .await?;
729 Ok(Vc::cell(escapes))
730 }
731
732 async fn namespace_escapes(
733 namespace_path_is_longer: bool,
734 plain_usage_is_all: bool,
735 ) -> Vec<(RcStr, bool)> {
736 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
737 BackendOptions::default(),
738 noop_backing_storage(),
739 ));
740 tt.run_once(async move {
741 let escapes = namespace_escapes_operation(namespace_path_is_longer, plain_usage_is_all)
742 .read_strongly_consistent()
743 .await?;
744 Ok((*escapes).clone())
745 })
746 .await
747 .unwrap()
748 }
749
750 fn namespace_a() -> ExportUsage {
751 ExportUsage::PartialNamespaceObject([rcstr!("a")].into_iter().collect())
752 }
753
754 fn reexport_all() -> ExportUsage {
755 ExportUsage::Passthrough {
757 namespace_object_may_escape: false,
758 }
759 }
760
761 fn namespace_graph(namespace_path_is_longer: bool, plain_usage: ExportUsage) -> Edges {
768 let (direct_usage, late_usage) = if namespace_path_is_longer {
769 (plain_usage, namespace_a())
770 } else {
771 (namespace_a(), plain_usage)
772 };
773 vec![
774 (
775 rcstr!("entry"),
776 vec![
777 (rcstr!("x"), direct_usage),
778 (rcstr!("m1"), ExportUsage::All),
779 ],
780 ),
781 (rcstr!("m1"), vec![(rcstr!("m2"), ExportUsage::All)]),
782 (rcstr!("m2"), vec![(rcstr!("x"), late_usage)]),
783 (rcstr!("x"), vec![(rcstr!("y"), reexport_all())]),
784 ]
785 }
786
787 fn expected_escapes() -> Vec<(RcStr, bool)> {
788 vec![
789 (rcstr!("entry"), false),
790 (rcstr!("m1"), false),
791 (rcstr!("m2"), false),
792 (rcstr!("x"), true),
793 (rcstr!("y"), true),
794 ]
795 }
796
797 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
798 async fn late_namespace_usage_of_all_module_reaches_passthrough_target() {
799 assert_eq!(namespace_escapes(true, true).await, expected_escapes());
800 }
801
802 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
803 async fn late_namespace_usage_of_already_used_name_reaches_passthrough_target() {
804 assert_eq!(namespace_escapes(true, false).await, expected_escapes());
805 }
806
807 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
808 async fn early_namespace_usage_of_all_module_reaches_passthrough_target() {
809 assert_eq!(namespace_escapes(false, true).await, expected_escapes());
810 }
811
812 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
813 async fn early_namespace_usage_of_already_used_name_reaches_passthrough_target() {
814 assert_eq!(namespace_escapes(false, false).await, expected_escapes());
815 }
816}